@ai-ecoverse/slicc-spectrum 1.37.1 → 1.38.0
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/README.md +3 -3
- package/dist/slicc-dummy.js +75 -5
- package/dist/slicc-dummy.js.map +3 -3
- package/dist/slicc-ui.js +201 -2
- package/dist/slicc-ui.js.map +3 -3
- package/dist/types/app/network.d.ts +16 -1
- package/dist/types/dummy/index.d.ts +3 -2
- package/dist/types/dummy/network.d.ts +8 -2
- package/dist/types/dummy.d.ts +2 -2
- package/dist/types/model/types.d.ts +25 -0
- package/package.json +1 -1
package/dist/slicc-ui.js
CHANGED
|
@@ -10623,6 +10623,27 @@ var automation = {
|
|
|
10623
10623
|
function automationOf(browser) {
|
|
10624
10624
|
return browser.declined ? "declined" : browser.via ?? "none";
|
|
10625
10625
|
}
|
|
10626
|
+
var tailnetStates = {
|
|
10627
|
+
off: ["neutral", "Off"],
|
|
10628
|
+
loading: ["info", "Loading"],
|
|
10629
|
+
"needs-login": ["notice", "Needs sign-in"],
|
|
10630
|
+
starting: ["info", "Connecting"],
|
|
10631
|
+
running: ["positive", "Connected"],
|
|
10632
|
+
failed: ["negative", "Failed"]
|
|
10633
|
+
};
|
|
10634
|
+
var tailnetOnly = {
|
|
10635
|
+
proxy: "Tailnet hosts only. Everything else goes through slicc-node.",
|
|
10636
|
+
extension: "Tailnet hosts only. Everything else goes through the SLICC extension.",
|
|
10637
|
+
page: "Tailnet hosts only. Everything else goes through this page\u2019s fetch.",
|
|
10638
|
+
none: "Tailnet hosts only. Nothing else is reachable."
|
|
10639
|
+
};
|
|
10640
|
+
function tailnetAddress(addresses) {
|
|
10641
|
+
return addresses.find((address) => address.startsWith("100.")) ?? addresses[0];
|
|
10642
|
+
}
|
|
10643
|
+
function exitNodeValue(tailnet) {
|
|
10644
|
+
if (tailnet.autoExitNode) return "auto";
|
|
10645
|
+
return tailnet.exitNodes?.find((node) => node.name === tailnet.exitNode)?.id ?? "none";
|
|
10646
|
+
}
|
|
10626
10647
|
function networkLabel(status2) {
|
|
10627
10648
|
return `Network: ${networkHealth[status2.health][1].toLowerCase()}`;
|
|
10628
10649
|
}
|
|
@@ -10652,7 +10673,10 @@ var SliccNetwork = class extends ModelElement {
|
|
|
10652
10673
|
checking: { state: true },
|
|
10653
10674
|
copied: { state: true },
|
|
10654
10675
|
uncopied: { state: true },
|
|
10655
|
-
error: { state: true }
|
|
10676
|
+
error: { state: true },
|
|
10677
|
+
busy: { state: true },
|
|
10678
|
+
tailnetError: { state: true },
|
|
10679
|
+
tailnetCopied: { state: true }
|
|
10656
10680
|
};
|
|
10657
10681
|
constructor() {
|
|
10658
10682
|
super();
|
|
@@ -10660,6 +10684,9 @@ var SliccNetwork = class extends ModelElement {
|
|
|
10660
10684
|
this.copied = false;
|
|
10661
10685
|
this.uncopied = false;
|
|
10662
10686
|
this.error = "";
|
|
10687
|
+
this.busy = "";
|
|
10688
|
+
this.tailnetError = "";
|
|
10689
|
+
this.tailnetCopied = "";
|
|
10663
10690
|
}
|
|
10664
10691
|
static styles = [
|
|
10665
10692
|
r(link_default),
|
|
@@ -10733,6 +10760,51 @@ var SliccNetwork = class extends ModelElement {
|
|
|
10733
10760
|
flex: none;
|
|
10734
10761
|
margin-block-start: var(--swc-spacing-50);
|
|
10735
10762
|
}
|
|
10763
|
+
.warning {
|
|
10764
|
+
display: flex;
|
|
10765
|
+
align-items: flex-start;
|
|
10766
|
+
gap: var(--swc-spacing-100);
|
|
10767
|
+
padding: var(--swc-spacing-200);
|
|
10768
|
+
border-radius: var(--swc-corner-radius-medium-default);
|
|
10769
|
+
background: var(--swc-notice-subtle-background-color-default);
|
|
10770
|
+
--swc-icon-color: var(--swc-notice-visual-color);
|
|
10771
|
+
}
|
|
10772
|
+
.warning swc-icon-alert-triangle {
|
|
10773
|
+
flex: none;
|
|
10774
|
+
margin-block-start: var(--swc-spacing-50);
|
|
10775
|
+
}
|
|
10776
|
+
.node {
|
|
10777
|
+
display: grid;
|
|
10778
|
+
grid-template-columns: max-content minmax(0, 1fr);
|
|
10779
|
+
align-items: center;
|
|
10780
|
+
gap: var(--swc-spacing-100) var(--swc-spacing-200);
|
|
10781
|
+
margin: 0;
|
|
10782
|
+
}
|
|
10783
|
+
.node dt {
|
|
10784
|
+
color: var(--swc-neutral-subdued-content-color-default);
|
|
10785
|
+
}
|
|
10786
|
+
.node dd {
|
|
10787
|
+
margin: 0;
|
|
10788
|
+
}
|
|
10789
|
+
.key {
|
|
10790
|
+
display: flex;
|
|
10791
|
+
flex-wrap: wrap;
|
|
10792
|
+
align-items: center;
|
|
10793
|
+
gap: var(--swc-spacing-100);
|
|
10794
|
+
}
|
|
10795
|
+
.key sp-textfield {
|
|
10796
|
+
flex: 1 1 200px;
|
|
10797
|
+
}
|
|
10798
|
+
.exit {
|
|
10799
|
+
display: flex;
|
|
10800
|
+
flex-wrap: wrap;
|
|
10801
|
+
align-items: center;
|
|
10802
|
+
gap: var(--swc-spacing-100) var(--swc-spacing-200);
|
|
10803
|
+
}
|
|
10804
|
+
.exit sp-picker {
|
|
10805
|
+
width: 240px;
|
|
10806
|
+
max-width: 100%;
|
|
10807
|
+
}
|
|
10736
10808
|
.command {
|
|
10737
10809
|
display: flex;
|
|
10738
10810
|
align-items: center;
|
|
@@ -10815,6 +10887,7 @@ var SliccNetwork = class extends ModelElement {
|
|
|
10815
10887
|
];
|
|
10816
10888
|
subscribe(model) {
|
|
10817
10889
|
this.error = "";
|
|
10890
|
+
this.tailnetError = "";
|
|
10818
10891
|
return model.network ? [model.network.on("network", () => this.requestUpdate())] : [];
|
|
10819
10892
|
}
|
|
10820
10893
|
focus() {
|
|
@@ -10843,6 +10916,131 @@ var SliccNetwork = class extends ModelElement {
|
|
|
10843
10916
|
this.uncopied = true;
|
|
10844
10917
|
}
|
|
10845
10918
|
}
|
|
10919
|
+
async #tailnet(action, run2) {
|
|
10920
|
+
if (this.busy) return;
|
|
10921
|
+
this.busy = action;
|
|
10922
|
+
this.tailnetError = "";
|
|
10923
|
+
try {
|
|
10924
|
+
await run2();
|
|
10925
|
+
} catch (error) {
|
|
10926
|
+
this.tailnetError = failure(error);
|
|
10927
|
+
} finally {
|
|
10928
|
+
this.busy = "";
|
|
10929
|
+
}
|
|
10930
|
+
}
|
|
10931
|
+
async #copyTailnet(what, value) {
|
|
10932
|
+
try {
|
|
10933
|
+
await globalThis.navigator.clipboard.writeText(value);
|
|
10934
|
+
this.tailnetCopied = what;
|
|
10935
|
+
this.tailnetError = "";
|
|
10936
|
+
} catch {
|
|
10937
|
+
this.tailnetCopied = "";
|
|
10938
|
+
this.tailnetError = "Couldn\u2019t copy. Select the text and copy it.";
|
|
10939
|
+
}
|
|
10940
|
+
}
|
|
10941
|
+
#waiting(action) {
|
|
10942
|
+
return this.busy !== "" && this.busy !== action;
|
|
10943
|
+
}
|
|
10944
|
+
#submitKey(event2, port) {
|
|
10945
|
+
event2.preventDefault();
|
|
10946
|
+
if (this.busy) return;
|
|
10947
|
+
const field = event2.currentTarget.querySelector(
|
|
10948
|
+
"sp-textfield"
|
|
10949
|
+
);
|
|
10950
|
+
const key2 = field.value.trim();
|
|
10951
|
+
field.value = "";
|
|
10952
|
+
if (key2) void this.#tailnet("key", () => port.submitAuthKey?.(key2));
|
|
10953
|
+
}
|
|
10954
|
+
#sendKey(event2) {
|
|
10955
|
+
event2.currentTarget.closest("form")?.requestSubmit();
|
|
10956
|
+
}
|
|
10957
|
+
#enterKey(event2) {
|
|
10958
|
+
if (event2.key === "Enter") this.#sendKey(event2);
|
|
10959
|
+
}
|
|
10960
|
+
async #signOut(port) {
|
|
10961
|
+
const ok = await confirm({
|
|
10962
|
+
title: "Sign out of Tailscale?",
|
|
10963
|
+
body: "This browser leaves your tailnet until you sign in again.",
|
|
10964
|
+
action: "Sign out",
|
|
10965
|
+
variant: "confirmation"
|
|
10966
|
+
});
|
|
10967
|
+
if (ok) await this.#tailnet("sign-out", () => port.logoutTailnet?.());
|
|
10968
|
+
}
|
|
10969
|
+
#copyRow(what, label, value) {
|
|
10970
|
+
const copied = this.tailnetCopied === what;
|
|
10971
|
+
return b`<dt>${label}</dt>
|
|
10972
|
+
<dd class="command" data-copy=${what}>
|
|
10973
|
+
<code>${value}</code>
|
|
10974
|
+
<swc-action-button size="s" quiet data-action=${`copy-${what}`} accessible-label=${copied ? "Copied" : `Copy ${label.toLowerCase()}`} @click=${() => this.#copyTailnet(what, value)}>
|
|
10975
|
+
<swc-icon-copy slot="icon"></swc-icon-copy>${copied ? "Copied" : "Copy"}
|
|
10976
|
+
</swc-action-button>
|
|
10977
|
+
</dd>`;
|
|
10978
|
+
}
|
|
10979
|
+
#signIn(port, tailnet) {
|
|
10980
|
+
return b`<p>Sign in to add this browser to your tailnet.</p>
|
|
10981
|
+
${tailnet.loginUrl ? b`<p><a class="swc-Link" href=${tailnet.loginUrl} target="_blank" rel="noopener noreferrer" data-action="tailnet-sign-in">Sign in to Tailscale</a></p>` : A}
|
|
10982
|
+
${port.submitAuthKey ? b`<p class="muted" id="tailnet-key">Or paste an auth key:</p>
|
|
10983
|
+
<form class="key" data-form="auth-key" @submit=${(event2) => this.#submitKey(event2, port)}>
|
|
10984
|
+
<sp-textfield size="m" type="password" autocomplete="off" label="Tailscale auth key" aria-labelledby="tailnet-key" placeholder="tskey-…" @keydown=${(event2) => this.#enterKey(event2)}></sp-textfield>
|
|
10985
|
+
<swc-button size="m" variant="secondary" data-action="submit-auth-key" ?pending=${this.busy === "key"} ?disabled=${this.#waiting("key")} @click=${(event2) => this.#sendKey(event2)}>Connect</swc-button>
|
|
10986
|
+
</form>` : A}`;
|
|
10987
|
+
}
|
|
10988
|
+
#running(port, status2, tailnet) {
|
|
10989
|
+
const address = tailnet.node ? tailnetAddress(tailnet.node.addresses) : void 0;
|
|
10990
|
+
const peers = tailnet.peers;
|
|
10991
|
+
return b`${tailnet.node ? b`<p>This browser is on your tailnet${peers === void 0 ? "" : ` with ${peers} other ${peers === 1 ? "device" : "devices"}`}.</p>
|
|
10992
|
+
<dl class="node">
|
|
10993
|
+
${this.#copyRow("name", "Name", tailnet.node.name)}
|
|
10994
|
+
${address ? this.#copyRow("address", "Address", address) : A}
|
|
10995
|
+
</dl>` : A}
|
|
10996
|
+
${port.setExitNode && tailnet.exitNodes ? b`<div class="exit">
|
|
10997
|
+
<span id="exit-node">Exit node</span>
|
|
10998
|
+
<sp-picker size="m" label="Exit node" aria-labelledby="exit-node" data-action="exit-node" .value=${l(exitNodeValue(tailnet))} ?pending=${this.busy === "exit"} ?disabled=${this.#waiting("exit")} @change=${(event2) => this.#exit(event2, port)}>
|
|
10999
|
+
<sp-menu-item value="none">None</sp-menu-item>
|
|
11000
|
+
<sp-menu-item value="auto">Automatic</sp-menu-item>
|
|
11001
|
+
${tailnet.exitNodes.map((node) => b`<sp-menu-item value=${node.id} ?disabled=${!node.online}>${node.name}${node.online ? A : b`<span slot="description">Offline</span>`}</sp-menu-item>`)}
|
|
11002
|
+
</sp-picker>
|
|
11003
|
+
</div>` : A}
|
|
11004
|
+
<p data-exit=${tailnet.exitNode ? "node" : "none"}>${tailnet.exitNode ? `Internet through the exit node ${tailnet.exitNode}.` : tailnetOnly[status2.route ?? "none"]}</p>
|
|
11005
|
+
${tailnet.shieldsUp === false ? b`<div class="warning" data-shields="down"><swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle><span>Shields down: peers on your tailnet can connect to this browser.</span></div>` : tailnet.shieldsUp ? b`<p class="muted" data-shields="up">Shields up: peers can’t connect to this browser.</p>` : A}
|
|
11006
|
+
${port.logoutTailnet ? b`<div class="actions"><swc-button size="m" variant="secondary" data-action="tailnet-sign-out" ?pending=${this.busy === "sign-out"} ?disabled=${this.#waiting("sign-out")} @click=${() => this.#signOut(port)}>Sign out</swc-button></div>` : A}`;
|
|
11007
|
+
}
|
|
11008
|
+
#exit(event2, port) {
|
|
11009
|
+
const value = event2.target.value;
|
|
11010
|
+
void this.#tailnet("exit", () => port.setExitNode?.(value === "none" ? null : value));
|
|
11011
|
+
}
|
|
11012
|
+
#failed(port, tailnet) {
|
|
11013
|
+
return b`<div class="error" data-error="tailnet"><swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle><span>${tailnet.detail ?? "Tailscale stopped."}</span></div>
|
|
11014
|
+
${port.check ? b`<div class="actions"><swc-button size="m" variant="secondary" data-action="tailnet-retry" ?pending=${this.busy === "retry"} ?disabled=${this.#waiting("retry")} @click=${() => this.#tailnet("retry", () => port.check?.())}>Retry</swc-button></div>` : A}`;
|
|
11015
|
+
}
|
|
11016
|
+
#tailnetBody(port, status2, tailnet) {
|
|
11017
|
+
switch (tailnet.state) {
|
|
11018
|
+
case "off":
|
|
11019
|
+
return b`<p class="muted">Reach the devices on your tailnet, and send the rest through an exit node.</p>`;
|
|
11020
|
+
case "needs-login":
|
|
11021
|
+
return this.#signIn(port, tailnet);
|
|
11022
|
+
case "running":
|
|
11023
|
+
return this.#running(port, status2, tailnet);
|
|
11024
|
+
case "failed":
|
|
11025
|
+
return this.#failed(port, tailnet);
|
|
11026
|
+
default:
|
|
11027
|
+
return b`<p class="muted">${tailnet.detail ?? (tailnet.state === "loading" ? "Loading Tailscale\u2026" : "Joining your tailnet\u2026")}</p>`;
|
|
11028
|
+
}
|
|
11029
|
+
}
|
|
11030
|
+
#tailnetSection(port, status2) {
|
|
11031
|
+
const tailnet = status2.tailnet;
|
|
11032
|
+
if (!tailnet) return A;
|
|
11033
|
+
const [variant, label] = tailnetStates[tailnet.state];
|
|
11034
|
+
return b`<section aria-labelledby="tailnet" data-tailnet=${tailnet.state}>
|
|
11035
|
+
<div class="head">
|
|
11036
|
+
<h2 id="tailnet">Tailscale</h2>
|
|
11037
|
+
<swc-status-light variant=${variant}>${label}</swc-status-light>
|
|
11038
|
+
</div>
|
|
11039
|
+
${port.setTailnet ? b`<sp-switch data-action="tailnet" .checked=${l(tailnet.state !== "off")} ?disabled=${!!this.busy} @change=${(event2) => this.#tailnet("switch", () => port.setTailnet?.(event2.target.checked))}>Use Tailscale</sp-switch>` : A}
|
|
11040
|
+
${this.#tailnetBody(port, status2, tailnet)}
|
|
11041
|
+
${this.tailnetError ? b`<div class="error" role="alert" data-error="tailnet-action"><swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle><span>${this.tailnetError}</span></div>` : A}
|
|
11042
|
+
</section>`;
|
|
11043
|
+
}
|
|
10846
11044
|
#state(status2) {
|
|
10847
11045
|
const [variant, label] = networkHealth[status2.health];
|
|
10848
11046
|
return b`<section aria-labelledby="state">
|
|
@@ -10900,6 +11098,7 @@ var SliccNetwork = class extends ModelElement {
|
|
|
10900
11098
|
const status2 = port.status();
|
|
10901
11099
|
return b`<div class="page" tabindex="-1">
|
|
10902
11100
|
${this.#state(status2)}
|
|
11101
|
+
${this.#tailnetSection(port, status2)}
|
|
10903
11102
|
${this.#whole(status2)}
|
|
10904
11103
|
${this.#failures(status2)}
|
|
10905
11104
|
</div>`;
|
|
@@ -15438,7 +15637,7 @@ var SliccApp = class extends ThemedElement {
|
|
|
15438
15637
|
const label = networkLabel(status2);
|
|
15439
15638
|
const content = this.screen === "phone" ? networkIcon[status2.health] : b`<swc-status-light size="s" variant=${variant}>Network${status2.health === "ok" ? A : b`<span class="state"> ${state2.toLowerCase()}</span>`}</swc-status-light>`;
|
|
15440
15639
|
return b`<swc-action-button id="network" quiet size="s" data-network data-health=${status2.health} accessible-label=${label} @click=${() => this.show("network")}>${content}</swc-action-button>
|
|
15441
|
-
<swc-tooltip for="network" placement="bottom">${status2.detail ?? label}</swc-tooltip>`;
|
|
15640
|
+
<swc-tooltip for="network" placement="bottom">${status2.detail ?? label}${status2.tailnet?.state === "running" ? " Tailscale is connected." : ""}</swc-tooltip>`;
|
|
15442
15641
|
}
|
|
15443
15642
|
#headerButton(id, label, icon, click) {
|
|
15444
15643
|
return b`<swc-action-button id=${id} size="s" quiet accessible-label=${label} @click=${click}>${icon}</swc-action-button>
|