Skip to content

Commit e247311

Browse files
tendstofortytwotylersmalley
authored andcommitted
Node Explorer: poll for peer/tailnet updates (#238)
Fixes #122. --------- Signed-off-by: Naman Sood <[email protected]>
1 parent 88c86f0 commit e247311

File tree

2 files changed

+82
-2
lines changed

2 files changed

+82
-2
lines changed

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,12 @@
428428
"examples": [
429429
false
430430
]
431+
},
432+
"tailscale.nodeExplorer.refreshInterval": {
433+
"type": "number",
434+
"default": 5000,
435+
"markdownDescription": "Time in milliseconds between automatic refreshes of the node explorer. A value of 0 disables automatic refreshing.",
436+
"scope": "window"
431437
}
432438
}
433439
}

src/node-explorer-provider.ts

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
22
import * as vscode from 'vscode';
33
import * as path from 'path';
4-
import { Peer, PeerGroup } from './types';
4+
import { Peer, PeerGroup, PeersResponse } from './types';
55
import { Utils } from 'vscode-uri';
66
import { Tailscale } from './tailscale/cli';
77
import { ConfigManager } from './config-manager';
@@ -34,6 +34,8 @@ export class NodeExplorerProvider
3434
// We want to use an array as the event type, but the API for this is currently being finalized. Until it's finalized, use any.
3535
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3636
public onDidChangeTreeData: vscode.Event<any> = this._onDidChangeTreeData.event;
37+
private previousStatus: PeersResponse | undefined = undefined;
38+
private currentStatus: PeersResponse | undefined = undefined;
3739

3840
constructor(
3941
private readonly ts: Tailscale,
@@ -58,12 +60,84 @@ export class NodeExplorerProvider
5860
this.registerRefresh();
5961
this.registerOpenDocsLink();
6062
this.registerDownloadCommand();
63+
this.pollForUpdates();
6164
}
6265

6366
getTreeItem(element: PeerBaseTreeItem): vscode.TreeItem {
6467
return element;
6568
}
6669

70+
private async getPeers() {
71+
this.previousStatus = this.currentStatus;
72+
this.currentStatus = await this.ts.getPeers();
73+
return this.currentStatus;
74+
}
75+
76+
private async diffRelay() {
77+
const status = await this.getPeers();
78+
const prevStatus = this.previousStatus;
79+
80+
if (!prevStatus) {
81+
return false;
82+
}
83+
84+
if (status.Errors) {
85+
if (!prevStatus.Errors || !status.Errors.every((e, i) => e === prevStatus.Errors?.[i])) {
86+
return true;
87+
}
88+
}
89+
90+
if (status.CurrentTailnet.Name !== prevStatus.CurrentTailnet.Name) {
91+
return true;
92+
}
93+
94+
if (status.CurrentTailnet.MagicDNSEnabled !== prevStatus.CurrentTailnet.MagicDNSEnabled) {
95+
return true;
96+
}
97+
98+
if (status.CurrentTailnet.MagicDNSSuffix !== prevStatus.CurrentTailnet.MagicDNSSuffix) {
99+
return true;
100+
}
101+
102+
for (let i = 0; i < status.PeerGroups.length; ++i) {
103+
if (status.PeerGroups[i].Name !== prevStatus.PeerGroups[i].Name) {
104+
return true;
105+
}
106+
if (
107+
status.PeerGroups[i].Peers.length !== prevStatus.PeerGroups[i].Peers.length ||
108+
!status.PeerGroups[i].Peers.every((p, j) => p.ID === prevStatus.PeerGroups[i].Peers[j].ID)
109+
) {
110+
return true;
111+
}
112+
}
113+
114+
return false;
115+
}
116+
117+
async pollForUpdates() {
118+
const interval = vscode.workspace
119+
.getConfiguration(EXTENSION_NS)
120+
.get<number>('nodeExplorer.refreshInterval');
121+
if (interval) {
122+
try {
123+
if (await this.diffRelay()) {
124+
this.refresh();
125+
}
126+
} catch (e) {
127+
// diffRelay might fail if the request to getPeers fails, eg.
128+
// if someone called `tailscale switch` in the the middle of
129+
// the request. If that happens, the setTimeout will cause us
130+
// to try again, so we just log this for now.
131+
Logger.error(`could not poll for updates: ${e}`);
132+
}
133+
// We set a timeout recursively instead of setting an interval
134+
// to avoid the case where the await expressions above take longer
135+
// than the provided interval, leading to new polls being sent out
136+
// before old ones are finished.
137+
setTimeout(() => this.pollForUpdates(), interval);
138+
}
139+
}
140+
67141
async getChildren(element?: PeerBaseTreeItem): Promise<PeerBaseTreeItem[]> {
68142
if (element instanceof PeerErrorItem) {
69143
return [];
@@ -144,7 +218,7 @@ export class NodeExplorerProvider
144218
const groups: PeerGroupItem[] = [];
145219
let hasErr = false;
146220
try {
147-
const status = await this.ts.getPeers();
221+
const status = await this.getPeers();
148222
if (status.Errors && status.Errors.length) {
149223
for (let index = 0; index < status.Errors.length; index++) {
150224
const err = status.Errors[index];

0 commit comments

Comments
 (0)