@auersperg/flare 0.3.6 → 0.3.8
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/cli/{chunk-r3ceakrn.js → chunk-y6e2h40n.js} +187 -20
- package/dist/cli/flare.js +15 -4
- package/dist/platform/admin.js +91 -50
- package/dist/platform/dashboard/_astro/auth-view.EASEF9BN.js +1 -0
- package/dist/platform/dashboard/_astro/cli-approval.DwZVHdyo.js +1 -0
- package/dist/platform/dashboard/_astro/common.TAClb6G9.js +1 -0
- package/dist/platform/dashboard/_astro/data-view.BaHEU4Xa.js +1 -0
- package/dist/platform/dashboard/_astro/functions-view.BGOYuDwu.js +1 -0
- package/dist/platform/dashboard/_astro/index.BPQdjJ4j.css +2 -0
- package/dist/platform/dashboard/_astro/index.astro_astro_type_script_index_0_lang.Blg5hU9g.js +3 -0
- package/dist/platform/dashboard/_astro/{input-group.CJxPRHej.js → input-group.DUp-h0xx.js} +1 -1
- package/dist/platform/dashboard/_astro/{sdk-view.COmbEZrF.js → sdk-view.2ErPn4cO.js} +1 -1
- package/dist/platform/dashboard/_astro/services-view.iZujitxl.js +1 -0
- package/dist/platform/dashboard/_astro/settings-view.CqK-mrlB.js +3 -0
- package/dist/platform/dashboard/index.html +1 -1
- package/dist/platform/manifest.json +39 -39
- package/dist/platform/runtime.js +214 -35
- package/package.json +1 -1
- package/dist/platform/dashboard/_astro/auth-view.UTGGCEHc.js +0 -1
- package/dist/platform/dashboard/_astro/cli-approval.BZAWmpiR.js +0 -1
- package/dist/platform/dashboard/_astro/common.DPoFQu44.js +0 -1
- package/dist/platform/dashboard/_astro/data-view.BgYyTCMN.js +0 -1
- package/dist/platform/dashboard/_astro/functions-view.PUkng3T1.js +0 -1
- package/dist/platform/dashboard/_astro/index.astro_astro_type_script_index_0_lang.BEuO3H0E.js +0 -4
- package/dist/platform/dashboard/_astro/index.lobU7uK5.css +0 -2
- package/dist/platform/dashboard/_astro/services-view.C34dy0dk.js +0 -1
- package/dist/platform/dashboard/_astro/settings-view.IuD2r5JW.js +0 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/flare/src/cli/platform.mjs
|
|
3
|
-
import { readFile as
|
|
3
|
+
import { readFile as readFile4, mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, cp } from "fs/promises";
|
|
4
4
|
import { join as join3 } from "path";
|
|
5
5
|
import { tmpdir } from "os";
|
|
6
6
|
import { randomBytes, randomUUID as randomUUID2 } from "crypto";
|
|
@@ -17,7 +17,7 @@ import { join } from "path";
|
|
|
17
17
|
import { fileURLToPath } from "url";
|
|
18
18
|
import { createHash } from "crypto";
|
|
19
19
|
var PLATFORM_FORMAT = 1;
|
|
20
|
-
var WRANGLER_VERSION = "4.
|
|
20
|
+
var WRANGLER_VERSION = "4.150.0";
|
|
21
21
|
var sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
22
22
|
var manifestDigest = ({ digest: _digest, ...manifest }) => sha256(JSON.stringify(manifest));
|
|
23
23
|
async function payloadFiles(directory, prefix = "") {
|
|
@@ -46,7 +46,7 @@ async function loadPlatformRelease(directory = fileURLToPath(new URL("../platfor
|
|
|
46
46
|
} catch {
|
|
47
47
|
throw Error("Platform payload missing or invalid. Install a complete Flare npm release.");
|
|
48
48
|
}
|
|
49
|
-
if (manifest.format !== PLATFORM_FORMAT || !/^\d+\.\d+\.\d+$/.test(manifest.version ?? "") || manifest.protocol !== 1 || manifest.runtimeAbi !== 4 || manifest.stateFormat !== 1 || manifest.wranglerVersion
|
|
49
|
+
if (manifest.format !== PLATFORM_FORMAT || !/^\d+\.\d+\.\d+$/.test(manifest.version ?? "") || manifest.protocol !== 1 || manifest.runtimeAbi !== 4 || manifest.stateFormat !== 1 || ![WRANGLER_VERSION, "4.149.0"].includes(manifest.wranglerVersion) || !/^\d{4}-\d{2}-\d{2}$/.test(manifest.compatibilityDate ?? "") || !Array.isArray(manifest.upgradeFrom) || manifest.upgradeFrom.some((version) => !/^\d+\.\d+\.\d+$/.test(version) || Bun.semver.order(version, manifest.version) >= 0) || !Array.isArray(manifest.files) || !manifest.files.length || manifest.files.length > 1000 || !/^[a-f0-9]{64}$/.test(manifest.digest ?? "") || manifest.digest !== manifestDigest(manifest))
|
|
50
50
|
throw Error("Unsupported or damaged platform manifest");
|
|
51
51
|
const paths = new Set;
|
|
52
52
|
let size = 0;
|
|
@@ -84,6 +84,16 @@ function assertUpgrade(installed, target) {
|
|
|
84
84
|
if (installed.version === target.version && installed.digest !== target.digest)
|
|
85
85
|
throw Error("Same platform version has different bytes. Publish a new version; refusing an in-place replacement.");
|
|
86
86
|
}
|
|
87
|
+
function configurationDigest(value) {
|
|
88
|
+
const canonical = (item) => {
|
|
89
|
+
if (Array.isArray(item))
|
|
90
|
+
return item.map(canonical);
|
|
91
|
+
if (item && typeof item === "object")
|
|
92
|
+
return Object.fromEntries(Object.keys(item).sort().map((key) => [key, canonical(item[key])]));
|
|
93
|
+
return item;
|
|
94
|
+
};
|
|
95
|
+
return sha256(JSON.stringify(canonical(value)));
|
|
96
|
+
}
|
|
87
97
|
|
|
88
98
|
// packages/flare/src/cli/cloudflare-account.mjs
|
|
89
99
|
class CloudflareError extends Error {
|
|
@@ -271,10 +281,85 @@ class CloudflareAccount {
|
|
|
271
281
|
}
|
|
272
282
|
|
|
273
283
|
// packages/flare/src/cli/platform-state.mjs
|
|
274
|
-
import { mkdir, readFile as
|
|
284
|
+
import { mkdir, readFile as readFile3, writeFile, rename, chmod } from "fs/promises";
|
|
275
285
|
import { join as join2 } from "path";
|
|
276
286
|
import { homedir } from "os";
|
|
277
287
|
import { randomUUID } from "crypto";
|
|
288
|
+
|
|
289
|
+
// packages/flare/src/cli/platform-config.mjs
|
|
290
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
291
|
+
function validatePlatformConfig(value = {}) {
|
|
292
|
+
const object = (v) => v && typeof v === "object" && !Array.isArray(v);
|
|
293
|
+
if (!object(value) || Object.keys(value).some((k) => !["capabilities", "secrets"].includes(k)))
|
|
294
|
+
throw Error("Platform config accepts only capabilities and secrets references");
|
|
295
|
+
const capabilities = value.capabilities ?? {}, secrets = value.secrets ?? [];
|
|
296
|
+
if (!object(capabilities) || !Array.isArray(secrets) || secrets.length > 64)
|
|
297
|
+
throw Error("Invalid platform capabilities/secrets references");
|
|
298
|
+
const bindings = new Set;
|
|
299
|
+
const references = secrets.map((r) => {
|
|
300
|
+
if (!object(r) || Object.keys(r).sort().join() !== "binding,secret_name,store_id" || !/^APP_SECRET_[A-Z0-9_]{1,64}$/.test(r.binding) || !/^[a-f0-9]{32}$/.test(r.store_id) || typeof r.secret_name !== "string" || !/^[A-Za-z0-9_.-]{1,128}$/.test(r.secret_name) || bindings.has(r.binding))
|
|
301
|
+
throw Error("Invalid or duplicate native Secrets Store reference");
|
|
302
|
+
bindings.add(r.binding);
|
|
303
|
+
return {
|
|
304
|
+
binding: r.binding,
|
|
305
|
+
store_id: r.store_id,
|
|
306
|
+
secret_name: r.secret_name
|
|
307
|
+
};
|
|
308
|
+
}).sort((a, b) => a.binding.localeCompare(b.binding));
|
|
309
|
+
const result = {};
|
|
310
|
+
const used = new Set;
|
|
311
|
+
for (const scope of Object.keys(capabilities).sort()) {
|
|
312
|
+
const p = capabilities[scope];
|
|
313
|
+
if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\/(dev|prod)$/.test(scope) || !object(p) || Object.keys(p).some((k) => !["outbound", "secrets"].includes(k)))
|
|
314
|
+
throw Error("Invalid capability scope");
|
|
315
|
+
const hosts = p.outbound ?? [], mapping = p.secrets ?? {};
|
|
316
|
+
if (!Array.isArray(hosts) || hosts.length > 20 || !object(mapping) || Object.keys(mapping).length > 16)
|
|
317
|
+
throw Error("Capability limits exceeded");
|
|
318
|
+
for (const host of hosts)
|
|
319
|
+
if (typeof host !== "string" || host.length > 253 || !/^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,63}$/.test(host) || /\.(localhost|local|internal)$/.test(host))
|
|
320
|
+
throw Error("Only exact public HTTPS hostnames are allowed");
|
|
321
|
+
for (const [name, binding] of Object.entries(mapping)) {
|
|
322
|
+
if (!/^[A-Z][A-Z0-9_]{0,63}$/.test(name) || !bindings.has(binding))
|
|
323
|
+
throw Error("Secret grant must reference an explicit APP_SECRET binding");
|
|
324
|
+
used.add(binding);
|
|
325
|
+
}
|
|
326
|
+
result[scope] = {
|
|
327
|
+
outbound: [...new Set(hosts)].sort(),
|
|
328
|
+
secrets: Object.fromEntries(Object.entries(mapping).sort())
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (references.some((r) => !used.has(r.binding)))
|
|
332
|
+
throw Error("Unused secret binding; grant it explicitly or remove it");
|
|
333
|
+
if (JSON.stringify(result).length > 16384)
|
|
334
|
+
throw Error("Capabilities exceed 16 KiB");
|
|
335
|
+
return { capabilities: result, secrets: references };
|
|
336
|
+
}
|
|
337
|
+
async function readPlatformConfig(path) {
|
|
338
|
+
const bytes = await readFile2(path);
|
|
339
|
+
if (bytes.length > 32768)
|
|
340
|
+
throw Error("Platform config exceeds 32 KiB");
|
|
341
|
+
return validatePlatformConfig(JSON.parse(bytes));
|
|
342
|
+
}
|
|
343
|
+
function accessConfiguration(app, policies) {
|
|
344
|
+
const {
|
|
345
|
+
created_at: _created,
|
|
346
|
+
updated_at: _updated,
|
|
347
|
+
policies: _embedded,
|
|
348
|
+
...config
|
|
349
|
+
} = app;
|
|
350
|
+
const clean = policies.map(({ created_at: _c, updated_at: _u, ...p }) => p).sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
351
|
+
return { app: config, policies: clean };
|
|
352
|
+
}
|
|
353
|
+
async function accessSnapshot(cf, id) {
|
|
354
|
+
const app = await cf.api(`access/apps/${id}`);
|
|
355
|
+
const policies = await cf.list(`access/apps/${id}/policies`);
|
|
356
|
+
return {
|
|
357
|
+
app,
|
|
358
|
+
digest: configurationDigest(accessConfiguration(app, policies))
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// packages/flare/src/cli/platform-state.mjs
|
|
278
363
|
var leaseMilliseconds = 15 * 60 * 1000;
|
|
279
364
|
var identifier = /^[a-z][a-z0-9-]{0,19}$/;
|
|
280
365
|
var resourceName = /^[a-z][a-z0-9-]{0,62}$/;
|
|
@@ -288,6 +373,8 @@ function hostname(value) {
|
|
|
288
373
|
function validateState(state, account, name) {
|
|
289
374
|
if (!state || state.format !== 1 || state.account !== account || state.name !== name || !uuid.test(state.id ?? "") || !validName(name) || typeof state.owner !== "string" || !/^\S+@\S+\.\S+$/.test(state.owner) || state.owner.length > 254)
|
|
290
375
|
throw Error("Unsupported installation state or account/name mismatch");
|
|
376
|
+
if (state.config !== undefined)
|
|
377
|
+
validatePlatformConfig(state.config);
|
|
291
378
|
const prefix = `flare-${name}-${state.id.replaceAll("-", "").slice(0, 12)}`;
|
|
292
379
|
const operator = state.origin === "operator";
|
|
293
380
|
if (state.origin !== undefined && !operator)
|
|
@@ -407,7 +494,7 @@ class InstallationStore {
|
|
|
407
494
|
await chmod(this.file, 384);
|
|
408
495
|
}
|
|
409
496
|
async local() {
|
|
410
|
-
const source = await
|
|
497
|
+
const source = await readFile3(this.file, "utf8").catch((error) => {
|
|
411
498
|
if (error.code !== "ENOENT")
|
|
412
499
|
throw error;
|
|
413
500
|
return null;
|
|
@@ -505,15 +592,63 @@ function settingsDigest(settings) {
|
|
|
505
592
|
stable.bindings = [...settings.bindings ?? []].sort((a, b) => a.name.localeCompare(b.name));
|
|
506
593
|
return configurationDigest(stable);
|
|
507
594
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
595
|
+
|
|
596
|
+
// packages/flare/src/cli/platform-health.mjs
|
|
597
|
+
async function platformHealth(cf, state, fetcher) {
|
|
598
|
+
const checkedAt = Date.now();
|
|
599
|
+
const probe = async (fn) => {
|
|
600
|
+
try {
|
|
601
|
+
return await fn();
|
|
602
|
+
} catch {
|
|
603
|
+
return { status: "unavailable" };
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
const [runtime, dashboard, access] = await Promise.all([
|
|
607
|
+
probe(async () => {
|
|
608
|
+
const r = await fetcher(`https://${state.addresses.runtimeHost}/health?ready=1`, { redirect: "error", signal: AbortSignal.timeout(5000) });
|
|
609
|
+
if (!r.ok) {
|
|
610
|
+
await r.body?.cancel();
|
|
611
|
+
return { status: "unhealthy", httpStatus: r.status };
|
|
612
|
+
}
|
|
613
|
+
const d = await r.json();
|
|
614
|
+
const matches = d.readiness === "control-plane-ready" && d.installationId === state.id && d.platformPayload === state.installed?.digest && d.version === state.installed?.version;
|
|
615
|
+
return {
|
|
616
|
+
status: matches ? "healthy" : "mismatch",
|
|
617
|
+
version: d.version,
|
|
618
|
+
installationId: d.installationId
|
|
619
|
+
};
|
|
620
|
+
}),
|
|
621
|
+
probe(async () => {
|
|
622
|
+
const r = await fetcher(`https://${state.addresses.adminHost}/api/session`, { redirect: "manual", signal: AbortSignal.timeout(5000) });
|
|
623
|
+
const location = r.headers.get("location");
|
|
624
|
+
await r.body?.cancel();
|
|
625
|
+
const closed = [401, 403].includes(r.status) || [301, 302, 303, 307, 308].includes(r.status) && location && new URL(location, `https://${state.addresses.adminHost}`).origin === state.access?.issuer;
|
|
626
|
+
return {
|
|
627
|
+
status: closed ? "access-protected" : "not-verified-closed",
|
|
628
|
+
httpStatus: r.status,
|
|
629
|
+
applicationHealth: "not-checked-without-owner-session"
|
|
630
|
+
};
|
|
631
|
+
}),
|
|
632
|
+
probe(async () => {
|
|
633
|
+
if (!state.access)
|
|
634
|
+
return { status: "not-configured" };
|
|
635
|
+
const { app, digest } = await accessSnapshot(cf, state.access.id);
|
|
636
|
+
const host = app.domain === state.addresses.adminHost || app.destinations?.some((d) => d.type === "public" && d.uri === state.addresses.adminHost);
|
|
637
|
+
const matches = !state.access.snapshot || (state.access.snapshotFormat === 2 ? digest : configurationDigest(app)) === state.access.snapshot;
|
|
638
|
+
return {
|
|
639
|
+
status: host && app.aud === state.access.aud && matches ? "verified" : "drift",
|
|
640
|
+
snapshot: digest,
|
|
641
|
+
snapshotFormat: 2
|
|
642
|
+
};
|
|
643
|
+
})
|
|
644
|
+
]);
|
|
645
|
+
return {
|
|
646
|
+
status: state.operation ? "incomplete" : runtime.status === "healthy" && dashboard.status === "access-protected" && access.status === "verified" ? "ready" : "not-verified",
|
|
647
|
+
checkedAt,
|
|
648
|
+
runtime,
|
|
649
|
+
dashboard,
|
|
650
|
+
access
|
|
515
651
|
};
|
|
516
|
-
return sha256(JSON.stringify(canonical(value)));
|
|
517
652
|
}
|
|
518
653
|
|
|
519
654
|
// packages/flare/src/cli/platform.mjs
|
|
@@ -681,6 +816,7 @@ function workerConfiguration(state, release, role, bootstrap = false) {
|
|
|
681
816
|
return {
|
|
682
817
|
...common,
|
|
683
818
|
worker_loaders: [{ binding: "LOADER" }],
|
|
819
|
+
...state.config ? { secrets_store_secrets: validatePlatformConfig(state.config).secrets } : {},
|
|
684
820
|
durable_objects: {
|
|
685
821
|
bindings: [{ name: "SUPERVISORS", class_name: "EnvironmentSupervisor" }]
|
|
686
822
|
},
|
|
@@ -720,7 +856,10 @@ function workerConfiguration(state, release, role, bootstrap = false) {
|
|
|
720
856
|
],
|
|
721
857
|
vars: {
|
|
722
858
|
...common.vars,
|
|
723
|
-
DEV_TOKEN_ISSUER: "https://" + state.addresses.runtimeHost
|
|
859
|
+
DEV_TOKEN_ISSUER: "https://" + state.addresses.runtimeHost,
|
|
860
|
+
...state.config ? {
|
|
861
|
+
APP_CAPABILITIES: JSON.stringify(validatePlatformConfig(state.config).capabilities)
|
|
862
|
+
} : {}
|
|
724
863
|
}
|
|
725
864
|
};
|
|
726
865
|
}
|
|
@@ -731,10 +870,15 @@ async function confirm(options, state, plan, ask, log) {
|
|
|
731
870
|
throw Error("Account confirmation required; no cloud changes were made");
|
|
732
871
|
}
|
|
733
872
|
async function assertAccess(cf, state) {
|
|
734
|
-
const app = await cf
|
|
873
|
+
const { app, digest } = await accessSnapshot(cf, state.access.id);
|
|
735
874
|
const coversHost = app.domain === state.addresses.adminHost || app.destinations?.some((d) => d.type === "public" && d.uri === state.addresses.adminHost);
|
|
736
|
-
if (!coversHost || app.aud !== state.access.aud
|
|
737
|
-
throw Error("Access
|
|
875
|
+
if (!coversHost || app.aud !== state.access.aud)
|
|
876
|
+
throw Error("Access identity/host drift; explicit snapshot acceptance cannot change identities");
|
|
877
|
+
const expected = state.access.snapshot;
|
|
878
|
+
if (expected && (state.access.snapshotFormat === 2 ? digest : configurationDigest(app)) !== expected)
|
|
879
|
+
throw Error(`Access application/policy drift. Review Cloudflare settings, then explicitly accept snapshot ${digest} using --accept-access; no policies were changed.`);
|
|
880
|
+
state.access.snapshot = digest;
|
|
881
|
+
state.access.snapshotFormat = 2;
|
|
738
882
|
}
|
|
739
883
|
async function remoteWorker(cf, name) {
|
|
740
884
|
try {
|
|
@@ -1140,12 +1284,16 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1140
1284
|
const discovered = adoption ? await discoverOperator(cf, store, name) : await store.discover();
|
|
1141
1285
|
let state = discovered.state;
|
|
1142
1286
|
if (operation === "status")
|
|
1143
|
-
return state ?
|
|
1287
|
+
return state ? {
|
|
1288
|
+
...publicState(state, release.manifest, discovered.row),
|
|
1289
|
+
health: await platformHealth(cf, state, dependencies.fetch ?? globalThis.fetch)
|
|
1290
|
+
} : {
|
|
1144
1291
|
account,
|
|
1145
1292
|
installation: name,
|
|
1146
1293
|
status: "not-installed",
|
|
1147
1294
|
target: release.manifest.version
|
|
1148
1295
|
};
|
|
1296
|
+
const requestedConfig = options.config ? await readPlatformConfig(options.config) : null;
|
|
1149
1297
|
const inventory = await preflight(cf);
|
|
1150
1298
|
if (adoption && !state)
|
|
1151
1299
|
state = await prepareOperator(cf, inventory, release, options, dependencies);
|
|
@@ -1164,6 +1312,20 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1164
1312
|
runtimeHost: options["runtime-host"]
|
|
1165
1313
|
});
|
|
1166
1314
|
}
|
|
1315
|
+
const configChanged = requestedConfig && configurationDigest(requestedConfig) !== configurationDigest(validatePlatformConfig(state.config));
|
|
1316
|
+
if (state.operation && requestedConfig && configurationDigest(requestedConfig) !== state.operation.configDigest)
|
|
1317
|
+
throw Error("Pending config differs; resume with the original config or omit --config");
|
|
1318
|
+
let accessChanged = false;
|
|
1319
|
+
if (options["accept-access"]) {
|
|
1320
|
+
if (!/^[a-f0-9]{64}$/.test(options["accept-access"]) || operation !== "update" || state.operation)
|
|
1321
|
+
throw Error("Access snapshot acceptance requires an idle managed update and exact SHA256");
|
|
1322
|
+
const actual = await accessSnapshot(cf, state.access.id);
|
|
1323
|
+
if (actual.digest !== options["accept-access"])
|
|
1324
|
+
throw Error("Access changed since review; read platform status and review the new snapshot");
|
|
1325
|
+
accessChanged = state.access.snapshotFormat !== 2 || state.access.snapshot !== actual.digest;
|
|
1326
|
+
state.access.snapshot = actual.digest;
|
|
1327
|
+
state.access.snapshotFormat = 2;
|
|
1328
|
+
}
|
|
1167
1329
|
if (state.operation && (state.operation.kind !== operation || state.operation.targetDigest !== release.manifest.digest))
|
|
1168
1330
|
throw Error("A different platform operation is incomplete. Resume it with the original CLI release before changing versions.");
|
|
1169
1331
|
if (state.installed) {
|
|
@@ -1172,7 +1334,7 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1172
1334
|
if (!state.operation) {
|
|
1173
1335
|
await assertResources(cf, state, inventory);
|
|
1174
1336
|
await assertNoDrift(cf, state);
|
|
1175
|
-
if (state.installed.digest === release.manifest.digest)
|
|
1337
|
+
if (state.installed.digest === release.manifest.digest && !configChanged && !accessChanged)
|
|
1176
1338
|
return { ...publicState(state, release.manifest), unchanged: true };
|
|
1177
1339
|
if (operation === "install" || adoption)
|
|
1178
1340
|
throw Error("Already installed. Use flare platform update to change the platform version");
|
|
@@ -1186,6 +1348,8 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1186
1348
|
from: state.installed?.version ?? state.adoptedFrom?.version ?? null,
|
|
1187
1349
|
to: release.manifest.version,
|
|
1188
1350
|
digest: release.manifest.digest,
|
|
1351
|
+
configuration: requestedConfig ?? state.config ?? null,
|
|
1352
|
+
acceptedAccessSnapshot: accessChanged ? state.access.snapshot : null,
|
|
1189
1353
|
dashboard: "https://" + state.addresses.adminHost,
|
|
1190
1354
|
runtime: "https://" + state.addresses.runtimeHost,
|
|
1191
1355
|
steps: state.installed || adoption ? ["migrations", "runtime", "admin", "verify"] : steps,
|
|
@@ -1208,10 +1372,13 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1208
1372
|
if (options["dry-run"])
|
|
1209
1373
|
return plan;
|
|
1210
1374
|
await confirm(options, state, plan, ask, log);
|
|
1375
|
+
if (!state.operation && requestedConfig)
|
|
1376
|
+
state.config = requestedConfig;
|
|
1211
1377
|
state.operation ??= {
|
|
1212
1378
|
id: randomUUID2(),
|
|
1213
1379
|
kind: operation,
|
|
1214
1380
|
targetDigest: release.manifest.digest,
|
|
1381
|
+
configDigest: configurationDigest(validatePlatformConfig(state.config)),
|
|
1215
1382
|
completed: [],
|
|
1216
1383
|
phase: "catalog",
|
|
1217
1384
|
previousVersions: { ...state.versions },
|
|
@@ -1235,7 +1402,7 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1235
1402
|
await assertResources(cf, state, inventory);
|
|
1236
1403
|
await assertNoDrift(cf, state);
|
|
1237
1404
|
}
|
|
1238
|
-
await store.initialize(state, await
|
|
1405
|
+
await store.initialize(state, await readFile4(join3(release.directory, "migrations/0006_platform_installation.sql"), "utf8"));
|
|
1239
1406
|
}
|
|
1240
1407
|
await store.acquire(state, options.resume === true);
|
|
1241
1408
|
const scratch = await mkdtemp(join3(tmpdir(), "flare-platform-"));
|
package/dist/cli/flare.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// packages/flare/package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@auersperg/flare",
|
|
6
|
-
version: "0.3.
|
|
6
|
+
version: "0.3.8",
|
|
7
7
|
license: "MIT",
|
|
8
8
|
type: "module",
|
|
9
9
|
description: "Flare client, server declarations and development CLI",
|
|
@@ -107,6 +107,8 @@ else if (command === "platform") {
|
|
|
107
107
|
console.log(`flare platform install|status|update|adopt [--account <ID>] [--name <name>]
|
|
108
108
|
install: [--owner <email>] [--dashboard-host <host> --runtime-host <host>] [--subdomain <workers.dev prefix>]
|
|
109
109
|
adopt: --owner <email> (existing standard flare-admin/flare-runtime/flare-catalog; default installation only)
|
|
110
|
+
install/update: [--config <operator JSON>]
|
|
111
|
+
update: [--accept-access <reviewed snapshot SHA256>]
|
|
110
112
|
install/update/adopt: [--dry-run] [--confirm <account ID>] [--resume]
|
|
111
113
|
Uses the platform payload bundled with this CLI release. Never updates applications or runs a local dashboard. Requires separate Cloudflare account permissions; FLARE_DEPLOY_TOKEN is not an account token.`);
|
|
112
114
|
} else {
|
|
@@ -122,8 +124,17 @@ Uses the platform payload bundled with this CLI release. Never updates applicati
|
|
|
122
124
|
"subdomain",
|
|
123
125
|
"confirm",
|
|
124
126
|
"dry-run",
|
|
125
|
-
"resume"
|
|
126
|
-
|
|
127
|
+
"resume",
|
|
128
|
+
"config"
|
|
129
|
+
] : operation === "update" ? [
|
|
130
|
+
"account",
|
|
131
|
+
"name",
|
|
132
|
+
"confirm",
|
|
133
|
+
"dry-run",
|
|
134
|
+
"resume",
|
|
135
|
+
"config",
|
|
136
|
+
"accept-access"
|
|
137
|
+
] : ["account", "name"];
|
|
127
138
|
for (let i = 0;i < args.length; i++) {
|
|
128
139
|
const key = args[i]?.slice(2);
|
|
129
140
|
if (!args[i]?.startsWith("--") || !allowed.includes(key) || key in options)
|
|
@@ -136,7 +147,7 @@ Uses the platform payload bundled with this CLI release. Never updates applicati
|
|
|
136
147
|
options[key] = args[++i];
|
|
137
148
|
}
|
|
138
149
|
}
|
|
139
|
-
const { platform } = await import("./chunk-
|
|
150
|
+
const { platform } = await import("./chunk-y6e2h40n.js");
|
|
140
151
|
console.log(JSON.stringify(await platform(options), null, 2));
|
|
141
152
|
}
|
|
142
153
|
} catch (error) {
|
package/dist/platform/admin.js
CHANGED
|
@@ -1,49 +1,3 @@
|
|
|
1
|
-
// packages/core/src/retention.ts
|
|
2
|
-
var jobRetentionMs = 30 * 86400000;
|
|
3
|
-
var catalogDeadlines = new WeakMap;
|
|
4
|
-
async function collectCatalog(database, now = Date.now()) {
|
|
5
|
-
if (now < (catalogDeadlines.get(database) ?? 0))
|
|
6
|
-
return;
|
|
7
|
-
const next = now + 86400000;
|
|
8
|
-
const claim = await database.prepare("UPDATE maintenance SET next=? WHERE id=1 AND next<=? RETURNING id").bind(next, now).first();
|
|
9
|
-
catalogDeadlines.set(database, claim ? next : now + 60000);
|
|
10
|
-
if (!claim)
|
|
11
|
-
return;
|
|
12
|
-
const statements = [
|
|
13
|
-
database.prepare("DELETE FROM audit WHERE id IN (SELECT id FROM audit WHERE at<? ORDER BY at LIMIT 1000)").bind(now - 90 * 86400000),
|
|
14
|
-
database.prepare("DELETE FROM credentials WHERE hash IN (SELECT hash FROM credentials WHERE expiresAt<? ORDER BY expiresAt LIMIT 1000)").bind(now - 14 * 86400000),
|
|
15
|
-
database.prepare("DELETE FROM cli_logins WHERE id IN (SELECT id FROM cli_logins WHERE expiresAt<? ORDER BY expiresAt LIMIT 1000)").bind(now)
|
|
16
|
-
];
|
|
17
|
-
const result = await database.batch(statements);
|
|
18
|
-
if (result.some((value) => Number(value.meta.changes) >= 1000)) {
|
|
19
|
-
catalogDeadlines.set(database, now + 60000);
|
|
20
|
-
await database.prepare("UPDATE maintenance SET next=? WHERE id=1").bind(now + 60000).run();
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
// packages/core/package.json
|
|
24
|
-
var package_default = {
|
|
25
|
-
name: "@flare/platform",
|
|
26
|
-
version: "0.3.6",
|
|
27
|
-
type: "module",
|
|
28
|
-
license: "MIT",
|
|
29
|
-
private: true,
|
|
30
|
-
files: [
|
|
31
|
-
"dist",
|
|
32
|
-
"src",
|
|
33
|
-
"bootstrap",
|
|
34
|
-
"migrations",
|
|
35
|
-
"README.md"
|
|
36
|
-
],
|
|
37
|
-
dependencies: {
|
|
38
|
-
agents: "0.28.0"
|
|
39
|
-
},
|
|
40
|
-
repository: {
|
|
41
|
-
type: "git",
|
|
42
|
-
url: "https://github.com/auersperg/flare.git",
|
|
43
|
-
directory: "packages/core"
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
1
|
// packages/flare/src/sdk/protocol.ts
|
|
48
2
|
var LIMITS = Object.freeze({
|
|
49
3
|
inputBytes: 65536,
|
|
@@ -297,6 +251,76 @@ async function accessEmail(access, audience) {
|
|
|
297
251
|
fail("UNAUTHORIZED");
|
|
298
252
|
return identity.email.toLowerCase();
|
|
299
253
|
}
|
|
254
|
+
function projectCustomer(value) {
|
|
255
|
+
if (value === undefined)
|
|
256
|
+
return "Default";
|
|
257
|
+
if (typeof value !== "string" || !value.trim() || value.length > 100)
|
|
258
|
+
fail("INVALID_INPUT");
|
|
259
|
+
return value.trim();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// packages/core/src/maintenance.ts
|
|
263
|
+
function scopeForDeletion(value) {
|
|
264
|
+
if (typeof value !== "string")
|
|
265
|
+
fail("INVALID_INPUT");
|
|
266
|
+
const parts = value.split("/");
|
|
267
|
+
if (parts.length !== 2)
|
|
268
|
+
fail("INVALID_INPUT");
|
|
269
|
+
return `${uuid(parts[0])}/${environment(parts[1])}`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// packages/core/src/dashboard-live.ts
|
|
273
|
+
async function dashboardAllowed(catalog, scope, email) {
|
|
274
|
+
const project = scopeForDeletion(scope).split("/")[0];
|
|
275
|
+
const row = await catalog.prepare("SELECT m.role FROM members m JOIN projects p ON p.id=? JOIN environments e ON e.projectId=p.id AND e.env=? WHERE m.email=? AND NOT EXISTS (SELECT 1 FROM project_deletions d WHERE d.projectId=p.id)").bind(project, scope.split("/")[1], email).first();
|
|
276
|
+
return !!row && ["owner", "editor", "viewer"].includes(row.role);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// packages/core/src/retention.ts
|
|
280
|
+
var jobRetentionMs = 30 * 86400000;
|
|
281
|
+
var catalogDeadlines = new WeakMap;
|
|
282
|
+
async function collectCatalog(database, now = Date.now()) {
|
|
283
|
+
if (now < (catalogDeadlines.get(database) ?? 0))
|
|
284
|
+
return;
|
|
285
|
+
const next = now + 86400000;
|
|
286
|
+
const claim = await database.prepare("UPDATE maintenance SET next=? WHERE id=1 AND next<=? RETURNING id").bind(next, now).first();
|
|
287
|
+
catalogDeadlines.set(database, claim ? next : now + 60000);
|
|
288
|
+
if (!claim)
|
|
289
|
+
return;
|
|
290
|
+
const statements = [
|
|
291
|
+
database.prepare("DELETE FROM audit WHERE id IN (SELECT id FROM audit WHERE at<? ORDER BY at LIMIT 1000)").bind(now - 90 * 86400000),
|
|
292
|
+
database.prepare("DELETE FROM credentials WHERE hash IN (SELECT hash FROM credentials WHERE expiresAt<? ORDER BY expiresAt LIMIT 1000)").bind(now - 14 * 86400000),
|
|
293
|
+
database.prepare("DELETE FROM cli_logins WHERE id IN (SELECT id FROM cli_logins WHERE expiresAt<? ORDER BY expiresAt LIMIT 1000)").bind(now)
|
|
294
|
+
];
|
|
295
|
+
const result = await database.batch(statements);
|
|
296
|
+
if (result.some((value) => Number(value.meta.changes) >= 1000)) {
|
|
297
|
+
catalogDeadlines.set(database, now + 60000);
|
|
298
|
+
await database.prepare("UPDATE maintenance SET next=? WHERE id=1").bind(now + 60000).run();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// packages/core/package.json
|
|
302
|
+
var package_default = {
|
|
303
|
+
name: "@flare/platform",
|
|
304
|
+
version: "0.3.8",
|
|
305
|
+
type: "module",
|
|
306
|
+
license: "MIT",
|
|
307
|
+
private: true,
|
|
308
|
+
files: [
|
|
309
|
+
"dist",
|
|
310
|
+
"src",
|
|
311
|
+
"bootstrap",
|
|
312
|
+
"migrations",
|
|
313
|
+
"README.md"
|
|
314
|
+
],
|
|
315
|
+
dependencies: {
|
|
316
|
+
agents: "0.28.0"
|
|
317
|
+
},
|
|
318
|
+
repository: {
|
|
319
|
+
type: "git",
|
|
320
|
+
url: "https://github.com/auersperg/flare.git",
|
|
321
|
+
directory: "packages/core"
|
|
322
|
+
}
|
|
323
|
+
};
|
|
300
324
|
|
|
301
325
|
// packages/core/src/project-deletion.ts
|
|
302
326
|
function authorizeProjectDeletion(actor, project, input) {
|
|
@@ -520,10 +544,11 @@ async function api(request, env, a) {
|
|
|
520
544
|
fail("FORBIDDEN");
|
|
521
545
|
writable(a);
|
|
522
546
|
const input = await jsonBody(request);
|
|
523
|
-
for (const field of ["name"
|
|
547
|
+
for (const field of ["name"])
|
|
524
548
|
if (typeof input[field] !== "string" || input[field].trim().length < 1 || input[field].length > 100)
|
|
525
549
|
fail("INVALID_INPUT");
|
|
526
|
-
const
|
|
550
|
+
const customerName = projectCustomer(input.customer);
|
|
551
|
+
const customer = await env.CATALOG.prepare("SELECT id FROM customers WHERE name=? COLLATE NOCASE LIMIT 1").bind(customerName).first();
|
|
527
552
|
const id = crypto.randomUUID(), customerId = customer?.id ?? crypto.randomUUID(), at = Date.now();
|
|
528
553
|
const alias = input.alias ? projectAlias(input.alias) : defaultAlias(input.name, id);
|
|
529
554
|
if (await env.CATALOG.prepare("SELECT projectId FROM project_aliases WHERE alias=?").bind(alias).first())
|
|
@@ -532,7 +557,7 @@ async function api(request, env, a) {
|
|
|
532
557
|
const result = await audit(env, a, id, null, "project.create", async () => {
|
|
533
558
|
await env.CATALOG.batch([
|
|
534
559
|
...customer ? [] : [
|
|
535
|
-
env.CATALOG.prepare("INSERT INTO customers VALUES (?,?,?)").bind(customerId,
|
|
560
|
+
env.CATALOG.prepare("INSERT INTO customers VALUES (?,?,?)").bind(customerId, customerName, at)
|
|
536
561
|
],
|
|
537
562
|
env.CATALOG.prepare("INSERT INTO projects VALUES (?,?,?,?)").bind(id, customerId, input.name.trim(), at),
|
|
538
563
|
env.CATALOG.prepare("INSERT INTO project_aliases (projectId,alias) VALUES (?,?)").bind(id, alias),
|
|
@@ -591,6 +616,19 @@ async function api(request, env, a) {
|
|
|
591
616
|
});
|
|
592
617
|
const which = environment(match[2]), scope = `${projectId}/${which}`, op = match[3] ?? "health";
|
|
593
618
|
if (request.method === "GET") {
|
|
619
|
+
if (op === "live") {
|
|
620
|
+
if (a.projectId || request.headers.get("origin") !== url.origin || request.headers.get("upgrade")?.toLowerCase() !== "websocket")
|
|
621
|
+
fail("FORBIDDEN");
|
|
622
|
+
return env.RUNTIME.fetch(new Request(`https://dashboard.internal/${scope}`, {
|
|
623
|
+
headers: {
|
|
624
|
+
upgrade: "websocket",
|
|
625
|
+
"x-flared-dashboard": JSON.stringify({
|
|
626
|
+
email: a.email,
|
|
627
|
+
expires: Date.now() + 600000
|
|
628
|
+
})
|
|
629
|
+
}
|
|
630
|
+
}));
|
|
631
|
+
}
|
|
594
632
|
if (["release", "release-bundle"].includes(op)) {
|
|
595
633
|
const hash = url.searchParams.get("hash");
|
|
596
634
|
if (!hash || !/^[a-f0-9]{64}$/.test(hash))
|
|
@@ -741,6 +779,9 @@ async function api(request, env, a) {
|
|
|
741
779
|
}
|
|
742
780
|
|
|
743
781
|
class AdminControl extends WorkerEntrypoint {
|
|
782
|
+
dashboardAllowed(scope, email) {
|
|
783
|
+
return dashboardAllowed(this.env.CATALOG, scope, email);
|
|
784
|
+
}
|
|
744
785
|
async readiness() {
|
|
745
786
|
const row = await this.env.CATALOG.prepare("SELECT installation_id FROM flare_installation WHERE singleton=1").first();
|
|
746
787
|
const owner = await this.env.CATALOG.prepare("SELECT email FROM members WHERE role='owner' LIMIT 1").first();
|
|
@@ -777,7 +818,7 @@ var admin_default = {
|
|
|
777
818
|
if (u.pathname.startsWith("/bootstrap/") || u.pathname.startsWith("/starter/"))
|
|
778
819
|
return new Response("Not found", { status: 404 });
|
|
779
820
|
const response = await env.ASSETS.fetch(request), headers = new Headers(response.headers);
|
|
780
|
-
headers.set("content-security-policy", "default-src 'self'; script-src 'self' " + { scripts: ["'sha256-xIb/Pa7FUshAu4ihwjchYcQpyCI8eS8kt40GpOAYCz8='", "'sha256-1BvDVWLr3QCC20uHhUDi27eDFVJpk1uMQJGIWKUks2I='", "'sha256-kmZE/W8I1iuTnl1b3yqzv9/DQZenPngd3NjSKMQl7y0='", "'sha256-Y8t0ntteBwH+PqGKUyjWW3CAk+BNJKSoQc0xZ68FjhM='", "'sha256-0O7iB6zr9x0r6X6eWYITC9V5q+yWCGAoFvcysBGbY9Q='", "'sha256-tQIkdAw2vqfQ1PjgUE4ML2A4pIV8SByQ1Seeehy6UIc='", "'sha256-4f86GpVBksx63uHEB4f15jGyhnVEOKjwqYxwnfDVOik='"], styles: ["'sha256-vv9IoKo7BSLbWcUHr3tNmfNVmm5L/9Cfn2H6LMk7/ow='", "'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='"] }.scripts.join(" ") + "; style-src 'self' " + { scripts: ["'sha256-xIb/Pa7FUshAu4ihwjchYcQpyCI8eS8kt40GpOAYCz8='", "'sha256-1BvDVWLr3QCC20uHhUDi27eDFVJpk1uMQJGIWKUks2I='", "'sha256-kmZE/W8I1iuTnl1b3yqzv9/DQZenPngd3NjSKMQl7y0='", "'sha256-Y8t0ntteBwH+PqGKUyjWW3CAk+BNJKSoQc0xZ68FjhM='", "'sha256-0O7iB6zr9x0r6X6eWYITC9V5q+yWCGAoFvcysBGbY9Q='", "'sha256-tQIkdAw2vqfQ1PjgUE4ML2A4pIV8SByQ1Seeehy6UIc='", "'sha256-4f86GpVBksx63uHEB4f15jGyhnVEOKjwqYxwnfDVOik='"], styles: ["'sha256-vv9IoKo7BSLbWcUHr3tNmfNVmm5L/9Cfn2H6LMk7/ow='", "'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='"] }.styles.join(" ") + "; connect-src 'self' " + env.RUNTIME_URL + " " + env.RUNTIME_URL.replace("https:", "wss:") + "; object-src 'none'; base-uri 'none'; frame-ancestors 'none'");
|
|
821
|
+
headers.set("content-security-policy", "default-src 'self'; script-src 'self' " + { scripts: ["'sha256-xIb/Pa7FUshAu4ihwjchYcQpyCI8eS8kt40GpOAYCz8='", "'sha256-1BvDVWLr3QCC20uHhUDi27eDFVJpk1uMQJGIWKUks2I='", "'sha256-kmZE/W8I1iuTnl1b3yqzv9/DQZenPngd3NjSKMQl7y0='", "'sha256-Y8t0ntteBwH+PqGKUyjWW3CAk+BNJKSoQc0xZ68FjhM='", "'sha256-0O7iB6zr9x0r6X6eWYITC9V5q+yWCGAoFvcysBGbY9Q='", "'sha256-tQIkdAw2vqfQ1PjgUE4ML2A4pIV8SByQ1Seeehy6UIc='", "'sha256-4f86GpVBksx63uHEB4f15jGyhnVEOKjwqYxwnfDVOik='"], styles: ["'sha256-vv9IoKo7BSLbWcUHr3tNmfNVmm5L/9Cfn2H6LMk7/ow='", "'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='"] }.scripts.join(" ") + "; style-src 'self' " + { scripts: ["'sha256-xIb/Pa7FUshAu4ihwjchYcQpyCI8eS8kt40GpOAYCz8='", "'sha256-1BvDVWLr3QCC20uHhUDi27eDFVJpk1uMQJGIWKUks2I='", "'sha256-kmZE/W8I1iuTnl1b3yqzv9/DQZenPngd3NjSKMQl7y0='", "'sha256-Y8t0ntteBwH+PqGKUyjWW3CAk+BNJKSoQc0xZ68FjhM='", "'sha256-0O7iB6zr9x0r6X6eWYITC9V5q+yWCGAoFvcysBGbY9Q='", "'sha256-tQIkdAw2vqfQ1PjgUE4ML2A4pIV8SByQ1Seeehy6UIc='", "'sha256-4f86GpVBksx63uHEB4f15jGyhnVEOKjwqYxwnfDVOik='"], styles: ["'sha256-vv9IoKo7BSLbWcUHr3tNmfNVmm5L/9Cfn2H6LMk7/ow='", "'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='"] }.styles.join(" ") + "; connect-src 'self' " + env.DASHBOARD_URL.replace("https:", "wss:") + " " + env.RUNTIME_URL + " " + env.RUNTIME_URL.replace("https:", "wss:") + "; object-src 'none'; base-uri 'none'; frame-ancestors 'none'");
|
|
781
822
|
headers.set("referrer-policy", "no-referrer");
|
|
782
823
|
headers.set("x-content-type-options", "nosniff");
|
|
783
824
|
headers.set("cache-control", response.ok && u.pathname.startsWith("/_astro/") ? "private, max-age=31536000, immutable" : "private, no-store");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,r as t}from"./client.BeKNsjPN.js";import{At as n,S as r,_ as i,a,b as o,f as s,g as c,n as l,p as u,pt as d,s as f,u as p,v as m,x as h,y as g}from"./common.TAClb6G9.js";import{o as _}from"./index.astro_astro_type_script_index_0_lang.Blg5hU9g.js";var v=e(t(),1),y=n();function b(e){let t=c(e.project,e.env),[n,b]=(0,v.useState)(``),[x,S]=(0,v.useState)(),C=p(t+`/auth-users`+(n?`?cursor=`+encodeURIComponent(n):``)),w=p(t+`/auth-settings`),T=e.session.role===`owner`,E=(n,r,i,a=[])=>{S({title:r,description:`Passwords are never displayed. Changes revoke existing sessions.`,productionId:e.env===`prod`?e.project.id:void 0,fields:a,submit:async r=>{try{await u(t+`/`+n,{...i,...s(a,e=>new FormData(r).get(e)),confirm:e.project.id})}catch(e){throw e instanceof Error&&e.message===`INVALID_PASSWORD`?Error(`Password must contain 6–128 characters. Spaces are allowed; the password is not trimmed.`):e instanceof Error&&e.message===`REGISTRATION_UNAVAILABLE`?Error(`This login is already registered. Choose another login or manage the existing user.`):e}C.refresh(),w.refresh()}})},D={name:`password`,type:`password`,required:!0,description:`6–128 characters.`};return(0,y.jsxs)(`div`,{className:`space-y-4 p-4`,children:[(0,y.jsx)(`h1`,{className:`page-title`,children:`Auth`}),(0,y.jsx)(f,{loading:C.loading||w.loading,error:C.error||w.error}),w.data&&(0,y.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,y.jsx)(_,{status:w.data.enabled?`Enabled`:`Disabled`}),(0,y.jsx)(_,{status:w.data.registration?`Public registration`:`Registration closed`}),T&&(0,y.jsx)(d,{variant:`outline`,onClick:()=>E(`auth-configure`,`Configure authentication`,{},[{name:`enabled`,type:`boolean`,value:w.data.enabled},{name:`registration`,type:`boolean`,value:w.data.registration}]),children:`Configure`}),T&&(0,y.jsx)(d,{onClick:()=>E(`auth-create`,`Create user`,{},[{name:`login`,type:`string`,required:!0},D]),children:`Create user`})]}),C.data&&(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(i,{children:[(0,y.jsx)(h,{children:(0,y.jsxs)(r,{children:[(0,y.jsx)(o,{children:`Login`}),(0,y.jsx)(o,{children:`User ID`}),(0,y.jsx)(o,{children:`Status`}),(0,y.jsx)(o,{children:`Created`}),(0,y.jsx)(o,{children:`Actions`})]})}),(0,y.jsx)(m,{children:C.data.users.map(e=>(0,y.jsxs)(r,{children:[(0,y.jsx)(g,{children:e.login}),(0,y.jsx)(g,{children:(0,y.jsx)(`code`,{children:e.id})}),(0,y.jsx)(g,{children:(0,y.jsx)(_,{status:e.disabled?`Blocked`:`Active`})}),(0,y.jsx)(g,{children:new Date(e.createdAt).toLocaleString()}),(0,y.jsx)(g,{children:(0,y.jsx)(`div`,{className:`flex flex-wrap gap-2`,children:T&&(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(d,{variant:`outline`,size:`sm`,onClick:()=>E(`auth-password`,`Change user password`,{id:e.id},[D]),children:`Change password`}),(0,y.jsx)(d,{variant:`outline`,size:`sm`,onClick:()=>E(`auth-revoke`,`Revoke all sessions`,{id:e.id}),children:`Revoke sessions`}),(0,y.jsx)(d,{variant:`outline`,size:`sm`,onClick:()=>E(`auth-disable`,e.disabled?`Unblock user`:`Block user`,{id:e.id,disabled:!e.disabled}),children:e.disabled?`Unblock`:`Block`})]})})})]},e.id))})]}),!C.data.users.length&&(0,y.jsx)(l,{title:`No users`}),(0,y.jsxs)(`div`,{className:`flex gap-2`,children:[(0,y.jsx)(d,{variant:`outline`,disabled:!n,onClick:()=>b(``),children:`First page`}),(0,y.jsx)(d,{variant:`outline`,disabled:!C.data.next,onClick:()=>b(C.data.next),children:`Next page`})]})]}),x&&(0,y.jsx)(a,{modal:x,onClose:()=>S(void 0)})]})}export{b as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,r as t}from"./client.BeKNsjPN.js";import{A as n,At as r,O as i,k as a,m as o,p as s,pt as c,q as l,r as u,t as d}from"./common.TAClb6G9.js";var f=e(t(),1),p=r();function m(){let e=new URLSearchParams(location.search).get(`cli`)??``,[t,r]=(0,f.useState)(``),[m,h]=(0,f.useState)([]),[g,_]=(0,f.useState)(`new`),[v,y]=(0,f.useState)(``),[b,x]=(0,f.useState)(``),[S,C]=(0,f.useState)(!1),[w,T]=(0,f.useState)(!1);(0,f.useEffect)(()=>{let t=new AbortController;return Promise.all([s(`cli/approval?id=`+encodeURIComponent(e),void 0,t.signal),s(`projects`,void 0,t.signal)]).then(([e,t])=>{r(e.code),h(t),_(`new`)}).catch(e=>{t.signal.aborted||x(o(e))}),()=>t.abort()},[e]);async function E(){C(!0),x(``);try{let t=g;if(t===`new`){let e=await s(`projects`,{name:v,customer:`Applications`,origins:[]});t=e.id,h(t=>[...t,{id:e.id,name:v,customer:`Applications`}]),_(t)}await s(`cli/approval`,{id:e,project:t}),T(!0)}catch(e){x(o(e))}finally{C(!1)}}return(0,p.jsxs)(`main`,{className:`mx-auto max-w-lg p-8`,children:[(0,p.jsx)(`h1`,{className:`mb-4 text-xl font-semibold`,children:`Connect Flare CLI`}),w?(0,p.jsx)(`p`,{children:`Connected. Return to your terminal. You can close this tab.`}):(0,p.jsxs)(a,{children:[(0,p.jsx)(`p`,{children:`Approve only a login you just started. Compare this code with your terminal:`}),(0,p.jsx)(`strong`,{className:`text-2xl font-mono`,children:t||`Loading…`}),(0,p.jsx)(`p`,{children:`Grants this computer deployment access to the selected project, including production. The credential expires in 30 days and can be revoked in Settings.`}),(0,p.jsxs)(i,{children:[(0,p.jsx)(n,{children:`Project`}),(0,p.jsx)(d,{label:`CLI project`,value:g,onChange:_,items:[...m.map(e=>({value:e.id,label:e.name})),{value:`new`,label:`Create new project`}]})]}),g===`new`&&(0,p.jsxs)(i,{children:[(0,p.jsx)(n,{children:`New project name`}),(0,p.jsx)(l,{value:v,onChange:e=>y(e.target.value),maxLength:100})]}),b&&(0,p.jsx)(u,{children:b}),(0,p.jsx)(c,{disabled:S||!t||!g||g===`new`&&!v.trim(),onClick:E,children:`Approve connection`})]})]})}export{m as default};
|