Preserve action button state across tabs

This commit is contained in:
ushan0v
2026-05-24 20:57:51 +03:00
parent 6f2a893bc2
commit d1fbf3f2c7
10 changed files with 760 additions and 841 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+10 -16
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-23 00:44+0300\n"
"PO-Revision-Date: 2026-05-23 00:44+0300\n"
"POT-Creation-Date: 2026-05-24 20:56+0300\n"
"PO-Revision-Date: 2026-05-24 20:56+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -185,15 +185,6 @@ msgstr "Закрыть подключение"
msgid "Closed"
msgstr "Закрытые"
msgid "Component action completed"
msgstr "Действие выполнено"
msgid "Component action failed"
msgstr "Не удалось выполнить действие"
msgid "Component action timed out"
msgstr "Время ожидания действия компонента истекло"
msgid "Conditions"
msgstr "Условия"
@@ -413,6 +404,9 @@ msgstr "Не удалось закрыть подключения"
msgid "Failed to copy!"
msgstr "Не удалось скопировать!"
msgid "Failed to execute"
msgstr "Не удалось выполнить"
msgid "Failed to execute!"
msgstr "Не удалось выполнить!"
@@ -464,8 +458,8 @@ msgstr "Установить extended"
msgid "Install stable"
msgstr "Установить stable"
msgid "Installed version is newer than upstream release"
msgstr "Установлена версия новее upstream-релиза"
msgid "Installed version is newer than release"
msgstr "Установлена версия новее релиза"
msgid "Interface Monitoring"
msgstr "Мониторинг интерфейса"
@@ -629,6 +623,9 @@ msgstr "Ключевое слово не должно содержать про
msgid "Latest"
msgstr "Последняя"
msgid "Latest version is installed"
msgstr "Установлена последняя версия"
msgid "List must be an HTTP(S) URL or a local .lst path"
msgstr "Список должен быть HTTP(S) URL или локальным путём .lst"
@@ -1094,9 +1091,6 @@ msgstr "Desync fwmark управляется Podkop Plus для защиты о
msgid "The DNS server used to look up the IP address of an upstream DNS server"
msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера"
msgid "The latest version is installed"
msgstr "Установлена текущая последняя версия"
msgid "The NFQUEUE number is assigned by Podkop Plus for each rule and must not be overridden here."
msgstr "Номер NFQUEUE назначается Podkop Plus для каждого правила и не должен переопределяться здесь."
@@ -56,7 +56,7 @@ function componentActionFailure(
error:
parsedResponse?.message ||
response.stderr ||
_('Component action failed'),
_('Failed to execute'),
} as Podkop.MethodFailureResponse;
}
@@ -252,7 +252,7 @@ export const PodkopShellMethods = {
return {
success: false,
error: _('Component action timed out'),
error: _('Operation timed out'),
} as Podkop.MethodFailureResponse;
},
subscriptionUpdate: async (section?: string, sourceIndex?: number) => {
@@ -127,11 +127,13 @@ function setDisplayedPodkopRunning(running: boolean) {
async function refreshDiagnosticServicesInfo({
force = false,
mountId = diagnosticMountId,
allowInactive = false,
}: {
force?: boolean;
mountId?: number;
allowInactive?: boolean;
} = {}) {
if (!isDiagnosticMountActive(mountId)) {
if (!allowInactive && !isDiagnosticMountActive(mountId)) {
return;
}
@@ -161,14 +163,11 @@ async function refreshDiagnosticServicesInfo({
return promise;
}
async function waitForPodkopRunningState(
expectedRunning: boolean,
mountId: number,
) {
async function waitForPodkopRunningState(expectedRunning: boolean) {
const startedAt = Date.now();
while (isDiagnosticMountActive(mountId)) {
await refreshDiagnosticServicesInfo({ force: true, mountId });
while (Date.now() - startedAt < SERVICE_ACTION_STATUS_TIMEOUT_MS) {
await refreshDiagnosticServicesInfo({ force: true, allowInactive: true });
const podkopRunning = Boolean(
store.get().servicesInfoWidget.data.podkopRunning,
@@ -178,10 +177,6 @@ async function waitForPodkopRunningState(
return true;
}
if (Date.now() - startedAt >= SERVICE_ACTION_STATUS_TIMEOUT_MS) {
return false;
}
await sleep(SERVICE_STATUS_REFRESH_INTERVAL_MS);
}
@@ -336,7 +331,7 @@ async function handleServiceRuntimeAction({
expectedRunning: boolean;
optimisticRunning?: boolean;
}) {
const mountId = diagnosticMountId;
const actionMountId = diagnosticMountId;
setDiagnosticActionLoading(action, true);
@@ -347,12 +342,10 @@ async function handleServiceRuntimeAction({
try {
await command();
const reachedExpectedState = await waitForPodkopRunningState(
expectedRunning,
mountId,
);
const reachedExpectedState =
await waitForPodkopRunningState(expectedRunning);
if (!reachedExpectedState && isDiagnosticMountActive(mountId)) {
if (!reachedExpectedState && isDiagnosticMountActive(actionMountId)) {
logger.error(
'[DIAGNOSTIC]',
`${action} did not reach expected running state`,
@@ -362,10 +355,8 @@ async function handleServiceRuntimeAction({
} catch (e) {
logger.error('[DIAGNOSTIC]', `handleServiceRuntimeAction(${action})`, e);
} finally {
if (isDiagnosticMountActive(mountId)) {
setDiagnosticActionLoading(action, false);
resetDiagnosticsChecks();
}
setDiagnosticActionLoading(action, false);
resetDiagnosticsChecks();
}
}
@@ -395,8 +386,6 @@ async function handleStop() {
}
async function handleEnable() {
const mountId = diagnosticMountId;
setDiagnosticActionLoading('enable', true);
try {
@@ -404,17 +393,15 @@ async function handleEnable() {
} catch (e) {
logger.error('[DIAGNOSTIC]', 'handleEnable - e', e);
} finally {
await refreshDiagnosticServicesInfo({ force: true, mountId });
if (isDiagnosticMountActive(mountId)) {
setDiagnosticActionLoading('enable', false);
}
await refreshDiagnosticServicesInfo({
force: true,
allowInactive: true,
});
setDiagnosticActionLoading('enable', false);
}
}
async function handleDisable() {
const mountId = diagnosticMountId;
setDiagnosticActionLoading('disable', true);
try {
@@ -422,11 +409,11 @@ async function handleDisable() {
} catch (e) {
logger.error('[DIAGNOSTIC]', 'handleDisable - e', e);
} finally {
await refreshDiagnosticServicesInfo({ force: true, mountId });
if (isDiagnosticMountActive(mountId)) {
setDiagnosticActionLoading('disable', false);
}
await refreshDiagnosticServicesInfo({
force: true,
allowInactive: true,
});
setDiagnosticActionLoading('disable', false);
}
}
@@ -785,7 +772,7 @@ function onPageUnmount() {
store.unsubscribe(onStoreUpdate);
// Clear store
store.reset(['diagnosticsActions', 'diagnosticsRunAction']);
store.reset(['diagnosticsRunAction']);
resetDiagnosticsChecks();
}
@@ -126,10 +126,10 @@ function getCheckToastMessage(status: UpdateStatus) {
}
if (status === 'dev') {
return _('Installed version is newer than upstream release');
return _('Installed version is newer than release');
}
return _('The latest version is installed');
return _('Latest version is installed');
}
async function refreshSystemInfoAfterMutation() {
@@ -229,7 +229,7 @@ async function handleComponentAction(button: ComponentActionButton) {
if (!response.success) {
setActionLoading(button.key, false);
showToast(response.error || _('Component action failed'), 'error');
showToast(response.error || _('Failed to execute'), 'error');
return;
}
@@ -257,21 +257,23 @@ async function handleComponentAction(button: ComponentActionButton) {
setActionLoading(button.key, false);
if (result.component === 'podkop' && result.action === 'install') {
showToast(
result.message || _('Component action completed'),
'success',
1200,
);
if (result.message) {
showToast(result.message, 'success', 1200);
}
reloadPageAfterPodkopUpdate();
return;
}
showToast(result.message || _('Component action completed'), 'success');
if (result.message) {
showToast(result.message, 'success');
}
void refreshSystemInfoAfterMutation();
} catch (error) {
logger.error('[UPDATES]', 'handleComponentAction failed', error);
setActionLoading(button.key, false);
showToast(_('Component action failed'), 'error');
showToast(_('Failed to execute'), 'error');
}
}
@@ -515,7 +517,6 @@ function onPageMount() {
function onPageUnmount() {
updatesMounted = false;
store.unsubscribe(onStoreUpdate);
store.reset(['updatesActions', 'updatesChecks']);
}
function registerLifecycleListeners() {
@@ -808,7 +808,7 @@ function parseComponentActionStartResult(response) {
function componentActionFailure(response, parsedResponse) {
return {
success: false,
error: parsedResponse?.message || response.stderr || _("Component action failed")
error: parsedResponse?.message || response.stderr || _("Failed to execute")
};
}
var PodkopShellMethods = {
@@ -956,7 +956,7 @@ var PodkopShellMethods = {
}
return {
success: false,
error: _("Component action timed out")
error: _("Operation timed out")
};
},
subscriptionUpdate: async (section, sourceIndex) => {
@@ -5835,9 +5835,10 @@ function setDisplayedPodkopRunning(running) {
}
async function refreshDiagnosticServicesInfo({
force = false,
mountId = diagnosticMountId
mountId = diagnosticMountId,
allowInactive = false
} = {}) {
if (!isDiagnosticMountActive(mountId)) {
if (!allowInactive && !isDiagnosticMountActive(mountId)) {
return;
}
if (!force && isMutatingServiceActionLoading()) {
@@ -5860,19 +5861,16 @@ async function refreshDiagnosticServicesInfo({
servicesInfoRefreshPromise = promise;
return promise;
}
async function waitForPodkopRunningState(expectedRunning, mountId) {
async function waitForPodkopRunningState(expectedRunning) {
const startedAt = Date.now();
while (isDiagnosticMountActive(mountId)) {
await refreshDiagnosticServicesInfo({ force: true, mountId });
while (Date.now() - startedAt < SERVICE_ACTION_STATUS_TIMEOUT_MS) {
await refreshDiagnosticServicesInfo({ force: true, allowInactive: true });
const podkopRunning = Boolean(
store.get().servicesInfoWidget.data.podkopRunning
);
if (podkopRunning === expectedRunning) {
return true;
}
if (Date.now() - startedAt >= SERVICE_ACTION_STATUS_TIMEOUT_MS) {
return false;
}
await sleep2(SERVICE_STATUS_REFRESH_INTERVAL_MS);
}
return false;
@@ -5989,18 +5987,15 @@ async function handleServiceRuntimeAction({
expectedRunning,
optimisticRunning
}) {
const mountId = diagnosticMountId;
const actionMountId = diagnosticMountId;
setDiagnosticActionLoading(action, true);
if (optimisticRunning !== void 0) {
setDisplayedPodkopRunning(optimisticRunning);
}
try {
await command();
const reachedExpectedState = await waitForPodkopRunningState(
expectedRunning,
mountId
);
if (!reachedExpectedState && isDiagnosticMountActive(mountId)) {
const reachedExpectedState = await waitForPodkopRunningState(expectedRunning);
if (!reachedExpectedState && isDiagnosticMountActive(actionMountId)) {
logger.error(
"[DIAGNOSTIC]",
`${action} did not reach expected running state`
@@ -6010,10 +6005,8 @@ async function handleServiceRuntimeAction({
} catch (e) {
logger.error("[DIAGNOSTIC]", `handleServiceRuntimeAction(${action})`, e);
} finally {
if (isDiagnosticMountActive(mountId)) {
setDiagnosticActionLoading(action, false);
resetDiagnosticsChecks();
}
setDiagnosticActionLoading(action, false);
resetDiagnosticsChecks();
}
}
async function handleRestart() {
@@ -6039,31 +6032,31 @@ async function handleStop() {
});
}
async function handleEnable() {
const mountId = diagnosticMountId;
setDiagnosticActionLoading("enable", true);
try {
await PodkopShellMethods.enable();
} catch (e) {
logger.error("[DIAGNOSTIC]", "handleEnable - e", e);
} finally {
await refreshDiagnosticServicesInfo({ force: true, mountId });
if (isDiagnosticMountActive(mountId)) {
setDiagnosticActionLoading("enable", false);
}
await refreshDiagnosticServicesInfo({
force: true,
allowInactive: true
});
setDiagnosticActionLoading("enable", false);
}
}
async function handleDisable() {
const mountId = diagnosticMountId;
setDiagnosticActionLoading("disable", true);
try {
await PodkopShellMethods.disable();
} catch (e) {
logger.error("[DIAGNOSTIC]", "handleDisable - e", e);
} finally {
await refreshDiagnosticServicesInfo({ force: true, mountId });
if (isDiagnosticMountActive(mountId)) {
setDiagnosticActionLoading("disable", false);
}
await refreshDiagnosticServicesInfo({
force: true,
allowInactive: true
});
setDiagnosticActionLoading("disable", false);
}
}
async function handleShowGlobalCheck() {
@@ -6348,7 +6341,7 @@ function onPageUnmount2() {
stopServicesInfoRefreshTimer();
servicesInfoRefreshPromise = null;
store.unsubscribe(onStoreUpdate2);
store.reset(["diagnosticsActions", "diagnosticsRunAction"]);
store.reset(["diagnosticsRunAction"]);
resetDiagnosticsChecks();
}
function registerLifecycleListeners2() {
@@ -8339,9 +8332,9 @@ function getCheckToastMessage(status) {
return _("Update is available");
}
if (status === "dev") {
return _("Installed version is newer than upstream release");
return _("Installed version is newer than release");
}
return _("The latest version is installed");
return _("Latest version is installed");
}
async function refreshSystemInfoAfterMutation() {
await ensureSystemInfo({ force: true, silent: true });
@@ -8419,7 +8412,7 @@ async function handleComponentAction(button) {
);
if (!response.success) {
setActionLoading(button.key, false);
showToast(response.error || _("Component action failed"), "error");
showToast(response.error || _("Failed to execute"), "error");
return;
}
const result = response.data;
@@ -8440,20 +8433,20 @@ async function handleComponentAction(button) {
patchSystemInfoAfterMutation(result);
setActionLoading(button.key, false);
if (result.component === "podkop" && result.action === "install") {
showToast(
result.message || _("Component action completed"),
"success",
1200
);
if (result.message) {
showToast(result.message, "success", 1200);
}
reloadPageAfterPodkopUpdate();
return;
}
showToast(result.message || _("Component action completed"), "success");
if (result.message) {
showToast(result.message, "success");
}
void refreshSystemInfoAfterMutation();
} catch (error) {
logger.error("[UPDATES]", "handleComponentAction failed", error);
setActionLoading(button.key, false);
showToast(_("Component action failed"), "error");
showToast(_("Failed to execute"), "error");
}
}
function getPrimaryUpdateAction(component, checkKey, installKey) {
@@ -8648,7 +8641,6 @@ function onPageMount4() {
function onPageUnmount4() {
updatesMounted = false;
store.unsubscribe(onStoreUpdate3);
store.reset(["updatesActions", "updatesChecks"]);
}
function registerLifecycleListeners4() {
if (updatesLifecycleRegistered) {
+10 -16
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-23 00:44+0300\n"
"PO-Revision-Date: 2026-05-23 00:44+0300\n"
"POT-Creation-Date: 2026-05-24 20:56+0300\n"
"PO-Revision-Date: 2026-05-24 20:56+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -185,15 +185,6 @@ msgstr "Закрыть подключение"
msgid "Closed"
msgstr "Закрытые"
msgid "Component action completed"
msgstr "Действие выполнено"
msgid "Component action failed"
msgstr "Не удалось выполнить действие"
msgid "Component action timed out"
msgstr "Время ожидания действия компонента истекло"
msgid "Conditions"
msgstr "Условия"
@@ -413,6 +404,9 @@ msgstr "Не удалось закрыть подключения"
msgid "Failed to copy!"
msgstr "Не удалось скопировать!"
msgid "Failed to execute"
msgstr "Не удалось выполнить"
msgid "Failed to execute!"
msgstr "Не удалось выполнить!"
@@ -464,8 +458,8 @@ msgstr "Установить extended"
msgid "Install stable"
msgstr "Установить stable"
msgid "Installed version is newer than upstream release"
msgstr "Установлена версия новее upstream-релиза"
msgid "Installed version is newer than release"
msgstr "Установлена версия новее релиза"
msgid "Interface Monitoring"
msgstr "Мониторинг интерфейса"
@@ -629,6 +623,9 @@ msgstr "Ключевое слово не должно содержать про
msgid "Latest"
msgstr "Последняя"
msgid "Latest version is installed"
msgstr "Установлена последняя версия"
msgid "List must be an HTTP(S) URL or a local .lst path"
msgstr "Список должен быть HTTP(S) URL или локальным путём .lst"
@@ -1094,9 +1091,6 @@ msgstr "Desync fwmark управляется Podkop Plus для защиты о
msgid "The DNS server used to look up the IP address of an upstream DNS server"
msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера"
msgid "The latest version is installed"
msgstr "Установлена текущая последняя версия"
msgid "The NFQUEUE number is assigned by Podkop Plus for each rule and must not be overridden here."
msgstr "Номер NFQUEUE назначается Podkop Plus для каждого правила и не должен переопределяться здесь."
File diff suppressed because it is too large Load Diff
+6 -28
View File
@@ -217,7 +217,7 @@ updates_write_finished_job_state() {
rm -f "$json_file"
raw_output="$(tr '\n' ' ' <"$output_file" 2>/dev/null | cut -c1-240)"
[ -n "$raw_output" ] || raw_output="Component action failed"
[ -n "$raw_output" ] || raw_output="Failed to execute"
jq -cn \
--argjson success false \
@@ -577,7 +577,7 @@ updates_check_success_compared() {
case "$status" in
latest)
message="The latest version is installed"
message="Latest version is installed"
updates_log "$component is up to date ($current_version)"
;;
outdated)
@@ -585,7 +585,7 @@ updates_check_success_compared() {
updates_log "$component update is available: $current_version -> $latest_version"
;;
dev)
message="Installed version is newer than upstream release"
message="Installed version is newer than release"
updates_log "$component installed version is newer than upstream release: $current_version -> $latest_version"
;;
esac
@@ -641,8 +641,6 @@ updates_capture_podkop_running_state() {
}
updates_restart_podkop_after_successful_change() {
local waited=0
[ -x "$PODKOP_SERVICE_INIT" ] || return 0
if [ "$UPDATES_PODKOP_WAS_RUNNING" != "1" ]; then
@@ -651,17 +649,6 @@ updates_restart_podkop_after_successful_change() {
fi
updates_log_command "Restarting Podkop Plus after successful component change" "$PODKOP_SERVICE_INIT" restart || true
while [ "$waited" -lt 60 ]; do
if "$PODKOP_SERVICE_INIT" status >/dev/null 2>&1; then
return 0
fi
sleep 1
waited=$((waited + 1))
done
updates_log "Podkop Plus did not report running after component change restart" "warn"
}
updates_append_arch_candidate() {
@@ -1457,15 +1444,6 @@ fi
if [ "$podkop_was_running" = "1" ] && [ -x /etc/init.d/podkop-plus ]; then
log_line "Restarting Podkop Plus after successful component change" "info"
/etc/init.d/podkop-plus restart >/dev/null 2>&1 || log_line "Podkop Plus restart command failed" "warn"
waited=0
while [ "$waited" -lt 60 ]; do
if /etc/init.d/podkop-plus status >/dev/null 2>&1; then
break
fi
sleep 1
waited=$((waited + 1))
done
else
log_line "Podkop Plus was not running before component change; restart skipped" "info"
fi
@@ -1499,7 +1477,7 @@ updates_check_podkop_plus() {
if ! is_podkop_release_version "$PODKOP_VERSION"; then
updates_log "Podkop Plus current version is not a release version ($PODKOP_VERSION)"
updates_success "podkop" "check_update" "Installed version is newer than upstream release" "$PODKOP_VERSION" "$latest_version" 0 "dev"
updates_success "podkop" "check_update" "Installed version is newer than release" "$PODKOP_VERSION" "$latest_version" 0 "dev"
fi
compare_result="$(podkop_release_version_compare "$PODKOP_VERSION" "$latest_version" 2>/dev/null || true)"
@@ -1510,7 +1488,7 @@ updates_check_podkop_plus() {
status="$(updates_status_from_compare "$compare_result")" || updates_fail "podkop" "check_update" "Failed to compare Podkop Plus versions" "$PODKOP_VERSION" "$latest_version"
case "$status" in
latest)
message="The latest version is installed"
message="Latest version is installed"
updates_log "Podkop Plus is already up to date ($PODKOP_VERSION)"
;;
outdated)
@@ -1518,7 +1496,7 @@ updates_check_podkop_plus() {
updates_log "Podkop Plus update found: $PODKOP_VERSION -> $latest_version"
;;
dev)
message="Installed version is newer than upstream release"
message="Installed version is newer than release"
updates_log "Podkop Plus installed version is newer than upstream release: $PODKOP_VERSION -> $latest_version"
;;
esac