sh3-core 0.6.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.
- package/README.md +9 -0
- package/dist/Shell.svelte +283 -0
- package/dist/Shell.svelte.d.ts +5 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +50 -0
- package/dist/app/admin/ApiKeysView.svelte +169 -0
- package/dist/app/admin/ApiKeysView.svelte.d.ts +3 -0
- package/dist/app/admin/AuthSettingsView.svelte +105 -0
- package/dist/app/admin/AuthSettingsView.svelte.d.ts +3 -0
- package/dist/app/admin/SystemView.svelte +73 -0
- package/dist/app/admin/SystemView.svelte.d.ts +3 -0
- package/dist/app/admin/UsersView.svelte +188 -0
- package/dist/app/admin/UsersView.svelte.d.ts +3 -0
- package/dist/app/admin/adminApp.d.ts +7 -0
- package/dist/app/admin/adminApp.js +25 -0
- package/dist/app/admin/adminShard.svelte.d.ts +4 -0
- package/dist/app/admin/adminShard.svelte.js +62 -0
- package/dist/app/store/InstalledView.svelte +246 -0
- package/dist/app/store/InstalledView.svelte.d.ts +3 -0
- package/dist/app/store/StoreView.svelte +522 -0
- package/dist/app/store/StoreView.svelte.d.ts +3 -0
- package/dist/app/store/storeApp.d.ts +10 -0
- package/dist/app/store/storeApp.js +26 -0
- package/dist/app/store/storeShard.svelte.d.ts +38 -0
- package/dist/app/store/storeShard.svelte.js +218 -0
- package/dist/apps/lifecycle.d.ts +42 -0
- package/dist/apps/lifecycle.js +184 -0
- package/dist/apps/registry.svelte.d.ts +40 -0
- package/dist/apps/registry.svelte.js +59 -0
- package/dist/apps/terminal/manifest.d.ts +8 -0
- package/dist/apps/terminal/manifest.js +13 -0
- package/dist/apps/terminal/terminal-app.d.ts +7 -0
- package/dist/apps/terminal/terminal-app.js +14 -0
- package/dist/apps/types.d.ts +93 -0
- package/dist/apps/types.js +10 -0
- package/dist/artifact.d.ts +32 -0
- package/dist/artifact.js +1 -0
- package/dist/assets/SH3.png +0 -0
- package/dist/assets/icons.svg +1126 -0
- package/dist/assets.d.ts +13 -0
- package/dist/auth/GuestBanner.svelte +134 -0
- package/dist/auth/GuestBanner.svelte.d.ts +3 -0
- package/dist/auth/SignInWall.svelte +203 -0
- package/dist/auth/SignInWall.svelte.d.ts +7 -0
- package/dist/auth/auth.svelte.d.ts +69 -0
- package/dist/auth/auth.svelte.js +165 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/types.d.ts +41 -0
- package/dist/auth/types.js +6 -0
- package/dist/build.d.ts +49 -0
- package/dist/build.js +236 -0
- package/dist/contract.d.ts +20 -0
- package/dist/contract.js +28 -0
- package/dist/createShell.d.ts +24 -0
- package/dist/createShell.js +131 -0
- package/dist/documents/backends.d.ts +17 -0
- package/dist/documents/backends.js +156 -0
- package/dist/documents/config.d.ts +7 -0
- package/dist/documents/config.js +27 -0
- package/dist/documents/handle.d.ts +6 -0
- package/dist/documents/handle.js +154 -0
- package/dist/documents/http-backend.d.ts +22 -0
- package/dist/documents/http-backend.js +78 -0
- package/dist/documents/index.d.ts +6 -0
- package/dist/documents/index.js +8 -0
- package/dist/documents/notifications.d.ts +9 -0
- package/dist/documents/notifications.js +39 -0
- package/dist/documents/types.d.ts +97 -0
- package/dist/documents/types.js +12 -0
- package/dist/env/client.d.ts +44 -0
- package/dist/env/client.js +106 -0
- package/dist/env/index.d.ts +2 -0
- package/dist/env/index.js +1 -0
- package/dist/env/types.d.ts +12 -0
- package/dist/env/types.js +8 -0
- package/dist/host-entry.d.ts +13 -0
- package/dist/host-entry.js +17 -0
- package/dist/host.d.ts +15 -0
- package/dist/host.js +86 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +14 -0
- package/dist/layout/DragPreview.svelte +63 -0
- package/dist/layout/DragPreview.svelte.d.ts +3 -0
- package/dist/layout/LayoutRenderer.svelte +262 -0
- package/dist/layout/LayoutRenderer.svelte.d.ts +6 -0
- package/dist/layout/SlotContainer.svelte +140 -0
- package/dist/layout/SlotContainer.svelte.d.ts +8 -0
- package/dist/layout/SlotDropZone.svelte +122 -0
- package/dist/layout/SlotDropZone.svelte.d.ts +8 -0
- package/dist/layout/drag.svelte.d.ts +45 -0
- package/dist/layout/drag.svelte.js +200 -0
- package/dist/layout/inspection.d.ts +72 -0
- package/dist/layout/inspection.js +209 -0
- package/dist/layout/ops.d.ts +100 -0
- package/dist/layout/ops.js +310 -0
- package/dist/layout/slotHostPool.svelte.d.ts +36 -0
- package/dist/layout/slotHostPool.svelte.js +229 -0
- package/dist/layout/store.svelte.d.ts +39 -0
- package/dist/layout/store.svelte.js +153 -0
- package/dist/layout/tree-walk.d.ts +15 -0
- package/dist/layout/tree-walk.js +33 -0
- package/dist/layout/types.d.ts +108 -0
- package/dist/layout/types.js +25 -0
- package/dist/migrations/shell-rename.d.ts +16 -0
- package/dist/migrations/shell-rename.js +48 -0
- package/dist/overlays/ModalFrame.svelte +87 -0
- package/dist/overlays/ModalFrame.svelte.d.ts +10 -0
- package/dist/overlays/PopupFrame.svelte +85 -0
- package/dist/overlays/PopupFrame.svelte.d.ts +10 -0
- package/dist/overlays/ToastItem.svelte +77 -0
- package/dist/overlays/ToastItem.svelte.d.ts +9 -0
- package/dist/overlays/focusTrap.d.ts +1 -0
- package/dist/overlays/focusTrap.js +64 -0
- package/dist/overlays/modal.d.ts +9 -0
- package/dist/overlays/modal.js +141 -0
- package/dist/overlays/popup.d.ts +9 -0
- package/dist/overlays/popup.js +108 -0
- package/dist/overlays/roots.d.ts +4 -0
- package/dist/overlays/roots.js +31 -0
- package/dist/overlays/toast.d.ts +6 -0
- package/dist/overlays/toast.js +93 -0
- package/dist/overlays/types.d.ts +31 -0
- package/dist/overlays/types.js +15 -0
- package/dist/platform/index.d.ts +10 -0
- package/dist/platform/index.js +33 -0
- package/dist/platform/tauri-backend.d.ts +15 -0
- package/dist/platform/tauri-backend.js +58 -0
- package/dist/primitives/.gitkeep +0 -0
- package/dist/primitives/ResizableSplitter.svelte +333 -0
- package/dist/primitives/ResizableSplitter.svelte.d.ts +35 -0
- package/dist/primitives/TabbedPanel.svelte +305 -0
- package/dist/primitives/TabbedPanel.svelte.d.ts +50 -0
- package/dist/primitives/base.css +42 -0
- package/dist/registry/client.d.ts +74 -0
- package/dist/registry/client.js +117 -0
- package/dist/registry/index.d.ts +13 -0
- package/dist/registry/index.js +14 -0
- package/dist/registry/installer.d.ts +53 -0
- package/dist/registry/installer.js +168 -0
- package/dist/registry/integrity.d.ts +32 -0
- package/dist/registry/integrity.js +92 -0
- package/dist/registry/loader.d.ts +50 -0
- package/dist/registry/loader.js +145 -0
- package/dist/registry/schema.d.ts +47 -0
- package/dist/registry/schema.js +185 -0
- package/dist/registry/storage.d.ts +37 -0
- package/dist/registry/storage.js +101 -0
- package/dist/registry/types.d.ts +262 -0
- package/dist/registry/types.js +14 -0
- package/dist/server-shard/types.d.ts +67 -0
- package/dist/server-shard/types.js +13 -0
- package/dist/sh3core-shard/ShellHome.svelte +192 -0
- package/dist/sh3core-shard/ShellHome.svelte.d.ts +3 -0
- package/dist/sh3core-shard/ShellTitle.svelte +171 -0
- package/dist/sh3core-shard/ShellTitle.svelte.d.ts +3 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.d.ts +2 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +53 -0
- package/dist/shards/activate.svelte.d.ts +52 -0
- package/dist/shards/activate.svelte.js +186 -0
- package/dist/shards/registry.d.ts +4 -0
- package/dist/shards/registry.js +28 -0
- package/dist/shards/types.d.ts +207 -0
- package/dist/shards/types.js +20 -0
- package/dist/shell-shard/InputLine.svelte +133 -0
- package/dist/shell-shard/InputLine.svelte.d.ts +11 -0
- package/dist/shell-shard/ScrollbackView.svelte +47 -0
- package/dist/shell-shard/ScrollbackView.svelte.d.ts +7 -0
- package/dist/shell-shard/Terminal.svelte +122 -0
- package/dist/shell-shard/Terminal.svelte.d.ts +8 -0
- package/dist/shell-shard/entries/PromptEntry.svelte +25 -0
- package/dist/shell-shard/entries/PromptEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/entries/RichEntry.svelte +19 -0
- package/dist/shell-shard/entries/RichEntry.svelte.d.ts +8 -0
- package/dist/shell-shard/entries/StatusEntry.svelte +22 -0
- package/dist/shell-shard/entries/StatusEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/entries/TextEntry.svelte +25 -0
- package/dist/shell-shard/entries/TextEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/manifest.d.ts +2 -0
- package/dist/shell-shard/manifest.js +11 -0
- package/dist/shell-shard/protocol.d.ts +90 -0
- package/dist/shell-shard/protocol.js +11 -0
- package/dist/shell-shard/registry.d.ts +69 -0
- package/dist/shell-shard/registry.js +47 -0
- package/dist/shell-shard/rich/AppCard.svelte +25 -0
- package/dist/shell-shard/rich/AppCard.svelte.d.ts +10 -0
- package/dist/shell-shard/rich/AppsTable.svelte +29 -0
- package/dist/shell-shard/rich/AppsTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/EnvTable.svelte +27 -0
- package/dist/shell-shard/rich/EnvTable.svelte.d.ts +8 -0
- package/dist/shell-shard/rich/HelpTable.svelte +29 -0
- package/dist/shell-shard/rich/HelpTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/HistoryList.svelte +37 -0
- package/dist/shell-shard/rich/HistoryList.svelte.d.ts +9 -0
- package/dist/shell-shard/rich/ShardsTable.svelte +28 -0
- package/dist/shell-shard/rich/ShardsTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/ViewsTable.svelte +31 -0
- package/dist/shell-shard/rich/ViewsTable.svelte.d.ts +13 -0
- package/dist/shell-shard/rich/ZoneTree.svelte +19 -0
- package/dist/shell-shard/rich/ZoneTree.svelte.d.ts +8 -0
- package/dist/shell-shard/rich/ZonesTable.svelte +27 -0
- package/dist/shell-shard/rich/ZonesTable.svelte.d.ts +11 -0
- package/dist/shell-shard/scrollback.svelte.d.ts +36 -0
- package/dist/shell-shard/scrollback.svelte.js +43 -0
- package/dist/shell-shard/session-client.svelte.d.ts +23 -0
- package/dist/shell-shard/session-client.svelte.js +120 -0
- package/dist/shell-shard/shellShard.svelte.d.ts +2 -0
- package/dist/shell-shard/shellShard.svelte.js +139 -0
- package/dist/shell-shard/verbs/apps.d.ts +3 -0
- package/dist/shell-shard/verbs/apps.js +50 -0
- package/dist/shell-shard/verbs/clear.d.ts +2 -0
- package/dist/shell-shard/verbs/clear.js +7 -0
- package/dist/shell-shard/verbs/help.d.ts +2 -0
- package/dist/shell-shard/verbs/help.js +21 -0
- package/dist/shell-shard/verbs/history.d.ts +2 -0
- package/dist/shell-shard/verbs/history.js +20 -0
- package/dist/shell-shard/verbs/index.d.ts +2 -0
- package/dist/shell-shard/verbs/index.js +29 -0
- package/dist/shell-shard/verbs/session.d.ts +5 -0
- package/dist/shell-shard/verbs/session.js +65 -0
- package/dist/shell-shard/verbs/shards.d.ts +2 -0
- package/dist/shell-shard/verbs/shards.js +14 -0
- package/dist/shell-shard/verbs/views.d.ts +4 -0
- package/dist/shell-shard/verbs/views.js +90 -0
- package/dist/shell-shard/verbs/zones.d.ts +3 -0
- package/dist/shell-shard/verbs/zones.js +38 -0
- package/dist/shellRuntime.svelte.d.ts +27 -0
- package/dist/shellRuntime.svelte.js +27 -0
- package/dist/state/backends.d.ts +26 -0
- package/dist/state/backends.js +99 -0
- package/dist/state/manage.d.ts +14 -0
- package/dist/state/manage.js +40 -0
- package/dist/state/types.d.ts +55 -0
- package/dist/state/types.js +17 -0
- package/dist/state/zones.svelte.d.ts +53 -0
- package/dist/state/zones.svelte.js +141 -0
- package/dist/theme.d.ts +28 -0
- package/dist/theme.js +92 -0
- package/dist/tokens.css +102 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# sh3-core
|
|
2
|
+
|
|
3
|
+
Core library for [SH3](https://github.com/Unfinished-Lair/sh3) — types, layout engine, state zones, overlays, shard/app lifecycle, and the shell component.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install sh3-core
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Part of the [SH3 monorepo](https://github.com/Unfinished-Lair/sh3).
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* <Shell> — top-level chrome component.
|
|
4
|
+
*
|
|
5
|
+
* Owns the tab bar, status bar, docked content area (layer 0), and the
|
|
6
|
+
* six overlay roots (layers 1-6). This is a stub implementation for
|
|
7
|
+
* phase 1: the content area is empty and the overlay roots are present
|
|
8
|
+
* but unmanaged. Layout tree rendering arrives in phase 2; overlay layer
|
|
9
|
+
* managers arrive in phase 5.
|
|
10
|
+
*
|
|
11
|
+
* The six overlay roots are mounted here (not lazily) so that layer
|
|
12
|
+
* managers in later phases have stable DOM targets to portal into.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import './tokens.css';
|
|
16
|
+
import './primitives/base.css';
|
|
17
|
+
import LayoutRenderer from './layout/LayoutRenderer.svelte';
|
|
18
|
+
import DragPreview from './layout/DragPreview.svelte';
|
|
19
|
+
import type { OverlayLayer } from './overlays/types';
|
|
20
|
+
import { registerLayerRoot, unregisterLayerRoot } from './overlays/roots';
|
|
21
|
+
import { returnToHome, isAdmin } from './api';
|
|
22
|
+
import { getActiveRoot } from './layout/store.svelte';
|
|
23
|
+
import { dockIntoActiveLayout } from './layout/inspection';
|
|
24
|
+
import { isAuthenticated, isLocalOwner, getUser, logout } from './auth/index';
|
|
25
|
+
import iconsUrl from './assets/icons.svg';
|
|
26
|
+
import GuestBanner from './auth/GuestBanner.svelte';
|
|
27
|
+
|
|
28
|
+
const authenticated = $derived(isAuthenticated());
|
|
29
|
+
const user = $derived(getUser());
|
|
30
|
+
const elevated = $derived(isAdmin());
|
|
31
|
+
// True when the layout manager is rendering the home root. We can't use
|
|
32
|
+
// getActiveApp() here: returnToHome() keeps the app warm (activeApp.id
|
|
33
|
+
// stays set) and only flips the layout store's activeRoot back to 'home'.
|
|
34
|
+
const onHome = $derived(getActiveRoot() === 'home');
|
|
35
|
+
|
|
36
|
+
// Layer metadata — order matches the stack in docs/design/layout.md.
|
|
37
|
+
// Index 0 here is layer 1 (floating panels); layer 0 is the content area.
|
|
38
|
+
const overlayLayers: { layer: number; name: OverlayLayer }[] = [
|
|
39
|
+
{ layer: 1, name: 'floating' },
|
|
40
|
+
{ layer: 2, name: 'drag-preview' },
|
|
41
|
+
{ layer: 3, name: 'popup' },
|
|
42
|
+
{ layer: 4, name: 'modal' },
|
|
43
|
+
{ layer: 5, name: 'toast' },
|
|
44
|
+
{ layer: 6, name: 'command' },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// Populated by bind:this during render; registered with the overlay
|
|
48
|
+
// module via $effect after mount so layer managers (shell.modal,
|
|
49
|
+
// shell.popup, shell.toast) can find their target DOM roots.
|
|
50
|
+
const overlayRoots: Partial<Record<OverlayLayer, HTMLDivElement>> = $state({});
|
|
51
|
+
|
|
52
|
+
$effect(() => {
|
|
53
|
+
for (const { name } of overlayLayers) {
|
|
54
|
+
const el = overlayRoots[name];
|
|
55
|
+
if (el) registerLayerRoot(name, el);
|
|
56
|
+
}
|
|
57
|
+
return () => {
|
|
58
|
+
for (const { name } of overlayLayers) unregisterLayerRoot(name);
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// The AZERTY `²` key (top-left, below Escape) docks the terminal view
|
|
63
|
+
// into the currently-rendered layout — whichever app is active, or the
|
|
64
|
+
// home root if none. Admin-only because the shell shard is admin-gated.
|
|
65
|
+
// Provisional ergonomics shortcut; a real hotkey contribution API is
|
|
66
|
+
// DF1 in the roadmap, and dedicated floating-window placement is
|
|
67
|
+
// SH9 / DF3.
|
|
68
|
+
//
|
|
69
|
+
// Bare key (no modifier) so it must be suppressed while the user is
|
|
70
|
+
// typing into an input/textarea/contenteditable, or the shortcut would
|
|
71
|
+
// eat `²` characters mid-word.
|
|
72
|
+
$effect(() => {
|
|
73
|
+
function onKeyDown(e: KeyboardEvent) {
|
|
74
|
+
if (e.key !== '²') return;
|
|
75
|
+
if (!elevated) return;
|
|
76
|
+
const target = e.target as HTMLElement | null;
|
|
77
|
+
if (target) {
|
|
78
|
+
const tag = target.tagName;
|
|
79
|
+
if (
|
|
80
|
+
tag === 'INPUT' ||
|
|
81
|
+
tag === 'TEXTAREA' ||
|
|
82
|
+
target.isContentEditable
|
|
83
|
+
) return;
|
|
84
|
+
}
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
dockIntoActiveLayout({
|
|
87
|
+
slotId: `shell.terminal.${Date.now()}`,
|
|
88
|
+
viewId: 'shell:terminal',
|
|
89
|
+
label: 'Shell',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
window.addEventListener('keydown', onKeyDown);
|
|
93
|
+
return () => window.removeEventListener('keydown', onKeyDown);
|
|
94
|
+
});
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<div class="shell">
|
|
98
|
+
<header class="shell-tabbar" data-shell-region="tabbar">
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
class="shell-tabbar-home-button"
|
|
102
|
+
onclick={() => returnToHome()}
|
|
103
|
+
disabled={onHome}
|
|
104
|
+
title="Home"
|
|
105
|
+
>
|
|
106
|
+
<svg class="shell-tabbar-home-icon" aria-hidden="true">
|
|
107
|
+
<use href="{iconsUrl}#house" />
|
|
108
|
+
</svg>
|
|
109
|
+
</button>
|
|
110
|
+
<span class="shell-tabbar-brand">SH3</span>
|
|
111
|
+
{#if authenticated && user}
|
|
112
|
+
<div class="shell-tabbar-user">
|
|
113
|
+
<span class="shell-tabbar-user-name">{user.displayName}</span>
|
|
114
|
+
<span class="shell-tabbar-tag">{elevated ? 'admin' : 'user'}</span>
|
|
115
|
+
{#if !isLocalOwner()}
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
class="shell-tabbar-signout"
|
|
119
|
+
onclick={() => logout()}
|
|
120
|
+
title="Sign out"
|
|
121
|
+
>
|
|
122
|
+
<svg class="shell-tabbar-signout-icon" aria-hidden="true">
|
|
123
|
+
<use href="{iconsUrl}#log-out" />
|
|
124
|
+
</svg>
|
|
125
|
+
</button>
|
|
126
|
+
{/if}
|
|
127
|
+
</div>
|
|
128
|
+
{/if}
|
|
129
|
+
</header>
|
|
130
|
+
|
|
131
|
+
<GuestBanner />
|
|
132
|
+
|
|
133
|
+
<main class="shell-content" data-shell-region="content" data-shell-layer="0">
|
|
134
|
+
<LayoutRenderer />
|
|
135
|
+
</main>
|
|
136
|
+
|
|
137
|
+
<footer class="shell-statusbar" data-shell-region="statusbar">
|
|
138
|
+
<!-- alpha tag moved to ShellHome title row -->
|
|
139
|
+
</footer>
|
|
140
|
+
|
|
141
|
+
<!--
|
|
142
|
+
Overlay roots. Each is absolutely positioned over the entire shell with
|
|
143
|
+
pointer-events: none by default; layer managers enable pointer events on
|
|
144
|
+
the specific surfaces they portal in.
|
|
145
|
+
-->
|
|
146
|
+
<div class="shell-overlays" aria-hidden="true">
|
|
147
|
+
{#each overlayLayers as { layer, name } (layer)}
|
|
148
|
+
<div
|
|
149
|
+
class="shell-overlay-root"
|
|
150
|
+
data-shell-overlay={name}
|
|
151
|
+
data-shell-layer={layer}
|
|
152
|
+
style="z-index: var(--shell-z-layer-{layer});"
|
|
153
|
+
bind:this={overlayRoots[name]}
|
|
154
|
+
>
|
|
155
|
+
{#if name === 'drag-preview'}
|
|
156
|
+
<DragPreview />
|
|
157
|
+
{/if}
|
|
158
|
+
</div>
|
|
159
|
+
{/each}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<style>
|
|
164
|
+
.shell {
|
|
165
|
+
display: grid;
|
|
166
|
+
grid-template-rows: var(--shell-tabbar-height) auto 1fr var(--shell-statusbar-height);
|
|
167
|
+
height: 100%;
|
|
168
|
+
width: 100%;
|
|
169
|
+
position: relative;
|
|
170
|
+
background: var(--shell-grad-bg, var(--shell-bg));
|
|
171
|
+
color: var(--shell-fg);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.shell-tabbar {
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
gap: var(--shell-pad-md);
|
|
178
|
+
padding: 0 var(--shell-pad-md);
|
|
179
|
+
background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
|
|
180
|
+
border-bottom: 1px solid var(--shell-border);
|
|
181
|
+
user-select: none;
|
|
182
|
+
}
|
|
183
|
+
.shell-tabbar-brand {
|
|
184
|
+
font-weight: 600;
|
|
185
|
+
color: var(--shell-accent);
|
|
186
|
+
letter-spacing: 0.5px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.shell-content {
|
|
190
|
+
position: relative;
|
|
191
|
+
overflow: hidden;
|
|
192
|
+
background: var(--shell-grad-bg, var(--shell-bg));
|
|
193
|
+
min-width: 0;
|
|
194
|
+
min-height: 0;
|
|
195
|
+
}
|
|
196
|
+
.shell-statusbar {
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
justify-content: space-between;
|
|
200
|
+
padding: 0 var(--shell-pad-md);
|
|
201
|
+
background: var(--shell-grad-bg-sunken, var(--shell-bg-sunken));
|
|
202
|
+
border-top: 1px solid var(--shell-border);
|
|
203
|
+
color: var(--shell-fg-muted);
|
|
204
|
+
font-size: 11px;
|
|
205
|
+
user-select: none;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.shell-overlays {
|
|
209
|
+
position: absolute;
|
|
210
|
+
inset: 0;
|
|
211
|
+
pointer-events: none;
|
|
212
|
+
}
|
|
213
|
+
.shell-overlay-root {
|
|
214
|
+
position: absolute;
|
|
215
|
+
inset: 0;
|
|
216
|
+
pointer-events: none;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.shell-tabbar-home-button {
|
|
220
|
+
display: flex;
|
|
221
|
+
align-items: center;
|
|
222
|
+
justify-content: center;
|
|
223
|
+
width: 24px;
|
|
224
|
+
height: 24px;
|
|
225
|
+
padding: 0;
|
|
226
|
+
background: transparent;
|
|
227
|
+
color: var(--shell-fg-muted);
|
|
228
|
+
border: 1px solid var(--shell-border);
|
|
229
|
+
}
|
|
230
|
+
.shell-tabbar-home-button:hover:not(:disabled) {
|
|
231
|
+
color: var(--shell-fg);
|
|
232
|
+
border-color: var(--shell-fg-muted);
|
|
233
|
+
}
|
|
234
|
+
.shell-tabbar-home-button:disabled {
|
|
235
|
+
color: var(--shell-fg-subtle);
|
|
236
|
+
border-color: var(--shell-border);
|
|
237
|
+
cursor: default;
|
|
238
|
+
}
|
|
239
|
+
.shell-tabbar-home-icon {
|
|
240
|
+
width: 14px;
|
|
241
|
+
height: 14px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.shell-tabbar-user {
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
gap: 6px;
|
|
248
|
+
margin-left: auto;
|
|
249
|
+
}
|
|
250
|
+
.shell-tabbar-user-name {
|
|
251
|
+
font-size: 12px;
|
|
252
|
+
color: var(--shell-fg-subtle);
|
|
253
|
+
}
|
|
254
|
+
.shell-tabbar-tag {
|
|
255
|
+
font-size: 9px;
|
|
256
|
+
font-weight: 700;
|
|
257
|
+
text-transform: uppercase;
|
|
258
|
+
letter-spacing: 0.08em;
|
|
259
|
+
color: #fff;
|
|
260
|
+
background: var(--shell-accent);
|
|
261
|
+
padding: 1px 6px;
|
|
262
|
+
border-radius: 6px;
|
|
263
|
+
}
|
|
264
|
+
.shell-tabbar-signout {
|
|
265
|
+
display: flex;
|
|
266
|
+
align-items: center;
|
|
267
|
+
justify-content: center;
|
|
268
|
+
width: 24px;
|
|
269
|
+
height: 24px;
|
|
270
|
+
padding: 0;
|
|
271
|
+
background: transparent;
|
|
272
|
+
color: var(--shell-fg-muted);
|
|
273
|
+
border: 1px solid var(--shell-border);
|
|
274
|
+
}
|
|
275
|
+
.shell-tabbar-signout:hover {
|
|
276
|
+
color: var(--shell-fg);
|
|
277
|
+
border-color: var(--shell-fg-muted);
|
|
278
|
+
}
|
|
279
|
+
.shell-tabbar-signout-icon {
|
|
280
|
+
width: 14px;
|
|
281
|
+
height: 14px;
|
|
282
|
+
}
|
|
283
|
+
</style>
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export { shell } from './shellRuntime.svelte';
|
|
2
|
+
export type { Shell } from './shellRuntime.svelte';
|
|
3
|
+
export type { Shard, ShardManifest, ShardContext, ViewDeclaration, ViewFactory, ViewHandle, MountContext, } from './shards/types';
|
|
4
|
+
export type { LayoutNode, SplitNode, TabsNode, SlotNode, TabEntry, SplitDirection, SizeMode, } from './layout/types';
|
|
5
|
+
export type { ZoneSchema, ZoneName, ZoneManager } from './state/types';
|
|
6
|
+
export { PERMISSION_STATE_MANAGE } from './state/types';
|
|
7
|
+
export type { StateZones } from './state/zones.svelte';
|
|
8
|
+
export type { EnvState } from './env/types';
|
|
9
|
+
export type { App, AppManifest, AppContext } from './apps/types';
|
|
10
|
+
export { listRegisteredApps, getActiveApp } from './apps/registry.svelte';
|
|
11
|
+
export { launchApp, returnToHome } from './apps/lifecycle';
|
|
12
|
+
export { inspectActiveLayout, spliceIntoActiveLayout, dockIntoActiveLayout, focusTab, focusView, collapseChild, expandChild, closeTab, } from './layout/inspection';
|
|
13
|
+
export type { DocumentHandle, DocumentHandleOptions, DocumentFormat, DocumentMeta, DocumentChange, AutosaveController, } from './documents/types';
|
|
14
|
+
export { registeredShards, activeShards } from './shards/activate.svelte';
|
|
15
|
+
export type { RegistryIndex, PackageEntry, PackageVersion, RequiredDependency, InstalledPackage, InstallResult, PackageMeta, } from './registry/types';
|
|
16
|
+
export type { ResolvedPackage } from './registry/client';
|
|
17
|
+
export { fetchRegistries, fetchBundle, buildPackageMeta } from './registry/client';
|
|
18
|
+
export { validateRegistryIndex } from './registry/schema';
|
|
19
|
+
export { isAdmin, isAuthenticated, isGuest, getUser, getAuthHeader } from './auth/index';
|
|
20
|
+
export type { AuthUser, AuthSession, BootConfig } from './auth/types';
|
|
21
|
+
/** Runtime feature flags for target-dependent behavior. */
|
|
22
|
+
export declare const capabilities: {
|
|
23
|
+
/** Whether this target supports hot-installing packages via dynamic import from blob URL. */
|
|
24
|
+
readonly hotInstall: boolean;
|
|
25
|
+
};
|
|
26
|
+
export type { ServerShard, ServerShardContext } from './server-shard/types';
|
|
27
|
+
export { VERSION } from './version';
|
|
28
|
+
export { setTokenOverrides, clearTokenOverrides, getTokenOverrides, } from './theme';
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Public framework API — the single import path shards and apps are
|
|
3
|
+
* allowed to touch. The phase 9 package boundary turns this file (and
|
|
4
|
+
* only this file) into the published entry point.
|
|
5
|
+
*
|
|
6
|
+
* What belongs here:
|
|
7
|
+
* - Types that shards and apps consume (Shard, App, ShardContext, etc.)
|
|
8
|
+
* - The `shell` runtime singleton (modal/popup/toast)
|
|
9
|
+
* - Layout inspection/mutation helpers for advanced shards (added in
|
|
10
|
+
* Task 12)
|
|
11
|
+
* - Host actions callable from inside views (launchApp, returnToHome,
|
|
12
|
+
* listRegisteredApps — added in Task 7 and Task 10)
|
|
13
|
+
*
|
|
14
|
+
* What does NOT belong here:
|
|
15
|
+
* - `registerShard` / `registerApp` — those are host-only and live in
|
|
16
|
+
* `host.ts`. Shards and apps must not register each other.
|
|
17
|
+
* - Framework internals (state zone plumbing, layout manager internals,
|
|
18
|
+
* shard activation machinery).
|
|
19
|
+
*
|
|
20
|
+
* Anything re-exported from this file is part of the public contract.
|
|
21
|
+
* Phase 10 adds a validator that enforces the import-hygiene rule; for
|
|
22
|
+
* now the convention is documented and honored by hand.
|
|
23
|
+
*/
|
|
24
|
+
// Runtime singleton.
|
|
25
|
+
export { shell } from './shellRuntime.svelte';
|
|
26
|
+
export { PERMISSION_STATE_MANAGE } from './state/types';
|
|
27
|
+
// Host actions callable from inside views (shell home, status bar, etc.).
|
|
28
|
+
export { listRegisteredApps, getActiveApp } from './apps/registry.svelte';
|
|
29
|
+
export { launchApp, returnToHome } from './apps/lifecycle';
|
|
30
|
+
// Layout inspection / mutation for advanced shards (diagnostic, etc.).
|
|
31
|
+
export { inspectActiveLayout, spliceIntoActiveLayout, dockIntoActiveLayout, focusTab, focusView, collapseChild, expandChild, closeTab, } from './layout/inspection';
|
|
32
|
+
// Shard introspection — read-only reactive maps exposing which shards are
|
|
33
|
+
// known to the host and which are currently active. Intended for diagnostic
|
|
34
|
+
// and tooling shards that need to visualize framework state. Phase 9
|
|
35
|
+
// addition: diagnostic used to reach `activate.svelte` directly via $lib;
|
|
36
|
+
// the package boundary requires routing through the public surface.
|
|
37
|
+
export { registeredShards, activeShards } from './shards/activate.svelte';
|
|
38
|
+
export { fetchRegistries, fetchBundle, buildPackageMeta } from './registry/client';
|
|
39
|
+
export { validateRegistryIndex } from './registry/schema';
|
|
40
|
+
// Admin mode (framework-internal components read admin status).
|
|
41
|
+
export { isAdmin, isAuthenticated, isGuest, getUser, getAuthHeader } from './auth/index';
|
|
42
|
+
/** Runtime feature flags for target-dependent behavior. */
|
|
43
|
+
export const capabilities = {
|
|
44
|
+
/** Whether this target supports hot-installing packages via dynamic import from blob URL. */
|
|
45
|
+
hotInstall: typeof Blob !== 'undefined' && typeof URL.createObjectURL === 'function',
|
|
46
|
+
};
|
|
47
|
+
// Package version.
|
|
48
|
+
export { VERSION } from './version';
|
|
49
|
+
// Theme token override API (shell-level theming support).
|
|
50
|
+
export { setTokenOverrides, clearTokenOverrides, getTokenOverrides, } from './theme';
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Admin API Keys view — list, create, reveal, revoke API keys.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
interface ApiKey {
|
|
7
|
+
id: string;
|
|
8
|
+
key: string;
|
|
9
|
+
label: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let keys = $state<ApiKey[]>([]);
|
|
14
|
+
let loading = $state(true);
|
|
15
|
+
let error = $state<string | null>(null);
|
|
16
|
+
|
|
17
|
+
// Create form
|
|
18
|
+
let showCreate = $state(false);
|
|
19
|
+
let newLabel = $state('');
|
|
20
|
+
let createError = $state<string | null>(null);
|
|
21
|
+
|
|
22
|
+
// Reveal state — tracks which key ids have been revealed
|
|
23
|
+
let revealed = $state<Set<string>>(new Set());
|
|
24
|
+
|
|
25
|
+
// Delete confirmation
|
|
26
|
+
let confirmingId = $state<string | null>(null);
|
|
27
|
+
|
|
28
|
+
async function fetchKeys() {
|
|
29
|
+
loading = true;
|
|
30
|
+
error = null;
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch('/api/admin/keys', { credentials: 'include' });
|
|
33
|
+
if (!res.ok) throw new Error('Failed to fetch keys');
|
|
34
|
+
keys = await res.json();
|
|
35
|
+
} catch (err) {
|
|
36
|
+
error = err instanceof Error ? err.message : 'Failed to load keys';
|
|
37
|
+
} finally {
|
|
38
|
+
loading = false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function createKey() {
|
|
43
|
+
createError = null;
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch('/api/admin/keys', {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
credentials: 'include',
|
|
49
|
+
body: JSON.stringify({ label: newLabel }),
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
const body = await res.json().catch(() => ({}));
|
|
53
|
+
createError = body.error || 'Failed to create key';
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const created: ApiKey = await res.json();
|
|
57
|
+
revealed.add(created.id);
|
|
58
|
+
revealed = new Set(revealed);
|
|
59
|
+
newLabel = '';
|
|
60
|
+
showCreate = false;
|
|
61
|
+
await fetchKeys();
|
|
62
|
+
} catch {
|
|
63
|
+
createError = 'Network error';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function toggleReveal(id: string) {
|
|
68
|
+
if (revealed.has(id)) {
|
|
69
|
+
revealed.delete(id);
|
|
70
|
+
} else {
|
|
71
|
+
revealed.add(id);
|
|
72
|
+
}
|
|
73
|
+
revealed = new Set(revealed);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function revokeKey(id: string) {
|
|
77
|
+
confirmingId = null;
|
|
78
|
+
try {
|
|
79
|
+
await fetch(`/api/admin/keys/${id}`, {
|
|
80
|
+
method: 'DELETE',
|
|
81
|
+
credentials: 'include',
|
|
82
|
+
});
|
|
83
|
+
revealed.delete(id);
|
|
84
|
+
revealed = new Set(revealed);
|
|
85
|
+
await fetchKeys();
|
|
86
|
+
} catch { /* ignore */ }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function formatDate(iso: string): string {
|
|
90
|
+
return new Date(iso).toLocaleDateString(undefined, {
|
|
91
|
+
year: 'numeric', month: 'short', day: 'numeric',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function maskKey(key: string): string {
|
|
96
|
+
return key.slice(0, 8) + '\u2026' + key.slice(-4);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fetchKeys();
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<div class="admin-keys">
|
|
103
|
+
<div class="admin-keys-header">
|
|
104
|
+
<h2>API Keys</h2>
|
|
105
|
+
<button type="button" class="admin-btn" onclick={() => { showCreate = !showCreate; }}>
|
|
106
|
+
{showCreate ? 'Cancel' : 'New key'}
|
|
107
|
+
</button>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
{#if showCreate}
|
|
111
|
+
<form class="admin-create-form" onsubmit={(e) => { e.preventDefault(); createKey(); }}>
|
|
112
|
+
<input class="admin-input" type="text" placeholder="Key label" bind:value={newLabel} />
|
|
113
|
+
<button type="submit" class="admin-btn" disabled={!newLabel.trim()}>Create</button>
|
|
114
|
+
{#if createError}<div class="admin-error">{createError}</div>{/if}
|
|
115
|
+
</form>
|
|
116
|
+
{/if}
|
|
117
|
+
|
|
118
|
+
{#if loading}
|
|
119
|
+
<p class="admin-muted">Loading...</p>
|
|
120
|
+
{:else if error}
|
|
121
|
+
<p class="admin-error">{error}</p>
|
|
122
|
+
{:else if keys.length === 0}
|
|
123
|
+
<p class="admin-muted">No API keys. Create one to enable external access.</p>
|
|
124
|
+
{:else}
|
|
125
|
+
<ul class="admin-key-list">
|
|
126
|
+
{#each keys as k (k.id)}
|
|
127
|
+
<li class="admin-key-item">
|
|
128
|
+
<div class="admin-key-info">
|
|
129
|
+
<span class="admin-key-label">{k.label}</span>
|
|
130
|
+
<span class="admin-key-meta">{k.id} · {formatDate(k.createdAt)}</span>
|
|
131
|
+
<code class="admin-key-value">{revealed.has(k.id) ? k.key : maskKey(k.key)}</code>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="admin-key-actions">
|
|
134
|
+
<button type="button" class="admin-btn-secondary" onclick={() => toggleReveal(k.id)}>
|
|
135
|
+
{revealed.has(k.id) ? 'Hide' : 'Reveal'}
|
|
136
|
+
</button>
|
|
137
|
+
{#if confirmingId === k.id}
|
|
138
|
+
<button type="button" class="admin-btn-danger" onclick={() => revokeKey(k.id)}>Confirm</button>
|
|
139
|
+
<button type="button" class="admin-btn-secondary" onclick={() => { confirmingId = null; }}>Cancel</button>
|
|
140
|
+
{:else}
|
|
141
|
+
<button type="button" class="admin-btn-danger" onclick={() => { confirmingId = k.id; }}>Revoke</button>
|
|
142
|
+
{/if}
|
|
143
|
+
</div>
|
|
144
|
+
</li>
|
|
145
|
+
{/each}
|
|
146
|
+
</ul>
|
|
147
|
+
{/if}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<style>
|
|
151
|
+
.admin-keys { padding: 24px; font-family: system-ui, sans-serif; color: var(--shell-fg); }
|
|
152
|
+
.admin-keys-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
|
|
153
|
+
.admin-keys-header h2 { margin: 0; font-size: 18px; }
|
|
154
|
+
.admin-create-form { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; max-width: 400px; }
|
|
155
|
+
.admin-input { padding: 8px 12px; background: var(--shell-bg, #1a1a2e); color: var(--shell-fg); border: 1px solid var(--shell-border, #3a3a5c); border-radius: var(--shell-radius, 6px); font-size: 13px; }
|
|
156
|
+
.admin-btn { font-weight: 600; font-size: 13px; }
|
|
157
|
+
.admin-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
158
|
+
.admin-btn-secondary { background: transparent; color: var(--shell-fg-subtle); border: 1px solid var(--shell-border); font-size: 12px; }
|
|
159
|
+
.admin-btn-danger { background: transparent; color: var(--shell-error, #d32f2f); border: 1px solid var(--shell-error, #d32f2f); font-size: 12px; }
|
|
160
|
+
.admin-key-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
161
|
+
.admin-key-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: var(--shell-bg-elevated, #252540); border: 1px solid var(--shell-border, #3a3a5c); border-radius: var(--shell-radius, 6px); }
|
|
162
|
+
.admin-key-info { display: flex; flex-direction: column; gap: 2px; }
|
|
163
|
+
.admin-key-label { font-weight: 600; }
|
|
164
|
+
.admin-key-meta { font-size: 11px; color: var(--shell-fg-subtle); }
|
|
165
|
+
.admin-key-value { font-size: 12px; color: var(--shell-fg-muted); background: var(--shell-bg, #1a1a2e); padding: 2px 6px; border-radius: 3px; margin-top: 2px; word-break: break-all; }
|
|
166
|
+
.admin-key-actions { display: flex; gap: 6px; flex-shrink: 0; }
|
|
167
|
+
.admin-muted { color: var(--shell-fg-muted); font-style: italic; }
|
|
168
|
+
.admin-error { color: var(--shell-error, #d32f2f); font-size: 13px; }
|
|
169
|
+
</style>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Admin Auth Settings view — toggle auth-related global settings.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { GlobalSettings } from '../../auth/types';
|
|
7
|
+
|
|
8
|
+
let settings = $state<GlobalSettings | null>(null);
|
|
9
|
+
let loading = $state(true);
|
|
10
|
+
let saving = $state(false);
|
|
11
|
+
let error = $state<string | null>(null);
|
|
12
|
+
|
|
13
|
+
async function fetchSettings() {
|
|
14
|
+
loading = true;
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch('/api/admin/settings', { credentials: 'include' });
|
|
17
|
+
if (!res.ok) throw new Error('Failed to fetch settings');
|
|
18
|
+
settings = await res.json();
|
|
19
|
+
} catch (err) {
|
|
20
|
+
error = err instanceof Error ? err.message : 'Failed to load settings';
|
|
21
|
+
} finally {
|
|
22
|
+
loading = false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function save() {
|
|
27
|
+
if (!settings) return;
|
|
28
|
+
saving = true;
|
|
29
|
+
error = null;
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch('/api/admin/settings', {
|
|
32
|
+
method: 'PUT',
|
|
33
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34
|
+
credentials: 'include',
|
|
35
|
+
body: JSON.stringify(settings),
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) throw new Error('Failed to save settings');
|
|
38
|
+
settings = await res.json();
|
|
39
|
+
} catch (err) {
|
|
40
|
+
error = err instanceof Error ? err.message : 'Failed to save';
|
|
41
|
+
} finally {
|
|
42
|
+
saving = false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fetchSettings();
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<div class="admin-auth">
|
|
50
|
+
<h2>Auth Settings</h2>
|
|
51
|
+
|
|
52
|
+
{#if loading}
|
|
53
|
+
<p class="admin-muted">Loading...</p>
|
|
54
|
+
{:else if settings}
|
|
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
|
+
<label class="admin-toggle">
|
|
63
|
+
<input type="checkbox" bind:checked={settings.auth.guestAllowed} />
|
|
64
|
+
<span>Allow guest browsing</span>
|
|
65
|
+
<span class="admin-hint">When sign-in is required, guests can still browse with session-only data.</span>
|
|
66
|
+
</label>
|
|
67
|
+
|
|
68
|
+
<label class="admin-toggle">
|
|
69
|
+
<input type="checkbox" bind:checked={settings.auth.selfRegistration} />
|
|
70
|
+
<span>Self-registration</span>
|
|
71
|
+
<span class="admin-hint">Visitors can create their own accounts from the sign-in screen.</span>
|
|
72
|
+
</label>
|
|
73
|
+
|
|
74
|
+
<label class="admin-field">
|
|
75
|
+
<span>Session lifetime (hours)</span>
|
|
76
|
+
<input type="number" class="admin-input admin-input-sm" min="1" max="8760" bind:value={settings.auth.sessionTTL} />
|
|
77
|
+
</label>
|
|
78
|
+
|
|
79
|
+
<button type="button" class="admin-btn" onclick={save} disabled={saving}>
|
|
80
|
+
{saving ? 'Saving...' : 'Save'}
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{#if error}
|
|
85
|
+
<div class="admin-error">{error}</div>
|
|
86
|
+
{/if}
|
|
87
|
+
{/if}
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<style>
|
|
91
|
+
.admin-auth { padding: 24px; font-family: system-ui, sans-serif; color: var(--shell-fg); }
|
|
92
|
+
.admin-auth h2 { margin: 0 0 16px; font-size: 18px; }
|
|
93
|
+
.admin-auth-fields { display: flex; flex-direction: column; gap: 16px; max-width: 480px; }
|
|
94
|
+
.admin-toggle { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; cursor: pointer; }
|
|
95
|
+
.admin-toggle input { accent-color: var(--shell-accent, #7c7cf0); }
|
|
96
|
+
.admin-hint { flex-basis: 100%; font-size: 11px; color: var(--shell-fg-muted); margin-left: 24px; }
|
|
97
|
+
.admin-field { display: flex; flex-direction: column; gap: 4px; }
|
|
98
|
+
.admin-field span { font-size: 13px; }
|
|
99
|
+
.admin-input { padding: 8px 12px; background: var(--shell-bg); color: var(--shell-fg); border: 1px solid var(--shell-border); border-radius: var(--shell-radius, 6px); font-size: 13px; }
|
|
100
|
+
.admin-input-sm { max-width: 120px; }
|
|
101
|
+
.admin-btn { padding: 8px 16px; font-weight: 600; align-self: flex-start; }
|
|
102
|
+
.admin-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
103
|
+
.admin-error { margin-top: 8px; color: var(--shell-error, #d32f2f); font-size: 13px; }
|
|
104
|
+
.admin-muted { color: var(--shell-fg-muted); font-style: italic; }
|
|
105
|
+
</style>
|