CH-J Server ManagerSSH کے ذریعے سرور کا انتظام
مینو
شائع شدہ سورس

CH-J Server Manager

ایپلیکیشن کی کسی مخصوص ریلیز کی ڈائریکٹریز اور فائلیں دیکھیں۔

سورس ZIP ڈاؤن لوڈ کریں
CH-J / Server Manager 377 فائلیں
core-20261019T111616Z-160a00bfcddc شامل کیا گیا: · بلڈ کیا گیا:
diagnosticsService.js شامل کیا گیا: تاریخ درج نہیں 10.7 KB · 167 لائنیں
کوڈ فائل ڈاؤن لوڈ کریں
1"use strict";
2const { EventEmitter } = require("node:events");
3const crypto = require("node:crypto");
4const net = require("node:net");
5const { normalizeOptions, parseTarget, PING_DURATION_LIMIT_MS, DiagnosticError, checkAbort, errorResult, boundedSignal, now } = require("./common");
6const { DnsDiagnostics, reverseName } = require("./dnsDiagnostics");
7const { ping, traceroute } = require("./processTools");
8const { HttpDiagnostics } = require("./httpDiagnostics");
9const { runTls, runTcp, websocketProbe } = require("./socketDiagnostics");
10const { DiagnosticsHistory, exportReport, scrub } = require("./historyStore");
11// A finite ping run may outlast the default 5 min budget; the hard cap stays 30 min.
12function jobTimeLimit(options) {
13 if (options.continuous) return PING_DURATION_LIMIT_MS;
14 const ping = options.tools.includes("ping") ? options.count * options.repetitions * options.intervalMs + (options.pingTimeoutMs || options.timeoutMs) + 60000 : 0;
15 return Math.min(PING_DURATION_LIMIT_MS, Math.max(5 * 60 * 1000, ping));
17function targetForAddress(target, address) {
18 const url = new URL(target.url); url.hostname = net.isIP(address) === 6 ? `[${address}]` : address;
19 return parseTarget(url.href);
21function sameIp(a, b) {
22 if (net.isIP(a) !== net.isIP(b)) return false;
23 return a === b || net.isIP(a) === 6 && new URL(`http://[${a}]/`).hostname === new URL(`http://[${b}]/`).hostname;
25class DiagnosticsService extends EventEmitter {
26 constructor(options = {}) {
27 super();
28 this.dns = options.dns || new DnsDiagnostics();
29 this.http = options.http || new HttpDiagnostics(this.dns);
30 this.history = options.history || new DiagnosticsHistory(options.storageRoot);
31 this.jobs = new Map(); this.resolvers = new Set();
32 this.adapters = { ping, traceroute, tls: runTls, tcp: runTcp,
33 websocket: (target, address, settings, signal) => websocketProbe(target, address, settings, signal), ...options.adapters };
34 }
35 async resolve(source) {
36 if (this.resolvers.size >= 2) throw new DiagnosticError("DIAGNOSTICS_BUSY");
37 const options = normalizeOptions({ ...source, tools: ["dns"] });
38 const controller = new AbortController(), bounded = boundedSignal(controller.signal, 30000);
39 this.resolvers.add(controller);
40 try { return { target: options.manualAddresses ? { ...options.target, type: "ipv4+ipv6" } : options.target, addresses: options.manualAddresses || await this.dns.resolve(options.target.host, options, bounded.signal) }; }
41 finally { bounded.close(); this.resolvers.delete(controller); }
42 }
43 start(source) {
44 if ([...this.jobs.values()].some((job) => job.report.status === "running")) throw new DiagnosticError("DIAGNOSTICS_BUSY");
45 const options = normalizeOptions(source), id = crypto.randomBytes(16).toString("hex"), controller = new AbortController();
46 const report = { id, createdAt: new Date().toISOString(), status: "running", options, results: {}, progress: [], addresses: [] };
47 const job = { report, controller, tools: new Map(), cancelledTools: new Set() };
48 this.jobs.set(id, job);
49 while (this.jobs.size > 5) this.jobs.delete(this.jobs.keys().next().value);
50 setImmediate(() => { job.promise = this._run(job).catch((error) => this._finish(job, errorResult(error))); });
51 return { id, report: scrub(report) };
52 }
53 cancel(id, tool) {
54 const job = this.jobs.get(String(id)); if (!job) throw new DiagnosticError("JOB_NOT_FOUND");
55 if (tool) {
56 if (!job.report.options.tools.includes(tool)) throw new DiagnosticError("INVALID_TOOLS");
57 job.cancelledTools.add(tool); job.tools.get(tool)?.abort();
58 } else job.controller.abort();
59 return { cancelling: true };
60 }
61 stopAll() {
62 for (const job of this.jobs.values()) if (job.report.status === "running") job.controller.abort();
63 for (const controller of this.resolvers) controller.abort();
64 }
65 get(id) { const job = this.jobs.get(id); return job ? scrub(job.report) : this.history.get(id); }
66 _emit(job, event) {
67 const value = { jobId: job.report.id, ...event };
68 if (event.kind !== "snapshot" && event.kind !== "complete") {
69 job.report.progress.push(scrub(event));
70 if (job.report.progress.length > 500) job.report.progress.shift();
71 }
72 this.emit("progress", value);
73 }
74 _finish(job, failure) {
75 if (job.report.status !== "running") return;
76 job.report.status = job.controller.signal.aborted ? "cancelled" : failure ? "failed" : "completed";
77 job.report.finishedAt = new Date().toISOString();
78 if (failure) job.report.error = failure;
79 if (job.controller.signal.reason?.code) job.report.error = errorResult(job.controller.signal.reason);
80 try { this.history.add(job.report); } catch (error) { job.report.historyError = error.code || "HISTORY_WRITE_FAILED"; }
81 this._emit(job, { kind: "complete", report: scrub(job.report) });
82 }
83 async _address(job, tool, item, signal, progress) {
84 const { report } = job, { options: base } = report;
85 // A manual IPv4+IPv6 pair gives each address its own literal URL; otherwise HTTP/TLS
86 // would connect to the first literal regardless of the address being tested.
87 const options = base.manualAddresses ? { ...base, target: targetForAddress(base.target, item.address) } : base;
88 try {
89 checkAbort(signal); let result;
90 if (tool === "http") result = await this.http.run(options.target, item.address, options, signal, progress);
91 else if (tool === "compression") result = await this.http.compression(options.target, item.address, options, signal, progress);
92 else if (tool === "tls" || tool === "websocket") result = await this.adapters[tool](options.target, item.address, options, signal, progress);
93 else result = await this.adapters[tool](item.address, options, signal, progress);
94 if (tool === "traceroute") {
95 const addresses = [...new Set(result.routes?.flatMap((route) => route.hops.flatMap((hop) => hop.addresses)) || [])].slice(0, options.maxHops);
96 result.reverseDns = {};
97 for (const address of addresses) {
98 checkAbort(signal);
99 try { const query = await this.dns.query(reverseName(address), "PTR", options, signal); result.reverseDns[address] = query.records.filter((r) => r.type === "PTR").map((r) => r.value); }
100 catch (error) { if (signal.aborted) throw error; result.reverseDns[address] = []; }
101 }
102 }
103 return { address: item.address, family: item.family, ...result };
104 } catch (error) {
105 const partial = { address: item.address, family: item.family, ...errorResult(error) };
106 if (tool === "ping") {
107 const latest = report.livePing?.[item.address];
108 if (latest) Object.assign(partial, { sent: latest.sent, received: latest.received, lossPercent: latest.lossPercent, statistics: latest.statistics });
109 partial.packets = report.progress.filter((e) => e.kind === "ping-packet" && e.address === item.address);
110 }
111 if (tool === "traceroute") partial.routes = [{ hops: report.progress.filter((e) => e.kind === "trace-hop" && e.address === item.address) }];
112 if (tool === "http") partial.comparisons = options.protocols.map((protocol) => ({ protocol, address: item.address, samples: report.progress.filter((e) => e.kind === "http-sample" && e.address === item.address && e.protocol === protocol) }));
113 return partial;
114 }
115 }
116 async _run(job) {
117 const { report, controller } = job, { options } = report, signal = controller.signal;
118 const deadline = setTimeout(() => controller.abort(new DiagnosticError("JOB_TIME_LIMIT")), jobTimeLimit(options));
119 try {
120 checkAbort(signal); const started = now();
121 if (options.manualAddresses) report.addresses = options.manualAddresses.map((item) => ({ ...item, source: "manual" }));
122 else {
123 try { report.addresses = await this.dns.resolve(options.target.host, options, signal); }
124 catch (error) { if (signal.aborted) throw error; report.resolutionError = errorResult(error); }
125 }
126 report.resolvedAddresses = [...report.addresses]; report.resolutionMs = now() - started;
127 if (options.selectedIp) {
128 const selected = report.addresses.filter((item) => sameIp(item.address, options.selectedIp));
129 if (!selected.length) throw new DiagnosticError("SELECTED_IP_NOT_RESOLVED");
130 report.addresses = selected;
131 }
132 if (options.mode === "ipv4") report.addresses = report.addresses.filter((a) => a.family === 4);
133 if (options.mode === "ipv6") report.addresses = report.addresses.filter((a) => a.family === 6);
134 if (options.mode === "auto" && !options.manualAddresses) report.addresses = [report.addresses.find((a) => a.family === 4) || report.addresses[0]].filter(Boolean);
135 if (options.mode === "both") report.addresses = [report.addresses.find((a) => a.family === 4), report.addresses.find((a) => a.family === 6)].filter(Boolean);
136 this._emit(job, { kind: "resolved", addresses: report.addresses, resolvedAddresses: report.resolvedAddresses, resolutionMs: report.resolutionMs, error: report.resolutionError });
137 // Two tools at a time; at most two IP families per tool. All workers settle before completion.
138 const queue = [...options.tools]; let cursor = 0;
139 await Promise.all(Array.from({ length: Math.min(2, queue.length) }, async () => {
140 while (cursor < queue.length && !signal.aborted) {
141 const tool = queue[cursor++], sub = new AbortController(); job.tools.set(tool, sub);
142 const abort = () => sub.abort(); signal.addEventListener("abort", abort, { once: true });
143 if (signal.aborted || job.cancelledTools.has(tool)) sub.abort();
144 this._emit(job, { kind: "tool-start", tool });
145 const progress = (event) => {
146 if (event.kind === "ping-packet") { report.livePing ||= {}; report.livePing[event.address] = event; }
147 this._emit(job, { tool, ...event });
148 };
149 try {
150 checkAbort(sub.signal);
151 if (tool === "dns") report.results.dns = await this.dns.run(options.target.host, options, sub.signal, progress);
152 else if (!report.addresses.length) report.results[tool] = [{ status: "not-tested", code: "NO_ADDRESS" }];
153 else report.results[tool] = await Promise.all(report.addresses.map((item) => this._address(job, tool, item, sub.signal, progress)));
154 } catch (error) { report.results[tool] = [{ ...errorResult(error) }]; }
155 finally {
156 this._emit(job, { kind: "tool-complete", tool, result: scrub(report.results[tool]) });
157 signal.removeEventListener("abort", abort); job.tools.delete(tool);
158 }
159 }
160 }));
161 this._finish(job);
162 } catch (error) { this._finish(job, errorResult(error)); }
163 finally { clearTimeout(deadline); }
164 }
165 export(id, format) { return exportReport(this.get(id), format); }
167module.exports = { DiagnosticsService, sameIp, jobTimeLimit };
SHA-25637f59ff58670243afea618bb6226dff90cc0795b4466deac2f150eec279c1c33

CH-J Proprietary Software License 1.14 · سورس CH-J Proprietary Software License 1.14 کے تحت فراہم کیا جاتا ہے۔ اس کی دستیابی لائسنس کی شرائط نہیں بدلتی اور نہ ہی اضافی حقوق دیتی ہے۔

آرکائیو کا SHA-256a9605c0870a223b6a8542bb0705edd51d8b04f3069582653417a8397fabcc09a
فائلیں