initial production release 🎉

This commit is contained in:
Andras
2021-03-24 22:11:14 +01:00
commit dbe82b3e7c
101 changed files with 12479 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
<script>
import { application} from "@store";
</script>
<div class="grid grid-cols-1 space-y-2 max-w-2xl md:mx-auto mx-6 text-center">
<label for="buildCommand">Build Command</label>
<input
id="buildCommand"
bind:value="{$application.build.command.build}"
placeholder="eg: yarn build"
/>
<label for="installCommand">Install Command</label>
<input
id="installCommand"
bind:value="{$application.build.command.installation}"
placeholder="eg: yarn install"
/>
<label for="baseDir">Base Directory</label>
<input id="baseDir" bind:value="{$application.build.directory}" placeholder="/" />
</div>

View File

@@ -0,0 +1,106 @@
<script>
import { application } from "@store";
</script>
<div>
<div
class="grid grid-cols-1 text-sm space-y-2 max-w-2xl md:mx-auto mx-6 pb-6 auto-cols-max"
>
<label for="buildPack">Build Pack</label>
<select id="buildPack" bind:value="{$application.build.pack}">
<option selected class="font-medium">static</option>
<option class="font-medium">nodejs</option>
</select>
</div>
<div
class="grid grid-cols-2 space-y-2 max-w-2xl md:mx-auto mx-6 justify-center items-center"
>
<label for="Domain">Domain</label>
<input
class:placeholder-red-500="{$application.publish.domain == null || $application.publish.domain == ''}"
class:border-red-500="{$application.publish.domain == null || $application.publish.domain == ''}"
id="Domain"
bind:value="{$application.publish.domain}"
placeholder="eg: coollabs.io (without www)"
/>
<label for="Path">Path Prefix</label>
<input
id="Path"
bind:value="{$application.publish.path}"
placeholder="/"
/>
<label for="publishDir">Publish Directory</label>
<input
id="publishDir"
bind:value="{$application.publish.directory}"
placeholder="/"
/>
{#if $application.build.pack !== "static"}
<label for="Port">Port</label>
<input
id="Port"
bind:value="{$application.publish.port}"
placeholder="{$application.build.pack === 'static'
? '80'
: '3000'}"
/>
{/if}
<!-- {#if config.buildPack === "static"}
<div class="text-base font-bold text-white pt-2">
Preview Deploys
</div>
<button
type="button"
on:click="{() =>
(config.previewDeploy = !config.previewDeploy)}"
aria-pressed="false"
class="relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black"
class:bg-green-600="{config.previewDeploy}"
class:bg-coolgray-300="{!config.previewDeploy}"
>
<span class="sr-only">Use setting</span>
<span
class="pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"
class:translate-x-5="{config.previewDeploy}"
class:translate-x-0="{!config.previewDeploy}"
>
<span
class="ease-in duration-200 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
class:opacity-0="{config.previewDeploy}"
class:opacity-100="{!config.previewDeploy}"
aria-hidden="true"
>
<svg
class="bg-white h-3 w-3 text-red-600"
fill="none"
viewBox="0 0 12 12"
>
<path
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"></path>
</svg>
</span>
<span
class="ease-out duration-100 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
aria-hidden="true"
class:opacity-100="{config.previewDeploy}"
class:opacity-0="{!config.previewDeploy}"
>
<svg
class="bg-white h-3 w-3 text-green-600"
fill="currentColor"
viewBox="0 0 12 12"
>
<path
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
></path>
</svg>
</span>
</span>
</button>
{/if} -->
</div>
</div>

View File

@@ -0,0 +1,72 @@
<script>
import { application } from "@store";
let secret = {
name: null,
value: null,
};
let foundSecret = null;
async function saveSecret() {
if (secret.name && secret.value) {
const found = $application.publish.secrets.find(
s => s.name === secret.name,
);
if (!found) {
$application.publish.secrets = [
...$application.publish.secrets,
{
name: secret.name,
value: secret.value,
},
];
secret = {
name: null,
value: null
}
} else {
foundSecret = found;
}
}
}
async function removeSecret(name) {
$application.publish.secrets = [...$application.publish.secrets.filter(s => s.name !== name)]
}
</script>
<div class="space-y-2 max-w-2xl md:mx-auto mx-6 text-center">
<div class="text-left text-base font-bold tracking-tight text-warmGray-400">New Secret</div>
<div class="grid md:grid-flow-col grid-flow-row gap-2">
<input id="secretName" bind:value="{secret.name}" placeholder="Name" />
<input id="secretValue" bind:value="{secret.value}" placeholder="Value" />
<button
class="button p-1 w-20 bg-green-600 hover:bg-green-500 text-white"
on:click="{saveSecret}">Save</button
>
</div>
{#if $application.publish.secrets.length > 0}
{#each $application.publish.secrets as s}
<div class="grid md:grid-flow-col grid-flow-row gap-2">
<input
id="{s.name}"
value="{s.name}"
disabled
class="bg-transparent border-transparent"
class:border-red-600="{foundSecret && foundSecret.name === s.name}"
/>
<input
id="{s.createdAt}"
value="ENCRYPTED"
disabled
class="bg-transparent border-transparent"
/>
<button
class="button w-20 bg-red-600 hover:bg-red-500 text-white"
on:click="{() => removeSecret(s.name)}">Delete</button
>
</div>
{/each}
{/if}
</div>

View File

@@ -0,0 +1,24 @@
<script>
export let loading, branches;
import { application } from "@store";
</script>
{#if loading}
<div class="grid grid-cols-1">
<label for="branch">Branch</label>
<select disabled>
<option selected>Loading branches</option>
</select>
</div>
{:else}
<div class="grid grid-cols-1">
<label for="branch">Branch</label>
<!-- svelte-ignore a11y-no-onchange -->
<select id="branch" bind:value="{$application.repository.branch}">
<option disabled selected>Select a branch</option>
{#each branches as branch}
<option value="{branch.name}" class="font-medium">{branch.name}</option>
{/each}
</select>
</div>
{/if}

View File

@@ -0,0 +1,133 @@
<script>
import { redirect, isActive } from "@roxi/routify";
import { fade } from "svelte/transition";
import { session, application, fetch, initialApplication } from "@store";
import Login from "./Login.svelte";
import Loading from "../../Loading.svelte";
import Repositories from "./Repositories.svelte";
import Branches from "./Branches.svelte";
import Tabs from "./Tabs.svelte";
let loading = {
branches: false,
};
let branches = [];
let repositories = [];
async function loadBranches() {
loading.branches = true;
const selectedRepository = repositories.find(
r => r.id === $application.repository.id,
);
if (selectedRepository) {
$application.repository.organization = selectedRepository.owner.login;
$application.repository.name = selectedRepository.name;
}
branches = await $fetch(
`https://api.github.com/repos/${$application.repository.organization}/${$application.repository.name}/branches`,
);
loading.branches = false;
}
async function loadGithub() {
try {
const { installations } = await $fetch(
"https://api.github.com/user/installations",
);
if (installations.length === 0) {
return false;
}
$application.github.installation.id = installations[0].id;
$application.github.app.id = installations[0].app_id;
const data = await $fetch(
`https://api.github.com/user/installations/${$application.github.installation.id}/repositories?per_page=10000`,
);
repositories = data.repositories;
const foundRepositoryOnGithub = data.repositories.find(
r =>
r.full_name ===
`${$application.repository.organization}/${$application.repository.name}`,
);
if (foundRepositoryOnGithub) {
$application.repository.id = foundRepositoryOnGithub.id;
await loadBranches();
}
} catch (error) {
return false;
}
}
function modifyGithubAppConfig() {
const left = screen.width / 2 - 1020 / 2;
const top = screen.height / 2 - 618 / 2;
const newWindow = open(
`https://github.com/apps/${
import.meta.env.VITE_GITHUB_APP_NAME
}/installations/new`,
"Install App",
"resizable=1, scrollbars=1, fullscreen=0, height=1000, width=1020,top=" +
top +
", left=" +
left +
", toolbar=0, menubar=0, status=0",
);
const timer = setInterval(async () => {
if (newWindow.closed) {
clearInterval(timer);
if (!$isActive("/application/new")) {
try {
const config = await $fetch(`/api/v1/config`, {
body: {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
},
});
$application = { ...config };
} catch (error) {
$redirect("/dashboard/applications");
}
} else {
$application = JSON.parse(JSON.stringify(initialApplication));
}
branches = [];
repositories = [];
await loadGithub();
}
}, 100);
}
</script>
<div in:fade="{{ duration: 100 }}">
{#if !$session.githubAppToken}
<Login />
{:else}
{#await loadGithub()}
<Loading />
{:then}
<div
class="text-center space-y-2 max-w-4xl mx-auto px-6"
in:fade="{{ duration: 100 }}"
>
<Repositories
bind:repositories
on:loadBranches="{loadBranches}"
on:modifyGithubAppConfig="{modifyGithubAppConfig}"
/>
{#if $application.repository.organization !== "new"}
<Branches loading="{loading.branches}" branches="{branches}" />
{/if}
{#if $application.repository.branch}
<Tabs />
{/if}
</div>
{/await}
{/if}
</div>

View File

@@ -0,0 +1,50 @@
<script>
import { session } from "@store";
function login() {
const left = screen.width / 2 - 1020 / 2;
const top = screen.height / 2 - 618 / 2;
const newWindow = open(
`https://github.com/login/oauth/authorize?client_id=${
import.meta.env.VITE_GITHUB_APP_CLIENTID
}`,
"Authenticate",
"resizable=1, scrollbars=1, fullscreen=0, height=618, width=1020,top=" +
top +
", left=" +
left +
", toolbar=0, menubar=0, status=0",
);
const timer = setInterval(() => {
if (newWindow.closed) {
clearInterval(timer);
const ghToken = new URL(newWindow.document.URL).searchParams.get(
"ghToken",
);
if (ghToken) {
$session.githubAppToken = ghToken;
}
}
}, 100);
}
</script>
<div class="text-center text-white">
<div class="text-2xl font-bold text-center pb-4">
Choose your Git provider
</div>
<button on:click="{login}" class="hover:scale-110 transform duration-100 transition">
<svg
class="w-16"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"
></path></svg
>
</button>
</div>

View File

@@ -0,0 +1,45 @@
<script>
import { createEventDispatcher } from "svelte";
import { isActive } from "@roxi/routify";
import { application } from "@store";
export let repositories;
const dispatch = createEventDispatcher();
const loadBranches = () => dispatch("loadBranches");
const modifyGithubAppConfig = () => dispatch("modifyGithubAppConfig");
</script>
<div class="grid grid-cols-1">
{#if repositories.length !== 0}
<label for="repository">Organization / Repository</label>
<div class="grid grid-cols-3">
<!-- svelte-ignore a11y-no-onchange -->
<select
id="repository"
class:cursor-not-allowed="{!$isActive('/application/new')}"
class="col-span-2"
bind:value="{$application.repository.id}"
on:change="{loadBranches}"
disabled="{!$isActive('/application/new')}"
>
<option selected disabled>Select a repository</option>
{#each repositories as repo}
<option value="{repo.id}" class="font-medium">
{repo.owner.login}
/
{repo.name}
</option>
{/each}
</select>
<button
class="button col-span-1 ml-2 bg-warmGray-800 hover:bg-warmGray-700 text-white"
on:click="{modifyGithubAppConfig}">Configure on Github</button
>
</div>
{:else}
<button
class="button col-span-1 ml-2 bg-warmGray-800 hover:bg-warmGray-700 text-white"
on:click="{modifyGithubAppConfig}">Add repositories on Github</button
>
{/if}
</div>

View File

@@ -0,0 +1,97 @@
<script>
import { redirect, isActive } from "@roxi/routify";
import { application, fetch, deployments } from "@store";
import General from "./ActiveTab/General.svelte";
import BuildStep from "./ActiveTab/BuildStep.svelte";
import Secrets from "./ActiveTab/Secrets.svelte";
import { onMount } from "svelte";
onMount(async () => {
if (!$isActive("/application/new")) {
const config = await $fetch(`/api/v1/config`, {
body: {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
},
});
$application = { ...config };
$redirect(`/application/:organization/:name/:branch/configuration`, {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
});
} else {
$deployments.applications.deployed.filter(d => {
const conf = d?.Spec?.Labels.application;
if (
conf.repository.organization ===
$application.repository.organization &&
conf.repository.name === $application.repository.name &&
conf.repository.branch === $application.repository.branch
) {
$redirect(`/application/:organization/:name/:branch/configuration`, {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
});
}
});
}
});
let activeTab = {
general: true,
buildStep: false,
secrets: false,
};
function activateTab(tab) {
if (activeTab.hasOwnProperty(tab)) {
activeTab = {
general: false,
buildStep: false,
secrets: false,
};
activeTab[tab] = true;
}
}
</script>
<div class="block text-center py-4">
<nav
class="flex space-x-4 justify-center font-bold text-md text-white"
aria-label="Tabs"
>
<div
on:click="{() => activateTab('general')}"
class:text-green-500="{activeTab.general}"
class="px-3 py-2 cursor-pointer hover:text-green-500"
>
General
</div>
<div
on:click="{() => activateTab('buildStep')}"
class:text-green-500="{activeTab.buildStep}"
class="px-3 py-2 cursor-pointer hover:text-green-500"
>
Build Step
</div>
<div
on:click="{() => activateTab('secrets')}"
class:text-green-500="{activeTab.secrets}"
class="px-3 py-2 cursor-pointer hover:text-green-500"
>
Secrets
</div>
</nav>
</div>
<div class="max-w-4xl mx-auto">
<div class="h-full">
{#if activeTab.general}
<General />
{:else if activeTab.buildStep}
<BuildStep />
{:else if activeTab.secrets}
<Secrets />
{/if}
</div>
</div>