{#each builds as build}
- {@const status = build.result || build.status}
-
+
#{build.id}
- {#if status}
- {@const { icon } = getStatusIcon(status)}
-
- {#if icon !== Icons.Unknown}
-
- {/if}
- {status}
-
- {/if}
+
-
diff --git a/src/routes/(ui)/build-admin/update/+page.server.ts b/src/routes/(ui)/build-admin/update/+page.server.ts
deleted file mode 100644
index 8d83ea62..00000000
--- a/src/routes/(ui)/build-admin/update/+page.server.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { SpanStatusCode, trace } from '@opentelemetry/api';
-import { error, redirect } from '@sveltejs/kit';
-import { fail, superValidate } from 'sveltekit-superforms';
-import { valibot } from 'sveltekit-superforms/adapters';
-import * as v from 'valibot';
-import type { Actions, PageServerLoad } from './$types';
-import { prisma } from '$lib/server/prisma';
-import {
- convertEmptyStrToNull,
- idSchema,
- paramNumber,
- selectFrom,
- stringIdSchema,
- stringLimits
-} from '$lib/valibot';
-
-const buildSchema = v.object({
- job_id: idSchema,
- status: v.pipe(convertEmptyStrToNull(stringLimits.build.status)),
- build_guid: v.pipe(convertEmptyStrToNull(), v.nullable(stringIdSchema)),
- result: convertEmptyStrToNull(stringLimits.build.result),
- error: v.pipe(
- convertEmptyStrToNull(stringLimits.build.error),
- v.nullable(v.pipe(v.string(), v.url()))
- ),
- artifact_url_base: v.pipe(
- convertEmptyStrToNull(stringLimits.build.artifact_url_base),
- v.nullable(v.pipe(v.string(), v.url()))
- ),
- artifact_files: convertEmptyStrToNull(stringLimits.build.artifact_files),
- channel: convertEmptyStrToNull(stringLimits.build.channel),
- version_code: v.nullable(idSchema),
- targets: convertEmptyStrToNull(stringLimits.build.targets),
- environment: convertEmptyStrToNull()
-});
-
-export const load = (async ({ url }) => {
- const id = v.safeParse(v.pipe(paramNumber, idSchema), url.searchParams.get('id'));
- if (!id.success) {
- error(400, `missing id param`);
- }
-
- const build = await prisma.build.findUnique({
- where: {
- id: id.output
- },
- select: selectFrom(buildSchema.entries)
- });
-
- if (!build) error(404);
-
- return {
- form: await superValidate(build, valibot(buildSchema))
- };
-}) satisfies PageServerLoad;
-
-export const actions: Actions = {
- default: async function ({ request, url }) {
- const id = v.safeParse(v.pipe(paramNumber, idSchema), url.searchParams.get('id'));
- if (!id.success) {
- error(400, `missing id param`);
- }
- const form = await superValidate(request, valibot(buildSchema));
- if (!form.valid) return fail(400, { form, ok: false });
-
- let build = { id: 0 };
-
- try {
- build = await prisma.build.update({
- where: {
- id: id.output
- },
- data: form.data
- });
- } catch (e) {
- trace.getActiveSpan()?.recordException(e as Error);
- trace.getActiveSpan()?.setStatus({
- code: SpanStatusCode.ERROR,
- message: (e as Error).message
- });
- return error(400, (e as Error).message);
- }
-
- redirect(303, `/build-admin/view?id=${build.id}`);
- }
-};
diff --git a/src/routes/(ui)/build-admin/update/+page.svelte b/src/routes/(ui)/build-admin/update/+page.svelte
deleted file mode 100644
index 312f6981..00000000
--- a/src/routes/(ui)/build-admin/update/+page.svelte
+++ /dev/null
@@ -1,133 +0,0 @@
-
-
-
- Home
- Builds
- {id}
- Update
-
-
{$title}
-
-
-
-
diff --git a/src/routes/(ui)/build-admin/view/+page.server.ts b/src/routes/(ui)/build-admin/view/+page.server.ts
index ab868aca..2cc147b9 100644
--- a/src/routes/(ui)/build-admin/view/+page.server.ts
+++ b/src/routes/(ui)/build-admin/view/+page.server.ts
@@ -14,6 +14,14 @@ export const load = (async ({ url }) => {
const build = await prisma.build.findUnique({
where: {
id: id.output
+ },
+ include: {
+ job: {
+ select: {
+ client: { select: { development: true } },
+ git_url: true
+ }
+ }
}
});
diff --git a/src/routes/(ui)/build-admin/view/+page.svelte b/src/routes/(ui)/build-admin/view/+page.svelte
index 8e77dc71..c672a725 100644
--- a/src/routes/(ui)/build-admin/view/+page.svelte
+++ b/src/routes/(ui)/build-admin/view/+page.svelte
@@ -1,9 +1,17 @@
@@ -20,84 +30,151 @@
{data.build.id}
-
{$title}
+
+
+ Build {data.build.id}
+
-
+
+ Created
+ {$dateCreated}
+
+
-
-
-
- | ID |
- {data.build.id} |
-
-
- | Job ID |
-
- {data.build.job_id}
- |
-
-
- | Status |
- {data.build.status} |
-
-
- | Build GUID |
- {data.build.build_guid} |
-
-
- | Result |
- {data.build.result} |
-
-
- | Error |
-
- {#if data.build.error?.match(/^https?:/)}
-
- {data.build.error}
-
+
+
+
+
+
+
+ Targets:
+
+
+ {data.build.targets || '(none)'}
+
+
+
+
+
+ Channel:
+
+
+ {data.build.channel || '(none)'}
+
+
+
+
+
+ Version Code:
+
+
+ {data.build.version_code ?? '(none)'}
+
+
+
+ {#if data.build.codebuild_url || data.build.build_guid}
+
+
+ Code Build:
+
+
+ {#if data.build.codebuild_url}
+ {data.build.build_guid}
{:else}
- {data.build.error}
+ {data.build.build_guid}
{/if}
- |
-
-
- | Artifacts |
-
- {#if data.build.artifact_files}
- {#each Object.entries(data.artifacts)
- .filter(([_, url]) => !!url)
- .sort(([a, _1], [b, _2]) => a.localeCompare(b, 'en-US')) as [name, url]}
- {name}
- ,
- {/each}
- {/if}
- |
-
-
- | Created |
- {getTimeDateString(data.build.created)} |
-
-
- | Updated |
- {getTimeDateString(data.build.updated)} |
-
-
- | Channel |
- {data.build.channel} |
-
-
- | Version Code |
- {data.build.version_code} |
-
-
- | Targets |
- {data.build.targets} |
-
-
- | Environment |
- {data.build.environment} |
-
-
-
+
+
+ {/if}
+ {#if data.build.error}
+ {@const isURL = !!data.build.error?.match(/^https?:/)}
+
+ {/if}
+ {#if data.build.artifact_files}
+ {@const artifacts = Object.entries(data.artifacts)
+ .filter(([_, url]) => !!url)
+ .sort(([a, _1], [b, _2]) => byString(a, b))}
+
+
Artifacts:
+
+ {#each artifacts as [name, url], i}
+ {name}
+ {#if i + 1 < artifacts.length}
+ ,
+ {/if}
+ {/each}
+
+
+ {/if}
+ {#if data.build.environment}
+ {@const parsed = safeParse(JSON2Entries, data.build.environment)}
+ {#if parsed.success && parsed.output?.length}
+
+
Environment:
+
+
+ {#each parsed.output as [key, value]}
+
+ | {key}: |
+ {value} |
+
+
+ | {key}: |
+
+
+ | {value} |
+
+ {/each}
+
+
+
+ {/if}
+ {/if}
+
+
+
diff --git a/src/routes/(ui)/client-admin/+page.server.ts b/src/routes/(ui)/client-admin/+page.server.ts
index 4ff8af89..b63a0176 100644
--- a/src/routes/(ui)/client-admin/+page.server.ts
+++ b/src/routes/(ui)/client-admin/+page.server.ts
@@ -5,19 +5,21 @@ import type { Actions, PageServerLoad } from './$types';
import { prisma } from '$lib/server/prisma';
import { tableSchema } from '$lib/valibot';
-const select: Prisma.clientSelect = {
+const select = {
id: true,
prefix: true,
access_token: true,
created: true,
updated: true,
+ development: true,
+ description: true,
_count: {
select: {
job: true,
project: true
}
}
-};
+} as const satisfies Prisma.clientSelect;
export const load = (async () => {
const clients = await prisma.client.findMany({ select, take: 20, orderBy: { id: 'desc' } });
@@ -42,8 +44,30 @@ export const actions: Actions = {
const form = await superValidate(request, valibot(tableSchema));
if (!form.valid) return fail(400, { form, ok: false });
+ const searchDev = form.data.search && 'development'.includes(form.data.search.toLowerCase());
+
+ const where = {
+ OR:
+ searchDev || form.data.search
+ ? [
+ { development: searchDev || undefined },
+ {
+ prefix: form.data.search
+ ? { contains: form.data.search, mode: 'insensitive' }
+ : undefined
+ },
+ {
+ description: form.data.search
+ ? { contains: form.data.search, mode: 'insensitive' }
+ : undefined
+ }
+ ]
+ : undefined
+ } as const satisfies Prisma.clientWhereInput;
+
const clients = await prisma.client.findMany({
select,
+ where,
orderBy: form.data.sort ? { [form.data.sort.field]: form.data.sort.direction } : undefined,
skip: form.data.page.page * form.data.page.size,
take: form.data.page.size
@@ -53,7 +77,8 @@ export const actions: Actions = {
form,
ok: true,
query: {
- data: clients
+ data: clients,
+ count: await prisma.client.count({ where })
}
};
}
diff --git a/src/routes/(ui)/client-admin/+page.svelte b/src/routes/(ui)/client-admin/+page.svelte
index 6164417d..9a465934 100644
--- a/src/routes/(ui)/client-admin/+page.svelte
+++ b/src/routes/(ui)/client-admin/+page.svelte
@@ -3,6 +3,8 @@
import type { PageData } from './$types';
import Breadcrumbs from '$lib/components/Breadcrumbs.svelte';
import Pagination from '$lib/components/Pagination.svelte';
+ import PaginationHeader from '$lib/components/PaginationHeader.svelte';
+ import SearchBar from '$lib/components/SearchBar.svelte';
import SecureDisplay from '$lib/components/SecureDisplay.svelte';
import Tooltip from '$lib/components/Tooltip.svelte';
import { Icons } from '$lib/icons';
@@ -19,12 +21,15 @@
let { data }: Props = $props();
let clients = $state(data.clients);
+ let count = $state(data.count);
const { form, enhance, submit } = superForm(data.form, {
dataType: 'json',
resetForm: false,
- onChange() {
- submit();
+ onChange({ paths }) {
+ if (!paths.includes('search')) {
+ submit();
+ }
},
onUpdate(event) {
const data = event.result.data as FormResult<{
@@ -32,12 +37,22 @@
}>;
if (event.form.valid && data.query) {
clients = data.query.data;
+ count = data.query.count;
}
}
});
+ function submitSearch() {
+ if ($form.page.page) {
+ $form.page.page = 0;
+ } else {
+ submit();
+ }
+ }
+
+ const mobileSizing = 'w-full md:w-auto';
+
const created = $derived(getRelativeTime(clients.map((c) => c.created)));
- const updated = $derived(getRelativeTime(clients.map((c) => c.updated)));
@@ -45,48 +60,70 @@
Home
{$title}
-
{$title}
-
Create Client
-
- Showing
- {$form.page.page * $form.page.size + 1}-{Math.min(
- ($form.page.page + 1) * $form.page.size,
- data.count
- )}
-
- of
- {data.count}
- items
-
+
+
+
{#each clients as client, i}
-
+
#{client.id}
- {client.prefix}
+ {client.prefix}
+ {#if client.development}
+
+
+ development
+
+ {/if}
Created: {$created[i]}
-
- Updated: {$updated[i]}
-
-
-
-
-
-
-
-
- {client._count.project}
+
+ {#if client.description}
+
{client.description}
+ {/if}
+
+
+
-
-
- {client._count.job}
+
+
+
+ {client._count.project}
+
+
+
+ {client._count.job}
+
@@ -94,7 +131,7 @@
diff --git a/src/routes/(ui)/client-admin/create/+page.svelte b/src/routes/(ui)/client-admin/create/+page.svelte
index b9ed24eb..195b0b28 100644
--- a/src/routes/(ui)/client-admin/create/+page.svelte
+++ b/src/routes/(ui)/client-admin/create/+page.svelte
@@ -2,7 +2,12 @@
import { superForm } from 'sveltekit-superforms';
import type { PageData } from './$types';
import Breadcrumbs from '$lib/components/Breadcrumbs.svelte';
+ import CancelButton from '$lib/components/CancelButton.svelte';
+ import InputWithMessage from '$lib/components/InputWithMessage.svelte';
import LabeledFormInput from '$lib/components/LabeledFormInput.svelte';
+ import PasswordInput from '$lib/components/PasswordInput.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import { Icons } from '$lib/icons';
import { title } from '$lib/stores';
import { stringLimits } from '$lib/valibot';
@@ -27,31 +32,72 @@
{$title}
+
+
+
+
+
-
+
+
+
+
diff --git a/src/routes/(ui)/client-admin/update/+page.server.ts b/src/routes/(ui)/client-admin/update/+page.server.ts
index d17a65ef..012cf918 100644
--- a/src/routes/(ui)/client-admin/update/+page.server.ts
+++ b/src/routes/(ui)/client-admin/update/+page.server.ts
@@ -5,7 +5,7 @@ import * as v from 'valibot';
import { clientSchema } from '../valibot';
import type { Actions, PageServerLoad } from './$types';
import { prisma } from '$lib/server/prisma';
-import { idSchema, paramNumber, selectFrom } from '$lib/valibot';
+import { idSchema, paramNumber } from '$lib/valibot';
export const load = (async ({ url }) => {
const id = v.safeParse(v.pipe(paramNumber, idSchema), url.searchParams.get('id'));
@@ -17,7 +17,12 @@ export const load = (async ({ url }) => {
where: {
id: id.output
},
- select: selectFrom(clientSchema.entries)
+ select: {
+ prefix: true,
+ development: true,
+ access_token: true,
+ description: true
+ }
});
if (!client) error(404);
diff --git a/src/routes/(ui)/client-admin/update/+page.svelte b/src/routes/(ui)/client-admin/update/+page.svelte
index d113ddb4..e6df963d 100644
--- a/src/routes/(ui)/client-admin/update/+page.svelte
+++ b/src/routes/(ui)/client-admin/update/+page.svelte
@@ -3,7 +3,12 @@
import type { PageData } from './$types';
import { page } from '$app/state';
import Breadcrumbs from '$lib/components/Breadcrumbs.svelte';
+ import CancelButton from '$lib/components/CancelButton.svelte';
+ import InputWithMessage from '$lib/components/InputWithMessage.svelte';
import LabeledFormInput from '$lib/components/LabeledFormInput.svelte';
+ import PasswordInput from '$lib/components/PasswordInput.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import { Icons } from '$lib/icons';
import { title } from '$lib/stores';
import { stringLimits } from '$lib/valibot';
@@ -27,37 +32,77 @@
Home
Clients
- {id}
- Update
+ {$title}
{$title}
+
+
+
+
+
-
+
+
+
+
diff --git a/src/routes/(ui)/client-admin/valibot.ts b/src/routes/(ui)/client-admin/valibot.ts
index ccb06d5d..b7502e45 100644
--- a/src/routes/(ui)/client-admin/valibot.ts
+++ b/src/routes/(ui)/client-admin/valibot.ts
@@ -2,16 +2,18 @@ import * as v from 'valibot';
import { stringLimits } from '$lib/valibot';
export const clientSchema = v.strictObject({
- access_token: v.pipe(
+ prefix: v.pipe(
v.string(),
v.transform((s) => s.trim()),
v.minLength(1),
- v.maxBytes(stringLimits.client.access_token)
+ v.maxBytes(stringLimits.client.prefix)
),
- prefix: v.pipe(
+ development: v.boolean(),
+ access_token: v.pipe(
v.string(),
v.transform((s) => s.trim()),
v.minLength(1),
- v.maxBytes(stringLimits.client.prefix)
- )
+ v.maxBytes(stringLimits.client.access_token)
+ ),
+ description: v.nullable(v.string())
});
diff --git a/src/routes/(ui)/client-admin/view/+page.server.ts b/src/routes/(ui)/client-admin/view/+page.server.ts
index cd6d1d65..7dc42dfa 100644
--- a/src/routes/(ui)/client-admin/view/+page.server.ts
+++ b/src/routes/(ui)/client-admin/view/+page.server.ts
@@ -15,6 +15,9 @@ export const load = (async ({ url }) => {
const client = await prisma.client.findUnique({
where: {
id: id.output
+ },
+ include: {
+ _count: { select: { job: true, project: true } }
}
});
@@ -30,7 +33,17 @@ export const actions: Actions = {
const form = await superValidate(request, valibot(v.object({ id: idSchema })));
if (!form.valid) return fail(400, { form, ok: false });
- await prisma.client.delete({ where: { id: form.data.id } });
+ const deleted = await prisma.client.deleteMany({
+ where: { id: form.data.id, NOT: [{ job: { some: {} } }, { project: { some: {} } }] }
+ });
+
+ if (!deleted.count) {
+ return fail(409, {
+ form,
+ ok: false,
+ message: 'Client is associated with one or more jobs or projects and could not be deleted'
+ });
+ }
redirect(303, `/client-admin`);
}
diff --git a/src/routes/(ui)/client-admin/view/+page.svelte b/src/routes/(ui)/client-admin/view/+page.svelte
index c9782d7d..d36d44d8 100644
--- a/src/routes/(ui)/client-admin/view/+page.svelte
+++ b/src/routes/(ui)/client-admin/view/+page.svelte
@@ -3,8 +3,12 @@
import { enhance } from '$app/forms';
import { page } from '$app/state';
import Breadcrumbs from '$lib/components/Breadcrumbs.svelte';
+ import SecureDisplay from '$lib/components/SecureDisplay.svelte';
+ import Tooltip from '$lib/components/Tooltip.svelte';
+ import { Icons } from '$lib/icons';
+ import IconContainer from '$lib/icons/IconContainer.svelte';
import { title } from '$lib/stores';
- import { getTimeDateString } from '$lib/utils/time';
+ import { getRelativeTime, getTimeDateString } from '$lib/utils/time';
$title = 'View Client: ' + page.url.searchParams.get('id')!;
@@ -13,6 +17,8 @@
}
let { data }: Props = $props();
+
+ const dateCreated = $derived(getRelativeTime(data.client.created));
@@ -21,47 +27,106 @@
{data.client.id}
-
{$title}
+
+
+ Client {data.client.id}
+
+
+Created
+ {$dateCreated}
+
-
-
Update
+
+
+ Edit Client
+
-
-
-
- | ID |
- {data.client.id} |
-
-
- | Access Token |
- {data.client.access_token} |
-
-
- | Prefix |
- {data.client.prefix} |
-
-
- | Created |
- {getTimeDateString(data.client.created)} |
-
-
- | Updated |
- {getTimeDateString(data.client.updated)} |
-
-
-
+
+
+
+
+
+ Prefix:
+
+
+ {data.client.prefix}
+
+
+
+
+
+ Development:
+
+
+ {data.client.development}
+
+
+
+
+
+ Projects:
+
+
+ {data.client._count.project}
+
+
+
+
+
+ Jobs:
+
+
+ {data.client._count.job}
+
+
+
+
+
+
+ Access Token:
+
+
+
+
+
+
+ Description:
+
+
{data.client.description}
+
+
+
+
diff --git a/src/routes/(ui)/job-admin/+page.server.ts b/src/routes/(ui)/job-admin/+page.server.ts
index 4532af69..ec89757a 100644
--- a/src/routes/(ui)/job-admin/+page.server.ts
+++ b/src/routes/(ui)/job-admin/+page.server.ts
@@ -6,7 +6,7 @@ import type { Actions, PageServerLoad } from './$types';
import { prisma } from '$lib/server/prisma';
import { applicationTypes, tableSchema } from '$lib/valibot';
-const select: Prisma.jobSelect = {
+const select = {
id: true,
request_id: true,
app_id: true,
@@ -17,7 +17,7 @@ const select: Prisma.jobSelect = {
prefix: true
}
}
-};
+} as const satisfies Prisma.jobSelect;
const searchSchema = v.object({
appType: v.nullable(v.picklist(applicationTypes)),
diff --git a/src/routes/(ui)/job-admin/+page.svelte b/src/routes/(ui)/job-admin/+page.svelte
index 28bb5acf..6b6b35ad 100644
--- a/src/routes/(ui)/job-admin/+page.svelte
+++ b/src/routes/(ui)/job-admin/+page.svelte
@@ -1,12 +1,12 @@
-
-
- Home
- Jobs
- {id}
- Update
-
-
{$title}
-
-
-
-
diff --git a/src/routes/(ui)/job-admin/view/+page.server.ts b/src/routes/(ui)/job-admin/view/+page.server.ts
index 5b40cc94..d6158d81 100644
--- a/src/routes/(ui)/job-admin/view/+page.server.ts
+++ b/src/routes/(ui)/job-admin/view/+page.server.ts
@@ -13,6 +13,16 @@ export const load = (async ({ url }) => {
const job = await prisma.job.findUnique({
where: {
id: id.output
+ },
+ select: {
+ id: true,
+ request_id: true,
+ git_url: true,
+ app_id: true,
+ publisher_id: true,
+ client: { select: { id: true, prefix: true, development: true } },
+ existing_version_code: true,
+ created: true
}
});
diff --git a/src/routes/(ui)/job-admin/view/+page.svelte b/src/routes/(ui)/job-admin/view/+page.svelte
index a2d67fcb..fd89a7b9 100644
--- a/src/routes/(ui)/job-admin/view/+page.svelte
+++ b/src/routes/(ui)/job-admin/view/+page.svelte
@@ -2,8 +2,14 @@
import type { PageData } from './$types';
import { page } from '$app/state';
import Breadcrumbs from '$lib/components/Breadcrumbs.svelte';
+ import CopyField from '$lib/components/CopyField.svelte';
+ import LinkToScriptoria from '$lib/components/LinkToScriptoria.svelte';
+ import Tooltip from '$lib/components/Tooltip.svelte';
+ import { Icons, getAppIcon, getBucketIcon } from '$lib/icons';
+ import IconContainer from '$lib/icons/IconContainer.svelte';
import { title } from '$lib/stores';
- import { getTimeDateString } from '$lib/utils/time';
+ import { getRelativeTime, getTimeDateString } from '$lib/utils/time';
+ import type { ApplicationType } from '$lib/valibot';
$title = 'View Job: ' + page.url.searchParams.get('id')!;
@@ -12,6 +18,8 @@
}
let { data }: Props = $props();
+
+ const dateCreated = $derived(getRelativeTime(data.job.created));
@@ -20,61 +28,112 @@
{data.job.id}
-
{$title}
+
+
+ Job {data.job.id}
+
+
+
+ Created
+ {$dateCreated}
+
+
+
-
-
Update
+
+
+
+
+
+ Publisher ID:
+
+
+ {data.job.publisher_id}
+
+
+
+ App ID:
+
+
+ {data.job.app_id}
+
+
+
+
+
+
+ Existing Version Code:
+
+
+ {data.job.existing_version_code}
+
+
+
+ {#if data.job.git_url}
+ {@const { icon, title } = getBucketIcon(data.job.git_url)}
+
+
{title}:
+
+
+
+
+ {data.job.git_url?.substring(0, 5)}
+
+
+ {data.job.git_url.split('/').slice(2, -1).join('/')}
+
+
+ /{data.job.git_url.split('/').pop()}
+
+
+
+
+ {/if}
+ {#if data.job.request_id}
+
+
Request ID:
+
+
{data.job.request_id}
+
+
+
+ {/if}
-
-
-
- | ID |
- {data.job.id} |
-
-
- | Request ID |
- {data.job.request_id} |
-
-
- | Git Url |
- {data.job.git_url} |
-
-
- | App ID |
- {data.job.app_id} |
-
-
- | Publisher ID |
- {data.job.publisher_id} |
-
-
- | Client ID |
-
- {data.job.client_id}
- |
-
-
- | Existing Version Code |
- {data.job.existing_version_code} |
-
-
- | Jenkins Build Url |
- {data.job.jenkins_build_url} |
-
-
- | Jenkins Publish Url |
-
- {data.job.jenkins_publish_url}
- |
-
-
- | Created |
- {getTimeDateString(data.job.created)} |
-
-
- | Updated |
- {getTimeDateString(data.job.updated)} |
-
-
-
+
diff --git a/src/routes/(ui)/project-admin/+page.server.ts b/src/routes/(ui)/project-admin/+page.server.ts
index 9005a3a5..e701077a 100644
--- a/src/routes/(ui)/project-admin/+page.server.ts
+++ b/src/routes/(ui)/project-admin/+page.server.ts
@@ -6,8 +6,9 @@ import type { Actions, PageServerLoad } from './$types';
import { prisma } from '$lib/server/prisma';
import { applicationTypes, tableSchema } from '$lib/valibot';
-const select: Prisma.projectSelect = {
+const select = {
id: true,
+ language_code: true,
project_name: true,
app_id: true,
client: {
@@ -17,7 +18,7 @@ const select: Prisma.projectSelect = {
}
},
url: true
-};
+} as const satisfies Prisma.projectSelect;
const searchSchema = v.object({
appType: v.nullable(v.picklist(applicationTypes)),
@@ -55,7 +56,8 @@ export const actions: Actions = {
OR: [
{ project_name: { contains: form.data.search, mode: 'insensitive' } },
{ client: { prefix: { contains: form.data.search, mode: 'insensitive' } } },
- { url: { contains: form.data.search, mode: 'insensitive' } }
+ { url: { contains: form.data.search, mode: 'insensitive' } },
+ { language_code: { contains: form.data.search, mode: 'insensitive' } }
]
}
: {}
diff --git a/src/routes/(ui)/project-admin/+page.svelte b/src/routes/(ui)/project-admin/+page.svelte
index cff4f078..9c2b5b3b 100644
--- a/src/routes/(ui)/project-admin/+page.svelte
+++ b/src/routes/(ui)/project-admin/+page.svelte
@@ -4,8 +4,9 @@
import AppTypeSelector from '$lib/components/AppTypeSelector.svelte';
import Breadcrumbs from '$lib/components/Breadcrumbs.svelte';
import Pagination from '$lib/components/Pagination.svelte';
+ import PaginationHeader from '$lib/components/PaginationHeader.svelte';
import SearchBar from '$lib/components/SearchBar.svelte';
- import { Icons, getAppIcon } from '$lib/icons';
+ import { Icons, getAppIcon, getBucketIcon } from '$lib/icons';
import IconContainer from '$lib/icons/IconContainer.svelte';
import { title } from '$lib/stores';
import type { ApplicationType } from '$lib/valibot';
@@ -81,42 +82,35 @@
-
- Showing
- {$form.page.page * $form.page.size + 1}-{Math.min(
- ($form.page.page + 1) * $form.page.size,
- count
- )}
-
- of
- {count}
- items
-
+
{#each projects as project}
-
+
#{project.id}
-
+
+ {project.language_code}
+
+
{project.project_name}
-
+
diff --git a/src/routes/(ui)/project-admin/update/+page.server.ts b/src/routes/(ui)/project-admin/update/+page.server.ts
deleted file mode 100644
index 8d80c60d..00000000
--- a/src/routes/(ui)/project-admin/update/+page.server.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { SpanStatusCode, trace } from '@opentelemetry/api';
-import { error, redirect } from '@sveltejs/kit';
-import { fail, superValidate } from 'sveltekit-superforms';
-import { valibot } from 'sveltekit-superforms/adapters';
-import * as v from 'valibot';
-import type { Actions, PageServerLoad } from './$types';
-import { prisma } from '$lib/server/prisma';
-import {
- convertEmptyStrToNull,
- idSchema,
- paramNumber,
- selectFrom,
- stringLimits
-} from '$lib/valibot';
-
-const projectSchema = v.object({
- status: convertEmptyStrToNull(stringLimits.project.status),
- result: convertEmptyStrToNull(stringLimits.project.result),
- error: convertEmptyStrToNull(stringLimits.project.error),
- url: v.pipe(
- convertEmptyStrToNull(stringLimits.project.url),
- v.nullable(v.pipe(v.string(), v.url()))
- ),
- user_id: convertEmptyStrToNull(stringLimits.project.user_id),
- group_id: convertEmptyStrToNull(stringLimits.project.group_id),
- app_id: convertEmptyStrToNull(stringLimits.project.app_id),
- client_id: v.nullable(idSchema),
- project_name: convertEmptyStrToNull(stringLimits.project.project_name),
- language_code: convertEmptyStrToNull(stringLimits.project.language_code),
- publishing_key: convertEmptyStrToNull(stringLimits.project.publishing_key)
-});
-
-export const load = (async ({ url }) => {
- const id = v.safeParse(v.pipe(paramNumber, idSchema), url.searchParams.get('id'));
- if (!id.success) {
- error(400, `missing id param`);
- }
-
- const project = await prisma.project.findUnique({
- where: {
- id: id.output
- },
- select: selectFrom(projectSchema.entries)
- });
-
- if (!project) error(404);
-
- return {
- form: await superValidate(project, valibot(projectSchema))
- };
-}) satisfies PageServerLoad;
-
-export const actions: Actions = {
- default: async function ({ request, url }) {
- const id = v.safeParse(v.pipe(paramNumber, idSchema), url.searchParams.get('id'));
- if (!id.success) {
- error(400, `missing id param`);
- }
- const form = await superValidate(request, valibot(projectSchema));
- if (!form.valid) return fail(400, { form, ok: false });
-
- let project = { id: 0 };
-
- try {
- project = await prisma.project.update({
- where: {
- id: id.output
- },
- data: form.data
- });
- } catch (e) {
- trace.getActiveSpan()?.recordException(e as Error);
- trace.getActiveSpan()?.setStatus({
- code: SpanStatusCode.ERROR,
- message: (e as Error).message
- });
- return error(400, (e as Error).message);
- }
-
- redirect(303, `/project-admin/view?id=${project.id}`);
- }
-};
diff --git a/src/routes/(ui)/project-admin/update/+page.svelte b/src/routes/(ui)/project-admin/update/+page.svelte
deleted file mode 100644
index 9300bf7d..00000000
--- a/src/routes/(ui)/project-admin/update/+page.svelte
+++ /dev/null
@@ -1,137 +0,0 @@
-
-
-
- Home
- Projects
- {id}
- Update
-
-
{$title}
-
-
-
-
diff --git a/src/routes/(ui)/project-admin/view/+page.server.ts b/src/routes/(ui)/project-admin/view/+page.server.ts
index 0e5247ab..889f2257 100644
--- a/src/routes/(ui)/project-admin/view/+page.server.ts
+++ b/src/routes/(ui)/project-admin/view/+page.server.ts
@@ -13,6 +13,18 @@ export const load = (async ({ url }) => {
const project = await prisma.project.findUnique({
where: {
id: id.output
+ },
+ select: {
+ id: true,
+ project_name: true,
+ created: true,
+ status: true,
+ result: true,
+ language_code: true,
+ app_id: true,
+ url: true,
+ client: { select: { id: true, prefix: true, development: true } },
+ error: true
}
});
diff --git a/src/routes/(ui)/project-admin/view/+page.svelte b/src/routes/(ui)/project-admin/view/+page.svelte
index d4c2c7c5..fc370678 100644
--- a/src/routes/(ui)/project-admin/view/+page.svelte
+++ b/src/routes/(ui)/project-admin/view/+page.svelte
@@ -2,8 +2,15 @@
import type { PageData } from './$types';
import { page } from '$app/state';
import Breadcrumbs from '$lib/components/Breadcrumbs.svelte';
+ import CopyField from '$lib/components/CopyField.svelte';
+ import LinkToScriptoria from '$lib/components/LinkToScriptoria.svelte';
+ import StatusBadge from '$lib/components/StatusBadge.svelte';
+ import Tooltip from '$lib/components/Tooltip.svelte';
+ import { Icons, getAppIcon, getBucketIcon } from '$lib/icons';
+ import IconContainer from '$lib/icons/IconContainer.svelte';
import { title } from '$lib/stores';
- import { getTimeDateString } from '$lib/utils/time';
+ import { getRelativeTime, getTimeDateString } from '$lib/utils/time';
+ import type { ApplicationType } from '$lib/valibot';
$title = 'View Project: ' + page.url.searchParams.get('id')!;
@@ -12,6 +19,8 @@
}
let { data }: Props = $props();
+
+ const dateCreated = $derived(getRelativeTime(data.project.created));
@@ -20,81 +29,108 @@
{data.project.id}
-
{$title}
-
-
-
Update
+
+
{data.project.project_name}
+
+
+
+ Created
+ {$dateCreated}
+
+
-
+ {:else}
+
+
+ {/if}
+
+ {/if}
+
+
+
diff --git a/src/routes/(ui)/release-admin/+page.server.ts b/src/routes/(ui)/release-admin/+page.server.ts
index 766f07c0..d877dd07 100644
--- a/src/routes/(ui)/release-admin/+page.server.ts
+++ b/src/routes/(ui)/release-admin/+page.server.ts
@@ -5,7 +5,7 @@ import type { Actions, PageServerLoad } from './$types';
import { prisma } from '$lib/server/prisma';
import { tableSchema } from '$lib/valibot';
-const select: Prisma.releaseSelect = {
+const select = {
id: true,
build: {
select: {
@@ -16,7 +16,7 @@ const select: Prisma.releaseSelect = {
status: true,
result: true,
codebuild_url: true
-};
+} as const satisfies Prisma.releaseSelect;
export const load = (async () => {
const releases = await prisma.release.findMany({
diff --git a/src/routes/(ui)/release-admin/+page.svelte b/src/routes/(ui)/release-admin/+page.svelte
index ff2d5835..0a8214b3 100644
--- a/src/routes/(ui)/release-admin/+page.svelte
+++ b/src/routes/(ui)/release-admin/+page.svelte
@@ -3,8 +3,10 @@
import type { PageData } from './$types';
import Breadcrumbs from '$lib/components/Breadcrumbs.svelte';
import Pagination from '$lib/components/Pagination.svelte';
+ import PaginationHeader from '$lib/components/PaginationHeader.svelte';
import SearchBar from '$lib/components/SearchBar.svelte';
- import { Icons, getStatusIcon } from '$lib/icons';
+ import StatusBadge from '$lib/components/StatusBadge.svelte';
+ import { Icons } from '$lib/icons';
import IconContainer from '$lib/icons/IconContainer.svelte';
import { title } from '$lib/stores';
@@ -76,47 +78,17 @@
-
- Showing
- {$form.page.page * $form.page.size + 1}-{Math.min(
- ($form.page.page + 1) * $form.page.size,
- count
- )}
-
- of
- {count}
- items
-
+
{#each releases as release}
- {@const status = release.result || release.status}
-
+
#{release.id}
- {#if status}
- {@const { icon } = getStatusIcon(status)}
-
- {#if icon !== Icons.Unknown}
-
- {/if}
- {status}
-
- {/if}
+
-
+
#{release.build.job_id}
diff --git a/src/routes/(ui)/release-admin/update/+page.server.ts b/src/routes/(ui)/release-admin/update/+page.server.ts
deleted file mode 100644
index b1197764..00000000
--- a/src/routes/(ui)/release-admin/update/+page.server.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { SpanStatusCode, trace } from '@opentelemetry/api';
-import { error, redirect } from '@sveltejs/kit';
-import { fail, superValidate } from 'sveltekit-superforms';
-import { valibot } from 'sveltekit-superforms/adapters';
-import * as v from 'valibot';
-import type { Actions, PageServerLoad } from './$types';
-import { prisma } from '$lib/server/prisma';
-import {
- convertEmptyStrToNull,
- idSchema,
- paramNumber,
- selectFrom,
- stringIdSchema,
- stringLimits
-} from '$lib/valibot';
-
-const releaseSchema = v.object({
- build_id: idSchema,
- status: convertEmptyStrToNull(stringLimits.release.status),
- result: convertEmptyStrToNull(stringLimits.release.result),
- error: v.pipe(
- convertEmptyStrToNull(stringLimits.release.error),
- v.nullable(v.pipe(v.string(), v.url()))
- ),
- channel: v.pipe(v.string(), v.maxBytes(stringLimits.release.channel)),
- title: convertEmptyStrToNull(stringLimits.release.title),
- defaultLanguage: convertEmptyStrToNull(stringLimits.release.defaultLanguage),
- build_guid: v.pipe(convertEmptyStrToNull(), v.nullable(stringIdSchema)),
- promote_from: convertEmptyStrToNull(stringLimits.release.promote_from),
- targets: convertEmptyStrToNull(stringLimits.release.targets),
- environment: convertEmptyStrToNull(),
- artifact_url_base: v.pipe(
- convertEmptyStrToNull(stringLimits.release.artifact_url_base),
- v.nullable(v.pipe(v.string(), v.url()))
- ),
- artifact_files: convertEmptyStrToNull(stringLimits.release.artifact_files)
-});
-
-export const load = (async ({ url }) => {
- const id = v.safeParse(v.pipe(paramNumber, idSchema), url.searchParams.get('id'));
- if (!id.success) {
- error(400, `missing id param`);
- }
-
- const release = await prisma.release.findUnique({
- where: {
- id: id.output
- },
- select: selectFrom(releaseSchema.entries)
- });
-
- if (!release) error(404);
-
- return {
- form: await superValidate(release, valibot(releaseSchema))
- };
-}) satisfies PageServerLoad;
-
-export const actions: Actions = {
- default: async function ({ request, url }) {
- const id = v.safeParse(v.pipe(paramNumber, idSchema), url.searchParams.get('id'));
- if (!id.success) {
- error(400, `missing id param`);
- }
- const form = await superValidate(request, valibot(releaseSchema));
- if (!form.valid) return fail(400, { form, ok: false });
-
- let release = { id: 0 };
-
- try {
- release = await prisma.release.update({
- where: {
- id: id.output
- },
- data: form.data
- });
- } catch (e) {
- trace.getActiveSpan()?.recordException(e as Error);
- trace.getActiveSpan()?.setStatus({
- code: SpanStatusCode.ERROR,
- message: (e as Error).message
- });
- return error(400, (e as Error).message);
- }
-
- redirect(303, `/release-admin/view?id=${release.id}`);
- }
-};
diff --git a/src/routes/(ui)/release-admin/update/+page.svelte b/src/routes/(ui)/release-admin/update/+page.svelte
deleted file mode 100644
index 02a014b5..00000000
--- a/src/routes/(ui)/release-admin/update/+page.svelte
+++ /dev/null
@@ -1,157 +0,0 @@
-
-
-
- Home
- Releases
- {id}
- Update
-
-{$title}
-
-
-
-
diff --git a/src/routes/(ui)/release-admin/view/+page.server.ts b/src/routes/(ui)/release-admin/view/+page.server.ts
index 007f265a..86cd24fa 100644
--- a/src/routes/(ui)/release-admin/view/+page.server.ts
+++ b/src/routes/(ui)/release-admin/view/+page.server.ts
@@ -14,6 +14,18 @@ export const load = (async ({ url }) => {
const release = await prisma.release.findUnique({
where: {
id: id.output
+ },
+ include: {
+ build: {
+ select: {
+ job: {
+ select: {
+ client: { select: { development: true } },
+ git_url: true
+ }
+ }
+ }
+ }
}
});
diff --git a/src/routes/(ui)/release-admin/view/+page.svelte b/src/routes/(ui)/release-admin/view/+page.svelte
index d08efb44..3da3840d 100644
--- a/src/routes/(ui)/release-admin/view/+page.svelte
+++ b/src/routes/(ui)/release-admin/view/+page.svelte
@@ -1,9 +1,17 @@
@@ -20,94 +30,151 @@
{data.release.id}
-{$title}
+
+
+ Release {data.release.id}
+
-
+
+ Created
+ {$dateCreated}
+
+
-
-
-
- | ID |
- {data.release.id} |
-
-
- | Build ID |
-
+ |
-
-
- | Status |
- {data.release.status} |
-
-
- | Created |
- {getTimeDateString(data.release.created)} |
-
-
- | Updated |
- {getTimeDateString(data.release.updated)} |
-
-
- | Result |
- {data.release.result} |
-
-
- | Error |
-
- {#if data.release.error?.match(/^https?:/)}
-
- {data.release.error}
-
+
+
+
+
+
+ Targets:
+
+
+ {data.release.targets || '(none)'}
+
+
+
+
+
+ Channel:
+
+
+ {data.release.channel || '(none)'}
+
+
+
+
+
+ Default Language:
+
+
+ {data.release.defaultLanguage ?? '(none)'}
+
+
+
+ {#if data.release.codebuild_url || data.release.build_guid}
+
+
+ Code Build:
+
+
+ {#if data.release.codebuild_url}
+ {data.release.build_guid}
{:else}
- {data.release.error}
+ {data.release.build_guid}
{/if}
- |
-
-
- | Artifacts |
-
- {#if data.release.artifact_files}
- {#each Object.entries(data.artifacts)
- .filter(([_, url]) => !!url)
- .sort(([a, _1], [b, _2]) => a.localeCompare(b, 'en-US')) as [name, url]}
- {name}
- ,
- {/each}
- {/if}
- |
-
-
- | Channel |
- {data.release.channel} |
-
-
- | Title |
- {data.release.title} |
-
-
- | Default Language |
- {data.release.defaultLanguage} |
-
-
- | Build GUID |
- {data.release.build_guid} |
-
-
- | Promote From |
- {data.release.promote_from} |
-
-
- | Targets |
- {data.release.targets} |
-
-
- | Environment |
- {data.release.environment} |
-
-
-
+
+
+ {/if}
+ {#if data.release.error}
+ {@const isURL = !!data.release.error?.match(/^https?:/)}
+
+ {/if}
+ {#if data.release.artifact_files}
+ {@const artifacts = Object.entries(data.artifacts)
+ .filter(([_, url]) => !!url)
+ .sort(([a, _1], [b, _2]) => byString(a, b))}
+
+
Artifacts:
+
+ {#each artifacts as [name, url], i}
+ {name}
+ {#if i + 1 < artifacts.length}
+ ,
+ {/if}
+ {/each}
+
+
+ {/if}
+ {#if data.release.environment}
+ {@const parsed = safeParse(JSON2Entries, data.release.environment)}
+ {#if parsed.success && parsed.output?.length}
+
+
Environment:
+
+
+ {#each parsed.output as [key, value]}
+
+ | {key}: |
+ {value} |
+
+
+ | {key}: |
+
+
+ | {value} |
+
+ {/each}
+
+
+
+ {/if}
+ {/if}
+
+
+