@pablozaiden/webapp 0.1.0 → 0.2.1
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 +4 -3
- package/docs/auth-validation.md +24 -5
- package/docs/auth.md +33 -9
- package/docs/cli.md +45 -0
- package/docs/deployment.md +3 -0
- package/docs/getting-started.md +47 -7
- package/docs/github-actions.md +8 -3
- package/docs/realtime.md +7 -2
- package/docs/server.md +89 -4
- package/docs/settings.md +15 -5
- package/docs/sidebar.md +3 -3
- package/docs/ui-guidelines.md +8 -2
- package/package.json +2 -1
- package/src/cli/api-command.ts +119 -0
- package/src/cli/credentials.ts +67 -0
- package/src/cli/device-auth.ts +179 -0
- package/src/cli/index.ts +4 -0
- package/src/cli/runtime.ts +51 -0
- package/src/contracts/index.ts +53 -0
- package/src/server/auth/api-keys.ts +13 -8
- package/src/server/auth/device-auth.ts +34 -11
- package/src/server/auth/passkeys.ts +191 -73
- package/src/server/auth/sqlite-store.ts +394 -44
- package/src/server/auth/store.ts +57 -13
- package/src/server/auth/types.ts +7 -3
- package/src/server/auth/users.ts +83 -0
- package/src/server/create-web-app-server.ts +451 -54
- package/src/server/index.ts +1 -0
- package/src/server/realtime/bus.ts +8 -2
- package/src/server/route-catalog.ts +147 -0
- package/src/server/routes.ts +35 -4
- package/src/server/runtime-config.ts +1 -1
- package/src/web/WebAppRoot.tsx +336 -30
- package/src/web/api-client.ts +64 -0
- package/src/web/components/index.tsx +44 -11
- package/src/web/index.ts +2 -0
- package/src/web/realtime/useRealtime.ts +2 -2
- package/src/web/render.tsx +26 -0
- package/src/web/styles.css +104 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { appWebSocketUrl } from "../api-client";
|
|
2
3
|
|
|
3
4
|
export type RealtimeStatus = "connecting" | "open" | "closed";
|
|
4
5
|
export type RealtimeAction = "created" | "updated" | "changed" | "deleted";
|
|
@@ -71,8 +72,7 @@ export function useRealtime<TEvent>({
|
|
|
71
72
|
for (const [key, value] of Object.entries(filters ?? {})) {
|
|
72
73
|
if (value) params.set(key, value);
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
-
socket = new WebSocket(`${protocol}//${window.location.host}${path}${params.size ? `?${params.toString()}` : ""}`);
|
|
75
|
+
socket = new WebSocket(appWebSocketUrl(`${path}${params.size ? `?${params.toString()}` : ""}`));
|
|
76
76
|
setStatus("connecting");
|
|
77
77
|
socket.onopen = () => {
|
|
78
78
|
retryRef.current = 0;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
var __pablozaidenWebAppRoots: WeakMap<Element, Root> | undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function roots(): WeakMap<Element, Root> {
|
|
9
|
+
globalThis.__pablozaidenWebAppRoots ??= new WeakMap<Element, Root>();
|
|
10
|
+
return globalThis.__pablozaidenWebAppRoots;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function renderWebApp(element: ReactNode, container: Element | string = "root"): Root {
|
|
14
|
+
const target = typeof container === "string" ? document.getElementById(container) : container;
|
|
15
|
+
if (!target) {
|
|
16
|
+
throw new Error(`Unable to find React root container: ${container}`);
|
|
17
|
+
}
|
|
18
|
+
const registry = roots();
|
|
19
|
+
let root = registry.get(target);
|
|
20
|
+
if (!root) {
|
|
21
|
+
root = createRoot(target);
|
|
22
|
+
registry.set(target, root);
|
|
23
|
+
}
|
|
24
|
+
root.render(element);
|
|
25
|
+
return root;
|
|
26
|
+
}
|
package/src/web/styles.css
CHANGED
|
@@ -444,6 +444,9 @@ textarea::placeholder {
|
|
|
444
444
|
}
|
|
445
445
|
|
|
446
446
|
.wapp-panel {
|
|
447
|
+
min-width: 0;
|
|
448
|
+
max-width: 100%;
|
|
449
|
+
box-sizing: border-box;
|
|
447
450
|
border: 1px solid var(--wapp-border-soft);
|
|
448
451
|
border-radius: 1.5rem;
|
|
449
452
|
background: var(--wapp-surface);
|
|
@@ -550,6 +553,10 @@ textarea::placeholder {
|
|
|
550
553
|
margin-bottom: 1rem;
|
|
551
554
|
}
|
|
552
555
|
|
|
556
|
+
.wapp-entity-header > div:first-child {
|
|
557
|
+
min-width: 0;
|
|
558
|
+
}
|
|
559
|
+
|
|
553
560
|
.wapp-entity-header h2 {
|
|
554
561
|
margin: 0;
|
|
555
562
|
font-size: 1.125rem;
|
|
@@ -580,12 +587,15 @@ textarea::placeholder {
|
|
|
580
587
|
}
|
|
581
588
|
|
|
582
589
|
.wapp-data-list {
|
|
590
|
+
max-width: 100%;
|
|
591
|
+
box-sizing: border-box;
|
|
583
592
|
overflow: hidden;
|
|
584
593
|
border: 1px solid var(--wapp-border-soft);
|
|
585
594
|
border-radius: 0.875rem;
|
|
586
595
|
}
|
|
587
596
|
|
|
588
597
|
.wapp-data-list-row {
|
|
598
|
+
box-sizing: border-box;
|
|
589
599
|
width: 100%;
|
|
590
600
|
display: flex;
|
|
591
601
|
align-items: center;
|
|
@@ -697,6 +707,14 @@ textarea::placeholder {
|
|
|
697
707
|
background: var(--wapp-surface);
|
|
698
708
|
}
|
|
699
709
|
|
|
710
|
+
.wapp-auth-dialog {
|
|
711
|
+
width: min(28rem, calc(100vw - 2rem));
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.wapp-auth-dialog .wapp-dialog-body:empty {
|
|
715
|
+
display: none;
|
|
716
|
+
}
|
|
717
|
+
|
|
700
718
|
.wapp-button {
|
|
701
719
|
min-height: 2.375rem;
|
|
702
720
|
border: 1px solid var(--wapp-border);
|
|
@@ -1014,6 +1032,17 @@ textarea::placeholder {
|
|
|
1014
1032
|
font-size: 0.8125rem;
|
|
1015
1033
|
}
|
|
1016
1034
|
|
|
1035
|
+
.wapp-inline-select {
|
|
1036
|
+
min-height: 2.375rem;
|
|
1037
|
+
border: 1px solid var(--wapp-border);
|
|
1038
|
+
border-radius: 0.375rem;
|
|
1039
|
+
background: var(--wapp-surface);
|
|
1040
|
+
color: var(--wapp-text);
|
|
1041
|
+
box-shadow: var(--wapp-shadow);
|
|
1042
|
+
padding: 0.5rem 2rem 0.5rem 0.75rem;
|
|
1043
|
+
font-size: 0.8125rem;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1017
1046
|
.wapp-field textarea {
|
|
1018
1047
|
min-height: 8rem;
|
|
1019
1048
|
resize: vertical;
|
|
@@ -1035,6 +1064,45 @@ textarea::placeholder {
|
|
|
1035
1064
|
color: var(--wapp-muted);
|
|
1036
1065
|
}
|
|
1037
1066
|
|
|
1067
|
+
.wapp-shutdown-countdown {
|
|
1068
|
+
display: grid;
|
|
1069
|
+
gap: 0.5rem;
|
|
1070
|
+
margin-top: 0.75rem;
|
|
1071
|
+
font-weight: 600;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
.wapp-shutdown-message {
|
|
1075
|
+
color: #dc2626;
|
|
1076
|
+
font-size: 0.875rem;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
:root.dark .wapp-shutdown-message {
|
|
1080
|
+
color: #f87171;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.wapp-shutdown-progress {
|
|
1084
|
+
width: 100%;
|
|
1085
|
+
height: 0.375rem;
|
|
1086
|
+
overflow: hidden;
|
|
1087
|
+
border-radius: 999px;
|
|
1088
|
+
background: #fecaca;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
:root.dark .wapp-shutdown-progress {
|
|
1092
|
+
background: #7f1d1d;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.wapp-shutdown-progress-bar {
|
|
1096
|
+
height: 100%;
|
|
1097
|
+
border-radius: inherit;
|
|
1098
|
+
background: #ef4444;
|
|
1099
|
+
transition: width 1s linear;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
:root.dark .wapp-shutdown-progress-bar {
|
|
1103
|
+
background: #f87171;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1038
1106
|
.wapp-segmented {
|
|
1039
1107
|
display: flex;
|
|
1040
1108
|
width: 100%;
|
|
@@ -1088,6 +1156,12 @@ textarea::placeholder {
|
|
|
1088
1156
|
padding: 0.75rem;
|
|
1089
1157
|
}
|
|
1090
1158
|
|
|
1159
|
+
.wapp-grid-2 {
|
|
1160
|
+
display: grid;
|
|
1161
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1162
|
+
gap: 0.75rem;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1091
1165
|
.wapp-list-row + .wapp-list-row {
|
|
1092
1166
|
border-top: 1px solid var(--wapp-border-soft);
|
|
1093
1167
|
}
|
|
@@ -1180,11 +1254,11 @@ textarea::placeholder {
|
|
|
1180
1254
|
|
|
1181
1255
|
.wapp-dialog-title {
|
|
1182
1256
|
display: flex;
|
|
1183
|
-
align-items:
|
|
1257
|
+
align-items: flex-start;
|
|
1184
1258
|
justify-content: space-between;
|
|
1185
1259
|
gap: 1rem;
|
|
1186
1260
|
border-bottom: 1px solid var(--wapp-border);
|
|
1187
|
-
padding: 1rem 1.5rem;
|
|
1261
|
+
padding: 1rem 1.5rem 0.75rem;
|
|
1188
1262
|
}
|
|
1189
1263
|
|
|
1190
1264
|
.wapp-dialog h2 {
|
|
@@ -1192,6 +1266,12 @@ textarea::placeholder {
|
|
|
1192
1266
|
font-size: 1.125rem;
|
|
1193
1267
|
}
|
|
1194
1268
|
|
|
1269
|
+
.wapp-dialog-title p {
|
|
1270
|
+
margin: 0.25rem 0 0;
|
|
1271
|
+
color: var(--wapp-muted);
|
|
1272
|
+
line-height: 1.35;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1195
1275
|
.wapp-dialog-close {
|
|
1196
1276
|
display: inline-flex;
|
|
1197
1277
|
align-items: center;
|
|
@@ -1274,6 +1354,28 @@ textarea::placeholder {
|
|
|
1274
1354
|
|
|
1275
1355
|
.wapp-main-content {
|
|
1276
1356
|
padding: 1rem;
|
|
1357
|
+
overflow-x: hidden;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
.wapp-panel-header,
|
|
1361
|
+
.wapp-entity-header {
|
|
1362
|
+
flex-wrap: wrap;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
.wapp-panel-actions,
|
|
1366
|
+
.wapp-entity-header-actions {
|
|
1367
|
+
width: 100%;
|
|
1368
|
+
justify-content: flex-start;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
.wapp-data-list-row {
|
|
1372
|
+
gap: 0.5rem;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
.wapp-data-list-row-meta,
|
|
1376
|
+
.wapp-data-list-row-badge,
|
|
1377
|
+
.wapp-data-list-row-actions {
|
|
1378
|
+
min-width: 0;
|
|
1277
1379
|
}
|
|
1278
1380
|
|
|
1279
1381
|
.wapp-mobile-only {
|