fix: resolve inherited form actions
This commit is contained in:
parent
c1bf74c3f0
commit
ccb8cb70be
@ -1,5 +1,10 @@
|
|||||||
# Lumi changelog
|
# Lumi changelog
|
||||||
|
|
||||||
|
## 0.2.3
|
||||||
|
|
||||||
|
- Fixed shared form action resolution so submit buttons without a `formaction` use their parent form's endpoint, restoring timed confirmations such as diagnostics-key creation.
|
||||||
|
- Fixed the same browser behavior in asynchronous core/plugin update forms, which could submit to the Updates page itself and report a context-free failure without running the update.
|
||||||
|
|
||||||
## 0.2.2
|
## 0.2.2
|
||||||
|
|
||||||
- Added stage-specific plugin update failures beside the affected plugin and persisted the production failure stage in update state.
|
- Added stage-specific plugin update failures beside the affected plugin and persisted the production failure stage in update state.
|
||||||
|
|||||||
1
TODO.md
1
TODO.md
@ -677,6 +677,7 @@ This section is for Lumi chat answer feedback and AI Improvement Center work, no
|
|||||||
|
|
||||||
## Done
|
## Done
|
||||||
|
|
||||||
|
- 2026-07-18: Fixed the shared submit-action resolver in core 0.2.3: ordinary buttons now inherit their parent form endpoint unless they explicitly declare `formaction`, restoring timed diagnostics-key creation and preventing async update actions from posting back to the Updates page.
|
||||||
- 2026-07-18: Added production-stage plugin update diagnostics in core 0.2.2: selected plugin source is verified before snapshotting, failures record their exact stage and target in update state, and the affected plugin row displays the server error directly.
|
- 2026-07-18: Added production-stage plugin update diagnostics in core 0.2.2: selected plugin source is verified before snapshotting, failures record their exact stage and target in update state, and the affected plugin row displays the server error directly.
|
||||||
- 2026-07-18: Added opt-in production diagnostics in core 0.2.2: administrators can issue or revoke a one-time access key for an HTTPS-only, rate-limited endpoint with five fixed read-only checks, recursive secret/path redaction, and audited access; no arbitrary command, SQL, file, URL, or write capability is exposed.
|
- 2026-07-18: Added opt-in production diagnostics in core 0.2.2: administrators can issue or revoke a one-time access key for an HTTPS-only, rate-limited endpoint with five fixed read-only checks, recursive secret/path redaction, and audited access; no arbitrary command, SQL, file, URL, or write capability is exposed.
|
||||||
- 2026-07-18: Released the follow-up core 0.2.1 update-page fix: update checks now have one client-side handler, successful results use clear module-specific wording, and exact core/plugin version controls are collapsed and list only the selected module’s versions.
|
- 2026-07-18: Released the follow-up core 0.2.1 update-page fix: update checks now have one client-side handler, successful results use clear module-specific wording, and exact core/plugin version controls are collapsed and list only the selected module’s versions.
|
||||||
|
|||||||
@ -160,6 +160,10 @@ Admin update actions publish Server-Sent Events through
|
|||||||
Core update success returns a five-second in-page notice before refresh/restart.
|
Core update success returns a five-second in-page notice before refresh/restart.
|
||||||
Plugin update success updates progress for the affected plugin action without a
|
Plugin update success updates progress for the affected plugin action without a
|
||||||
whole-page refresh, then restarts Lumi so the selected plugin code is loaded.
|
whole-page refresh, then restarts Lumi so the selected plugin code is loaded.
|
||||||
|
Submit buttons inherit the endpoint and method from their parent form unless
|
||||||
|
they explicitly declare `formaction` or `formmethod`; this prevents browsers'
|
||||||
|
current-page fallback values from redirecting an update away from its apply
|
||||||
|
route.
|
||||||
|
|
||||||
Plugin failures are shown directly beside the affected plugin instead of only
|
Plugin failures are shown directly beside the affected plugin instead of only
|
||||||
changing the action button label. The error identifies the failed stage:
|
changing the action button label. The error identifies the failed stage:
|
||||||
|
|||||||
@ -14,7 +14,7 @@ editable: false
|
|||||||
Lumi is the core web UI and bot runtime.
|
Lumi is the core web UI and bot runtime.
|
||||||
## Runtime
|
## Runtime
|
||||||
Package: lumi-bot
|
Package: lumi-bot
|
||||||
Version: 0.2.2
|
Version: 0.2.3
|
||||||
## Routes
|
## Routes
|
||||||
- GET /api/events
|
- GET /api/events
|
||||||
- POST /api/destructive-confirmations
|
- POST /api/destructive-confirmations
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "lumi-bot",
|
"name": "lumi-bot",
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "lumi-bot",
|
"name": "lumi-bot",
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.12",
|
"adm-zip": "^0.5.12",
|
||||||
"better-sqlite3": "^11.5.0",
|
"better-sqlite3": "^11.5.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lumi-bot",
|
"name": "lumi-bot",
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -2,6 +2,36 @@
|
|||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"channel": "stable",
|
"channel": "stable",
|
||||||
"releases": [
|
"releases": [
|
||||||
|
{
|
||||||
|
"version": "0.2.3",
|
||||||
|
"ref": "refs/tags/v0.2.3",
|
||||||
|
"released_at": "2026-07-18",
|
||||||
|
"installable": true,
|
||||||
|
"rollback_safe": true,
|
||||||
|
"replaces_versions": [
|
||||||
|
"1.2.0"
|
||||||
|
],
|
||||||
|
"data_policy": "preserve",
|
||||||
|
"dependency_policy": "sync_on_restart",
|
||||||
|
"migration_notes": "Fixes shared form endpoint resolution so timed confirmations and asynchronous repository updates reach their intended routes.",
|
||||||
|
"plugins": {
|
||||||
|
"auto-vc": "0.1.6",
|
||||||
|
"birthday": "0.1.3",
|
||||||
|
"economy-framework": "0.2.10",
|
||||||
|
"economy-games": "0.1.7",
|
||||||
|
"expression-interaction": "0.2.1",
|
||||||
|
"lumi_ai": "0.8.2",
|
||||||
|
"moderation": "0.1.5",
|
||||||
|
"okf": "0.1.1",
|
||||||
|
"quotes": "0.1.2",
|
||||||
|
"sample-plugin": "0.1.0",
|
||||||
|
"throne_wishlist": "0.1.2",
|
||||||
|
"welcome_messages": "0.1.1"
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"lumi_ai_web_search": "0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"ref": "refs/tags/v0.2.2",
|
"ref": "refs/tags/v0.2.2",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ const path = require("path");
|
|||||||
const AdmZip = require("adm-zip");
|
const AdmZip = require("adm-zip");
|
||||||
|
|
||||||
const root = path.join(__dirname, "..");
|
const root = path.join(__dirname, "..");
|
||||||
const destination = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.2-repair.zip");
|
const destination = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.3-repair.zip");
|
||||||
const files = [
|
const files = [
|
||||||
"CHANGELOG.md",
|
"CHANGELOG.md",
|
||||||
"README.md",
|
"README.md",
|
||||||
@ -53,10 +53,10 @@ for (const relativePath of files) {
|
|||||||
|
|
||||||
const manifest = {
|
const manifest = {
|
||||||
schema_version: 1,
|
schema_version: 1,
|
||||||
name: "Lumi core 0.2.2 repair",
|
name: "Lumi core 0.2.3 repair",
|
||||||
target: "core",
|
target: "core",
|
||||||
from_versions: ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2"],
|
from_versions: ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2", "0.2.3"],
|
||||||
to_version: "0.2.2",
|
to_version: "0.2.3",
|
||||||
data_policy: "preserve",
|
data_policy: "preserve",
|
||||||
dependency_policy: "sync_on_restart",
|
dependency_policy: "sync_on_restart",
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
|
|||||||
@ -7,7 +7,7 @@ const AdmZip = require("adm-zip");
|
|||||||
const { verifyPatchPackage } = require("../src/services/update-manager");
|
const { verifyPatchPackage } = require("../src/services/update-manager");
|
||||||
|
|
||||||
const root = path.join(__dirname, "..");
|
const root = path.join(__dirname, "..");
|
||||||
const archivePath = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.2-repair.zip");
|
const archivePath = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.3-repair.zip");
|
||||||
assert.equal(fs.existsSync(archivePath), true, "build the repair patch first");
|
assert.equal(fs.existsSync(archivePath), true, "build the repair patch first");
|
||||||
const zip = new AdmZip(archivePath);
|
const zip = new AdmZip(archivePath);
|
||||||
const entries = zip.getEntries().filter((entry) => !entry.isDirectory);
|
const entries = zip.getEntries().filter((entry) => !entry.isDirectory);
|
||||||
@ -15,9 +15,9 @@ const names = new Set(entries.map((entry) => entry.entryName.replace(/\\/g, "/")
|
|||||||
assert.equal(names.has("patch-manifest.json"), true);
|
assert.equal(names.has("patch-manifest.json"), true);
|
||||||
const manifest = JSON.parse(zip.readAsText("patch-manifest.json"));
|
const manifest = JSON.parse(zip.readAsText("patch-manifest.json"));
|
||||||
assert.equal(manifest.target, "core");
|
assert.equal(manifest.target, "core");
|
||||||
assert.equal(manifest.to_version, "0.2.2");
|
assert.equal(manifest.to_version, "0.2.3");
|
||||||
assert.equal(manifest.data_policy, "preserve");
|
assert.equal(manifest.data_policy, "preserve");
|
||||||
assert.deepEqual(manifest.from_versions, ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2"]);
|
assert.deepEqual(manifest.from_versions, ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2", "0.2.3"]);
|
||||||
|
|
||||||
const forbidden = /^(?:data|plugins|node_modules|config|storage|uploads|logs|database|databases|knowledge\/(?:community|corrections))(?:\/|$)|^\.env(?:\.|$)|^\.secrets$/;
|
const forbidden = /^(?:data|plugins|node_modules|config|storage|uploads|logs|database|databases|knowledge\/(?:community|corrections))(?:\/|$)|^\.env(?:\.|$)|^\.secrets$/;
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
@ -29,7 +29,7 @@ for (const [relativePath, expected] of Object.entries(manifest.files)) {
|
|||||||
assert.equal(actual, expected, `${relativePath} checksum`);
|
assert.equal(actual, expected, `${relativePath} checksum`);
|
||||||
}
|
}
|
||||||
assert.equal(Object.keys(manifest.files).length + 1, entries.length, "every repair file must be checksummed");
|
assert.equal(Object.keys(manifest.files).length + 1, entries.length, "every repair file must be checksummed");
|
||||||
assert.equal(JSON.parse(zip.readAsText("package.json")).version, "0.2.2");
|
assert.equal(JSON.parse(zip.readAsText("package.json")).version, "0.2.3");
|
||||||
|
|
||||||
const simulation = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-repair-simulation-"));
|
const simulation = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-repair-simulation-"));
|
||||||
try {
|
try {
|
||||||
@ -47,11 +47,11 @@ try {
|
|||||||
fs.writeFileSync(target, `preserve:${sentinel}`);
|
fs.writeFileSync(target, `preserve:${sentinel}`);
|
||||||
}
|
}
|
||||||
zip.extractAllTo(simulation, true);
|
zip.extractAllTo(simulation, true);
|
||||||
assert.equal(verifyPatchPackage(simulation).to_version, "0.2.2");
|
assert.equal(verifyPatchPackage(simulation).to_version, "0.2.3");
|
||||||
for (const sentinel of sentinels) {
|
for (const sentinel of sentinels) {
|
||||||
assert.equal(fs.readFileSync(path.join(simulation, sentinel), "utf8"), `preserve:${sentinel}`);
|
assert.equal(fs.readFileSync(path.join(simulation, sentinel), "utf8"), `preserve:${sentinel}`);
|
||||||
}
|
}
|
||||||
assert.equal(JSON.parse(fs.readFileSync(path.join(simulation, "package.json"), "utf8")).version, "0.2.2");
|
assert.equal(JSON.parse(fs.readFileSync(path.join(simulation, "package.json"), "utf8")).version, "0.2.3");
|
||||||
assert.equal(fs.existsSync(path.join(simulation, "src", "services", "dependency-manager.js")), true);
|
assert.equal(fs.existsSync(path.join(simulation, "src", "services", "dependency-manager.js")), true);
|
||||||
assert.equal(fs.existsSync(path.join(simulation, "src", "services", "production-diagnostics.js")), true);
|
assert.equal(fs.existsSync(path.join(simulation, "src", "services", "production-diagnostics.js")), true);
|
||||||
assert.equal(fs.existsSync(path.join(simulation, "src", "web", "views", "admin-diagnostics.ejs")), true);
|
assert.equal(fs.existsSync(path.join(simulation, "src", "web", "views", "admin-diagnostics.ejs")), true);
|
||||||
|
|||||||
@ -77,6 +77,7 @@ function verifySharedInfrastructure() {
|
|||||||
const appScript = fs.readFileSync(path.join(root, "src", "web", "public", "app.js"), "utf8");
|
const appScript = fs.readFileSync(path.join(root, "src", "web", "public", "app.js"), "utf8");
|
||||||
assert(appScript.includes("submitter?.dataset?.confirmMode"), "Submit-button confirmation metadata is ignored");
|
assert(appScript.includes("submitter?.dataset?.confirmMode"), "Submit-button confirmation metadata is ignored");
|
||||||
assert(appScript.includes("submitter?.dataset?.confirmLabel || form.dataset.confirmLabel"), "The clicked submit button cannot override generic confirmation copy");
|
assert(appScript.includes("submitter?.dataset?.confirmLabel || form.dataset.confirmLabel"), "The clicked submit button cannot override generic confirmation copy");
|
||||||
|
assert(appScript.includes('submitter?.hasAttribute?.("formaction") ? submitter.formAction : form.action'), "Submit buttons without formaction incorrectly override their parent form action");
|
||||||
assert(appScript.includes('form.querySelector(\'input[name="confirmation_token"]\')'), "Timed form confirmation tokens are not attached to submitted forms");
|
assert(appScript.includes('form.querySelector(\'input[name="confirmation_token"]\')'), "Timed form confirmation tokens are not attached to submitted forms");
|
||||||
assert(appScript.includes("const response = await fetch(action, requestOptions)"), "Async update requests do not submit through the shared confirmed form payload");
|
assert(appScript.includes("const response = await fetch(action, requestOptions)"), "Async update requests do not submit through the shared confirmed form payload");
|
||||||
assert(!appScript.includes("window.LumiConfirm.destructiveFetch(form.action"), "Timed update forms request a second confirmation instead of reusing the form token");
|
assert(!appScript.includes("window.LumiConfirm.destructiveFetch(form.action"), "Timed update forms request a second confirmation instead of reusing the form token");
|
||||||
|
|||||||
@ -4,8 +4,8 @@ const path = require("path");
|
|||||||
const { findSafeTarget } = require("../src/services/versioning");
|
const { findSafeTarget } = require("../src/services/versioning");
|
||||||
|
|
||||||
const root = path.join(__dirname, "..");
|
const root = path.join(__dirname, "..");
|
||||||
const releaseVersion = "0.2.2";
|
const releaseVersion = "0.2.3";
|
||||||
const previousCoreVersion = "0.2.1";
|
const previousCoreVersion = "0.2.2";
|
||||||
const earliestCompatibleCoreVersion = "0.1.9";
|
const earliestCompatibleCoreVersion = "0.1.9";
|
||||||
const changedPlugins = {
|
const changedPlugins = {
|
||||||
"auto-vc": { from: "0.1.5", to: "0.1.6", knowledge: "auto-vc" },
|
"auto-vc": { from: "0.1.5", to: "0.1.6", knowledge: "auto-vc" },
|
||||||
@ -87,4 +87,4 @@ assert.equal(webSearch.minimum_lumi_version, "0.2.0");
|
|||||||
assert.equal(webSearch.minimum_lumi_ai_version, changedPlugins.lumi_ai.to);
|
assert.equal(webSearch.minimum_lumi_ai_version, changedPlugins.lumi_ai.to);
|
||||||
assert.equal(hasVersionHeading(readText("plugins/lumi_ai_web_search/CHANGELOG.md"), webSearch.version), true);
|
assert.equal(hasVersionHeading(readText("plugins/lumi_ai_web_search/CHANGELOG.md"), webSearch.version), true);
|
||||||
|
|
||||||
console.log("Release metadata verification passed: core 0.2.2 and 11 changed plugin/tool packages.");
|
console.log("Release metadata verification passed: core 0.2.3 and 11 changed plugin/tool packages.");
|
||||||
|
|||||||
@ -16,7 +16,7 @@ function readJson(relativePath) {
|
|||||||
|
|
||||||
const releaseIndex = readJson("release-index.json");
|
const releaseIndex = readJson("release-index.json");
|
||||||
const releaseVersions = releaseIndex.releases.map((release) => release.version);
|
const releaseVersions = releaseIndex.releases.map((release) => release.version);
|
||||||
assert.deepEqual(releaseVersions, ["0.2.2", "0.2.1", "0.2.0", "0.1.9"]);
|
assert.deepEqual(releaseVersions, ["0.2.3", "0.2.2", "0.2.1", "0.2.0", "0.1.9"]);
|
||||||
assert.equal(new Set(releaseVersions).size, releaseVersions.length, "release versions must be unique");
|
assert.equal(new Set(releaseVersions).size, releaseVersions.length, "release versions must be unique");
|
||||||
for (const release of releaseIndex.releases) {
|
for (const release of releaseIndex.releases) {
|
||||||
assert.equal(normalizeRepositoryRef(release.ref), release.ref);
|
assert.equal(normalizeRepositoryRef(release.ref), release.ref);
|
||||||
@ -35,8 +35,9 @@ for (const [toolId, version] of Object.entries(currentRelease.tools)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseTarget = {
|
const baseTarget = {
|
||||||
current_version: "0.2.0",
|
current_version: "0.2.2",
|
||||||
available_versions: [
|
available_versions: [
|
||||||
|
{ version: "0.2.3", ref: "refs/tags/v0.2.3", rollback_safe: true },
|
||||||
{ version: "0.2.2", ref: "refs/tags/v0.2.2", rollback_safe: true },
|
{ version: "0.2.2", ref: "refs/tags/v0.2.2", rollback_safe: true },
|
||||||
{ version: "0.2.1", ref: "refs/tags/v0.2.1", rollback_safe: true },
|
{ version: "0.2.1", ref: "refs/tags/v0.2.1", rollback_safe: true },
|
||||||
{ version: "0.2.0", ref: "refs/tags/v0.2.0", rollback_safe: true },
|
{ version: "0.2.0", ref: "refs/tags/v0.2.0", rollback_safe: true },
|
||||||
@ -62,7 +63,7 @@ const corrected = buildStatus({
|
|||||||
channel: "stable"
|
channel: "stable"
|
||||||
});
|
});
|
||||||
assert.equal(corrected.version_correction, true);
|
assert.equal(corrected.version_correction, true);
|
||||||
assert.equal(corrected.safe_target_version, "0.2.2");
|
assert.equal(corrected.safe_target_version, "0.2.3");
|
||||||
assert.equal(corrected.update_available, true);
|
assert.equal(corrected.update_available, true);
|
||||||
assert.equal(corrected.blocked, false);
|
assert.equal(corrected.blocked, false);
|
||||||
|
|
||||||
|
|||||||
@ -195,7 +195,9 @@ function verifySharedUpdateActions() {
|
|||||||
const dashboard = fs.readFileSync(path.join(root, "src", "web", "views", "admin-dashboard.ejs"), "utf8");
|
const dashboard = fs.readFileSync(path.join(root, "src", "web", "views", "admin-dashboard.ejs"), "utf8");
|
||||||
const settings = fs.readFileSync(path.join(root, "src", "web", "views", "admin-settings.ejs"), "utf8");
|
const settings = fs.readFileSync(path.join(root, "src", "web", "views", "admin-settings.ejs"), "utf8");
|
||||||
assert(appSource.includes('button[data-update-action]'));
|
assert(appSource.includes('button[data-update-action]'));
|
||||||
assert(appSource.includes("submitter?.formAction || form.action"));
|
assert(appSource.includes('submitter?.hasAttribute?.("formaction") ? submitter.formAction : form.action'));
|
||||||
|
assert(appSource.includes('submitter?.hasAttribute?.("formmethod") ? submitter.formMethod : form.method'));
|
||||||
|
assert(!appSource.includes("submitter?.formAction || form.action"));
|
||||||
assert(appSource.includes('actionPath.endsWith("/check")'));
|
assert(appSource.includes('actionPath.endsWith("/check")'));
|
||||||
assert(appSource.includes('form.matches("[data-update-check-form]")'));
|
assert(appSource.includes('form.matches("[data-update-check-form]")'));
|
||||||
assert(dashboard.includes('action="/admin/check-update" class="inline-form" data-update-action'));
|
assert(dashboard.includes('action="/admin/check-update" class="inline-form" data-update-action'));
|
||||||
|
|||||||
@ -1146,9 +1146,18 @@
|
|||||||
let activeDestructive = null;
|
let activeDestructive = null;
|
||||||
let activeCallbackConfirm = null;
|
let activeCallbackConfirm = null;
|
||||||
|
|
||||||
|
// A button without a formaction can report the current document URL through
|
||||||
|
// its formAction property. Only prefer the button value when the attribute
|
||||||
|
// was explicitly authored; otherwise the parent form owns the endpoint.
|
||||||
|
const effectiveFormAction = (form, submitter = null) =>
|
||||||
|
submitter?.hasAttribute?.("formaction") ? submitter.formAction : form.action;
|
||||||
|
|
||||||
|
const effectiveFormMethod = (form, submitter = null) =>
|
||||||
|
submitter?.hasAttribute?.("formmethod") ? submitter.formMethod : form.method;
|
||||||
|
|
||||||
const destructiveAction = (form, submitter = null) => {
|
const destructiveAction = (form, submitter = null) => {
|
||||||
try {
|
try {
|
||||||
const action = submitter?.formAction || form.action;
|
const action = effectiveFormAction(form, submitter);
|
||||||
return new URL(action, window.location.origin).pathname;
|
return new URL(action, window.location.origin).pathname;
|
||||||
} catch {
|
} catch {
|
||||||
return "";
|
return "";
|
||||||
@ -1517,7 +1526,7 @@
|
|||||||
const submitter = event.submitter || form.querySelector("button[type='submit']");
|
const submitter = event.submitter || form.querySelector("button[type='submit']");
|
||||||
if (!form.matches("[data-update-action]") && !submitter?.matches?.("[data-update-action]")) return;
|
if (!form.matches("[data-update-action]") && !submitter?.matches?.("[data-update-action]")) return;
|
||||||
if (form.matches("[data-update-check-form]")) return;
|
if (form.matches("[data-update-check-form]")) return;
|
||||||
const action = submitter?.formAction || form.action;
|
const action = effectiveFormAction(form, submitter);
|
||||||
const actionPath = normalizeDestructiveAction(action);
|
const actionPath = normalizeDestructiveAction(action);
|
||||||
if (updateLog && actionPath.startsWith("/admin/updates/") && actionPath.endsWith("/check")) return;
|
if (updateLog && actionPath.startsWith("/admin/updates/") && actionPath.endsWith("/check")) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -1548,7 +1557,7 @@
|
|||||||
appendUpdateLog(`Started ${submitter?.textContent?.trim() || "update action"}.`, "info", form);
|
appendUpdateLog(`Started ${submitter?.textContent?.trim() || "update action"}.`, "info", form);
|
||||||
try {
|
try {
|
||||||
const requestOptions = {
|
const requestOptions = {
|
||||||
method: submitter?.formMethod || form.method || "POST",
|
method: effectiveFormMethod(form, submitter) || "POST",
|
||||||
body: new FormData(form),
|
body: new FormData(form),
|
||||||
headers: { Accept: "application/json" },
|
headers: { Accept: "application/json" },
|
||||||
credentials: "same-origin"
|
credentials: "same-origin"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Lumi Core",
|
"name": "Lumi Core",
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"channel": "stable",
|
"channel": "stable",
|
||||||
"released_at": "2026-07-18",
|
"released_at": "2026-07-18",
|
||||||
"compatible_from": "0.1.9",
|
"compatible_from": "0.1.9",
|
||||||
@ -8,7 +8,7 @@
|
|||||||
"replaces_versions": [
|
"replaces_versions": [
|
||||||
"1.2.0"
|
"1.2.0"
|
||||||
],
|
],
|
||||||
"migration_notes": "Includes the 1.2.0 version correction, production plugin-update diagnostics, and an optional secured read-only production diagnostics endpoint. Lumi synchronizes runtime dependencies on restart and preserves settings, databases, plugin data, community knowledge, AI models, runtimes, uploads, logs, and secrets.",
|
"migration_notes": "Includes the 1.2.0 version correction, production plugin-update diagnostics, secured read-only production diagnostics, and the shared form-action fix required by timed confirmations and repository updates. Lumi synchronizes runtime dependencies on restart and preserves settings, databases, plugin data, community knowledge, AI models, runtimes, uploads, logs, and secrets.",
|
||||||
"rollback_safe": true,
|
"rollback_safe": true,
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"Node.js 18 or newer"
|
"Node.js 18 or newer"
|
||||||
@ -49,6 +49,18 @@
|
|||||||
],
|
],
|
||||||
"rollback_safe": true,
|
"rollback_safe": true,
|
||||||
"migration_notes": "Adds stage-specific plugin update preflight/error reporting and an optional secured read-only production diagnostics endpoint; preserved local data is not replaced."
|
"migration_notes": "Adds stage-specific plugin update preflight/error reporting and an optional secured read-only production diagnostics endpoint; preserved local data is not replaced."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.2.3",
|
||||||
|
"channel": "stable",
|
||||||
|
"released_at": "2026-07-18",
|
||||||
|
"compatible_from": "0.1.9",
|
||||||
|
"migration_kind": "patch",
|
||||||
|
"replaces_versions": [
|
||||||
|
"1.2.0"
|
||||||
|
],
|
||||||
|
"rollback_safe": true,
|
||||||
|
"migration_notes": "Fixes shared browser form endpoint resolution for timed confirmations and repository updates; preserved local data is not replaced."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user