@axium/server 0.37.1 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,40 +1,45 @@
1
- <script>
1
+ <script lang="ts">
2
+ import { text } from '@axium/client';
2
3
  import { Version } from '@axium/client/components';
3
4
  import { Severity } from '@axium/core';
4
- import { capitalize } from 'utilium';
5
+ import { getPackage } from '@axium/core/packages';
6
+ import { _throw, capitalize } from 'utilium';
5
7
 
6
8
  const { data } = $props();
9
+ const packages = ['server', 'core', 'client'] as const;
7
10
  </script>
8
11
 
9
12
  <svelte:head>
10
- <title>Admin — Dashboard</title>
13
+ <title>{text('page.admin.dashboard.title')}</title>
11
14
  </svelte:head>
12
15
 
13
- <h2>Administration</h2>
16
+ <h2>{text('page.admin.heading')}</h2>
14
17
 
15
- {#each ['server', 'core', 'client'] as name}
16
- {@const info = data.versions[name]}
17
- <p>Axium {capitalize(name)} <Version v={info.version} latest={info.latest} /></p>
18
+ {#each packages as name}
19
+ <p>
20
+ Axium {capitalize(name)}
21
+ <Version v={data.versions[name]} latest={getPackage('@axium/' + name).then(pkg => pkg?._latest || _throw(null))} />
22
+ </p>
18
23
  {/each}
19
24
 
20
- <h3><a href="/admin/users">Users</a></h3>
25
+ <h3><a href="/admin/users">{text('page.admin.dashboard.users_link')}</a></h3>
21
26
 
22
- <p>{data.users} users, {data.sessions} sessions, {data.passkeys} passkeys.</p>
27
+ <p>{text('page.admin.dashboard.stats', { users: data.users, sessions: data.sessions, passkeys: data.passkeys })}</p>
23
28
 
24
- <h3><a href="/admin/config">Configuration</a></h3>
29
+ <h3><a href="/admin/config">{text('page.admin.dashboard.config_link')}</a></h3>
25
30
 
26
- <p>{data.configFiles} files loaded.</p>
31
+ <p>{text('page.admin.dashboard.config_files', { count: data.configFiles })}</p>
27
32
 
28
- <h3><a href="/admin/plugins">Plugins</a></h3>
33
+ <h3><a href="/admin/plugins">{text('page.admin.dashboard.plugins_link')}</a></h3>
29
34
 
30
- <p>{data.plugins} plugins loaded.</p>
35
+ <p>{text('page.admin.dashboard.plugins_loaded', { count: data.plugins })}</p>
31
36
 
32
- <h3><a href="/admin/audit">Audit Log</a></h3>
37
+ <h3><a href="/admin/audit">{text('page.admin.audit.heading')}</a></h3>
33
38
 
34
39
  <p>
35
- {Object.entries(data.auditEvents)
36
- .filter(([, count]) => count)
37
- .map(([severity, count]) => `${count} ${Severity[severity].toUpperCase()} events`)
40
+ {data.auditEvents
41
+ .map((count, severity) => count && `${count} ${Severity[severity].toUpperCase()} events`)
42
+ .filter(v => v)
38
43
  .join(', ')}.
39
44
  </p>
40
45
 
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { text } from '@axium/client';
2
3
  import { Icon } from '@axium/client/components';
3
4
  import '@axium/client/styles/list';
4
5
  import './styles.css';
@@ -9,14 +10,14 @@
9
10
  </script>
10
11
 
11
12
  <svelte:head>
12
- <title>Admin — Audit Log</title>
13
+ <title>{text('page.admin.audit.title')}</title>
13
14
  </svelte:head>
14
15
 
15
- <h2>Audit Log</h2>
16
+ <h2>{text('page.admin.audit.heading')}</h2>
16
17
 
17
18
  {#if data.filterError}
18
19
  <div class="error">
19
- <strong>Invalid Filter:</strong>
20
+ <strong>{text('page.admin.audit.invalid_filter')}</strong>
20
21
  {#each data.filterError.split('\n') as line}
21
22
  <p>{line}</p>
22
23
  {/each}
@@ -24,10 +25,10 @@
24
25
  {/if}
25
26
 
26
27
  <form id="filter" method="dialog">
27
- <h4>Filters</h4>
28
+ <h4>{text('page.admin.audit.filters')}</h4>
28
29
 
29
30
  <div class="filter-field">
30
- <span>Minimum Severity:</span>
31
+ <span>{text('page.admin.audit.filter.severity')}</span>
31
32
  <select name="severity" value={data.filter.severity}>
32
33
  {#each severityNames as value}
33
34
  <option {value} selected={value == 'info'}>{capitalize(value)}</option>
@@ -36,25 +37,25 @@
36
37
  </div>
37
38
 
38
39
  <div class="filter-field">
39
- <span>Since:</span>
40
+ <span>{text('page.admin.audit.filter.since')}</span>
40
41
  <input type="date" name="since" value={data.filter.since} />
41
42
  </div>
42
43
 
43
44
  <div class="filter-field">
44
- <span>Until:</span>
45
+ <span>{text('page.admin.audit.filter.until')}</span>
45
46
  <input type="date" name="until" value={data.filter.until} />
46
47
  </div>
47
48
 
48
49
  <div class="filter-field">
49
- <span>Tags:</span>
50
+ <span>{text('page.admin.audit.filter.tags')}</span>
50
51
  <input type="text" name="tags" value={data.filter.tags} />
51
52
  </div>
52
53
 
53
54
  <div class="filter-field">
54
- <span>Event Name:</span>
55
+ <span>{text('page.admin.audit.filter.event')}</span>
55
56
  {#if data.configured}
56
57
  <select name="event">
57
- <option value="">Any</option>
58
+ <option value="">{text('page.admin.audit.any')}</option>
58
59
  {#each data.configured.name as name}
59
60
  <option value={name} selected={data.filter.event == name}>{name}</option>
60
61
  {/each}
@@ -65,10 +66,10 @@
65
66
  </div>
66
67
 
67
68
  <div class="filter-field">
68
- <span>Source:</span>
69
+ <span>{text('page.admin.audit.filter.source')}</span>
69
70
  {#if data.configured}
70
71
  <select name="source">
71
- <option value="">Any</option>
72
+ <option value="">{text('page.admin.audit.any')}</option>
72
73
  {#each data.configured.source as source}
73
74
  <option value={source} selected={data.filter.source == source}>{source}</option>
74
75
  {/each}
@@ -79,7 +80,7 @@
79
80
  </div>
80
81
 
81
82
  <div class="filter-field">
82
- <span>User UUID:</span>
83
+ <span>{text('page.admin.audit.filter.user')}</span>
83
84
  <input type="text" name="user" size="36" value={data.filter.user} />
84
85
  </div>
85
86
 
@@ -112,14 +113,14 @@
112
113
  }
113
114
  }
114
115
  location.search = params ? '?' + params.toString() : '';
115
- }}>Apply</button
116
+ }}>{text('page.admin.audit.apply')}</button
116
117
  >
117
118
  <button
118
119
  class="inline-button"
119
120
  onclick={e => {
120
121
  e.preventDefault();
121
122
  location.search = '';
122
- }}>Reset</button
123
+ }}>{text('page.admin.audit.reset')}</button
123
124
  >
124
125
  </div>
125
126
  </form>
@@ -127,17 +128,17 @@
127
128
  <div class="list-container">
128
129
  <div class="list">
129
130
  <div class="list-item list-header">
130
- <span>Timestamp</span>
131
- <span>Severity</span>
132
- <span>Source</span>
133
- <span>Name</span>
134
- <span>Tags</span>
135
- <span>User</span>
131
+ <span>{text('page.admin.audit.timestamp')}</span>
132
+ <span>{text('page.admin.audit.severity')}</span>
133
+ <span>{text('page.admin.audit.source')}</span>
134
+ <span>{text('page.admin.audit.name')}</span>
135
+ <span>{text('page.admin.audit.tags')}</span>
136
+ <span>{text('page.admin.audit.user')}</span>
136
137
  </div>
137
138
 
138
139
  {#each data.events as event}
139
140
  <div class="list-item" onclick={e => e.currentTarget === e.target && (location.href = '/admin/audit/' + event.id)}>
140
- <span>{new Date(event.timestamp).toLocaleString()}</span>
141
+ <span>{event.timestamp.toLocaleString()}</span>
141
142
  <span class="severity--{Severity[event.severity].toLowerCase()}">{Severity[event.severity]}</span>
142
143
  <span>{event.source}</span>
143
144
  <span>{event.name}</span>
@@ -148,12 +149,12 @@
148
149
  {#if event.userId === data.session?.userId}<span class="subtle">(You)</span>{/if}
149
150
  </a>
150
151
  {:else}
151
- <i>Unknown</i>
152
+ <i>{text('generic.unknown')}</i>
152
153
  {/if}
153
154
  <a href="/admin/audit/{event.id}"><Icon i="chevron-right" /></a>
154
155
  </div>
155
156
  {:else}
156
- <p class="list-empty">No audit log events found</p>
157
+ <p class="list-empty">{text('page.admin.audit.no_events')}</p>
157
158
  {/each}
158
159
  </div>
159
160
  </div>
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { text } from '@axium/client';
2
3
  import { Severity } from '@axium/core/audit';
3
4
  import '../styles.css';
4
5
  import UserCard from '@axium/client/components/UserCard';
@@ -8,40 +9,40 @@
8
9
  </script>
9
10
 
10
11
  <svelte:head>
11
- <title>Admin Audit Log Event #{event.id}</title>
12
+ <title>{text('page.admin.audit.event_title', { id: event.id })}</title>
12
13
  </svelte:head>
13
14
 
14
- <h2>Audit Event</h2>
15
+ <h2>{text('page.admin.audit.event_heading')}</h2>
15
16
 
16
- <h4>UUID</h4>
17
+ <h4>{text('page.admin.audit.uuid')}</h4>
17
18
  <p>{event.id}</p>
18
19
 
19
- <h4>Severity</h4>
20
+ <h4>{text('page.admin.audit.severity')}</h4>
20
21
  <p class="severity--{Severity[event.severity].toLowerCase()}">{Severity[event.severity]}</p>
21
22
 
22
- <h4>Name</h4>
23
+ <h4>{text('page.admin.audit.name')}</h4>
23
24
  <p>{event.name}</p>
24
25
 
25
- <h4>Timestamp</h4>
26
- <p>{new Date(event.timestamp).toLocaleString()}</p>
26
+ <h4>{text('page.admin.audit.timestamp')}</h4>
27
+ <p>{event.timestamp.toLocaleString()}</p>
27
28
 
28
- <h4>Source</h4>
29
+ <h4>{text('page.admin.audit.source')}</h4>
29
30
  <p>{event.source}</p>
30
31
 
31
- <h4>Tags</h4>
32
+ <h4>{text('page.admin.audit.tags')}</h4>
32
33
  <p>{event.tags.join(', ')}</p>
33
34
 
34
- <h4>User</h4>
35
+ <h4>{text('page.admin.audit.user')}</h4>
35
36
  {#if event.user}
36
37
  <UserCard user={event.user} href="/admin/users/{event.user.id}" />
37
38
  {:else}
38
- <i>Unknown</i>
39
+ <i>{text('generic.unknown')}</i>
39
40
  {/if}
40
41
 
41
- <h4>Extra Data</h4>
42
+ <h4>{text('page.admin.audit.extra_data')}</h4>
42
43
 
43
44
  {#if event.name == 'response_error'}
44
- <h5>Error Stack</h5>
45
+ <h5>{text('page.admin.audit.error_stack')}</h5>
45
46
  <pre>{event.extra.stack}</pre>
46
47
  {:else}
47
48
  <pre>{JSON.stringify(event.extra, null, 4)}</pre>
@@ -1,16 +1,17 @@
1
1
  <script lang="ts">
2
+ import { text } from '@axium/client';
2
3
  const { data } = $props();
3
4
  </script>
4
5
 
5
6
  <svelte:head>
6
- <title>Admin — Configuration</title>
7
+ <title>{text('page.admin.config.title')}</title>
7
8
  </svelte:head>
8
9
 
9
- <h2>Active Configuration</h2>
10
+ <h2>{text('page.admin.config.active')}</h2>
10
11
 
11
12
  <pre>{JSON.stringify(data.config, null, 4)}</pre>
12
13
 
13
- <h2 id="files">Loaded Files</h2>
14
+ <h2 id="files">{text('page.admin.config.loaded_files')}</h2>
14
15
 
15
16
  {#each Object.entries(data.files) as [path, config]}
16
17
  <details>
@@ -1,23 +1,31 @@
1
1
  <script lang="ts">
2
+ import { text } from '@axium/client';
2
3
  import { Version, ZodForm } from '@axium/client/components';
3
4
  import { fetchAPI } from '@axium/client/requests';
4
5
  import { serverConfigs } from '@axium/core';
6
+ import { getPackage } from '@axium/core/packages';
7
+ import { _throw } from 'utilium';
5
8
 
6
9
  const { data } = $props();
7
10
  </script>
8
11
 
9
12
  <svelte:head>
10
- <title>Admin — Plugins</title>
13
+ <title>{text('page.admin.plugins.title')}</title>
11
14
  </svelte:head>
12
15
 
13
- <h2>Plugins</h2>
16
+ <h2>{text('page.admin.plugins.heading')}</h2>
14
17
 
15
18
  {#each data.plugins as plugin}
16
19
  {@const cfg = serverConfigs.get(plugin.name)}
17
20
  <div class="plugin">
18
- <h3>{plugin.name}<Version v={plugin.version} latest={plugin.latest} /></h3>
21
+ <h3>
22
+ {plugin.name}<Version
23
+ v={plugin.version}
24
+ latest={plugin.update_checks ? getPackage(plugin.name).then(p => p?._latest || _throw(null)) : null}
25
+ />
26
+ </h3>
19
27
  <p>
20
- <strong>Loaded from</strong>
28
+ <strong>{text('page.admin.plugins.loaded_from')}</strong>
21
29
  {#if plugin.path.endsWith('/package.json')}
22
30
  <span class="path plugin-path">{plugin.path.slice(0, -13)}</span>
23
31
  {:else}
@@ -28,18 +36,18 @@
28
36
  <a class="path" href="/admin/config#{plugin.loadedBy}">{plugin.loadedBy}</a>
29
37
  {/if}
30
38
  </p>
31
- <p><strong>Author:</strong> {plugin.author}</p>
39
+ <p><strong>{text('page.admin.plugins.author')}</strong> {plugin.author}</p>
32
40
  <p class="apps">
33
- <strong>Provided apps:</strong>
41
+ <strong>{text('page.admin.plugins.provided_apps')}</strong>
34
42
  {#if plugin.apps?.length}
35
43
  {#each plugin.apps as app, i}
36
44
  <a href="/{app.id}">{app.name}</a>{i != plugin.apps.length - 1 ? ', ' : ''}
37
45
  {/each}
38
- {:else}<i>None</i>{/if}
46
+ {:else}<i>{text('generic.none')}</i>{/if}
39
47
  </p>
40
48
  <p>{plugin.description}</p>
41
49
  {#if cfg && plugin.config}
42
- <h4>Configuration</h4>
50
+ <h4>{text('page.admin.plugins.configuration')}</h4>
43
51
  {@const { schema, labels } = cfg}
44
52
  <ZodForm
45
53
  rootValue={plugin.config}
@@ -51,7 +59,7 @@
51
59
  {/if}
52
60
  </div>
53
61
  {:else}
54
- <i>No plugins loaded.</i>
62
+ <i>{text('page.admin.plugins.none')}</i>
55
63
  {/each}
56
64
 
57
65
  <style>
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { text } from '@axium/client';
2
3
  import { FormDialog, Icon, URLText } from '@axium/client/components';
3
4
  import { fetchAPI } from '@axium/client/requests';
4
5
  import '@axium/client/styles/list';
@@ -13,10 +14,10 @@
13
14
  </script>
14
15
 
15
16
  <svelte:head>
16
- <title>Admin — Users</title>
17
+ <title>{text('page.admin.users.title')}</title>
17
18
  </svelte:head>
18
19
 
19
- <h2>Users</h2>
20
+ <h2>{text('page.admin.users.heading')}</h2>
20
21
 
21
22
  {#snippet attr(i: string, text: string, color: string = colorHashRGB(text))}
22
23
  <span class="attribute" style:background-color={color}><Icon {i} />{text}</span>
@@ -24,12 +25,12 @@
24
25
 
25
26
  <button command="show-modal" commandfor="create-user" class="icon-text">
26
27
  <Icon i="plus" />
27
- Create User
28
+ {text('page.admin.users.create')}
28
29
  </button>
29
30
 
30
31
  <FormDialog
31
32
  id="create-user"
32
- submitText="Create"
33
+ submitText={text('generic.create')}
33
34
  submit={(data: { email: string; name: string }) =>
34
35
  fetchAPI('PUT', 'admin/users', data).then(res => {
35
36
  verification = res.verification;
@@ -38,30 +39,30 @@
38
39
  })}
39
40
  >
40
41
  <div>
41
- <label for="email">Email</label>
42
+ <label for="email">{text('generic.email')}</label>
42
43
  <input name="email" type="email" required />
43
44
  </div>
44
45
  <div>
45
- <label for="name">Name</label>
46
+ <label for="name">{text('generic.username')}</label>
46
47
  <input name="name" type="text" required />
47
48
  </div>
48
49
  </FormDialog>
49
50
 
50
51
  <dialog bind:this={createdUserDialog} id="created-user-verification">
51
- <h3>New User Created</h3>
52
+ <h3>{text('page.admin.users.created_title')}</h3>
52
53
 
53
- <p>They can log in using this URL:</p>
54
+ <p>{text('page.admin.users.created_url')}</p>
54
55
 
55
56
  <URLText url="/login/token?user={verification?.userId}&token={verification?.token}" />
56
57
 
57
- <button onclick={() => createdUserDialog?.close()}>Okay</button>
58
+ <button onclick={() => createdUserDialog?.close()}>{text('generic.ok')}</button>
58
59
  </dialog>
59
60
 
60
61
  <div id="user-list" class="list">
61
62
  <div class="list-item list-header">
62
- <span>Name</span>
63
- <span>Email</span>
64
- <span>Attributes</span>
63
+ <span>{text('generic.username')}</span>
64
+ <span>{text('generic.email')}</span>
65
+ <span>{text('page.admin.users.attributes')}</span>
65
66
  </div>
66
67
  {#each users as user}
67
68
  <div class="user list-item" onclick={e => e.currentTarget === e.target && (location.href = '/admin/users/' + user.id)}>
@@ -69,7 +70,7 @@
69
70
  <span>{user.email}</span>
70
71
  <span class="mobile-hide">
71
72
  {#if user.isAdmin}
72
- {@render attr('crown', 'Admin', '#710')}
73
+ {@render attr('crown', text('page.admin.users.admin_tag'), '#710')}
73
74
  {/if}
74
75
  {#each user.tags as tag}
75
76
  {@render attr('hashtag', tag)}
@@ -80,15 +81,15 @@
80
81
  </span>
81
82
  <a class="icon-text mobile-button" href="/admin/audit?user={user.id}">
82
83
  <Icon i="file-shield" />
83
- <span class="mobile-only">Audit</span>
84
+ <span class="mobile-only">{text('page.admin.users.audit')}</span>
84
85
  </a>
85
86
  <a class="icon-text mobile-button" href="/admin/users/{user.id}">
86
87
  <Icon i="chevron-right" />
87
- <span class="mobile-only">Manage</span>
88
+ <span class="mobile-only">{text('page.admin.users.manage')}</span>
88
89
  </a>
89
90
  </div>
90
91
  {:else}
91
- <div class="error">No users!</div>
92
+ <div class="error">{text('page.admin.users.none')}</div>
92
93
  {/each}
93
94
  </div>
94
95
 
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { text } from '@axium/client';
2
3
  import { ClipboardCopy, FormDialog, Icon, SessionList, ZodForm, ZodInput } from '@axium/client/components';
3
4
  import { fetchAPI } from '@axium/client/requests';
4
5
  import '@axium/client/styles/account';
@@ -19,108 +20,110 @@
19
20
  </script>
20
21
 
21
22
  <svelte:head>
22
- <title>Admin — User Management</title>
23
+ <title>{text('page.admin.users.manage_title')}</title>
23
24
  </svelte:head>
24
25
 
25
26
  <a href="/admin/users">
26
27
  <button class="icon-text">
27
- <Icon i="up-left" /> Back to all users
28
+ <Icon i="up-left" />
29
+ {text('page.admin.users.back')}
28
30
  </button>
29
31
  </a>
30
32
 
31
- <h2>User Management</h2>
33
+ <h2>{text('page.admin.users.manage_heading')}</h2>
32
34
 
33
35
  <div id="info" class="section main">
34
36
  <div class="item info">
35
- <p>UUID</p>
37
+ <p>{text('page.admin.users.uuid')}</p>
36
38
  <p>{user.id}</p>
37
39
  <ClipboardCopy value={user.id} --size="16px" />
38
40
  </div>
39
41
 
40
42
  <div class="item info">
41
- <p>Display Name</p>
43
+ <p>{text('page.admin.users.display_name')}</p>
42
44
  <p>{user.name}</p>
43
45
  <ClipboardCopy value={user.name} --size="16px" />
44
46
  </div>
45
47
 
46
48
  <div class="item info">
47
- <p>Email</p>
49
+ <p>{text('generic.email')}</p>
48
50
  <p>
49
51
  <a href="mailto:{user.email}">{user.email}</a>, {user.emailVerified
50
- ? 'verified ' + user.emailVerified.toLocaleString()
51
- : 'not verified'}
52
+ ? text('page.admin.users.email_verified', { date: user.emailVerified.toLocaleString() })
53
+ : text('page.admin.users.email_not_verified')}
52
54
  </p>
53
55
  <ClipboardCopy value={user.email} --size="16px" />
54
56
  </div>
55
57
 
56
58
  <div class="item info">
57
- <p>Registered</p>
59
+ <p>{text('page.admin.users.registered')}</p>
58
60
  <p>{formatDateRange(user.registeredAt)}</p>
59
61
  <ClipboardCopy value={user.registeredAt.toISOString()} --size="16px" />
60
62
  </div>
61
63
  <div class="item info">
62
- <p>Administrator</p>
64
+ <p>{text('page.admin.users.administrator')}</p>
63
65
  {#if user.isAdmin}
64
- <strong>Yes</strong>
66
+ <strong>{text('generic.yes')}</strong>
65
67
  {:else}
66
- <p>No</p>
68
+ <p>{text('generic.no')}</p>
67
69
  {/if}
68
70
  <p></p>
69
71
  </div>
70
72
  <div class="item info">
71
- <p>Suspended</p>
73
+ <p>{text('page.admin.users.suspended')}</p>
72
74
  {#if user.isSuspended}
73
- <strong>Yes</strong>
75
+ <strong>{text('generic.yes')}</strong>
74
76
  {:else}
75
- <p>No</p>
77
+ <p>{text('generic.no')}</p>
76
78
  {/if}
77
79
  <button
78
80
  onclick={async () => {
79
81
  const { isSuspended } = await fetchAPI('PATCH', 'admin/users', { isSuspended: !user.isSuspended, id: user.id });
80
82
  user.isSuspended = isSuspended;
81
- }}>{user.isSuspended ? 'Unsuspend' : 'Suspend'}</button
83
+ }}>{user.isSuspended ? text('page.admin.users.unsuspend') : text('page.admin.users.suspend')}</button
82
84
  >
83
85
  </div>
84
86
  <div class="item info">
85
- <p>Profile Image</p>
87
+ <p>{text('page.admin.users.profile_image')}</p>
86
88
  {#if user.image}
87
89
  <a href={user.image} target="_blank" rel="noopener noreferrer">{user.image}</a>
88
90
  <ClipboardCopy value={user.image} --size="16px" />
89
91
  {:else}
90
- <i>Default</i>
92
+ <i>{text('page.admin.users.default_image')}</i>
91
93
  <p></p>
92
94
  {/if}
93
95
  </div>
94
96
  <div class="item info">
95
- <p>Roles</p>
97
+ <p>{text('page.admin.users.roles')}</p>
96
98
  <ZodInput bind:rootValue={user} path="roles" schema={User.shape.roles} {updateValue} noLabel />
97
99
  </div>
98
100
  <div class="item info">
99
- <p>Tags</p>
101
+ <p>{text('page.admin.users.tags')}</p>
100
102
  <ZodInput bind:rootValue={user} path="tags" schema={User.shape.tags} {updateValue} noLabel />
101
103
  </div>
102
104
 
103
105
  <button class="inline-button icon-text danger" command="show-modal" commandfor="delete-user">
104
- <Icon i="trash" /> Delete User
106
+ <Icon i="trash" />
107
+ {text('page.admin.users.delete_user')}
105
108
  </button>
106
109
 
107
110
  <FormDialog
108
111
  id="delete-user"
109
112
  submit={() => deleteUser(user.id, session?.userId).then(() => (window.location.href = '/admin/users'))}
110
- submitText="Delete User"
113
+ submitText={text('page.admin.users.delete_user')}
111
114
  submitDanger
112
115
  >
113
- <p>Are you sure you want to delete this user?<br />This action can't be undone.</p>
116
+ <p>{text('page.admin.users.delete_confirm')}<br />{text('generic.action_irreversible')}</p>
114
117
  </FormDialog>
115
118
  </div>
116
119
 
117
120
  <div id="sessions" class="section main">
118
- <h3>Sessions</h3>
121
+ <h3>{text('generic.sessions')}</h3>
119
122
  <SessionList {sessions} {user} />
120
123
  </div>
121
124
 
122
125
  <div id="preferences" class="section main">
123
- <h3>Preferences</h3>
126
+ <h3>{text('generic.preferences')}</h3>
124
127
  <ZodForm
125
128
  bind:rootValue={user.preferences}
126
129
  schema={Preferences}
@@ -1,9 +1,10 @@
1
1
  <script lang="ts">
2
+ import { text } from '@axium/client';
2
3
  import Login from '@axium/client/components/Login';
3
4
  </script>
4
5
 
5
6
  <svelte:head>
6
- <title>Login</title>
7
+ <title>{text('generic.login')}</title>
7
8
  </svelte:head>
8
9
 
9
10
  <Login fullPage />
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { text } from '@axium/client';
2
3
  import Icon from '@axium/client/components/Icon';
3
4
  import { fetchAPI } from '@axium/client/requests';
4
5
  import { startAuthentication } from '@simplewebauthn/browser';
@@ -28,7 +29,7 @@
28
29
  </script>
29
30
 
30
31
  <svelte:head>
31
- <title>Local Client Login</title>
32
+ <title>{text('page.login.client.title')}</title>
32
33
  </svelte:head>
33
34
 
34
35
  {#if error}
@@ -36,15 +37,15 @@
36
37
  {:else if authDone}
37
38
  <div class="center success">
38
39
  <h1><Icon i="check" /></h1>
39
- <p>Login successful! You can close this tab.</p>
40
+ <p>{text('page.login.client.success')}</p>
40
41
  </div>
41
42
  {:else}
42
43
  <div id="local-login" class="center">
43
- <h2>Local Client Login</h2>
44
- <p>Are you sure you want to log in to this local client?</p>
44
+ <h2>{text('page.login.client.title')}</h2>
45
+ <p>{text('page.login.client.confirm')}</p>
45
46
  <div>
46
- <button>Cancel</button>
47
- <button class="danger" {onclick}>Authorize</button>
47
+ <button>{text('generic.cancel')}</button>
48
+ <button class="danger" {onclick}>{text('page.login.client.authorize')}</button>
48
49
  </div>
49
50
  </div>
50
51
  {/if}
@@ -67,6 +68,6 @@
67
68
 
68
69
  #local-login {
69
70
  background-color: var(--bg-menu);
70
- border: 1px solid var(--border-accent);
71
+ border: var(--border-accent);
71
72
  }
72
73
  </style>