feat: 综合平台services多服务支持+SW网络优先缓存修复+适配层/定时任务完善

This commit is contained in:
gouki
2026-08-05 11:57:17 +00:00
parent 7f1268f508
commit 1b7d4823c3
47 changed files with 2895 additions and 1189 deletions
+193
View File
@@ -0,0 +1,193 @@
/* 全局响应式状态与工具函数 */
const { reactive, computed } = Vue;
const Api = window.VpsApi;
const Fmt = window.VpsFmt;
/* ---------------- 全局状态 ---------------- */
const store = reactive({
view: 'dashboard',
assets: [],
providers: [],
subdomains: [],
siteCerts: [],
overview: {},
expiring: [],
serverMetrics: {},
search: '',
filterType: '',
filterStatus: '',
assetTab: '', // 资产页 TAB'' 全部 / vps / domain / cloudflare / subscription(付费资产) / ai_agent
monitorTab: '', // 监控中心 TAB:同上
dark: localStorage.getItem('vps_dark') === '1',
loading: false,
error: '',
assetModal: { show: false, editing: null, form: null },
providerModal: { show: false, editing: null, form: null },
});
function applyDark() {
document.documentElement.classList.toggle('dark', store.dark);
localStorage.setItem('vps_dark', store.dark ? '1' : '0');
}
/* ---------------- 表单工厂 ---------------- */
function emptyAssetForm() {
return {
name: '', asset_type: 'vps', provider: '', provider_id: null, renewal_cycle: 'monthly', renew_url: '', cancel_url: '',
account: '', expiry_date: '', auto_renew: false, cost: 0, currency: 'USD',
status: 'active', is_archived: false, remark: '',
vps_detail: { ip_address: '', tailscale_ip: '', region: '', os: '', cpu_cores: 1, memory_gb: 1, disk_gb: 20, bandwidth_gb: null, ssh_port: 22, panel_url: '', ssh_user: '', login_method: 'key', ssh_key: '', password: '', purpose: '' },
domain_detail: { domain_name: '', registrar: '', dns_provider: '', cloudflare_account: '', is_using: true, redirect_target: '', bind_asset_id: null },
ai_detail: { provider: '', api_key: '', plan: '', balance: null, currency: 'USD', monthly_usage: null, monthly_limit: null },
cloudflare_detail: { account_email: '', sub_type: 'zone', sub_name: '', zone_name: '', status: '' },
};
}
function buildAssetPayload(f) {
const p = {
name: f.name, asset_type: f.asset_type, provider: f.provider,
provider_id: f.provider_id || null, renewal_cycle: f.renewal_cycle || null,
renew_url: f.renew_url || null, cancel_url: f.cancel_url || null,
account: f.account || null, expiry_date: f.expiry_date || null,
auto_renew: !!f.auto_renew, cost: Number(f.cost) || 0, currency: f.currency,
status: f.status, is_archived: !!f.is_archived, remark: f.remark || null,
};
if (f.asset_type === 'vps') {
const v = f.vps_detail;
p.vps_detail = { ip_address: v.ip_address, tailscale_ip: v.tailscale_ip || null, region: v.region || null, os: v.os || null, cpu_cores: Number(v.cpu_cores) || 1, memory_gb: Number(v.memory_gb) || 1, disk_gb: Number(v.disk_gb) || 20, bandwidth_gb: v.bandwidth_gb ? Number(v.bandwidth_gb) : null, ssh_port: Number(v.ssh_port) || 22, panel_url: v.panel_url || null, ssh_user: v.ssh_user || null, login_method: v.login_method || 'key', ssh_key: v.ssh_key || null, password: v.password || null, purpose: v.purpose || null };
} else if (f.asset_type === 'domain') {
const d = f.domain_detail;
p.domain_detail = { domain_name: d.domain_name, registrar: d.registrar || null, dns_provider: d.dns_provider || null, cloudflare_account: d.cloudflare_account || null, is_using: !!d.is_using, redirect_target: d.redirect_target || null, bind_asset_id: d.bind_asset_id || null };
} else if (f.asset_type === 'ai_agent') {
const a = f.ai_detail;
p.ai_detail = { provider: a.provider, api_key: a.api_key || null, plan: a.plan || null, balance: a.balance ? Number(a.balance) : null, currency: a.currency || 'USD', monthly_usage: a.monthly_usage ? Number(a.monthly_usage) : null, monthly_limit: a.monthly_limit ? Number(a.monthly_limit) : null };
} else if (f.asset_type === 'cloudflare') {
const c = f.cloudflare_detail;
p.cloudflare_detail = { account_email: c.account_email || null, sub_type: c.sub_type || 'zone', sub_name: c.sub_name || null, zone_name: c.zone_name || null, status: c.status || null };
}
return p;
}
/* ---------------- 数据加载 ---------------- */
async function loadAssets() {
const params = new URLSearchParams();
if (store.filterType) params.set('asset_type', store.filterType);
if (store.filterStatus) params.set('status', store.filterStatus);
if (store.search) params.set('q', store.search);
params.set('sort', 'expiry_date'); params.set('order', 'asc');
store.assets = await Api.get('/assets?' + params.toString());
}
async function loadProviders() { store.providers = await Api.get('/providers'); }
async function loadSubdomains() { store.subdomains = await Api.get('/subdomains'); }
async function loadSiteCerts() { store.siteCerts = await Api.get('/site-certs'); }
async function loadOverview() { store.overview = await Api.get('/stats/overview'); }
async function loadExpiring() { store.expiring = await Api.get('/stats/expiring?days=30'); }
async function loadAll() {
store.loading = true; store.error = '';
try { await Promise.all([loadAssets(), loadProviders(), loadSubdomains(), loadSiteCerts(), loadOverview(), loadExpiring()]); }
catch (e) { store.error = e.message; } finally { store.loading = false; }
}
/* ---------------- 资产操作 ---------------- */
function openAssetCreate(presetType) {
const form = emptyAssetForm();
if (presetType) form.asset_type = presetType;
store.assetModal = { show: true, editing: null, form };
}
function openAICreate(provider) {
const form = emptyAssetForm();
form.asset_type = 'ai_agent';
if (provider) {
form.provider = provider;
form.ai_detail.provider = provider;
const p = store.providers.find(x => x.slug === provider);
if (p) form.provider_id = p.id;
}
store.assetModal = { show: true, editing: null, form };
}
function openAssetEdit(a) {
const form = emptyAssetForm();
['name', 'asset_type', 'provider', 'provider_id', 'renewal_cycle', 'renew_url', 'cancel_url', 'account', 'expiry_date', 'auto_renew', 'cost', 'currency', 'status', 'is_archived', 'remark'].forEach(k => { form[k] = a[k]; });
if (a.vps_detail) Object.assign(form.vps_detail, a.vps_detail);
if (a.domain_detail) Object.assign(form.domain_detail, a.domain_detail);
if (a.ai_detail) Object.assign(form.ai_detail, a.ai_detail);
if (a.cloudflare_detail) Object.assign(form.cloudflare_detail, a.cloudflare_detail);
form.vps_detail.ssh_key = ''; form.vps_detail.password = '';
store.assetModal = { show: true, editing: a.id, form };
}
async function saveAsset() {
store.error = '';
const f = store.assetModal.form;
try {
const payload = buildAssetPayload(f);
if (store.assetModal.editing) await Api.put('/assets/' + store.assetModal.editing, payload);
else await Api.post('/assets', payload);
store.assetModal.show = false;
await loadAll();
} catch (e) { store.error = e.message; }
}
async function deleteAsset(a) {
if (!confirm('确认删除资产「' + a.name + '」?此操作不可恢复。')) return;
store.error = '';
try { await Api.del('/assets/' + a.id); await loadAll(); }
catch (e) { store.error = e.message; }
}
/* ---------------- 平台操作 ---------------- */
function openProviderCreate() {
store.providerModal = { show: true, editing: null, form: { slug: '', name: '', name_en: '', category: 'vps', services: '', website: '', console_url: '', sdk_type: '', enabled: true, remark: '', api_config: '' } };
}
function openProviderEdit(p) {
store.providerModal = { show: true, editing: p.id, form: { slug: p.slug, name: p.name, name_en: p.name_en || '', category: p.category, services: p.services || '', website: p.website || '', console_url: p.console_url || '', sdk_type: p.sdk_type || '', enabled: p.enabled, remark: p.remark || '', api_config: '' } };
}
async function saveProvider() {
store.error = '';
const f = store.providerModal.form;
const payload = { slug: f.slug, name: f.name, name_en: f.name_en || null, category: f.category, services: f.services || null, website: f.website || null, console_url: f.console_url || null, sdk_type: f.sdk_type || null, enabled: !!f.enabled, remark: f.remark || null, api_config: f.api_config || null };
try {
if (store.providerModal.editing) await Api.put('/providers/' + store.providerModal.editing, payload);
else await Api.post('/providers', payload);
store.providerModal.show = false;
await loadProviders();
} catch (e) { store.error = e.message; }
}
async function seedProviders() {
store.error = '';
try { const r = await Api.post('/providers/seed', {}); await loadProviders(); alert('已初始化预设平台,新增 ' + r.added + ' 个'); }
catch (e) { store.error = e.message; }
}
async function deleteProvider(p) {
if (!confirm('确认删除平台「' + p.name + '」?')) return;
try { await Api.del('/providers/' + p.id); await loadProviders(); }
catch (e) { store.error = e.message; }
}
/* ---------------- 服务器监控 ---------------- */
async function viewServer(a) {
try {
const [metrics, info, checks, security] = await Promise.all([
Api.get('/monitor/' + a.id + '/metrics?limit=200'),
Api.get('/monitor/' + a.id + '/info'),
Api.get('/monitor/' + a.id + '/security'),
Api.get('/monitor/' + a.id + '/security-score'),
]);
store.serverMetrics = { asset: a, metrics, info, checks, security };
} catch (e) { store.serverMetrics = { asset: a, metrics: [], info: null, checks: [], security: null }; }
// 通过 hash 路由切换视图,保证刷新/分享链接后能回到 servers 视图
if (window.location.hash !== '#servers') {
window.location.hash = '#servers';
} else {
store.view = 'servers';
}
}
/* ---------------- 设置 ---------------- */
const settings = reactive({ apiKey: Api.getApiKey(), saved: false });
function saveSettings() { Api.setApiKey(settings.apiKey); settings.saved = true; setTimeout(() => settings.saved = false, 2000); }
function normalizedMonthly(cost, cycle) {
if (!cost) return 0;
if (cycle === 'yearly') return cost / 12;
if (cycle === 'quarterly') return cost / 3;
return cost;
}