Files
NeoTachyon/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/tachyon.js
T

495 lines
15 KiB
JavaScript

"use strict";
"require view";
"require form";
"require baseclass";
"require uci";
"require ui";
"require view.tachyon.main as main";
// Global settings
"require view.tachyon.settings as settings";
// Sections
"require view.tachyon.section as section";
// Server
"require view.tachyon.server as server";
// Dashboard
"require view.tachyon.dashboard as dashboard";
// Monitoring
"require view.tachyon.monitoring as monitoring";
// Diagnostic
"require view.tachyon.diagnostic as diagnostic";
// Updates
"require view.tachyon.updates as updates";
const UCI_PACKAGE = main.TACHYON_UCI_PACKAGE;
const CBI_PREFIX = UCI_PACKAGE;
function renderSectionAdd(sectionRef, extra_class) {
const el = form.GridSection.prototype.renderSectionAdd.apply(sectionRef, [
extra_class,
]);
const nameEl = el.querySelector(".cbi-section-create-name");
ui.addValidator(
nameEl,
"uciname",
true,
(value) => {
const button = el.querySelector(".cbi-section-create > .cbi-button-add");
const uciconfig = sectionRef.uciconfig || sectionRef.map.config;
if (!value) {
button.disabled = true;
return true;
}
if (uci.get(uciconfig, value)) {
button.disabled = true;
return _("Expecting: %s").format(_("unique UCI identifier"));
}
button.disabled = null;
return true;
},
"blur",
"keyup",
);
return el;
}
function getRuleEditButtonText() {
const label = _("Edit rule action");
return label === "Edit rule action" ? "Edit" : label;
}
function configureGridSection(sectionRef, type, title, addTitle) {
sectionRef.anonymous = false;
sectionRef.addremove = true;
sectionRef.sortable = true;
sectionRef.rowcolors = true;
sectionRef.nodescriptions = true;
sectionRef.modaltitle = function (section_id) {
const label = uci.get(UCI_PACKAGE, section_id, "label");
return section_id ? `${title}: ${label || section_id}` : addTitle;
};
sectionRef.sectiontitle = function (section_id) {
return uci.get(UCI_PACKAGE, section_id, "label") || section_id;
};
sectionRef.renderSectionAdd = function (extra_class) {
return renderSectionAdd(sectionRef, extra_class);
};
if (type === "section") {
sectionRef.renderRowActions = function (section_id) {
return form.TableSection.prototype.renderRowActions.call(
this,
section_id,
getRuleEditButtonText(),
);
};
}
}
const EntryPoint = {
async render() {
main.injectGlobalStyles();
const uiCapabilities = {
loaded: false,
singBoxExtended: false,
singBoxTiny: false,
singBoxTailscale: true,
zapretInstalled: false,
zapret2Installed: false,
byedpiInstalled: false,
serverInboundsEnabledCount: -1,
};
let uiCapabilitiesPromise = null;
let serverSectionRef = null;
const applyUiCapabilities = function () {
if (serverSectionRef) {
server.applyServerCapabilities(serverSectionRef, uiCapabilities);
}
if (typeof window !== "undefined") {
window.dispatchEvent(
new CustomEvent(main.TACHYON_ACTION_PROVIDERS_AVAILABILITY_EVENT, {
detail: {
zapretInstalled: uiCapabilities.zapretInstalled,
zapret2Installed: uiCapabilities.zapret2Installed,
byedpiInstalled: uiCapabilities.byedpiInstalled,
},
}),
);
}
if (main.store && typeof main.store.set === "function") {
const currentSystemInfo = main.store.get().diagnosticsSystemInfo;
main.store.set({
diagnosticsSystemInfo: {
...currentSystemInfo,
providerInfoLoaded: true,
sing_box_extended: uiCapabilities.singBoxExtended ? 1 : 0,
sing_box_tiny: uiCapabilities.singBoxTiny ? 1 : 0,
sing_box_tailscale: uiCapabilities.singBoxTailscale ? 1 : 0,
zapret_installed: uiCapabilities.zapretInstalled ? 1 : 0,
zapret2_installed: uiCapabilities.zapret2Installed ? 1 : 0,
byedpi_installed: uiCapabilities.byedpiInstalled ? 1 : 0,
server_inbounds_enabled_count:
uiCapabilities.serverInboundsEnabledCount,
zapret_version: uiCapabilities.zapretInstalled
? currentSystemInfo.zapret_version
: "not installed",
zapret2_version: uiCapabilities.zapret2Installed
? currentSystemInfo.zapret2_version
: "not installed",
byedpi_version: uiCapabilities.byedpiInstalled
? currentSystemInfo.byedpi_version
: "not installed",
},
});
}
};
const updateUiCapabilities = function (data) {
uiCapabilities.loaded = true;
uiCapabilities.singBoxExtended = Boolean(
Number(data?.sing_box_extended) === 1,
);
uiCapabilities.singBoxTiny = Boolean(Number(data?.sing_box_tiny) === 1);
uiCapabilities.singBoxTailscale =
typeof data?.sing_box_tailscale === "undefined"
? true
: Boolean(Number(data.sing_box_tailscale) === 1);
uiCapabilities.zapretInstalled = Boolean(
Number(data?.zapret_installed) === 1,
);
uiCapabilities.zapret2Installed = Boolean(
Number(data?.zapret2_installed) === 1,
);
uiCapabilities.byedpiInstalled = Boolean(
Number(data?.byedpi_installed) === 1,
);
const serverInboundsEnabledCount =
typeof data?.server_inbounds_enabled_count !== "undefined"
? Number(data.server_inbounds_enabled_count)
: -1;
uiCapabilities.serverInboundsEnabledCount = Number.isFinite(
serverInboundsEnabledCount,
)
? serverInboundsEnabledCount
: -1;
applyUiCapabilities();
return uiCapabilities;
};
const applyUiState = function (data) {
const result = updateUiCapabilities(data?.capabilities || data || {});
if (typeof main.applyUiStateToStore === "function" && data?.service) {
main.applyUiStateToStore(data);
} else if (
main.store &&
typeof main.store.set === "function" &&
data?.service
) {
main.store.set({
servicesInfoWidget: {
loading: false,
failed: false,
data: {
singbox: Number(data.service.sing_box?.running) || 0,
tachyonRunning: Number(data.service.tachyon?.running) || 0,
tachyonEnabled: Number(data.service.tachyon?.enabled) || 0,
tachyonStatus: data.service.tachyon?.status || "",
},
},
});
}
return result;
};
const loadFallbackUiCapabilities = function () {
return Promise.allSettled([
main.TachyonShellMethods.getServerCapabilities(),
main.TachyonShellMethods.checkZapretRuntime(),
main.TachyonShellMethods.checkZapret2Runtime(),
main.TachyonShellMethods.checkByedpiRuntime(),
main.TachyonShellMethods.checkInboundsConfig(),
]).then(
([
serverCapabilitiesResult,
zapretRuntimeResult,
zapret2RuntimeResult,
byedpiRuntimeResult,
inboundsConfigResult,
]) => {
const serverCapabilities =
serverCapabilitiesResult.status === "fulfilled"
? serverCapabilitiesResult.value
: null;
const zapretRuntime =
zapretRuntimeResult.status === "fulfilled"
? zapretRuntimeResult.value
: null;
const zapret2Runtime =
zapret2RuntimeResult.status === "fulfilled"
? zapret2RuntimeResult.value
: null;
const byedpiRuntime =
byedpiRuntimeResult.status === "fulfilled"
? byedpiRuntimeResult.value
: null;
const inboundsConfig =
inboundsConfigResult.status === "fulfilled"
? inboundsConfigResult.value
: null;
return updateUiCapabilities({
sing_box_extended:
serverCapabilities?.success &&
Number(serverCapabilities.data?.sing_box_extended) === 1
? 1
: 0,
sing_box_tiny:
serverCapabilities?.success &&
Number(serverCapabilities.data?.sing_box_tiny) === 1
? 1
: 0,
sing_box_tailscale:
!serverCapabilities?.success ||
typeof serverCapabilities.data?.sing_box_tailscale ===
"undefined" ||
Number(serverCapabilities.data?.sing_box_tailscale) === 1
? 1
: 0,
zapret_installed:
zapretRuntime?.success &&
Number(zapretRuntime.data?.zapret_installed) === 1
? 1
: 0,
zapret2_installed:
zapret2Runtime?.success &&
Number(zapret2Runtime.data?.zapret2_installed) === 1
? 1
: 0,
byedpi_installed:
byedpiRuntime?.success &&
Number(byedpiRuntime.data?.byedpi_installed) === 1
? 1
: 0,
server_inbounds_enabled_count:
inboundsConfig?.success &&
typeof inboundsConfig.data?.enabled_count !== "undefined"
? inboundsConfig.data.enabled_count
: -1,
});
},
);
};
const loadUiCapabilities = function () {
if (uiCapabilities.loaded) {
return Promise.resolve(uiCapabilities);
}
if (uiCapabilitiesPromise) {
return uiCapabilitiesPromise;
}
uiCapabilitiesPromise = main.TachyonShellMethods.getUiCapabilities()
.then((response) => {
if (!response?.success) {
throw new Error("UI capabilities request failed");
}
return updateUiCapabilities(response.data);
})
.catch((error) => {
console.warn("Failed to load Tachyon UI capabilities", error);
return main.TachyonShellMethods.getUiState()
.then((response) => {
if (!response?.success) {
throw new Error("UI state request failed");
}
return applyUiState(response.data);
})
.catch((fallbackError) => {
console.warn("Failed to load Tachyon UI state", fallbackError);
return loadFallbackUiCapabilities();
});
})
.finally(() => {
uiCapabilitiesPromise = null;
});
return uiCapabilitiesPromise;
};
const tachyonMap = new form.Map(
UCI_PACKAGE,
_("Tachyon Settings"),
_("Configuration for Tachyon service"),
);
tachyonMap.tabbed = true;
const originalHandleSaveApply = tachyonMap.handleSaveApply;
tachyonMap.handleSaveApply = function (ev, mode) {
const refreshUiState = function () {
main.TachyonShellMethods.getUiState()
.then((response) => {
if (
response?.success &&
typeof main.applyUiStateToStore === "function"
) {
main.applyUiStateToStore(response.data);
}
})
.catch(() => null);
};
if (main.store && typeof main.store.set === "function") {
const servicesInfoWidget = main.store.get().servicesInfoWidget;
main.store.set({
servicesInfoWidget: {
...servicesInfoWidget,
data: {
...servicesInfoWidget.data,
tachyonStatus: "reloading",
},
},
});
}
return Promise.resolve(originalHandleSaveApply.call(this, ev, mode))
.then((result) => {
window.setTimeout(refreshUiState, 250);
return result;
})
.catch((error) => {
refreshUiState();
throw error;
});
};
const rulesSection = tachyonMap.section(
form.GridSection,
"section",
_("Sections"),
_("Drag rows to change priority. The rule at the top is checked first."),
);
configureGridSection(
rulesSection,
"section",
_("Section"),
_("Add a section"),
);
section.configureSectionSection(rulesSection, {
loadActionProvidersAvailability: loadUiCapabilities,
});
section.createSectionContent(rulesSection);
const serverSection = tachyonMap.section(
form.GridSection,
"server",
_("Servers"),
_("Accept external proxy connections and route them with sing-box."),
);
configureGridSection(
serverSection,
"server",
_("Server"),
_("Add a server inbound"),
);
serverSectionRef = serverSection;
server.configureServerSection(serverSection, {
loadCapabilities: loadUiCapabilities,
});
server.createServerContent(serverSection, uiCapabilities);
const settingsSection = tachyonMap.section(
form.TypedSection,
"settings",
_("Settings"),
);
settingsSection.anonymous = true;
settingsSection.addremove = false;
settingsSection.cfgsections = function () {
return ["settings"];
};
settings.createSettingsContent(settingsSection, uiCapabilities);
const diagnosticSection = tachyonMap.section(
form.TypedSection,
"diagnostic",
_("Diagnostics"),
);
diagnosticSection.anonymous = true;
diagnosticSection.addremove = false;
diagnosticSection.cfgsections = function () {
return ["diagnostic"];
};
diagnostic.createDiagnosticContent(diagnosticSection);
const dashboardSection = tachyonMap.section(
form.TypedSection,
"dashboard",
_("Dashboard"),
);
dashboardSection.anonymous = true;
dashboardSection.addremove = false;
dashboardSection.cfgsections = function () {
return ["dashboard"];
};
dashboard.createDashboardContent(dashboardSection);
const monitoringSection = tachyonMap.section(
form.TypedSection,
"monitoring",
_("Monitoring"),
);
monitoringSection.anonymous = true;
monitoringSection.addremove = false;
monitoringSection.cfgsections = function () {
return ["monitoring"];
};
monitoring.createMonitoringContent(monitoringSection);
const updatesSection = tachyonMap.section(
form.TypedSection,
"updates",
_("Components"),
);
updatesSection.anonymous = true;
updatesSection.addremove = false;
updatesSection.cfgsections = function () {
return ["updates"];
};
updates.createUpdatesContent(updatesSection);
await loadUiCapabilities().catch(() => null);
const rendered = await tachyonMap.render();
main.coreService({
waitForLogWatcherStart: loadUiCapabilities,
logWatcherStartDelayMs: 5000,
});
return rendered;
},
};
return view.extend(EntryPoint);