@pablozaiden/webapp 1.1.0 → 1.1.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/docs/getting-started.md +10 -0
- package/docs/server.md +14 -0
- package/docs/settings.md +8 -0
- package/package.json +1 -1
- package/src/contracts/index.ts +11 -0
- package/src/server/create-web-app-server.ts +3 -1
- package/src/server/framework-endpoints.ts +34 -1
- package/src/server/logger.ts +68 -1
- package/src/server/request-schemas.ts +4 -0
- package/src/web/components/index.tsx +12 -0
- package/src/web/settings/account-section.tsx +25 -1
- package/src/web/styles.css +26 -0
- package/src/web/webapp-config.tsx +1 -0
package/docs/getting-started.md
CHANGED
|
@@ -190,6 +190,16 @@ reload; `fromEnv` indicates that the server environment locks the value.
|
|
|
190
190
|
`LoggerAdapter` remains application-owned and can adapt the state to any
|
|
191
191
|
logging library.
|
|
192
192
|
|
|
193
|
+
### In-memory server logs
|
|
194
|
+
|
|
195
|
+
Admins can enable temporary server-log capture from Developer Settings next to
|
|
196
|
+
the log-level selector. The framework exposes the current state in
|
|
197
|
+
`/api/config`; it starts disabled on every process start and is never stored in
|
|
198
|
+
the database. Admin clients can retrieve a bounded chronological snapshot from
|
|
199
|
+
`GET /api/server/logs` after enabling capture. The buffer is limited to the
|
|
200
|
+
newest 1,000 framework logger entries or 512 KiB of rendered log lines, and
|
|
201
|
+
turning the setting off clears it.
|
|
202
|
+
|
|
193
203
|
### Transient notifications
|
|
194
204
|
|
|
195
205
|
The standard `renderWebApp` runtime provides the framework notification service
|
package/docs/server.md
CHANGED
|
@@ -153,6 +153,8 @@ Built-in endpoints include:
|
|
|
153
153
|
| `/device` | Browser device-code approval screen |
|
|
154
154
|
| `/.well-known/jwks.json`, `/.well-known/openid-configuration` | Token verification metadata |
|
|
155
155
|
| `/api/preferences/theme`, `/api/preferences/log-level` | Settings persistence |
|
|
156
|
+
| `GET /api/server/logs` | Admin-only snapshot of the in-memory server logs |
|
|
157
|
+
| `PUT /api/server/logs/settings` | Admin-only runtime toggle for in-memory server logs |
|
|
156
158
|
| `/api/server/kill` | Authenticated server shutdown |
|
|
157
159
|
| `/api/ws` | Realtime websocket by default |
|
|
158
160
|
|
|
@@ -162,6 +164,18 @@ The effective log level in `GET /api/config` and `GET
|
|
|
162
164
|
to `true`. PUT remains an authenticated, same-origin admin mutation and
|
|
163
165
|
returns a conflict when the environment controls the value.
|
|
164
166
|
|
|
167
|
+
`GET /api/server/logs` is restricted to admin users and returns
|
|
168
|
+
`{ enabled: boolean, logs: ServerLogEntry[] }`. Entries are returned in
|
|
169
|
+
chronological order and contain the timestamp, level, logger scope, message,
|
|
170
|
+
and rendered log line. When capture is disabled, the endpoint returns `200`
|
|
171
|
+
with `enabled: false` and an empty list. `PUT /api/server/logs/settings`
|
|
172
|
+
accepts `{ "enabled": boolean }`, is restricted to admins, and uses the normal
|
|
173
|
+
same-origin policy for browser mutations. API-key and bearer callers continue
|
|
174
|
+
to use the existing token-auth behavior. The in-memory buffer is limited to
|
|
175
|
+
the newest 1,000 entries and 512 KiB of rendered UTF-8 lines; disabling capture
|
|
176
|
+
clears it. These endpoints only expose messages emitted through the framework
|
|
177
|
+
logger and never make the logs durable.
|
|
178
|
+
|
|
165
179
|
Server-side applications that need credentials for internal runtimes should use
|
|
166
180
|
the server-only `createManagedApiKey`, `listManagedApiKeys`, and
|
|
167
181
|
`revokeManagedApiKey` helpers. Managed keys are persisted in the same API-key
|
package/docs/settings.md
CHANGED
|
@@ -8,6 +8,8 @@ Settings is framework-owned so apps stay consistent. It includes:
|
|
|
8
8
|
- Theme preference: system/light/dark, stored per user
|
|
9
9
|
- Admin user management
|
|
10
10
|
- Admin log-level preference unless `{PREFIX}_LOG_LEVEL` is set
|
|
11
|
+
- Admin in-memory server-log capture toggle; disabled and cleared on process
|
|
12
|
+
start or when turned off
|
|
11
13
|
- Admin server kill
|
|
12
14
|
- Version/about
|
|
13
15
|
|
|
@@ -33,6 +35,12 @@ the shared hook state after a successful save, and `fromEnv` is true when
|
|
|
33
35
|
`{PREFIX}_LOG_LEVEL` locks the value. Applications should not add a separate
|
|
34
36
|
configuration fetch or initializer component.
|
|
35
37
|
|
|
38
|
+
Developer Settings also includes **Store server logs in memory**. It is an
|
|
39
|
+
admin-only, process-local setting and is disabled when the server starts. When
|
|
40
|
+
enabled, the framework logger retains only the newest 1,000 eligible entries
|
|
41
|
+
within a 512 KiB rendered-line limit; disabling it immediately clears the
|
|
42
|
+
buffer. The setting and captured logs are never stored in SQLite.
|
|
43
|
+
|
|
36
44
|
Security lists only show useful active user credentials. Managed API keys created by
|
|
37
45
|
server-side applications, and their metadata, are not returned by the browser API-key
|
|
38
46
|
list or rendered in Settings. Expired API keys are purged before listing, and revoked
|
package/package.json
CHANGED
package/src/contracts/index.ts
CHANGED
|
@@ -114,6 +114,14 @@ export interface AuthSessionSummary {
|
|
|
114
114
|
active: boolean;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
export interface ServerLogEntry {
|
|
118
|
+
timestamp: string;
|
|
119
|
+
level: LogLevelName;
|
|
120
|
+
scope: string;
|
|
121
|
+
message: string;
|
|
122
|
+
line: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
117
125
|
export interface WebAppConfigResponse {
|
|
118
126
|
appName: string;
|
|
119
127
|
version: string;
|
|
@@ -127,6 +135,9 @@ export interface WebAppConfigResponse {
|
|
|
127
135
|
level: LogLevelName;
|
|
128
136
|
fromEnv: boolean;
|
|
129
137
|
};
|
|
138
|
+
inMemoryLogs: {
|
|
139
|
+
enabled: boolean;
|
|
140
|
+
};
|
|
130
141
|
deviceAuth: {
|
|
131
142
|
enabled: boolean;
|
|
132
143
|
};
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { createAuthentication } from "./authentication";
|
|
12
12
|
import { createFrameworkEndpointHandler } from "./framework-endpoints";
|
|
13
13
|
import { createServerLifecycle } from "./server-lifecycle";
|
|
14
|
-
import { setLogLevel } from "./logger";
|
|
14
|
+
import { inMemoryLogStorage, resetInMemoryLogStorage, setLogLevel } from "./logger";
|
|
15
15
|
import { createWebDocumentProvider, htmlResponse } from "./web-document";
|
|
16
16
|
import { createPublicRouteDispatcher } from "./public-route-dispatch";
|
|
17
17
|
import { createRouteDispatcher } from "./route-dispatch";
|
|
@@ -45,6 +45,7 @@ export function createWebAppServer<TEvent = unknown>(input: WebAppServerConfig<T
|
|
|
45
45
|
if (config.appName !== input.appName || config.envPrefix !== input.envPrefix) {
|
|
46
46
|
throw new Error("runtimeConfig appName and envPrefix must match the createWebAppServer inputs");
|
|
47
47
|
}
|
|
48
|
+
resetInMemoryLogStorage();
|
|
48
49
|
const store = input.store ?? sqliteWebAppStore({ dataDir: config.dataDir });
|
|
49
50
|
store.initialize();
|
|
50
51
|
const savedLogLevel = store.getLogLevelPreference();
|
|
@@ -80,6 +81,7 @@ export function createWebAppServer<TEvent = unknown>(input: WebAppServerConfig<T
|
|
|
80
81
|
deviceAuthEnabled,
|
|
81
82
|
configResponse: input.configResponse,
|
|
82
83
|
onLogLevelChange: input.logLevel?.onChange,
|
|
84
|
+
inMemoryLogs: inMemoryLogStorage,
|
|
83
85
|
ensureWebDocument,
|
|
84
86
|
});
|
|
85
87
|
const publicRouteDispatcher = createPublicRouteDispatcher({
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
createUserRequestSchema,
|
|
49
49
|
deviceAuthorizationRequestSchema,
|
|
50
50
|
deviceCodeActionRequestSchema,
|
|
51
|
+
inMemoryLogSettingsRequestSchema,
|
|
51
52
|
logLevelPreferenceRequestSchema,
|
|
52
53
|
passkeyBootstrapOptionsSchema,
|
|
53
54
|
refreshTokenRequestSchema,
|
|
@@ -61,7 +62,8 @@ import {
|
|
|
61
62
|
} from "./request-schemas";
|
|
62
63
|
import { resolveEffectiveLogLevel, type RuntimeConfig } from "./runtime-config";
|
|
63
64
|
import { setLogLevel } from "./logger";
|
|
64
|
-
import {
|
|
65
|
+
import type { InMemoryLogStorage } from "./logger";
|
|
66
|
+
import { errorResponse, jsonResponse, methodNotAllowed, notFound, parseJson, successResponse } from "./responses";
|
|
65
67
|
import type { WebSocketData } from "./realtime/bus";
|
|
66
68
|
|
|
67
69
|
export interface FrameworkEndpointDependencies {
|
|
@@ -75,6 +77,7 @@ export interface FrameworkEndpointDependencies {
|
|
|
75
77
|
deviceAuthEnabled: boolean;
|
|
76
78
|
configResponse?: WebAppServerConfig["configResponse"];
|
|
77
79
|
onLogLevelChange?: (level: LogLevelName) => void;
|
|
80
|
+
inMemoryLogs: InMemoryLogStorage;
|
|
78
81
|
ensureWebDocument: () => Promise<WebDocument>;
|
|
79
82
|
}
|
|
80
83
|
|
|
@@ -97,6 +100,7 @@ export function createFrameworkEndpointHandler(dependencies: FrameworkEndpointDe
|
|
|
97
100
|
deviceAuthEnabled,
|
|
98
101
|
configResponse: extendConfigResponse,
|
|
99
102
|
onLogLevelChange,
|
|
103
|
+
inMemoryLogs,
|
|
100
104
|
ensureWebDocument,
|
|
101
105
|
} = dependencies;
|
|
102
106
|
|
|
@@ -115,6 +119,9 @@ export function createFrameworkEndpointHandler(dependencies: FrameworkEndpointDe
|
|
|
115
119
|
level: resolveEffectiveLogLevel(config, store.getLogLevelPreference()),
|
|
116
120
|
fromEnv: config.logLevelFromEnv,
|
|
117
121
|
},
|
|
122
|
+
inMemoryLogs: {
|
|
123
|
+
enabled: inMemoryLogs.isEnabled(),
|
|
124
|
+
},
|
|
118
125
|
apiKeys: { enabled: Boolean(apiKeysEnabled) },
|
|
119
126
|
deviceAuth: { enabled: Boolean(deviceAuthEnabled) },
|
|
120
127
|
} satisfies WebAppConfigResponse;
|
|
@@ -433,6 +440,32 @@ export function createFrameworkEndpointHandler(dependencies: FrameworkEndpointDe
|
|
|
433
440
|
return successResponse({ level: body.level });
|
|
434
441
|
}
|
|
435
442
|
}
|
|
443
|
+
if (path === "/api/server/logs") {
|
|
444
|
+
const auth = await authentication.authorize(req, true);
|
|
445
|
+
if (auth instanceof Response) return auth;
|
|
446
|
+
ensureAdmin(auth);
|
|
447
|
+
if (req.method !== "GET") {
|
|
448
|
+
return methodNotAllowed();
|
|
449
|
+
}
|
|
450
|
+
const enabled = inMemoryLogs.isEnabled();
|
|
451
|
+
return jsonResponse({
|
|
452
|
+
enabled,
|
|
453
|
+
logs: enabled ? inMemoryLogs.getEntries() : [],
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (path === "/api/server/logs/settings") {
|
|
457
|
+
const auth = await authentication.authorize(req, true);
|
|
458
|
+
if (auth instanceof Response) return auth;
|
|
459
|
+
ensureAdmin(auth);
|
|
460
|
+
if (req.method !== "PUT") {
|
|
461
|
+
return methodNotAllowed();
|
|
462
|
+
}
|
|
463
|
+
const originFailure = checkSameOrigin(req, config, auth, "mutations");
|
|
464
|
+
if (originFailure) return originFailure;
|
|
465
|
+
const body = await parseJson(req, inMemoryLogSettingsRequestSchema);
|
|
466
|
+
inMemoryLogs.setEnabled(body.enabled);
|
|
467
|
+
return successResponse({ enabled: inMemoryLogs.isEnabled() });
|
|
468
|
+
}
|
|
436
469
|
if (path === "/api/server/kill" && req.method === "POST") {
|
|
437
470
|
const auth = await authentication.authorize(req, true);
|
|
438
471
|
if (auth instanceof Response) return auth;
|
package/src/server/logger.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LogLevelName } from "../contracts";
|
|
1
|
+
import type { LogLevelName, ServerLogEntry } from "../contracts";
|
|
2
2
|
|
|
3
3
|
const ORDER: Record<LogLevelName, number> = {
|
|
4
4
|
trace: 10,
|
|
@@ -8,7 +8,21 @@ const ORDER: Record<LogLevelName, number> = {
|
|
|
8
8
|
error: 50,
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
export const MAX_IN_MEMORY_LOG_ENTRIES = 1_000;
|
|
12
|
+
export const MAX_IN_MEMORY_LOG_BYTES = 512 * 1024;
|
|
13
|
+
|
|
11
14
|
let currentLevel: LogLevelName = "info";
|
|
15
|
+
let inMemoryLogStorageEnabled = false;
|
|
16
|
+
let inMemoryLogEntries: ServerLogEntry[] = [];
|
|
17
|
+
let inMemoryLogBytes = 0;
|
|
18
|
+
const textEncoder = new TextEncoder();
|
|
19
|
+
|
|
20
|
+
export interface InMemoryLogStorage {
|
|
21
|
+
isEnabled(): boolean;
|
|
22
|
+
setEnabled(enabled: boolean): void;
|
|
23
|
+
getEntries(): ServerLogEntry[];
|
|
24
|
+
reset(): void;
|
|
25
|
+
}
|
|
12
26
|
|
|
13
27
|
export function setLogLevel(level: LogLevelName): void {
|
|
14
28
|
currentLevel = level;
|
|
@@ -18,6 +32,58 @@ export function getLogLevel(): LogLevelName {
|
|
|
18
32
|
return currentLevel;
|
|
19
33
|
}
|
|
20
34
|
|
|
35
|
+
function lineByteLength(line: string): number {
|
|
36
|
+
return textEncoder.encode(line).byteLength;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function appendInMemoryLogEntry(entry: ServerLogEntry): void {
|
|
40
|
+
if (!inMemoryLogStorageEnabled) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const entryBytes = lineByteLength(entry.line);
|
|
44
|
+
if (entryBytes > MAX_IN_MEMORY_LOG_BYTES) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
inMemoryLogEntries.push(entry);
|
|
48
|
+
inMemoryLogBytes += entryBytes;
|
|
49
|
+
while (inMemoryLogEntries.length > MAX_IN_MEMORY_LOG_ENTRIES || inMemoryLogBytes > MAX_IN_MEMORY_LOG_BYTES) {
|
|
50
|
+
const removed = inMemoryLogEntries.shift();
|
|
51
|
+
if (!removed) {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
inMemoryLogBytes -= lineByteLength(removed.line);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function setInMemoryLogStorageEnabled(enabled: boolean): void {
|
|
59
|
+
inMemoryLogStorageEnabled = enabled;
|
|
60
|
+
if (!enabled) {
|
|
61
|
+
inMemoryLogEntries = [];
|
|
62
|
+
inMemoryLogBytes = 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isInMemoryLogStorageEnabled(): boolean {
|
|
67
|
+
return inMemoryLogStorageEnabled;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getInMemoryLogEntries(): ServerLogEntry[] {
|
|
71
|
+
return inMemoryLogEntries.map((entry) => ({ ...entry }));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function resetInMemoryLogStorage(): void {
|
|
75
|
+
inMemoryLogStorageEnabled = false;
|
|
76
|
+
inMemoryLogEntries = [];
|
|
77
|
+
inMemoryLogBytes = 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const inMemoryLogStorage: InMemoryLogStorage = {
|
|
81
|
+
isEnabled: isInMemoryLogStorageEnabled,
|
|
82
|
+
setEnabled: setInMemoryLogStorageEnabled,
|
|
83
|
+
getEntries: getInMemoryLogEntries,
|
|
84
|
+
reset: resetInMemoryLogStorage,
|
|
85
|
+
};
|
|
86
|
+
|
|
21
87
|
export function createLogger(scope: string) {
|
|
22
88
|
function log(level: LogLevelName, message: string, fields?: Record<string, unknown>): void {
|
|
23
89
|
if (ORDER[level] < ORDER[currentLevel]) {
|
|
@@ -26,6 +92,7 @@ export function createLogger(scope: string) {
|
|
|
26
92
|
const timestamp = new Date().toISOString();
|
|
27
93
|
const suffix = fields ? ` ${JSON.stringify(fields)}` : "";
|
|
28
94
|
const line = `${timestamp}\t${level.toUpperCase()}\t${scope}\t${message}${suffix}`;
|
|
95
|
+
appendInMemoryLogEntry({ timestamp, level, scope, message, line });
|
|
29
96
|
if (level === "error") {
|
|
30
97
|
console.error(line);
|
|
31
98
|
} else if (level === "warn") {
|
|
@@ -142,3 +142,7 @@ export const themePreferenceRequestSchema = z.object({
|
|
|
142
142
|
export const logLevelPreferenceRequestSchema = z.object({
|
|
143
143
|
level: z.enum(["trace", "debug", "info", "warn", "error"]),
|
|
144
144
|
});
|
|
145
|
+
|
|
146
|
+
export const inMemoryLogSettingsRequestSchema = z.object({
|
|
147
|
+
enabled: z.boolean(),
|
|
148
|
+
});
|
|
@@ -252,6 +252,18 @@ export function SelectField({ label, children, ...props }: SelectHTMLAttributes<
|
|
|
252
252
|
);
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
export function CheckboxField({ label, hint, ...props }: Omit<InputHTMLAttributes<HTMLInputElement>, "type"> & { label: string; hint?: string }) {
|
|
256
|
+
return (
|
|
257
|
+
<label className="wapp-checkbox-field">
|
|
258
|
+
<input {...props} type="checkbox" />
|
|
259
|
+
<span>
|
|
260
|
+
{label}
|
|
261
|
+
{hint ? <small>{hint}</small> : null}
|
|
262
|
+
</span>
|
|
263
|
+
</label>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
255
267
|
export function SegmentedControl<T extends string>({ value, options, onChange, label }: { value: T; options: Array<{ value: T; label: string }>; onChange: (value: T) => void; label: string }) {
|
|
256
268
|
return (
|
|
257
269
|
<div className="wapp-segmented" role="group" aria-label={label}>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import type { LogLevelName, WebAppConfigResponse } from "../../contracts";
|
|
3
3
|
import { appJson } from "../api-client";
|
|
4
|
-
import { Badge, Button, ErrorState, FormSection, LoadingState, SelectField } from "../components";
|
|
4
|
+
import { Badge, Button, CheckboxField, ErrorState, FormSection, LoadingState, SelectField } from "../components";
|
|
5
5
|
import { useLogLevel } from "../log-level";
|
|
6
6
|
import { isThemePreference, useTheme } from "../theme";
|
|
7
7
|
|
|
@@ -21,6 +21,7 @@ export function AccountSection({ config, refresh, setError }: AccountSectionProp
|
|
|
21
21
|
const { preference, setPreference, loading, error, retry } = useTheme();
|
|
22
22
|
const logLevel = useLogLevel();
|
|
23
23
|
const [logLevelSaving, setLogLevelSaving] = useState(false);
|
|
24
|
+
const [inMemoryLogsSaving, setInMemoryLogsSaving] = useState(false);
|
|
24
25
|
|
|
25
26
|
async function updateLogLevel(value: string) {
|
|
26
27
|
if (!isLogLevelName(value)) {
|
|
@@ -39,6 +40,22 @@ export function AccountSection({ config, refresh, setError }: AccountSectionProp
|
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
async function updateInMemoryLogs(enabled: boolean) {
|
|
44
|
+
try {
|
|
45
|
+
setError(undefined);
|
|
46
|
+
setInMemoryLogsSaving(true);
|
|
47
|
+
await appJson<unknown>("/api/server/logs/settings", {
|
|
48
|
+
method: "PUT",
|
|
49
|
+
body: JSON.stringify({ enabled }),
|
|
50
|
+
});
|
|
51
|
+
await refresh();
|
|
52
|
+
} catch (err) {
|
|
53
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
54
|
+
} finally {
|
|
55
|
+
setInMemoryLogsSaving(false);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
return (
|
|
43
60
|
<>
|
|
44
61
|
{config.currentUser ? (
|
|
@@ -88,6 +105,13 @@ export function AccountSection({ config, refresh, setError }: AccountSectionProp
|
|
|
88
105
|
{LOG_LEVEL_NAMES.map((level) => <option key={level} value={level}>{level}</option>)}
|
|
89
106
|
</SelectField>
|
|
90
107
|
) : null}
|
|
108
|
+
<CheckboxField
|
|
109
|
+
label="Store server logs in memory"
|
|
110
|
+
hint="Temporary; disabled and cleared when the server process starts or this setting is turned off."
|
|
111
|
+
checked={config.inMemoryLogs.enabled}
|
|
112
|
+
disabled={inMemoryLogsSaving}
|
|
113
|
+
onChange={(event) => void updateInMemoryLogs(event.currentTarget.checked)}
|
|
114
|
+
/>
|
|
91
115
|
</FormSection>
|
|
92
116
|
) : null}
|
|
93
117
|
</>
|
package/src/web/styles.css
CHANGED
|
@@ -1618,6 +1618,32 @@ textarea::placeholder {
|
|
|
1618
1618
|
resize: vertical;
|
|
1619
1619
|
}
|
|
1620
1620
|
|
|
1621
|
+
.wapp-checkbox-field {
|
|
1622
|
+
display: flex;
|
|
1623
|
+
align-items: flex-start;
|
|
1624
|
+
gap: 0.5rem;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
.wapp-checkbox-field input {
|
|
1628
|
+
width: 1rem;
|
|
1629
|
+
min-height: 1rem;
|
|
1630
|
+
margin-top: 0.125rem;
|
|
1631
|
+
flex: 0 0 auto;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
.wapp-checkbox-field span {
|
|
1635
|
+
font-size: 0.8125rem;
|
|
1636
|
+
font-weight: 500;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
.wapp-checkbox-field small {
|
|
1640
|
+
display: block;
|
|
1641
|
+
color: var(--wapp-muted);
|
|
1642
|
+
font-size: 0.75rem;
|
|
1643
|
+
font-weight: 400;
|
|
1644
|
+
margin-top: 0.25rem;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1621
1647
|
.wapp-field small {
|
|
1622
1648
|
display: block;
|
|
1623
1649
|
color: var(--wapp-muted);
|
|
@@ -50,6 +50,7 @@ function isWebAppConfigResponse(value: unknown): value is WebAppConfigResponse {
|
|
|
50
50
|
&& isRecord(value["logLevel"])
|
|
51
51
|
&& isLogLevelName(value["logLevel"]["level"])
|
|
52
52
|
&& typeof value["logLevel"]["fromEnv"] === "boolean"
|
|
53
|
+
&& hasBooleanFields(value["inMemoryLogs"], ["enabled"])
|
|
53
54
|
&& hasBooleanFields(value["deviceAuth"], ["enabled"])
|
|
54
55
|
&& hasBooleanFields(value["apiKeys"], ["enabled"]);
|
|
55
56
|
}
|