@svadmin/lite 0.3.1 → 0.3.3
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/package.json +2 -2
- package/src/components/LiteAuditLog.svelte +2 -2
- package/src/components/LiteLayout.svelte +2 -2
- package/src/components/LiteShow.svelte +2 -2
- package/src/components/LiteTable.svelte +1 -1
- package/src/components/advanced/LiteDrawerForm.svelte +1 -1
- package/src/components/advanced/LiteModalForm.svelte +1 -1
- package/src/components/buttons/LiteCloneButton.svelte +1 -1
- package/src/components/buttons/LiteCreateButton.svelte +1 -1
- package/src/components/buttons/LiteDeleteButton.svelte +1 -1
- package/src/components/buttons/LiteEditButton.svelte +1 -1
- package/src/components/buttons/LiteImportButton.svelte +1 -1
- package/src/components/buttons/LiteListButton.svelte +1 -1
- package/src/components/buttons/LiteRefreshButton.svelte +1 -1
- package/src/components/buttons/LiteShowButton.svelte +1 -1
- package/src/components/layout/LiteSidebar.svelte +2 -2
- package/src/components/pages/LiteCreatePage.svelte +2 -2
- package/src/components/pages/LiteEditPage.svelte +3 -3
- package/src/components/pages/LiteListPage.svelte +5 -0
- package/src/components/pages/LiteShowPage.svelte +1 -1
- package/src/schema-generator.ts +2 -0
- package/src/server-adapter.ts +19 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/lite",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "SSR-compatible lightweight admin UI for @svadmin — zero client-side JS, works in IE11",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"svelte": "^5.0.0",
|
|
24
|
-
"@svadmin/core": "^0.
|
|
24
|
+
"@svadmin/core": "^0.22.1",
|
|
25
25
|
"@sveltejs/kit": "^2.0.0"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
@@ -79,11 +79,11 @@
|
|
|
79
79
|
{#if totalPages > 1}
|
|
80
80
|
<div class="lite-pagination" style="margin-top:12px;">
|
|
81
81
|
{#if page > 1}
|
|
82
|
-
<a href=
|
|
82
|
+
<a href={`${basePath}page={page - 1}`} class="lite-btn lite-btn-sm">« Prev</a>
|
|
83
83
|
{/if}
|
|
84
84
|
<span style="padding:0 12px;font-size:14px;color:#64748b;">Page {page} / {totalPages} ({total} total)</span>
|
|
85
85
|
{#if page < totalPages}
|
|
86
|
-
<a href=
|
|
86
|
+
<a href={`${basePath}page={page + 1}`} class="lite-btn lite-btn-sm">Next »</a>
|
|
87
87
|
{/if}
|
|
88
88
|
</div>
|
|
89
89
|
{/if}
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
{:else}
|
|
82
82
|
{#each menuResources as res}
|
|
83
83
|
<a
|
|
84
|
-
href=
|
|
84
|
+
href={`${basePath}/${res.name}`}
|
|
85
85
|
class={res.name === currentResource ? 'active' : ''}
|
|
86
86
|
>
|
|
87
87
|
{res.label}
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
{#if userName}
|
|
92
92
|
<div style="position:absolute;bottom:0;left:0;right:0;padding:12px 16px;border-top:1px solid #334155;font-size:12px;color:#94a3b8;">
|
|
93
93
|
{userName}
|
|
94
|
-
<form method="POST" action=
|
|
94
|
+
<form method="POST" action={`${basePath}/login?/logout`} style="display:inline;margin-left:8px;">
|
|
95
95
|
<button type="submit" class="lite-btn lite-btn-sm" style="color:#94a3b8;border-color:#475569;background:transparent;">Logout</button>
|
|
96
96
|
</form>
|
|
97
97
|
</div>
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
<h1>{resource.label} #{id}</h1>
|
|
34
34
|
<div>
|
|
35
35
|
{#if canEdit}
|
|
36
|
-
<a href=
|
|
36
|
+
<a href={`${basePath}/${resource.name}/edit/${id}`} class="lite-btn lite-btn-primary">
|
|
37
37
|
{t('common.edit') || 'Edit'}
|
|
38
38
|
</a>
|
|
39
39
|
{/if}
|
|
40
|
-
<a href=
|
|
40
|
+
<a href={`${basePath}/${resource.name}`} class="lite-btn" style="margin-left:8px;">
|
|
41
41
|
{t('common.backToList') || 'Back to List'}
|
|
42
42
|
</a>
|
|
43
43
|
</div>
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
{#if canEdit || canDelete}
|
|
104
104
|
<td class="actions">
|
|
105
105
|
{#if canEdit}
|
|
106
|
-
<a href=
|
|
106
|
+
<a href={`${basePath}/${resource.name}/edit/${id}`} class="lite-btn lite-btn-sm">{t('common.edit') || 'Edit'}</a>
|
|
107
107
|
{/if}
|
|
108
108
|
{#if canDelete}
|
|
109
109
|
<!-- Delete uses <details> for no-JS confirmation -->
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<p style="margin: 0 0 8px; font-size: 13px; color: #111827;">
|
|
38
38
|
{t('common.areYouSure') || 'Are you sure?'}
|
|
39
39
|
</p>
|
|
40
|
-
<form method="POST" action="?/
|
|
40
|
+
<form method="POST" action="?/delete" style="display:inline-flex; gap: 8px;">
|
|
41
41
|
<input type="hidden" name="id" value={String(recordItemId)} />
|
|
42
42
|
{#if redirectUrl}
|
|
43
43
|
<input type="hidden" name="redirect" value={redirectUrl} />
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
</summary>
|
|
32
32
|
<div class="lite-confirm-panel">
|
|
33
33
|
<p style="margin: 0 0 8px; font-size: 13px;">{t('common.importData') || 'Import data (CSV/JSON)'}</p>
|
|
34
|
-
<form method="POST" action="
|
|
34
|
+
<form method="POST" action="?/${resource}_import" enctype="multipart/form-data" style="display:flex; flex-direction: column; gap: 8px;">
|
|
35
35
|
<input type="file" name="file" accept=".csv,.json" required style="font-size: 13px;" />
|
|
36
36
|
<div style="display:flex; gap: 8px; justify-content: flex-end;">
|
|
37
37
|
<button
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
{:else}
|
|
76
76
|
{#each menuResources as res}
|
|
77
77
|
<a
|
|
78
|
-
href=
|
|
78
|
+
href={`${basePath}/${res.name}`}
|
|
79
79
|
class={res.name === currentResource ? 'active' : ''}
|
|
80
80
|
>
|
|
81
81
|
{res.label ?? res.name}
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
{#if userName}
|
|
86
86
|
<div style="position:absolute;bottom:0;left:0;right:0;padding:12px 16px;border-top:1px solid #334155;font-size:12px;color:#94a3b8;">
|
|
87
87
|
{userName}
|
|
88
|
-
<form method="POST" action=
|
|
88
|
+
<form method="POST" action={`${basePath}/login?/logout`} style="display:inline;margin-left:8px;">
|
|
89
89
|
<button type="submit" class="lite-btn lite-btn-sm" style="color:#94a3b8;border-color:#475569;background:transparent;padding:2px 8px;">Logout</button>
|
|
90
90
|
</form>
|
|
91
91
|
</div>
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
{/if}
|
|
38
38
|
<LiteListButton resource={resource.name} {basePath} />
|
|
39
39
|
{#if canDelete}
|
|
40
|
-
<LiteDeleteButton resource={resource.name} recordItemId={idStr} redirectUrl=
|
|
40
|
+
<LiteDeleteButton resource={resource.name} recordItemId={idStr} redirectUrl={`${basePath}/${resource.name}`} {basePath} />
|
|
41
41
|
{/if}
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
{resource}
|
|
49
49
|
{errors}
|
|
50
50
|
values={record}
|
|
51
|
-
action="?/
|
|
52
|
-
cancelUrl=
|
|
51
|
+
action="?/update"
|
|
52
|
+
cancelUrl={`${basePath}/${resource.name}`}
|
|
53
53
|
/>
|
|
54
54
|
</div>
|
|
@@ -70,6 +70,11 @@
|
|
|
70
70
|
<LitePagination
|
|
71
71
|
page={pagination.page}
|
|
72
72
|
totalPages={Math.ceil(total / pagination.perPage)}
|
|
73
|
+
preserveParams={{
|
|
74
|
+
...(currentSort ? { sort: currentSort } : {}),
|
|
75
|
+
...(currentOrder ? { order: currentOrder } : {}),
|
|
76
|
+
...(currentSearch ? { q: currentSearch } : {})
|
|
77
|
+
}}
|
|
73
78
|
/>
|
|
74
79
|
{/if}
|
|
75
80
|
</div>
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
{/if}
|
|
40
40
|
<LiteListButton resource={resource.name} {basePath} />
|
|
41
41
|
{#if canDelete}
|
|
42
|
-
<LiteDeleteButton resource={resource.name} recordItemId={idStr} redirectUrl=
|
|
42
|
+
<LiteDeleteButton resource={resource.name} recordItemId={idStr} redirectUrl={`${basePath}/${resource.name}`} {basePath} />
|
|
43
43
|
{/if}
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
package/src/schema-generator.ts
CHANGED
|
@@ -43,6 +43,8 @@ function fieldToZod(field: FieldDefinition): z.ZodTypeAny {
|
|
|
43
43
|
}
|
|
44
44
|
break;
|
|
45
45
|
case 'multiselect':
|
|
46
|
+
schema = z.array(z.string()).default([]);
|
|
47
|
+
break;
|
|
46
48
|
case 'tags':
|
|
47
49
|
schema = z.string().transform((v: string) => v ? v.split(',').map((s: string) => s.trim()) : []);
|
|
48
50
|
break;
|
package/src/server-adapter.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
ResourceDefinition, FieldDefinition,
|
|
10
10
|
Sort, Filter,
|
|
11
11
|
} from '@svadmin/core';
|
|
12
|
-
import type
|
|
12
|
+
import { fail, redirect, isRedirect, type RequestEvent } from '@sveltejs/kit';
|
|
13
13
|
|
|
14
14
|
// ─── List Loader ──────────────────────────────────────────────
|
|
15
15
|
|
|
@@ -107,6 +107,7 @@ export function createCrudActions(
|
|
|
107
107
|
const result = await dp.create({ resource: resource.name, variables });
|
|
108
108
|
return { success: true, id: (result.data as Record<string, unknown>)[pk] };
|
|
109
109
|
} catch (e) {
|
|
110
|
+
if (isRedirect(e)) throw e;
|
|
110
111
|
return { success: false, error: (e as Error).message, values: variables };
|
|
111
112
|
}
|
|
112
113
|
},
|
|
@@ -120,6 +121,7 @@ export function createCrudActions(
|
|
|
120
121
|
await dp.update({ resource: resource.name, id, variables });
|
|
121
122
|
return { success: true };
|
|
122
123
|
} catch (e) {
|
|
124
|
+
if (isRedirect(e)) throw e;
|
|
123
125
|
return { success: false, error: (e as Error).message, values: variables };
|
|
124
126
|
}
|
|
125
127
|
},
|
|
@@ -127,10 +129,13 @@ export function createCrudActions(
|
|
|
127
129
|
delete: async ({ request }: RequestEvent) => {
|
|
128
130
|
const formData = await request.formData();
|
|
129
131
|
const id = formData.get('id') as string;
|
|
132
|
+
const redirectTo = formData.get('redirect') as string | undefined;
|
|
130
133
|
try {
|
|
131
134
|
await dp.deleteOne({ resource: resource.name, id });
|
|
135
|
+
if (redirectTo) throw redirect(303, redirectTo);
|
|
132
136
|
return { success: true };
|
|
133
137
|
} catch (e) {
|
|
138
|
+
if (isRedirect(e)) throw e;
|
|
134
139
|
return { success: false, error: (e as Error).message };
|
|
135
140
|
}
|
|
136
141
|
},
|
|
@@ -153,21 +158,31 @@ export function createAuthGuard(
|
|
|
153
158
|
return resolve(event);
|
|
154
159
|
}
|
|
155
160
|
|
|
161
|
+
// Local Lite Session Verify
|
|
162
|
+
const session = event.cookies.get('svadmin-session');
|
|
163
|
+
if (!session) {
|
|
164
|
+
return new Response(null, {
|
|
165
|
+
status: 302,
|
|
166
|
+
headers: { Location: loginPath },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
156
170
|
try {
|
|
157
171
|
const check = await authProvider.check();
|
|
158
172
|
if (!check.authenticated) {
|
|
173
|
+
event.cookies.delete('svadmin-session', { path: '/' });
|
|
159
174
|
return new Response(null, {
|
|
160
175
|
status: 302,
|
|
161
176
|
headers: { Location: loginPath },
|
|
162
177
|
});
|
|
163
178
|
}
|
|
164
179
|
} catch {
|
|
180
|
+
event.cookies.delete('svadmin-session', { path: '/' });
|
|
165
181
|
return new Response(null, {
|
|
166
182
|
status: 302,
|
|
167
183
|
headers: { Location: loginPath },
|
|
168
184
|
});
|
|
169
185
|
}
|
|
170
|
-
|
|
171
186
|
return resolve(event);
|
|
172
187
|
};
|
|
173
188
|
}
|
|
@@ -190,10 +205,11 @@ export function createAuthActions(authProvider: AuthProvider) {
|
|
|
190
205
|
sameSite: 'lax',
|
|
191
206
|
maxAge: 60 * 60 * 24 * 7, // 7 days
|
|
192
207
|
});
|
|
193
|
-
|
|
208
|
+
throw redirect(303, result.redirectTo ?? '/lite');
|
|
194
209
|
}
|
|
195
210
|
return { success: false, error: result.error?.message ?? 'Login failed' };
|
|
196
211
|
} catch (e) {
|
|
212
|
+
if (isRedirect(e)) throw e;
|
|
197
213
|
return { success: false, error: (e as Error).message };
|
|
198
214
|
}
|
|
199
215
|
},
|