@smartnet360/svelte-components 0.0.124 → 0.0.125
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/apps/antenna-tools/components/AntennaSettingsModal.svelte +4 -174
- package/dist/apps/antenna-tools/components/DatabaseViewer.svelte +2 -2
- package/dist/apps/antenna-tools/components/MSIConverter.svelte +302 -43
- package/dist/apps/antenna-tools/db.js +4 -0
- package/dist/apps/antenna-tools/utils/db-utils.d.ts +19 -0
- package/dist/apps/antenna-tools/utils/db-utils.js +108 -0
- package/dist/core/Auth/LoginForm.svelte +397 -0
- package/dist/core/Auth/LoginForm.svelte.d.ts +16 -0
- package/dist/core/Auth/auth.svelte.d.ts +22 -0
- package/dist/core/Auth/auth.svelte.js +184 -0
- package/dist/core/Auth/config.d.ts +25 -0
- package/dist/core/Auth/config.js +256 -0
- package/dist/core/Auth/index.d.ts +4 -0
- package/dist/core/Auth/index.js +5 -0
- package/dist/core/Auth/types.d.ts +140 -0
- package/dist/core/Auth/types.js +2 -0
- package/dist/core/LandingPage/App.svelte +102 -0
- package/dist/core/LandingPage/App.svelte.d.ts +20 -0
- package/dist/core/LandingPage/LandingPage.svelte +480 -0
- package/dist/core/LandingPage/LandingPage.svelte.d.ts +21 -0
- package/dist/core/LandingPage/index.d.ts +2 -0
- package/dist/core/LandingPage/index.js +3 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +4 -0
- package/package.json +1 -1
|
@@ -256,6 +256,114 @@ export async function clearAllAntennas() {
|
|
|
256
256
|
throw error;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Clear antennas by specific frequency bands
|
|
261
|
+
* @param bands - Array of band frequencies to delete (e.g., [700, 800, 1800])
|
|
262
|
+
* @returns Number of antennas deleted
|
|
263
|
+
*/
|
|
264
|
+
export async function clearAntennasByBands(bands) {
|
|
265
|
+
try {
|
|
266
|
+
trackDataOperation('clear', {
|
|
267
|
+
inProgress: true,
|
|
268
|
+
message: `Clearing bands: ${bands.join(', ')} MHz...`
|
|
269
|
+
});
|
|
270
|
+
// Get count before deletion
|
|
271
|
+
const toDelete = await db.antennas.where('frequency').anyOf(bands).toArray();
|
|
272
|
+
const deleteCount = toDelete.length;
|
|
273
|
+
// Delete matching antennas
|
|
274
|
+
await db.antennas.where('frequency').anyOf(bands).delete();
|
|
275
|
+
// Reload remaining antennas into store
|
|
276
|
+
const remaining = await db.antennas.toArray();
|
|
277
|
+
antennas.set(remaining);
|
|
278
|
+
updateDbStatus({
|
|
279
|
+
antennaCount: remaining.length,
|
|
280
|
+
lastUpdated: new Date()
|
|
281
|
+
});
|
|
282
|
+
trackDataOperation('clear', {
|
|
283
|
+
inProgress: false,
|
|
284
|
+
success: true,
|
|
285
|
+
message: `Deleted ${deleteCount} antennas from bands: ${bands.join(', ')} MHz`
|
|
286
|
+
});
|
|
287
|
+
return deleteCount;
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
console.error('Error clearing antennas by bands:', error);
|
|
291
|
+
trackDataOperation('clear', {
|
|
292
|
+
inProgress: false,
|
|
293
|
+
success: false,
|
|
294
|
+
error: error instanceof Error ? error.message : 'Unknown error clearing bands',
|
|
295
|
+
message: 'Failed to clear bands'
|
|
296
|
+
});
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Get count of antennas per frequency band
|
|
302
|
+
* @returns Map of band frequency to count
|
|
303
|
+
*/
|
|
304
|
+
export async function getAntennasCountByBand() {
|
|
305
|
+
const all = await db.antennas.toArray();
|
|
306
|
+
const counts = new Map();
|
|
307
|
+
for (const antenna of all) {
|
|
308
|
+
const current = counts.get(antenna.frequency) || 0;
|
|
309
|
+
counts.set(antenna.frequency, current + 1);
|
|
310
|
+
}
|
|
311
|
+
return counts;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Add antennas to database (merge mode - no clearing)
|
|
315
|
+
* Duplicates (same name + frequency + tilt) are overwritten
|
|
316
|
+
*/
|
|
317
|
+
export async function addAntennas(newAntennas) {
|
|
318
|
+
try {
|
|
319
|
+
trackDataOperation('import', { inProgress: true, message: 'Adding antennas...' });
|
|
320
|
+
let added = 0;
|
|
321
|
+
let updated = 0;
|
|
322
|
+
for (const antenna of newAntennas) {
|
|
323
|
+
// Check for existing antenna with same name, frequency, and tilt
|
|
324
|
+
const existing = await db.antennas
|
|
325
|
+
.where(['name', 'frequency', 'tilt'])
|
|
326
|
+
.equals([antenna.name, antenna.frequency, antenna.tilt])
|
|
327
|
+
.first();
|
|
328
|
+
if (existing) {
|
|
329
|
+
// Delete and re-add to update (simpler than partial update with arrays)
|
|
330
|
+
await db.antennas.delete(existing.id);
|
|
331
|
+
await db.antennas.add(antenna);
|
|
332
|
+
updated++;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
// Add new
|
|
336
|
+
await db.antennas.add(antenna);
|
|
337
|
+
added++;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Reload store
|
|
341
|
+
const all = await db.antennas.toArray();
|
|
342
|
+
antennas.set(all);
|
|
343
|
+
localStorage.setItem('antenna-tools-data-imported', 'true');
|
|
344
|
+
updateDbStatus({
|
|
345
|
+
initialized: true,
|
|
346
|
+
antennaCount: all.length,
|
|
347
|
+
lastUpdated: new Date()
|
|
348
|
+
});
|
|
349
|
+
trackDataOperation('import', {
|
|
350
|
+
inProgress: false,
|
|
351
|
+
success: true,
|
|
352
|
+
message: `Added ${added}, updated ${updated} antennas`
|
|
353
|
+
});
|
|
354
|
+
return { added, updated };
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
console.error('Error adding antennas:', error);
|
|
358
|
+
trackDataOperation('import', {
|
|
359
|
+
inProgress: false,
|
|
360
|
+
success: false,
|
|
361
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
362
|
+
message: 'Add failed'
|
|
363
|
+
});
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
259
367
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
260
368
|
// UTILITY FUNCTIONS
|
|
261
369
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { AuthState } from './auth.svelte.js';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Auth state instance */
|
|
6
|
+
auth: AuthState;
|
|
7
|
+
/** Title for login form */
|
|
8
|
+
title?: string;
|
|
9
|
+
/** Subtitle/description */
|
|
10
|
+
subtitle?: string;
|
|
11
|
+
/** Logo URL (optional) */
|
|
12
|
+
logo?: string;
|
|
13
|
+
/** Callback on successful login */
|
|
14
|
+
onLogin?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
auth,
|
|
19
|
+
title = 'Welcome',
|
|
20
|
+
subtitle = 'Sign in to access your tools',
|
|
21
|
+
logo,
|
|
22
|
+
onLogin
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
// Form state
|
|
26
|
+
let username = $state('');
|
|
27
|
+
let password = $state('');
|
|
28
|
+
let agreedToMonitoring = $state(false);
|
|
29
|
+
let showPassword = $state(false);
|
|
30
|
+
|
|
31
|
+
// Form validation
|
|
32
|
+
const isFormValid = $derived(
|
|
33
|
+
username.trim().length > 0 &&
|
|
34
|
+
password.length > 0 &&
|
|
35
|
+
agreedToMonitoring
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
async function handleSubmit(e: SubmitEvent) {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
|
|
41
|
+
if (!isFormValid) return;
|
|
42
|
+
|
|
43
|
+
const success = await auth.login(username.trim(), password, agreedToMonitoring);
|
|
44
|
+
|
|
45
|
+
if (success) {
|
|
46
|
+
username = '';
|
|
47
|
+
password = '';
|
|
48
|
+
agreedToMonitoring = false;
|
|
49
|
+
onLogin?.();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function togglePassword() {
|
|
54
|
+
showPassword = !showPassword;
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div class="login-page">
|
|
59
|
+
<div class="login-card">
|
|
60
|
+
<!-- Header -->
|
|
61
|
+
<div class="login-header">
|
|
62
|
+
{#if logo}
|
|
63
|
+
<img src={logo} alt="Logo" class="login-logo" />
|
|
64
|
+
{:else}
|
|
65
|
+
<div class="login-icon">
|
|
66
|
+
<i class="bi bi-broadcast-pin"></i>
|
|
67
|
+
</div>
|
|
68
|
+
{/if}
|
|
69
|
+
<h1>{title}</h1>
|
|
70
|
+
<p>{subtitle}</p>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<!-- Error -->
|
|
74
|
+
{#if auth.error}
|
|
75
|
+
<div class="login-error">
|
|
76
|
+
<i class="bi bi-exclamation-circle"></i>
|
|
77
|
+
<span>{auth.error}</span>
|
|
78
|
+
<button type="button" onclick={() => auth.clearError()} aria-label="Dismiss error">
|
|
79
|
+
<i class="bi bi-x"></i>
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
{/if}
|
|
83
|
+
|
|
84
|
+
<!-- Form -->
|
|
85
|
+
<form onsubmit={handleSubmit} class="login-form">
|
|
86
|
+
<div class="form-field">
|
|
87
|
+
<label for="username">Username</label>
|
|
88
|
+
<div class="input-wrapper">
|
|
89
|
+
<i class="bi bi-person"></i>
|
|
90
|
+
<input
|
|
91
|
+
type="text"
|
|
92
|
+
id="username"
|
|
93
|
+
bind:value={username}
|
|
94
|
+
placeholder="Enter username"
|
|
95
|
+
autocomplete="username"
|
|
96
|
+
disabled={auth.isLoading}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div class="form-field">
|
|
102
|
+
<label for="password">Password</label>
|
|
103
|
+
<div class="input-wrapper">
|
|
104
|
+
<i class="bi bi-lock"></i>
|
|
105
|
+
<input
|
|
106
|
+
type={showPassword ? 'text' : 'password'}
|
|
107
|
+
id="password"
|
|
108
|
+
bind:value={password}
|
|
109
|
+
placeholder="Enter password"
|
|
110
|
+
autocomplete="current-password"
|
|
111
|
+
disabled={auth.isLoading}
|
|
112
|
+
/>
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
class="toggle-password"
|
|
116
|
+
onclick={togglePassword}
|
|
117
|
+
tabindex={-1}
|
|
118
|
+
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
|
119
|
+
>
|
|
120
|
+
<i class="bi {showPassword ? 'bi-eye-slash' : 'bi-eye'}"></i>
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<label class="checkbox-field">
|
|
126
|
+
<input
|
|
127
|
+
type="checkbox"
|
|
128
|
+
bind:checked={agreedToMonitoring}
|
|
129
|
+
disabled={auth.isLoading}
|
|
130
|
+
/>
|
|
131
|
+
<span class="checkmark"></span>
|
|
132
|
+
<span class="checkbox-text">
|
|
133
|
+
I agree to activity monitoring for security purposes
|
|
134
|
+
</span>
|
|
135
|
+
</label>
|
|
136
|
+
|
|
137
|
+
<button
|
|
138
|
+
type="submit"
|
|
139
|
+
class="login-button"
|
|
140
|
+
disabled={!isFormValid || auth.isLoading}
|
|
141
|
+
>
|
|
142
|
+
{#if auth.isLoading}
|
|
143
|
+
<span class="spinner"></span>
|
|
144
|
+
Signing in...
|
|
145
|
+
{:else}
|
|
146
|
+
Sign In
|
|
147
|
+
<i class="bi bi-arrow-right"></i>
|
|
148
|
+
{/if}
|
|
149
|
+
</button>
|
|
150
|
+
</form>
|
|
151
|
+
|
|
152
|
+
<div class="login-footer">
|
|
153
|
+
<i class="bi bi-shield-check"></i>
|
|
154
|
+
LDAP Authentication
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<style>
|
|
160
|
+
.login-page {
|
|
161
|
+
min-height: 100vh;
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
justify-content: center;
|
|
165
|
+
padding: 1.5rem;
|
|
166
|
+
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.login-card {
|
|
170
|
+
width: 100%;
|
|
171
|
+
max-width: 380px;
|
|
172
|
+
background: #fff;
|
|
173
|
+
border-radius: 16px;
|
|
174
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
|
|
175
|
+
overflow: hidden;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.login-header {
|
|
179
|
+
text-align: center;
|
|
180
|
+
padding: 2.5rem 2rem 1.5rem;
|
|
181
|
+
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
|
182
|
+
color: #fff;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.login-logo {
|
|
186
|
+
height: 48px;
|
|
187
|
+
margin-bottom: 1rem;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.login-icon {
|
|
191
|
+
font-size: 2.5rem;
|
|
192
|
+
margin-bottom: 0.75rem;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.login-header h1 {
|
|
196
|
+
font-size: 1.5rem;
|
|
197
|
+
font-weight: 600;
|
|
198
|
+
margin: 0 0 0.25rem;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.login-header p {
|
|
202
|
+
font-size: 0.875rem;
|
|
203
|
+
opacity: 0.85;
|
|
204
|
+
margin: 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.login-error {
|
|
208
|
+
display: flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
gap: 0.5rem;
|
|
211
|
+
margin: 1rem 1.5rem 0;
|
|
212
|
+
padding: 0.75rem 1rem;
|
|
213
|
+
background: #fef2f2;
|
|
214
|
+
border: 1px solid #fecaca;
|
|
215
|
+
border-radius: 8px;
|
|
216
|
+
color: #dc2626;
|
|
217
|
+
font-size: 0.875rem;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.login-error button {
|
|
221
|
+
margin-left: auto;
|
|
222
|
+
background: none;
|
|
223
|
+
border: none;
|
|
224
|
+
color: inherit;
|
|
225
|
+
cursor: pointer;
|
|
226
|
+
padding: 0;
|
|
227
|
+
font-size: 1.25rem;
|
|
228
|
+
line-height: 1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.login-form {
|
|
232
|
+
padding: 1.5rem 2rem 2rem;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.form-field {
|
|
236
|
+
margin-bottom: 1.25rem;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.form-field label {
|
|
240
|
+
display: block;
|
|
241
|
+
font-size: 0.8125rem;
|
|
242
|
+
font-weight: 500;
|
|
243
|
+
color: #374151;
|
|
244
|
+
margin-bottom: 0.5rem;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.input-wrapper {
|
|
248
|
+
position: relative;
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.input-wrapper > i:first-child {
|
|
254
|
+
position: absolute;
|
|
255
|
+
left: 0.875rem;
|
|
256
|
+
color: #9ca3af;
|
|
257
|
+
font-size: 1rem;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.input-wrapper input {
|
|
261
|
+
width: 100%;
|
|
262
|
+
padding: 0.75rem 0.875rem 0.75rem 2.5rem;
|
|
263
|
+
border: 1px solid #e5e7eb;
|
|
264
|
+
border-radius: 8px;
|
|
265
|
+
font-size: 0.9375rem;
|
|
266
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.input-wrapper input:focus {
|
|
270
|
+
outline: none;
|
|
271
|
+
border-color: #3b82f6;
|
|
272
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.input-wrapper input:disabled {
|
|
276
|
+
background: #f9fafb;
|
|
277
|
+
cursor: not-allowed;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.toggle-password {
|
|
281
|
+
position: absolute;
|
|
282
|
+
right: 0.5rem;
|
|
283
|
+
background: none;
|
|
284
|
+
border: none;
|
|
285
|
+
color: #9ca3af;
|
|
286
|
+
cursor: pointer;
|
|
287
|
+
padding: 0.5rem;
|
|
288
|
+
font-size: 1rem;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.toggle-password:hover {
|
|
292
|
+
color: #6b7280;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.checkbox-field {
|
|
296
|
+
display: flex;
|
|
297
|
+
align-items: flex-start;
|
|
298
|
+
gap: 0.625rem;
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
margin-bottom: 1.5rem;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.checkbox-field input {
|
|
304
|
+
position: absolute;
|
|
305
|
+
opacity: 0;
|
|
306
|
+
pointer-events: none;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.checkmark {
|
|
310
|
+
flex-shrink: 0;
|
|
311
|
+
width: 18px;
|
|
312
|
+
height: 18px;
|
|
313
|
+
border: 2px solid #d1d5db;
|
|
314
|
+
border-radius: 4px;
|
|
315
|
+
transition: all 0.15s;
|
|
316
|
+
display: flex;
|
|
317
|
+
align-items: center;
|
|
318
|
+
justify-content: center;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.checkbox-field input:checked + .checkmark {
|
|
322
|
+
background: #3b82f6;
|
|
323
|
+
border-color: #3b82f6;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.checkbox-field input:checked + .checkmark::after {
|
|
327
|
+
content: '';
|
|
328
|
+
width: 5px;
|
|
329
|
+
height: 9px;
|
|
330
|
+
border: solid #fff;
|
|
331
|
+
border-width: 0 2px 2px 0;
|
|
332
|
+
transform: rotate(45deg);
|
|
333
|
+
margin-bottom: 2px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.checkbox-text {
|
|
337
|
+
font-size: 0.8125rem;
|
|
338
|
+
color: #4b5563;
|
|
339
|
+
line-height: 1.4;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.login-button {
|
|
343
|
+
width: 100%;
|
|
344
|
+
padding: 0.875rem 1.5rem;
|
|
345
|
+
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
|
346
|
+
color: #fff;
|
|
347
|
+
border: none;
|
|
348
|
+
border-radius: 8px;
|
|
349
|
+
font-size: 0.9375rem;
|
|
350
|
+
font-weight: 500;
|
|
351
|
+
cursor: pointer;
|
|
352
|
+
display: flex;
|
|
353
|
+
align-items: center;
|
|
354
|
+
justify-content: center;
|
|
355
|
+
gap: 0.5rem;
|
|
356
|
+
transition: opacity 0.15s, transform 0.15s;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.login-button:hover:not(:disabled) {
|
|
360
|
+
opacity: 0.9;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.login-button:active:not(:disabled) {
|
|
364
|
+
transform: scale(0.98);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.login-button:disabled {
|
|
368
|
+
opacity: 0.5;
|
|
369
|
+
cursor: not-allowed;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.spinner {
|
|
373
|
+
width: 16px;
|
|
374
|
+
height: 16px;
|
|
375
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
376
|
+
border-top-color: #fff;
|
|
377
|
+
border-radius: 50%;
|
|
378
|
+
animation: spin 0.8s linear infinite;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
@keyframes spin {
|
|
382
|
+
to { transform: rotate(360deg); }
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.login-footer {
|
|
386
|
+
text-align: center;
|
|
387
|
+
padding: 1rem;
|
|
388
|
+
background: #f8fafc;
|
|
389
|
+
border-top: 1px solid #e5e7eb;
|
|
390
|
+
font-size: 0.75rem;
|
|
391
|
+
color: #64748b;
|
|
392
|
+
display: flex;
|
|
393
|
+
align-items: center;
|
|
394
|
+
justify-content: center;
|
|
395
|
+
gap: 0.375rem;
|
|
396
|
+
}
|
|
397
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AuthState } from './auth.svelte.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Auth state instance */
|
|
4
|
+
auth: AuthState;
|
|
5
|
+
/** Title for login form */
|
|
6
|
+
title?: string;
|
|
7
|
+
/** Subtitle/description */
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
/** Logo URL (optional) */
|
|
10
|
+
logo?: string;
|
|
11
|
+
/** Callback on successful login */
|
|
12
|
+
onLogin?: () => void;
|
|
13
|
+
}
|
|
14
|
+
declare const LoginForm: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type LoginForm = ReturnType<typeof LoginForm>;
|
|
16
|
+
export default LoginForm;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { UserSession, User, FeatureAccess, AuthConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create auth state store with Svelte 5 runes
|
|
4
|
+
* This must be called within a component context or .svelte.ts file
|
|
5
|
+
*/
|
|
6
|
+
export declare function createAuthState(config?: Partial<AuthConfig>): {
|
|
7
|
+
readonly session: UserSession | null;
|
|
8
|
+
readonly isLoading: boolean;
|
|
9
|
+
readonly error: string | null;
|
|
10
|
+
readonly initialized: boolean;
|
|
11
|
+
readonly isAuthenticated: boolean;
|
|
12
|
+
readonly user: User | null;
|
|
13
|
+
readonly permissions: FeatureAccess[];
|
|
14
|
+
readonly token: string | null;
|
|
15
|
+
login: (username: string, password: string, agreedToMonitoring: boolean) => Promise<boolean>;
|
|
16
|
+
loginDev: (devUser: User, devPermissions: FeatureAccess[]) => void;
|
|
17
|
+
logout: () => Promise<void>;
|
|
18
|
+
hasPermission: (featureId: string, level?: "view" | "edit" | "admin") => boolean;
|
|
19
|
+
canAccessRoute: (route: string, requiredPermission?: string) => boolean;
|
|
20
|
+
clearError: () => void;
|
|
21
|
+
};
|
|
22
|
+
export type AuthState = ReturnType<typeof createAuthState>;
|