@auersperg/flare 0.3.7 → 0.3.9
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-0s0vrjtw.js} +213 -24
- package/dist/cli/flare.js +15 -4
- package/dist/platform/admin.js +80 -47
- package/dist/platform/dashboard/_astro/auth-view.EASEF9BN.js +1 -0
- package/dist/platform/dashboard/_astro/{cli-approval.C4-RBXQh.js → cli-approval.DwZVHdyo.js} +1 -1
- package/dist/platform/dashboard/_astro/common.TAClb6G9.js +1 -0
- package/dist/platform/dashboard/_astro/{data-view.B2afgdXn.js → data-view.BaHEU4Xa.js} +1 -1
- package/dist/platform/dashboard/_astro/{functions-view.B0jPBNNy.js → functions-view.BGOYuDwu.js} +1 -1
- package/dist/platform/dashboard/_astro/index.astro_astro_type_script_index_0_lang.Blg5hU9g.js +3 -0
- package/dist/platform/dashboard/_astro/{input-group.BziAwKdZ.js → input-group.DUp-h0xx.js} +1 -1
- package/dist/platform/dashboard/_astro/{sdk-view.DAd5aPbd.js → sdk-view.2ErPn4cO.js} +1 -1
- package/dist/platform/dashboard/_astro/{services-view.BvkS7FHr.js → services-view.iZujitxl.js} +1 -1
- 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 +33 -33
- package/dist/platform/runtime.js +214 -35
- package/package.json +1 -1
- package/dist/platform/dashboard/_astro/auth-view.DxyBDw7Z.js +0 -1
- package/dist/platform/dashboard/_astro/common.D6YHScsr.js +0 -1
- package/dist/platform/dashboard/_astro/index.astro_astro_type_script_index_0_lang.BQ7pqT75.js +0 -3
- package/dist/platform/dashboard/_astro/settings-view.BXt0dexJ.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,75 @@ 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
|
+
function runtimeReadinessFailures(data, state, release = state.installed) {
|
|
598
|
+
const expected = {
|
|
599
|
+
readiness: "control-plane-ready",
|
|
600
|
+
installationId: state.id,
|
|
601
|
+
platformPayload: release?.digest,
|
|
602
|
+
version: release?.version,
|
|
603
|
+
runtimeAbi: release?.runtimeAbi ?? 4,
|
|
604
|
+
protocol: release?.protocol ?? 1
|
|
605
|
+
};
|
|
606
|
+
return Object.keys(expected).filter((key) => expected[key] === undefined || data?.[key] !== expected[key]);
|
|
607
|
+
}
|
|
608
|
+
async function platformHealth(cf, state, fetcher) {
|
|
609
|
+
const checkedAt = Date.now();
|
|
610
|
+
const probe = async (fn) => {
|
|
611
|
+
try {
|
|
612
|
+
return await fn();
|
|
613
|
+
} catch {
|
|
614
|
+
return { status: "unavailable" };
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
const [runtime, dashboard, access] = await Promise.all([
|
|
618
|
+
probe(async () => {
|
|
619
|
+
const r = await fetcher(`https://${state.addresses.runtimeHost}/health?ready=1`, { redirect: "error", signal: AbortSignal.timeout(5000) });
|
|
620
|
+
if (!r.ok) {
|
|
621
|
+
await r.body?.cancel();
|
|
622
|
+
return { status: "unhealthy", httpStatus: r.status };
|
|
623
|
+
}
|
|
624
|
+
const d = await r.json();
|
|
625
|
+
const mismatches = runtimeReadinessFailures(d, state);
|
|
626
|
+
return {
|
|
627
|
+
status: mismatches.length ? "mismatch" : "healthy",
|
|
628
|
+
...mismatches.length ? { mismatches } : {},
|
|
629
|
+
version: d.version,
|
|
630
|
+
installationId: d.installationId
|
|
631
|
+
};
|
|
632
|
+
}),
|
|
633
|
+
probe(async () => {
|
|
634
|
+
const r = await fetcher(`https://${state.addresses.adminHost}/api/session`, { redirect: "manual", signal: AbortSignal.timeout(5000) });
|
|
635
|
+
const location = r.headers.get("location");
|
|
636
|
+
await r.body?.cancel();
|
|
637
|
+
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;
|
|
638
|
+
return {
|
|
639
|
+
status: closed ? "access-protected" : "not-verified-closed",
|
|
640
|
+
httpStatus: r.status,
|
|
641
|
+
applicationHealth: "not-checked-without-owner-session"
|
|
642
|
+
};
|
|
643
|
+
}),
|
|
644
|
+
probe(async () => {
|
|
645
|
+
if (!state.access)
|
|
646
|
+
return { status: "not-configured" };
|
|
647
|
+
const { app, digest } = await accessSnapshot(cf, state.access.id);
|
|
648
|
+
const host = app.domain === state.addresses.adminHost || app.destinations?.some((d) => d.type === "public" && d.uri === state.addresses.adminHost);
|
|
649
|
+
const matches = !state.access.snapshot || (state.access.snapshotFormat === 2 ? digest : configurationDigest(app)) === state.access.snapshot;
|
|
650
|
+
return {
|
|
651
|
+
status: host && app.aud === state.access.aud && matches ? "verified" : "drift",
|
|
652
|
+
snapshot: digest,
|
|
653
|
+
snapshotFormat: 2
|
|
654
|
+
};
|
|
655
|
+
})
|
|
656
|
+
]);
|
|
657
|
+
return {
|
|
658
|
+
status: state.operation ? "incomplete" : runtime.status === "healthy" && dashboard.status === "access-protected" && access.status === "verified" ? "ready" : "not-verified",
|
|
659
|
+
checkedAt,
|
|
660
|
+
runtime,
|
|
661
|
+
dashboard,
|
|
662
|
+
access
|
|
515
663
|
};
|
|
516
|
-
return sha256(JSON.stringify(canonical(value)));
|
|
517
664
|
}
|
|
518
665
|
|
|
519
666
|
// packages/flare/src/cli/platform.mjs
|
|
@@ -681,6 +828,7 @@ function workerConfiguration(state, release, role, bootstrap = false) {
|
|
|
681
828
|
return {
|
|
682
829
|
...common,
|
|
683
830
|
worker_loaders: [{ binding: "LOADER" }],
|
|
831
|
+
...state.config ? { secrets_store_secrets: validatePlatformConfig(state.config).secrets } : {},
|
|
684
832
|
durable_objects: {
|
|
685
833
|
bindings: [{ name: "SUPERVISORS", class_name: "EnvironmentSupervisor" }]
|
|
686
834
|
},
|
|
@@ -720,7 +868,10 @@ function workerConfiguration(state, release, role, bootstrap = false) {
|
|
|
720
868
|
],
|
|
721
869
|
vars: {
|
|
722
870
|
...common.vars,
|
|
723
|
-
DEV_TOKEN_ISSUER: "https://" + state.addresses.runtimeHost
|
|
871
|
+
DEV_TOKEN_ISSUER: "https://" + state.addresses.runtimeHost,
|
|
872
|
+
...state.config ? {
|
|
873
|
+
APP_CAPABILITIES: JSON.stringify(validatePlatformConfig(state.config).capabilities)
|
|
874
|
+
} : {}
|
|
724
875
|
}
|
|
725
876
|
};
|
|
726
877
|
}
|
|
@@ -731,10 +882,15 @@ async function confirm(options, state, plan, ask, log) {
|
|
|
731
882
|
throw Error("Account confirmation required; no cloud changes were made");
|
|
732
883
|
}
|
|
733
884
|
async function assertAccess(cf, state) {
|
|
734
|
-
const app = await cf
|
|
885
|
+
const { app, digest } = await accessSnapshot(cf, state.access.id);
|
|
735
886
|
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
|
|
887
|
+
if (!coversHost || app.aud !== state.access.aud)
|
|
888
|
+
throw Error("Access identity/host drift; explicit snapshot acceptance cannot change identities");
|
|
889
|
+
const expected = state.access.snapshot;
|
|
890
|
+
if (expected && (state.access.snapshotFormat === 2 ? digest : configurationDigest(app)) !== expected)
|
|
891
|
+
throw Error(`Access application/policy drift. Review Cloudflare settings, then explicitly accept snapshot ${digest} using --accept-access; no policies were changed.`);
|
|
892
|
+
state.access.snapshot = digest;
|
|
893
|
+
state.access.snapshotFormat = 2;
|
|
738
894
|
}
|
|
739
895
|
async function remoteWorker(cf, name) {
|
|
740
896
|
try {
|
|
@@ -880,19 +1036,29 @@ async function verify(cf, state, release, fetcher, wait) {
|
|
|
880
1036
|
await assertAccess(cf, state);
|
|
881
1037
|
await supervisorNamespace(cf, state);
|
|
882
1038
|
let healthy = false;
|
|
1039
|
+
let lastProbe = "unavailable";
|
|
883
1040
|
for (let attempt = 0;attempt < 5; attempt++) {
|
|
884
1041
|
try {
|
|
885
1042
|
const response = await fetcher(`https://${state.addresses.runtimeHost}/health?ready=1`, { redirect: "error", signal: AbortSignal.timeout(15000) });
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1043
|
+
lastProbe = `HTTP ${response.status}`;
|
|
1044
|
+
if (!response.ok)
|
|
1045
|
+
await response.body?.cancel();
|
|
1046
|
+
else {
|
|
1047
|
+
const failures = runtimeReadinessFailures(await response.json(), state, release.manifest);
|
|
1048
|
+
healthy = failures.length === 0;
|
|
1049
|
+
if (failures.length)
|
|
1050
|
+
lastProbe += "; mismatches: " + failures.join(",");
|
|
1051
|
+
}
|
|
1052
|
+
} catch {
|
|
1053
|
+
lastProbe = "network/timeout or invalid health response";
|
|
1054
|
+
}
|
|
889
1055
|
if (healthy)
|
|
890
1056
|
break;
|
|
891
1057
|
if (attempt < 4)
|
|
892
1058
|
await wait(2000);
|
|
893
1059
|
}
|
|
894
1060
|
if (!healthy)
|
|
895
|
-
throw Error("Runtime health does not match the installed platform payload; operation remains resumable");
|
|
1061
|
+
throw Error("Runtime health does not match the installed platform payload; operation remains resumable. Last probe: " + lastProbe);
|
|
896
1062
|
for (const path of ["/", "/api/session"]) {
|
|
897
1063
|
const response = await fetcher(`https://${state.addresses.adminHost}${path}`, { redirect: "manual", signal: AbortSignal.timeout(15000) });
|
|
898
1064
|
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
@@ -1140,12 +1306,16 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1140
1306
|
const discovered = adoption ? await discoverOperator(cf, store, name) : await store.discover();
|
|
1141
1307
|
let state = discovered.state;
|
|
1142
1308
|
if (operation === "status")
|
|
1143
|
-
return state ?
|
|
1309
|
+
return state ? {
|
|
1310
|
+
...publicState(state, release.manifest, discovered.row),
|
|
1311
|
+
health: await platformHealth(cf, state, dependencies.fetch ?? globalThis.fetch)
|
|
1312
|
+
} : {
|
|
1144
1313
|
account,
|
|
1145
1314
|
installation: name,
|
|
1146
1315
|
status: "not-installed",
|
|
1147
1316
|
target: release.manifest.version
|
|
1148
1317
|
};
|
|
1318
|
+
const requestedConfig = options.config ? await readPlatformConfig(options.config) : null;
|
|
1149
1319
|
const inventory = await preflight(cf);
|
|
1150
1320
|
if (adoption && !state)
|
|
1151
1321
|
state = await prepareOperator(cf, inventory, release, options, dependencies);
|
|
@@ -1164,6 +1334,20 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1164
1334
|
runtimeHost: options["runtime-host"]
|
|
1165
1335
|
});
|
|
1166
1336
|
}
|
|
1337
|
+
const configChanged = requestedConfig && configurationDigest(requestedConfig) !== configurationDigest(validatePlatformConfig(state.config));
|
|
1338
|
+
if (state.operation && requestedConfig && configurationDigest(requestedConfig) !== state.operation.configDigest)
|
|
1339
|
+
throw Error("Pending config differs; resume with the original config or omit --config");
|
|
1340
|
+
let accessChanged = false;
|
|
1341
|
+
if (options["accept-access"]) {
|
|
1342
|
+
if (!/^[a-f0-9]{64}$/.test(options["accept-access"]) || operation !== "update" || state.operation)
|
|
1343
|
+
throw Error("Access snapshot acceptance requires an idle managed update and exact SHA256");
|
|
1344
|
+
const actual = await accessSnapshot(cf, state.access.id);
|
|
1345
|
+
if (actual.digest !== options["accept-access"])
|
|
1346
|
+
throw Error("Access changed since review; read platform status and review the new snapshot");
|
|
1347
|
+
accessChanged = state.access.snapshotFormat !== 2 || state.access.snapshot !== actual.digest;
|
|
1348
|
+
state.access.snapshot = actual.digest;
|
|
1349
|
+
state.access.snapshotFormat = 2;
|
|
1350
|
+
}
|
|
1167
1351
|
if (state.operation && (state.operation.kind !== operation || state.operation.targetDigest !== release.manifest.digest))
|
|
1168
1352
|
throw Error("A different platform operation is incomplete. Resume it with the original CLI release before changing versions.");
|
|
1169
1353
|
if (state.installed) {
|
|
@@ -1172,7 +1356,7 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1172
1356
|
if (!state.operation) {
|
|
1173
1357
|
await assertResources(cf, state, inventory);
|
|
1174
1358
|
await assertNoDrift(cf, state);
|
|
1175
|
-
if (state.installed.digest === release.manifest.digest)
|
|
1359
|
+
if (state.installed.digest === release.manifest.digest && !configChanged && !accessChanged)
|
|
1176
1360
|
return { ...publicState(state, release.manifest), unchanged: true };
|
|
1177
1361
|
if (operation === "install" || adoption)
|
|
1178
1362
|
throw Error("Already installed. Use flare platform update to change the platform version");
|
|
@@ -1186,6 +1370,8 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1186
1370
|
from: state.installed?.version ?? state.adoptedFrom?.version ?? null,
|
|
1187
1371
|
to: release.manifest.version,
|
|
1188
1372
|
digest: release.manifest.digest,
|
|
1373
|
+
configuration: requestedConfig ?? state.config ?? null,
|
|
1374
|
+
acceptedAccessSnapshot: accessChanged ? state.access.snapshot : null,
|
|
1189
1375
|
dashboard: "https://" + state.addresses.adminHost,
|
|
1190
1376
|
runtime: "https://" + state.addresses.runtimeHost,
|
|
1191
1377
|
steps: state.installed || adoption ? ["migrations", "runtime", "admin", "verify"] : steps,
|
|
@@ -1208,10 +1394,13 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1208
1394
|
if (options["dry-run"])
|
|
1209
1395
|
return plan;
|
|
1210
1396
|
await confirm(options, state, plan, ask, log);
|
|
1397
|
+
if (!state.operation && requestedConfig)
|
|
1398
|
+
state.config = requestedConfig;
|
|
1211
1399
|
state.operation ??= {
|
|
1212
1400
|
id: randomUUID2(),
|
|
1213
1401
|
kind: operation,
|
|
1214
1402
|
targetDigest: release.manifest.digest,
|
|
1403
|
+
configDigest: configurationDigest(validatePlatformConfig(state.config)),
|
|
1215
1404
|
completed: [],
|
|
1216
1405
|
phase: "catalog",
|
|
1217
1406
|
previousVersions: { ...state.versions },
|
|
@@ -1235,7 +1424,7 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1235
1424
|
await assertResources(cf, state, inventory);
|
|
1236
1425
|
await assertNoDrift(cf, state);
|
|
1237
1426
|
}
|
|
1238
|
-
await store.initialize(state, await
|
|
1427
|
+
await store.initialize(state, await readFile4(join3(release.directory, "migrations/0006_platform_installation.sql"), "utf8"));
|
|
1239
1428
|
}
|
|
1240
1429
|
await store.acquire(state, options.resume === true);
|
|
1241
1430
|
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.9",
|
|
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-0s0vrjtw.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.7",
|
|
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,
|
|
@@ -305,6 +259,69 @@ function projectCustomer(value) {
|
|
|
305
259
|
return value.trim();
|
|
306
260
|
}
|
|
307
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.9",
|
|
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
|
+
};
|
|
324
|
+
|
|
308
325
|
// packages/core/src/project-deletion.ts
|
|
309
326
|
function authorizeProjectDeletion(actor, project, input) {
|
|
310
327
|
if (actor.role !== "owner" || actor.projectId)
|
|
@@ -599,6 +616,19 @@ async function api(request, env, a) {
|
|
|
599
616
|
});
|
|
600
617
|
const which = environment(match[2]), scope = `${projectId}/${which}`, op = match[3] ?? "health";
|
|
601
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
|
+
}
|
|
602
632
|
if (["release", "release-bundle"].includes(op)) {
|
|
603
633
|
const hash = url.searchParams.get("hash");
|
|
604
634
|
if (!hash || !/^[a-f0-9]{64}$/.test(hash))
|
|
@@ -749,6 +779,9 @@ async function api(request, env, a) {
|
|
|
749
779
|
}
|
|
750
780
|
|
|
751
781
|
class AdminControl extends WorkerEntrypoint {
|
|
782
|
+
dashboardAllowed(scope, email) {
|
|
783
|
+
return dashboardAllowed(this.env.CATALOG, scope, email);
|
|
784
|
+
}
|
|
752
785
|
async readiness() {
|
|
753
786
|
const row = await this.env.CATALOG.prepare("SELECT installation_id FROM flare_installation WHERE singleton=1").first();
|
|
754
787
|
const owner = await this.env.CATALOG.prepare("SELECT email FROM members WHERE role='owner' LIMIT 1").first();
|
|
@@ -785,7 +818,7 @@ var admin_default = {
|
|
|
785
818
|
if (u.pathname.startsWith("/bootstrap/") || u.pathname.startsWith("/starter/"))
|
|
786
819
|
return new Response("Not found", { status: 404 });
|
|
787
820
|
const response = await env.ASSETS.fetch(request), headers = new Headers(response.headers);
|
|
788
|
-
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'");
|
|
789
822
|
headers.set("referrer-policy", "no-referrer");
|
|
790
823
|
headers.set("x-content-type-options", "nosniff");
|
|
791
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};
|
package/dist/platform/dashboard/_astro/{cli-approval.C4-RBXQh.js → cli-approval.DwZVHdyo.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,r as t}from"./client.BeKNsjPN.js";import{A as n,
|
|
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};
|