create-interview-cockpit 0.13.0 → 0.15.0
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 +1 -1
- package/template/client/src/api.ts +39 -0
- package/template/client/src/browserSecurityTemplates.ts +3242 -0
- package/template/client/src/components/BrowserSecurityLabModal.tsx +1510 -0
- package/template/client/src/components/CodeRunnerModal.tsx +406 -55
- package/template/client/src/components/LabsPanel.tsx +123 -12
- package/template/client/src/components/LinkedConvosPicker.tsx +121 -63
- package/template/client/src/components/Sidebar.tsx +113 -0
- package/template/client/src/reactLab.ts +408 -0
- package/template/client/src/store.ts +15 -1
- package/template/client/src/types.ts +2 -0
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/cockpit.json +1 -1
- package/template/server/src/google-drive.ts +2 -0
- package/template/server/src/index.ts +90 -24
- package/template/server/src/storage.ts +2 -0
|
@@ -1143,6 +1143,414 @@ export const DEFAULT_MODULE_FEDERATION_LAB: FrontendLabWorkspace = {
|
|
|
1143
1143
|
files: MODULE_FEDERATION_DEFAULT_FILES,
|
|
1144
1144
|
};
|
|
1145
1145
|
|
|
1146
|
+
// ─── Isolated Mount/Unmount MFE workspace ───────────────────────────────────
|
|
1147
|
+
// Each remote manages its own React root — no shared React tree with the host.
|
|
1148
|
+
// The remote exposes mount(el, props) / unmount(el) instead of a React component.
|
|
1149
|
+
const MODULE_FEDERATION_ISOLATED_FILES: Record<string, string> = {
|
|
1150
|
+
"README.md": `# Isolated Mount/Unmount Module Federation Lab
|
|
1151
|
+
|
|
1152
|
+
## Pattern overview
|
|
1153
|
+
|
|
1154
|
+
In the classic "shared React tree" pattern the host imports a remote React component
|
|
1155
|
+
and renders it inside its own tree - both share one React runtime.
|
|
1156
|
+
|
|
1157
|
+
In this **isolated** pattern:
|
|
1158
|
+
- The remote exposes \`mount(el, props)\` and \`unmount(el)\` functions
|
|
1159
|
+
- The host calls those functions with a DOM element
|
|
1160
|
+
- Each remote creates its own \`ReactDOM.createRoot\` — it is NOT in the host's tree
|
|
1161
|
+
- The remote can use a different React major version from the host
|
|
1162
|
+
|
|
1163
|
+
## Structure
|
|
1164
|
+
|
|
1165
|
+
- \`apps/host\` — the shell app (React 18)
|
|
1166
|
+
- \`apps/mfe-auth\` — the micro-frontend (could be React 18 or 19)
|
|
1167
|
+
|
|
1168
|
+
## Key files
|
|
1169
|
+
|
|
1170
|
+
- \`apps/mfe-auth/src/mount.jsx\` — exposes \`mount\` / \`unmount\`
|
|
1171
|
+
- \`apps/mfe-auth/webpack.config.js\` — exposes \`./mount\` (not a React component)
|
|
1172
|
+
- \`apps/host/src/MfeContainer.jsx\` — host side: DOM ref + useEffect calling mount/unmount
|
|
1173
|
+
- \`apps/host/src/App.jsx\` — renders \`<MfeContainer />\` like any normal component
|
|
1174
|
+
|
|
1175
|
+
## What to experiment with
|
|
1176
|
+
|
|
1177
|
+
1. Try passing different versions of React to host vs mfe-auth and see they don't conflict
|
|
1178
|
+
2. Add a second MFE (\`apps/mfe-dashboard\`) following the same mount/unmount contract
|
|
1179
|
+
3. Pass props through \`mount(el, { user, theme })\` and handle updates in the remote
|
|
1180
|
+
4. Observe that React context from the host does NOT flow into the remote
|
|
1181
|
+
`,
|
|
1182
|
+
"package.json": `{
|
|
1183
|
+
"name": "mf-isolated-lab",
|
|
1184
|
+
"private": true,
|
|
1185
|
+
"workspaces": [
|
|
1186
|
+
"apps/host",
|
|
1187
|
+
"apps/mfe-auth"
|
|
1188
|
+
],
|
|
1189
|
+
"scripts": {
|
|
1190
|
+
"dev": "concurrently -k -n host,mfe-auth -c cyan,magenta 'npm run dev --workspace=@mf-isolated/host' 'npm run dev --workspace=@mf-isolated/mfe-auth'",
|
|
1191
|
+
"build": "npm run build --workspace=@mf-isolated/host && npm run build --workspace=@mf-isolated/mfe-auth"
|
|
1192
|
+
},
|
|
1193
|
+
"devDependencies": {
|
|
1194
|
+
"concurrently": "^9.2.1"
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
`,
|
|
1198
|
+
"apps/host/package.json": `{
|
|
1199
|
+
"name": "@mf-isolated/host",
|
|
1200
|
+
"version": "1.0.0",
|
|
1201
|
+
"private": true,
|
|
1202
|
+
"scripts": {
|
|
1203
|
+
"dev": "webpack serve --config webpack.config.js",
|
|
1204
|
+
"build": "webpack --config webpack.config.js"
|
|
1205
|
+
},
|
|
1206
|
+
"dependencies": {
|
|
1207
|
+
"react": "^19.0.0",
|
|
1208
|
+
"react-dom": "^19.0.0"
|
|
1209
|
+
},
|
|
1210
|
+
"devDependencies": {
|
|
1211
|
+
"esbuild": "^0.28.0",
|
|
1212
|
+
"esbuild-loader": "^4.4.3",
|
|
1213
|
+
"html-webpack-plugin": "^5.6.7",
|
|
1214
|
+
"webpack": "^5.106.2",
|
|
1215
|
+
"webpack-cli": "^7.0.2",
|
|
1216
|
+
"webpack-dev-server": "^5.2.3"
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
`,
|
|
1220
|
+
"apps/host/webpack.config.js": `const HtmlWebpackPlugin = require("html-webpack-plugin");
|
|
1221
|
+
const { ModuleFederationPlugin } = require("webpack").container;
|
|
1222
|
+
const deps = require("./package.json").dependencies;
|
|
1223
|
+
|
|
1224
|
+
const HOST_PORT = parseInt(process.env.HOST_PORT || "3001");
|
|
1225
|
+
const MFE_AUTH_PORT = parseInt(process.env.MFE_AUTH_PORT || "3002");
|
|
1226
|
+
|
|
1227
|
+
module.exports = {
|
|
1228
|
+
mode: "development",
|
|
1229
|
+
entry: "./src/index.js",
|
|
1230
|
+
output: {
|
|
1231
|
+
publicPath: "auto",
|
|
1232
|
+
},
|
|
1233
|
+
resolve: { extensions: [".js", ".jsx"] },
|
|
1234
|
+
module: {
|
|
1235
|
+
rules: [
|
|
1236
|
+
{
|
|
1237
|
+
test: /\\.(js|jsx)$/,
|
|
1238
|
+
exclude: /node_modules/,
|
|
1239
|
+
use: {
|
|
1240
|
+
loader: "esbuild-loader",
|
|
1241
|
+
options: { loader: "jsx", jsx: "automatic", target: "es2020" },
|
|
1242
|
+
},
|
|
1243
|
+
},
|
|
1244
|
+
],
|
|
1245
|
+
},
|
|
1246
|
+
plugins: [
|
|
1247
|
+
new ModuleFederationPlugin({
|
|
1248
|
+
name: "host",
|
|
1249
|
+
remotes: {
|
|
1250
|
+
// The remote exposes mount/unmount — NOT a React component
|
|
1251
|
+
mfeAuth: \`mfeAuth@http://localhost:\${MFE_AUTH_PORT}/remoteEntry.js\`,
|
|
1252
|
+
},
|
|
1253
|
+
// Host does NOT share React with the remote.
|
|
1254
|
+
// Each app brings its own copy.
|
|
1255
|
+
shared: {
|
|
1256
|
+
react: { requiredVersion: deps.react },
|
|
1257
|
+
"react-dom": { requiredVersion: deps["react-dom"] },
|
|
1258
|
+
},
|
|
1259
|
+
}),
|
|
1260
|
+
new HtmlWebpackPlugin({ template: "./public/index.html" }),
|
|
1261
|
+
],
|
|
1262
|
+
devServer: {
|
|
1263
|
+
port: HOST_PORT,
|
|
1264
|
+
headers: { "Access-Control-Allow-Origin": "*" },
|
|
1265
|
+
},
|
|
1266
|
+
};
|
|
1267
|
+
`,
|
|
1268
|
+
"apps/host/public/index.html": `<!doctype html>
|
|
1269
|
+
<html lang="en">
|
|
1270
|
+
<head>
|
|
1271
|
+
<meta charset="UTF-8" />
|
|
1272
|
+
<title>Host (Isolated MFE)</title>
|
|
1273
|
+
<style>
|
|
1274
|
+
body { font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; margin: 0; }
|
|
1275
|
+
h1 { padding: 1rem 1.5rem 0; font-size: 1.1rem; color: #94a3b8; }
|
|
1276
|
+
.mfe-slot {
|
|
1277
|
+
margin: 1rem 1.5rem;
|
|
1278
|
+
border: 1px solid #334155;
|
|
1279
|
+
border-radius: 8px;
|
|
1280
|
+
padding: 1rem;
|
|
1281
|
+
min-height: 80px;
|
|
1282
|
+
background: #1e293b;
|
|
1283
|
+
}
|
|
1284
|
+
</style>
|
|
1285
|
+
</head>
|
|
1286
|
+
<body>
|
|
1287
|
+
<div id="root"></div>
|
|
1288
|
+
</body>
|
|
1289
|
+
</html>
|
|
1290
|
+
`,
|
|
1291
|
+
"apps/host/src/index.js": `// Async boundary: required for Module Federation dynamic imports
|
|
1292
|
+
import("./bootstrap");
|
|
1293
|
+
`,
|
|
1294
|
+
"apps/host/src/bootstrap.jsx": `import React from "react";
|
|
1295
|
+
import { createRoot } from "react-dom/client";
|
|
1296
|
+
import App from "./App";
|
|
1297
|
+
|
|
1298
|
+
createRoot(document.getElementById("root")).render(<App />);
|
|
1299
|
+
`,
|
|
1300
|
+
"apps/host/src/App.jsx": `import React from "react";
|
|
1301
|
+
import MfeContainer from "./MfeContainer";
|
|
1302
|
+
|
|
1303
|
+
export default function App() {
|
|
1304
|
+
return (
|
|
1305
|
+
<div>
|
|
1306
|
+
<h1>Host App — Isolated MFE demo</h1>
|
|
1307
|
+
<p style={{ padding: "0 1.5rem", color: "#64748b", fontSize: "0.8rem" }}>
|
|
1308
|
+
The auth widget below is rendered by mfe-auth into its own React root.
|
|
1309
|
+
The host and the remote do NOT share a React tree.
|
|
1310
|
+
</p>
|
|
1311
|
+
|
|
1312
|
+
{/* Host renders a plain DOM container.
|
|
1313
|
+
MfeContainer calls mount/unmount on that element. */}
|
|
1314
|
+
<MfeContainer user={{ name: "Alice", role: "admin" }} />
|
|
1315
|
+
</div>
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
`,
|
|
1319
|
+
"apps/host/src/MfeContainer.jsx": `import React, { useRef, useEffect } from "react";
|
|
1320
|
+
|
|
1321
|
+
// Lazy-load the remote's mount/unmount contract — not a React component.
|
|
1322
|
+
const mfeAuthMountPromise = import("mfeAuth/mount");
|
|
1323
|
+
|
|
1324
|
+
export default function MfeContainer({ user }) {
|
|
1325
|
+
const elRef = useRef(null);
|
|
1326
|
+
const mountedRef = useRef(null); // holds { unmount } returned by remote
|
|
1327
|
+
|
|
1328
|
+
useEffect(() => {
|
|
1329
|
+
let cancelled = false;
|
|
1330
|
+
let cleanup = null;
|
|
1331
|
+
|
|
1332
|
+
mfeAuthMountPromise.then(({ mount }) => {
|
|
1333
|
+
if (cancelled || !elRef.current) return;
|
|
1334
|
+
// mount() creates a new ReactDOM.createRoot inside mfe-auth.
|
|
1335
|
+
// It returns an object with an unmount() method.
|
|
1336
|
+
cleanup = mount(elRef.current, { user });
|
|
1337
|
+
mountedRef.current = cleanup;
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
return () => {
|
|
1341
|
+
cancelled = true;
|
|
1342
|
+
// On cleanup, call the remote's own unmount so it can tear down its root.
|
|
1343
|
+
cleanup?.unmount();
|
|
1344
|
+
};
|
|
1345
|
+
}, []); // mount once — treat like a portal
|
|
1346
|
+
|
|
1347
|
+
// If props change, tell the remote to update.
|
|
1348
|
+
// The remote decides how to handle prop updates.
|
|
1349
|
+
useEffect(() => {
|
|
1350
|
+
mountedRef.current?.update?.({ user });
|
|
1351
|
+
}, [user]);
|
|
1352
|
+
|
|
1353
|
+
return (
|
|
1354
|
+
<div
|
|
1355
|
+
ref={elRef}
|
|
1356
|
+
className="mfe-slot"
|
|
1357
|
+
style={{
|
|
1358
|
+
margin: "1rem 1.5rem",
|
|
1359
|
+
border: "1px solid #334155",
|
|
1360
|
+
borderRadius: "8px",
|
|
1361
|
+
padding: "1rem",
|
|
1362
|
+
minHeight: "80px",
|
|
1363
|
+
background: "#1e293b",
|
|
1364
|
+
}}
|
|
1365
|
+
/>
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
`,
|
|
1369
|
+
"apps/mfe-auth/package.json": `{
|
|
1370
|
+
"name": "@mf-isolated/mfe-auth",
|
|
1371
|
+
"version": "1.0.0",
|
|
1372
|
+
"private": true,
|
|
1373
|
+
"scripts": {
|
|
1374
|
+
"dev": "webpack serve --config webpack.config.js",
|
|
1375
|
+
"build": "webpack --config webpack.config.js"
|
|
1376
|
+
},
|
|
1377
|
+
"dependencies": {
|
|
1378
|
+
"react": "^19.0.0",
|
|
1379
|
+
"react-dom": "^19.0.0"
|
|
1380
|
+
},
|
|
1381
|
+
"devDependencies": {
|
|
1382
|
+
"esbuild": "^0.28.0",
|
|
1383
|
+
"esbuild-loader": "^4.4.3",
|
|
1384
|
+
"html-webpack-plugin": "^5.6.7",
|
|
1385
|
+
"webpack": "^5.106.2",
|
|
1386
|
+
"webpack-cli": "^7.0.2",
|
|
1387
|
+
"webpack-dev-server": "^5.2.3"
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
`,
|
|
1391
|
+
"apps/mfe-auth/webpack.config.js": `const HtmlWebpackPlugin = require("html-webpack-plugin");
|
|
1392
|
+
const { ModuleFederationPlugin } = require("webpack").container;
|
|
1393
|
+
const deps = require("./package.json").dependencies;
|
|
1394
|
+
|
|
1395
|
+
const MFE_AUTH_PORT = parseInt(process.env.MFE_AUTH_PORT || "3002");
|
|
1396
|
+
|
|
1397
|
+
module.exports = {
|
|
1398
|
+
mode: "development",
|
|
1399
|
+
entry: "./src/index.js",
|
|
1400
|
+
output: {
|
|
1401
|
+
publicPath: "auto",
|
|
1402
|
+
},
|
|
1403
|
+
resolve: { extensions: [".js", ".jsx"] },
|
|
1404
|
+
module: {
|
|
1405
|
+
rules: [
|
|
1406
|
+
{
|
|
1407
|
+
test: /\\.(js|jsx)$/,
|
|
1408
|
+
exclude: /node_modules/,
|
|
1409
|
+
use: {
|
|
1410
|
+
loader: "esbuild-loader",
|
|
1411
|
+
options: { loader: "jsx", jsx: "automatic", target: "es2020" },
|
|
1412
|
+
},
|
|
1413
|
+
},
|
|
1414
|
+
],
|
|
1415
|
+
},
|
|
1416
|
+
plugins: [
|
|
1417
|
+
new ModuleFederationPlugin({
|
|
1418
|
+
name: "mfeAuth",
|
|
1419
|
+
filename: "remoteEntry.js",
|
|
1420
|
+
exposes: {
|
|
1421
|
+
// Key difference: we expose the MOUNT MODULE, not a React component.
|
|
1422
|
+
// The host never imports a JSX element from us directly.
|
|
1423
|
+
"./mount": "./src/mount.jsx",
|
|
1424
|
+
},
|
|
1425
|
+
// This remote brings its OWN React copy.
|
|
1426
|
+
// No singleton sharing with the host here.
|
|
1427
|
+
shared: {
|
|
1428
|
+
react: { requiredVersion: deps.react },
|
|
1429
|
+
"react-dom": { requiredVersion: deps["react-dom"] },
|
|
1430
|
+
},
|
|
1431
|
+
}),
|
|
1432
|
+
new HtmlWebpackPlugin({ template: "./public/index.html" }),
|
|
1433
|
+
],
|
|
1434
|
+
devServer: {
|
|
1435
|
+
port: MFE_AUTH_PORT,
|
|
1436
|
+
headers: { "Access-Control-Allow-Origin": "*" },
|
|
1437
|
+
},
|
|
1438
|
+
};
|
|
1439
|
+
`,
|
|
1440
|
+
"apps/mfe-auth/public/index.html": `<!doctype html>
|
|
1441
|
+
<html lang="en">
|
|
1442
|
+
<head><meta charset="UTF-8" /><title>MFE Auth (standalone)</title></head>
|
|
1443
|
+
<body><div id="root"></div></body>
|
|
1444
|
+
</html>
|
|
1445
|
+
`,
|
|
1446
|
+
"apps/mfe-auth/src/index.js": `import("./bootstrap");
|
|
1447
|
+
`,
|
|
1448
|
+
"apps/mfe-auth/src/bootstrap.jsx": `// Standalone entry — only used when running mfe-auth on its own for development.
|
|
1449
|
+
import React from "react";
|
|
1450
|
+
import { createRoot } from "react-dom/client";
|
|
1451
|
+
import App from "./App";
|
|
1452
|
+
|
|
1453
|
+
createRoot(document.getElementById("root")).render(
|
|
1454
|
+
<App user={{ name: "Dev User", role: "developer" }} />
|
|
1455
|
+
);
|
|
1456
|
+
`,
|
|
1457
|
+
"apps/mfe-auth/src/App.jsx": `import React from "react";
|
|
1458
|
+
|
|
1459
|
+
// Standalone view — used for local development of the MFE in isolation.
|
|
1460
|
+
export default function App({ user = {} }) {
|
|
1461
|
+
return (
|
|
1462
|
+
<div style={{ padding: "1rem", fontFamily: "system-ui, sans-serif", background: "#0f172a", color: "#e2e8f0", minHeight: "100vh" }}>
|
|
1463
|
+
<h2 style={{ color: "#a78bfa", fontSize: "0.9rem", margin: "0 0 0.5rem" }}>
|
|
1464
|
+
mfe-auth — standalone dev view
|
|
1465
|
+
</h2>
|
|
1466
|
+
<AuthWidget user={user} />
|
|
1467
|
+
</div>
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// The actual React component that this MFE renders.
|
|
1472
|
+
export function AuthWidget({ user = {} }) {
|
|
1473
|
+
return (
|
|
1474
|
+
<div style={{ padding: "0.75rem 1rem", background: "#1e293b", borderRadius: "6px", border: "1px solid #334155" }}>
|
|
1475
|
+
<p style={{ margin: 0, fontSize: "0.8rem", color: "#94a3b8" }}>Auth MFE — isolated React root</p>
|
|
1476
|
+
<p style={{ margin: "0.5rem 0 0", fontSize: "0.75rem", color: "#64748b" }}>
|
|
1477
|
+
Logged in as: <strong style={{ color: "#e2e8f0" }}>{user.name ?? "Guest"}</strong>
|
|
1478
|
+
{user.role && <span style={{ marginLeft: "0.5rem", color: "#6366f1" }}>({user.role})</span>}
|
|
1479
|
+
</p>
|
|
1480
|
+
<p style={{ margin: "0.5rem 0 0", fontSize: "0.7rem", color: "#334155", fontStyle: "italic" }}>
|
|
1481
|
+
This component lives in its own React root — not in the host tree.
|
|
1482
|
+
React context from the host does not reach here.
|
|
1483
|
+
</p>
|
|
1484
|
+
</div>
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
`,
|
|
1488
|
+
"apps/mfe-auth/src/mount.jsx": `// ─────────────────────────────────────────────────────────────
|
|
1489
|
+
// mount.jsx — the public contract exposed via Module Federation
|
|
1490
|
+
//
|
|
1491
|
+
// The host imports THIS file, not a React component.
|
|
1492
|
+
// It calls mount(domElement, props) to render, unmount(domElement) to tear down.
|
|
1493
|
+
//
|
|
1494
|
+
// This is the core of the isolated MFE pattern:
|
|
1495
|
+
// 1. We create our OWN React root (not the host's)
|
|
1496
|
+
// 2. We own our own lifecycle
|
|
1497
|
+
// 3. The host never sees our React instance
|
|
1498
|
+
// ─────────────────────────────────────────────────────────────
|
|
1499
|
+
import React from "react";
|
|
1500
|
+
import { createRoot } from "react-dom/client";
|
|
1501
|
+
import { AuthWidget } from "./App";
|
|
1502
|
+
|
|
1503
|
+
// Keep track of roots by element so we can update or unmount them.
|
|
1504
|
+
const roots = new WeakMap();
|
|
1505
|
+
|
|
1506
|
+
/**
|
|
1507
|
+
* Mount the MFE into the given DOM element.
|
|
1508
|
+
*
|
|
1509
|
+
* @param {HTMLElement} el - the host-provided DOM node
|
|
1510
|
+
* @param {object} props - initial props from the host
|
|
1511
|
+
* @returns {{ unmount: () => void, update: (props: object) => void }}
|
|
1512
|
+
*/
|
|
1513
|
+
export function mount(el, props = {}) {
|
|
1514
|
+
const root = createRoot(el);
|
|
1515
|
+
roots.set(el, root);
|
|
1516
|
+
|
|
1517
|
+
root.render(<AuthWidget {...props} />);
|
|
1518
|
+
|
|
1519
|
+
return {
|
|
1520
|
+
/** Call this to pass updated props from the host without remounting. */
|
|
1521
|
+
update(newProps) {
|
|
1522
|
+
root.render(<AuthWidget {...newProps} />);
|
|
1523
|
+
},
|
|
1524
|
+
/** Tear down the React root when the host removes this MFE. */
|
|
1525
|
+
unmount() {
|
|
1526
|
+
root.unmount();
|
|
1527
|
+
roots.delete(el);
|
|
1528
|
+
},
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Convenience function: unmount using only the element reference.
|
|
1534
|
+
* Useful if the host didn't store the return value of mount().
|
|
1535
|
+
*/
|
|
1536
|
+
export function unmount(el) {
|
|
1537
|
+
const root = roots.get(el);
|
|
1538
|
+
if (root) {
|
|
1539
|
+
root.unmount();
|
|
1540
|
+
roots.delete(el);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
`,
|
|
1544
|
+
};
|
|
1545
|
+
|
|
1546
|
+
export const ISOLATED_MODULE_FEDERATION_LAB: FrontendLabWorkspace = {
|
|
1547
|
+
version: 1,
|
|
1548
|
+
label: "Webpack MF — Isolated Mount/Unmount",
|
|
1549
|
+
type: "module-federation",
|
|
1550
|
+
activeFile: "apps/mfe-auth/src/mount.jsx",
|
|
1551
|
+
files: MODULE_FEDERATION_ISOLATED_FILES,
|
|
1552
|
+
};
|
|
1553
|
+
|
|
1146
1554
|
export function defaultForType(type: FrontendLabType): FrontendLabWorkspace {
|
|
1147
1555
|
if (type === "nextjs") return DEFAULT_NEXTJS_LAB;
|
|
1148
1556
|
if (type === "module-federation") return DEFAULT_MODULE_FEDERATION_LAB;
|
|
@@ -220,6 +220,7 @@ interface Store {
|
|
|
220
220
|
| "user"
|
|
221
221
|
| "ai"
|
|
222
222
|
| "sandbox"
|
|
223
|
+
| "browser-security"
|
|
223
224
|
| "infra"
|
|
224
225
|
| "react"
|
|
225
226
|
| "nextjs"
|
|
@@ -283,6 +284,8 @@ interface Store {
|
|
|
283
284
|
clientCode: string;
|
|
284
285
|
clientLang: string;
|
|
285
286
|
fileId?: string;
|
|
287
|
+
label?: string;
|
|
288
|
+
origin?: "sandbox" | "browser-security";
|
|
286
289
|
/** If set, the client panel opens in React or Next.js preview mode instead of script mode */
|
|
287
290
|
clientType?: "script" | "react" | "nextjs" | "module-federation";
|
|
288
291
|
reactFiles?: Record<string, string> | null;
|
|
@@ -298,6 +301,8 @@ interface Store {
|
|
|
298
301
|
clientLang: string,
|
|
299
302
|
fileId?: string,
|
|
300
303
|
opts?: {
|
|
304
|
+
label?: string;
|
|
305
|
+
origin?: "sandbox" | "browser-security";
|
|
301
306
|
clientType?: "script" | "react" | "nextjs" | "module-federation";
|
|
302
307
|
reactFiles?: Record<string, string>;
|
|
303
308
|
reactActiveFile?: string;
|
|
@@ -320,6 +325,11 @@ interface Store {
|
|
|
320
325
|
openDeploymentLab: () => void;
|
|
321
326
|
closeDeploymentLab: () => void;
|
|
322
327
|
|
|
328
|
+
// ── Browser Security Lab ─────────────────────────────────────
|
|
329
|
+
showBrowserSecurityLab: boolean;
|
|
330
|
+
openBrowserSecurityLab: () => void;
|
|
331
|
+
closeBrowserSecurityLab: () => void;
|
|
332
|
+
|
|
323
333
|
// ── Infra Lab ────────────────────────────────────────────────
|
|
324
334
|
showInfraLab: boolean;
|
|
325
335
|
runnerInitialInfra: InfraLabWorkspace | null;
|
|
@@ -373,6 +383,7 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
373
383
|
runnerInitialSandbox: null,
|
|
374
384
|
runnerInitialFileId: null,
|
|
375
385
|
showDeploymentLab: false,
|
|
386
|
+
showBrowserSecurityLab: false,
|
|
376
387
|
showInfraLab: false,
|
|
377
388
|
runnerInitialInfra: null,
|
|
378
389
|
runnerInitialInfraFileId: null,
|
|
@@ -971,6 +982,8 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
971
982
|
clientCode,
|
|
972
983
|
clientLang,
|
|
973
984
|
fileId,
|
|
985
|
+
label: opts?.label,
|
|
986
|
+
origin: opts?.origin,
|
|
974
987
|
clientType: opts?.clientType,
|
|
975
988
|
reactFiles: opts?.reactFiles,
|
|
976
989
|
reactActiveFile: opts?.reactActiveFile,
|
|
@@ -1058,9 +1071,10 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
1058
1071
|
}));
|
|
1059
1072
|
},
|
|
1060
1073
|
closeCodeRunner: () => set({ showCodeRunner: false }),
|
|
1061
|
-
showDeploymentLab: false,
|
|
1062
1074
|
openDeploymentLab: () => set({ showDeploymentLab: true }),
|
|
1063
1075
|
closeDeploymentLab: () => set({ showDeploymentLab: false }),
|
|
1076
|
+
openBrowserSecurityLab: () => set({ showBrowserSecurityLab: true }),
|
|
1077
|
+
closeBrowserSecurityLab: () => set({ showBrowserSecurityLab: false }),
|
|
1064
1078
|
closeInfraLab: () => set({ showInfraLab: false }),
|
|
1065
1079
|
|
|
1066
1080
|
fetchAiSettings: async () => {
|
|
@@ -3,6 +3,7 @@ export type ContextFileOrigin =
|
|
|
3
3
|
| "ai"
|
|
4
4
|
| "upload"
|
|
5
5
|
| "sandbox"
|
|
6
|
+
| "browser-security"
|
|
6
7
|
| "infra"
|
|
7
8
|
| "react"
|
|
8
9
|
| "nextjs"
|
|
@@ -17,6 +18,7 @@ export interface ContextFile {
|
|
|
17
18
|
/** Distinguishes how this file was added. 'upload' = user-uploaded doc,
|
|
18
19
|
* 'user' = code saved from Code Runner, 'ai' = AI-generated code block,
|
|
19
20
|
* 'sandbox' = paired server+client sandbox saved as JSON,
|
|
21
|
+
* 'browser-security' = paired server+client security lab saved as JSON,
|
|
20
22
|
* 'infra' = Terraform-style infra lab workspace saved as JSON. */
|
|
21
23
|
origin?: ContextFileOrigin;
|
|
22
24
|
/** Language hint for code snippets (e.g. 'typescript', 'javascript'). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/app.tsx","./src/api.ts","./src/infralab.ts","./src/main.tsx","./src/reactlab.ts","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/infralabmodal.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/notesmodal.tsx","./src/components/plotembed.tsx","./src/components/sidebar.tsx","./src/components/textannotator.tsx","./src/components/vizcraftembed.tsx","./src/components/workspaceswitcher.tsx"],"errors":true,"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/app.tsx","./src/api.ts","./src/browsersecuritytemplates.ts","./src/infralab.ts","./src/main.tsx","./src/reactlab.ts","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/browsersecuritylabmodal.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/deploymentlabmodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/infralabmodal.tsx","./src/components/labspanel.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/notesmodal.tsx","./src/components/plotembed.tsx","./src/components/sidebar.tsx","./src/components/textannotator.tsx","./src/components/vizcraftembed.tsx","./src/components/workspaceswitcher.tsx"],"errors":true,"version":"5.9.3"}
|
package/template/cockpit.json
CHANGED
|
@@ -358,6 +358,7 @@ export async function syncWorkspace(
|
|
|
358
358
|
(cs.origin === "user" ||
|
|
359
359
|
cs.origin === "ai" ||
|
|
360
360
|
cs.origin === "sandbox" ||
|
|
361
|
+
cs.origin === "browser-security" ||
|
|
361
362
|
cs.origin === "react" ||
|
|
362
363
|
cs.origin === "nextjs" ||
|
|
363
364
|
cs.origin === "module-federation" ||
|
|
@@ -769,6 +770,7 @@ export async function exportWorkspace(
|
|
|
769
770
|
cf.origin === "user" ||
|
|
770
771
|
cf.origin === "ai" ||
|
|
771
772
|
cf.origin === "sandbox" ||
|
|
773
|
+
cf.origin === "browser-security" ||
|
|
772
774
|
cf.origin === "react" ||
|
|
773
775
|
cf.origin === "nextjs" ||
|
|
774
776
|
cf.origin === "module-federation" ||
|