"""
_SCRIPT = r"""
const ABOUT = __ABOUT__;
const T = {
en:{
tagline:'Finds the scale on any COM port, shows the live weight and serves it to your network.',
simulation:'Simulation', lang:'فارسی',
tab_scales:'Scales', tab_find:'Find my scale', tab_connect:'Network access', tab_manual:'Manual & monitor',
tab_protocols:'Protocols', tab_about:'About',
no_scales:'No scale saved yet.', no_scales_hint:'Plug the scale into this PC and let the program find it.',
find_now:'Find my scale', stable:'stable', moving:'moving', no_data:'no data', stale:'old reading',
overload:'overload', delete:'Delete', confirm_delete:'Delete the profile "{name}"?',
net:'net', gross:'gross', age:'{ms} ms ago', copied:'Copied', copy:'Copy', address_for:'Address for other systems',
find_title:'Find the scale automatically',
find_intro:'The program listens to each port at every common speed, then asks the scale in the languages of the major manufacturers. Only read-only requests are sent: it never zeroes, tares or prints.',
ports:'COM ports', no_ports:'No COM port on this PC. Plug in the USB-serial adapter and check its driver in Device Manager.',
refresh:'Refresh', thorough:'Thorough search (more speeds and framings, slower)',
wait_print:'The scale sends only when Print is pressed (e.g. wired to a label printer) — wait for me to press it',
listen_only:'Listen only — never send anything to the scale',
log_press_print:'{port}: ▶ press PRINT on the scale now — listening at {baud} for {seconds} s',
log_print_wrong_speed:'{port}: data at {baud} was not readable — press PRINT again (trying the next speed)',
log_print_timeout:'{port}: nothing arrived while waiting for Print',
mode_print:'sends when Print is pressed', printed:'printed {ago}', last_labels:'Last printed labels',
listening_all_day:'Listening all the time: every printed label is recorded.',
ago_s:'{n} s ago', ago_m:'{n} min ago', ago_h:'{n} h ago', no_labels:'No label printed yet.',
start:'Start search', cancel:'Cancel', running:'Searching…', results:'Results',
found_on:'Scale found on {port}', not_found_on:'No scale recognised on {port}', confidence:'confidence',
protocol:'Protocol', line:'Line', mode:'Mode', reading:'Reading', sample:'What arrived',
mode_continuous:'sends by itself', mode_poll:'answers a request', save_as:'Save as', label:'Description',
save:'Save', saved:'Saved', field_tested:'tested on real hardware',
heard_unknown:'Readable data arrived at {baud} {framing} but the format is not in the library yet:',
tips_title:'If nothing is found',
tip1:'Serial output must be switched on in the indicator\'s menu (often called COM, rS, SEr, Serial, Continuous or On request).',
tip2:'Check the cable: many indicators need a null-modem (crossed) cable, some a straight one. Pins 2, 3 and 5 are enough.',
tip3:'Put something on the scale or press its Print key while searching — some indicators only send then.',
tip4:'Try the thorough search, or use “Manual & monitor” to watch the raw bytes.',
ch340:'Driver warning', bt:'Bluetooth — skipped unless ticked',
log_port_start:'Searching {port}…', log_listen:'{port}: listening at {baud} {framing}',
log_heard:'{port}: {bytes} bytes at {baud} ({readable}% readable)',
log_seven_bit:'{port}: looks like 7 data bits with parity — listening again as 7E1/7O1',
log_ask:'{port}: asking {protocol} at {baud} {framing}', log_reply:'{port}: reply to {protocol}: {text}',
log_found:'✔ {port}: {protocol} at {baud} {framing} — {value} {unit}', log_not_found:'{port}: nothing recognised',
log_open_failed:'{port}: cannot open — {error}', log_done:'Finished: {found} scale(s) found',
log_error:'Error: {error}',
conn_title:'Make the scales on this PC available on the network', conn_intro:'Other systems — your server, an ERP, a script — read the scales on this PC over the network. Four things make that work:',
step_address:'The address of this PC', step_key:'The access key', step_firewall:'An open port in Windows Firewall',
step_autostart:'Start with Windows (optional, recommended)',
port_label:'Port', addresses:'Addresses', key_hint:'Only systems that have this key can read the scales. Keep it private.',
show:'Show', hide:'Hide', new_key:'New key', confirm_key:'Make a new key? Systems using the old key will stop working until you enter the new one.',
fw_what:'Windows Firewall blocks incoming connections by default, so other machines cannot reach port {port} on this PC until a rule allows it. The button below asks Windows for administrator permission (a UAC prompt) and adds the inbound rule “SyncPulse Scale Bridge” for TCP port {port} on all network types. It also removes any block rule Windows added for this program if its “Allow access?” question was cancelled.',
fw_open:'Open port {port} in the firewall', fw_remove:'Remove the rule', fw_is_open:'Port {port} is open in Windows Firewall.',
fw_is_closed:'Port {port} is not open yet.', fw_only_from:'Allow only from (server IP, optional)',
fw_manual:'To do it by hand, run in an administrator Command Prompt:',
fw_not_windows:'Not on Windows — open TCP port {port} in this system\'s firewall.',
as_what:'Runs the bridge in the background when the PC starts, before anyone logs in, and restarts it if it stops. Needs administrator permission once.',
as_on:'Starts with Windows.', as_off:'Does not start with Windows.', as_other:'A task exists but points to another copy of the program.',
as_enable:'Start with Windows', as_disable:'Stop starting with Windows',
as_note:'After enabling it, close this window and restart the PC (or keep this window open until then). Opening the program again later shows this page for the background copy.',
in_syncpulse:'Then, in the system that needs the weight', in_syncpulse_steps:'Send this request with the key above. The answer is JSON — value, unit, stable, age_ms … (documentation on GitHub):',
allowed_title:'Allowed clients', allowed_hint:'Leave empty to accept any machine that has the key, or list the IP addresses of the machines that may read the scales (comma separated).',
config_file:'Settings file', cancelled_uac:'Cancelled at the Windows prompt.', failed:'Failed: {error}', done:'Done',
manual_title:'Manual settings and raw monitor', manual_intro:'Watch exactly what arrives on a port with the settings you choose. Useful for an unknown indicator, a cable test, or to check a setting.',
baud:'Speed (baud)', framing:'Framing', request:'Request to send (hex, empty = only listen)', poll:'Every (ms)',
preset:'Fill the request from a protocol', monitor_start:'Start monitor', monitor_stop:'Stop',
frames:'Frames', guesses:'Protocols that can read this', none_yet:'Nothing received yet.', open_error:'Port error: {error}',
save_manual:'Save these settings as a profile', unit_if_missing:'Unit when the scale sends none', output_unit:'Convert to',
as_sent:'as sent', stable_samples:'Equal readings = stable',
proto_title:'Protocol library', proto_intro:'Formats the program can read. Tested on real hardware are marked; the others follow the manufacturers\' published formats and are covered by tests with their sample frames. The same data format is used by many other brands.',
families:'Used by', request_col:'Request', kind:'Type', reference:'Reference',
kind_continuous:'continuous', kind_poll:'on request', kind_handshake:'handshake', kind_print:'on Print key',
about_title:'About', about_text:'SyncPulse Scale Bridge connects weighing scales with an RS-232 serial port to computers and networks. Copy the single program file to the PC beside the scale, run it, and it finds the port, the speed and the protocol by itself, shows the live weight and serves it on the local network.',
about_platform:'Free software that works with any system through its simple HTTP/JSON interface. Originally developed for the SyncPulse platform.',
author:'Author', company:'Company', website:'Website', license:'License', source:'Source code', version:'Version',
license_text:'Free software under the GNU General Public License v3 or later, with an additional term (section 7(b)): the author attribution shown in the program must be kept in copies and modified versions.',
made_by:'Made by', err_name:'Choose a name: letters, digits, - or _.', already_saved:'saved as {names}'
},
fa:{
tagline:'باسکول را روی هر درگاه COM پیدا میکند، وزن زنده را نشان میدهد و آن را روی شبکه در اختیار میگذارد.',
simulation:'شبیهساز', lang:'English',
tab_scales:'باسکولها', tab_find:'پیدا کردن باسکول', tab_connect:'دسترسی از شبکه', tab_manual:'تنظیم دستی و مانیتور',
tab_protocols:'پروتکلها', tab_about:'درباره',
no_scales:'هنوز باسکولی ذخیره نشده است.', no_scales_hint:'باسکول را به این رایانه وصل کنید تا برنامه آن را پیدا کند.',
find_now:'پیدا کردن باسکول', stable:'پایدار', moving:'در حال تغییر', no_data:'بدون داده', stale:'خوانش قدیمی',
overload:'بیش از ظرفیت', delete:'حذف', confirm_delete:'پروفایل «{name}» حذف شود؟',
net:'خالص', gross:'ناخالص', age:'{ms} میلیثانیه پیش', copied:'کپی شد', copy:'کپی', address_for:'نشانی برای سامانههای دیگر',
find_title:'پیدا کردن خودکار باسکول',
find_intro:'برنامه روی هر درگاه با همهی سرعتهای رایج گوش میدهد و سپس با زبان سازندههای اصلی از باسکول وزن میخواهد. فقط درخواستهای خواندنی فرستاده میشود: هیچوقت صفر، تار یا چاپ نمیکند.',
ports:'درگاههای COM', no_ports:'درگاه COM روی این رایانه نیست. مبدل USB به سریال را وصل کنید و درایور آن را در Device Manager بررسی کنید.',
refresh:'بازخوانی', thorough:'جستجوی کامل (سرعتها و قالبهای بیشتر، کندتر)',
wait_print:'باسکول فقط با زدن کلید چاپ داده میفرستد (مثلاً وصل به لیبلزن) — منتظر بمان تا چاپ بزنم',
listen_only:'فقط گوش بده — هیچ چیزی به باسکول فرستاده نشود',
log_press_print:'{port}: ▶ همین حالا کلید چاپ باسکول را بزنید — گوش دادن با {baud} تا {seconds} ثانیه',
log_print_wrong_speed:'{port}: داده با سرعت {baud} خوانا نبود — دوباره چاپ بزنید (سرعت بعدی)',
log_print_timeout:'{port}: در زمان انتظار برای چاپ چیزی نرسید',
mode_print:'با زدن کلید چاپ میفرستد', printed:'چاپشده {ago}', last_labels:'آخرین برچسبهای چاپشده',
listening_all_day:'همیشه در حال گوش دادن: هر برچسبی که چاپ شود ثبت میشود.',
ago_s:'{n} ثانیه پیش', ago_m:'{n} دقیقه پیش', ago_h:'{n} ساعت پیش', no_labels:'هنوز برچسبی چاپ نشده است.',
start:'شروع جستجو', cancel:'لغو', running:'در حال جستجو…', results:'نتیجه',
found_on:'باسکول روی {port} پیدا شد', not_found_on:'روی {port} باسکولی شناخته نشد', confidence:'اطمینان',
protocol:'پروتکل', line:'تنظیم خط', mode:'روش', reading:'خوانش', sample:'دادهی دریافتی',
mode_continuous:'خودش پیوسته میفرستد', mode_poll:'به درخواست پاسخ میدهد', save_as:'ذخیره با نام', label:'توضیح',
save:'ذخیره', saved:'ذخیره شد', field_tested:'روی دستگاه واقعی آزموده شده',
heard_unknown:'با {baud} {framing} دادهی خوانا رسید اما این قالب هنوز در کتابخانه نیست:',
tips_title:'اگر چیزی پیدا نشد',
tip1:'خروجی سریال باید در منوی نمایشگر روشن باشد (معمولاً با نام COM، rS، SEr، Serial، Continuous یا On request).',
tip2:'کابل را بررسی کنید: بسیاری از نمایشگرها کابل نالمودم (ضربدری) و برخی کابل مستقیم میخواهند. پایههای ۲، ۳ و ۵ کافی است.',
tip3:'هنگام جستجو چیزی روی باسکول بگذارید یا کلید چاپ را بزنید؛ برخی نمایشگرها فقط در آن لحظه میفرستند.',
tip4:'جستجوی کامل را امتحان کنید یا از «تنظیم دستی و مانیتور» بایتهای خام را ببینید.',
ch340:'هشدار درایور', bt:'بلوتوث — مگر تیک بخورد، بررسی نمیشود',
log_port_start:'جستجو در {port}…', log_listen:'{port}: گوش دادن با {baud} {framing}',
log_heard:'{port}: {bytes} بایت با سرعت {baud} ({readable}٪ خوانا)',
log_seven_bit:'{port}: بهنظر ۷ بیت داده با پریتی است — دوباره با 7E1/7O1 گوش میدهد',
log_ask:'{port}: پرسش {protocol} با {baud} {framing}', log_reply:'{port}: پاسخ به {protocol}: {text}',
log_found:'✔ {port}: {protocol} با {baud} {framing} — {value} {unit}', log_not_found:'{port}: چیزی شناخته نشد',
log_open_failed:'{port}: باز نمیشود — {error}', log_done:'پایان: {found} باسکول پیدا شد',
log_error:'خطا: {error}',
conn_title:'در دسترس گذاشتن باسکولهای این رایانه روی شبکه', conn_intro:'سامانههای دیگر — سرور شما، ERP، یک اسکریپت — از راه شبکه باسکولهای این رایانه را میخوانند. برای این کار چهار چیز لازم است:',
step_address:'نشانی این رایانه', step_key:'کلید دسترسی', step_firewall:'باز بودن درگاه در فایروال ویندوز',
step_autostart:'اجرا همراه ویندوز (اختیاری، پیشنهادی)',
port_label:'درگاه', addresses:'نشانیها', key_hint:'فقط سامانههایی که این کلید را دارند میتوانند باسکولها را بخوانند. آن را محرمانه نگه دارید.',
show:'نمایش', hide:'پنهان', new_key:'کلید تازه', confirm_key:'کلید تازه ساخته شود؟ سامانههایی که کلید قبلی را دارند تا کلید تازه را وارد نکنید کار نمیکنند.',
fw_what:'فایروال ویندوز بهطور پیشفرض اتصالهای ورودی را میبندد؛ بنابراین تا وقتی قانونی اجازه ندهد، رایانههای دیگر به درگاه {port} این رایانه نمیرسند. دکمهی زیر از ویندوز اجازهی مدیر (پنجرهی UAC) میگیرد و قانون ورودی «SyncPulse Scale Bridge» را برای TCP درگاه {port} روی همهی انواع شبکه میسازد. اگر پرسش «Allow access؟» ویندوز لغو شده باشد، قانون مسدودکنندهای را هم که ویندوز برای این برنامه ساخته پاک میکند.',
fw_open:'باز کردن درگاه {port} در فایروال', fw_remove:'حذف قانون', fw_is_open:'درگاه {port} در فایروال ویندوز باز است.',
fw_is_closed:'درگاه {port} هنوز باز نیست.', fw_only_from:'فقط از این نشانی (IP سرور، اختیاری)',
fw_manual:'برای انجام دستی، در Command Prompt با دسترسی مدیر اجرا کنید:',
fw_not_windows:'این سیستم ویندوز نیست — درگاه TCP {port} را در فایروال همین سیستم باز کنید.',
as_what:'پل را هنگام روشن شدن رایانه، پیش از ورود هر کاربر، در پسزمینه اجرا میکند و اگر بسته شود دوباره راه میاندازد. یکبار اجازهی مدیر لازم دارد.',
as_on:'همراه ویندوز اجرا میشود.', as_off:'همراه ویندوز اجرا نمیشود.', as_other:'وظیفهای هست ولی به نسخهی دیگری از برنامه اشاره میکند.',
as_enable:'اجرا همراه ویندوز', as_disable:'لغو اجرا همراه ویندوز',
as_note:'پس از فعال کردن، این پنجره را ببندید و رایانه را دوباره راهاندازی کنید (یا تا آن زمان این پنجره را باز نگه دارید). اگر بعداً برنامه را باز کنید، همین صفحه برای نسخهی پسزمینه باز میشود.',
in_syncpulse:'سپس در سامانهای که وزن را لازم دارد', in_syncpulse_steps:'این درخواست را با کلید بالا بفرستید. پاسخ JSON است — value، unit، stable، age_ms … (مستندات در گیتهاب):',
allowed_title:'سامانههای مجاز', allowed_hint:'خالی بگذارید تا هر دستگاهی که کلید را دارد پذیرفته شود، یا IP رایانههایی را که اجازهی خواندن دارند با ویرگول جدا بنویسید.',
config_file:'فایل تنظیمات', cancelled_uac:'در پنجرهی ویندوز لغو شد.', failed:'ناموفق: {error}', done:'انجام شد',
manual_title:'تنظیم دستی و مانیتور خام', manual_intro:'دقیقاً ببینید با تنظیمی که انتخاب میکنید چه چیزی روی درگاه میرسد. برای نمایشگر ناشناخته، آزمایش کابل یا بررسی یک تنظیم.',
baud:'سرعت (baud)', framing:'قالب', request:'درخواست ارسالی (hex؛ خالی = فقط گوش دادن)', poll:'هر (میلیثانیه)',
preset:'پر کردن درخواست از روی پروتکل', monitor_start:'شروع مانیتور', monitor_stop:'توقف',
frames:'فریمها', guesses:'پروتکلهایی که این داده را میخوانند', none_yet:'هنوز چیزی نرسیده است.', open_error:'خطای درگاه: {error}',
save_manual:'ذخیرهی این تنظیم بهعنوان پروفایل', unit_if_missing:'واحد، وقتی باسکول واحد نمیفرستد', output_unit:'تبدیل به',
as_sent:'همانطور که میرسد', stable_samples:'تعداد خوانش برابر = پایدار',
proto_title:'کتابخانهی پروتکلها', proto_intro:'قالبهایی که برنامه میخواند. موارد آزموده روی دستگاه واقعی علامت دارند؛ بقیه بر پایهی قالبهای منتشرشدهی سازندگاناند و با فریمهای نمونهی همان مستندات آزمایش شدهاند. بسیاری از برندهای دیگر هم از همین قالبها استفاده میکنند.',
families:'کاربرد', request_col:'درخواست', kind:'نوع', reference:'مرجع',
kind_continuous:'پیوسته', kind_poll:'با درخواست', kind_handshake:'دستدادن', kind_print:'با کلید چاپ',
about_title:'درباره', about_text:'SyncPulse Scale Bridge باسکولهای دارای درگاه سریال RS-232 را به رایانه و شبکه وصل میکند. تنها فایل برنامه را روی رایانهی کنار باسکول کپی و اجرا کنید؛ درگاه، سرعت و پروتکل را خودش پیدا میکند، وزن زنده را نشان میدهد و روی شبکهی محلی در اختیار میگذارد.',
about_platform:'نرمافزار آزاد است و با رابط سادهی HTTP/JSON با هر سامانهای کار میکند. در اصل برای پلتفرم SyncPulse توسعه داده شد.',
author:'سازنده', company:'شرکت', website:'وبسایت', license:'مجوز', source:'کد منبع', version:'نسخه',
license_text:'نرمافزار آزاد با مجوز GNU GPL نسخهی ۳ یا بالاتر، با یک شرط افزوده (بند ۷(ب)): نام و مشخصات سازنده که در برنامه نمایش داده میشود باید در همهی نسخهها و نسخههای تغییر یافته حفظ شود.',
made_by:'ساختهی', err_name:'یک نام انتخاب کنید: حروف انگلیسی، عدد، - یا _.', already_saved:'ذخیرهشده با نام {names}'
}};
const S = {lang:'en', tab:'scales', state:null, ports:[], protocols:[], job:null, jobSince:0, log:[],
weights:{}, inflight:{}, showKey:false, monitor:null, monitorData:null, busy:false, sel:null, draft:{}, shownResults:-1, shownLog:0, shownRunning:false};
try { S.lang = localStorage.getItem('sb.lang') || ((navigator.language||'').toLowerCase().startsWith('fa') ? 'fa' : 'en'); } catch (e) {}
const $ = (sel, el = document) => el.querySelector(sel);
const esc = v => String(v ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
function t(key, vars) {
let s = (T[S.lang] && T[S.lang][key]) ?? T.en[key] ?? key;
if (vars) s = s.replace(/\{(\w+)\}/g, (m, k) => vars[k] ?? '');
return s;
}
function toast(msg) {
const el = $('#toast'); el.textContent = msg; el.classList.add('show');
clearTimeout(toast.h); toast.h = setTimeout(() => el.classList.remove('show'), 1800);
}
async function api(path, opts = {}) {
const headers = {'X-Requested-With': 'ScaleBridge'};
if (opts.body !== undefined) headers['Content-Type'] = 'application/json';
let data;
try {
const res = await fetch(path, {method: opts.method || (opts.body !== undefined ? 'POST' : 'GET'), headers,
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined, cache: 'no-store'});
try { data = await res.json(); } catch (e) { data = {ok: false, error: 'HTTP ' + res.status}; }
if (!res.ok) data.ok = false;
} catch (e) { data = {ok: false, error: String(e.message || e)}; }
return data;
}
async function copy(text) {
try { await navigator.clipboard.writeText(text); } catch (e) {
const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select();
try { document.execCommand('copy'); } catch (e2) {} ta.remove();
}
toast(t('copied'));
}
function fmtValue(v) {
if (v === null || v === undefined) return '—';
const n = Number(v); if (!isFinite(n)) return esc(v);
return n.toLocaleString('en-US', {maximumFractionDigits: 6});
}
function protoName(id) { const p = S.protocols.find(x => x.id === id); return p ? p.name : id; }
function slug(s) { return String(s || '').replace(/[^A-Za-z0-9_-]+/g, '-').replace(/^[-_]+|[-_]+$/g, '').toLowerCase().slice(0, 40) || 'scale'; }
function lineText(serial) { return serial ? `${serial.baudrate} ${serial.bytesize}${serial.parity}${serial.stopbits}` : ''; }
// ---------------------------------------------------------------- chrome
function applyLang() {
document.documentElement.lang = S.lang;
document.documentElement.dir = S.lang === 'fa' ? 'rtl' : 'ltr';
document.querySelectorAll('[data-t]').forEach(el => { el.textContent = t(el.dataset.t); });
$('#langBtn').textContent = t('lang');
$('#ver').textContent = 'v' + ABOUT.version;
$('#sign').innerHTML = `${esc(t('made_by'))} ${esc(ABOUT.author)} · ${esc(ABOUT.company)}${esc(ABOUT.website.replace(/^https?:\/\//, ''))}GitHubLinkedIn${esc(t('source'))}${esc(ABOUT.license)}`;
renderAll();
}
function setTab(tab) {
S.tab = tab;
document.querySelectorAll('#tabs button').forEach(b => b.classList.toggle('on', b.dataset.tab === tab));
document.querySelectorAll('main > section').forEach(s => s.classList.toggle('hidden', s.id !== 'p-' + tab));
if (tab !== 'manual') stopMonitor();
if (tab === 'find') loadPorts();
if (location.hash !== '#' + tab) history.replaceState(null, '', '#' + tab);
renderAll();
}
function renderAll(keepFind) {
renderScales(); if (keepFind) paintResults(); else renderFind();
renderConnect(); renderManual(); renderProtocols(); renderAbout();
}
// ---------------------------------------------------------------- scales
function renderScales() {
const el = $('#p-scales'); if (!S.state) { el.innerHTML = ''; return; }
const profiles = S.state.profiles || [];
if (!profiles.length) {
el.innerHTML = `
`;
}
// ---------------------------------------------------------------- events & boot
document.addEventListener('click', async ev => {
const b = ev.target.closest('button'); if (!b || b.disabled) return;
if (b.dataset.tab) return setTab(b.dataset.tab);
if (b.dataset.go) return setTab(b.dataset.go);
if (b.id === 'langBtn') { S.lang = S.lang === 'fa' ? 'en' : 'fa'; try { localStorage.setItem('sb.lang', S.lang); } catch (e) {} return applyLang(); }
if (b.dataset.del) {
if (!confirm(t('confirm_delete', {name: b.dataset.del}))) return;
const r = await api('/api/profiles/' + encodeURIComponent(b.dataset.del), {method: 'DELETE'});
if (r.ok) { S.state.profiles = r.profiles; delete S.weights[b.dataset.del]; renderAll(); } else toast(t('failed', {error: r.error}));
return;
}
if (b.dataset.saveResult) return saveResult(b.dataset.saveResult);
if (b.dataset.copy) return copy(b.dataset.copy);
switch (b.id) {
case 'startDetect': return startDetect();
case 'cancelDetect': await api('/api/detect/cancel', {body: {}}); return;
case 'refreshPorts': return loadPorts();
case 'keyShow': S.showKey = !S.showKey; return renderConnect();
case 'keyCopy': return copy(S.state.token);
case 'keyNew':
if (!confirm(t('confirm_key'))) return;
{ const r = await api('/api/token', {body: {}}); if (r.ok) { S.state.token = r.token; S.showKey = true; renderConnect(); } }
return;
case 'fwOpen': return adminAction('/api/firewall', {remote_ip: $('#fwRemote').value.trim()});
case 'fwRemove': return adminAction('/api/firewall', {remove: true});
case 'asOn': return adminAction('/api/autostart', {enable: true});
case 'asOff': return adminAction('/api/autostart', {enable: false});
case 'allowedSave': {
const clients = $('#allowed').value.split(/[,\s]+/).filter(Boolean);
const r = await api('/api/allowed', {body: {clients}});
if (r.ok) { S.state.allowed_clients = r.allowed_clients; toast(t('saved')); } else toast(t('failed', {error: r.error}));
return;
}
case 'monBtn': return S.monitor ? stopMonitor() : startMonitor();
case 'm-save': return saveManual();
}
});
document.addEventListener('input', ev => {
const el = ev.target; const port = el.dataset && (el.dataset.name || el.dataset.label || el.dataset.unit);
if (!port) return;
const d = S.draft[port] = S.draft[port] || {};
if (el.dataset.name) d.name = el.value; else if (el.dataset.label) d.label = el.value; else d.unit = el.value;
});
document.addEventListener('change', ev => {
if (ev.target.name === 'port') {
S.sel = new Set([...document.querySelectorAll('input[name=port]:checked')].map(i => i.value));
}
if (ev.target.id === 'm-proto') {
const p = S.protocols.find(x => x.id === ev.target.value);
if (p) { $('#m-request').value = p.request_hex; const s = $('#m-sproto'); if (s) { s.value = p.id; renderCustom(); } }
}
if (ev.target.id === 'm-sproto') renderCustom();
});
async function refreshState(fresh) {
const r = await api('/api/state' + (fresh ? '?fresh=1' : ''));
if (r.ok) {
S.state = r; $('#simPill').classList.toggle('hidden', !r.simulate);
const typing = document.activeElement && document.activeElement.closest && document.activeElement.closest('#p-find');
renderAll(!!typing);
}
}
async function boot() {
const [state, protos, ports, job] = await Promise.all([api('/api/state'), api('/api/protocols'), api('/api/ports'), api('/api/detect')]);
if (protos.ok) S.protocols = protos.protocols;
if (ports.ok) S.ports = ports.ports;
if (state.ok) { S.state = state; $('#simPill').classList.toggle('hidden', !state.simulate); }
if (job.ok && job.job) { S.job = job.job; S.log = job.job.log; S.jobSince = job.job.log_size; if (job.job.running) pollDetect(); }
applyLang();
const tab = location.hash.replace('#', '');
setTab(['scales','find','connect','manual','protocols','about'].includes(tab) ? tab : ((state.profiles || []).length ? 'scales' : 'find'));
pollWeights();
}
boot();
"""
_REMOTE = r"""
SyncPulse Scale Bridge
SyncPulse Scale Bridge __VERSION__
This bridge serves the scales connected to this PC to the network (JSON over HTTP, see
the documentation). Its control page opens only on the PC itself:
http://127.0.0.1:__PORT__/
این پل وزن باسکولهای وصل به این رایانه را روی شبکه در اختیار میگذارد. صفحهی مدیریت فقط روی خود همین رایانه باز میشود.