@feynmanzhang/open-party 0.1.3-beta.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +895 -280
- package/dist/cli/index.js.map +1 -1
- package/dist/party-server.js +373 -23
- package/dist/party-server.js.map +1 -1
- package/package.json +1 -1
package/dist/party-server.js
CHANGED
|
@@ -28,6 +28,7 @@ var init_config = __esm({
|
|
|
28
28
|
import { execFileSync, execSync } from "child_process";
|
|
29
29
|
import { existsSync } from "fs";
|
|
30
30
|
import { join } from "path";
|
|
31
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
31
32
|
function parsePossiblyNoisyJson(raw2) {
|
|
32
33
|
const trimmed = raw2.trim();
|
|
33
34
|
const start = trimmed.indexOf("{");
|
|
@@ -192,6 +193,62 @@ function getTailscaleInstallationStatus() {
|
|
|
192
193
|
function resetTailscaleBinaryCache() {
|
|
193
194
|
cachedBinary = null;
|
|
194
195
|
}
|
|
196
|
+
function logoutTailscale(timeout = 15e3) {
|
|
197
|
+
const binary = getTailscaleBinary();
|
|
198
|
+
try {
|
|
199
|
+
const output = runExec([binary, "logout"], timeout);
|
|
200
|
+
resetTailscaleBinaryCache();
|
|
201
|
+
return { success: true, output: output.trim() };
|
|
202
|
+
} catch (e) {
|
|
203
|
+
const err = e;
|
|
204
|
+
return { success: false, output: (err.stderr ?? err.message ?? "").trim() };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function startInteractiveLogin() {
|
|
208
|
+
const binary = getTailscaleBinary();
|
|
209
|
+
const child = nodeSpawn(binary, ["login"], {
|
|
210
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
211
|
+
windowsHide: true
|
|
212
|
+
});
|
|
213
|
+
const urlRegex = /https:\/\/login\.tailscale\.com\/a\/[^\s]+/;
|
|
214
|
+
const promise = new Promise((resolve) => {
|
|
215
|
+
let stdout = "";
|
|
216
|
+
let resolved = false;
|
|
217
|
+
const done = (result) => {
|
|
218
|
+
if (resolved) return;
|
|
219
|
+
resolved = true;
|
|
220
|
+
resolve(result);
|
|
221
|
+
};
|
|
222
|
+
child.stdout?.on("data", (data) => {
|
|
223
|
+
stdout += data.toString();
|
|
224
|
+
const match2 = stdout.match(urlRegex);
|
|
225
|
+
if (match2) {
|
|
226
|
+
done({ success: true, url: match2[0], output: stdout.trim() });
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
child.stderr?.on("data", (data) => {
|
|
230
|
+
stdout += data.toString();
|
|
231
|
+
});
|
|
232
|
+
child.on("close", (code) => {
|
|
233
|
+
if (code === 0) {
|
|
234
|
+
done({ success: true, output: stdout.trim() });
|
|
235
|
+
} else {
|
|
236
|
+
done({ success: false, output: stdout.trim() || `Exited with code ${code}` });
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
child.on("error", (err) => {
|
|
240
|
+
done({ success: false, output: err.message });
|
|
241
|
+
});
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
done({ success: false, output: "Timeout waiting for login URL" });
|
|
244
|
+
try {
|
|
245
|
+
child.kill();
|
|
246
|
+
} catch {
|
|
247
|
+
}
|
|
248
|
+
}, 3e4);
|
|
249
|
+
});
|
|
250
|
+
return { promise, process: child };
|
|
251
|
+
}
|
|
195
252
|
function getInstallInstructions(platform) {
|
|
196
253
|
switch (platform) {
|
|
197
254
|
case "linux":
|
|
@@ -700,6 +757,58 @@ var init_state = __esm({
|
|
|
700
757
|
}
|
|
701
758
|
});
|
|
702
759
|
|
|
760
|
+
// src/cli/tailscale-installer.ts
|
|
761
|
+
var tailscale_installer_exports = {};
|
|
762
|
+
__export(tailscale_installer_exports, {
|
|
763
|
+
installTailscale: () => installTailscale
|
|
764
|
+
});
|
|
765
|
+
import { spawn } from "child_process";
|
|
766
|
+
async function installTailscale(platform) {
|
|
767
|
+
const entry = INSTALL_COMMANDS[platform];
|
|
768
|
+
if (!entry) {
|
|
769
|
+
return {
|
|
770
|
+
success: false,
|
|
771
|
+
output: `Unsupported platform: ${platform}. Please install manually from https://tailscale.com/download`
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
const cmd = entry.needsSudo ? "sudo" : entry.cmd;
|
|
775
|
+
const args = entry.needsSudo ? [entry.cmd, ...entry.args] : entry.args;
|
|
776
|
+
console.log(`Running: ${cmd} ${args.join(" ")}
|
|
777
|
+
`);
|
|
778
|
+
return new Promise((resolve) => {
|
|
779
|
+
const child = spawn(cmd, args, {
|
|
780
|
+
stdio: "inherit",
|
|
781
|
+
windowsHide: true
|
|
782
|
+
});
|
|
783
|
+
let exited = false;
|
|
784
|
+
child.on("close", (code) => {
|
|
785
|
+
if (exited) return;
|
|
786
|
+
exited = true;
|
|
787
|
+
if (code === 0) {
|
|
788
|
+
resolve({ success: true, output: "Installation completed." });
|
|
789
|
+
} else {
|
|
790
|
+
resolve({ success: false, output: `Installation exited with code ${code}` });
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
child.on("error", (err) => {
|
|
794
|
+
if (exited) return;
|
|
795
|
+
exited = true;
|
|
796
|
+
resolve({ success: false, output: err.message });
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
var INSTALL_COMMANDS;
|
|
801
|
+
var init_tailscale_installer = __esm({
|
|
802
|
+
"src/cli/tailscale-installer.ts"() {
|
|
803
|
+
"use strict";
|
|
804
|
+
INSTALL_COMMANDS = {
|
|
805
|
+
linux: { cmd: "bash", args: ["-c", "curl -fsSL https://tailscale.com/install.sh | sh"], needsSudo: true },
|
|
806
|
+
darwin: { cmd: "brew", args: ["install", "tailscale"], needsSudo: false },
|
|
807
|
+
win32: { cmd: "winget", args: ["install", "Tailscale.Tailscale", "--accept-source-agreements"], needsSudo: false }
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
|
|
703
812
|
// node_modules/hono/dist/compose.js
|
|
704
813
|
var compose = (middleware, onError, onNotFound) => {
|
|
705
814
|
return (context, next) => {
|
|
@@ -3869,7 +3978,21 @@ body::after{
|
|
|
3869
3978
|
}
|
|
3870
3979
|
.btn-join:hover{background:rgba(0,255,240,0.18);box-shadow:0 0 10px rgba(0,255,240,0.2)}
|
|
3871
3980
|
.btn-join:active{transform:scale(0.97)}
|
|
3872
|
-
.btn-
|
|
3981
|
+
.btn-logout{border-color:var(--red);color:var(--red);background:rgba(255,51,102,0.08)}
|
|
3982
|
+
.btn-logout:hover{background:rgba(255,51,102,0.18);box-shadow:0 0 10px rgba(255,51,102,0.2)}
|
|
3983
|
+
.btn-install{border-color:var(--yellow);color:var(--yellow);background:rgba(255,170,0,0.08)}
|
|
3984
|
+
.btn-install:hover{background:rgba(255,170,0,0.18);box-shadow:0 0 10px rgba(255,170,0,0.2)}
|
|
3985
|
+
|
|
3986
|
+
/* Tab bar inside modal */
|
|
3987
|
+
.tab-bar{display:flex;gap:0;margin-bottom:18px;border-bottom:1px solid var(--border)}
|
|
3988
|
+
.tab-bar .tab{
|
|
3989
|
+
font-family:var(--font-mono);font-size:0.8rem;padding:8px 16px;cursor:pointer;
|
|
3990
|
+
color:var(--muted);border-bottom:2px solid transparent;transition:all 0.2s;
|
|
3991
|
+
}
|
|
3992
|
+
.tab-bar .tab:hover{color:var(--text)}
|
|
3993
|
+
.tab-bar .tab.active{color:var(--cyan);border-bottom-color:var(--cyan)}
|
|
3994
|
+
.tab-content{display:none}
|
|
3995
|
+
.tab-content.active{display:block}
|
|
3873
3996
|
|
|
3874
3997
|
/* Modal */
|
|
3875
3998
|
.modal-overlay{
|
|
@@ -4042,16 +4165,47 @@ body::after{
|
|
|
4042
4165
|
|
|
4043
4166
|
<div class="footer">OPEN PARTY v0.1 // DECENTRALIZED AGENT NETWORK</div>
|
|
4044
4167
|
|
|
4045
|
-
<!-- Join Network Modal -->
|
|
4168
|
+
<!-- Join Network / Login Modal (two tabs: Interactive + Auth Key) -->
|
|
4046
4169
|
<div class="modal-overlay" id="joinModal">
|
|
4047
4170
|
<div class="modal">
|
|
4048
|
-
<div class="modal-title">
|
|
4049
|
-
<div class="
|
|
4050
|
-
|
|
4051
|
-
|
|
4171
|
+
<div class="modal-title">CONNECT TO TAILNET</div>
|
|
4172
|
+
<div class="tab-bar" id="joinTabs">
|
|
4173
|
+
<div class="tab active" data-tab="interactive">Interactive</div>
|
|
4174
|
+
<div class="tab" data-tab="authkey">Auth Key</div>
|
|
4175
|
+
</div>
|
|
4176
|
+
|
|
4177
|
+
<!-- Interactive tab -->
|
|
4178
|
+
<div class="tab-content active" id="tabInteractive">
|
|
4179
|
+
<div class="modal-desc">Click the button below to open a browser authentication page.<br>Your Tailscale connection will be established once you authenticate.</div>
|
|
4180
|
+
<div class="modal-status" id="interactiveStatus"></div>
|
|
4181
|
+
<div class="modal-actions">
|
|
4182
|
+
<button class="modal-btn modal-btn-cancel" id="btnCancelJoin">Cancel</button>
|
|
4183
|
+
<button class="modal-btn modal-btn-submit" id="btnInteractiveLogin">Open Browser Login</button>
|
|
4184
|
+
</div>
|
|
4185
|
+
</div>
|
|
4186
|
+
|
|
4187
|
+
<!-- Auth Key tab -->
|
|
4188
|
+
<div class="tab-content" id="tabAuthkey">
|
|
4189
|
+
<div class="modal-desc">Enter your Tailscale auth key to join the network.<br>You can generate one from the Tailscale admin console.</div>
|
|
4190
|
+
<input type="password" class="modal-input" id="authKeyInput" placeholder="tskey-auth-xxxxx..." autocomplete="off" spellcheck="false" />
|
|
4191
|
+
<div class="modal-status" id="joinStatus"></div>
|
|
4192
|
+
<div class="modal-actions">
|
|
4193
|
+
<button class="modal-btn modal-btn-cancel" id="btnCancelAuthkey">Cancel</button>
|
|
4194
|
+
<button class="modal-btn modal-btn-submit" id="btnSubmitJoin">Connect</button>
|
|
4195
|
+
</div>
|
|
4196
|
+
</div>
|
|
4197
|
+
</div>
|
|
4198
|
+
</div>
|
|
4199
|
+
|
|
4200
|
+
<!-- Logout Confirmation Modal -->
|
|
4201
|
+
<div class="modal-overlay" id="logoutModal">
|
|
4202
|
+
<div class="modal">
|
|
4203
|
+
<div class="modal-title" style="color:var(--red)">LOG OUT OF TAILNET</div>
|
|
4204
|
+
<div class="modal-desc">This will disconnect from Tailscale and remove your credentials.<br>You will need to re-authenticate to reconnect.</div>
|
|
4205
|
+
<div class="modal-status" id="logoutStatus"></div>
|
|
4052
4206
|
<div class="modal-actions">
|
|
4053
|
-
<button class="modal-btn modal-btn-cancel" id="
|
|
4054
|
-
<button class="modal-btn modal-btn-submit" id="
|
|
4207
|
+
<button class="modal-btn modal-btn-cancel" id="btnCancelLogout">Cancel</button>
|
|
4208
|
+
<button class="modal-btn modal-btn-submit" style="border-color:var(--red);color:var(--red);background:rgba(255,51,102,0.12)" id="btnConfirmLogout">Log Out</button>
|
|
4055
4209
|
</div>
|
|
4056
4210
|
</div>
|
|
4057
4211
|
</div>
|
|
@@ -4351,28 +4505,63 @@ body::after{
|
|
|
4351
4505
|
}
|
|
4352
4506
|
}, 1000);
|
|
4353
4507
|
|
|
4354
|
-
// ---- Join
|
|
4508
|
+
// ---- Join Modal Tabs ----
|
|
4509
|
+
const joinTabs = $$('#joinTabs .tab');
|
|
4510
|
+
joinTabs.forEach(function(tab) {
|
|
4511
|
+
tab.addEventListener('click', function() {
|
|
4512
|
+
joinTabs.forEach(function(t) { t.classList.remove('active'); });
|
|
4513
|
+
tab.classList.add('active');
|
|
4514
|
+
// Toggle tab contents
|
|
4515
|
+
const target = tab.getAttribute('data-tab');
|
|
4516
|
+
$$('.tab-content').forEach(function(tc) { tc.classList.remove('active'); });
|
|
4517
|
+
if (target === 'interactive') {
|
|
4518
|
+
$('#tabInteractive').classList.add('active');
|
|
4519
|
+
} else {
|
|
4520
|
+
$('#tabAuthkey').classList.add('active');
|
|
4521
|
+
}
|
|
4522
|
+
});
|
|
4523
|
+
});
|
|
4524
|
+
|
|
4525
|
+
// ---- Join Network Modal (open/close) ----
|
|
4355
4526
|
const joinModal = $('#joinModal');
|
|
4356
4527
|
const btnJoin = $('#btnJoinNetwork');
|
|
4357
4528
|
const btnCancel = $('#btnCancelJoin');
|
|
4529
|
+
const btnCancelAuthkey = $('#btnCancelAuthkey');
|
|
4358
4530
|
const btnSubmit = $('#btnSubmitJoin');
|
|
4359
4531
|
const authKeyInput = $('#authKeyInput');
|
|
4360
4532
|
const joinStatus = $('#joinStatus');
|
|
4361
4533
|
|
|
4362
4534
|
function openJoinModal() {
|
|
4535
|
+
// Reset both tabs
|
|
4363
4536
|
joinStatus.className = 'modal-status';
|
|
4364
4537
|
joinStatus.textContent = '';
|
|
4365
4538
|
authKeyInput.value = '';
|
|
4539
|
+
$('#interactiveStatus').className = 'modal-status';
|
|
4540
|
+
$('#interactiveStatus').textContent = '';
|
|
4541
|
+
// Default to Interactive tab
|
|
4542
|
+
$$('#joinTabs .tab').forEach(function(t) { t.classList.remove('active'); });
|
|
4543
|
+
$$('#joinTabs .tab')[0].classList.add('active');
|
|
4544
|
+
$$('.tab-content').forEach(function(tc) { tc.classList.remove('active'); });
|
|
4545
|
+
$('#tabInteractive').classList.add('active');
|
|
4366
4546
|
joinModal.classList.add('open');
|
|
4367
|
-
setTimeout(function() { authKeyInput.focus(); }, 100);
|
|
4368
4547
|
}
|
|
4369
4548
|
|
|
4370
4549
|
function closeJoinModal() {
|
|
4371
4550
|
joinModal.classList.remove('open');
|
|
4372
4551
|
}
|
|
4373
4552
|
|
|
4374
|
-
btnJoin.addEventListener('click',
|
|
4553
|
+
btnJoin.addEventListener('click', function() {
|
|
4554
|
+
// Decide action based on Tailscale state
|
|
4555
|
+
if (tsState && tsState.state === 'connected') {
|
|
4556
|
+
openLogoutModal();
|
|
4557
|
+
} else if (tsState && tsState.state === 'not_installed') {
|
|
4558
|
+
doInstallTailscale();
|
|
4559
|
+
} else {
|
|
4560
|
+
openJoinModal();
|
|
4561
|
+
}
|
|
4562
|
+
});
|
|
4375
4563
|
btnCancel.addEventListener('click', closeJoinModal);
|
|
4564
|
+
btnCancelAuthkey.addEventListener('click', closeJoinModal);
|
|
4376
4565
|
joinModal.addEventListener('click', function(e) {
|
|
4377
4566
|
if (e.target === joinModal) closeJoinModal();
|
|
4378
4567
|
});
|
|
@@ -4381,6 +4570,7 @@ body::after{
|
|
|
4381
4570
|
if (e.key === 'Escape') closeJoinModal();
|
|
4382
4571
|
});
|
|
4383
4572
|
|
|
4573
|
+
// ---- Auth Key submit ----
|
|
4384
4574
|
btnSubmit.addEventListener('click', async function() {
|
|
4385
4575
|
const key = authKeyInput.value.trim();
|
|
4386
4576
|
if (!key) {
|
|
@@ -4402,9 +4592,9 @@ body::after{
|
|
|
4402
4592
|
if (data.success) {
|
|
4403
4593
|
joinStatus.className = 'modal-status success';
|
|
4404
4594
|
joinStatus.textContent = 'Successfully joined network!';
|
|
4405
|
-
btnJoin.textContent = '
|
|
4406
|
-
btnJoin.
|
|
4407
|
-
setTimeout(function() { closeJoinModal(); fullRefresh(); }, 1500);
|
|
4595
|
+
btnJoin.textContent = 'Logout';
|
|
4596
|
+
btnJoin.className = 'btn-join btn-logout';
|
|
4597
|
+
setTimeout(function() { closeJoinModal(); checkTailscaleStatus(); fullRefresh(); }, 1500);
|
|
4408
4598
|
} else {
|
|
4409
4599
|
joinStatus.className = 'modal-status error';
|
|
4410
4600
|
joinStatus.textContent = data.output || 'Failed to join network';
|
|
@@ -4417,6 +4607,133 @@ body::after{
|
|
|
4417
4607
|
btnSubmit.textContent = 'Connect';
|
|
4418
4608
|
});
|
|
4419
4609
|
|
|
4610
|
+
// ---- Interactive Login ----
|
|
4611
|
+
const btnInteractiveLogin = $('#btnInteractiveLogin');
|
|
4612
|
+
btnInteractiveLogin.addEventListener('click', async function() {
|
|
4613
|
+
const statusEl = $('#interactiveStatus');
|
|
4614
|
+
statusEl.className = 'modal-status';
|
|
4615
|
+
statusEl.textContent = '';
|
|
4616
|
+
btnInteractiveLogin.disabled = true;
|
|
4617
|
+
btnInteractiveLogin.innerHTML = '<span class="spinner"></span>Opening browser...';
|
|
4618
|
+
|
|
4619
|
+
try {
|
|
4620
|
+
const r = await fetch('/dashboard/api/tailscale-login', { method: 'POST' });
|
|
4621
|
+
const data = await r.json();
|
|
4622
|
+
|
|
4623
|
+
if (data.success && data.url) {
|
|
4624
|
+
// Open the auth URL in a new tab
|
|
4625
|
+
window.open(data.url, '_blank');
|
|
4626
|
+
statusEl.className = 'modal-status success';
|
|
4627
|
+
statusEl.textContent = 'Authentication page opened in your browser. Waiting for connection...';
|
|
4628
|
+
|
|
4629
|
+
// Poll for connection
|
|
4630
|
+
var pollCount = 0;
|
|
4631
|
+
var pollInterval = setInterval(async function() {
|
|
4632
|
+
pollCount++;
|
|
4633
|
+
if (pollCount > 40) { // 2 minutes timeout
|
|
4634
|
+
clearInterval(pollInterval);
|
|
4635
|
+
statusEl.className = 'modal-status error';
|
|
4636
|
+
statusEl.textContent = 'Timed out waiting for authentication. Please try again.';
|
|
4637
|
+
btnInteractiveLogin.disabled = false;
|
|
4638
|
+
btnInteractiveLogin.textContent = 'Open Browser Login';
|
|
4639
|
+
return;
|
|
4640
|
+
}
|
|
4641
|
+
try {
|
|
4642
|
+
var sr = await fetch('/dashboard/api/tailscale-status');
|
|
4643
|
+
var sd = await sr.json();
|
|
4644
|
+
if (sd.state === 'connected') {
|
|
4645
|
+
clearInterval(pollInterval);
|
|
4646
|
+
btnJoin.textContent = 'Logout';
|
|
4647
|
+
btnJoin.className = 'btn-join btn-logout';
|
|
4648
|
+
closeJoinModal();
|
|
4649
|
+
checkTailscaleStatus();
|
|
4650
|
+
fullRefresh();
|
|
4651
|
+
return;
|
|
4652
|
+
}
|
|
4653
|
+
} catch { /* poll error, continue */ }
|
|
4654
|
+
}, 3000);
|
|
4655
|
+
} else {
|
|
4656
|
+
statusEl.className = 'modal-status error';
|
|
4657
|
+
statusEl.textContent = data.output || 'Failed to start interactive login';
|
|
4658
|
+
btnInteractiveLogin.disabled = false;
|
|
4659
|
+
btnInteractiveLogin.textContent = 'Open Browser Login';
|
|
4660
|
+
}
|
|
4661
|
+
} catch (e) {
|
|
4662
|
+
statusEl.className = 'modal-status error';
|
|
4663
|
+
statusEl.textContent = 'Network error: ' + (e.message || 'unknown');
|
|
4664
|
+
btnInteractiveLogin.disabled = false;
|
|
4665
|
+
btnInteractiveLogin.textContent = 'Open Browser Login';
|
|
4666
|
+
}
|
|
4667
|
+
});
|
|
4668
|
+
|
|
4669
|
+
// ---- Logout Modal ----
|
|
4670
|
+
const logoutModal = $('#logoutModal');
|
|
4671
|
+
const btnConfirmLogout = $('#btnConfirmLogout');
|
|
4672
|
+
const btnCancelLogout = $('#btnCancelLogout');
|
|
4673
|
+
const logoutStatus = $('#logoutStatus');
|
|
4674
|
+
|
|
4675
|
+
function openLogoutModal() {
|
|
4676
|
+
logoutStatus.className = 'modal-status';
|
|
4677
|
+
logoutStatus.textContent = '';
|
|
4678
|
+
logoutModal.classList.add('open');
|
|
4679
|
+
}
|
|
4680
|
+
|
|
4681
|
+
btnCancelLogout.addEventListener('click', function() { logoutModal.classList.remove('open'); });
|
|
4682
|
+
logoutModal.addEventListener('click', function(e) { if (e.target === logoutModal) logoutModal.classList.remove('open'); });
|
|
4683
|
+
|
|
4684
|
+
btnConfirmLogout.addEventListener('click', async function() {
|
|
4685
|
+
btnConfirmLogout.disabled = true;
|
|
4686
|
+
btnConfirmLogout.innerHTML = '<span class="spinner"></span>Logging out...';
|
|
4687
|
+
logoutStatus.className = 'modal-status';
|
|
4688
|
+
logoutStatus.textContent = '';
|
|
4689
|
+
|
|
4690
|
+
try {
|
|
4691
|
+
const r = await fetch('/dashboard/api/logout', { method: 'POST' });
|
|
4692
|
+
const data = await r.json();
|
|
4693
|
+
logoutModal.classList.remove('open');
|
|
4694
|
+
if (data.success) {
|
|
4695
|
+
checkTailscaleStatus();
|
|
4696
|
+
fullRefresh();
|
|
4697
|
+
} else {
|
|
4698
|
+
alert('Logout failed: ' + (data.output || 'unknown error'));
|
|
4699
|
+
}
|
|
4700
|
+
} catch (e) {
|
|
4701
|
+
logoutModal.classList.remove('open');
|
|
4702
|
+
alert('Network error: ' + (e.message || 'unknown'));
|
|
4703
|
+
}
|
|
4704
|
+
btnConfirmLogout.disabled = false;
|
|
4705
|
+
btnConfirmLogout.textContent = 'Log Out';
|
|
4706
|
+
});
|
|
4707
|
+
|
|
4708
|
+
// ---- Install Tailscale ----
|
|
4709
|
+
async function doInstallTailscale() {
|
|
4710
|
+
if (!confirm('Install Tailscale on this machine?')) return;
|
|
4711
|
+
|
|
4712
|
+
btnJoin.disabled = true;
|
|
4713
|
+
btnJoin.innerHTML = '<span class="spinner"></span>Installing...';
|
|
4714
|
+
|
|
4715
|
+
try {
|
|
4716
|
+
const r = await fetch('/dashboard/api/install-tailscale', { method: 'POST' });
|
|
4717
|
+
const data = await r.json();
|
|
4718
|
+
if (data.success) {
|
|
4719
|
+
btnJoin.textContent = 'Installed';
|
|
4720
|
+
btnJoin.disabled = false;
|
|
4721
|
+
checkTailscaleStatus();
|
|
4722
|
+
fullRefresh();
|
|
4723
|
+
} else {
|
|
4724
|
+
alert('Installation failed: ' + (data.output || 'unknown error'));
|
|
4725
|
+
btnJoin.textContent = 'Install Tailscale';
|
|
4726
|
+
btnJoin.className = 'btn-join btn-install';
|
|
4727
|
+
btnJoin.disabled = false;
|
|
4728
|
+
}
|
|
4729
|
+
} catch (e) {
|
|
4730
|
+
alert('Network error: ' + (e.message || 'unknown'));
|
|
4731
|
+
btnJoin.textContent = 'Install Tailscale';
|
|
4732
|
+
btnJoin.className = 'btn-join btn-install';
|
|
4733
|
+
btnJoin.disabled = false;
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4420
4737
|
// Check initial Tailscale status (tri-state)
|
|
4421
4738
|
let tsState = null;
|
|
4422
4739
|
let tsInstallInfo = null;
|
|
@@ -4435,17 +4752,23 @@ body::after{
|
|
|
4435
4752
|
if (tsState.state === 'connected') {
|
|
4436
4753
|
dot.className = 'status-dot';
|
|
4437
4754
|
text.textContent = 'ONLINE';
|
|
4438
|
-
btnJoin.textContent = '
|
|
4439
|
-
btnJoin.
|
|
4755
|
+
btnJoin.textContent = 'Logout';
|
|
4756
|
+
btnJoin.className = 'btn-join btn-logout';
|
|
4757
|
+
btnJoin.style.display = '';
|
|
4440
4758
|
panel.style.display = 'none';
|
|
4441
4759
|
} else if (tsState.state === 'not_installed') {
|
|
4442
4760
|
dot.className = 'status-dot not-installed';
|
|
4443
4761
|
text.textContent = 'NOT INSTALLED';
|
|
4444
|
-
btnJoin.
|
|
4762
|
+
btnJoin.textContent = 'Install Tailscale';
|
|
4763
|
+
btnJoin.className = 'btn-join btn-install';
|
|
4764
|
+
btnJoin.style.display = '';
|
|
4445
4765
|
await renderNotInstalledPanel();
|
|
4446
4766
|
} else {
|
|
4447
4767
|
dot.className = 'status-dot not-connected';
|
|
4448
4768
|
text.textContent = 'NOT CONNECTED';
|
|
4769
|
+
btnJoin.textContent = 'Join Network';
|
|
4770
|
+
btnJoin.className = 'btn-join';
|
|
4771
|
+
btnJoin.style.display = '';
|
|
4449
4772
|
await renderNotConnectedPanel();
|
|
4450
4773
|
}
|
|
4451
4774
|
}
|
|
@@ -4479,7 +4802,7 @@ body::after{
|
|
|
4479
4802
|
html += '</div>';
|
|
4480
4803
|
}
|
|
4481
4804
|
|
|
4482
|
-
html += '<div class="ts-setup-hint">Or run <code style="color:var(--cyan)">npx open-party setup</code
|
|
4805
|
+
html += '<div class="ts-setup-hint">Or click the <strong>Install Tailscale</strong> button above, or run <code style="color:var(--cyan)">npx open-party setup</code></div>';
|
|
4483
4806
|
html += '<button class="btn-redetect" onclick="window.__redetectTailscale()">Re-detect</button>';
|
|
4484
4807
|
panel.innerHTML = html;
|
|
4485
4808
|
panel.style.display = 'block';
|
|
@@ -4487,14 +4810,10 @@ body::after{
|
|
|
4487
4810
|
|
|
4488
4811
|
async function renderNotConnectedPanel() {
|
|
4489
4812
|
const panel = $('#tsPanel');
|
|
4490
|
-
const btnJoin = $('#btnJoinNetwork');
|
|
4491
|
-
btnJoin.style.display = '';
|
|
4492
|
-
btnJoin.textContent = 'Join Network';
|
|
4493
|
-
btnJoin.classList.remove('connected');
|
|
4494
4813
|
|
|
4495
4814
|
let html = '<div class="ts-panel-title not-connected">Tailscale Not Connected</div>';
|
|
4496
4815
|
html += '<div class="ts-info-row"><span class="label">Status:</span><span class="value" style="color:var(--yellow)">Installed but not authenticated</span></div>';
|
|
4497
|
-
html += '<div class="ts-setup-hint">
|
|
4816
|
+
html += '<div class="ts-setup-hint">Use the <strong>Join Network</strong> button above to log in</div>';
|
|
4498
4817
|
html += '<button class="btn-redetect" onclick="window.__redetectTailscale()">Re-detect</button>';
|
|
4499
4818
|
panel.innerHTML = html;
|
|
4500
4819
|
panel.style.display = 'block';
|
|
@@ -4617,6 +4936,37 @@ dashboardRoutes.post("/api/join-network", async (c) => {
|
|
|
4617
4936
|
return c.json({ success: false, output: e.message }, 500);
|
|
4618
4937
|
}
|
|
4619
4938
|
});
|
|
4939
|
+
var activeLogin = null;
|
|
4940
|
+
dashboardRoutes.post("/api/logout", async (c) => {
|
|
4941
|
+
const result = logoutTailscale();
|
|
4942
|
+
if (result.success) {
|
|
4943
|
+
resetTailscaleBinaryCache();
|
|
4944
|
+
refreshSelfIp();
|
|
4945
|
+
}
|
|
4946
|
+
return c.json(result, result.success ? 200 : 500);
|
|
4947
|
+
});
|
|
4948
|
+
dashboardRoutes.post("/api/tailscale-login", async (c) => {
|
|
4949
|
+
if (activeLogin?.url) {
|
|
4950
|
+
return c.json({ success: true, url: activeLogin.url });
|
|
4951
|
+
}
|
|
4952
|
+
const { promise, process: process2 } = startInteractiveLogin();
|
|
4953
|
+
activeLogin = { process: process2 };
|
|
4954
|
+
const result = await promise;
|
|
4955
|
+
if (result.success && result.url) {
|
|
4956
|
+
activeLogin.url = result.url;
|
|
4957
|
+
return c.json({ success: true, url: result.url });
|
|
4958
|
+
}
|
|
4959
|
+
activeLogin = null;
|
|
4960
|
+
return c.json({ success: false, output: result.output }, 500);
|
|
4961
|
+
});
|
|
4962
|
+
dashboardRoutes.post("/api/install-tailscale", async (c) => {
|
|
4963
|
+
const { installTailscale: installTailscale2 } = await Promise.resolve().then(() => (init_tailscale_installer(), tailscale_installer_exports));
|
|
4964
|
+
const result = await installTailscale2(process.platform);
|
|
4965
|
+
if (result.success) {
|
|
4966
|
+
resetTailscaleBinaryCache();
|
|
4967
|
+
}
|
|
4968
|
+
return c.json(result, result.success ? 200 : 500);
|
|
4969
|
+
});
|
|
4620
4970
|
|
|
4621
4971
|
// src/server/index.ts
|
|
4622
4972
|
async function periodicCleanup() {
|