op-packages/luci-app-daede/htdocs/luci-static/resources/view/daede/airport-sync.js
github-actions[bot] bd9e30ea67 🌴 Sync 2026-06-17 21:17:50
2026-06-17 21:17:50 +08:00

174 lines
5.4 KiB
JavaScript

// SPDX-License-Identifier: Apache-2.0
'use strict';
'require baseclass';
function deriveAirportName(value) {
try {
const url = new URL(String(value || ''));
const filename = url.searchParams.get('filename');
if (filename) {
const name = filename.replace(/\.(?:ya?ml)$/i, '').trim();
if (name)
return name;
}
return url.hostname || '机场_1';
} catch (e) {
return '机场_1';
}
}
function nextPastedName(names) {
const used = {};
(names || []).forEach(function(name) { used[String(name)] = true; });
let index = 1;
while (used['机场_' + index])
index++;
return '机场_' + index;
}
function makeAirportId() {
return 'airport_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);
}
function backendId(id) {
return String(id || '').replace(/[^A-Za-z0-9]/g, '') || 'airport';
}
function backendGroupName(name, backend, id) {
const value = String(name || '').trim();
if (isGroupNameValid(value, backend))
return value || backendId(id);
return backendId(id);
}
function isGroupNameValid(name, backend) {
const value = String(name || '').trim();
return !!value && (backend !== 'dae' || /^[A-Za-z_][A-Za-z0-9_-]*$/.test(value));
}
// Coerce an auto-derived name (URL hostname/filename) into a name the third
// step accepts, so the group name is pre-filled valid and needs no manual fix.
// daed allows any non-empty name (incl. CJK); dae requires
// /^[A-Za-z_][A-Za-z0-9_-]*$/, so collapse illegal chars to '_' and drop a
// leading non-letter/underscore run.
function sanitizeGroupName(name, backend) {
const value = String(name || '').trim();
if (!value || backend !== 'dae')
return value;
const cleaned = value
.replace(/[^A-Za-z0-9_-]+/g, '_')
.replace(/_{2,}/g, '_')
.replace(/^[^A-Za-z_]+/, '')
.replace(/[-_]+$/, '');
return cleaned || 'airport';
}
function hashSource(value) {
// LuCI is commonly served over plain HTTP, where Web Crypto is unavailable.
// This stable opaque key is only used to match a previously managed source.
let h1 = 0x811c9dc5, h2 = 0x9e3779b9, h3 = 0x85ebca6b, h4 = 0xc2b2ae35;
const text = unescape(encodeURIComponent(String(value || '')));
for (let index = 0; index < text.length; index++) {
const code = text.charCodeAt(index);
h1 = Math.imul(h1 ^ code, 0x01000193);
h2 = Math.imul(h2 ^ code, 0x5bd1e995);
h3 = Math.imul(h3 ^ code, 0x27d4eb2d);
h4 = Math.imul(h4 ^ code, 0x165667b1);
}
const words = [ h1, h2, h3, h4, h1 ^ h3, h2 ^ h4, h1 ^ h2 ^ h4, h1 ^ h2 ^ h3 ^ h4 ];
return Promise.resolve(words.map(function(word) {
return (word >>> 0).toString(16).padStart(8, '0');
}).join(''));
}
function list(value) {
if (Array.isArray(value))
return value.filter(Boolean).map(String);
return value ? [ String(value) ] : [];
}
function parseAirportSection(section) {
if (!section)
return null;
return {
sid: section['.name'],
id: String(section.id || ''),
backend: String(section.backend || ''),
name: String(section.name || ''),
source_hash: String(section.source_hash || ''),
group_id: String(section.group_id || ''),
subscription_id: String(section.subscription_id || ''),
node_ids: list(section.node_id)
};
}
function airportSectionValues(record) {
return {
id: String(record.id || ''),
backend: String(record.backend || ''),
name: String(record.name || ''),
source_hash: String(record.sourceHash || record.source_hash || ''),
group_id: String(record.groupId || record.group_id || ''),
subscription_id: String(record.subscriptionId || record.subscription_id || ''),
node_id: list(record.nodeIds || record.node_ids)
};
}
function matchAirport(records, candidate) {
const sameBackend = (records || []).filter(function(record) {
return record.backend === candidate.backend;
});
if (candidate.selectedId) {
const selected = sameBackend.find(function(record) { return record.id === candidate.selectedId; });
if (selected)
return selected;
}
if (candidate.sourceHash) {
const bySource = sameBackend.find(function(record) { return record.source_hash === candidate.sourceHash; });
if (bySource)
return bySource;
}
return sameBackend.find(function(record) { return record.name === candidate.name; }) || null;
}
function safeOldNodeIds(oldIds, newIds, otherAirportNodeIds, otherGroupNodeIds) {
const keep = {};
list(newIds).forEach(function(id) { keep[id] = true; });
(otherAirportNodeIds || []).forEach(function(ids) { list(ids).forEach(function(id) { keep[id] = true; }); });
(otherGroupNodeIds || []).forEach(function(ids) { list(ids).forEach(function(id) { keep[id] = true; }); });
return list(oldIds).filter(function(id) { return !keep[id]; });
}
function findManagedGroup(groups, record) {
if (!record || !record.group_id)
return null;
return (groups || []).find(function(group) {
return group.id === record.group_id && group.name === record.name;
}) || null;
}
const api = {
deriveAirportName: deriveAirportName,
nextPastedName: nextPastedName,
makeAirportId: makeAirportId,
backendId: backendId,
backendGroupName: backendGroupName,
isGroupNameValid: isGroupNameValid,
sanitizeGroupName: sanitizeGroupName,
hashSource: hashSource,
parseAirportSection: parseAirportSection,
airportSectionValues: airportSectionValues,
matchAirport: matchAirport,
findManagedGroup: findManagedGroup,
safeOldNodeIds: safeOldNodeIds
};
if (typeof module !== 'undefined' && module.exports)
module.exports = api;
if (typeof baseclass !== 'undefined')
return baseclass.extend(api);
return api;