Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/frontend/src/routes/(app)/settings/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<script lang="ts">
import { appbar } from "../+layout.svelte";
import type { LayoutProps } from "./$types";

let { children }: LayoutProps = $props();
</script>

<main class="max-w-2xl flex flex-col gap-4 mx-auto">
{@render appbar(["Administration", "Settings"])}

<main class="mx-auto flex max-w-2xl flex-col gap-4">
{@render children()}
</main>
161 changes: 93 additions & 68 deletions packages/frontend/src/routes/(app)/settings/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,89 +1,115 @@
<script lang="ts">
import BoundaryFailed from "$lib/components/BoundaryFailed.svelte";
import LabeledInput from "$lib/components/LabeledInput.svelte";
import {
getBot,
updateBotImage,
updateBotToken,
} from "$lib/remotes/bot.remote";
import { getBot, updateBotImage, updateBotToken } from "$lib/remotes/bot.remote";
import toaster from "$lib/utils/toaster";
import {
BracesIcon,
ContainerIcon,
LoaderCircleIcon,
RectangleEllipsisIcon,
SaveIcon,
TextCursorIcon,
} from "@lucide/svelte";
import { Switch } from "@skeletonlabs/skeleton-svelte";
import { slide } from "svelte/transition";

const { dockerImage: defaultValues } = await getBot();
updateBotImage.fields.dockerImage.username.set(
defaultValues.username ?? undefined,
);
updateBotImage.fields.dockerImage.image.set(defaultValues.image);

let authentication: boolean = $state(!!defaultValues.username);
</script>

<h6 class="h6 font-normal">Deployment</h6>

<div class="flex flex-col gap-4">
<form {...updateBotImage} class="flex flex-col gap-3">
<LabeledInput
label="Container image url"
icon={ContainerIcon}
placeholder="ghcr.io/image:tag"
required
{...updateBotImage.fields.dockerImage.image.as("text")}
issues={updateBotImage.fields.dockerImage.image.issues()}
<svelte:boundary>
<form
{...updateBotImage.enhance(({ submit, element }) => {
toaster.promise(submit(), {
loading: { title: "Updating deployment config.." },
success: () => {
element.reset();
return { title: "Successfully applied deployment config" };
},
error: (error) => ({
title: "Error",
description: JSON.parse(error as string)?.message ?? "An error occurred",
}),
});
})}
class="flex flex-col gap-3"
>
{#snippet suffix()}
<button class="btn-icon btn-icon-sm text-primary-700-300">
{#if updateBotImage.pending}
<LoaderCircleIcon class="animate-spin" />
{:else}
{const { dockerImage: defaultValues } = await getBot()}
{let authentication = $state(!!defaultValues.username)}

<LabeledInput
label="Container image url"
icon={ContainerIcon}
placeholder="ghcr.io/image:tag"
required
{...updateBotImage.fields.dockerImage.image.as("text", defaultValues.image)}
issues={updateBotImage.fields.dockerImage.image.issues()}
>
{#snippet suffix()}
<button
class="btn-icon btn-icon-sm text-primary-700-300"
disabled={!!updateBotImage.pending}
>
<SaveIcon />
{/if}
</button>
{/snippet}
</LabeledInput>
</button>
{/snippet}
</LabeledInput>

<Switch
defaultChecked={authentication}
checked={authentication}
onCheckedChange={(details) => (authentication = details.checked)}
>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>Registry authentication</Switch.Label>
<Switch.HiddenInput />
</Switch>
<Switch
defaultChecked={authentication}
checked={authentication}
onCheckedChange={(details) => (authentication = details.checked)}
>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>Registry authentication</Switch.Label>
<Switch.HiddenInput />
</Switch>

{#if authentication}
<div class="grid grid-cols-2 gap-2" transition:slide={{ duration: 150 }}>
<LabeledInput
label="Username"
icon={TextCursorIcon}
required
{...updateBotImage.fields.dockerImage.username.as("text")}
issues={updateBotImage.fields.dockerImage.username.issues()}
/>
{#if authentication}
<div class="grid grid-cols-2 gap-2" transition:slide={{ duration: 150 }}>
<LabeledInput
label="Username"
icon={TextCursorIcon}
required
{...updateBotImage.fields.dockerImage.username.as("text", defaultValues.username ?? "")}
issues={updateBotImage.fields.dockerImage.username.issues()}
/>

<LabeledInput
label="Password"
icon={RectangleEllipsisIcon}
required
{...updateBotImage.fields.dockerImage.password.as("password")}
issues={updateBotImage.fields.dockerImage.password.issues()}
/>
</div>
{/if}
</form>
<LabeledInput
label="Password"
icon={RectangleEllipsisIcon}
required
{...updateBotImage.fields.dockerImage.password.as("password")}
issues={updateBotImage.fields.dockerImage.password.issues()}
/>
</div>
{/if}
</form>

{#snippet failed(error, reset)}
<BoundaryFailed {error} {reset} />
{/snippet}
</svelte:boundary>

<hr class="hr" />

<form {...updateBotToken}>
<form
{...updateBotToken.enhance(({ submit, element }) => {
toaster.promise(submit(), {
loading: { title: "Updating token.." },
success: () => {
element.reset();
return { title: "Successfully registered token" };
},
error: (error) => ({
title: "Error",
description: JSON.parse(error as string)?.message ?? "An error occurred",
}),
});
})}
>
<LabeledInput
label="Bot token"
icon={BracesIcon}
Expand All @@ -93,12 +119,11 @@
issues={updateBotToken.fields.token.issues()}
>
{#snippet suffix()}
<button class="btn-icon btn-icon-sm text-primary-700-300">
{#if updateBotToken.pending}
<LoaderCircleIcon class="animate-spin" />
{:else}
<SaveIcon />
{/if}
<button
class="btn-icon btn-icon-sm text-primary-700-300"
disabled={!!updateBotToken.pending}
>
<SaveIcon />
</button>
{/snippet}
</LabeledInput>
Expand Down
Loading