Just v2
This commit is contained in:
Andras Bacsai
2022-02-10 15:47:44 +01:00
committed by GitHub
parent a64b095c13
commit 460ae85226
403 changed files with 22039 additions and 12465 deletions

View File

@@ -0,0 +1,41 @@
<div class="lds-ripple absolute left-0">
<div />
<div />
</div>
<style>
.lds-ripple {
display: inline-block;
position: relative;
left: -19px;
top: -8px;
width: 40px;
height: 40px;
}
.lds-ripple div {
position: absolute;
border: 4px solid #fff;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.lds-ripple div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes lds-ripple {
0% {
top: 1px;
left: 1px;
width: 0;
height: 0;
opacity: 1;
}
100% {
top: 0px;
left: 0px;
width: 36px;
height: 36px;
opacity: 0;
}
}
</style>

View File

@@ -0,0 +1,75 @@
<script lang="ts">
export let buildId;
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
const dispatch = createEventDispatcher();
import { page } from '$app/stores';
import Loading from '$lib/components/Loading.svelte';
import LoadingLogs from '../_Loading.svelte';
import { get } from '$lib/api';
import { errorNotification } from '$lib/form';
let logs = [];
let loading = true;
let currentStatus;
let streamInterval;
const { id } = $page.params;
async function streamLogs(sequence = 0) {
try {
let { logs: responseLogs, status } = await get(
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${sequence}`
);
currentStatus = status;
logs = logs.concat(responseLogs);
loading = false;
streamInterval = setInterval(async () => {
if (status !== 'running') {
clearInterval(streamInterval);
return;
}
const nextSequence = logs[logs.length - 1].time;
try {
const data = await get(
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${nextSequence}`
);
status = data.status;
currentStatus = status;
logs = logs.concat(data.logs);
dispatch('updateBuildStatus', { status });
} catch ({ error }) {
return errorNotification(error);
}
}, 1000);
} catch ({ error }) {
return errorNotification(error);
}
}
onDestroy(() => {
clearInterval(streamInterval);
});
onMount(async () => {
window.scrollTo(0, 0);
await streamLogs();
});
</script>
{#if loading}
<Loading />
{:else}
<div class="relative">
{#if currentStatus === 'running'}
<LoadingLogs />
{/if}
<div
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words"
>
{#each logs as log}
<div>{log.line + '\n'}</div>
{/each}
</div>
</div>
{/if}

View File

@@ -0,0 +1,28 @@
import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const buildId = event.url.searchParams.get('buildId');
const sequence = Number(event.url.searchParams.get('sequence'));
try {
let logs = await db.prisma.buildLog.findMany({
where: { buildId, time: { gt: sequence } },
orderBy: { time: 'asc' }
});
const { status } = await db.prisma.build.findFirst({ where: { id: buildId } });
return {
body: {
logs,
status
}
};
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,40 @@
import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import { dayjs } from '$lib/dayjs';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { id } = event.params;
const buildId = event.url.searchParams.get('buildId');
const skip = Number(event.url.searchParams.get('skip')) || 0;
let builds = [];
try {
const buildCount = await db.prisma.build.count({ where: { applicationId: id } });
if (buildId) {
builds = await db.prisma.build.findMany({ where: { applicationId: id, id: buildId } });
} else {
builds = await db.prisma.build.findMany({
where: { applicationId: id },
orderBy: { createdAt: 'desc' },
take: 5,
skip
});
}
builds = builds.map((build) => {
const updatedAt = dayjs(build.updatedAt).utc();
build.took = updatedAt.diff(dayjs(build.createdAt)) / 1000;
build.since = updatedAt.fromNow();
return build;
});
return {
status: 200,
body: {
builds,
buildCount
}
};
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,143 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
let endpoint = `/applications/${params.id}/logs/build.json?skip=0`;
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
application: stuff.application,
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
import { page } from '$app/stores';
import { dateOptions, getDomain } from '$lib/components/common';
import BuildLog from './_BuildLog.svelte';
import { get } from '$lib/api';
import { errorNotification } from '$lib/form';
import { goto } from '$app/navigation';
export let builds;
export let application;
export let buildCount;
let buildId;
$: buildId;
let skip = 0;
let noMoreBuilds = buildCount < 5 || buildCount <= skip;
const { id } = $page.params;
let preselectedBuildId = $page.url.searchParams.get('buildId');
if (preselectedBuildId) buildId = preselectedBuildId;
async function updateBuildStatus({ detail }) {
const { status } = detail;
if (status !== 'running') {
try {
const data = await get(`/applications/${id}/logs/build.json?buildId=${buildId}`);
builds = builds.filter((build) => {
if (build.id === data.builds[0].id) {
build.status = data.builds[0].status;
build.took = data.builds[0].took;
build.since = data.builds[0].since;
}
window.location.reload();
return build;
});
return;
} catch ({ error }) {
return errorNotification(error);
}
} else {
builds = builds.filter((build) => {
if (build.id === buildId) build.status = status;
return build;
});
}
}
async function loadMoreBuilds() {
if (buildCount >= skip) {
skip = skip + 5;
noMoreBuilds = buildCount >= skip;
try {
const data = await get(`/applications/${id}/logs/build.json?skip=${skip}`);
builds = builds.concat(data.builds);
return;
} catch ({ error }) {
return errorNotification(error);
}
} else {
noMoreBuilds = true;
}
}
async function loadBuild(build) {
buildId = build;
goto(`/applications/${id}/logs/build?buildId=${buildId}`);
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">
Build logs of <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
</div>
</div>
<div class="flex flex-row justify-start space-x-2 px-10 pt-6 ">
<div class="min-w-[16rem] space-y-2">
{#each builds as build (build.id)}
<div
data-tooltip={new Intl.DateTimeFormat('default', dateOptions).format(
new Date(build.createdAt)
) + `\n${build.status}`}
on:click={() => loadBuild(build.id)}
class="tooltip-top flex cursor-pointer items-center justify-center rounded-r border-l-2 border-transparent py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl"
class:bg-coolgray-400={buildId === build.id}
class:border-red-500={build.status === 'failed'}
class:border-green-500={build.status === 'success'}
class:border-yellow-500={build.status === 'inprogress'}
>
<div class="flex-col px-2">
<div class="text-sm font-bold">
{application.branch}
</div>
<div class="text-xs">
{build.type}
</div>
</div>
<div class="flex-1" />
<div class="w-48 text-center text-xs">
{#if build.status === 'running'}
<div class="font-bold">Running</div>
{:else}
<div>{build.since}</div>
<div>Finished in <span class="font-bold">{build.took}s</span></div>
{/if}
</div>
</div>
{/each}
{#if buildCount > 0 && !noMoreBuilds}
<button class="w-full" on:click={loadMoreBuilds}>Load More</button>
{/if}
</div>
<div class="w-96 flex-1">
{#if buildId}
{#key buildId}
<svelte:component this={BuildLog} {buildId} on:updateBuildStatus={updateBuildStatus} />
{/key}
{/if}
</div>
</div>
{#if buildCount === 0}
<div class="text-center text-xl font-bold">No logs found</div>
{/if}

View File

@@ -0,0 +1,53 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import { dayjs } from '$lib/dayjs';
import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const { destinationDockerId, destinationDocker } = await db.prisma.application.findUnique({
where: { id },
include: { destinationDocker: true }
});
if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker });
try {
const container = await docker.engine.getContainer(id);
if (container) {
return {
body: {
logs: (await container.logs({ stdout: true, stderr: true, timestamps: true }))
.toString()
.split('\n')
.map((l) => l.slice(8))
.filter((a) => a)
}
};
}
} catch (error) {
const { statusCode } = error;
if (statusCode === 404) {
return {
body: {
logs: []
}
};
}
}
}
return {
status: 200,
body: {
message: 'No logs found.'
}
};
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,75 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
import { onDestroy, onMount } from 'svelte';
export const load: Load = async ({ fetch, params, url, stuff }) => {
let endpoint = `/applications/${params.id}/logs.json`;
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
application: stuff.application,
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${url}`)
};
};
</script>
<script lang="ts">
export let application;
import { page } from '$app/stores';
import LoadingLogs from './_Loading.svelte';
import { getDomain } from '$lib/components/common';
import { get } from '$lib/api';
import { errorNotification } from '$lib/form';
let loadLogsInterval = null;
let logs = [];
const { id } = $page.params;
onMount(async () => {
loadLogs();
loadLogsInterval = setInterval(() => {
loadLogs();
}, 3000);
});
onDestroy(() => {
clearInterval(loadLogsInterval);
});
async function loadLogs() {
try {
const newLogs = await get(`/applications/${id}/logs.json`);
logs = newLogs.logs;
return;
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">
Application logs of <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
</div>
</div>
<div class="flex flex-row justify-center space-x-2 px-10 pt-6">
{#if logs.length === 0}
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
{:else}
<div class="relative w-full">
<LoadingLogs />
<div
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 p-6 whitespace-pre-wrap break-words w-full"
>
{#each logs as log}
{log + '\n'}
{/each}
</div>
</div>
{/if}
</div>