@syncular/console 0.0.4-25 → 0.0.4-26
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/App.d.ts.map +1 -1
- package/dist/App.js +2 -1
- package/dist/App.js.map +1 -1
- package/dist/browser-main.js +1 -1
- package/dist/browser-main.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +2 -1
- package/dist/layout.js.map +1 -1
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +4 -1
- package/dist/mount.js.map +1 -1
- package/dist/pages/Ops.js +2 -2
- package/dist/pages/Ops.js.map +1 -1
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +3 -0
- package/dist/server.js.map +1 -0
- package/dist/static-server.d.ts +18 -0
- package/dist/static-server.d.ts.map +1 -0
- package/dist/static-server.js +137 -0
- package/dist/static-server.js.map +1 -0
- package/dist/styles.css +1 -0
- package/dist/theme-scope.d.ts +3 -0
- package/dist/theme-scope.d.ts.map +1 -0
- package/dist/theme-scope.js +5 -0
- package/dist/theme-scope.js.map +1 -0
- package/package.json +18 -7
- package/src/App.tsx +6 -1
- package/src/browser-main.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/layout.tsx +4 -1
- package/src/mount.tsx +5 -1
- package/src/pages/Ops.tsx +4 -2
- package/src/server.ts +2 -0
- package/src/static-server.ts +219 -0
- package/src/styles/globals.css +5 -1
- package/src/theme-scope.ts +5 -0
- package/web-dist/assets/index-CTkQp6YC.js +86 -0
- package/web-dist/assets/index-j_U2SoXa.css +1 -0
- package/web-dist/console.css +1 -0
- package/web-dist/index.html +4 -4
- package/web-dist/chunk-7ayekhzx.css +0 -1
- package/web-dist/chunk-myppbvt5.js +0 -90
package/src/mount.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot, type Root } from 'react-dom/client';
|
|
3
3
|
import { App, type SyncularConsoleProps } from './App';
|
|
4
|
+
import { applyConsoleThemeScope } from './theme-scope';
|
|
4
5
|
|
|
5
6
|
interface MountSyncularConsoleOptions {
|
|
6
7
|
strictMode?: boolean;
|
|
@@ -27,7 +28,10 @@ export function mountSyncularConsoleApp(
|
|
|
27
28
|
containerOrSelector: Element | string,
|
|
28
29
|
options: MountSyncularConsoleOptions = {}
|
|
29
30
|
): Root {
|
|
30
|
-
const
|
|
31
|
+
const container = resolveContainer(containerOrSelector);
|
|
32
|
+
applyConsoleThemeScope(container);
|
|
33
|
+
|
|
34
|
+
const root = createRoot(container);
|
|
31
35
|
const app = (
|
|
32
36
|
<App basePath={options.basePath} defaultConfig={options.defaultConfig} />
|
|
33
37
|
);
|
package/src/pages/Ops.tsx
CHANGED
|
@@ -684,8 +684,10 @@ function OperationsAuditView({ partitionId }: { partitionId?: string }) {
|
|
|
684
684
|
</TableRow>
|
|
685
685
|
</TableHeader>
|
|
686
686
|
<TableBody>
|
|
687
|
-
{(data?.items ?? []).map((event) => (
|
|
688
|
-
<TableRow
|
|
687
|
+
{(data?.items ?? []).map((event, index) => (
|
|
688
|
+
<TableRow
|
|
689
|
+
key={`${event.operationId}:${event.createdAt}:${index}`}
|
|
690
|
+
>
|
|
689
691
|
<TableCell className="whitespace-nowrap text-xs text-neutral-400">
|
|
690
692
|
{formatDateTime(event.createdAt)}
|
|
691
693
|
</TableCell>
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import {
|
|
6
|
+
CONSOLE_BASEPATH_META,
|
|
7
|
+
CONSOLE_SERVER_URL_META,
|
|
8
|
+
CONSOLE_TOKEN_META,
|
|
9
|
+
normalizeBasePath,
|
|
10
|
+
} from './runtime-config';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_MOUNT_PATH = '/console';
|
|
13
|
+
const DEFAULT_INDEX_CACHE_CONTROL = 'no-store';
|
|
14
|
+
const DEFAULT_ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable';
|
|
15
|
+
|
|
16
|
+
const CONTENT_TYPES: Record<string, string> = {
|
|
17
|
+
'.css': 'text/css; charset=utf-8',
|
|
18
|
+
'.html': 'text/html; charset=utf-8',
|
|
19
|
+
'.ico': 'image/x-icon',
|
|
20
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
21
|
+
'.json': 'application/json; charset=utf-8',
|
|
22
|
+
'.manifest': 'application/manifest+json; charset=utf-8',
|
|
23
|
+
'.png': 'image/png',
|
|
24
|
+
'.svg': 'image/svg+xml',
|
|
25
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
26
|
+
'.webmanifest': 'application/manifest+json; charset=utf-8',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export interface ConsoleUiPrefill {
|
|
30
|
+
basePath?: string;
|
|
31
|
+
serverUrl?: string;
|
|
32
|
+
token?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CreateConsoleStaticResponderOptions {
|
|
36
|
+
mountPath?: string;
|
|
37
|
+
staticDir?: string;
|
|
38
|
+
defaultPrefill?: ConsoleUiPrefill;
|
|
39
|
+
indexCacheControl?: string;
|
|
40
|
+
assetCacheControl?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ServeConsoleStaticRequestOptions {
|
|
44
|
+
prefill?: ConsoleUiPrefill;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type ConsoleStaticResponder = (
|
|
48
|
+
request: Request,
|
|
49
|
+
options?: ServeConsoleStaticRequestOptions
|
|
50
|
+
) => Promise<Response | null>;
|
|
51
|
+
|
|
52
|
+
function normalizeMountPath(mountPath: string | undefined): string {
|
|
53
|
+
const value = mountPath?.trim() ?? '';
|
|
54
|
+
if (!value || value === '/') return '/';
|
|
55
|
+
const withLeadingSlash = value.startsWith('/') ? value : `/${value}`;
|
|
56
|
+
return withLeadingSlash.replace(/\/+$/g, '') || '/';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeRequestPath(pathname: string): string {
|
|
60
|
+
const decodedPathname = decodeURIComponent(pathname);
|
|
61
|
+
return decodedPathname === '' ? '/' : decodedPathname;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function matchesMountPath(pathname: string, mountPath: string): boolean {
|
|
65
|
+
if (mountPath === '/') return pathname.startsWith('/');
|
|
66
|
+
return pathname === mountPath || pathname.startsWith(`${mountPath}/`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function relativePathForMount(pathname: string, mountPath: string): string {
|
|
70
|
+
if (mountPath === '/') return pathname;
|
|
71
|
+
const withoutPrefix = pathname.slice(mountPath.length);
|
|
72
|
+
return withoutPrefix === '' ? '/' : withoutPrefix;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function escapeHtmlAttribute(value: string): string {
|
|
76
|
+
return value
|
|
77
|
+
.replaceAll('&', '&')
|
|
78
|
+
.replaceAll('"', '"')
|
|
79
|
+
.replaceAll('<', '<')
|
|
80
|
+
.replaceAll('>', '>');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function withMetaTag(html: string, name: string, value: string): string {
|
|
84
|
+
const escapedValue = escapeHtmlAttribute(value);
|
|
85
|
+
const pattern = new RegExp(`<meta name="${name}" content="[^"]*"\\s*/?>`);
|
|
86
|
+
if (pattern.test(html)) {
|
|
87
|
+
return html.replace(
|
|
88
|
+
pattern,
|
|
89
|
+
`<meta name="${name}" content="${escapedValue}" />`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return html.replace(
|
|
94
|
+
'</head>',
|
|
95
|
+
` <meta name="${name}" content="${escapedValue}" />\n </head>`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isWithinDirectory(baseDir: string, targetPath: string): boolean {
|
|
100
|
+
return (
|
|
101
|
+
targetPath === baseDir || targetPath.startsWith(`${baseDir}${path.sep}`)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function contentTypeFor(pathname: string): string {
|
|
106
|
+
const extension = path.extname(pathname).toLowerCase();
|
|
107
|
+
return CONTENT_TYPES[extension] ?? 'application/octet-stream';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function resolveConsoleStaticDir(): string {
|
|
111
|
+
const staticDirFromModule = fileURLToPath(
|
|
112
|
+
new URL('../web-dist', import.meta.url)
|
|
113
|
+
);
|
|
114
|
+
return path.resolve(staticDirFromModule);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function renderIndexHtml(args: {
|
|
118
|
+
template: string;
|
|
119
|
+
mountPath: string;
|
|
120
|
+
prefill?: ConsoleUiPrefill;
|
|
121
|
+
}): string {
|
|
122
|
+
const resolvedBasePath = normalizeBasePath(
|
|
123
|
+
args.prefill?.basePath ?? args.mountPath
|
|
124
|
+
);
|
|
125
|
+
const resolvedServerUrl = args.prefill?.serverUrl ?? '';
|
|
126
|
+
const resolvedToken = args.prefill?.token ?? '';
|
|
127
|
+
|
|
128
|
+
return withMetaTag(
|
|
129
|
+
withMetaTag(
|
|
130
|
+
withMetaTag(args.template, CONSOLE_BASEPATH_META, resolvedBasePath),
|
|
131
|
+
CONSOLE_SERVER_URL_META,
|
|
132
|
+
resolvedServerUrl
|
|
133
|
+
),
|
|
134
|
+
CONSOLE_TOKEN_META,
|
|
135
|
+
resolvedToken
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function createConsoleStaticResponder(
|
|
140
|
+
options: CreateConsoleStaticResponderOptions = {}
|
|
141
|
+
): ConsoleStaticResponder {
|
|
142
|
+
const mountPath = normalizeMountPath(options.mountPath ?? DEFAULT_MOUNT_PATH);
|
|
143
|
+
const staticDir = path.resolve(
|
|
144
|
+
options.staticDir ?? resolveConsoleStaticDir()
|
|
145
|
+
);
|
|
146
|
+
const indexPath = path.join(staticDir, 'index.html');
|
|
147
|
+
|
|
148
|
+
if (!existsSync(indexPath)) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Console distributable missing: ${indexPath}. Build @syncular/console before serving static assets.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const indexTemplate = readFileSync(indexPath, 'utf8');
|
|
155
|
+
const indexCacheControl =
|
|
156
|
+
options.indexCacheControl ?? DEFAULT_INDEX_CACHE_CONTROL;
|
|
157
|
+
const assetCacheControl =
|
|
158
|
+
options.assetCacheControl ?? DEFAULT_ASSET_CACHE_CONTROL;
|
|
159
|
+
const defaultPrefill = options.defaultPrefill;
|
|
160
|
+
|
|
161
|
+
return async (request, requestOptions = {}) => {
|
|
162
|
+
const method = request.method.toUpperCase();
|
|
163
|
+
if (method !== 'GET' && method !== 'HEAD') {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const url = new URL(request.url);
|
|
168
|
+
const pathname = normalizeRequestPath(url.pathname);
|
|
169
|
+
if (!matchesMountPath(pathname, mountPath)) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const relativePath = relativePathForMount(pathname, mountPath);
|
|
174
|
+
const effectivePrefill = {
|
|
175
|
+
...defaultPrefill,
|
|
176
|
+
...requestOptions.prefill,
|
|
177
|
+
};
|
|
178
|
+
const sendIndex = () => {
|
|
179
|
+
const html = renderIndexHtml({
|
|
180
|
+
template: indexTemplate,
|
|
181
|
+
mountPath,
|
|
182
|
+
prefill: effectivePrefill,
|
|
183
|
+
});
|
|
184
|
+
return new Response(method === 'HEAD' ? undefined : html, {
|
|
185
|
+
status: 200,
|
|
186
|
+
headers: {
|
|
187
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
188
|
+
'Cache-Control': indexCacheControl,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
if (
|
|
194
|
+
relativePath === '/' ||
|
|
195
|
+
relativePath === '/index.html' ||
|
|
196
|
+
path.extname(relativePath).length === 0
|
|
197
|
+
) {
|
|
198
|
+
return sendIndex();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const candidatePath = path.resolve(staticDir, `.${relativePath}`);
|
|
202
|
+
if (!isWithinDirectory(staticDir, candidatePath)) {
|
|
203
|
+
return new Response('Forbidden', { status: 403 });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!existsSync(candidatePath) || !statSync(candidatePath).isFile()) {
|
|
207
|
+
return new Response('Not Found', { status: 404 });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const body = method === 'HEAD' ? undefined : await readFile(candidatePath);
|
|
211
|
+
return new Response(body, {
|
|
212
|
+
status: 200,
|
|
213
|
+
headers: {
|
|
214
|
+
'Content-Type': contentTypeFor(candidatePath),
|
|
215
|
+
'Cache-Control': assetCacheControl,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
}
|
package/src/styles/globals.css
CHANGED