Skip to content

Commit

Permalink
feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
daniellacosse committed Nov 12, 2024
1 parent ab29f2c commit 2a6076d
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 85 deletions.
5 changes: 0 additions & 5 deletions server_manager/model/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ export interface Server {
// Lists the access keys for this server, including the admin.
listAccessKeys(): Promise<AccessKey[]>;

// Returns stats for bytes transferred across all access keys of this server.
getDataUsage(): Promise<BytesByAccessKey>;

// Returns server metrics
getServerMetrics(): Promise<ServerMetricsJson>;

Expand Down Expand Up @@ -189,8 +186,6 @@ export interface AccessKey {
dataLimit?: DataLimit;
}

export type BytesByAccessKey = Map<AccessKeyId, number>;

// Data transfer allowance, measured in bytes.
// NOTE: Must be kept in sync with the definition in src/shadowbox/access_key.ts.
export interface DataLimit {
Expand Down
99 changes: 45 additions & 54 deletions server_manager/www/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -970,8 +970,7 @@ export class App {
console.error(`Failed to load access keys: ${error}`);
this.appRoot.showError(this.appRoot.localize('error-keys-get'));
}
this.showTransferStats(server, view);
this.showTunnelTimeStats(server, view);
this.showServerMetrics(server, view);
}, 0);
}

Expand Down Expand Up @@ -1036,30 +1035,56 @@ export class App {
}
}

private async refreshTransferStats(
private async refreshServerMetrics(
selectedServer: server_model.Server,
serverView: ServerView
) {
try {
const usageMap = await selectedServer.getDataUsage();
const keyTransfers = [...usageMap.values()];
const serverMetrics = await selectedServer.getServerMetrics();

let totalUserHours = 0;
for (const {
tunnelTime: {seconds},
} of serverMetrics.servers) {
// convert to hours
totalUserHours += seconds / (60 * 60);
}

serverView.totalUserHours = totalUserHours;
serverView.totalDevices = serverView.totalUserHours / (30 * 24);

let totalInboundBytes = 0;
for (const accessKeyBytes of keyTransfers) {
totalInboundBytes += accessKeyBytes;
for (const {
dataTransferred: {bytes},
} of serverMetrics.accessKeys) {
totalInboundBytes += bytes;
}

serverView.totalInboundBytes = totalInboundBytes;

// Update all the displayed access keys, even if usage didn't change, in case data limits did.
const keyDataTransferMap = serverMetrics.accessKeys.reduce(
(map, {accessKeyId, dataTransferred}) => {
map.set(String(accessKeyId), dataTransferred.bytes);
return map;
},
new Map<string, number>()
);

let keyTransferMax = 0;
let dataLimitMax = selectedServer.getDefaultDataLimit()?.bytes ?? 0;
for (const key of await selectedServer.listAccessKeys()) {
serverView.updateAccessKeyRow(key.id, {
transferredBytes: usageMap.get(key.id) ?? 0,
dataLimitBytes: key.dataLimit?.bytes,
for (const accessKey of await selectedServer.listAccessKeys()) {
serverView.updateAccessKeyRow(accessKey.id, {
transferredBytes: keyDataTransferMap.get(accessKey.id) ?? 0,
dataLimitBytes: accessKey.dataLimit?.bytes,
});
keyTransferMax = Math.max(keyTransferMax, usageMap.get(key.id) ?? 0);
dataLimitMax = Math.max(dataLimitMax, key.dataLimit?.bytes ?? 0);
keyTransferMax = Math.max(
keyTransferMax,
keyDataTransferMap.get(accessKey.id) ?? 0
);
dataLimitMax = Math.max(dataLimitMax, accessKey.dataLimit?.bytes ?? 0);
}

serverView.baselineDataTransfer = Math.max(keyTransferMax, dataLimitMax);
} catch (e) {
// Since failures are invisible to users we generally want exceptions here to bubble
Expand All @@ -1075,45 +1100,11 @@ export class App {
}
}

private async refreshTunnelTimeStats(
selectedServer: server_model.Server,
serverView: ServerView
) {
const serverMetrics = await selectedServer.getServerMetrics();

let sum = 0;
for (const {
tunnelTime: {seconds},
} of serverMetrics.servers) {
sum += seconds / (60 * 60);
}

serverView.totalUserHours = sum;
serverView.totalDevices = serverView.totalUserHours / (30 * 24);
}

private showTransferStats(
selectedServer: server_model.Server,
serverView: ServerView
) {
this.refreshTransferStats(selectedServer, serverView);
// Get transfer stats once per minute for as long as server is selected.
const statsRefreshRateMs = 60 * 1000;
const intervalId = setInterval(() => {
if (this.selectedServer !== selectedServer) {
// Server is no longer running, stop interval
clearInterval(intervalId);
return;
}
this.refreshTransferStats(selectedServer, serverView);
}, statsRefreshRateMs);
}

private showTunnelTimeStats(
private showServerMetrics(
selectedServer: server_model.Server,
serverView: ServerView
) {
this.refreshTunnelTimeStats(selectedServer, serverView);
this.refreshServerMetrics(selectedServer, serverView);
// Get transfer stats once per minute for as long as server is selected.
const statsRefreshRateMs = 60 * 1000;
const intervalId = setInterval(() => {
Expand All @@ -1122,7 +1113,7 @@ export class App {
clearInterval(intervalId);
return;
}
this.refreshTunnelTimeStats(selectedServer, serverView);
this.refreshServerMetrics(selectedServer, serverView);
}, statsRefreshRateMs);
}

Expand Down Expand Up @@ -1193,7 +1184,7 @@ export class App {
this.appRoot.showNotification(this.appRoot.localize('saved'));
serverView.defaultDataLimitBytes = limit?.bytes;
serverView.isDefaultDataLimitEnabled = true;
this.refreshTransferStats(this.selectedServer, serverView);
this.refreshServerMetrics(this.selectedServer, serverView);
// Don't display the feature collection disclaimer anymore.
serverView.showFeatureMetricsDisclaimer = false;
window.localStorage.setItem(
Expand All @@ -1219,7 +1210,7 @@ export class App {
await this.selectedServer.removeDefaultDataLimit();
serverView.isDefaultDataLimitEnabled = false;
this.appRoot.showNotification(this.appRoot.localize('saved'));
this.refreshTransferStats(this.selectedServer, serverView);
this.refreshServerMetrics(this.selectedServer, serverView);
} catch (error) {
console.error(`Failed to remove server default data limit: ${error}`);
this.appRoot.showError(this.appRoot.localize('error-remove-data-limit'));
Expand Down Expand Up @@ -1267,7 +1258,7 @@ export class App {
const serverView = await this.appRoot.getServerView(server.getId());
try {
await server.setAccessKeyDataLimit(keyId, {bytes: dataLimitBytes});
this.refreshTransferStats(server, serverView);
this.refreshServerMetrics(server, serverView);
this.appRoot.showNotification(this.appRoot.localize('saved'));
return true;
} catch (error) {
Expand All @@ -1288,7 +1279,7 @@ export class App {
const serverView = await this.appRoot.getServerView(server.getId());
try {
await server.removeAccessKeyDataLimit(keyId);
this.refreshTransferStats(server, serverView);
this.refreshServerMetrics(server, serverView);
this.appRoot.showNotification(this.appRoot.localize('saved'));
return true;
} catch (error) {
Expand Down
27 changes: 1 addition & 26 deletions server_manager/www/shadowbox_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,6 @@ interface ServerConfigJson {
accessKeyDataLimit?: server.DataLimit;
}

// Byte transfer stats for the past 30 days, including both inbound and outbound.
// TODO: this is copied at src/shadowbox/model/metrics.ts. Both copies should
// be kept in sync, until we can find a way to share code between the web_app
// and shadowbox.
interface DataUsageByAccessKeyJson {
// The accessKeyId should be of type AccessKeyId, however that results in the tsc
// error TS1023: An index signature parameter type must be 'string' or 'number'.
// See https://github.com/Microsoft/TypeScript/issues/2491
// TODO: this still says "UserId", changing to "AccessKeyId" will require
// a change on the shadowbox server.
bytesTransferredByUserId: {[accessKeyId: string]: number};
}

// Converts the access key JSON from the API to its model.
function makeAccessKeyModel(apiAccessKey: AccessKeyJson): server.AccessKey {
return apiAccessKey as server.AccessKey;
Expand Down Expand Up @@ -149,20 +136,8 @@ export class ShadowboxServer implements server.Server {
await this.api.request<void>(`access-keys/${keyId}/data-limit`, 'DELETE');
}

async getDataUsage(): Promise<server.BytesByAccessKey> {
const jsonResponse =
await this.api.request<DataUsageByAccessKeyJson>('metrics/transfer');
const usageMap = new Map<server.AccessKeyId, number>();
for (const [accessKeyId, bytes] of Object.entries(
jsonResponse.bytesTransferredByUserId
)) {
usageMap.set(accessKeyId, bytes ?? 0);
}
return usageMap;
}

async getServerMetrics(): Promise<server.ServerMetricsJson> {
//TODO
//TODO: this.api.request<server.ServerMetricsJson>('server/metrics')
return {
servers: [
{
Expand Down

0 comments on commit 2a6076d

Please sign in to comment.