diff --git a/webui/src/app/app.module.ts b/webui/src/app/app.module.ts
index c9ba2741a..c9f573855 100644
--- a/webui/src/app/app.module.ts
+++ b/webui/src/app/app.module.ts
@@ -13,6 +13,7 @@ import { ProvidersComponent } from './components/providers/providers.component';
import { LetDirective } from './directives/let.directive';
import { BackendFilterPipe } from './pipes/backend.filter.pipe';
import { FrontendFilterPipe } from './pipes/frontend.filter.pipe';
+import { HumanReadableFilterPipe } from './pipes/humanreadable.filter.pipe';
import { KeysPipe } from './pipes/keys.pipe';
import { ApiService } from './services/api.service';
import { WindowService } from './services/window.service';
@@ -28,6 +29,7 @@ import { WindowService } from './services/window.service';
KeysPipe,
FrontendFilterPipe,
BackendFilterPipe,
+ HumanReadableFilterPipe,
LetDirective
],
imports: [
diff --git a/webui/src/app/components/providers/providers.component.html b/webui/src/app/components/providers/providers.component.html
index bcf9df5e5..72fa96973 100644
--- a/webui/src/app/components/providers/providers.component.html
+++ b/webui/src/app/components/providers/providers.component.html
@@ -435,6 +435,41 @@
+
+
+
+
+
+
+
+
+
+
+
+ Extractor Function
+ {{ p.ratelimit.extractorFunc }}
+
+
+
+
+ Rateset |
+ Period |
+ Average |
+ Burst |
+
+
+
+ {{rateset.id}} |
+ {{rateset.period | humanreadable}} |
+ {{rateset.average}} |
+ {{rateset.burst}} |
+
+
+
+
+
+
+
diff --git a/webui/src/app/pipes/humanreadable.filter.pipe.spec.ts b/webui/src/app/pipes/humanreadable.filter.pipe.spec.ts
new file mode 100644
index 000000000..4cdaa6f4a
--- /dev/null
+++ b/webui/src/app/pipes/humanreadable.filter.pipe.spec.ts
@@ -0,0 +1,46 @@
+import { HumanReadableFilterPipe } from './humanreadable.filter.pipe';
+
+describe('HumanReadableFilterPipe', () => {
+ const pipe = new HumanReadableFilterPipe();
+
+ const datatable = [{
+ 'given': '180000000000',
+ 'expected': '180s'
+ },
+ {
+ 'given': '4096.0',
+ 'expected': '4096ns'
+ },
+ {
+ 'given': '7200000000000',
+ 'expected': '120m'
+ },
+ {
+ 'given': '1337',
+ 'expected': '1337ns'
+ },
+ {
+ 'given': 'traefik',
+ 'expected': 'traefik',
+ },
+ {
+ 'given': '-23',
+ 'expected': '-23',
+ },
+ {
+ 'given': '0',
+ 'expected': '0',
+ },
+ ];
+
+ datatable.forEach(item => {
+ it((item.given + ' should be transformed to ' + item.expected ), () => {
+ expect(pipe.transform(item.given)).toEqual(item.expected);
+ });
+ });
+
+ it('create an instance', () => {
+ expect(pipe).toBeTruthy();
+ });
+
+});
diff --git a/webui/src/app/pipes/humanreadable.filter.pipe.ts b/webui/src/app/pipes/humanreadable.filter.pipe.ts
new file mode 100644
index 000000000..d0f5e8736
--- /dev/null
+++ b/webui/src/app/pipes/humanreadable.filter.pipe.ts
@@ -0,0 +1,27 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+/**
+ * HumanReadableFilterPipe converts a time period in nanoseconds to a human-readable
+ * string.
+ */
+@Pipe({name: 'humanreadable'})
+export class HumanReadableFilterPipe implements PipeTransform {
+ transform(value): any {
+ let result = '';
+ const powerOf10 = Math.floor(Math.log10(value));
+
+ if (powerOf10 > 11) {
+ result = value / (60 * Math.pow(10, 9)) + 'm';
+ } else if (powerOf10 > 9) {
+ result = value / Math.pow(10, 9) + 's';
+ } else if (powerOf10 > 6) {
+ result = value / Math.pow(10, 6) + 'ms';
+ } else if (value > 0) {
+ result = Math.floor(value) + 'ns';
+ } else {
+ result = value;
+ }
+
+ return result;
+ }
+}
diff --git a/webui/src/app/services/api.service.ts b/webui/src/app/services/api.service.ts
index ae87b4ff8..d4b02289e 100644
--- a/webui/src/app/services/api.service.ts
+++ b/webui/src/app/services/api.service.ts
@@ -67,6 +67,9 @@ export class ApiService {
frontend.headers.customResponseHeaders = this.toHeaderArray(frontend.headers.customResponseHeaders);
frontend.headers.sslProxyHeaders = this.toHeaderArray(frontend.headers.sslProxyHeaders);
}
+ if (frontend.ratelimit && frontend.ratelimit.rateset) {
+ frontend.ratelimit.rateset = this.toArray(frontend.ratelimit.rateset, 'id');
+ }
return frontend;
});