Skip to content

Commit

Permalink
basic groups
Browse files Browse the repository at this point in the history
  • Loading branch information
LucaRickli committed Dec 8, 2024
1 parent 079cff1 commit 06c6050
Show file tree
Hide file tree
Showing 22 changed files with 428 additions and 242 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ Distroless docker container running deno
- `x.x.x-pre` Pre-release images (potentially unstable)
- `unstable` Built on every push to main

### Example

#### Docker run

```sh
Expand Down
2 changes: 1 addition & 1 deletion src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
}
& th,
& td {
@apply whitespace-nowrap py-2;
@apply whitespace-nowrap px-1.5 py-2;
}
& tr {
@apply border-t first:border-t-0;
Expand Down
24 changes: 22 additions & 2 deletions src/lib/components/data/acl/RuleInfo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import Settings from 'lucide-svelte/icons/settings-2';
import Trash from 'lucide-svelte/icons/trash-2';
import Info from 'lucide-svelte/icons/info';
import * as Tooltip from '$lib/components/ui/tooltip';
import { Badge } from '$lib/components/ui/badge';
import { Label } from '$lib/components/ui/label';
import EditRule from './EditRule.svelte';
export let rule: AclData['acls'][0];
Expand Down Expand Up @@ -35,7 +38,7 @@

<!-- <p class="select-none text-sm text-muted-foreground">|</p> -->

<button class="link text-red-600/60 hover:text-red-600">
<button class="link text-muted-foreground hover:text-red-600">
<Trash class="h-5 w-5" />
</button>
</div>
Expand All @@ -57,7 +60,24 @@
</div>

<div>
<Label>Routes</Label>
<Label class="flex items-center gap-1.5">
Routes
<Tooltip.Root>
<Tooltip.Trigger tabindex={-1}>
<Info class="h-3.5 w-3.5" />
</Tooltip.Trigger>

<Tooltip.Content side="right">
<div class="grid grid-cols-2 items-center gap-x-1 gap-y-0.5">
<ArrowLeft class="h-4 w-4" />
<p>In</p>

<ArrowRight class="h-4 w-4" />
<p>Out</p>
</div>
</Tooltip.Content>
</Tooltip.Root>
</Label>
<ul class="space-y-0.5">
{#each [...prefixes.in]
.map((prefix) => ({ prefix, direction: 'in' }))
Expand Down
145 changes: 145 additions & 0 deletions src/lib/components/data/apikey/ApikeyInfo.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import TimerOff from 'lucide-svelte/icons/timer-off';
import * as Sheet from '$lib/components/ui/sheet';
import * as Table from '$lib/components/ui/table';
import * as Tooltip from '$lib/components/ui/tooltip';
import ConfirmAction from '$lib/components/utils/ConfirmAction.svelte';
import { formatDuration, isExpired } from '$lib/utils/time';
import { errorToast, successToast } from '$lib/utils/toast';
import { formatError } from '$lib/utils/error';
import { cn } from '$lib/utils/shadcn';
import { ApiKey } from '$lib/api';
import Plus from 'lucide-svelte/icons/plus';
export let apikeys: ApiKey[] | undefined = undefined;
const dispatch = createEventDispatcher<{ submit: undefined }>();
if (!apikeys) {
ApiKey.list().then(({ data }) => {
apikeys = data;
});
}
async function expireApikey(key: ApiKey) {
try {
if (!key.expiration || isExpired(key.expiration)) return;
const { error } = await key.expire();
if (error) throw error;
successToast(`Expired API key "${key.prefix}..."`);
dispatch('submit');
} catch (err) {
console.error(err);
errorToast(formatError(err));
}
}
</script>

<Sheet.Root let:close>
<Sheet.Trigger asChild let:builder>
<slot name="trigger" {builder} {close} />
</Sheet.Trigger>

<Sheet.Content>
<Sheet.Header>
<Sheet.Title>API keys</Sheet.Title>
<Sheet.Description>
API keys provide administrator level access to Headscale and should never be shared with
untrusted parties.
</Sheet.Description>
</Sheet.Header>

<ul class="menu">
<li>
<button disabled on:click={() => void 0}>
<Plus />
<span> Create </span>
</button>
</li>
</ul>

<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head></Table.Head>
<Table.Head>Key</Table.Head>
<Table.Head>Created</Table.Head>
<Table.Head>Expires</Table.Head>
<Table.Head>Last seen</Table.Head>
<Table.Head>ID</Table.Head>
</Table.Row>
</Table.Header>

<Table.Body>
{#if apikeys?.length}
{#each apikeys as apiKey}
<Table.Row>
<Table.Cell class="pr-0">
<ConfirmAction
title="Expire API key"
on:submit={() => expireApikey(apiKey).then(close)}
asChild={false}
disabled={!apiKey.expiration || isExpired(apiKey.expiration)}
>
<Tooltip.Root slot="trigger">
<Tooltip.Trigger tabindex={-1}>
<TimerOff
class={cn(
!apiKey.expiration ||
(isExpired(apiKey.expiration) && '!text-muted-foreground'),
'h-4 w-4 hover:text-red-600'
)}
/>
</Tooltip.Trigger>

<Tooltip.Content side="left">
<p>Expire</p>
</Tooltip.Content>
</Tooltip.Root>
</ConfirmAction>
</Table.Cell>

<Table.Cell>
{apiKey.prefix ? apiKey.prefix + '...' : undefined}
</Table.Cell>

<Table.Cell>
{apiKey.createdAt ? new Date(apiKey.createdAt).toLocaleString() : undefined}
</Table.Cell>

<Table.Cell>
{apiKey.expiration
? isExpired(apiKey.expiration)
? new Date(apiKey.expiration).toLocaleDateString()
: formatDuration(new Date(apiKey.expiration).getTime() - Date.now())
: undefined}
</Table.Cell>

<Table.Cell>
{apiKey.lastSeen
? formatDuration(Date.now() - new Date(apiKey.lastSeen).getTime())
: undefined}
</Table.Cell>

<Table.Cell>
{apiKey.id}
</Table.Cell>
</Table.Row>
{/each}
{:else}
<p class="w-full text-center text-sm text-muted-foreground">no API keys found</p>
{/if}
<tr></tr>
</Table.Body>
</Table.Root>

<slot />
</Sheet.Content>
</Sheet.Root>
66 changes: 66 additions & 0 deletions src/lib/components/data/group/GroupInfo.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script lang="ts">
import Settings from 'lucide-svelte/icons/settings-2';
import Trash from 'lucide-svelte/icons/trash-2';
import Plus from 'lucide-svelte/icons/plus';
import * as Sheet from '$lib/components/ui/sheet';
import Badge from '$lib/components/ui/badge/badge.svelte';
import { groupRegex, type Acl } from '$lib/api';
export let groups: Acl['groups'] | undefined;
</script>

<Sheet.Root>
<Sheet.Trigger asChild let:builder>
<slot name="trigger" {builder} />
</Sheet.Trigger>

<Sheet.Content>
<Sheet.Header>
<Sheet.Title>Groups</Sheet.Title>
<Sheet.Description>Group together users to ease policy management</Sheet.Description>
</Sheet.Header>

<ul class="menu">
<li>
<button disabled>
<Plus />
<span> Create </span>
</button>
</li>
</ul>

<div class="space-y-4">
{#each groups || [] as group}
<div class="space-y-3 border-b pb-5 last:border-b-0">
<div class="flex items-center justify-between gap-3">
<p class="font-semibold">
{group.name?.replace(groupRegex, '')}
</p>

<div class="flex items-center gap-1.5">
<button class="link text-muted-foreground hover:text-current">
<Settings class="h-5 w-5" />
</button>

<button class="link text-muted-foreground hover:text-red-600">
<Trash class="h-5 w-5" />
</button>
</div>
</div>

<div class="flex gap-1.5">
{#each group.members as member}
<Badge>
{member}
</Badge>
{/each}
</div>
</div>
{/each}
</div>

<slot />
</Sheet.Content>
</Sheet.Root>
81 changes: 0 additions & 81 deletions src/lib/components/data/machine/DeleteMachine.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,84 +30,3 @@
<slot name="trigger" {builder} />
</svelte:fragment>
</ConfirmAction>

<!-- <script lang="ts">
import { defaults, superForm } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { createEventDispatcher } from 'svelte';
import { z } from 'zod';
import * as Dialog from '$lib/components/ui/dialog';
import { Checkbox } from '$lib/components/ui/checkbox';
import * as Form from '$lib/components/form';
import { errorToast, successToast } from '$lib/utils/toast';
import { formatError } from '$lib/utils/error';
import type { Machine } from '$lib/api';
export let machine: Machine;
const dispatch = createEventDispatcher<{ submit: undefined }>();
let open: boolean;
const schema = z.object({
confirm: z.literal(true, {
errorMap: (issue) => ({ ...issue, message: 'Acknowledgement required to proceed' })
})
});
const form = superForm(defaults(zod(schema)), {
SPA: true,
dataType: 'json',
invalidateAll: true,
validators: zod(schema),
async onUpdate(ev) {
if (ev.form.valid) {
try {
const { error } = await machine.delete();
if (error) throw error;
successToast(`Deleted machine "${machine.givenName || machine.name}"`);
dispatch('submit');
open = false;
} catch (err) {
console.error(err);
errorToast(formatError(err));
}
}
}
});
const { form: formData, constraints } = form;
</script>
<Dialog.Root bind:open>
<Dialog.Trigger asChild let:builder>
<slot name="trigger" {builder} />
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Delete machine</Dialog.Title>
</Dialog.Header>
<Form.Root {form} destructive hasRequired class="mt-4" disabled={!$formData.confirm}>
<Form.Field {form} name="confirm">
<Form.Control let:attrs>
<div class="flex flex-row-reverse items-center justify-end gap-2.5">
<Form.Label for={attrs.id}>I understand that this action can not be undone</Form.Label>
<Checkbox
class="border-current"
{...attrs}
{...$constraints.confirm || {}}
bind:checked={$formData.confirm}
/>
</div>
<Form.FieldErrors />
</Form.Control>
</Form.Field>
</Form.Root>
</Dialog.Content>
</Dialog.Root> -->
Loading

0 comments on commit 06c6050

Please sign in to comment.