@feynmanzhang/open-party 0.1.2 → 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 +1227 -291
- package/dist/cli/index.js.map +1 -1
- package/dist/party-server.js +389 -24
- 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("{");
|
|
@@ -152,7 +153,7 @@ function joinTailnet(authKey, timeout = 3e4) {
|
|
|
152
153
|
const binary = getTailscaleBinary();
|
|
153
154
|
try {
|
|
154
155
|
const output = execWithSudoFallback(
|
|
155
|
-
[binary, "up", "--authkey", authKey
|
|
156
|
+
[binary, "up", "--authkey", authKey],
|
|
156
157
|
timeout
|
|
157
158
|
);
|
|
158
159
|
return { success: true, output: output.trim() };
|
|
@@ -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) => {
|
|
@@ -3502,6 +3611,9 @@ var serve = (options, listeningListener) => {
|
|
|
3502
3611
|
// src/server/index.ts
|
|
3503
3612
|
init_config();
|
|
3504
3613
|
init_state();
|
|
3614
|
+
import { mkdirSync, writeFileSync, unlinkSync } from "fs";
|
|
3615
|
+
import { join as join2, dirname } from "path";
|
|
3616
|
+
import { homedir } from "os";
|
|
3505
3617
|
|
|
3506
3618
|
// src/server/models.ts
|
|
3507
3619
|
function sanitizeAgentInfo(info) {
|
|
@@ -3866,7 +3978,21 @@ body::after{
|
|
|
3866
3978
|
}
|
|
3867
3979
|
.btn-join:hover{background:rgba(0,255,240,0.18);box-shadow:0 0 10px rgba(0,255,240,0.2)}
|
|
3868
3980
|
.btn-join:active{transform:scale(0.97)}
|
|
3869
|
-
.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}
|
|
3870
3996
|
|
|
3871
3997
|
/* Modal */
|
|
3872
3998
|
.modal-overlay{
|
|
@@ -4039,16 +4165,47 @@ body::after{
|
|
|
4039
4165
|
|
|
4040
4166
|
<div class="footer">OPEN PARTY v0.1 // DECENTRALIZED AGENT NETWORK</div>
|
|
4041
4167
|
|
|
4042
|
-
<!-- Join Network Modal -->
|
|
4168
|
+
<!-- Join Network / Login Modal (two tabs: Interactive + Auth Key) -->
|
|
4043
4169
|
<div class="modal-overlay" id="joinModal">
|
|
4044
4170
|
<div class="modal">
|
|
4045
|
-
<div class="modal-title">
|
|
4046
|
-
<div class="
|
|
4047
|
-
|
|
4048
|
-
|
|
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>
|
|
4049
4206
|
<div class="modal-actions">
|
|
4050
|
-
<button class="modal-btn modal-btn-cancel" id="
|
|
4051
|
-
<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>
|
|
4052
4209
|
</div>
|
|
4053
4210
|
</div>
|
|
4054
4211
|
</div>
|
|
@@ -4348,28 +4505,63 @@ body::after{
|
|
|
4348
4505
|
}
|
|
4349
4506
|
}, 1000);
|
|
4350
4507
|
|
|
4351
|
-
// ---- 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) ----
|
|
4352
4526
|
const joinModal = $('#joinModal');
|
|
4353
4527
|
const btnJoin = $('#btnJoinNetwork');
|
|
4354
4528
|
const btnCancel = $('#btnCancelJoin');
|
|
4529
|
+
const btnCancelAuthkey = $('#btnCancelAuthkey');
|
|
4355
4530
|
const btnSubmit = $('#btnSubmitJoin');
|
|
4356
4531
|
const authKeyInput = $('#authKeyInput');
|
|
4357
4532
|
const joinStatus = $('#joinStatus');
|
|
4358
4533
|
|
|
4359
4534
|
function openJoinModal() {
|
|
4535
|
+
// Reset both tabs
|
|
4360
4536
|
joinStatus.className = 'modal-status';
|
|
4361
4537
|
joinStatus.textContent = '';
|
|
4362
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');
|
|
4363
4546
|
joinModal.classList.add('open');
|
|
4364
|
-
setTimeout(function() { authKeyInput.focus(); }, 100);
|
|
4365
4547
|
}
|
|
4366
4548
|
|
|
4367
4549
|
function closeJoinModal() {
|
|
4368
4550
|
joinModal.classList.remove('open');
|
|
4369
4551
|
}
|
|
4370
4552
|
|
|
4371
|
-
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
|
+
});
|
|
4372
4563
|
btnCancel.addEventListener('click', closeJoinModal);
|
|
4564
|
+
btnCancelAuthkey.addEventListener('click', closeJoinModal);
|
|
4373
4565
|
joinModal.addEventListener('click', function(e) {
|
|
4374
4566
|
if (e.target === joinModal) closeJoinModal();
|
|
4375
4567
|
});
|
|
@@ -4378,6 +4570,7 @@ body::after{
|
|
|
4378
4570
|
if (e.key === 'Escape') closeJoinModal();
|
|
4379
4571
|
});
|
|
4380
4572
|
|
|
4573
|
+
// ---- Auth Key submit ----
|
|
4381
4574
|
btnSubmit.addEventListener('click', async function() {
|
|
4382
4575
|
const key = authKeyInput.value.trim();
|
|
4383
4576
|
if (!key) {
|
|
@@ -4399,9 +4592,9 @@ body::after{
|
|
|
4399
4592
|
if (data.success) {
|
|
4400
4593
|
joinStatus.className = 'modal-status success';
|
|
4401
4594
|
joinStatus.textContent = 'Successfully joined network!';
|
|
4402
|
-
btnJoin.textContent = '
|
|
4403
|
-
btnJoin.
|
|
4404
|
-
setTimeout(function() { closeJoinModal(); fullRefresh(); }, 1500);
|
|
4595
|
+
btnJoin.textContent = 'Logout';
|
|
4596
|
+
btnJoin.className = 'btn-join btn-logout';
|
|
4597
|
+
setTimeout(function() { closeJoinModal(); checkTailscaleStatus(); fullRefresh(); }, 1500);
|
|
4405
4598
|
} else {
|
|
4406
4599
|
joinStatus.className = 'modal-status error';
|
|
4407
4600
|
joinStatus.textContent = data.output || 'Failed to join network';
|
|
@@ -4414,6 +4607,133 @@ body::after{
|
|
|
4414
4607
|
btnSubmit.textContent = 'Connect';
|
|
4415
4608
|
});
|
|
4416
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
|
+
|
|
4417
4737
|
// Check initial Tailscale status (tri-state)
|
|
4418
4738
|
let tsState = null;
|
|
4419
4739
|
let tsInstallInfo = null;
|
|
@@ -4432,17 +4752,23 @@ body::after{
|
|
|
4432
4752
|
if (tsState.state === 'connected') {
|
|
4433
4753
|
dot.className = 'status-dot';
|
|
4434
4754
|
text.textContent = 'ONLINE';
|
|
4435
|
-
btnJoin.textContent = '
|
|
4436
|
-
btnJoin.
|
|
4755
|
+
btnJoin.textContent = 'Logout';
|
|
4756
|
+
btnJoin.className = 'btn-join btn-logout';
|
|
4757
|
+
btnJoin.style.display = '';
|
|
4437
4758
|
panel.style.display = 'none';
|
|
4438
4759
|
} else if (tsState.state === 'not_installed') {
|
|
4439
4760
|
dot.className = 'status-dot not-installed';
|
|
4440
4761
|
text.textContent = 'NOT INSTALLED';
|
|
4441
|
-
btnJoin.
|
|
4762
|
+
btnJoin.textContent = 'Install Tailscale';
|
|
4763
|
+
btnJoin.className = 'btn-join btn-install';
|
|
4764
|
+
btnJoin.style.display = '';
|
|
4442
4765
|
await renderNotInstalledPanel();
|
|
4443
4766
|
} else {
|
|
4444
4767
|
dot.className = 'status-dot not-connected';
|
|
4445
4768
|
text.textContent = 'NOT CONNECTED';
|
|
4769
|
+
btnJoin.textContent = 'Join Network';
|
|
4770
|
+
btnJoin.className = 'btn-join';
|
|
4771
|
+
btnJoin.style.display = '';
|
|
4446
4772
|
await renderNotConnectedPanel();
|
|
4447
4773
|
}
|
|
4448
4774
|
}
|
|
@@ -4476,7 +4802,7 @@ body::after{
|
|
|
4476
4802
|
html += '</div>';
|
|
4477
4803
|
}
|
|
4478
4804
|
|
|
4479
|
-
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>';
|
|
4480
4806
|
html += '<button class="btn-redetect" onclick="window.__redetectTailscale()">Re-detect</button>';
|
|
4481
4807
|
panel.innerHTML = html;
|
|
4482
4808
|
panel.style.display = 'block';
|
|
@@ -4484,14 +4810,10 @@ body::after{
|
|
|
4484
4810
|
|
|
4485
4811
|
async function renderNotConnectedPanel() {
|
|
4486
4812
|
const panel = $('#tsPanel');
|
|
4487
|
-
const btnJoin = $('#btnJoinNetwork');
|
|
4488
|
-
btnJoin.style.display = '';
|
|
4489
|
-
btnJoin.textContent = 'Join Network';
|
|
4490
|
-
btnJoin.classList.remove('connected');
|
|
4491
4813
|
|
|
4492
4814
|
let html = '<div class="ts-panel-title not-connected">Tailscale Not Connected</div>';
|
|
4493
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>';
|
|
4494
|
-
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>';
|
|
4495
4817
|
html += '<button class="btn-redetect" onclick="window.__redetectTailscale()">Re-detect</button>';
|
|
4496
4818
|
panel.innerHTML = html;
|
|
4497
4819
|
panel.style.display = 'block';
|
|
@@ -4614,6 +4936,37 @@ dashboardRoutes.post("/api/join-network", async (c) => {
|
|
|
4614
4936
|
return c.json({ success: false, output: e.message }, 500);
|
|
4615
4937
|
}
|
|
4616
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
|
+
});
|
|
4617
4970
|
|
|
4618
4971
|
// src/server/index.ts
|
|
4619
4972
|
async function periodicCleanup() {
|
|
@@ -4623,7 +4976,15 @@ app.use("*", cors());
|
|
|
4623
4976
|
app.route("/agent", agentRoutes);
|
|
4624
4977
|
app.route("/proxy", proxyRoutes);
|
|
4625
4978
|
app.route("/dashboard", dashboardRoutes);
|
|
4979
|
+
function pidFilePath() {
|
|
4980
|
+
const pluginData = process.env.CLAUDE_PLUGIN_DATA || "";
|
|
4981
|
+
if (pluginData) return join2(pluginData, "server.pid");
|
|
4982
|
+
return join2(homedir(), ".open-party", "server.pid");
|
|
4983
|
+
}
|
|
4626
4984
|
async function main() {
|
|
4985
|
+
const pidPath = pidFilePath();
|
|
4986
|
+
mkdirSync(dirname(pidPath), { recursive: true });
|
|
4987
|
+
writeFileSync(pidPath, String(process.pid));
|
|
4627
4988
|
console.log(`Starting Party Server on port ${PARTY_PORT} (Tailscale IP: ${getSelfIp()})`);
|
|
4628
4989
|
process.on("SIGHUP", () => {
|
|
4629
4990
|
});
|
|
@@ -4632,6 +4993,10 @@ async function main() {
|
|
|
4632
4993
|
const cleanupPromise = periodicCleanup();
|
|
4633
4994
|
const shutdown = () => {
|
|
4634
4995
|
console.log("\nShutting down Party Server...");
|
|
4996
|
+
try {
|
|
4997
|
+
unlinkSync(pidPath);
|
|
4998
|
+
} catch {
|
|
4999
|
+
}
|
|
4635
5000
|
server.close();
|
|
4636
5001
|
process.exit(0);
|
|
4637
5002
|
};
|