sh3-core 0.19.6 → 0.20.2
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.
- package/dist/app/admin/AuthSettingsView.svelte +3 -9
- package/dist/app/admin/MountsView.svelte +276 -0
- package/dist/app/admin/MountsView.svelte.d.ts +3 -0
- package/dist/app/admin/SystemView.svelte +6 -6
- package/dist/app/admin/UsersView.svelte +103 -7
- package/dist/app/admin/adminApp.js +1 -0
- package/dist/app/admin/adminShard.svelte.js +10 -0
- package/dist/apps/lifecycle.js +1 -0
- package/dist/apps/types.d.ts +7 -0
- package/dist/assets/iconIds.generated.d.ts +1 -1
- package/dist/assets/iconIds.generated.js +1 -0
- package/dist/assets/icons.svg +5 -0
- package/dist/auth/admin-users.svelte.js +2 -1
- package/dist/auth/auth.svelte.d.ts +4 -5
- package/dist/auth/auth.svelte.js +5 -6
- package/dist/auth/types.d.ts +0 -2
- package/dist/chrome/CompactChrome.svelte +25 -6
- package/dist/chrome/FloatsSheet.svelte +7 -32
- package/dist/chrome/FloatsSheet.svelte.d.ts +1 -2
- package/dist/chrome/FloatsSheet.svelte.test.js +8 -14
- package/dist/chrome/MenuSheet.svelte +154 -148
- package/dist/chrome/MenuSheet.svelte.d.ts +1 -2
- package/dist/chrome/MenuSheet.svelte.test.js +24 -12
- package/dist/createShell.js +32 -21
- package/dist/createShell.remoteAuth.test.js +9 -3
- package/dist/documents/backends.d.ts +12 -0
- package/dist/documents/backends.js +230 -3
- package/dist/documents/backends.test.js +147 -1
- package/dist/documents/browse.d.ts +18 -1
- package/dist/documents/browse.js +40 -7
- package/dist/documents/browse.test.js +35 -35
- package/dist/documents/config.d.ts +6 -0
- package/dist/documents/config.js +18 -1
- package/dist/documents/handle.js +65 -17
- package/dist/documents/handle.test.js +88 -1
- package/dist/documents/http-backend.d.ts +6 -0
- package/dist/documents/http-backend.js +71 -2
- package/dist/documents/http-backend.test.js +51 -1
- package/dist/documents/index.d.ts +2 -2
- package/dist/documents/index.js +1 -1
- package/dist/documents/picker-api.d.ts +4 -2
- package/dist/documents/picker-api.test.d.ts +1 -1
- package/dist/documents/picker-api.test.js +89 -59
- package/dist/documents/picker-primitive.d.ts +4 -0
- package/dist/documents/picker-primitive.js +27 -29
- package/dist/documents/types.d.ts +93 -19
- package/dist/documents/types.js +6 -0
- package/dist/layout/presets.test.js +4 -4
- package/dist/layout/types.d.ts +1 -1
- package/dist/layouts-shard/LayoutsSection.svelte +3 -16
- package/dist/primitives/widgets/DocumentFilePicker.d.ts +6 -2
- package/dist/primitives/widgets/DocumentFilePicker.js +12 -5
- package/dist/primitives/widgets/DocumentFilePicker.svelte +27 -5
- package/dist/primitives/widgets/DocumentFilePicker.svelte.d.ts +14 -0
- package/dist/primitives/widgets/DocumentFilePicker.test.d.ts +1 -0
- package/dist/primitives/widgets/DocumentFilePicker.test.js +33 -0
- package/dist/primitives/widgets/DocumentOpener.svelte +20 -0
- package/dist/primitives/widgets/DocumentOpener.svelte.d.ts +14 -0
- package/dist/primitives/widgets/DocumentSaver.svelte +17 -0
- package/dist/primitives/widgets/DocumentSaver.svelte.d.ts +13 -0
- package/dist/primitives/widgets/PickerList.svelte +1 -0
- package/dist/primitives/widgets/_DocumentBrowser.svelte +419 -35
- package/dist/primitives/widgets/_DocumentBrowser.svelte.d.ts +12 -0
- package/dist/primitives/widgets/_DocumentBrowser.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/_DocumentBrowser.svelte.test.js +277 -0
- package/dist/primitives/widgets/_FolderConfirmDelete.svelte +57 -0
- package/dist/primitives/widgets/_FolderConfirmDelete.svelte.d.ts +12 -0
- package/dist/projects-shard/DeleteProjectDialog.svelte +32 -1
- package/dist/projects-shard/ProjectManage.svelte +197 -28
- package/dist/projects-shard/ProjectManage.svelte.test.d.ts +1 -0
- package/dist/projects-shard/ProjectManage.svelte.test.js +320 -0
- package/dist/projects-shard/ProjectsSection.svelte +3 -16
- package/dist/projects-shard/projectsApi.js +2 -1
- package/dist/registry/permission-descriptions.js +4 -0
- package/dist/server-shard/types.d.ts +21 -0
- package/dist/sh3Api/headless.js +10 -0
- package/dist/sh3core-shard/HomeSection.svelte +107 -0
- package/dist/sh3core-shard/HomeSection.svelte.d.ts +10 -0
- package/dist/sh3core-shard/Sh3Home.svelte +9 -23
- package/dist/shards/activate.svelte.d.ts +4 -0
- package/dist/shards/activate.svelte.js +11 -3
- package/dist/shards/types.d.ts +7 -0
- package/dist/shell-shard/Terminal.svelte +4 -1
- package/dist/shell-shard/Terminal.svelte.d.ts +2 -0
- package/dist/shell-shard/dispatch.d.ts +2 -0
- package/dist/shell-shard/dispatch.js +2 -0
- package/dist/shell-shard/manifest.js +7 -1
- package/dist/shell-shard/shellShard.svelte.js +1 -1
- package/dist/shell-shard/tenant-fs-client.js +2 -1
- package/dist/shell-shard/verbs/cat.d.ts +2 -0
- package/dist/shell-shard/verbs/cat.js +35 -0
- package/dist/shell-shard/verbs/cat.test.d.ts +1 -0
- package/dist/shell-shard/verbs/cat.test.js +49 -0
- package/dist/shell-shard/verbs/index.js +12 -0
- package/dist/shell-shard/verbs/ls.d.ts +2 -0
- package/dist/shell-shard/verbs/ls.js +48 -0
- package/dist/shell-shard/verbs/ls.test.d.ts +1 -0
- package/dist/shell-shard/verbs/ls.test.js +64 -0
- package/dist/shell-shard/verbs/mkdir.d.ts +2 -0
- package/dist/shell-shard/verbs/mkdir.js +30 -0
- package/dist/shell-shard/verbs/mkdir.test.d.ts +1 -0
- package/dist/shell-shard/verbs/mkdir.test.js +48 -0
- package/dist/shell-shard/verbs/mv.d.ts +2 -0
- package/dist/shell-shard/verbs/mv.js +33 -0
- package/dist/shell-shard/verbs/mv.test.d.ts +1 -0
- package/dist/shell-shard/verbs/mv.test.js +55 -0
- package/dist/shell-shard/verbs/rm.d.ts +2 -0
- package/dist/shell-shard/verbs/rm.js +28 -0
- package/dist/shell-shard/verbs/rm.test.d.ts +1 -0
- package/dist/shell-shard/verbs/rm.test.js +47 -0
- package/dist/shell-shard/verbs/scope-parse.d.ts +7 -0
- package/dist/shell-shard/verbs/scope-parse.js +33 -0
- package/dist/shell-shard/verbs/scope-parse.test.d.ts +1 -0
- package/dist/shell-shard/verbs/scope-parse.test.js +76 -0
- package/dist/shell-shard/verbs/xfer.d.ts +2 -0
- package/dist/shell-shard/verbs/xfer.js +101 -0
- package/dist/shell-shard/verbs/xfer.test.d.ts +1 -0
- package/dist/shell-shard/verbs/xfer.test.js +96 -0
- package/dist/transport/apiFetch.js +12 -5
- package/dist/verbs/types.d.ts +18 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { GlobalSettings } from '../../auth/types';
|
|
7
|
+
import { apiFetch } from '../../transport/apiFetch';
|
|
7
8
|
|
|
8
9
|
let settings = $state<GlobalSettings | null>(null);
|
|
9
10
|
let loading = $state(true);
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
async function fetchSettings() {
|
|
14
15
|
loading = true;
|
|
15
16
|
try {
|
|
16
|
-
const res = await
|
|
17
|
+
const res = await apiFetch('/api/admin/settings');
|
|
17
18
|
if (!res.ok) throw new Error('Failed to fetch settings');
|
|
18
19
|
settings = await res.json();
|
|
19
20
|
} catch (err) {
|
|
@@ -28,10 +29,9 @@
|
|
|
28
29
|
saving = true;
|
|
29
30
|
error = null;
|
|
30
31
|
try {
|
|
31
|
-
const res = await
|
|
32
|
+
const res = await apiFetch('/api/admin/settings', {
|
|
32
33
|
method: 'PUT',
|
|
33
34
|
headers: { 'Content-Type': 'application/json' },
|
|
34
|
-
credentials: 'include',
|
|
35
35
|
body: JSON.stringify(settings),
|
|
36
36
|
});
|
|
37
37
|
if (!res.ok) throw new Error('Failed to save settings');
|
|
@@ -53,12 +53,6 @@
|
|
|
53
53
|
<p class="admin-muted">Loading...</p>
|
|
54
54
|
{:else if settings}
|
|
55
55
|
<div class="admin-auth-fields">
|
|
56
|
-
<label class="admin-toggle">
|
|
57
|
-
<input type="checkbox" bind:checked={settings.auth.required} />
|
|
58
|
-
<span>Require sign-in</span>
|
|
59
|
-
<span class="admin-hint">When enabled, unauthenticated visitors see a sign-in wall.</span>
|
|
60
|
-
</label>
|
|
61
|
-
|
|
62
56
|
<label class="admin-toggle">
|
|
63
57
|
<input type="checkbox" bind:checked={settings.auth.guestAllowed} />
|
|
64
58
|
<span>Allow guest browsing</span>
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { apiFetch } from '../../transport/apiFetch';
|
|
3
|
+
|
|
4
|
+
interface MountEntry {
|
|
5
|
+
id: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
path: string;
|
|
8
|
+
status: 'resolved' | 'unresolved' | 'error';
|
|
9
|
+
attachmentCount: number;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let mounts = $state<MountEntry[]>([]);
|
|
15
|
+
let loading = $state(true);
|
|
16
|
+
let error = $state<string | null>(null);
|
|
17
|
+
|
|
18
|
+
// Create form
|
|
19
|
+
let showCreate = $state(false);
|
|
20
|
+
let newId = $state('');
|
|
21
|
+
let newLabel = $state('');
|
|
22
|
+
let newPath = $state('');
|
|
23
|
+
let createError = $state<string | null>(null);
|
|
24
|
+
let createWarning = $state<string | null>(null);
|
|
25
|
+
|
|
26
|
+
// Edit state
|
|
27
|
+
let editingId = $state<string | null>(null);
|
|
28
|
+
let editLabel = $state('');
|
|
29
|
+
let editPath = $state('');
|
|
30
|
+
|
|
31
|
+
// Browse state
|
|
32
|
+
let browsingId = $state<string | null>(null);
|
|
33
|
+
let browseEntries = $state<{ name: string; kind: string; size?: number }[]>([]);
|
|
34
|
+
let browseError = $state<string | null>(null);
|
|
35
|
+
|
|
36
|
+
// Delete confirmation
|
|
37
|
+
let deletingId = $state<string | null>(null);
|
|
38
|
+
|
|
39
|
+
async function fetchMounts() {
|
|
40
|
+
loading = true;
|
|
41
|
+
error = null;
|
|
42
|
+
try {
|
|
43
|
+
const res = await apiFetch('/api/admin/mounts');
|
|
44
|
+
if (!res.ok) throw new Error('Failed to fetch mounts');
|
|
45
|
+
mounts = await res.json();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
error = err instanceof Error ? err.message : 'Failed to load mounts';
|
|
48
|
+
} finally {
|
|
49
|
+
loading = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function createMount() {
|
|
54
|
+
createError = null;
|
|
55
|
+
createWarning = null;
|
|
56
|
+
try {
|
|
57
|
+
const res = await apiFetch('/api/admin/mounts', {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify({ id: newId, label: newLabel || undefined, path: newPath }),
|
|
61
|
+
});
|
|
62
|
+
const body = await res.json();
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
createError = body.error || 'Failed to create mount';
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (body.warning) createWarning = body.warning;
|
|
68
|
+
newId = '';
|
|
69
|
+
newLabel = '';
|
|
70
|
+
newPath = '';
|
|
71
|
+
showCreate = false;
|
|
72
|
+
createWarning = null;
|
|
73
|
+
await fetchMounts();
|
|
74
|
+
} catch {
|
|
75
|
+
createError = 'Network error';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function startEdit(mount: MountEntry) {
|
|
80
|
+
editingId = mount.id;
|
|
81
|
+
editLabel = mount.label || '';
|
|
82
|
+
editPath = mount.path;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function saveEdit() {
|
|
86
|
+
if (!editingId) return;
|
|
87
|
+
try {
|
|
88
|
+
const res = await apiFetch(`/api/admin/mounts/${editingId}`, {
|
|
89
|
+
method: 'PUT',
|
|
90
|
+
headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
body: JSON.stringify({ label: editLabel || undefined, path: editPath }),
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) return;
|
|
94
|
+
editingId = null;
|
|
95
|
+
await fetchMounts();
|
|
96
|
+
} catch { /* ignore */ }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function deleteMount(id: string) {
|
|
100
|
+
try {
|
|
101
|
+
await apiFetch(`/api/admin/mounts/${id}`, { method: 'DELETE' });
|
|
102
|
+
deletingId = null;
|
|
103
|
+
await fetchMounts();
|
|
104
|
+
} catch { /* ignore */ }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function toggleBrowse(id: string) {
|
|
108
|
+
if (browsingId === id) {
|
|
109
|
+
browsingId = null;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
browsingId = id;
|
|
113
|
+
browseError = null;
|
|
114
|
+
browseEntries = [];
|
|
115
|
+
try {
|
|
116
|
+
const res = await apiFetch(`/api/admin/mounts/${id}/browse`);
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
const body = await res.json().catch(() => ({}));
|
|
119
|
+
browseError = body.error || 'Failed to browse';
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
browseEntries = await res.json();
|
|
123
|
+
} catch {
|
|
124
|
+
browseError = 'Network error';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function statusColor(status: string): string {
|
|
129
|
+
return status === 'resolved' ? '#4caf50' : status === 'unresolved' ? '#ff9800' : '#f44336';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fetchMounts();
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<svelte:window onkeydown={(e) => { if (e.key === 'Escape' && deletingId) deletingId = null; }} />
|
|
136
|
+
|
|
137
|
+
<div class="admin-mounts">
|
|
138
|
+
<div class="admin-section-header">
|
|
139
|
+
<h2>Mounts</h2>
|
|
140
|
+
<button type="button" class="admin-btn" onclick={() => { showCreate = !showCreate; }}>
|
|
141
|
+
{showCreate ? 'Cancel' : 'New mount'}
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
{#if showCreate}
|
|
146
|
+
<form class="admin-create-form" onsubmit={(e) => { e.preventDefault(); createMount(); }}>
|
|
147
|
+
<input class="admin-input" type="text" placeholder="Mount ID (slug, e.g. game-assets)" bind:value={newId} />
|
|
148
|
+
<input class="admin-input" type="text" placeholder="Label (optional)" bind:value={newLabel} />
|
|
149
|
+
<input class="admin-input" type="text" placeholder="Absolute path on server" bind:value={newPath} />
|
|
150
|
+
<button type="submit" class="admin-btn" disabled={!newId.trim() || !newPath.trim()}>Create</button>
|
|
151
|
+
{#if createWarning}<div class="admin-warning">{createWarning}</div>{/if}
|
|
152
|
+
{#if createError}<div class="admin-error">{createError}</div>{/if}
|
|
153
|
+
</form>
|
|
154
|
+
{/if}
|
|
155
|
+
|
|
156
|
+
{#if loading}
|
|
157
|
+
<p class="admin-muted">Loading...</p>
|
|
158
|
+
{:else if error}
|
|
159
|
+
<p class="admin-error">{error}</p>
|
|
160
|
+
{:else}
|
|
161
|
+
<ul class="admin-mount-list">
|
|
162
|
+
{#each mounts as mount (mount.id)}
|
|
163
|
+
<li class="admin-mount-item">
|
|
164
|
+
<div class="admin-mount-main">
|
|
165
|
+
{#if editingId === mount.id}
|
|
166
|
+
<form class="admin-edit-form" onsubmit={(e) => { e.preventDefault(); saveEdit(); }}>
|
|
167
|
+
<input class="admin-input" type="text" bind:value={editLabel} placeholder="Label" />
|
|
168
|
+
<input class="admin-input" type="text" bind:value={editPath} placeholder="Path" />
|
|
169
|
+
<div class="admin-edit-actions">
|
|
170
|
+
<button type="submit" class="admin-btn">Save</button>
|
|
171
|
+
<button type="button" class="admin-btn-secondary" onclick={() => { editingId = null; }}>Cancel</button>
|
|
172
|
+
</div>
|
|
173
|
+
</form>
|
|
174
|
+
{:else}
|
|
175
|
+
<div class="admin-mount-info">
|
|
176
|
+
<div class="admin-mount-top">
|
|
177
|
+
<span class="admin-status-dot" style="background: {statusColor(mount.status)}" title={mount.status}></span>
|
|
178
|
+
<span class="admin-mount-name">{mount.label || mount.id}</span>
|
|
179
|
+
<span class="admin-mount-id">{mount.id}</span>
|
|
180
|
+
</div>
|
|
181
|
+
<span class="admin-mount-path" title={mount.path}>{mount.path}</span>
|
|
182
|
+
<span class="admin-mount-meta">{mount.attachmentCount} tenant(s) attached</span>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="admin-mount-actions">
|
|
185
|
+
<button type="button" class="admin-btn-secondary" onclick={() => toggleBrowse(mount.id)}>
|
|
186
|
+
{browsingId === mount.id ? 'Close' : 'Browse'}
|
|
187
|
+
</button>
|
|
188
|
+
<button type="button" class="admin-btn-secondary" onclick={() => startEdit(mount)}>Edit</button>
|
|
189
|
+
<button type="button" class="admin-btn-danger" onclick={() => { deletingId = mount.id; }}>Delete</button>
|
|
190
|
+
</div>
|
|
191
|
+
{/if}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{#if browsingId === mount.id}
|
|
195
|
+
<div class="admin-mount-browse">
|
|
196
|
+
{#if browseError}
|
|
197
|
+
<p class="admin-error">{browseError}</p>
|
|
198
|
+
{:else if browseEntries.length === 0}
|
|
199
|
+
<p class="admin-muted">Empty directory</p>
|
|
200
|
+
{:else}
|
|
201
|
+
<ul class="admin-browse-list">
|
|
202
|
+
{#each browseEntries as entry (entry.name)}
|
|
203
|
+
<li class="admin-browse-item">
|
|
204
|
+
<span class="admin-browse-kind">{entry.kind === 'directory' ? '[_]' : '[ ]'}</span>
|
|
205
|
+
<span>{entry.name}</span>
|
|
206
|
+
{#if entry.size !== undefined}
|
|
207
|
+
<span class="admin-browse-size">{entry.size} B</span>
|
|
208
|
+
{/if}
|
|
209
|
+
</li>
|
|
210
|
+
{/each}
|
|
211
|
+
</ul>
|
|
212
|
+
{/if}
|
|
213
|
+
</div>
|
|
214
|
+
{/if}
|
|
215
|
+
</li>
|
|
216
|
+
{/each}
|
|
217
|
+
</ul>
|
|
218
|
+
{/if}
|
|
219
|
+
|
|
220
|
+
{#if deletingId}
|
|
221
|
+
<div class="admin-modal-root">
|
|
222
|
+
<button
|
|
223
|
+
type="button"
|
|
224
|
+
class="admin-modal-backdrop"
|
|
225
|
+
aria-label="Close dialog"
|
|
226
|
+
onclick={() => { deletingId = null; }}
|
|
227
|
+
></button>
|
|
228
|
+
<div class="admin-modal" role="dialog" aria-modal="true" aria-labelledby="delete-mount-title">
|
|
229
|
+
<h3 id="delete-mount-title">Delete Mount</h3>
|
|
230
|
+
<p>This will remove the mount and all its tenant attachments. This cannot be undone.</p>
|
|
231
|
+
<div class="admin-modal-actions">
|
|
232
|
+
<button type="button" class="admin-btn-danger" onclick={() => deleteMount(deletingId!)}>Delete</button>
|
|
233
|
+
<button type="button" class="admin-btn-secondary" onclick={() => { deletingId = null; }}>Cancel</button>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
{/if}
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<style>
|
|
241
|
+
.admin-mounts { padding: 24px; font-family: system-ui, sans-serif; color: var(--sh3-fg); }
|
|
242
|
+
.admin-section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
|
|
243
|
+
.admin-section-header h2 { margin: 0; font-size: 18px; }
|
|
244
|
+
.admin-create-form, .admin-edit-form { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; max-width: 500px; }
|
|
245
|
+
.admin-input { padding: 8px 12px; background: var(--sh3-bg, #1a1a2e); color: var(--sh3-fg); border: 1px solid var(--sh3-border, #3a3a5c); border-radius: var(--sh3-radius, 6px); font-size: 13px; }
|
|
246
|
+
.admin-btn { font-weight: 600; font-size: 13px; }
|
|
247
|
+
.admin-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
248
|
+
.admin-btn-secondary { background: transparent; color: var(--sh3-fg-subtle); border: 1px solid var(--sh3-border); font-size: 12px; }
|
|
249
|
+
.admin-btn-danger { background: transparent; color: var(--sh3-error, #d32f2f); border: 1px solid var(--sh3-error, #d32f2f); font-size: 12px; }
|
|
250
|
+
.admin-mount-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
251
|
+
.admin-mount-item { padding: 12px 16px; background: var(--sh3-bg-elevated, #252540); border: 1px solid var(--sh3-border, #3a3a5c); border-radius: var(--sh3-radius, 6px); }
|
|
252
|
+
.admin-mount-main { display: flex; justify-content: space-between; align-items: flex-start; }
|
|
253
|
+
.admin-mount-info { display: flex; flex-direction: column; gap: 4px; flex: 1; }
|
|
254
|
+
.admin-mount-top { display: flex; align-items: center; gap: 8px; }
|
|
255
|
+
.admin-status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
256
|
+
.admin-mount-name { font-weight: 600; }
|
|
257
|
+
.admin-mount-id { font-size: 11px; color: var(--sh3-fg-subtle); font-family: monospace; }
|
|
258
|
+
.admin-mount-path { font-size: 12px; font-family: monospace; color: var(--sh3-fg-subtle); word-break: break-all; }
|
|
259
|
+
.admin-mount-meta { font-size: 11px; color: var(--sh3-fg-muted); }
|
|
260
|
+
.admin-mount-actions { display: flex; gap: 6px; flex-shrink: 0; margin-left: 16px; }
|
|
261
|
+
.admin-edit-actions { display: flex; gap: 6px; }
|
|
262
|
+
.admin-mount-browse { margin-top: 12px; padding: 12px; background: var(--sh3-bg, #1a1a2e); border-radius: var(--sh3-radius, 6px); max-height: 200px; overflow-y: auto; }
|
|
263
|
+
.admin-browse-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
|
|
264
|
+
.admin-browse-item { display: flex; gap: 8px; font-size: 12px; font-family: monospace; }
|
|
265
|
+
.admin-browse-kind { color: var(--sh3-fg-subtle); width: 24px; }
|
|
266
|
+
.admin-browse-size { color: var(--sh3-fg-muted); margin-left: auto; }
|
|
267
|
+
.admin-modal-root { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; z-index: 100; }
|
|
268
|
+
.admin-modal-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.6); border: none; padding: 0; margin: 0; cursor: pointer; }
|
|
269
|
+
.admin-modal { position: relative; background: var(--sh3-bg-elevated, #252540); border: 1px solid var(--sh3-border, #3a3a5c); border-radius: var(--sh3-radius, 8px); padding: 24px; max-width: 400px; width: 100%; }
|
|
270
|
+
.admin-modal h3 { margin: 0 0 8px; }
|
|
271
|
+
.admin-modal p { margin: 0 0 16px; font-size: 13px; color: var(--sh3-fg-subtle); }
|
|
272
|
+
.admin-modal-actions { display: flex; gap: 8px; justify-content: flex-end; }
|
|
273
|
+
.admin-error { color: var(--sh3-error, #d32f2f); font-size: 13px; }
|
|
274
|
+
.admin-warning { color: #ff9800; font-size: 13px; }
|
|
275
|
+
.admin-muted { color: var(--sh3-fg-muted); font-style: italic; }
|
|
276
|
+
</style>
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Admin System view — server status, restart, and package-bundle cache policy.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { apiFetch } from '../../transport/apiFetch';
|
|
7
|
+
|
|
6
8
|
const SNAP_POINTS: Array<{ value: number; label: string }> = [
|
|
7
9
|
{ value: 0, label: 'Off (no-store)' },
|
|
8
10
|
{ value: 5, label: '5s (dev)' },
|
|
@@ -35,7 +37,7 @@
|
|
|
35
37
|
|
|
36
38
|
async function fetchVersion() {
|
|
37
39
|
try {
|
|
38
|
-
const res = await
|
|
40
|
+
const res = await apiFetch('/api/version');
|
|
39
41
|
if (res.ok) {
|
|
40
42
|
const body = await res.json();
|
|
41
43
|
version = body.version;
|
|
@@ -45,7 +47,7 @@
|
|
|
45
47
|
|
|
46
48
|
async function fetchSettings() {
|
|
47
49
|
try {
|
|
48
|
-
const res = await
|
|
50
|
+
const res = await apiFetch('/api/admin/settings');
|
|
49
51
|
if (res.ok) {
|
|
50
52
|
const body = await res.json();
|
|
51
53
|
const age = body.packages?.cacheMaxAge ?? 31536000;
|
|
@@ -59,9 +61,8 @@
|
|
|
59
61
|
savingCache = true;
|
|
60
62
|
cacheError = null;
|
|
61
63
|
try {
|
|
62
|
-
const res = await
|
|
64
|
+
const res = await apiFetch('/api/admin/settings', {
|
|
63
65
|
method: 'PUT',
|
|
64
|
-
credentials: 'include',
|
|
65
66
|
headers: { 'Content-Type': 'application/json' },
|
|
66
67
|
body: JSON.stringify({ packages: { cacheMaxAge } }),
|
|
67
68
|
});
|
|
@@ -84,9 +85,8 @@
|
|
|
84
85
|
restarting = true;
|
|
85
86
|
restartError = null;
|
|
86
87
|
try {
|
|
87
|
-
const res = await
|
|
88
|
+
const res = await apiFetch('/api/admin/restart', {
|
|
88
89
|
method: 'POST',
|
|
89
|
-
credentials: 'include',
|
|
90
90
|
});
|
|
91
91
|
if (!res.ok) {
|
|
92
92
|
const body = await res.json().catch(() => ({}));
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { AuthUser } from '../../auth/types';
|
|
7
|
+
import { apiFetch } from '../../transport/apiFetch';
|
|
7
8
|
|
|
8
9
|
let users = $state<AuthUser[]>([]);
|
|
9
10
|
let loading = $state(true);
|
|
@@ -23,11 +24,54 @@
|
|
|
23
24
|
let editRole = $state<'admin' | 'user'>('user');
|
|
24
25
|
let editPassword = $state('');
|
|
25
26
|
|
|
27
|
+
// Mount attachment state
|
|
28
|
+
let mountModalUser = $state<AuthUser | null>(null);
|
|
29
|
+
let allMounts = $state<{ id: string; label?: string; status: string }[]>([]);
|
|
30
|
+
let attachedMountIds = $state<Set<string>>(new Set());
|
|
31
|
+
let mountLoading = $state(false);
|
|
32
|
+
|
|
33
|
+
async function openMountModal(user: AuthUser) {
|
|
34
|
+
mountModalUser = user;
|
|
35
|
+
mountLoading = true;
|
|
36
|
+
try {
|
|
37
|
+
const [mountsRes, attRes] = await Promise.all([
|
|
38
|
+
apiFetch('/api/admin/mounts'),
|
|
39
|
+
apiFetch(`/api/admin/tenants/${user.id}/attachments`),
|
|
40
|
+
]);
|
|
41
|
+
allMounts = mountsRes.ok ? await mountsRes.json() : [];
|
|
42
|
+
const atts: { mountId: string }[] = attRes.ok ? await attRes.json() : [];
|
|
43
|
+
attachedMountIds = new Set(atts.map(a => a.mountId));
|
|
44
|
+
} catch { /* ignore */ }
|
|
45
|
+
mountLoading = false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function toggleMountAttachment(mountId: string) {
|
|
49
|
+
if (!mountModalUser) return;
|
|
50
|
+
const wasAttached = attachedMountIds.has(mountId);
|
|
51
|
+
try {
|
|
52
|
+
if (wasAttached) {
|
|
53
|
+
await apiFetch('/api/admin/mount-attachments', {
|
|
54
|
+
method: 'DELETE',
|
|
55
|
+
headers: { 'Content-Type': 'application/json' },
|
|
56
|
+
body: JSON.stringify({ mountId, tenantId: mountModalUser.id }),
|
|
57
|
+
});
|
|
58
|
+
attachedMountIds = new Set([...attachedMountIds].filter(id => id !== mountId));
|
|
59
|
+
} else {
|
|
60
|
+
await apiFetch('/api/admin/mount-attachments', {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: { 'Content-Type': 'application/json' },
|
|
63
|
+
body: JSON.stringify({ mountId, tenantId: mountModalUser.id }),
|
|
64
|
+
});
|
|
65
|
+
attachedMountIds = new Set([...attachedMountIds, mountId]);
|
|
66
|
+
}
|
|
67
|
+
} catch { /* ignore */ }
|
|
68
|
+
}
|
|
69
|
+
|
|
26
70
|
async function fetchUsers() {
|
|
27
71
|
loading = true;
|
|
28
72
|
error = null;
|
|
29
73
|
try {
|
|
30
|
-
const res = await
|
|
74
|
+
const res = await apiFetch('/api/admin/users');
|
|
31
75
|
if (!res.ok) throw new Error('Failed to fetch users');
|
|
32
76
|
users = await res.json();
|
|
33
77
|
} catch (err) {
|
|
@@ -40,10 +84,9 @@
|
|
|
40
84
|
async function createUser() {
|
|
41
85
|
createError = null;
|
|
42
86
|
try {
|
|
43
|
-
const res = await
|
|
87
|
+
const res = await apiFetch('/api/admin/users', {
|
|
44
88
|
method: 'POST',
|
|
45
89
|
headers: { 'Content-Type': 'application/json' },
|
|
46
|
-
credentials: 'include',
|
|
47
90
|
body: JSON.stringify({
|
|
48
91
|
username: newUsername,
|
|
49
92
|
displayName: newDisplayName || newUsername,
|
|
@@ -82,10 +125,9 @@
|
|
|
82
125
|
};
|
|
83
126
|
if (editPassword.trim()) patch.password = editPassword;
|
|
84
127
|
try {
|
|
85
|
-
const res = await
|
|
128
|
+
const res = await apiFetch(`/api/admin/users/${editingId}`, {
|
|
86
129
|
method: 'PUT',
|
|
87
130
|
headers: { 'Content-Type': 'application/json' },
|
|
88
|
-
credentials: 'include',
|
|
89
131
|
body: JSON.stringify(patch),
|
|
90
132
|
});
|
|
91
133
|
if (!res.ok) return;
|
|
@@ -96,9 +138,8 @@
|
|
|
96
138
|
|
|
97
139
|
async function deleteUser(id: string) {
|
|
98
140
|
try {
|
|
99
|
-
await
|
|
141
|
+
await apiFetch(`/api/admin/users/${id}`, {
|
|
100
142
|
method: 'DELETE',
|
|
101
|
-
credentials: 'include',
|
|
102
143
|
});
|
|
103
144
|
await fetchUsers();
|
|
104
145
|
} catch { /* ignore */ }
|
|
@@ -107,6 +148,8 @@
|
|
|
107
148
|
fetchUsers();
|
|
108
149
|
</script>
|
|
109
150
|
|
|
151
|
+
<svelte:window onkeydown={(e) => { if (e.key === 'Escape' && mountModalUser) mountModalUser = null; }} />
|
|
152
|
+
|
|
110
153
|
<div class="admin-users">
|
|
111
154
|
<div class="admin-users-header">
|
|
112
155
|
<h2>Users</h2>
|
|
@@ -156,6 +199,7 @@
|
|
|
156
199
|
<span class="admin-user-meta">{user.username} · {user.role}</span>
|
|
157
200
|
</div>
|
|
158
201
|
<div class="admin-user-actions">
|
|
202
|
+
<button type="button" class="admin-btn-secondary" onclick={() => openMountModal(user)}>Mounts</button>
|
|
159
203
|
<button type="button" class="admin-btn-secondary" onclick={() => startEdit(user)}>Edit</button>
|
|
160
204
|
<button type="button" class="admin-btn-danger" onclick={() => deleteUser(user.id)}>Delete</button>
|
|
161
205
|
</div>
|
|
@@ -164,6 +208,46 @@
|
|
|
164
208
|
{/each}
|
|
165
209
|
</ul>
|
|
166
210
|
{/if}
|
|
211
|
+
|
|
212
|
+
{#if mountModalUser}
|
|
213
|
+
<div class="admin-modal-root">
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
class="admin-modal-backdrop"
|
|
217
|
+
aria-label="Close dialog"
|
|
218
|
+
onclick={() => { mountModalUser = null; }}
|
|
219
|
+
></button>
|
|
220
|
+
<div class="admin-modal" role="dialog" aria-modal="true" aria-labelledby="mount-modal-title">
|
|
221
|
+
<h3 id="mount-modal-title">Mount Attachments for {mountModalUser.displayName}</h3>
|
|
222
|
+
{#if mountLoading}
|
|
223
|
+
<p class="admin-muted">Loading...</p>
|
|
224
|
+
{:else if allMounts.length === 0}
|
|
225
|
+
<p class="admin-muted">No mounts configured. Create mounts first.</p>
|
|
226
|
+
{:else}
|
|
227
|
+
<ul class="admin-mount-checklist">
|
|
228
|
+
{#each allMounts as mount (mount.id)}
|
|
229
|
+
<li class="admin-mount-checklist-item">
|
|
230
|
+
<label class="admin-checklist-label">
|
|
231
|
+
<input
|
|
232
|
+
class="sh3-base-check"
|
|
233
|
+
type="checkbox"
|
|
234
|
+
checked={attachedMountIds.has(mount.id)}
|
|
235
|
+
onchange={() => toggleMountAttachment(mount.id)}
|
|
236
|
+
/>
|
|
237
|
+
<span class="admin-status-dot" style="background: {mount.status === 'resolved' ? '#4caf50' : mount.status === 'unresolved' ? '#ff9800' : '#f44336'}"></span>
|
|
238
|
+
<span>{mount.label || mount.id}</span>
|
|
239
|
+
<span class="admin-mount-id">{mount.id}</span>
|
|
240
|
+
</label>
|
|
241
|
+
</li>
|
|
242
|
+
{/each}
|
|
243
|
+
</ul>
|
|
244
|
+
{/if}
|
|
245
|
+
<div class="admin-modal-actions">
|
|
246
|
+
<button type="button" class="admin-btn" onclick={() => { mountModalUser = null; }}>Close</button>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
{/if}
|
|
167
251
|
</div>
|
|
168
252
|
|
|
169
253
|
<style>
|
|
@@ -185,4 +269,16 @@
|
|
|
185
269
|
.admin-edit-actions { display: flex; gap: 6px; }
|
|
186
270
|
.admin-error { color: var(--sh3-error, #d32f2f); font-size: 13px; }
|
|
187
271
|
.admin-muted { color: var(--sh3-fg-muted); font-style: italic; }
|
|
272
|
+
.admin-mount-checklist { list-style: none; margin: 0 0 16px; padding: 0; display: flex; flex-direction: column; gap: 4px; }
|
|
273
|
+
.admin-mount-checklist-item { padding: 8px 0; border-bottom: 1px solid var(--sh3-border, #3a3a5c); }
|
|
274
|
+
.admin-checklist-label { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; }
|
|
275
|
+
.admin-checklist-label input[type="checkbox"] { cursor: pointer; }
|
|
276
|
+
.admin-status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
277
|
+
.admin-modal-root { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; z-index: 100; }
|
|
278
|
+
.admin-modal-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.6); border: none; padding: 0; margin: 0; cursor: pointer; }
|
|
279
|
+
.admin-modal { position: relative; background: var(--sh3-bg-elevated, #252540); border: 1px solid var(--sh3-border, #3a3a5c); border-radius: var(--sh3-radius, 8px); padding: 24px; max-width: 400px; width: 100%; }
|
|
280
|
+
.admin-modal h3 { margin: 0 0 8px; }
|
|
281
|
+
.admin-modal p { margin: 0 0 16px; font-size: 13px; color: var(--sh3-fg-subtle); }
|
|
282
|
+
.admin-modal-actions { display: flex; gap: 8px; justify-content: flex-end; }
|
|
283
|
+
.admin-mount-id { font-size: 11px; color: var(--sh3-fg-subtle); font-family: monospace; margin-left: auto; }
|
|
188
284
|
</style>
|
|
@@ -21,6 +21,7 @@ export const adminApp = {
|
|
|
21
21
|
{ slotId: 'admin.auth', viewId: 'sh3-admin:auth', label: 'Auth' },
|
|
22
22
|
{ slotId: 'admin.system', viewId: 'sh3-admin:system', label: 'System' },
|
|
23
23
|
{ slotId: 'admin.keys', viewId: 'sh3-admin:keys', label: 'Keys' },
|
|
24
|
+
{ slotId: 'admin.mounts', viewId: 'sh3-admin:mounts', label: 'Mounts' },
|
|
24
25
|
],
|
|
25
26
|
},
|
|
26
27
|
};
|
|
@@ -15,6 +15,7 @@ import UsersView from './UsersView.svelte';
|
|
|
15
15
|
import AuthSettingsView from './AuthSettingsView.svelte';
|
|
16
16
|
import SystemView from './SystemView.svelte';
|
|
17
17
|
import ApiKeysView from './ApiKeysView.svelte';
|
|
18
|
+
import MountsView from './MountsView.svelte';
|
|
18
19
|
import { VERSION } from '../../version';
|
|
19
20
|
/** Module-level server URL, set during activate. */
|
|
20
21
|
export let adminServerUrl = '';
|
|
@@ -23,11 +24,13 @@ export const adminShard = {
|
|
|
23
24
|
id: 'sh3-admin',
|
|
24
25
|
label: 'Admin',
|
|
25
26
|
version: VERSION,
|
|
27
|
+
permissions: ['documents:mount'],
|
|
26
28
|
views: [
|
|
27
29
|
{ id: 'sh3-admin:users', label: 'Users' },
|
|
28
30
|
{ id: 'sh3-admin:auth', label: 'Auth Settings' },
|
|
29
31
|
{ id: 'sh3-admin:system', label: 'System' },
|
|
30
32
|
{ id: 'sh3-admin:keys', label: 'API Keys' },
|
|
33
|
+
{ id: 'sh3-admin:mounts', label: 'Mounts' },
|
|
31
34
|
],
|
|
32
35
|
},
|
|
33
36
|
activate(ctx) {
|
|
@@ -55,9 +58,16 @@ export const adminShard = {
|
|
|
55
58
|
return { unmount() { unmount(instance); } };
|
|
56
59
|
},
|
|
57
60
|
};
|
|
61
|
+
const mountsFactory = {
|
|
62
|
+
mount(container, _context) {
|
|
63
|
+
const instance = mount(MountsView, { target: container });
|
|
64
|
+
return { unmount() { unmount(instance); } };
|
|
65
|
+
},
|
|
66
|
+
};
|
|
58
67
|
ctx.registerView('sh3-admin:users', usersFactory);
|
|
59
68
|
ctx.registerView('sh3-admin:auth', authFactory);
|
|
60
69
|
ctx.registerView('sh3-admin:system', systemFactory);
|
|
61
70
|
ctx.registerView('sh3-admin:keys', keysFactory);
|
|
71
|
+
ctx.registerView('sh3-admin:mounts', mountsFactory);
|
|
62
72
|
},
|
|
63
73
|
};
|
package/dist/apps/lifecycle.js
CHANGED
|
@@ -69,6 +69,7 @@ function getOrCreateAppContext(appId, scopeId, args) {
|
|
|
69
69
|
const scope = scopeId !== null && scopeId !== void 0 ? scopeId : resolveLaunchScope();
|
|
70
70
|
ctx = {
|
|
71
71
|
scopeId: scope,
|
|
72
|
+
getScope: () => sessionState.activeProjectId ? 'project' : 'tenant',
|
|
72
73
|
args: args !== null && args !== void 0 ? args : {},
|
|
73
74
|
state: (schema) => createStateZones(`__app__:${appId}:scope:${scope}`, schema),
|
|
74
75
|
zones: ((_a = app === null || app === void 0 ? void 0 : app.manifest.permissions) === null || _a === void 0 ? void 0 : _a.includes(PERMISSION_STATE_MANAGE))
|
package/dist/apps/types.d.ts
CHANGED
|
@@ -96,6 +96,13 @@ export interface AppContext {
|
|
|
96
96
|
* scope and cannot reach across scopes — exiting a scope unloads the app.
|
|
97
97
|
*/
|
|
98
98
|
scopeId: string;
|
|
99
|
+
/**
|
|
100
|
+
* Whether this app instance is running in a 'tenant' (personal) or 'project'
|
|
101
|
+
* scope. Determined at launch time from session state; the app's document
|
|
102
|
+
* handles and state zones are bound to this scope for their lifetime.
|
|
103
|
+
* Returns 'tenant' when no project is active, 'project' otherwise.
|
|
104
|
+
*/
|
|
105
|
+
getScope(): 'tenant' | 'project';
|
|
99
106
|
/**
|
|
100
107
|
* Arguments supplied by the caller at launch time via `LaunchAppOptions.args`.
|
|
101
108
|
* Defaults to `{}` when the app is launched without explicit args.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const ICON_IDS: readonly ["activity", "align-horizontal-justify-center", "align-horizontal-justify-end", "align-horizontal-justify-start", "app-window", "archive", "archive-restore", "axis-3d", "box", "brick-wall", "bug", "building-2", "cable", "calendar", "camera", "check", "chevron-down", "chevron-right", "circle-check", "circle-dot", "circle-minus", "circle-x", "clipboard", "clipboard-paste", "clock", "command", "compass", "component", "copy", "cpu", "crop", "crosshair", "crown", "dollar-sign", "download", "droplet", "ellipsis-vertical", "eraser", "euro", "external-link", "eye", "eye-off", "file", "file-archive", "file-diff", "file-plus", "file-text", "flame", "flip-horizontal-2", "flip-vertical-2", "folder", "folder-open", "folder-plus", "folder-tree", "gallery-vertical-end", "gamepad-2", "gauge", "gem", "git-branch", "git-commit-horizontal", "git-merge", "globe", "grid-2x2", "grid-3x3", "group", "hard-drive", "heart", "history", "house", "image", "info", "joystick", "key", "layers", "layout-dashboard", "layout-grid", "layout-list", "layout-panel-left", "layout-panel-top", "layout-template", "lightbulb", "link", "list-ordered", "list-tree", "lock", "log-out", "magnet", "mail", "map", "maximize", "menu", "minimize", "moon", "mouse-pointer", "move", "move-3d", "music", "navigation", "network", "notebook-pen", "palette", "panel-right", "panel-top", "pause", "pencil", "pipette", "play", "plus", "pointer", "pound-sterling", "receipt", "redo-2", "refresh-cw", "rocket", "rotate-3d", "rotate-ccw", "rotate-cw", "ruler", "save", "scissors", "scroll-text", "search", "send", "server", "settings", "shield", "skull", "sliders-horizontal", "snowflake", "sparkles", "square", "square-terminal", "star", "sun", "sword", "table-properties", "target", "texture", "timer", "trash-2", "triangle-alert", "type", "undo-2", "ungroup", "unity", "upload", "user", "users", "video", "volume-2", "wand-sparkles", "wind", "x", "zap", "zoom-in", "zoom-out"];
|
|
1
|
+
export declare const ICON_IDS: readonly ["activity", "align-horizontal-justify-center", "align-horizontal-justify-end", "align-horizontal-justify-start", "app-window", "archive", "archive-restore", "axis-3d", "box", "brick-wall", "bug", "building-2", "cable", "calendar", "camera", "check", "chevron-down", "chevron-left", "chevron-right", "circle-check", "circle-dot", "circle-minus", "circle-x", "clipboard", "clipboard-paste", "clock", "command", "compass", "component", "copy", "cpu", "crop", "crosshair", "crown", "dollar-sign", "download", "droplet", "ellipsis-vertical", "eraser", "euro", "external-link", "eye", "eye-off", "file", "file-archive", "file-diff", "file-plus", "file-text", "flame", "flip-horizontal-2", "flip-vertical-2", "folder", "folder-open", "folder-plus", "folder-tree", "gallery-vertical-end", "gamepad-2", "gauge", "gem", "git-branch", "git-commit-horizontal", "git-merge", "globe", "grid-2x2", "grid-3x3", "group", "hard-drive", "heart", "history", "house", "image", "info", "joystick", "key", "layers", "layout-dashboard", "layout-grid", "layout-list", "layout-panel-left", "layout-panel-top", "layout-template", "lightbulb", "link", "list-ordered", "list-tree", "lock", "log-out", "magnet", "mail", "map", "maximize", "menu", "minimize", "moon", "mouse-pointer", "move", "move-3d", "music", "navigation", "network", "notebook-pen", "palette", "panel-right", "panel-top", "pause", "pencil", "pipette", "play", "plus", "pointer", "pound-sterling", "receipt", "redo-2", "refresh-cw", "rocket", "rotate-3d", "rotate-ccw", "rotate-cw", "ruler", "save", "scissors", "scroll-text", "search", "send", "server", "settings", "shield", "skull", "sliders-horizontal", "snowflake", "sparkles", "square", "square-terminal", "star", "sun", "sword", "table-properties", "target", "texture", "timer", "trash-2", "triangle-alert", "type", "undo-2", "ungroup", "unity", "upload", "user", "users", "video", "volume-2", "wand-sparkles", "wind", "x", "zap", "zoom-in", "zoom-out"];
|
|
2
2
|
export type IconId = (typeof ICON_IDS)[number];
|
package/dist/assets/icons.svg
CHANGED
|
@@ -1159,4 +1159,9 @@
|
|
|
1159
1159
|
<circle cx="12" cy="19" r="1" />
|
|
1160
1160
|
</symbol>
|
|
1161
1161
|
|
|
1162
|
+
<!-- lucide/chevron-left -->
|
|
1163
|
+
<symbol id="chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1164
|
+
<path d="m15 18-6-6 6-6" />
|
|
1165
|
+
</symbol>
|
|
1166
|
+
|
|
1162
1167
|
</svg>
|