Feature: API Tokens (#848)
* initial commit * fix last_used timestamp --------- Co-authored-by: Christian Beutel <>
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { _ } from "svelte-i18n";
|
||||
import type { ChangeEventHandler } from "svelte/elements";
|
||||
|
||||
|
||||
interface Props {
|
||||
name?: string;
|
||||
value?: string | Date;
|
||||
label?: string;
|
||||
error?: string | string[] | null;
|
||||
min?: string | number;
|
||||
max?: string | number;
|
||||
onchange?: ChangeEventHandler<HTMLInputElement>;
|
||||
}
|
||||
|
||||
@@ -15,7 +17,9 @@
|
||||
value = $bindable(),
|
||||
label = "",
|
||||
error = "",
|
||||
onchange
|
||||
min,
|
||||
max,
|
||||
onchange,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
@@ -32,6 +36,8 @@
|
||||
class:border-red-400={(error?.length ?? 0) > 0}
|
||||
class:bg-input-background-error={(error?.length ?? 0) > 0}
|
||||
type="date"
|
||||
{min}
|
||||
{max}
|
||||
bind:value
|
||||
{onchange}
|
||||
/>
|
||||
|
||||
88
web/src/lib/components/settings/api_token_modal.svelte
Normal file
88
web/src/lib/components/settings/api_token_modal.svelte
Normal file
@@ -0,0 +1,88 @@
|
||||
<script lang="ts">
|
||||
import Modal from "$lib/components/base/modal.svelte";
|
||||
import { APITokenCreateSchema } from "$lib/models/api/api_token_schema";
|
||||
import type { APIToken } from "$lib/models/api_token";
|
||||
import { validator } from "@felte/validator-zod";
|
||||
import { createForm } from "felte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { z } from "zod";
|
||||
import Datepicker from "../base/datepicker.svelte";
|
||||
import TextField from "../base/text_field.svelte";
|
||||
import Toggle from "../base/toggle.svelte";
|
||||
|
||||
interface Props {
|
||||
onsave?: (token: APIToken) => void;
|
||||
}
|
||||
|
||||
let { onsave }: Props = $props();
|
||||
|
||||
let modal: Modal;
|
||||
|
||||
let expires: boolean = $state(false);
|
||||
|
||||
const today = new Date();
|
||||
today.setDate(today.getDate() + 1);
|
||||
const tomorrow = today.toISOString().split("T")[0];
|
||||
|
||||
export function openModal() {
|
||||
modal.openModal();
|
||||
setFields("name", "");
|
||||
setFields("expiration", undefined);
|
||||
setErrors("name", []);
|
||||
setErrors("expiration", []);
|
||||
}
|
||||
|
||||
const { form, errors, setFields, setErrors } = createForm<
|
||||
z.infer<typeof APITokenCreateSchema>
|
||||
>({
|
||||
initialValues: {
|
||||
name: "",
|
||||
expiration: "",
|
||||
},
|
||||
extend: validator({
|
||||
schema: APITokenCreateSchema.extend({
|
||||
user: z.string().optional(),
|
||||
}),
|
||||
}),
|
||||
onSubmit: async (form) => {
|
||||
onsave?.(form);
|
||||
modal.closeModal!();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
id="api-token-modal"
|
||||
size="md:min-w-md"
|
||||
title={$_("generate-new-token")}
|
||||
bind:this={modal}
|
||||
>
|
||||
{#snippet content()}
|
||||
<form class="space-y-4" id="api-token-form" use:form>
|
||||
<TextField label={$_("name")} name="name" error={$errors.name}
|
||||
></TextField>
|
||||
<Toggle label={$_("expires")} bind:value={expires}></Toggle>
|
||||
{#if expires}
|
||||
<Datepicker
|
||||
label={$_("expiration")}
|
||||
name="expiration"
|
||||
error={$errors.expiration}
|
||||
min={tomorrow}
|
||||
></Datepicker>
|
||||
{/if}
|
||||
</form>
|
||||
{/snippet}
|
||||
{#snippet footer()}
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="btn-secondary" onclick={() => modal.closeModal()}
|
||||
>{$_("cancel")}</button
|
||||
>
|
||||
<button
|
||||
class="btn-primary"
|
||||
type="submit"
|
||||
form="api-token-form"
|
||||
name="save">{$_("save")}</button
|
||||
>
|
||||
</div>
|
||||
{/snippet}</Modal
|
||||
>
|
||||
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import Modal from "$lib/components/base/modal.svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import TextField from "../base/text_field.svelte";
|
||||
|
||||
interface Props {
|
||||
token: string | null;
|
||||
}
|
||||
|
||||
let { token = $bindable() }: Props = $props();
|
||||
|
||||
let modal: Modal;
|
||||
let copied: boolean = $state(false);
|
||||
|
||||
export function openModal() {
|
||||
copied = false;
|
||||
modal.openModal();
|
||||
}
|
||||
|
||||
export function closeModal() {
|
||||
token = null;
|
||||
modal.closeModal();
|
||||
}
|
||||
|
||||
function copyTokenToClipboard() {
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(token);
|
||||
copied = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
id="api-token-success-modal"
|
||||
size="md:min-w-md"
|
||||
title={$_("new-token-generated")}
|
||||
bind:this={modal}
|
||||
>
|
||||
{#snippet content()}
|
||||
<p class="max-w-lg">
|
||||
Please copy your new API token now. For your security, we cannot
|
||||
show it to you again. If you lose this token, you will need to
|
||||
delete it and generate a new one.
|
||||
</p>
|
||||
<div class="flex items-center gap-x-4 flex-nowrap">
|
||||
<div class="flex-1">
|
||||
<TextField value={token ?? ""} disabled></TextField>
|
||||
</div>
|
||||
<button
|
||||
class="btn-secondary"
|
||||
onclick={() => copyTokenToClipboard()}
|
||||
aria-label="copy token to clipboard"
|
||||
><i class="fa {copied ? 'fa-check' : 'fa-clipboard'}"
|
||||
></i></button
|
||||
>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet footer()}
|
||||
<button class="btn-primary" onclick={() => modal.closeModal()}
|
||||
>{$_("close")}</button
|
||||
>
|
||||
{/snippet}</Modal
|
||||
>
|
||||
Reference in New Issue
Block a user