Skip to content

Commit 287167e

Browse files
committed
Complete Admin cleanup, R2 asset validation, GFSP package lifecycle, backup foundation, and Project Workspace real project flow - PR_26168_237-through-244-platform-foundation-stack
1 parent b10beb8 commit 287167e

24 files changed

Lines changed: 8015 additions & 2192 deletions

admin/infrastructure.html

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,6 @@ <h3 id="admin-infrastructure-image-zoom-title">Game Foundry Infrastructure</h3>
9090
</tbody>
9191
</table>
9292
</div>
93-
<div class="table-wrapper">
94-
<table class="data-table" aria-label="Admin storage connectivity results">
95-
<caption>Storage Connectivity Results</caption>
96-
<thead>
97-
<tr>
98-
<th scope="col">Action</th>
99-
<th scope="col">Status</th>
100-
<th scope="col">Executed</th>
101-
<th scope="col">Message</th>
102-
</tr>
103-
</thead>
104-
<tbody data-admin-storage-connectivity-result-rows>
105-
<tr><td>startup</td><td>SKIP</td><td>no</td><td>No storage connectivity action has been run.</td></tr>
106-
</tbody>
107-
</table>
108-
</div>
10993
</section>
11094
<aside class="tool-column tool-group-platform">
11195
<div class="tool-column-header">
@@ -125,17 +109,7 @@ <h2>Inspector</h2>
125109
<summary>Guardrails</summary>
126110
<div class="accordion-body content-stack">
127111
<p>Infrastructure status remains a read-only Admin reference.</p>
128-
<p>Storage connectivity tests run through the Local API and never expose secrets.</p>
129-
</div>
130-
</details>
131-
<details class="vertical-accordion" open>
132-
<summary>Storage Connectivity</summary>
133-
<div class="accordion-body content-stack">
134-
<button class="btn btn--compact" type="button" data-admin-storage-connectivity-action="storage-list">List</button>
135-
<button class="btn btn--compact" type="button" data-admin-storage-connectivity-action="storage-write-test-object">Write Test Object</button>
136-
<button class="btn btn--compact" type="button" data-admin-storage-connectivity-action="storage-read-test-object">Read Test Object</button>
137-
<button class="btn btn--compact" type="button" data-admin-storage-connectivity-action="storage-delete-test-object">Delete Test Object</button>
138-
<p class="status" role="status" data-admin-storage-connectivity-status>Storage connectivity not run.</p>
112+
<p>Storage connectivity tests run from Admin System Health and never expose secrets.</p>
139113
</div>
140114
</details>
141115
</div>

admin/operations.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ <h2>Admin Operations</h2>
3232
<details class="vertical-accordion" open>
3333
<summary>Project Packaging</summary>
3434
<div class="accordion-body content-stack">
35-
<p>Package actions are routed through the Local API. Full .gfsp execution returns visible not implemented diagnostics until package tooling is completed.</p>
36-
<p>Import Project Package requires explicit confirmation behavior before any overwrite can proceed.</p>
35+
<p>Package actions are routed through the Local API. Export, Validate, and Import use the server-owned .gfsp package contract.</p>
36+
<p>Import Project Package validates first and requires explicit confirmation before Replace Existing can proceed.</p>
3737
<div class="content-stack" data-admin-operation-group="project-packaging">
3838
<p class="status">Loading Project Packaging actions.</p>
3939
</div>
@@ -42,8 +42,8 @@ <h2>Admin Operations</h2>
4242
<details class="vertical-accordion" open>
4343
<summary>Backup &amp; Recovery</summary>
4444
<div class="accordion-body content-stack">
45-
<p>Backup and restore actions are staged for reviewed server-side execution.</p>
46-
<p>Restore From Backup is risky and requires explicit confirmation messaging before any restore behavior is implemented.</p>
45+
<p>Backup and restore actions use guarded Local API contracts.</p>
46+
<p>Restore From Backup reports the current environment lane and requires explicit RESTORE confirmation before runtime state is restored.</p>
4747
<div class="content-stack" data-admin-operation-group="backup-recovery">
4848
<p class="status">Loading Backup &amp; Recovery actions.</p>
4949
</div>

assets/theme-v2/js/admin-infrastructure.js

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
2-
readAdminInfrastructureStoragePathStatus,
3-
runAdminInfrastructureStorageConnectivityAction
2+
readAdminInfrastructureStoragePathStatus
43
} from "../../../src/engine/api/admin-infrastructure-api-client.js";
54

65
const STORAGE_PATH_LANES = Object.freeze([
@@ -13,46 +12,16 @@ const STORAGE_PATH_LANES = Object.freeze([
1312
class AdminInfrastructureStoragePathStatus {
1413
constructor(root) {
1514
this.root = root;
16-
this.connectivityActionButtons = Array.from(root.querySelectorAll("[data-admin-storage-connectivity-action]"));
17-
this.connectivityRows = root.querySelector("[data-admin-storage-connectivity-result-rows]");
18-
this.connectivityStatus = root.querySelector("[data-admin-storage-connectivity-status]");
1915
this.rows = root.querySelector("[data-admin-storage-path-status-rows]");
2016
}
2117

2218
init() {
2319
if (!this.rows) {
2420
return;
2521
}
26-
this.connectivityActionButtons.forEach((button) => {
27-
button.addEventListener("click", () => this.runStorageConnectivityAction(button.dataset.adminStorageConnectivityAction));
28-
});
2922
this.load();
3023
}
3124

32-
setConnectivityStatus(status, message) {
33-
if (this.connectivityStatus) {
34-
this.connectivityStatus.textContent = `${status}: ${message}`;
35-
}
36-
}
37-
38-
appendConnectivityResult(result = {}) {
39-
if (!this.connectivityRows) {
40-
return;
41-
}
42-
const row = document.createElement("tr");
43-
[
44-
result.actionId || "storage-connectivity",
45-
result.status || "WARN",
46-
result.executed === true ? "yes" : "no",
47-
result.message || "No storage connectivity message returned.",
48-
].forEach((value) => {
49-
const cell = document.createElement("td");
50-
cell.textContent = value;
51-
row.append(cell);
52-
});
53-
this.connectivityRows.prepend(row);
54-
}
55-
5625
renderRows(rows) {
5726
this.rows.replaceChildren();
5827
rows.forEach((row) => {
@@ -90,15 +59,6 @@ class AdminInfrastructureStoragePathStatus {
9059
}
9160
}
9261

93-
runStorageConnectivityAction(actionId) {
94-
try {
95-
const result = runAdminInfrastructureStorageConnectivityAction(actionId);
96-
this.appendConnectivityResult(result);
97-
this.setConnectivityStatus(result.status || "WARN", result.message || "Storage connectivity action returned no message.");
98-
} catch (error) {
99-
this.setConnectivityStatus("FAIL", error instanceof Error ? error.message : "Storage connectivity action failed.");
100-
}
101-
}
10262
}
10363

10464
document.addEventListener("DOMContentLoaded", () => {

assets/theme-v2/js/admin-operations.js

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,57 @@ class AdminOperationsController {
2828
this.status.textContent = `${status}: ${message}`;
2929
}
3030

31+
createLabeledControl(labelText, control) {
32+
const label = document.createElement("label");
33+
label.textContent = labelText;
34+
label.append(control);
35+
return label;
36+
}
37+
38+
renderActionInputs(container, action) {
39+
if (action.requiresPackageFile) {
40+
const input = document.createElement("input");
41+
input.accept = ".gfsp";
42+
input.dataset.adminOperationPackageFile = action.id || "";
43+
input.type = "file";
44+
container.append(this.createLabeledControl("Project Package", input));
45+
}
46+
if (action.requiresBackupFile) {
47+
const input = document.createElement("input");
48+
input.accept = ".json";
49+
input.dataset.adminOperationBackupFile = action.id || "";
50+
input.type = "file";
51+
container.append(this.createLabeledControl("Backup File", input));
52+
}
53+
if (action.supportsImportModes) {
54+
const select = document.createElement("select");
55+
select.dataset.adminOperationImportMode = action.id || "";
56+
[
57+
["import-as-new", "Import As New Project"],
58+
["replace-existing", "Replace Existing"],
59+
].forEach(([value, text]) => {
60+
const option = document.createElement("option");
61+
option.value = value;
62+
option.textContent = text;
63+
select.append(option);
64+
});
65+
container.append(this.createLabeledControl("Import Mode", select));
66+
}
67+
if (action.confirmationRequired) {
68+
const checkbox = document.createElement("input");
69+
checkbox.dataset.adminOperationConfirmation = action.id || "";
70+
checkbox.type = "checkbox";
71+
container.append(this.createLabeledControl("Confirm Risky Operation", checkbox));
72+
if (action.confirmationPhrase) {
73+
const phrase = document.createElement("input");
74+
phrase.dataset.adminOperationConfirmationPhrase = action.id || "";
75+
phrase.placeholder = action.confirmationPhrase;
76+
phrase.type = "text";
77+
container.append(this.createLabeledControl(`Type ${action.confirmationPhrase}`, phrase));
78+
}
79+
}
80+
}
81+
3182
renderActionGroups(actionGroups = []) {
3283
this.groupContainers.forEach((container, groupId) => {
3384
const group = actionGroups.find((candidate) => candidate.id === groupId);
@@ -40,12 +91,13 @@ class AdminOperationsController {
4091
return;
4192
}
4293
group.actions.forEach((action, index) => {
94+
this.renderActionInputs(container, action);
4395
const button = document.createElement("button");
4496
button.className = index === 0 ? "btn btn--compact primary" : "btn btn--compact";
4597
button.dataset.adminOperationAction = action.id || "";
4698
button.textContent = action.label || action.id || "Unnamed Action";
4799
button.type = "button";
48-
button.addEventListener("click", () => this.runAction(button.dataset.adminOperationAction));
100+
button.addEventListener("click", () => this.runAction(action));
49101
container.append(button);
50102
if (action.confirmationRequired || action.risky || action.notImplemented) {
51103
const warning = document.createElement("p");
@@ -94,6 +146,53 @@ class AdminOperationsController {
94146
this.resultRows.prepend(row);
95147
}
96148

149+
async readFileAsBase64(file) {
150+
const bytes = new Uint8Array(await file.arrayBuffer());
151+
const chunkSize = 32768;
152+
let binary = "";
153+
for (let index = 0; index < bytes.length; index += chunkSize) {
154+
binary += String.fromCharCode(...bytes.slice(index, index + chunkSize));
155+
}
156+
return btoa(binary);
157+
}
158+
159+
async collectActionOptions(action = {}) {
160+
const options = {};
161+
const packageFileInput = this.root.querySelector(`[data-admin-operation-package-file='${action.id}']`);
162+
const packageFile = packageFileInput?.files?.[0];
163+
if (packageFile) {
164+
options.packageFileName = packageFile.name;
165+
options.packageBytesBase64 = await this.readFileAsBase64(packageFile);
166+
}
167+
168+
const backupFileInput = this.root.querySelector(`[data-admin-operation-backup-file='${action.id}']`);
169+
const backupFile = backupFileInput?.files?.[0];
170+
if (backupFile) {
171+
options.backupFileName = backupFile.name;
172+
options.backupBytesBase64 = await this.readFileAsBase64(backupFile);
173+
}
174+
175+
const importMode = this.root.querySelector(`[data-admin-operation-import-mode='${action.id}']`);
176+
if (importMode) {
177+
options.importMode = importMode.value;
178+
}
179+
180+
const confirmation = this.root.querySelector(`[data-admin-operation-confirmation='${action.id}']`);
181+
if (confirmation) {
182+
if (action.id === "restore-from-backup") {
183+
options.restoreConfirmed = confirmation.checked;
184+
} else {
185+
options.overwriteConfirmed = confirmation.checked;
186+
}
187+
}
188+
189+
const phrase = this.root.querySelector(`[data-admin-operation-confirmation-phrase='${action.id}']`);
190+
if (phrase) {
191+
options.confirmationPhrase = phrase.value;
192+
}
193+
return options;
194+
}
195+
97196
load() {
98197
try {
99198
const payload = readAdminOperationsStatus();
@@ -107,9 +206,11 @@ class AdminOperationsController {
107206
}
108207
}
109208

110-
runAction(actionId) {
209+
async runAction(action = {}) {
111210
try {
112-
const result = runAdminOperationAction(actionId);
211+
this.setStatus("SKIP", `Running ${action.label || action.id || "Admin Operation"} through the Local API.`);
212+
const options = await this.collectActionOptions(action);
213+
const result = runAdminOperationAction(action.id, options);
113214
this.appendResult(result);
114215
this.setStatus(result.status || "SKIP", result.message || "Admin operation returned no message.");
115216
} catch (error) {

assets/theme-v2/js/gamefoundry-partials.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,11 @@
7575
owner: "admin/db-viewer.html",
7676
"admin-operations": "admin/operations.html",
7777
"admin-site-settings": "admin/site-settings.html",
78-
"admin-site-setup": "admin/site-setup.html",
7978
"admin-branding": "admin/branding.html",
8079
"admin-themes": "admin/themes.html",
8180
"admin-design-system": "admin/design-system.html",
8281
"admin-controls": "admin/controls.html",
8382
"admin-db-viewer": "admin/db-viewer.html",
84-
"admin-environments": "admin/environments.html",
85-
"admin-game-migration": "admin/game-migration.html",
8683
"admin-grouping-colors": "admin/grouping-colors.html",
8784
"admin-platform-settings": "admin/platform-settings.html",
8885
"admin-ratings": "admin/ratings.html",

docs_build/codex/decisions/project-packages.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@
66

77
Game Foundry Studio project packages use an internal ZIP-based package format. The `.gfsp` extension identifies the package as a Game Foundry Studio Project while preserving the operational advantages of a ZIP container for export, import, validation, and inspection.
88

9+
## Package Metadata Contract
10+
11+
The runtime package scaffold writes these required internal files:
12+
13+
- `metadata/package.json`
14+
- `project/project.json`
15+
- `assets/asset-references.json`
16+
17+
`metadata/package.json` owns the package identity and must include:
18+
19+
- `packageType`: `Game Foundry Studio Project`
20+
- `extension`: `.gfsp`
21+
- `contractVersion`
22+
- `filename`
23+
- `filenameFormat`
24+
- `createdAt`
25+
- `project.projectKey`
26+
- `project.name`
27+
- `requiredFiles`
28+
29+
`project/project.json` owns the exported Project Workspace project record. It must carry the API-owned project key, local record id, display name, owner key, and status.
30+
31+
`assets/asset-references.json` owns storage references only. Asset binary/object bytes stay in configured project asset storage and are referenced by storage object keys; package metadata must not include storage secrets or browser-owned product data.
32+
933
## Filename Format
1034

1135
Project package filenames use:
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# PR_26168_237-admin-ia-cleanup
2+
3+
## Branch Validation
4+
- PASS: current branch verified as `main`.
5+
- Expected branch: `main`.
6+
7+
## Summary
8+
- Removed duplicated storage-connectivity status/actions from `admin/infrastructure.html`.
9+
- Kept Admin ownership clear: Infrastructure is architecture/reference, System Health owns status/connectivity, Operations owns actions.
10+
- Removed obsolete Admin status route aliases for `admin-site-setup`, `admin-environments`, and `admin-game-migration` from the shared route resolver.
11+
12+
## Requirement Checklist
13+
- PASS: Remove/hide obsolete Admin/Owner status pages now replaced by System Health.
14+
- PASS: Verify navigation consistency; Admin menu still exposes Infrastructure, Operations, Platform Settings, System Health, Tool Votes, Users.
15+
- PASS: Remove duplicate health/status ownership from Infrastructure.
16+
- PASS: Keep PR222-236 behavior intact; System Health retains storage connectivity startup/manual actions.
17+
- PASS: No sample JSON or `start_of_day` changes.
18+
19+
## Validation Lane Report
20+
- PASS: `npx playwright test tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs ... --workers=1 --reporter=line` targeted Admin/IA/System Health/Operations subset, 12/12 combined targeted tests passed.
21+
- PASS: `node --check assets/theme-v2/js/admin-infrastructure.js`.
22+
- PASS: `node --check assets/theme-v2/js/gamefoundry-partials.js`.
23+
- PASS: static HTML guard found no inline scripts/styles/event handlers in touched pages.
24+
- PASS: `npm run validate:browser-env-agnostic`.
25+
- PASS/WARN: Playwright V8 coverage report generated at `docs_build/dev/reports/playwright_v8_coverage_report.txt`; server-side files report advisory WARN when not browser-collected.
26+
27+
## Manual Validation Notes
28+
- Infrastructure still displays only `assets/GFS-Infrastructure v1-3.png`.
29+
- Infrastructure path status remains visible for `/dev/projects/`, `/ist/projects/`, `/uat/projects/`, `/prd/projects/`.
30+
- Storage connectivity controls are visible on Admin System Health only.
31+
32+
## Full Samples Decision
33+
- SKIP: no sample JSON or sample launch/runtime surface was changed.

0 commit comments

Comments
 (0)