Skip to content

Commit

Permalink
Per-Key Data Limits in the Manager (#841)
Browse files Browse the repository at this point in the history
* Add per-key data limits to the UI

* Hold byte values instead of DisplayDataAmounts in OutlineServerView and OutlinePerKeyDatALimitDialog

This allows us to fix a layering issue where, at the time of converting
a server access key to a DisplayAccessKey, we couldn't compute the
'relevant transfer' for progress bar displays since we couldn't get at
the server's default data limit.

* Mayp#values() returns an Iterator, not an array

* Rename event field to defaultDataLimitBytes

* Don't reset the dialog state on failure

* Use a loop instead of Math.max in refreshTransferStats

* Unconditionally enable the default data limit in the UI after adding it to the server

Co-authored-by: Vinicius Fortuna <[email protected]>

* Consistent handling of the view's default data limit state for both addition and removal

* Use callbacks to set and remove the per key data limit

* s/_showMenu/_showDataLimit/g

* Make language a public property of the dialog

* The dialog only needs the active data limit

* Use a separate method for the handler to open the dialog

* Use _activeDataLimitBytes

* We can use the ?? operator in `refreshTransferStats`

* s/dataLimitType/dataLimitUnit/g

* Rename a couple members

Co-authored-by: Vinicius Fortuna <[email protected]>
  • Loading branch information
Jonathan Cohen and fortuna authored Mar 15, 2021
1 parent 11a6b34 commit 2f2e658
Show file tree
Hide file tree
Showing 16 changed files with 986 additions and 221 deletions.
10 changes: 8 additions & 2 deletions src/server_manager/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"confirmation-server-destroy-title": "Destroy Server?",
"confirmation-server-remove": "This action removes your server from the Outline Manager, but does not block proxy access to users. You will still need to manually delete the Outline server from your host machine.",
"confirmation-server-remove-title": "Remove Server?",
"data-limit": "Data Limit",
"data-limit-per-key": "Data limit per key",
"data-limits": "Data limits",
"data-limits-description": "Set a 30 day trailing data transfer limit for access keys on this server.",
Expand Down Expand Up @@ -52,15 +53,17 @@
"error-metrics": "Error setting metrics enabled",
"error-network": "A network error occurred.",
"error-not-saved": "Not Saved",
"error-remove-data-limit": "Could not disable access key data limit",
"error-remove-data-limit": "Could not disable default data limit",
"error-remove-per-key-limit": "Could not remove data limit from this access key",
"error-server-creation": "There was an error creating your Outline server.",
"error-server-destroy": "Failed to destroy server",
"error-server-removed": "{serverName} no longer present in your DigitalOcean account.",
"error-server-rename": "Failed to rename server",
"error-server-unreachable": "Your Outline Server was installed correctly, but we are not able to connect to it. Most likely this is because your server's firewall rules are blocking incoming connections. Please review them and make sure to allow incoming TCP connections on ports ranging from 1024 to 65535.",
"error-server-unreachable-title": "Unable to connect to your Outline Server",
"error-servers-removed": "{serverNames} no longer present in your DigitalOcean account.",
"error-set-data-limit": "Could not set access key data limit",
"error-set-data-limit": "Could not set default data limit",
"error-set-per-key-limit": "Could not set data limit for this access key",
"error-unexpected": "An unexpected error occurred.",
"experiments": "Experiments",
"experiments-description": "Test new features and provide us with feedback before they are released.",
Expand Down Expand Up @@ -125,6 +128,7 @@
"nav-licenses": "Licenses",
"nav-privacy": "Privacy",
"nav-terms": "Terms",
"no-data-limit": "None",
"notification-app-update": "An updated version of the Outline Manager has been downloaded. It will be installed when you restart the application.",
"notification-feedback-thanks": "Thanks for helping us improve! We love hearing from you.",
"notification-key-added": "Key added",
Expand All @@ -144,6 +148,8 @@
"oauth-verify": "Check your inbox for an email from DigitalOcean, and click the link in it to confirm your account.",
"oauth-verify-tag": "Confirm your email...",
"okay": "OK",
"per-key-data-limit-dialog-set-custom": "Set a custom data limit",
"per-key-data-limit-dialog-title": "Data Limit - {keyName}",
"region-description": "This is where your internet experience will come from.",
"region-setup": "Set up Outline",
"region-title": "Select the location of your server.",
Expand Down
38 changes: 34 additions & 4 deletions src/server_manager/messages/master_messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@
"message": "Remove Server?",
"description": "This string appears in a dialog that requests user confirmation for removing a server from the application. 'Remove' in this context does not imply server deletion."
},
"data_limit": {
"message": "Data Limit",
"Description": "This string appears in various places related to the data transfer limit for a single access key."
},
"data_limit_per_key": {
"message": "Data limit per key",
"description": "This string appears as a label to an input to set the default access key data limit for a server."
Expand Down Expand Up @@ -308,12 +312,20 @@
"description": "This string signifies that an error we didn't expect was encountered."
},
"error_set_data_limit": {
"message": "Could not set access key data limit",
"description": "This string appears in an error notification toast. It is shown on failure to set a data transfer limit on access keys."
"message": "Could not set default data limit",
"description": "This string appears in an error notification toast. It is shown on failure to set the default data transfer limit on access keys."
},
"error_remove_data_limit": {
"message": "Could not disable access key data limit",
"description": "This string appears in an error notification toast. It is shown on failure to remove a data transfer limit on access keys."
"message": "Could not disable default data limit",
"description": "This string appears in an error notification toast. It is shown on failure to remove the default data transfer limit on access keys."
},
"error_set_per_key_limit": {
"message": "Could not set data limit for this access key",
"description": "This string appears in an error notification toast. It is shown on failure to set the data transfer limit on a particular access key."
},
"error_remove_per_key_limit": {
"message": "Could not remove data limit from this access key",
"description": "This string appears in an error notification toast. It is shown on failure to remove the data transfer limit on a particular access key."
},
"experiments": {
"message": "Experiments",
Expand Down Expand Up @@ -607,6 +619,10 @@
"message": "About",
"description": "This string appears in an application drawer as a navigation link. Clicking it opens a dialog with information about Outline."
},
"no_data_limit": {
"message": "None",
"description": "This string appears alongside each access key in the data transfer stats section if it is under no data limit. Example: 450 MB / None"
},
"nav_data_collection": {
"message": "Data collection",
"description": "This string appears in an application drawer as a navigation link. Clicking it opens Outline's data collection policy in the browser."
Expand Down Expand Up @@ -707,6 +723,20 @@
"message": "OK",
"description": "This string appears across the application as a button. It lets the user acknowledge displayed information. Clicking dismisses the enclosing UI element."
},
"per_key_data_limit_dialog_set_custom": {
"message": "Set a custom data limit",
"description": "This string appears next to a checkbox in the per-key data limit dialog which, when selected, shows the input to add a data limit to an access key"
},
"per_key_data_limit_dialog_title": {
"message": "Data Limit - $KEY_NAME$",
"description": "This string appears as the title of the per-key data limit dialog.",
"placeholders": {
"KEY_NAME": {
"content": "{keyName}",
"Example": "Key 1"
}
}
},
"region_description": {
"message": "This is where your internet experience will come from.",
"description": "This string appears within the server creation flow, as a sub-header of the server selection view. Lets the user know about the implications of selecting a server location."
Expand Down
29 changes: 22 additions & 7 deletions src/server_manager/model/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,26 @@ export interface Server {
// Removes the access key given by id.
removeAccessKey(accessKeyId: AccessKeyId): Promise<void>;

// Sets a data transfer limit over a 30 day rolling window for all access keys.
setAccessKeyDataLimit(limit: DataLimit): Promise<void>;
// Sets a default access key data transfer limit over a 30 day rolling window for all access keys.
// This limit is overridden by per-key data limits. Forces enforcement of all data limits,
// including per-key data limits.
setDefaultDataLimit(limit: DataLimit): Promise<void>;

// Returns the access key data transfer limit, or undefined if it has not been set.
getAccessKeyDataLimit(): DataLimit|undefined;
// Returns the server default access key data transfer limit, or undefined if it has not been set.
getDefaultDataLimit(): DataLimit|undefined;

// Removes the access key data transfer limit.
removeAccessKeyDataLimit(): Promise<void>;
// Removes the server default data limit. Per-key data limits are still enforced. Traffic is
// tracked for if the limit is re-enabled. Forces enforcement of all data limits, including
// per-key limits.
removeDefaultDataLimit(): Promise<void>;

// Sets the custom data limit for a specific key. This limit overrides the server default limit
// if it exists. Forces enforcement of the chosen key's data limit.
setAccessKeyDataLimit(accessKeyId: AccessKeyId, limit: DataLimit): Promise<void>;

// Removes the custom data limit for a specific key. The key is still bound by the server default
// limit if it exists. Forces enforcement of the chosen key's data limit.
removeAccessKeyDataLimit(accessKeyId: AccessKeyId): Promise<void>;

// Returns whether metrics are enabled.
getMetricsEnabled(): boolean;
Expand Down Expand Up @@ -114,7 +126,9 @@ export interface ManagedServerHost {
getHostId(): string;
}

export class DataAmount { terabytes: number; }
export class DataAmount {
terabytes: number;
}

export class MonetaryCost {
// Value in US dollars.
Expand Down Expand Up @@ -148,6 +162,7 @@ export interface AccessKey {
id: AccessKeyId;
name: string;
accessUrl: string;
dataLimit?: DataLimit;
}

export type BytesByAccessKey = Map<AccessKeyId, number>;
Expand Down
4 changes: 2 additions & 2 deletions src/server_manager/model/survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
// limitations under the License.

export interface Surveys {
// Displays a survey when the data limits feature is enabled.
// Displays a survey when a server default data limit is set.
presentDataLimitsEnabledSurvey(): Promise<void>;
// Displays a survey when the data limits feature is disabled.
// Displays a survey when a server default data limit is removed.
presentDataLimitsDisabledSurvey(): Promise<void>;
}
15 changes: 11 additions & 4 deletions src/server_manager/web_app/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ function createTestApp(
manualServerRepo?: server.ManualServerRepository) {
const VERSION = '0.0.1';
if (!cloudAccounts) {
cloudAccounts = new CloudAccounts((token: string) => new FakeDigitalOceanAccount(), new InMemoryStorage());
cloudAccounts =
new CloudAccounts((token: string) => new FakeDigitalOceanAccount(), new InMemoryStorage());
}
if (!manualServerRepo) {
manualServerRepo = new FakeManualServerRepository();
Expand Down Expand Up @@ -220,13 +221,19 @@ class FakeServer implements server.Server {
setPortForNewAccessKeys(): Promise<void> {
return Promise.reject(new Error('FakeServer.setPortForNewAccessKeys not implemented'));
}
setAccessKeyDataLimit(limit: server.DataLimit): Promise<void> {
setAccessKeyDataLimit(accessKeyId: string, limit: server.DataLimit): Promise<void> {
return Promise.reject(new Error('FakeServer.setAccessKeyDataLimit not implemented'));
}
removeAccessKeyDataLimit(): Promise<void> {
removeAccessKeyDataLimit(accessKeyId: string): Promise<void> {
return Promise.reject(new Error('FakeServer.removeAccessKeyDataLimit not implemented'));
}
setDefaultDataLimit(limit: server.DataLimit): Promise<void> {
return Promise.reject(new Error('FakeServer.setDefaultDataLimit not implemented'));
}
removeDefaultDataLimit(): Promise<void> {
return Promise.resolve();
}
getAccessKeyDataLimit(): server.DataLimit|undefined {
getDefaultDataLimit(): server.DataLimit|undefined {
return undefined;
}
}
Expand Down
Loading

0 comments on commit 2f2e658

Please sign in to comment.