@underpostnet/cyberia 3.2.80 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +50 -15
- package/.github/workflows/coverall.cyberia.ci.yml +87 -0
- package/.github/workflows/cyberia-client.cd.yml +2 -7
- package/.github/workflows/cyberia-server.cd.yml +2 -7
- package/.github/workflows/docker-image.cyberia-client.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-server.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +4 -4
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +3 -3
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +3 -3
- package/.github/workflows/engine-cyberia.cd.yml +3 -67
- package/.github/workflows/ghpkg.ci.yml +7 -1
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -9
- package/CHANGELOG.md +291 -1
- package/CLI-HELP.md +174 -23
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +1 -1
- package/bin/build.js +7 -5
- package/bin/cyberia.js +272 -98
- package/bin/deploy.js +19 -17
- package/bin/index.js +272 -98
- package/compose.env +50 -15
- package/conf.js +2 -0
- package/deploy/dd-cyberia/deploy.sh +42 -0
- package/deploy/dd-cyberia/init.sh +63 -0
- package/deploy/dd-cyberia/sync-deploy.sh +194 -0
- package/deploy/lib/logging.sh +96 -0
- package/deploy/pwa-microservices-template/deploy.sh +72 -0
- package/deploy/release/deploy.sh +62 -0
- package/deployment.yaml +1 -210
- package/docker-compose.yml +90 -85
- package/hardhat/package-lock.json +139 -131
- package/hardhat/package.json +4 -4
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +52 -0
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -210
- package/manifests/deployment/dd-cyberia-development/gateway.yaml +80 -0
- package/manifests/deployment/dd-cyberia-development/httproute.yaml +504 -0
- package/manifests/deployment/dd-cyberia-development/proxy.yaml +12 -12
- package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +0 -82
- package/manifests/deployment/dd-cyberia-development/traffic-service.yaml +121 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/playwright/deployment.yaml +1 -1
- package/manifests/mongodb/kustomization.yaml +4 -1
- package/manifests/mongodb/statefulset.yaml +4 -0
- package/manifests/mongodb/storage-class.yaml +9 -2
- package/nginx.conf +86 -16
- package/package.json +2 -2
- package/proxy.yaml +12 -12
- package/pv-pvc.yaml +0 -82
- package/scripts/audit-selinux.sh +64 -0
- package/scripts/coverall-test-cyberia.sh +24 -0
- package/scripts/coverall-test.sh +24 -0
- package/scripts/gpu-diag.sh +0 -0
- package/scripts/ip-info.sh +0 -0
- package/scripts/k3s-node-setup.sh +18 -15
- package/scripts/kubeadm-node-setup.sh +12 -23
- package/scripts/link-local-underpost-cli.sh +0 -0
- package/scripts/lxd-vm-setup.sh +0 -0
- package/scripts/maas-nat-firewalld.sh +0 -0
- package/scripts/nat-iptables.sh +12 -4
- package/scripts/rhel-grpc-setup.sh +0 -0
- package/scripts/rocky-kickstart.sh +25 -9
- package/scripts/test-monitor.sh +4 -3
- package/src/api/cyberia-action/cyberia-action.model.js +1 -0
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +49 -15
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +8 -12
- package/src/api/cyberia-instance/cyberia-instance.controller.js +3 -0
- package/src/api/cyberia-instance/cyberia-instance.router.js +13 -0
- package/src/api/cyberia-instance/cyberia-instance.service.js +43 -0
- package/src/api/cyberia-instance/cyberia-portal-connector.js +7 -5
- package/src/api/cyberia-instance/cyberia-random-source.js +80 -0
- package/src/api/cyberia-instance/cyberia-world-generator.js +4 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +87 -1
- package/src/api/object-layer/object-layer.model.js +204 -3
- package/src/api/object-layer/object-layer.service.js +1 -14
- package/src/cli/baremetal.js +1 -2
- package/src/cli/cloud-init.js +1 -1
- package/src/cli/cluster.js +786 -96
- package/src/cli/db.js +11 -4
- package/src/cli/deploy.js +1698 -177
- package/src/cli/docker-compose.js +19 -178
- package/src/cli/env.js +1 -1
- package/src/cli/image.js +15 -7
- package/src/cli/index.js +245 -44
- package/src/cli/ipfs.js +82 -11
- package/src/cli/lxd.js +1 -1
- package/src/cli/monitor.js +2 -2
- package/src/cli/release.js +57 -22
- package/src/cli/repository.js +12 -10
- package/src/cli/run.js +2195 -427
- package/src/cli/secrets.js +969 -0
- package/src/cli/ssh.js +206 -105
- package/src/cli/system.js +26 -13
- package/src/cli/test.js +1 -1
- package/src/cli/vultr.js +583 -0
- package/src/cli/wireguard.js +2125 -0
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
- package/src/client/components/cyberia/InstanceSelectionView.js +11 -8
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +5 -0
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +33 -0
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +106 -39
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +35 -3
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +44 -7
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
- package/src/client/services/object-layer/object-layer.management.js +4 -4
- package/src/client-builder/client-build.js +102 -13
- package/src/client-builder/ssr.js +27 -73
- package/src/db/mongo/MongoBootstrap.js +295 -54
- package/src/db/mongo/MongooseDB.js +51 -32
- package/src/index.js +25 -1
- package/src/projects/cyberia/besu-genesis-generator.js +3 -2
- package/src/projects/cyberia/catalog-cyberia.js +5 -0
- package/src/projects/cyberia/generate-saga.js +14 -23
- package/src/projects/cyberia/hot-reload-trigger.js +3 -3
- package/src/projects/cyberia/instance-data.js +63 -3
- package/src/projects/cyberia/object-layer.js +11 -21
- package/src/projects/underpost/catalog-underpost.js +4 -1
- package/src/runtime/cyberia-client/Dockerfile +1 -1
- package/src/runtime/cyberia-client/Dockerfile.dev +1 -1
- package/src/runtime/cyberia-server/Dockerfile +1 -1
- package/src/runtime/cyberia-server/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
- package/src/runtime/engine-cyberia/compose.env +50 -15
- package/src/runtime/engine-cyberia/docker-compose.yml +90 -85
- package/src/runtime/engine-cyberia/nginx.conf +86 -16
- package/src/server/backup.js +1 -1
- package/src/server/conf.js +1216 -168
- package/src/server/cri.js +70 -0
- package/src/server/cron.js +249 -51
- package/src/server/dns.js +100 -6
- package/src/server/environment.js +98 -0
- package/src/server/forward-proxy.js +549 -0
- package/src/server/middlewares.js +56 -1
- package/src/server/process.js +0 -1
- package/src/server/selinux.js +185 -0
- package/src/server/systemd.js +205 -0
- package/src/server/underpost-compression.js +186 -0
- package/src/server/underpost-gateway.js +1083 -0
- package/src/server/underpost-ingress.js +380 -0
- package/test/cluster-instances.test.js +435 -0
- package/test/cyberia-instance-conf-defaults.test.js +1 -0
- package/test/deploy-node-placement.test.js +45 -0
- package/test/instance-traffic-plan.test.js +710 -0
- package/test/selinux.test.js +71 -0
- package/test/sops-secret-store.test.js +612 -0
- package/test/underpost-gateway.test.js +510 -0
- package/test/underpost-ingress.test.js +305 -0
- package/test/wireguard-edge.test.js +1177 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback World Engine — editor for the procedural fallback world's default items.
|
|
3
|
+
*
|
|
4
|
+
* The fallback world (src/api/cyberia-instance/cyberia-fallback-world.js) is
|
|
5
|
+
* generated in memory and never persisted, so it has no CyberiaInstance document
|
|
6
|
+
* to edit. This view is the trimmed-down counterpart of InstanceEngineCyberia:
|
|
7
|
+
* only the default-items section plus a reload trigger.
|
|
8
|
+
*
|
|
9
|
+
* The composed item set is staged on the engine process and shipped with the
|
|
10
|
+
* reload trigger — it is deliberately volatile (see cyberia-fallback-default-items.js),
|
|
11
|
+
* so an engine restart returns the world to pure code defaults.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { BtnIcon } from '../core/BtnIcon.js';
|
|
15
|
+
import { Input } from '../core/Input.js';
|
|
16
|
+
import { htmls, s } from '../core/VanillaJs.js';
|
|
17
|
+
import { commonModeratorGuard } from '../core/CommonJs.js';
|
|
18
|
+
import { NotificationManager } from '../core/NotificationManager.js';
|
|
19
|
+
import { DropDown } from '../core/DropDown.js';
|
|
20
|
+
import { CyberiaInstanceService } from '../../services/cyberia-instance/cyberia-instance.service.js';
|
|
21
|
+
import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
|
|
22
|
+
import { DefaultCyberiaItems } from './SharedDefaultsCyberia.js';
|
|
23
|
+
|
|
24
|
+
const dropdownValueKey = (value = '') => String(value).trim().replaceAll(' ', '-');
|
|
25
|
+
const createDropdownOption = (value, onClick = () => {}, display = value, data = value) => ({
|
|
26
|
+
value,
|
|
27
|
+
display,
|
|
28
|
+
data,
|
|
29
|
+
onClick,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
class FallbackWorldEngineCyberia {
|
|
33
|
+
static itemIdsDropdownId = 'fallback-world-engine-item-ids-dropdown';
|
|
34
|
+
static itemInventoryListId = 'fallback-world-engine-item-inventory-list';
|
|
35
|
+
// itemId → defaultPlayerInventory flag for the currently selected item ids.
|
|
36
|
+
static itemInventoryFlags = {};
|
|
37
|
+
// Ids the code defaults already ship in an entity's defaultObjectLayers.
|
|
38
|
+
// Selecting one and leaving it unchecked REMOVES it from those defaults, so
|
|
39
|
+
// the list flags them instead of letting a player lose their starting kit.
|
|
40
|
+
static entityDefaultItemIds = [];
|
|
41
|
+
|
|
42
|
+
// Current selection, read from the dropdown's `oncheckvalues` (always live —
|
|
43
|
+
// it is updated before `_renderSelectedBadges` runs, unlike `.value` which
|
|
44
|
+
// lags by one in the option-click path).
|
|
45
|
+
static getSelectedItemIds() {
|
|
46
|
+
const token = DropDown.Tokens[FallbackWorldEngineCyberia.itemIdsDropdownId];
|
|
47
|
+
if (!token) return [];
|
|
48
|
+
const fromChecks = Object.values(token.oncheckvalues || {})
|
|
49
|
+
.map((v) => v.data)
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
if (fromChecks.length > 0) return fromChecks;
|
|
52
|
+
return Array.isArray(token.value) ? token.value.filter(Boolean) : [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Payload shape shared with CyberiaInstance.itemIds. */
|
|
56
|
+
static getItemIdsPayload() {
|
|
57
|
+
return FallbackWorldEngineCyberia.getSelectedItemIds().map((id) => ({
|
|
58
|
+
id,
|
|
59
|
+
defaultPlayerInventory: !!FallbackWorldEngineCyberia.itemInventoryFlags[id],
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Render the per-item "Default Player Inventory" toggles for every selected
|
|
64
|
+
// item id. Kept in sync with the dropdown via a MutationObserver on its badge
|
|
65
|
+
// container (see render()).
|
|
66
|
+
static renderItemInventoryList(containerId = FallbackWorldEngineCyberia.itemInventoryListId) {
|
|
67
|
+
const container = s(`.${containerId}`);
|
|
68
|
+
if (!container) return;
|
|
69
|
+
|
|
70
|
+
const selected = FallbackWorldEngineCyberia.getSelectedItemIds();
|
|
71
|
+
|
|
72
|
+
// Drop flags for items no longer selected so the payload never carries them.
|
|
73
|
+
for (const key of Object.keys(FallbackWorldEngineCyberia.itemInventoryFlags)) {
|
|
74
|
+
if (!selected.includes(key)) delete FallbackWorldEngineCyberia.itemInventoryFlags[key];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (selected.length === 0) {
|
|
78
|
+
htmls(
|
|
79
|
+
`.${containerId}`,
|
|
80
|
+
html`<div style="color:#888;font-size:12px;">
|
|
81
|
+
No item IDs selected — the fallback world runs on pure code defaults.
|
|
82
|
+
</div>`,
|
|
83
|
+
);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let listHtml = '';
|
|
88
|
+
for (const itemId of selected) {
|
|
89
|
+
const isChecked = !!FallbackWorldEngineCyberia.itemInventoryFlags[itemId];
|
|
90
|
+
const checked = isChecked ? 'checked' : '';
|
|
91
|
+
const strips = !isChecked && FallbackWorldEngineCyberia.entityDefaultItemIds.includes(itemId);
|
|
92
|
+
listHtml += html`<div class="fl" style="border-bottom:1px solid #444;padding:4px 0;align-items:center;">
|
|
93
|
+
<div class="in fll" style="flex:1;font-size:12px;font-family:monospace;">
|
|
94
|
+
${itemId}
|
|
95
|
+
${strips
|
|
96
|
+
? html`<span
|
|
97
|
+
style="color:#d90;font-size:11px;font-family:initial;margin-left:6px;"
|
|
98
|
+
title="This id is part of the code entity defaults. Left unchecked, it is removed from them."
|
|
99
|
+
><i class="fa-solid fa-triangle-exclamation"></i> removes from entity defaults</span
|
|
100
|
+
>`
|
|
101
|
+
: ''}
|
|
102
|
+
</div>
|
|
103
|
+
<div class="in fll" style="display:flex;align-items:center;justify-content:flex-end;">
|
|
104
|
+
<label style="font-size:11px;cursor:pointer;display:flex;align-items:center;gap:5px;">
|
|
105
|
+
<input
|
|
106
|
+
type="checkbox"
|
|
107
|
+
class="fallback-world-engine-item-inv-checkbox"
|
|
108
|
+
data-item-id="${itemId}"
|
|
109
|
+
${checked}
|
|
110
|
+
style="cursor:pointer;"
|
|
111
|
+
/>
|
|
112
|
+
Default Player Inventory
|
|
113
|
+
</label>
|
|
114
|
+
</div>
|
|
115
|
+
</div>`;
|
|
116
|
+
}
|
|
117
|
+
htmls(`.${containerId}`, listHtml);
|
|
118
|
+
|
|
119
|
+
container.querySelectorAll('.fallback-world-engine-item-inv-checkbox').forEach((cb) => {
|
|
120
|
+
cb.onchange = () => {
|
|
121
|
+
FallbackWorldEngineCyberia.itemInventoryFlags[cb.dataset.itemId] = cb.checked;
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static syncItemIdsDropdownSelection(itemIds = []) {
|
|
127
|
+
const dropdownId = FallbackWorldEngineCyberia.itemIdsDropdownId;
|
|
128
|
+
if (!DropDown.Tokens[dropdownId]) return;
|
|
129
|
+
|
|
130
|
+
// Accept both the `[{ id, defaultPlayerInventory }]` shape and a plain
|
|
131
|
+
// string[], matching what the fallback-world endpoint may return.
|
|
132
|
+
const normalized = (itemIds || [])
|
|
133
|
+
.map((entry) => (typeof entry === 'string' ? { id: entry, defaultPlayerInventory: false } : entry))
|
|
134
|
+
.filter((entry) => entry && entry.id);
|
|
135
|
+
|
|
136
|
+
DropDown.Tokens[dropdownId].value = [];
|
|
137
|
+
if (s(`.${dropdownId}`)) s(`.${dropdownId}`).value = [];
|
|
138
|
+
DropDown.Tokens[dropdownId].oncheckvalues = {};
|
|
139
|
+
htmls(`.dropdown-current-${dropdownId}`, '');
|
|
140
|
+
htmls(`.${dropdownId}-render-container`, '');
|
|
141
|
+
|
|
142
|
+
FallbackWorldEngineCyberia.itemInventoryFlags = {};
|
|
143
|
+
const ids = [];
|
|
144
|
+
for (const entry of normalized) {
|
|
145
|
+
const key = dropdownValueKey(entry.id);
|
|
146
|
+
DropDown.Tokens[dropdownId].oncheckvalues[key] = {
|
|
147
|
+
data: entry.id,
|
|
148
|
+
display: entry.id,
|
|
149
|
+
value: entry.id,
|
|
150
|
+
};
|
|
151
|
+
FallbackWorldEngineCyberia.itemInventoryFlags[entry.id] = !!entry.defaultPlayerInventory;
|
|
152
|
+
ids.push(entry.id);
|
|
153
|
+
}
|
|
154
|
+
DropDown.Tokens[dropdownId].value = [...ids];
|
|
155
|
+
if (s(`.${dropdownId}`)) s(`.${dropdownId}`).value = [...ids];
|
|
156
|
+
DropDown.Tokens[dropdownId]._renderSelectedBadges?.();
|
|
157
|
+
FallbackWorldEngineCyberia.renderItemInventoryList();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static async buildItemIdsDropdown() {
|
|
161
|
+
return await DropDown.instance({
|
|
162
|
+
id: FallbackWorldEngineCyberia.itemIdsDropdownId,
|
|
163
|
+
label: html`Object Layer Item IDs`,
|
|
164
|
+
data: [],
|
|
165
|
+
type: 'checkbox',
|
|
166
|
+
containerClass: 'inl',
|
|
167
|
+
excludeSelected: true,
|
|
168
|
+
serviceProvider: async (q) => {
|
|
169
|
+
const result = await ObjectLayerService.searchItemIds({ q });
|
|
170
|
+
if (result.status === 'success' && result.data?.itemIds) {
|
|
171
|
+
return result.data.itemIds.map((itemId) => createDropdownOption(itemId));
|
|
172
|
+
}
|
|
173
|
+
return [];
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static async render(options = {}) {
|
|
179
|
+
const { appStore } = options;
|
|
180
|
+
const role = appStore?.Data?.user?.main?.model?.user?.role || 'guest';
|
|
181
|
+
const canMutate = commonModeratorGuard(role);
|
|
182
|
+
const idHotReloadUrl = 'fallback-world-engine-input-hot-reload-url';
|
|
183
|
+
const idItemIdsDropdown = FallbackWorldEngineCyberia.itemIdsDropdownId;
|
|
184
|
+
const idItemInventoryList = FallbackWorldEngineCyberia.itemInventoryListId;
|
|
185
|
+
|
|
186
|
+
setTimeout(async () => {
|
|
187
|
+
// Keep the per-item "Default Player Inventory" toggles in sync with the
|
|
188
|
+
// item-ids dropdown. Every add/remove/clear re-renders the dropdown badge
|
|
189
|
+
// container, so observing it is a reliable change hook (the dropdown
|
|
190
|
+
// exposes no onChange callback).
|
|
191
|
+
const itemBadgeContainer = s(`.dropdown-current-${idItemIdsDropdown}`);
|
|
192
|
+
if (itemBadgeContainer) {
|
|
193
|
+
const itemInventoryObserver = new MutationObserver(() => {
|
|
194
|
+
FallbackWorldEngineCyberia.renderItemInventoryList();
|
|
195
|
+
});
|
|
196
|
+
itemInventoryObserver.observe(itemBadgeContainer, { childList: true, subtree: true });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Load whatever this engine process currently serves, so reopening the
|
|
200
|
+
// view shows the live set rather than an empty form.
|
|
201
|
+
try {
|
|
202
|
+
const result = await CyberiaInstanceService.getFallbackDefaultItems();
|
|
203
|
+
if (result?.status === 'success') {
|
|
204
|
+
FallbackWorldEngineCyberia.entityDefaultItemIds = result.data?.entityDefaultItemIds || [];
|
|
205
|
+
FallbackWorldEngineCyberia.syncItemIdsDropdownSelection(result.data?.itemIds || []);
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
NotificationManager.Push({ html: error.message, status: 'error' });
|
|
209
|
+
}
|
|
210
|
+
FallbackWorldEngineCyberia.renderItemInventoryList();
|
|
211
|
+
|
|
212
|
+
if (s(`.btn-fallback-world-engine-load-defaults`))
|
|
213
|
+
s(`.btn-fallback-world-engine-load-defaults`).onclick = () => {
|
|
214
|
+
// Seed from the canonical registry so an operator starts from the
|
|
215
|
+
// same list `import-default-items` creates ObjectLayers for. Ids the
|
|
216
|
+
// entity defaults already ship start checked: selecting them
|
|
217
|
+
// unchecked would strip them from those defaults, which is never what
|
|
218
|
+
// "load the defaults" is asking for.
|
|
219
|
+
const flags = Object.fromEntries(
|
|
220
|
+
FallbackWorldEngineCyberia.getItemIdsPayload().map((e) => [e.id, e.defaultPlayerInventory]),
|
|
221
|
+
);
|
|
222
|
+
FallbackWorldEngineCyberia.syncItemIdsDropdownSelection(
|
|
223
|
+
DefaultCyberiaItems.map((entry) => ({
|
|
224
|
+
id: entry.item.id,
|
|
225
|
+
defaultPlayerInventory:
|
|
226
|
+
flags[entry.item.id] ?? FallbackWorldEngineCyberia.entityDefaultItemIds.includes(entry.item.id),
|
|
227
|
+
})),
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
if (s(`.btn-fallback-world-engine-clear`))
|
|
232
|
+
s(`.btn-fallback-world-engine-clear`).onclick = () => {
|
|
233
|
+
FallbackWorldEngineCyberia.syncItemIdsDropdownSelection([]);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (s(`.btn-fallback-world-engine-toggle-hot-reload`))
|
|
237
|
+
s(`.btn-fallback-world-engine-toggle-hot-reload`).onclick = () => {
|
|
238
|
+
const body = s(`.fallback-world-engine-hot-reload-body`);
|
|
239
|
+
const caret = s(`.fallback-world-engine-hot-reload-caret`);
|
|
240
|
+
if (body) body.classList.toggle('hide');
|
|
241
|
+
if (caret) {
|
|
242
|
+
caret.classList.toggle('fa-caret-right');
|
|
243
|
+
caret.classList.toggle('fa-caret-down');
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const triggerHotReload = async (mode) => {
|
|
248
|
+
const buttons = [
|
|
249
|
+
s(`.btn-fallback-world-engine-hot-reload`),
|
|
250
|
+
s(`.btn-fallback-world-engine-hot-reload-incremental`),
|
|
251
|
+
];
|
|
252
|
+
const statusSelector = `.fallback-world-engine-hot-reload-status`;
|
|
253
|
+
const serverUrl = s(`.${idHotReloadUrl}`)?.value?.trim();
|
|
254
|
+
if (!serverUrl) {
|
|
255
|
+
NotificationManager.Push({ html: 'Set the cyberia-server URL first.', status: 'warning' });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (const btn of buttons) if (btn) btn.disabled = true;
|
|
260
|
+
if (s(statusSelector))
|
|
261
|
+
htmls(statusSelector, html`<span style="color:#888;">Triggering ${mode} hot reload…</span>`);
|
|
262
|
+
try {
|
|
263
|
+
const result = await CyberiaInstanceService.fallbackHotReload({
|
|
264
|
+
body: { serverUrl, mode, itemIds: FallbackWorldEngineCyberia.getItemIdsPayload() },
|
|
265
|
+
});
|
|
266
|
+
if (result.status === 'error') {
|
|
267
|
+
NotificationManager.Push({ html: result.message, status: 'error' });
|
|
268
|
+
if (s(statusSelector)) htmls(statusSelector, html`<span style="color:#d66;">${result.message}</span>`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const { transport, durationMs, message, grpcError, itemIds } = result.data || {};
|
|
272
|
+
NotificationManager.Push({ html: `Hot reload via ${transport}: ${message}`, status: 'success' });
|
|
273
|
+
if (s(statusSelector))
|
|
274
|
+
htmls(
|
|
275
|
+
statusSelector,
|
|
276
|
+
html`<span style="color:#6b6;">
|
|
277
|
+
via <b>${transport}</b> · ${durationMs}ms · ${(itemIds || []).length} default item(s) staged ·
|
|
278
|
+
${message} </span
|
|
279
|
+
>${grpcError ? html`<div style="color:#a80;">gRPC unavailable: ${grpcError}</div>` : ''}`,
|
|
280
|
+
);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
NotificationManager.Push({ html: error.message, status: 'error' });
|
|
283
|
+
if (s(statusSelector)) htmls(statusSelector, html`<span style="color:#d66;">${error.message}</span>`);
|
|
284
|
+
} finally {
|
|
285
|
+
for (const btn of buttons) if (btn) btn.disabled = false;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
if (s(`.btn-fallback-world-engine-hot-reload`))
|
|
290
|
+
s(`.btn-fallback-world-engine-hot-reload`).onclick = () => triggerHotReload('full');
|
|
291
|
+
if (s(`.btn-fallback-world-engine-hot-reload-incremental`))
|
|
292
|
+
s(`.btn-fallback-world-engine-hot-reload-incremental`).onclick = () => triggerHotReload('incremental');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return html`
|
|
296
|
+
<div class="in fallback-world-engine-container">
|
|
297
|
+
<div class="in section-mp" style="color:#888;font-size:12px;">
|
|
298
|
+
The fallback world is generated in memory and never persisted, so it has no instance document to edit. Items
|
|
299
|
+
selected here are staged on this engine process and applied when a cyberia-server reloads. They are volatile:
|
|
300
|
+
an engine restart returns the world to its code defaults.
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<div class="in section-mp" style="margin-top: 10px;">
|
|
304
|
+
<div class="in input-label" style="font-size:14px;margin-bottom:5px;">Default Items</div>
|
|
305
|
+
${await FallbackWorldEngineCyberia.buildItemIdsDropdown()}
|
|
306
|
+
</div>
|
|
307
|
+
<div class="in section-mp" style="margin-top: 5px;">
|
|
308
|
+
<div class="in input-label" style="font-size:13px;margin-bottom:5px;">Default Player Inventory</div>
|
|
309
|
+
<div class="in" style="color:#888;font-size:12px;margin-bottom:6px;">
|
|
310
|
+
Every selected id is resolved to an atlas at boot. Ids checked here are seeded into each new player's
|
|
311
|
+
inventory (inactive, never auto-equipped); unchecked ids are explicitly removed from the player's default
|
|
312
|
+
layers.
|
|
313
|
+
</div>
|
|
314
|
+
<div class="in ${idItemInventoryList}" style="max-height:200px;overflow-y:auto;"></div>
|
|
315
|
+
${canMutate
|
|
316
|
+
? html`<div class="in" style="display:flex;gap:5px;flex-wrap:wrap;margin-top:10px;">
|
|
317
|
+
${await BtnIcon.instance({
|
|
318
|
+
class: 'wfa btn-fallback-world-engine-load-defaults',
|
|
319
|
+
label: html`<i class="fa-solid fa-list-check"></i> Load Canonical Defaults`,
|
|
320
|
+
})}
|
|
321
|
+
${await BtnIcon.instance({
|
|
322
|
+
class: 'wfa btn-fallback-world-engine-clear',
|
|
323
|
+
label: html`<i class="fa-solid fa-eraser"></i> Clear Selection`,
|
|
324
|
+
})}
|
|
325
|
+
</div>`
|
|
326
|
+
: ''}
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
${canMutate
|
|
330
|
+
? html`<div class="in section-mp" style="margin-top: 10px;">
|
|
331
|
+
${await BtnIcon.instance({
|
|
332
|
+
class: 'wfa btn-fallback-world-engine-toggle-hot-reload',
|
|
333
|
+
label: html`<i class="fa-solid fa-caret-right fallback-world-engine-hot-reload-caret"></i> Hot Reload`,
|
|
334
|
+
})}
|
|
335
|
+
<div class="in fallback-world-engine-hot-reload-body hide">
|
|
336
|
+
<div class="in" style="color:#888;font-size:12px;margin:5px 0;">
|
|
337
|
+
Stages the items above on this engine, then rebuilds the world of a running cyberia-server so it
|
|
338
|
+
re-fetches them. Tries the gRPC control service first and falls back to the REST endpoint. For a
|
|
339
|
+
multi-instance deployment, append the variant sub-path so the trigger reaches the right world (e.g.
|
|
340
|
+
<code>https://server.cyberiaonline.com/TEST</code>); the default world uses the bare origin.
|
|
341
|
+
</div>
|
|
342
|
+
${await Input.instance({
|
|
343
|
+
id: idHotReloadUrl,
|
|
344
|
+
label: html`<i class="fa-solid fa-server"></i> Cyberia Server URL`,
|
|
345
|
+
containerClass: 'in',
|
|
346
|
+
placeholder: 'https://server.cyberiaonline.com/TEST',
|
|
347
|
+
type: 'text',
|
|
348
|
+
})}
|
|
349
|
+
<div class="in" style="display:flex;gap:5px;flex-wrap:wrap;margin-top:5px;">
|
|
350
|
+
${await BtnIcon.instance({
|
|
351
|
+
class: 'wfa btn-fallback-world-engine-hot-reload',
|
|
352
|
+
label: html`<i class="fa-solid fa-rotate"></i> Trigger Hot Reload`,
|
|
353
|
+
})}
|
|
354
|
+
${await BtnIcon.instance({
|
|
355
|
+
class: 'wfa btn-fallback-world-engine-hot-reload-incremental',
|
|
356
|
+
label: html`<i class="fa-solid fa-layer-group"></i> Incremental (assets only)`,
|
|
357
|
+
})}
|
|
358
|
+
</div>
|
|
359
|
+
<div class="in fallback-world-engine-hot-reload-status" style="margin-top:5px;font-size:12px;"></div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>`
|
|
362
|
+
: ''}
|
|
363
|
+
</div>
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export { FallbackWorldEngineCyberia };
|
|
@@ -26,6 +26,7 @@ import { htmls, s, htmlStrSanitize } from '../core/VanillaJs.js';
|
|
|
26
26
|
import { getProxyPath } from '../core/Router.js';
|
|
27
27
|
import { getApiBaseUrl } from '../../services/core/core.service.js';
|
|
28
28
|
import { CyberiaInstanceService } from '../../services/cyberia-instance/cyberia-instance.service.js';
|
|
29
|
+
import { DEFAULT_INSTANCE_CODE } from './SharedDefaultsCyberia.js';
|
|
29
30
|
|
|
30
31
|
// Client deployment that actually hosts the playable worlds. Matches the portal
|
|
31
32
|
// landing CTA (MainBodyCyberiaPortal); override per-instance via `onPlay`.
|
|
@@ -33,9 +34,8 @@ const DEFAULT_CLIENT_BASE_URL = 'https://client.cyberiaonline.com';
|
|
|
33
34
|
|
|
34
35
|
// Authoritative simulation host and the default instance's code, mirroring the
|
|
35
36
|
// mmo-server multiInstance block in conf.instances.json. The default variant is
|
|
36
|
-
// served at
|
|
37
|
+
// served at `/`; other variants use their literal `/<CODE>` path.
|
|
37
38
|
const DEFAULT_SERVER_BASE_URL = 'https://server.cyberiaonline.com';
|
|
38
|
-
const DEFAULT_INSTANCE_CODE = 'amethyst-strata-expansion';
|
|
39
39
|
|
|
40
40
|
const placeholderThumbnail = () => `${getProxyPath()}assets/ui-icons/world-default-forest-city.png`;
|
|
41
41
|
|
|
@@ -95,17 +95,17 @@ const normalizeInstance = (doc, { clientBaseUrl }) => {
|
|
|
95
95
|
players: Number.isFinite(players) ? players : null,
|
|
96
96
|
capacity: Number.isFinite(capacity) && capacity > 0 ? capacity : null,
|
|
97
97
|
tags: Array.isArray(doc.tags) ? doc.tags.filter(Boolean) : [],
|
|
98
|
-
playUrl: `${clientBaseUrl}${code ? `/${encodeURIComponent(code)}` : ''}`,
|
|
98
|
+
playUrl: `${clientBaseUrl}${code && code !== DEFAULT_INSTANCE_CODE ? `/${encodeURIComponent(code)}` : ''}`,
|
|
99
99
|
playable: statusMeta(status).playable,
|
|
100
100
|
};
|
|
101
101
|
};
|
|
102
102
|
|
|
103
103
|
// ── Live status via the cyberia-server metrics API ───────────────────────────
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
104
|
+
// The default is served at `/`; other variants use the compact `/<CODE>` paths
|
|
105
|
+
// from conf.instances.json. The proxy strips non-root prefixes, so each world's
|
|
106
|
+
// own /api/v1/metrics reports its health + capacity.
|
|
107
|
+
// Cross-origin, so the metrics CORS allow-list (CYBERIA_CORS_ALLOWED_ORIGINS)
|
|
108
|
+
// must include this portal.
|
|
109
109
|
|
|
110
110
|
const metricsUrlFor = (code, { serverBaseUrl, defaultInstanceCode }) => {
|
|
111
111
|
const path = code && code !== defaultInstanceCode ? `/${encodeURIComponent(code)}` : '';
|
|
@@ -316,6 +316,9 @@ class InstanceSelectionView {
|
|
|
316
316
|
});
|
|
317
317
|
return;
|
|
318
318
|
}
|
|
319
|
+
const defaultSuffix = `/${encodeURIComponent(DEFAULT_INSTANCE_CODE)}`;
|
|
320
|
+
if (instance.playUrl?.endsWith(defaultSuffix))
|
|
321
|
+
instance.playUrl = instance.playUrl.slice(0, -defaultSuffix.length);
|
|
319
322
|
if (typeof onPlay === 'function') return onPlay(instance);
|
|
320
323
|
if (instance.playUrl) location.href = instance.playUrl;
|
|
321
324
|
};
|
|
@@ -50,6 +50,11 @@
|
|
|
50
50
|
// Shared content vocabulary
|
|
51
51
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Default instance code for the Cyberia engine
|
|
55
|
+
*/
|
|
56
|
+
export const DEFAULT_INSTANCE_CODE = 'amethyst-strata-expansion';
|
|
57
|
+
|
|
53
58
|
/**
|
|
54
59
|
* Canonical set of ObjectLayer item type names. Used as the
|
|
55
60
|
* `data.item.type` discriminator and as the asset directory name on disk.
|
|
@@ -34,6 +34,7 @@ import { InstanceEngineCyberia } from '../cyberia/InstanceEngineCyberia.js';
|
|
|
34
34
|
import { InstanceSelectionView } from '../cyberia/InstanceSelectionView.js';
|
|
35
35
|
import { ActionEngineCyberia } from '../cyberia/ActionEngineCyberia.js';
|
|
36
36
|
import { EntityEngineCyberia } from '../cyberia/EntityEngineCyberia.js';
|
|
37
|
+
import { FallbackWorldEngineCyberia } from '../cyberia/FallbackWorldEngineCyberia.js';
|
|
37
38
|
|
|
38
39
|
class AppShellCyberiaPortal {
|
|
39
40
|
static Data = {};
|
|
@@ -288,6 +289,18 @@ class AppShellCyberiaPortal {
|
|
|
288
289
|
handleContainerClass: 'handle-btn-container',
|
|
289
290
|
tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('cyberia-entity-engine')),
|
|
290
291
|
})}
|
|
292
|
+
${await BtnIcon.instance({
|
|
293
|
+
class: 'in wfa main-btn-menu main-btn-fallback-world-engine',
|
|
294
|
+
useMenuBtn: true,
|
|
295
|
+
label: renderMenuLabel({
|
|
296
|
+
icon: html`<img class="inl cyberia-menu-icon" src="${getProxyPath()}assets/ui-icons/engine.png" />`,
|
|
297
|
+
text: html`<span class="menu-label-text">${Translate.instance('fallback-world-engine')}</span>`,
|
|
298
|
+
}),
|
|
299
|
+
attrs: `data-id="fallback-world-engine"`,
|
|
300
|
+
tabHref: `${getProxyPath()}fallback-world-engine`,
|
|
301
|
+
handleContainerClass: 'handle-btn-container',
|
|
302
|
+
tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('fallback-world-engine')),
|
|
303
|
+
})}
|
|
291
304
|
</div>
|
|
292
305
|
`,
|
|
293
306
|
// htmlMainBody: async () => {
|
|
@@ -762,6 +775,26 @@ class AppShellCyberiaPortal {
|
|
|
762
775
|
});
|
|
763
776
|
});
|
|
764
777
|
|
|
778
|
+
EventsUI.onClick(`.main-btn-fallback-world-engine`, async () => {
|
|
779
|
+
const { barConfig } = await Themes[Css.currentTheme]();
|
|
780
|
+
await Modal.instance({
|
|
781
|
+
id: 'modal-fallback-world-engine',
|
|
782
|
+
route: 'fallback-world-engine',
|
|
783
|
+
barConfig,
|
|
784
|
+
title: renderViewTitle({
|
|
785
|
+
icon: html`<img class="inl cyberia-menu-icon-modal" src="${getProxyPath()}assets/ui-icons/engine.png" />`,
|
|
786
|
+
text: `<span class='inl cyberia-text-title-modal'>${Translate.instance('fallback-world-engine')}</span>`,
|
|
787
|
+
}),
|
|
788
|
+
html: async () => await FallbackWorldEngineCyberia.render({ appStore: AppStoreCyberiaPortal }),
|
|
789
|
+
handleType: 'bar',
|
|
790
|
+
maximize: true,
|
|
791
|
+
mode: 'view',
|
|
792
|
+
slideMenu: 'modal-menu',
|
|
793
|
+
RouterInstance,
|
|
794
|
+
observer: true,
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
|
|
765
798
|
s(`.main-btn-admin`).onclick = () => {
|
|
766
799
|
const { protocol, hostname } = window.location;
|
|
767
800
|
return (location.href = `${protocol}//${hostname}/admin${['', 0][random(0, 1)]}`);
|
|
@@ -56,6 +56,10 @@ class RouterCyberiaPortal {
|
|
|
56
56
|
title: 'cyberia-entity-engine',
|
|
57
57
|
render: () => s(`.main-btn-cyberia-entity-engine`).click(),
|
|
58
58
|
},
|
|
59
|
+
'/fallback-world-engine': {
|
|
60
|
+
title: 'fallback-world-engine',
|
|
61
|
+
render: () => s(`.main-btn-fallback-world-engine`).click(),
|
|
62
|
+
},
|
|
59
63
|
'/instance-selection': {
|
|
60
64
|
title: 'instance-selection',
|
|
61
65
|
render: () => s(`.main-btn-instance-selection`).click(),
|
|
@@ -30,6 +30,10 @@ class TranslateCyberiaPortal {
|
|
|
30
30
|
en: 'Cyberia Entity Engine',
|
|
31
31
|
es: 'Cyberia Entity Engine',
|
|
32
32
|
};
|
|
33
|
+
Translate.Data['fallback-world-engine'] = {
|
|
34
|
+
en: 'Fallback World Engine',
|
|
35
|
+
es: 'Fallback World Engine',
|
|
36
|
+
};
|
|
33
37
|
Translate.Data['instance-selection'] = {
|
|
34
38
|
en: 'Select World',
|
|
35
39
|
es: 'Seleccionar Mundo',
|