fix(updates): стабильный диалог установки, сохранение баннеров и логов без паразитных автозакрытий
This commit is contained in:
@@ -575,8 +575,11 @@ async function applyCompletedComponentAction({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (notify) {
|
if (notify) {
|
||||||
modalController?.completeSuccess(result.message, { reloadPage: true });
|
if (modalController) {
|
||||||
reloadPageAfterTachyonUpdate();
|
modalController.completeSuccess(result.message, { reloadPage: true });
|
||||||
|
} else {
|
||||||
|
reloadPageAfterTachyonUpdate();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
modalController?.completeSuccess(result.message);
|
modalController?.completeSuccess(result.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export function showUpdateProgressModal(
|
|||||||
|
|
||||||
const logPreEl = E('pre', {
|
const logPreEl = E('pre', {
|
||||||
class: 'tachyon-update-modal__log',
|
class: 'tachyon-update-modal__log',
|
||||||
}) as HTMLPreElement;
|
}, _('Operation started. Waiting for log output...')) as HTMLPreElement;
|
||||||
|
|
||||||
const logPanelEl = E('div', { class: 'tachyon-update-modal__log-panel' }, [
|
const logPanelEl = E('div', { class: 'tachyon-update-modal__log-panel' }, [
|
||||||
E('div', { class: 'tachyon-update-modal__log-header' }, [
|
E('div', { class: 'tachyon-update-modal__log-header' }, [
|
||||||
@@ -205,8 +205,6 @@ export function showUpdateProgressModal(
|
|||||||
let logPollTimer: ReturnType<typeof setTimeout> | null = null;
|
let logPollTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
let logPollStopped = true;
|
let logPollStopped = true;
|
||||||
|
|
||||||
let autoCompletedFromLog = false;
|
|
||||||
|
|
||||||
function appendLogText(text: string) {
|
function appendLogText(text: string) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return;
|
return;
|
||||||
@@ -214,26 +212,6 @@ export function showUpdateProgressModal(
|
|||||||
logFullText += text;
|
logFullText += text;
|
||||||
logPreEl.textContent = logFullText;
|
logPreEl.textContent = logFullText;
|
||||||
logPreEl.scrollTop = logPreEl.scrollHeight;
|
logPreEl.scrollTop = logPreEl.scrollHeight;
|
||||||
|
|
||||||
if (
|
|
||||||
!autoCompletedFromLog &&
|
|
||||||
options.component === 'tachyon' &&
|
|
||||||
(options.action === 'install' || options.action === 'reinstall')
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
logFullText.includes('Tachyon updated to') ||
|
|
||||||
logFullText.includes('Start Tachyon')
|
|
||||||
) {
|
|
||||||
autoCompletedFromLog = true;
|
|
||||||
window.setTimeout(() => {
|
|
||||||
if (activeModalController === controller) {
|
|
||||||
controller.completeSuccess(_('Tachyon updated successfully!'), {
|
|
||||||
reloadPage: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pollLog() {
|
async function pollLog() {
|
||||||
@@ -338,21 +316,19 @@ export function showUpdateProgressModal(
|
|||||||
? _('Check completed!')
|
? _('Check completed!')
|
||||||
: options.action === 'remove'
|
: options.action === 'remove'
|
||||||
? _('Removal completed successfully!')
|
? _('Removal completed successfully!')
|
||||||
: _('Update completed successfully!'));
|
: _('Operation completed successfully!'));
|
||||||
|
|
||||||
actionButtonContainer.replaceChildren(
|
const bannerEl = E('div', { class: 'tachyon-update-modal__success-banner' }, [
|
||||||
E('div', { class: 'tachyon-update-modal__success-banner' }, [
|
renderCheckIcon24(),
|
||||||
renderCheckIcon24(),
|
E('span', {}, successMsg),
|
||||||
E('span', {}, successMsg),
|
]);
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (opts?.reloadPage) {
|
if (opts?.reloadPage) {
|
||||||
let secondsLeft = 3;
|
let secondsLeft = 3;
|
||||||
const reloadBtnText = () =>
|
const reloadBtnText = () =>
|
||||||
`${_('Reloading page in')} ${secondsLeft}s...`;
|
`${_('Reloading page in')} ${secondsLeft}s...`;
|
||||||
|
|
||||||
const closeBtn = renderButton({
|
const reloadBtn = renderButton({
|
||||||
classNames: ['cbi-button-save'],
|
classNames: ['cbi-button-save'],
|
||||||
text: reloadBtnText(),
|
text: reloadBtnText(),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@@ -360,7 +336,14 @@ export function showUpdateProgressModal(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
actionButtonContainer.replaceChildren(closeBtn);
|
actionButtonContainer.replaceChildren(
|
||||||
|
bannerEl,
|
||||||
|
E(
|
||||||
|
'div',
|
||||||
|
{ class: 'tachyon-update-modal__action-buttons' },
|
||||||
|
[reloadBtn],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const reloadTimer = setInterval(() => {
|
const reloadTimer = setInterval(() => {
|
||||||
secondsLeft -= 1;
|
secondsLeft -= 1;
|
||||||
@@ -368,7 +351,7 @@ export function showUpdateProgressModal(
|
|||||||
clearInterval(reloadTimer);
|
clearInterval(reloadTimer);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
closeBtn.textContent = reloadBtnText();
|
reloadBtn.textContent = reloadBtnText();
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
@@ -395,13 +378,20 @@ export function showUpdateProgressModal(
|
|||||||
});
|
});
|
||||||
actionButtons.push(closeBtn);
|
actionButtons.push(closeBtn);
|
||||||
|
|
||||||
actionButtonContainer.replaceChildren(...actionButtons);
|
actionButtonContainer.replaceChildren(
|
||||||
|
bannerEl,
|
||||||
|
E(
|
||||||
|
'div',
|
||||||
|
{ class: 'tachyon-update-modal__action-buttons' },
|
||||||
|
actionButtons,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const defaultAutoCloseMs = opts?.onInstall
|
const defaultAutoCloseMs = opts?.onInstall
|
||||||
? 0
|
? 0
|
||||||
: isCheckAction
|
: isCheckAction
|
||||||
? 1200
|
? 1200
|
||||||
: 1200;
|
: 0;
|
||||||
const autoCloseMs = opts?.autoCloseMs ?? defaultAutoCloseMs;
|
const autoCloseMs = opts?.autoCloseMs ?? defaultAutoCloseMs;
|
||||||
if (autoCloseMs > 0) {
|
if (autoCloseMs > 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -419,12 +409,10 @@ export function showUpdateProgressModal(
|
|||||||
cleanupTimers();
|
cleanupTimers();
|
||||||
finishLogTracking();
|
finishLogTracking();
|
||||||
|
|
||||||
actionButtonContainer.replaceChildren(
|
const bannerEl = E('div', { class: 'tachyon-update-modal__error-banner' }, [
|
||||||
E('div', { class: 'tachyon-update-modal__error-banner' }, [
|
renderXIcon24(),
|
||||||
renderXIcon24(),
|
E('span', {}, errorMessage || _('Operation failed')),
|
||||||
E('span', {}, errorMessage || _('Operation failed')),
|
]);
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const closeBtn = renderButton({
|
const closeBtn = renderButton({
|
||||||
classNames: ['cbi-button-remove'],
|
classNames: ['cbi-button-remove'],
|
||||||
@@ -434,7 +422,14 @@ export function showUpdateProgressModal(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
actionButtonContainer.appendChild(closeBtn);
|
actionButtonContainer.replaceChildren(
|
||||||
|
bannerEl,
|
||||||
|
E(
|
||||||
|
'div',
|
||||||
|
{ class: 'tachyon-update-modal__action-buttons' },
|
||||||
|
[closeBtn],
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
startLogTracking: (jobId: string) => {
|
startLogTracking: (jobId: string) => {
|
||||||
if (!jobId || logTrackingJobId === jobId) {
|
if (!jobId || logTrackingJobId === jobId) {
|
||||||
|
|||||||
@@ -253,12 +253,20 @@ export const styles = `
|
|||||||
|
|
||||||
.tachyon-update-modal__actions {
|
.tachyon-update-modal__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
border-top: 1px solid var(--border-color, rgba(255,255,255,0.12));
|
border-top: 1px solid var(--border-color, rgba(255,255,255,0.12));
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tachyon-update-modal__action-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tachyon-update-modal__log-panel {
|
.tachyon-update-modal__log-panel {
|
||||||
|
|||||||
@@ -107,10 +107,11 @@ describe('renderUpdateProgressModal', () => {
|
|||||||
vi.advanceTimersByTime(5000);
|
vi.advanceTimersByTime(5000);
|
||||||
expect(mockUi.hideModal).not.toHaveBeenCalled();
|
expect(mockUi.hideModal).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
controller.close();
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('auto-closes after completed installation without hanging', async () => {
|
it('keeps completed installation open until user closes or autoCloseMs is explicit', async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
mockUi.showModal.mockReset();
|
mockUi.showModal.mockReset();
|
||||||
mockUi.hideModal.mockReset();
|
mockUi.hideModal.mockReset();
|
||||||
@@ -127,8 +128,12 @@ describe('renderUpdateProgressModal', () => {
|
|||||||
|
|
||||||
controller.completeSuccess('Update completed successfully!');
|
controller.completeSuccess('Update completed successfully!');
|
||||||
|
|
||||||
// Advance timer past autoCloseMs (1200ms)
|
// Modal stays open so user can inspect log
|
||||||
vi.advanceTimersByTime(1500);
|
vi.advanceTimersByTime(5000);
|
||||||
|
expect(mockUi.hideModal).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Explicit close works
|
||||||
|
controller.close();
|
||||||
expect(mockUi.hideModal).toHaveBeenCalled();
|
expect(mockUi.hideModal).toHaveBeenCalled();
|
||||||
|
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
|||||||
@@ -16008,7 +16008,7 @@ function showUpdateProgressModal(options) {
|
|||||||
]);
|
]);
|
||||||
const logPreEl = E("pre", {
|
const logPreEl = E("pre", {
|
||||||
class: "tachyon-update-modal__log"
|
class: "tachyon-update-modal__log"
|
||||||
});
|
}, _("Operation started. Waiting for log output..."));
|
||||||
const logPanelEl = E("div", { class: "tachyon-update-modal__log-panel" }, [
|
const logPanelEl = E("div", { class: "tachyon-update-modal__log-panel" }, [
|
||||||
E("div", { class: "tachyon-update-modal__log-header" }, [
|
E("div", { class: "tachyon-update-modal__log-header" }, [
|
||||||
E("b", {}, _("Operation log")),
|
E("b", {}, _("Operation log")),
|
||||||
@@ -16058,7 +16058,6 @@ function showUpdateProgressModal(options) {
|
|||||||
let logFullText = "";
|
let logFullText = "";
|
||||||
let logPollTimer = null;
|
let logPollTimer = null;
|
||||||
let logPollStopped = true;
|
let logPollStopped = true;
|
||||||
let autoCompletedFromLog = false;
|
|
||||||
function appendLogText(text) {
|
function appendLogText(text) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return;
|
return;
|
||||||
@@ -16066,18 +16065,6 @@ function showUpdateProgressModal(options) {
|
|||||||
logFullText += text;
|
logFullText += text;
|
||||||
logPreEl.textContent = logFullText;
|
logPreEl.textContent = logFullText;
|
||||||
logPreEl.scrollTop = logPreEl.scrollHeight;
|
logPreEl.scrollTop = logPreEl.scrollHeight;
|
||||||
if (!autoCompletedFromLog && options.component === "tachyon" && (options.action === "install" || options.action === "reinstall")) {
|
|
||||||
if (logFullText.includes("Tachyon updated to") || logFullText.includes("Start Tachyon")) {
|
|
||||||
autoCompletedFromLog = true;
|
|
||||||
window.setTimeout(() => {
|
|
||||||
if (activeModalController === controller) {
|
|
||||||
controller.completeSuccess(_("Tachyon updated successfully!"), {
|
|
||||||
reloadPage: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 1e3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
async function pollLog() {
|
async function pollLog() {
|
||||||
if (logPollStopped || !logTrackingJobId) {
|
if (logPollStopped || !logTrackingJobId) {
|
||||||
@@ -16155,31 +16142,36 @@ function showUpdateProgressModal(options) {
|
|||||||
completeSuccess: (message, opts) => {
|
completeSuccess: (message, opts) => {
|
||||||
cleanupTimers();
|
cleanupTimers();
|
||||||
finishLogTracking();
|
finishLogTracking();
|
||||||
const successMsg = message || (isCheckAction ? _("Check completed!") : options.action === "remove" ? _("Removal completed successfully!") : _("Update completed successfully!"));
|
const successMsg = message || (isCheckAction ? _("Check completed!") : options.action === "remove" ? _("Removal completed successfully!") : _("Operation completed successfully!"));
|
||||||
actionButtonContainer.replaceChildren(
|
const bannerEl = E("div", { class: "tachyon-update-modal__success-banner" }, [
|
||||||
E("div", { class: "tachyon-update-modal__success-banner" }, [
|
renderCheckIcon24(),
|
||||||
renderCheckIcon24(),
|
E("span", {}, successMsg)
|
||||||
E("span", {}, successMsg)
|
]);
|
||||||
])
|
|
||||||
);
|
|
||||||
if (opts?.reloadPage) {
|
if (opts?.reloadPage) {
|
||||||
let secondsLeft = 3;
|
let secondsLeft = 3;
|
||||||
const reloadBtnText = () => `${_("Reloading page in")} ${secondsLeft}s...`;
|
const reloadBtnText = () => `${_("Reloading page in")} ${secondsLeft}s...`;
|
||||||
const closeBtn = renderButton({
|
const reloadBtn = renderButton({
|
||||||
classNames: ["cbi-button-save"],
|
classNames: ["cbi-button-save"],
|
||||||
text: reloadBtnText(),
|
text: reloadBtnText(),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
actionButtonContainer.replaceChildren(closeBtn);
|
actionButtonContainer.replaceChildren(
|
||||||
|
bannerEl,
|
||||||
|
E(
|
||||||
|
"div",
|
||||||
|
{ class: "tachyon-update-modal__action-buttons" },
|
||||||
|
[reloadBtn]
|
||||||
|
)
|
||||||
|
);
|
||||||
const reloadTimer = setInterval(() => {
|
const reloadTimer = setInterval(() => {
|
||||||
secondsLeft -= 1;
|
secondsLeft -= 1;
|
||||||
if (secondsLeft <= 0) {
|
if (secondsLeft <= 0) {
|
||||||
clearInterval(reloadTimer);
|
clearInterval(reloadTimer);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
closeBtn.textContent = reloadBtnText();
|
reloadBtn.textContent = reloadBtnText();
|
||||||
}
|
}
|
||||||
}, 1e3);
|
}, 1e3);
|
||||||
} else {
|
} else {
|
||||||
@@ -16203,8 +16195,15 @@ function showUpdateProgressModal(options) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
actionButtons.push(closeBtn);
|
actionButtons.push(closeBtn);
|
||||||
actionButtonContainer.replaceChildren(...actionButtons);
|
actionButtonContainer.replaceChildren(
|
||||||
const defaultAutoCloseMs = opts?.onInstall ? 0 : isCheckAction ? 1200 : 1200;
|
bannerEl,
|
||||||
|
E(
|
||||||
|
"div",
|
||||||
|
{ class: "tachyon-update-modal__action-buttons" },
|
||||||
|
actionButtons
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const defaultAutoCloseMs = opts?.onInstall ? 0 : isCheckAction ? 1200 : 0;
|
||||||
const autoCloseMs = opts?.autoCloseMs ?? defaultAutoCloseMs;
|
const autoCloseMs = opts?.autoCloseMs ?? defaultAutoCloseMs;
|
||||||
if (autoCloseMs > 0) {
|
if (autoCloseMs > 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -16218,12 +16217,10 @@ function showUpdateProgressModal(options) {
|
|||||||
completeError: (errorMessage) => {
|
completeError: (errorMessage) => {
|
||||||
cleanupTimers();
|
cleanupTimers();
|
||||||
finishLogTracking();
|
finishLogTracking();
|
||||||
actionButtonContainer.replaceChildren(
|
const bannerEl = E("div", { class: "tachyon-update-modal__error-banner" }, [
|
||||||
E("div", { class: "tachyon-update-modal__error-banner" }, [
|
renderXIcon24(),
|
||||||
renderXIcon24(),
|
E("span", {}, errorMessage || _("Operation failed"))
|
||||||
E("span", {}, errorMessage || _("Operation failed"))
|
]);
|
||||||
])
|
|
||||||
);
|
|
||||||
const closeBtn = renderButton({
|
const closeBtn = renderButton({
|
||||||
classNames: ["cbi-button-remove"],
|
classNames: ["cbi-button-remove"],
|
||||||
text: _("Close"),
|
text: _("Close"),
|
||||||
@@ -16231,7 +16228,14 @@ function showUpdateProgressModal(options) {
|
|||||||
controller.close();
|
controller.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
actionButtonContainer.appendChild(closeBtn);
|
actionButtonContainer.replaceChildren(
|
||||||
|
bannerEl,
|
||||||
|
E(
|
||||||
|
"div",
|
||||||
|
{ class: "tachyon-update-modal__action-buttons" },
|
||||||
|
[closeBtn]
|
||||||
|
)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
startLogTracking: (jobId) => {
|
startLogTracking: (jobId) => {
|
||||||
if (!jobId || logTrackingJobId === jobId) {
|
if (!jobId || logTrackingJobId === jobId) {
|
||||||
@@ -16630,8 +16634,11 @@ async function applyCompletedComponentAction({
|
|||||||
showToast(result.message, "success", 1200);
|
showToast(result.message, "success", 1200);
|
||||||
}
|
}
|
||||||
if (notify) {
|
if (notify) {
|
||||||
modalController?.completeSuccess(result.message, { reloadPage: true });
|
if (modalController) {
|
||||||
reloadPageAfterTachyonUpdate();
|
modalController.completeSuccess(result.message, { reloadPage: true });
|
||||||
|
} else {
|
||||||
|
reloadPageAfterTachyonUpdate();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
modalController?.completeSuccess(result.message);
|
modalController?.completeSuccess(result.message);
|
||||||
}
|
}
|
||||||
@@ -17696,12 +17703,20 @@ var styles6 = `
|
|||||||
|
|
||||||
.tachyon-update-modal__actions {
|
.tachyon-update-modal__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
border-top: 1px solid var(--border-color, rgba(255,255,255,0.12));
|
border-top: 1px solid var(--border-color, rgba(255,255,255,0.12));
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tachyon-update-modal__action-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tachyon-update-modal__log-panel {
|
.tachyon-update-modal__log-panel {
|
||||||
|
|||||||
Reference in New Issue
Block a user