diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..062b090c21 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +dashboard/node_modules \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index e0ecc31cf3..0000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,76 +0,0 @@ -module.exports = { - overrides: [ - { - files: [ - "src/renderer/**/*.js", - "build/**/*.js", - "src/renderer/**/*.vue" - ], - extends: [ - 'eslint:recommended', - 'plugin:vue/recommended' - ], - env: { - node: true - }, - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - }, - rules: { - "indent": ["error", 2], - "no-unused-vars": "off", - "vue/order-in-components": "off", - "vue/attributes-order": "off", - "vue/max-attributes-per-line": "off" - } - }, - { - files: [ - "build/*.ts", - "src/**/*.ts", - "spec/**/*.ts" - ], - parser: "@typescript-eslint/parser", - extends: [ - 'plugin:@typescript-eslint/recommended', - ], - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - }, - rules: { - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "off", - "indent": ["error", 2] - }, - }, - { - files: [ - "dashboard/**/*.ts", - "dashboard/**/*.tsx", - ], - parser: "@typescript-eslint/parser", - extends: [ - 'plugin:@typescript-eslint/recommended', - ], - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - jsx: true, - }, - rules: { - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/ban-ts-ignore": "off", - "indent": ["error", 2] - }, - } - ] -}; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000..4b0412cebd --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,96 @@ +{ + "settings": { + "react": { + "version": "16.11" + } + }, + "overrides": [ + { + "files": [ + "src/renderer/**/*.js", + "build/**/*.js", + "src/renderer/**/*.vue" + ], + "extends": [ + "eslint:recommended", + "plugin:vue/recommended" + ], + "env": { + "node": true + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "rules": { + "indent": ["error", 2], + "no-unused-vars": "off", + "vue/order-in-components": "off", + "vue/attributes-order": "off", + "vue/max-attributes-per-line": "off" + } + }, + { + "files": [ + "build/*.ts", + "src/**/*.ts", + "spec/**/*.ts", + "dashboard/**/*.ts", + "dashboard/**/*.tsx" + ], + "excludedFiles": [ + "**/node_modules/**/*" + ], + "parser": "@typescript-eslint/parser", + "extends": [ + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended" + ], + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module", + "project": "./tsconfig.json", + "jsx": true + }, + "rules": { + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "varsIgnorePattern": "^_", + "argsIgnorePattern": "^_" + } + ], + "@typescript-eslint/explicit-function-return-type": "error", + "@typescript-eslint/array-type": [ + "error", + { + "default": "array", + "readyonly": "array" + } + ], + "@typescript-eslint/no-explicit-any": "off", + "indent": ["error", 2], + "@typescript-eslint/no-use-before-define": "error", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-var-requires": "error", + "@typescript-eslint/await-thenable": "error", + "@typescript-eslint/consistent-type-assertions": [ + "error", + { + "assertionStyle": "as", + "objectLiteralTypeAssertions": "never" + } + ], + "@typescript-eslint/consistent-type-definitions": [ + "error", + "interface" + ], + "react/prop-types": [ 0 ], + "brace-style": ["error", "1tbs", { "allowSingleLine": false }], + "curly": "error", + "space-before-blocks": "error", + "semi": "error" + } + } + ] +} diff --git a/Makefile b/Makefile index 6139afdfba..784f123d45 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,10 @@ integration-win: yarn integration lint: - yarn lint + yarn run eslint --ext tsx,ts,vue --max-warnings=0 src/ dashboard/ build/ + +lint-fix: + yarn run eslint --ext tsx,ts,vue --max-warnings=0 src/ dashboard/ build/ --fix test-app: yarn test diff --git a/build/download_helm.ts b/build/download_helm.ts index 2f81b14583..acb8820ddd 100644 --- a/build/download_helm.ts +++ b/build/download_helm.ts @@ -1,3 +1,3 @@ -import { helmCli } from "../src/main/helm-cli" +import { helmCli } from "../src/main/helm-cli"; -helmCli.ensureBinary() +helmCli.ensureBinary(); diff --git a/build/download_kubectl.ts b/build/download_kubectl.ts index aa31ea6cae..6e8f1b55fb 100644 --- a/build/download_kubectl.ts +++ b/build/download_kubectl.ts @@ -1,9 +1,9 @@ -import * as request from "request" -import * as fs from "fs" -import { ensureDir, pathExists } from "fs-extra" -import * as md5File from "md5-file" -import * as requestPromise from "request-promise-native" -import * as path from "path" +import * as request from "request"; +import * as fs from "fs"; +import { ensureDir, pathExists } from "fs-extra"; +import * as md5File from "md5-file"; +import * as requestPromise from "request-promise-native"; +import * as path from "path"; class KubectlDownloader { public kubectlVersion: string @@ -13,92 +13,94 @@ class KubectlDownloader { constructor(clusterVersion: string, platform: string, arch: string, target: string) { this.kubectlVersion = clusterVersion; - const binaryName = platform === "windows" ? "kubectl.exe" : "kubectl" + const binaryName = platform === "windows" ? "kubectl.exe" : "kubectl"; this.url = `https://storage.googleapis.com/kubernetes-release/release/v${this.kubectlVersion}/bin/${platform}/${arch}/${binaryName}`; this.dirname = path.dirname(target); this.path = target; } - protected async urlEtag() { + protected async urlEtag(): Promise { const response = await requestPromise({ method: "HEAD", uri: this.url, resolveWithFullResponse: true - }).catch((error) => { console.log(error) }) + }).catch((error) => { + console.log(error); + }); if (response.headers["etag"]) { - return response.headers["etag"].replace(/"/g, "") + return response.headers["etag"].replace(/"/g, ""); } - return "" + return ""; } - public async checkBinary() { - const exists = await pathExists(this.path) + public async checkBinary(): Promise { + const exists = await pathExists(this.path); if (exists) { - const hash = md5File.sync(this.path) - const etag = await this.urlEtag() + const hash = md5File.sync(this.path); + const etag = await this.urlEtag(); if(hash == etag) { - console.log("Kubectl md5sum matches the remote etag") - return true + console.log("Kubectl md5sum matches the remote etag"); + return true; } - console.log("Kubectl md5sum " + hash + " does not match the remote etag " + etag + ", unlinking and downloading again") - await fs.promises.unlink(this.path) + console.log("Kubectl md5sum " + hash + " does not match the remote etag " + etag + ", unlinking and downloading again"); + await fs.promises.unlink(this.path); } - return false + return false; } - public async downloadKubectl() { + public async downloadKubectl(): Promise { const exists = await this.checkBinary(); if(exists) { - console.log("Already exists and is valid") - return + console.log("Already exists and is valid"); + return; } - await ensureDir(path.dirname(this.path), 0o755) + await ensureDir(path.dirname(this.path), 0o755); - const file = fs.createWriteStream(this.path) - console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`) + const file = fs.createWriteStream(this.path); + console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`); const requestOpts: request.UriOptions & request.CoreOptions = { uri: this.url, gzip: true - } - const stream = request(requestOpts) + }; + const stream = request(requestOpts); stream.on("complete", () => { - console.log("kubectl binary download finished") - file.end(() => {}) - }) + console.log("kubectl binary download finished"); + file.end(() => {}); + }); stream.on("error", (error) => { - console.log(error) - fs.unlink(this.path, () => {}) - throw(error) - }) - return new Promise((resolve, reject) => { + console.log(error); + fs.unlink(this.path, () => {}); + throw(error); + }); + return new Promise((resolve, _reject) => { file.on("close", () => { - console.log("kubectl binary download closed") - fs.chmod(this.path, 0o755, () => {}) - resolve() - }) - stream.pipe(file) - }) + console.log("kubectl binary download closed"); + fs.chmod(this.path, 0o755, () => {}); + resolve(); + }); + stream.pipe(file); + }); } } -const downloadVersion: string = require("../package.json").config.bundledKubectlVersion -const baseDir = path.join(process.env.INIT_CWD, 'binaries', 'client') +const downloadVersion: string = require("../package.json").config.bundledKubectlVersion; +const baseDir = path.join(process.env.INIT_CWD, 'binaries', 'client'); const downloads = [ { platform: 'linux', arch: 'amd64', target: path.join(baseDir, 'linux', 'x64', 'kubectl') }, { platform: 'darwin', arch: 'amd64', target: path.join(baseDir, 'darwin', 'x64', 'kubectl') }, { platform: 'windows', arch: 'amd64', target: path.join(baseDir, 'windows', 'x64', 'kubectl.exe') }, { platform: 'windows', arch: '386', target: path.join(baseDir, 'windows', 'ia32', 'kubectl.exe') } -] +]; downloads.forEach((dlOpts) => { - console.log(dlOpts) + console.log(dlOpts); const downloader = new KubectlDownloader(downloadVersion, dlOpts.platform, dlOpts.arch, dlOpts.target); console.log("Downloading: " + JSON.stringify(dlOpts)); - downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete"))) -}) + downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete"))); +}); diff --git a/dashboard/client/api/__test__/parseAPI.test.ts b/dashboard/client/api/__test__/parseAPI.test.ts index e700650e05..8d527b1bae 100644 --- a/dashboard/client/api/__test__/parseAPI.test.ts +++ b/dashboard/client/api/__test__/parseAPI.test.ts @@ -1,8 +1,8 @@ -import { KubeApi, IKubeApiLinkBase } from "../kube-api"; +import { KubeApi, KubeApiLinkBase } from "../kube-api"; interface ParseAPITest { url: string; - expected: Required; + expected: Required; } const tests: ParseAPITest[] = [ diff --git a/dashboard/client/api/api-manager.ts b/dashboard/client/api/api-manager.ts index 89e1b1cf34..0524bf8a72 100644 --- a/dashboard/client/api/api-manager.ts +++ b/dashboard/client/api/api-manager.ts @@ -17,7 +17,7 @@ export class ApiManager { private stores = observable.map(); private views = observable.map(); - getApi(pathOrCallback: string | ((api: KubeApi) => boolean)) { + getApi(pathOrCallback: string | ((api: KubeApi) => boolean)): KubeApi { if (typeof pathOrCallback === "string") { return this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase); } @@ -25,27 +25,32 @@ export class ApiManager { return Array.from(this.apis.values()).find(pathOrCallback); } - registerApi(apiBase: string, api: KubeApi) { + registerApi(apiBase: string, api: KubeApi): void { if (!this.apis.has(apiBase)) { this.apis.set(apiBase, api); } } protected resolveApi(api: string | KubeApi): KubeApi { - if (typeof api === "string") return this.getApi(api) + if (typeof api === "string") { + return this.getApi(api); + } return api; } - unregisterApi(api: string | KubeApi) { - if (typeof api === "string") this.apis.delete(api); - else { + unregisterApi(api: string | KubeApi): void { + if (typeof api === "string") { + this.apis.delete(api); + } else { const apis = Array.from(this.apis.entries()); const entry = apis.find(entry => entry[1] === api); - if (entry) this.unregisterApi(entry[0]); + if (entry) { + this.unregisterApi(entry[0]); + } } } - registerStore(api: KubeApi, store: KubeObjectStore) { + registerStore(api: KubeApi, store: KubeObjectStore): void { this.stores.set(api, store); } @@ -53,7 +58,7 @@ export class ApiManager { return this.stores.get(this.resolveApi(api)); } - registerViews(api: KubeApi | KubeApi[], views: ApiComponents) { + registerViews(api: KubeApi | KubeApi[], views: ApiComponents): void { if (Array.isArray(api)) { api.forEach(api => this.registerViews(api, views)); return; @@ -66,7 +71,7 @@ export class ApiManager { } getViews(api: string | KubeApi): ApiComponents { - return this.views.get(this.resolveApi(api)) || {} + return this.views.get(this.resolveApi(api)) || {}; } } diff --git a/dashboard/client/api/endpoints/__tests__/cron-job.api.test.ts b/dashboard/client/api/endpoints/__tests__/cron-job.api.test.ts index 1d0bf66847..5800aa1642 100644 --- a/dashboard/client/api/endpoints/__tests__/cron-job.api.test.ts +++ b/dashboard/client/api/endpoints/__tests__/cron-job.api.test.ts @@ -18,7 +18,7 @@ const cronJob = new CronJob({ suspend: false, }, status: {} -} as any) +} as any); describe("Check for CronJob schedule never run", () => { test("Should be false with normal schedule", () => { @@ -31,12 +31,12 @@ describe("Check for CronJob schedule never run", () => { }); test("Should be true with date 31 of February", () => { - cronJob.spec.schedule = "30 06 31 2 *" + cronJob.spec.schedule = "30 06 31 2 *"; expect(cronJob.isNeverRun()).toBeTruthy(); }); test("Should be true with date 32 of July", () => { - cronJob.spec.schedule = "0 30 06 32 7 *" + cronJob.spec.schedule = "0 30 06 32 7 *"; expect(cronJob.isNeverRun()).toBeTruthy(); }); diff --git a/dashboard/client/api/endpoints/cert-manager.api.ts b/dashboard/client/api/endpoints/cert-manager.api.ts index bfeb2692cb..bd29158606 100644 --- a/dashboard/client/api/endpoints/cert-manager.api.ts +++ b/dashboard/client/api/endpoints/cert-manager.api.ts @@ -3,96 +3,23 @@ // API docs: https://docs.cert-manager.io/en/latest/reference/api-docs/index.html import { KubeObject } from "../kube-object"; -import { ISecretRef, secretsApi } from "./secret.api"; +import { SecretRef, secretsApi } from "./secret.api"; import { getDetailsUrl } from "../../navigation"; import { KubeApi } from "../kube-api"; -export class Certificate extends KubeObject { - static kind = "Certificate" +export type IssuerType = "ACME" | "CA" | "SelfSigned" | "Vault" | "Venafi"; - spec: { - secretName: string; - commonName?: string; - dnsNames?: string[]; - organization?: string[]; - ipAddresses?: string[]; - duration?: string; - renewBefore?: string; - isCA?: boolean; - keySize?: number; - keyAlgorithm?: "rsa" | "ecdsa"; - issuerRef: { - kind?: string; - name: string; - }; - acme?: { - config: { - domains: string[]; - http01: { - ingress?: string; - ingressClass?: string; - }; - dns01?: { - provider: string; - }; - }[]; - }; - } - status: { - conditions?: { - lastTransitionTime: string; // 2019-06-04T07:35:58Z, - message: string; // Certificate is up to date and has not expired, - reason: string; // Ready, - status: string; // True, - type: string; // Ready - }[]; - notAfter: string; // 2019-11-01T05:36:27Z - lastFailureTime?: string; - } +export interface IssuerConditionBase { + lastTransitionTime: string; // 2019-06-05T07:10:42Z, + message: string; // The ACME account was registered with the ACME server, + reason: string; // ACMEAccountRegistered, + status: string; // True, + type: string; // Ready +} - getType(): string { - const { isCA, acme } = this.spec; - if (isCA) return "CA" - if (acme) return "ACME" - } - - getCommonName() { - return this.spec.commonName || "" - } - - getIssuerName() { - return this.spec.issuerRef.name; - } - - getSecretName() { - return this.spec.secretName; - } - - getIssuerDetailsUrl() { - return getDetailsUrl(issuersApi.getUrl({ - namespace: this.getNs(), - name: this.getIssuerName(), - })) - } - - getSecretDetailsUrl() { - return getDetailsUrl(secretsApi.getUrl({ - namespace: this.getNs(), - name: this.getSecretName(), - })) - } - - getConditions() { - const { conditions = [] } = this.status; - return conditions.map(condition => { - const { message, reason, lastTransitionTime, status } = condition; - return { - ...condition, - isReady: status === "True", - tooltip: `${message || reason} (${lastTransitionTime})` - } - }); - } +export interface IssuerCondition extends IssuerConditionBase { + tooltip: string; + isReady: boolean; } export class Issuer extends KubeObject { @@ -103,23 +30,23 @@ export class Issuer extends KubeObject { email: string; server: string; skipTLSVerify?: boolean; - privateKeySecretRef: ISecretRef; + privateKeySecretRef: SecretRef; solvers?: { dns01?: { cnameStrategy: string; acmedns?: { host: string; - accountSecretRef: ISecretRef; + accountSecretRef: SecretRef; }; akamai?: { - accessTokenSecretRef: ISecretRef; - clientSecretSecretRef: ISecretRef; - clientTokenSecretRef: ISecretRef; + accessTokenSecretRef: SecretRef; + clientSecretSecretRef: SecretRef; + clientTokenSecretRef: SecretRef; serviceConsumerDomain: string; }; azuredns?: { clientID: string; - clientSecretSecretRef: ISecretRef; + clientSecretSecretRef: SecretRef; hostedZoneName: string; resourceGroupName: string; subscriptionID: string; @@ -127,26 +54,26 @@ export class Issuer extends KubeObject { }; clouddns?: { project: string; - serviceAccountSecretRef: ISecretRef; + serviceAccountSecretRef: SecretRef; }; cloudflare?: { email: string; - apiKeySecretRef: ISecretRef; + apiKeySecretRef: SecretRef; }; digitalocean?: { - tokenSecretRef: ISecretRef; + tokenSecretRef: SecretRef; }; rfc2136?: { nameserver: string; tsigAlgorithm: string; tsigKeyName: string; - tsigSecretSecretRef: ISecretRef; + tsigSecretSecretRef: SecretRef; }; route53?: { accessKeyID: string; hostedZoneID: string; region: string; - secretAccessKeySecretRef: ISecretRef; + secretAccessKeySecretRef: SecretRef; }; webhook?: { config: object; // arbitrary json @@ -180,7 +107,7 @@ export class Issuer extends KubeObject { appRole: { path: string; roleId: string; - secretRef: ISecretRef; + secretRef: SecretRef; }; }; }; @@ -188,7 +115,7 @@ export class Issuer extends KubeObject { venafi?: { zone: string; cloud?: { - apiTokenSecretRef: ISecretRef; + apiTokenSecretRef: SecretRef; }; tpp?: { url: string; @@ -204,25 +131,29 @@ export class Issuer extends KubeObject { acme?: { uri: string; }; - conditions?: { - lastTransitionTime: string; // 2019-06-05T07:10:42Z, - message: string; // The ACME account was registered with the ACME server, - reason: string; // ACMEAccountRegistered, - status: string; // True, - type: string; // Ready - }[]; + conditions?: IssuerConditionBase[]; } - getType() { + getType(): IssuerType { const { acme, ca, selfSigned, vault, venafi } = this.spec; - if (acme) return "ACME" - if (ca) return "CA" - if (selfSigned) return "SelfSigned" - if (vault) return "Vault" - if (venafi) return "Venafi" + if (acme) { + return "ACME"; + } + if (ca) { + return "CA"; + } + if (selfSigned) { + return "SelfSigned"; + } + if (vault) { + return "Vault"; + } + if (venafi) { + return "Venafi"; + } } - getConditions() { + getConditions(): IssuerCondition[] { const { conditions = [] } = this.status; return conditions.map(condition => { const { message, reason, lastTransitionTime, status } = condition; @@ -230,7 +161,113 @@ export class Issuer extends KubeObject { ...condition, isReady: status === "True", tooltip: `${message || reason} (${lastTransitionTime})`, - } + }; + }); + } +} + +export const issuersApi = new KubeApi({ + kind: Issuer.kind, + apiBase: "/apis/cert-manager.io/v1alpha2/issuers", + isNamespaced: true, + objectConstructor: Issuer, +}); + +export interface CertificateConditionBase { + lastTransitionTime: string; // 2019-06-04T07:35:58Z, + message: string; // Certificate is up to date and has not expired, + reason: string; // Ready, + status: string; // True, + type: string; // Ready +} + +export interface CertificateCondition extends CertificateConditionBase { + isReady: boolean; + tooltip: string; +} + +export class Certificate extends KubeObject { + static kind = "Certificate" + + spec: { + secretName: string; + commonName?: string; + dnsNames?: string[]; + organization?: string[]; + ipAddresses?: string[]; + duration?: string; + renewBefore?: string; + isCA?: boolean; + keySize?: number; + keyAlgorithm?: "rsa" | "ecdsa"; + issuerRef: { + kind?: string; + name: string; + }; + acme?: { + config: { + domains: string[]; + http01: { + ingress?: string; + ingressClass?: string; + }; + dns01?: { + provider: string; + }; + }[]; + }; + } + status: { + conditions?: CertificateConditionBase[]; + notAfter: string; // 2019-11-01T05:36:27Z + lastFailureTime?: string; + } + + getType(): string { + const { isCA, acme } = this.spec; + if (isCA) { + return "CA"; + } + if (acme) { + return "ACME"; + } + } + + getCommonName(): string { + return this.spec.commonName || ""; + } + + getIssuerName(): string { + return this.spec.issuerRef.name; + } + + getSecretName(): string { + return this.spec.secretName; + } + + getIssuerDetailsUrl(): string { + return getDetailsUrl(issuersApi.getUrl({ + namespace: this.getNs(), + name: this.getIssuerName(), + })); + } + + getSecretDetailsUrl(): string { + return getDetailsUrl(secretsApi.getUrl({ + namespace: this.getNs(), + name: this.getSecretName(), + })); + } + + getConditions(): CertificateCondition[] { + const { conditions = [] } = this.status; + return conditions.map(condition => { + const { message, reason, lastTransitionTime, status } = condition; + return { + ...condition, + isReady: status === "True", + tooltip: `${message || reason} (${lastTransitionTime})` + }; }); } } @@ -246,13 +283,6 @@ export const certificatesApi = new KubeApi({ objectConstructor: Certificate, }); -export const issuersApi = new KubeApi({ - kind: Issuer.kind, - apiBase: "/apis/cert-manager.io/v1alpha2/issuers", - isNamespaced: true, - objectConstructor: Issuer, -}); - export const clusterIssuersApi = new KubeApi({ kind: ClusterIssuer.kind, apiBase: "/apis/cert-manager.io/v1alpha2/clusterissuers", diff --git a/dashboard/client/api/endpoints/cluster.api.ts b/dashboard/client/api/endpoints/cluster.api.ts index e602340219..d89f99334f 100644 --- a/dashboard/client/api/endpoints/cluster.api.ts +++ b/dashboard/client/api/endpoints/cluster.api.ts @@ -1,11 +1,11 @@ -import { IMetrics, IMetricsReqParams, metricsApi } from "./metrics.api"; +import { Metrics, MetricsReqParams, metricsApi } from "./metrics.api"; import { KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; export class ClusterApi extends KubeApi { - async getMetrics(nodeNames: string[], params?: IMetricsReqParams): Promise { + async getMetrics(nodeNames: string[], params?: MetricsReqParams): Promise { const nodes = nodeNames.join("|"); - const opts = { category: "cluster", nodes: nodes } + const opts = { category: "cluster", nodes: nodes }; return metricsApi.getMetrics({ memoryUsage: opts, @@ -31,7 +31,7 @@ export enum ClusterStatus { ERROR = "Error" } -export interface IClusterMetrics { +export interface ClusterMetrics { [metric: string]: T; memoryUsage: T; memoryRequests: T; @@ -82,10 +82,16 @@ export class Cluster extends KubeObject { errorReason?: string; } - getStatus() { - if (this.metadata.deletionTimestamp) return ClusterStatus.REMOVING; - if (!this.status || !this.status) return ClusterStatus.CREATING; - if (this.status.errorMessage) return ClusterStatus.ERROR; + getStatus(): ClusterStatus { + if (this.metadata.deletionTimestamp) { + return ClusterStatus.REMOVING; + } + if (!this.status || !this.status) { + return ClusterStatus.CREATING; + } + if (this.status.errorMessage) { + return ClusterStatus.ERROR; + } return ClusterStatus.ACTIVE; } } diff --git a/dashboard/client/api/endpoints/component-status.api.ts b/dashboard/client/api/endpoints/component-status.api.ts index 92d91f92a8..c426e929db 100644 --- a/dashboard/client/api/endpoints/component-status.api.ts +++ b/dashboard/client/api/endpoints/component-status.api.ts @@ -1,7 +1,7 @@ import { KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; -export interface IComponentStatusCondition { +export interface ComponentStatusCondition { type: string; status: string; message: string; @@ -10,9 +10,9 @@ export interface IComponentStatusCondition { export class ComponentStatus extends KubeObject { static kind = "ComponentStatus" - conditions: IComponentStatusCondition[] + conditions: ComponentStatusCondition[] - getTruthyConditions() { + getTruthyConditions(): ComponentStatusCondition[] { return this.conditions.filter(c => c.status === "True"); } } diff --git a/dashboard/client/api/endpoints/config.api.ts b/dashboard/client/api/endpoints/config.api.ts index 70ad639236..423d1d27c0 100644 --- a/dashboard/client/api/endpoints/config.api.ts +++ b/dashboard/client/api/endpoints/config.api.ts @@ -1,9 +1,10 @@ // App configuration api import { apiBase } from "../index"; import { IConfig } from "../../../server/common/config"; +import { CancelablePromise } from "client/utils/cancelableFetch"; export const configApi = { - getConfig() { - return apiBase.get("/config") + getConfig(): CancelablePromise { + return apiBase.get("/config"); }, }; diff --git a/dashboard/client/api/endpoints/crd.api.ts b/dashboard/client/api/endpoints/crd.api.ts index 39a4e50f8c..c7e86cbc21 100644 --- a/dashboard/client/api/endpoints/crd.api.ts +++ b/dashboard/client/api/endpoints/crd.api.ts @@ -51,81 +51,80 @@ export class CustomResourceDefinition extends KubeObject { storedVersions: string[]; } - getResourceUrl() { + getResourceUrl(): string { return crdResourcesURL({ params: { group: this.getGroup(), name: this.getPluralName(), } - }) + }); } - getResourceApiBase() { + getResourceApiBase(): string { const { version, group } = this.spec; - return `/apis/${group}/${version}/${this.getPluralName()}` + return `/apis/${group}/${version}/${this.getPluralName()}`; } - getPluralName() { - return this.getNames().plural + getPluralName(): string { + return this.getNames().plural; } - getResourceKind() { - return this.spec.names.kind + getResourceKind(): string { + return this.spec.names.kind; } - getResourceTitle() { + getResourceTitle(): string { const name = this.getPluralName(); - return name[0].toUpperCase() + name.substr(1) + return name[0].toUpperCase() + name.substr(1); } - getGroup() { + getGroup(): string { return this.spec.group; } - getScope() { + getScope(): string { return this.spec.scope; } - getVersion() { + getVersion(): string { return this.spec.version; } - isNamespaced() { + isNamespaced(): boolean { return this.getScope() === "Namespaced"; } - getStoredVersions() { + getStoredVersions(): string { return this.status.storedVersions.join(", "); } - getNames() { + getNames(): CustomResourceDefinition["spec"]["names"] { return this.spec.names; } - getConversion() { + getConversion(): string { return JSON.stringify(this.spec.conversion); } - getPrinterColumns(ignorePriority = true) { + getPrinterColumns(ignorePriority = true): Required { const columns = this.spec.additionalPrinterColumns || []; return columns .filter(column => column.name != "Age") .filter(column => ignorePriority ? true : !column.priority); } - getValidation() { + getValidation(): string { return JSON.stringify(this.spec.validation, null, 2); } - getConditions() { - if (!this.status.conditions) return []; - return this.status.conditions.map(condition => { + getConditions(): Required { + return (this.status.conditions || []).map(condition => { const { message, reason, lastTransitionTime, status } = condition; return { ...condition, isReady: status === "True", tooltip: `${message || reason} (${lastTransitionTime})` - } + }; }); } } diff --git a/dashboard/client/api/endpoints/cron-job.api.ts b/dashboard/client/api/endpoints/cron-job.api.ts index e48a8636b8..cf3c5f0de1 100644 --- a/dashboard/client/api/endpoints/cron-job.api.ts +++ b/dashboard/client/api/endpoints/cron-job.api.ts @@ -1,6 +1,6 @@ import moment from "moment"; import { KubeObject } from "../kube-object"; -import { IPodContainer } from "./pods.api"; +import { PodContainer } from "./pods.api"; import { formatDuration } from "../../utils/formatDuration"; import { autobind } from "../../utils"; import { KubeApi } from "../kube-api"; @@ -39,7 +39,7 @@ export class CronJob extends KubeObject { creationTimestamp?: string; }; spec: { - containers: IPodContainer[]; + containers: PodContainer[]; restartPolicy: string; terminationGracePeriodSeconds: number; dnsPolicy: string; @@ -56,26 +56,27 @@ export class CronJob extends KubeObject { lastScheduleTime: string; } - getSuspendFlag() { - return this.spec.suspend.toString() + getSuspendFlag(): string { + return this.spec.suspend.toString(); } - getLastScheduleTime() { - const diff = moment().diff(this.status.lastScheduleTime) - return formatDuration(diff, true) + getLastScheduleTime(): string { + return formatDuration(moment().diff(this.status.lastScheduleTime), true); } - getSchedule() { - return this.spec.schedule + getSchedule(): string { + return this.spec.schedule; } - isNeverRun() { + isNeverRun(): boolean { const schedule = this.getSchedule(); const daysInMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; const stamps = schedule.split(" "); const day = Number(stamps[stamps.length - 3]); // 1-31 const month = Number(stamps[stamps.length - 2]); // 1-12 - if (schedule.startsWith("@")) return false; + if (schedule.startsWith("@")) { + return false; + } return day > daysInMonth[month - 1]; } } diff --git a/dashboard/client/api/endpoints/daemon-set.api.ts b/dashboard/client/api/endpoints/daemon-set.api.ts index eedd4a1749..bc028f521e 100644 --- a/dashboard/client/api/endpoints/daemon-set.api.ts +++ b/dashboard/client/api/endpoints/daemon-set.api.ts @@ -1,6 +1,6 @@ import get from "lodash/get"; -import { IPodContainer } from "./pods.api"; -import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; +import { PodContainer } from "./pods.api"; +import { Affinity, WorkloadKubeObject } from "../workload-kube-object"; import { autobind } from "../../utils"; import { KubeApi } from "../kube-api"; @@ -22,13 +22,13 @@ export class DaemonSet extends WorkloadKubeObject { }; }; spec: { - containers: IPodContainer[]; - initContainers?: IPodContainer[]; + containers: PodContainer[]; + initContainers?: PodContainer[]; restartPolicy: string; terminationGracePeriodSeconds: number; dnsPolicy: string; hostPID: boolean; - affinity?: IAffinity; + affinity?: Affinity; nodeSelector?: { [selector: string]: string; }; @@ -61,10 +61,10 @@ export class DaemonSet extends WorkloadKubeObject { numberUnavailable: number; } - getImages() { - const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []) - const initContainers: IPodContainer[] = get(this, "spec.template.spec.initContainers", []) - return [...containers, ...initContainers].map(container => container.image) + getImages(): string[] { + const containers: PodContainer[] = get(this, "spec.template.spec.containers", []); + const initContainers: PodContainer[] = get(this, "spec.template.spec.initContainers", []); + return [...containers, ...initContainers].map(container => container.image); } } diff --git a/dashboard/client/api/endpoints/deployment.api.ts b/dashboard/client/api/endpoints/deployment.api.ts index dbc3744ee0..54d0317dc5 100644 --- a/dashboard/client/api/endpoints/deployment.api.ts +++ b/dashboard/client/api/endpoints/deployment.api.ts @@ -1,27 +1,24 @@ -import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; +import { Affinity, WorkloadKubeObject } from "../workload-kube-object"; import { autobind } from "../../utils"; import { KubeApi } from "../kube-api"; +import { CancelablePromise } from "client/utils/cancelableFetch"; +import { KubeJsonApiData } from "../kube-json-api"; export class DeploymentApi extends KubeApi { - protected getScaleApiUrl(params: { namespace: string; name: string }) { - return this.getUrl(params) + "/scale" + protected getScaleApiUrl(params: { namespace: string; name: string }): string { + return this.getUrl(params) + "/scale"; } - getReplicas(params: { namespace: string; name: string }): Promise { - return this.request - .get(this.getScaleApiUrl(params)) - .then(({ status }: any) => status.replicas) + async getReplicas(params: { namespace: string; name: string }): Promise { + const { status } = await this.request.get(this.getScaleApiUrl(params)); + return status.replicas; } - scale(params: { namespace: string; name: string }, replicas: number) { - return this.request.put(this.getScaleApiUrl(params), { - data: { - metadata: params, - spec: { - replicas: replicas - } - } - }) + scale(metadata: { namespace: string; name: string }, replicas: number): CancelablePromise { + return this.request.put( + this.getScaleApiUrl(metadata), + { data: { metadata, spec: { replicas } } } + ); } } @@ -96,7 +93,7 @@ export class Deployment extends WorkloadKubeObject { restartPolicy: string; terminationGracePeriodSeconds: number; dnsPolicy: string; - affinity?: IAffinity; + affinity?: Affinity; nodeSelector?: { [selector: string]: string; }; @@ -145,20 +142,15 @@ export class Deployment extends WorkloadKubeObject { }[]; } - getConditions(activeOnly = false) { - const { conditions } = this.status - if (!conditions) return [] - if (activeOnly) { - return conditions.filter(c => c.status === "True") - } - return conditions + getConditions(activeOnly = false): Deployment["status"]["conditions"] { + return this.status.conditions.filter(({ status }) => !activeOnly || status === "True"); } - getConditionsText(activeOnly = true) { - return this.getConditions(activeOnly).map(({ type }) => type).join(" ") + getConditionsText(activeOnly = true): string { + return this.getConditions(activeOnly).map(({ type }) => type).join(" "); } - getReplicas() { + getReplicas(): number { return this.spec.replicas || 0; } } diff --git a/dashboard/client/api/endpoints/endpoint.api.ts b/dashboard/client/api/endpoints/endpoint.api.ts index 6f0c400969..a9f95dc752 100644 --- a/dashboard/client/api/endpoints/endpoint.api.ts +++ b/dashboard/client/api/endpoints/endpoint.api.ts @@ -2,25 +2,25 @@ import { autobind } from "../../utils"; import { KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; -export interface IEndpointPort { +export interface EndpointPort { name?: string; protocol: string; port: number; } -export interface IEndpointAddress { +export interface EndpointAddress { hostname: string; ip: string; nodeName: string; } -export interface IEndpointSubset { - addresses: IEndpointAddress[]; - notReadyAddresses: IEndpointAddress[]; - ports: IEndpointPort[]; +export interface EndpointSubset { + addresses: EndpointAddress[]; + notReadyAddresses: EndpointAddress[]; + ports: EndpointPort[]; } -interface ITargetRef { +interface TargetRef { kind: string; namespace: string; name: string; @@ -29,7 +29,7 @@ interface ITargetRef { apiVersion: string; } -export class EndpointAddress implements IEndpointAddress { +export class EndpointAddress implements EndpointAddress { hostname: string; ip: string; nodeName: string; @@ -41,34 +41,34 @@ export class EndpointAddress implements IEndpointAddress { resourceVersion: string; }; - constructor(data: IEndpointAddress) { - Object.assign(this, data) + constructor(data: EndpointAddress) { + Object.assign(this, data); } - getId() { - return this.ip + getId(): string { + return this.ip; } - getName() { - return this.hostname + getName(): string { + return this.hostname; } - getTargetRef(): ITargetRef { + getTargetRef(): TargetRef { if (this.targetRef) { - return Object.assign(this.targetRef, {apiVersion: "v1"}) + return Object.assign(this.targetRef, {apiVersion: "v1"}); } else { - return null + return null; } } } -export class EndpointSubset implements IEndpointSubset { - addresses: IEndpointAddress[]; - notReadyAddresses: IEndpointAddress[]; - ports: IEndpointPort[]; +export class EndpointSubset implements EndpointSubset { + addresses: EndpointAddress[]; + notReadyAddresses: EndpointAddress[]; + ports: EndpointPort[]; - constructor(data: IEndpointSubset) { - Object.assign(this, data) + constructor(data: EndpointSubset) { + Object.assign(this, data); } getAddresses(): EndpointAddress[] { @@ -83,16 +83,16 @@ export class EndpointSubset implements IEndpointSubset { toString(): string { if(!this.addresses) { - return "" + return ""; } return this.addresses.map(address => { if (!this.ports) { - return address.ip + return address.ip; } return this.ports.map(port => { - return `${address.ip}:${port.port}` - }).join(", ") - }).join(", ") + return `${address.ip}:${port.port}`; + }).join(", "); + }).join(", "); } } @@ -100,7 +100,7 @@ export class EndpointSubset implements IEndpointSubset { export class Endpoint extends KubeObject { static kind = "Endpoint" - subsets: IEndpointSubset[] + subsets: EndpointSubset[] getEndpointSubsets(): EndpointSubset[] { const subsets = this.subsets || []; @@ -109,9 +109,9 @@ export class Endpoint extends KubeObject { toString(): string { if(this.subsets) { - return this.getEndpointSubsets().map(es => es.toString()).join(", ") + return this.getEndpointSubsets().map(es => es.toString()).join(", "); } else { - return "" + return ""; } } diff --git a/dashboard/client/api/endpoints/events.api.ts b/dashboard/client/api/endpoints/events.api.ts index 33f7b4a3ec..88ee8da1fe 100644 --- a/dashboard/client/api/endpoints/events.api.ts +++ b/dashboard/client/api/endpoints/events.api.ts @@ -31,23 +31,23 @@ export class KubeEvent extends KubeObject { reportingComponent: string reportingInstance: string - isWarning() { + isWarning(): boolean { return this.type === "Warning"; } - getSource() { - const { component, host } = this.source - return `${component} ${host || ""}` + getSource(): string { + const { component, host } = this.source; + return `${component} ${host || ""}`; } - getFirstSeenTime() { - const diff = moment().diff(this.firstTimestamp) - return formatDuration(diff, true) + getFirstSeenTime(): string { + const diff = moment().diff(this.firstTimestamp); + return formatDuration(diff, true); } - getLastSeenTime() { - const diff = moment().diff(this.lastTimestamp) - return formatDuration(diff, true) + getLastSeenTime(): string { + const diff = moment().diff(this.lastTimestamp); + return formatDuration(diff, true); } } @@ -56,4 +56,4 @@ export const eventApi = new KubeApi({ apiBase: "/api/v1/events", isNamespaced: true, objectConstructor: KubeEvent, -}) +}); diff --git a/dashboard/client/api/endpoints/helm-charts.api.ts b/dashboard/client/api/endpoints/helm-charts.api.ts index ced9d13c1c..11f0e71c89 100644 --- a/dashboard/client/api/endpoints/helm-charts.api.ts +++ b/dashboard/client/api/endpoints/helm-charts.api.ts @@ -2,54 +2,7 @@ import pathToRegExp from "path-to-regexp"; import { apiKubeHelm } from "../index"; import { stringify } from "querystring"; import { autobind } from "../../utils"; - -interface IHelmChartList { - [repo: string]: { - [name: string]: HelmChart; - }; -} - -export interface IHelmChartDetails { - readme: string; - versions: HelmChart[]; -} - -const endpoint = pathToRegExp.compile(`/v2/charts/:repo?/:name?`) as (params?: { - repo?: string; - name?: string; -}) => string; - -export const helmChartsApi = { - list() { - return apiKubeHelm - .get(endpoint()) - .then(data => { - return Object - .values(data) - .reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), []) - .map(HelmChart.create); - }); - }, - - get(repo: string, name: string, readmeVersion?: string) { - const path = endpoint({ repo, name }); - return apiKubeHelm - .get(path + "?" + stringify({ version: readmeVersion })) - .then(data => { - const versions = data.versions.map(HelmChart.create); - const readme = data.readme; - return { - readme, - versions, - } - }); - }, - - getValues(repo: string, name: string, version: string) { - return apiKubeHelm - .get(`/v2/charts/${repo}/${name}/values?` + stringify({ version })); - } -}; +import { CancelablePromise } from "client/utils/cancelableFetch"; @autobind() export class HelmChart { @@ -57,10 +10,6 @@ export class HelmChart { Object.assign(this, data); } - static create(data: any) { - return new HelmChart(data); - } - apiVersion: string name: string version: string @@ -83,47 +32,74 @@ export class HelmChart { deprecated?: boolean tillerVersion?: string - getId() { + getId(): string { return this.digest; } - getName() { + getName(): string { return this.name; } - getFullName(splitter = "/") { - return [this.getRepository(), this.getName()].join(splitter); + getFullName(splitter = "/"): string { + return [this.repo, this.name].join(splitter); } - getDescription() { - return this.description; - } - - getIcon() { - return this.icon; - } - - getHome() { - return this.home; - } - - getMaintainers() { + getMaintainers(): Required { return this.maintainers || []; } - getVersion() { - return this.version; - } - - getRepository() { - return this.repo; - } - - getAppVersion() { + getAppVersion(): string { return this.appVersion || ""; } - getKeywords() { + getKeywords(): Required { return this.keywords || []; } } + +interface HelmChartList { + [repo: string]: { + [name: string]: HelmChart; + }; +} + +export interface HelmChartDetails { + readme: string; + versions: HelmChart[]; +} + +const endpoint = pathToRegExp.compile(`/v2/charts/:repo?/:name?`) as (params?: { + repo?: string; + name?: string; +}) => string; + +export interface HelmChartInfo { + readme: string; + versions: HelmChart[]; +} + +export const helmChartsApi = { + list(): CancelablePromise { + return apiKubeHelm.get(endpoint()) + .then(data => { + return Object.values(data) + .reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), []) + .map(data => new HelmChart(data)); + }); + }, + + get(repo: string, name: string, readmeVersion?: string): CancelablePromise { + const path = endpoint({ repo, name }); + + return apiKubeHelm.get(path + "?" + stringify({ version: readmeVersion })) + .then(({ readme, versions }) => ({ + readme, + versions: versions.map(data => new HelmChart(data)) + })); + }, + + getValues(repo: string, name: string, version: string): CancelablePromise { + return apiKubeHelm + .get(`/v2/charts/${repo}/${name}/values?` + stringify({ version })); + } +}; diff --git a/dashboard/client/api/endpoints/helm-releases.api.ts b/dashboard/client/api/endpoints/helm-releases.api.ts index 52f797b006..ca697d6f1c 100644 --- a/dashboard/client/api/endpoints/helm-releases.api.ts +++ b/dashboard/client/api/endpoints/helm-releases.api.ts @@ -6,8 +6,10 @@ import { apiKubeHelm } from "../index"; import { helmChartStore } from "../../components/+apps-helm-charts/helm-chart.store"; import { ItemObject } from "../../item.store"; import { KubeObject } from "../kube-object"; +import { CancelablePromise } from "client/utils/cancelableFetch"; +import { KubeJsonApiData } from "../kube-json-api"; -interface IReleasePayload { +interface ReleasePayload { name: string; namespace: string; version: string; @@ -23,15 +25,15 @@ interface IReleasePayload { }; } -interface IReleaseRawDetails extends IReleasePayload { +interface ReleaseRawDetails extends ReleasePayload { resources: string; } -export interface IReleaseDetails extends IReleasePayload { +export interface ReleaseInfo extends ReleasePayload { resources: KubeObject[]; } -export interface IReleaseCreatePayload { +export interface ReleaseCreatePayload { name?: string; repo: string; chart: string; @@ -40,19 +42,19 @@ export interface IReleaseCreatePayload { values: string; } -export interface IReleaseUpdatePayload { +export interface ReleaseUpdatePayload { repo: string; chart: string; version: string; values: string; } -export interface IReleaseUpdateDetails { +export interface ReleaseUpdateDetails { log: string; - release: IReleaseDetails; + release: ReleaseInfo; } -export interface IReleaseRevision { +export interface ReleaseRevision { revision: number; updated: string; status: string; @@ -67,74 +69,12 @@ const endpoint = pathToRegExp.compile(`/v2/releases/:namespace?/:name?`) as ( } ) => string; -export const helmReleasesApi = { - list(namespace?: string) { - return apiKubeHelm - .get(endpoint({ namespace })) - .then(releases => releases.map(HelmRelease.create)); - }, - - get(name: string, namespace: string) { - const path = endpoint({ name, namespace }); - return apiKubeHelm.get(path).then(details => { - const items: KubeObject[] = JSON.parse(details.resources).items; - const resources = items.map(item => KubeObject.create(item)); - return { - ...details, - resources - } - }); - }, - - create(payload: IReleaseCreatePayload): Promise { - const { repo, ...data } = payload; - data.chart = `${repo}/${data.chart}`; - data.values = jsYaml.safeLoad(data.values); - return apiKubeHelm.post(endpoint(), { data }); - }, - - update(name: string, namespace: string, payload: IReleaseUpdatePayload): Promise { - const { repo, ...data } = payload; - data.chart = `${repo}/${data.chart}`; - data.values = jsYaml.safeLoad(data.values); - return apiKubeHelm.put(endpoint({ name, namespace }), { data }); - }, - - async delete(name: string, namespace: string) { - const path = endpoint({ name, namespace }); - return apiKubeHelm.del(path); - }, - - getValues(name: string, namespace: string) { - const path = endpoint({ name, namespace }) + "/values"; - return apiKubeHelm.get(path); - }, - - getHistory(name: string, namespace: string): Promise { - const path = endpoint({ name, namespace }) + "/history"; - return apiKubeHelm.get(path); - }, - - rollback(name: string, namespace: string, revision: number) { - const path = endpoint({ name, namespace }) + "/rollback"; - return apiKubeHelm.put(path, { - data: { - revision: revision - } - }); - } -}; - @autobind() export class HelmRelease implements ItemObject { constructor(data: any) { Object.assign(this, data); } - static create(data: any) { - return new HelmRelease(data); - } - appVersion: string name: string namespace: string @@ -143,45 +83,32 @@ export class HelmRelease implements ItemObject { updated: string revision: number - getId() { + getId(): string { return this.namespace + this.name; } - getName() { + getName(): string { return this.name; } - getNs() { - return this.namespace; - } - - getChart(withVersion = false) { - let chart = this.chart - if(!withVersion && this.getVersion() != "" ) { - const search = new RegExp(`-${this.getVersion()}`) + getChart(withVersion = false): string { + let chart = this.chart; + if (!withVersion && this.getVersion() != "") { + const search = new RegExp(`-${this.getVersion()}`); chart = chart.replace(search, ""); } - return chart + return chart; } - getRevision() { - return this.revision; - } - - getStatus() { + getStatus(): string { return capitalize(this.status); } - getVersion() { - const versions = this.chart.match(/(v?\d+)[^-].*$/) - if (versions) { - return versions[0] - } else { - return "" - } + getVersion(): string { + return this.chart.match(/(v?\d+)[^-].*$/)?.[0] || ""; } - getUpdated(humanize = true, compact = true) { + getUpdated(humanize = true, compact = true): number | string { const now = new Date().getTime(); const updated = this.updated.replace(/\s\w*$/, ""); // 2019-11-26 10:58:09 +0300 MSK -> 2019-11-26 10:58:09 +0300 to pass into Date() const updatedDate = new Date(updated).getTime(); @@ -194,11 +121,67 @@ export class HelmRelease implements ItemObject { // Helm does not store from what repository the release is installed, // so we have to try to guess it by searching charts - async getRepo() { + async getRepo(): Promise { const chartName = this.getChart(); const version = this.getVersion(); const versions = await helmChartStore.getVersions(chartName); const chartVersion = versions.find(chartVersion => chartVersion.version === version); - return chartVersion ? chartVersion.repo : ""; + return chartVersion?.repo || ""; } } + +export const helmReleasesApi = { + async list(namespace?: string): Promise { + const releases = await apiKubeHelm.get(endpoint({ namespace })); + return releases.map(data => new HelmRelease(data)); + }, + + async get(name: string, namespace: string): Promise { + const path = endpoint({ name, namespace }); + const details = await apiKubeHelm.get(path); + const items: KubeObject[] = JSON.parse(details.resources).items; + const resources = items.map(item => new KubeObject(item)); + return { + ...details, + resources + }; + }, + + create(payload: ReleaseCreatePayload): Promise { + const { repo, ...data } = payload; + data.chart = `${repo}/${data.chart}`; + data.values = jsYaml.safeLoad(data.values); + return apiKubeHelm.post(endpoint(), { data }); + }, + + update(name: string, namespace: string, payload: ReleaseUpdatePayload): Promise { + const { repo, ...data } = payload; + data.chart = `${repo}/${data.chart}`; + data.values = jsYaml.safeLoad(data.values); + return apiKubeHelm.put(endpoint({ name, namespace }), { data }); + }, + + delete(name: string, namespace: string): CancelablePromise { + const path = endpoint({ name, namespace }); + return apiKubeHelm.del(path); + }, + + getValues(name: string, namespace: string): CancelablePromise { + const path = endpoint({ name, namespace }) + "/values"; + return apiKubeHelm.get(path); + }, + + getHistory(name: string, namespace: string): Promise { + const path = endpoint({ name, namespace }) + "/history"; + return apiKubeHelm.get(path); + }, + + rollback(name: string, namespace: string, revision: number): CancelablePromise { + const path = endpoint({ name, namespace }) + "/rollback"; + return apiKubeHelm.put(path, { + data: { + revision: revision + } + }); + } +}; diff --git a/dashboard/client/api/endpoints/hpa.api.ts b/dashboard/client/api/endpoints/hpa.api.ts index 4410bb9521..5c05910d39 100644 --- a/dashboard/client/api/endpoints/hpa.api.ts +++ b/dashboard/client/api/endpoints/hpa.api.ts @@ -22,7 +22,7 @@ export type IHpaMetricData = T & { targetAverageValue?: string; } -export interface IHpaMetric { +export interface HpaMetric { [kind: string]: IHpaMetricData; type: HpaMetricType; @@ -38,6 +38,19 @@ export interface IHpaMetric { }>; } +export interface PodStatusCondition { + lastTransitionTime: string; + message: string; + reason: string; + status: string; + type: string; +} + +export interface PodCondition extends PodStatusCondition { + isReady: boolean; + tooltip: string; +} + export class HorizontalPodAutoscaler extends KubeObject { static kind = "HorizontalPodAutoscaler"; @@ -49,58 +62,34 @@ export class HorizontalPodAutoscaler extends KubeObject { }; minReplicas: number; maxReplicas: number; - metrics: IHpaMetric[]; + metrics: HpaMetric[]; } status: { currentReplicas: number; desiredReplicas: number; - currentMetrics: IHpaMetric[]; - conditions: { - lastTransitionTime: string; - message: string; - reason: string; - status: string; - type: string; - }[]; + currentMetrics: HpaMetric[]; + conditions: PodStatusCondition[]; } - getMaxPods() { - return this.spec.maxReplicas || 0; - } - - getMinPods() { - return this.spec.minReplicas || 0; - } - - getReplicas() { - return this.status.currentReplicas; - } - - getConditions() { - if (!this.status.conditions) return []; + getConditions(): PodCondition[] { + if (!this.status.conditions) { + return []; + } return this.status.conditions.map(condition => { const { message, reason, lastTransitionTime, status } = condition; return { ...condition, isReady: status === "True", tooltip: `${message || reason} (${lastTransitionTime})` - } + }; }); } - getMetrics() { - return this.spec.metrics || []; - } - - getCurrentMetrics() { - return this.status.currentMetrics || []; - } - - protected getMetricName(metric: IHpaMetric): string { + protected getMetricName(metric: HpaMetric): string { const { type, resource, pods, object, external } = metric; switch (type) { case HpaMetricType.Resource: - return resource.name + return resource.name; case HpaMetricType.Pods: return pods.metricName; case HpaMetricType.Object: @@ -111,9 +100,9 @@ export class HorizontalPodAutoscaler extends KubeObject { } // todo: refactor - getMetricValues(metric: IHpaMetric): string { + getMetricValues(metric: HpaMetric): string { const metricType = metric.type.toLowerCase(); - const currentMetric = this.getCurrentMetrics().find(current => + const currentMetric = this.status.currentMetrics.find(current => metric.type == current.type && this.getMetricName(metric) == this.getMetricName(current) ); const current = currentMetric ? currentMetric[metricType] : null; @@ -122,11 +111,15 @@ export class HorizontalPodAutoscaler extends KubeObject { let targetValue = "unknown"; if (current) { currentValue = current.currentAverageUtilization || current.currentAverageValue || current.currentValue; - if (current.currentAverageUtilization) currentValue += "%"; + if (current.currentAverageUtilization) { + currentValue += "%"; + } } if (target) { targetValue = target.targetAverageUtilization || target.targetAverageValue || target.targetValue; - if (target.targetAverageUtilization) targetValue += "%" + if (target.targetAverageUtilization) { + targetValue += "%"; + } } return `${currentValue} / ${targetValue}`; } diff --git a/dashboard/client/api/endpoints/index.ts b/dashboard/client/api/endpoints/index.ts index f175d6feb9..63b6aca643 100644 --- a/dashboard/client/api/endpoints/index.ts +++ b/dashboard/client/api/endpoints/index.ts @@ -1,33 +1,33 @@ // Local express.js endpoints -export * from "./config.api" -export * from "./cluster.api" -export * from "./kubeconfig.api" +export * from "./config.api"; +export * from "./cluster.api"; +export * from "./kubeconfig.api"; // Kubernetes endpoints // Docs: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/ -export * from "./namespaces.api" -export * from "./cluster-role.api" -export * from "./cluster-role-binding.api" -export * from "./role.api" -export * from "./role-binding.api" -export * from "./secret.api" -export * from "./service-accounts.api" -export * from "./nodes.api" -export * from "./pods.api" -export * from "./deployment.api" -export * from "./daemon-set.api" -export * from "./stateful-set.api" -export * from "./replica-set.api" -export * from "./job.api" -export * from "./cron-job.api" -export * from "./configmap.api" -export * from "./ingress.api" -export * from "./network-policy.api" -export * from "./persistent-volume-claims.api" -export * from "./persistent-volume.api" -export * from "./service.api" -export * from "./endpoint.api" -export * from "./storage-class.api" -export * from "./pod-metrics.api" -export * from "./podsecuritypolicy.api" -export * from "./selfsubjectrulesreviews.api" +export * from "./namespaces.api"; +export * from "./cluster-role.api"; +export * from "./cluster-role-binding.api"; +export * from "./role.api"; +export * from "./role-binding.api"; +export * from "./secret.api"; +export * from "./service-accounts.api"; +export * from "./nodes.api"; +export * from "./pods.api"; +export * from "./deployment.api"; +export * from "./daemon-set.api"; +export * from "./stateful-set.api"; +export * from "./replica-set.api"; +export * from "./job.api"; +export * from "./cron-job.api"; +export * from "./configmap.api"; +export * from "./ingress.api"; +export * from "./network-policy.api"; +export * from "./persistent-volume-claims.api"; +export * from "./persistent-volume.api"; +export * from "./service.api"; +export * from "./endpoint.api"; +export * from "./storage-class.api"; +export * from "./pod-metrics.api"; +export * from "./podsecuritypolicy.api"; +export * from "./selfsubjectrulesreviews.api"; diff --git a/dashboard/client/api/endpoints/ingress.api.ts b/dashboard/client/api/endpoints/ingress.api.ts index dc277af04b..550fa57b14 100644 --- a/dashboard/client/api/endpoints/ingress.api.ts +++ b/dashboard/client/api/endpoints/ingress.api.ts @@ -1,11 +1,11 @@ import { KubeObject } from "../kube-object"; import { autobind } from "../../utils"; -import { IMetrics, metricsApi } from "./metrics.api"; +import { Metrics, metricsApi } from "./metrics.api"; import { KubeApi } from "../kube-api"; export class IngressApi extends KubeApi { - getMetrics(ingress: string, namespace: string): Promise { - const opts = { category: "ingress", ingress } + getMetrics(ingress: string, namespace: string): Promise { + const opts = { category: "ingress", ingress }; return metricsApi.getMetrics({ bytesSentSuccess: opts, bytesSentFailure: opts, @@ -17,7 +17,7 @@ export class IngressApi extends KubeApi { } } -export interface IIngressMetrics { +export interface IngressMetrics { [metric: string]: T; bytesSentSuccess: T; bytesSentFailure: T; @@ -56,52 +56,55 @@ export class Ingress extends KubeObject { }; } - getRoutes() { - const { spec: { tls, rules } } = this - if (!rules) return [] + getRoutes(): string[] { + const { spec: { tls, rules } } = this; + if (!rules) { + return []; + } - let protocol = "http" - const routes: string[] = [] + let protocol = "http"; + const routes: string[] = []; if (tls && tls.length > 0) { - protocol += "s" + protocol += "s"; } rules.map(rule => { - const host = rule.host ? rule.host : "*" + const host = rule.host ? rule.host : "*"; if (rule.http && rule.http.paths) { rule.http.paths.forEach(path => { - routes.push(protocol + "://" + host + (path.path || "/") + " ⇢ " + path.backend.serviceName + ":" + path.backend.servicePort) - }) + routes.push(protocol + "://" + host + (path.path || "/") + " ⇢ " + path.backend.serviceName + ":" + path.backend.servicePort); + }); } - }) + }); return routes; } - getHosts() { - const { spec: { rules } } = this - if (!rules) return [] - return rules.filter(rule => rule.host).map(rule => rule.host) + getHosts(): string[] { + const { spec: { rules } } = this; + if (!rules) { + return []; + } + return rules.filter(rule => rule.host).map(rule => rule.host); } - getPorts() { - const ports: number[] = [] - const { spec: { tls, rules, backend } } = this - const httpPort = 80 - const tlsPort = 443 + getPorts(): string { + const ports: number[] = []; + const { spec: { tls, rules, backend } } = this; + const httpPort = 80; + const tlsPort = 443; if (rules && rules.length > 0) { if (rules.some(rule => rule.hasOwnProperty("http"))) { - ports.push(httpPort) + ports.push(httpPort); } - } - else { + } else { if (backend && backend.servicePort) { - ports.push(backend.servicePort) + ports.push(backend.servicePort); } } if (tls && tls.length > 0) { - ports.push(tlsPort) + ports.push(tlsPort); } - return ports.join(", ") + return ports.join(", "); } } diff --git a/dashboard/client/api/endpoints/job.api.ts b/dashboard/client/api/endpoints/job.api.ts index d4657605f6..d02a2833dc 100644 --- a/dashboard/client/api/endpoints/job.api.ts +++ b/dashboard/client/api/endpoints/job.api.ts @@ -1,9 +1,19 @@ import get from "lodash/get"; import { autobind } from "../../utils"; -import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; -import { IPodContainer } from "./pods.api"; +import { Affinity, WorkloadKubeObject } from "../workload-kube-object"; +import { PodContainer } from "./pods.api"; import { KubeApi } from "../kube-api"; import { JsonApiParams } from "../json-api"; +import { CancelablePromise } from "client/utils/cancelableFetch"; +import { KubeJsonApiData } from "../kube-json-api"; + +export interface JobCondition { + type: string; + status: string; + lastProbeTime: string; + lastTransitionTime: string; + message?: string; +} @autobind() export class Job extends WorkloadKubeObject { @@ -26,12 +36,12 @@ export class Job extends WorkloadKubeObject { }; }; spec: { - containers: IPodContainer[]; + containers: PodContainer[]; restartPolicy: string; terminationGracePeriodSeconds: number; dnsPolicy: string; hostPID: boolean; - affinity?: IAffinity; + affinity?: Affinity; nodeSelector?: { [selector: string]: string; }; @@ -44,7 +54,7 @@ export class Job extends WorkloadKubeObject { schedulerName: string; }; }; - containers?: IPodContainer[]; + containers?: PodContainer[]; restartPolicy?: string; terminationGracePeriodSeconds?: number; dnsPolicy?: string; @@ -53,48 +63,36 @@ export class Job extends WorkloadKubeObject { schedulerName?: string; } status: { - conditions: { - type: string; - status: string; - lastProbeTime: string; - lastTransitionTime: string; - message?: string; - }[]; + conditions: JobCondition[]; startTime: string; completionTime: string; succeeded: number; } - getDesiredCompletions() { + getDesiredCompletions(): number { return this.spec.completions || 0; } - getCompletions() { + getCompletions(): number { return this.status.succeeded || 0; } - getParallelism() { - return this.spec.parallelism; - } - - getCondition() { + getCondition(): JobCondition { // Type of Job condition could be only Complete or Failed // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.14/#jobcondition-v1-batch - const { conditions } = this.status; - if (!conditions) return; - return conditions.find(({ status }) => status === "True"); + return this.status.conditions.find(({ status }) => status === "True"); } - getImages() { - const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []) - return [...containers].map(container => container.image) + getImages(): string[] { + const containers: PodContainer[] = get(this, "spec.template.spec.containers", []); + return containers.map(container => container.image); } - delete() { + delete(): CancelablePromise { const params: JsonApiParams = { query: { propagationPolicy: "Background" } - } - return super.delete(params) + }; + return super.delete(params); } } diff --git a/dashboard/client/api/endpoints/kubeconfig.api.ts b/dashboard/client/api/endpoints/kubeconfig.api.ts index c6476badf4..5011e459de 100644 --- a/dashboard/client/api/endpoints/kubeconfig.api.ts +++ b/dashboard/client/api/endpoints/kubeconfig.api.ts @@ -1,12 +1,14 @@ // Kubeconfig api import { apiBase } from "../index"; +import { CancelablePromise } from "client/utils/cancelableFetch"; +import { JsonApiData } from "../json-api"; export const kubeConfigApi = { - getUserConfig() { + getUserConfig(): CancelablePromise { return apiBase.get("/kubeconfig/user"); }, - getServiceAccountConfig(account: string, namespace: string) { + getServiceAccountConfig(account: string, namespace: string): CancelablePromise { return apiBase.get(`/kubeconfig/service-account/${namespace}/${account}`); }, }; diff --git a/dashboard/client/api/endpoints/metrics.api.ts b/dashboard/client/api/endpoints/metrics.api.ts index ee011160bf..2d3628cd4c 100644 --- a/dashboard/client/api/endpoints/metrics.api.ts +++ b/dashboard/client/api/endpoints/metrics.api.ts @@ -4,15 +4,15 @@ import moment from "moment"; import { apiBase } from "../index"; import { IMetricsQuery } from "../../../server/common/metrics"; -export interface IMetrics { +export interface Metrics { status: string; data: { resultType: string; - result: IMetricsResult[]; + result: MetricsResult[]; }; } -export interface IMetricsResult { +export interface MetricsResult { metric: { [name: string]: string; instance: string; @@ -25,7 +25,7 @@ export interface IMetricsResult { values: [number, string][]; } -export interface IMetricsReqParams { +export interface MetricsReqParams { start?: number | string; // timestamp in seconds or valid date-string end?: number | string; step?: number; // step in seconds (default: 60s = each point 1m) @@ -34,7 +34,7 @@ export interface IMetricsReqParams { } export const metricsApi = { - async getMetrics(query: T, reqParams: IMetricsReqParams = {}): Promise { + async getMetrics(query: T, reqParams: MetricsReqParams = {}): Promise { const { range = 3600, step = 60, namespace } = reqParams; let { start, end } = reqParams; @@ -55,18 +55,20 @@ export const metricsApi = { }, }; -export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics { +export function normalizeMetrics(metrics: Metrics, frames = 60): Metrics { if (!metrics?.data?.result) { return { data: { resultType: "", result: [{ - metric: {}, + metric: { + instance: "", + }, values: [] - } as IMetricsResult], + }], }, status: "", - } + }; } const { result } = metrics.data; @@ -75,34 +77,40 @@ export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics { if (frames > 0) { // fill the gaps result.forEach(res => { - if (!res.values || !res.values.length) return; + if (!res.values || !res.values.length) { + return; + } while (res.values.length < frames) { const timestamp = moment.unix(res.values[0][0]).subtract(1, "minute").unix(); - res.values.unshift([timestamp, "0"]) + res.values.unshift([timestamp, "0"]); } }); } } else { // always return at least empty values array result.push({ - metric: {}, + metric: { + instance: "", + }, values: [] - } as IMetricsResult); + }); } return metrics; } -export function isMetricsEmpty(metrics: { [key: string]: IMetrics }) { +export function isMetricsEmpty(metrics: { [key: string]: Metrics }): boolean { return Object.values(metrics).every(metric => !metric?.data?.result?.length); } -export function getItemMetrics(metrics: { [key: string]: IMetrics }, itemName: string): { [key: string]: IMetrics } { - if (!metrics) return; +export function getItemMetrics(metrics: { [key: string]: Metrics }, itemName: string): { [key: string]: Metrics } { + if (!metrics) { + return; + } const itemMetrics = { ...metrics }; for (const metric in metrics) { if (!metrics[metric]?.data?.result) { - continue + continue; } const results = metrics[metric].data.result; const result = results.find(res => Object.values(res.metric)[0] == itemName); @@ -111,7 +119,7 @@ export function getItemMetrics(metrics: { [key: string]: IMetrics }, itemName: s return itemMetrics; } -export function getMetricLastPoints(metrics: { [key: string]: IMetrics }) { +export function getMetricLastPoints(metrics: { [key: string]: Metrics }): { [metric: string]: number } { const result: Partial<{[metric: string]: number}> = {}; Object.keys(metrics).forEach(metricName => { diff --git a/dashboard/client/api/endpoints/namespaces.api.ts b/dashboard/client/api/endpoints/namespaces.api.ts index 96e14b4235..242efd1ea2 100644 --- a/dashboard/client/api/endpoints/namespaces.api.ts +++ b/dashboard/client/api/endpoints/namespaces.api.ts @@ -15,7 +15,7 @@ export class Namespace extends KubeObject { phase: string; } - getStatus() { + getStatus(): string { return this.status ? this.status.phase : "-"; } } diff --git a/dashboard/client/api/endpoints/network-policy.api.ts b/dashboard/client/api/endpoints/network-policy.api.ts index 57c6d1fae4..0903e78982 100644 --- a/dashboard/client/api/endpoints/network-policy.api.ts +++ b/dashboard/client/api/endpoints/network-policy.api.ts @@ -2,22 +2,22 @@ import { KubeObject } from "../kube-object"; import { autobind } from "../../utils"; import { KubeApi } from "../kube-api"; -export interface IPolicyIpBlock { +export interface PolicyIpBlock { cidr: string; except?: string[]; } -export interface IPolicySelector { +export interface PolicySelector { matchLabels: { [label: string]: string; }; } -export interface IPolicyIngress { +export interface PolicyIngress { from: { - ipBlock?: IPolicyIpBlock; - namespaceSelector?: IPolicySelector; - podSelector?: IPolicySelector; + ipBlock?: PolicyIpBlock; + namespaceSelector?: PolicySelector; + podSelector?: PolicySelector; }[]; ports: { protocol: string; @@ -25,9 +25,9 @@ export interface IPolicyIngress { }[]; } -export interface IPolicyEgress { +export interface PolicyEgress { to: { - ipBlock: IPolicyIpBlock; + ipBlock: PolicyIpBlock; }[]; ports: { protocol: string; @@ -47,19 +47,23 @@ export class NetworkPolicy extends KubeObject { }; }; policyTypes: string[]; - ingress: IPolicyIngress[]; - egress: IPolicyEgress[]; + ingress: PolicyIngress[]; + egress: PolicyEgress[]; } getMatchLabels(): string[] { - if (!this.spec.podSelector || !this.spec.podSelector.matchLabels) return []; + if (!this.spec.podSelector || !this.spec.podSelector.matchLabels) { + return []; + } return Object .entries(this.spec.podSelector.matchLabels) - .map(data => data.join(":")) + .map(data => data.join(":")); } getTypes(): string[] { - if (!this.spec.policyTypes) return []; + if (!this.spec.policyTypes) { + return []; + } return this.spec.policyTypes; } } diff --git a/dashboard/client/api/endpoints/nodes.api.ts b/dashboard/client/api/endpoints/nodes.api.ts index 23b42100bb..d5ba2276dc 100644 --- a/dashboard/client/api/endpoints/nodes.api.ts +++ b/dashboard/client/api/endpoints/nodes.api.ts @@ -1,11 +1,11 @@ import { KubeObject } from "../kube-object"; import { autobind, cpuUnitsToNumber, unitsToBytes } from "../../utils"; -import { IMetrics, metricsApi } from "./metrics.api"; +import { Metrics, metricsApi } from "./metrics.api"; import { KubeApi } from "../kube-api"; export class NodesApi extends KubeApi { - getMetrics(): Promise { - const opts = { category: "nodes"} + getMetrics(): Promise { + const opts = { category: "nodes"}; return metricsApi.getMetrics({ memoryUsage: opts, @@ -18,7 +18,7 @@ export class NodesApi extends KubeApi { } } -export interface INodeMetrics { +export interface NodeMetrics { [metric: string]: T; memoryUsage: T; memoryCapacity: T; @@ -28,6 +28,21 @@ export interface INodeMetrics { fsSize: T; } +export interface NodeTaint { + key: string; + value: string; + effect: string; +} + +export interface NodeConditions { + type: string; + status?: string; + lastHeartbeatTime?: string; + lastTransitionTime?: string; + reason?: string; + message?: string; +} + @autobind() export class Node extends KubeObject { static kind = "Node" @@ -35,11 +50,7 @@ export class Node extends KubeObject { spec: { podCIDR: string; externalID: string; - taints?: { - key: string; - value: string; - effect: string; - }[]; + taints?: NodeTaint[]; unschedulable?: boolean; } status: { @@ -53,14 +64,7 @@ export class Node extends KubeObject { memory: string; pods: string; }; - conditions: { - type: string; - status?: string; - lastHeartbeatTime?: string; - lastTransitionTime?: string; - reason?: string; - message?: string; - }[]; + conditions: NodeConditions[]; addresses: { type: string; address: string; @@ -83,75 +87,75 @@ export class Node extends KubeObject { }[]; } - getNodeConditionText() { - const { conditions } = this.status - if (!conditions) return "" - return conditions.reduce((types, current) => { - if (current.status !== "True") return "" - return types += ` ${current.type}` - }, "") + getNodeConditionText(): string { + return this.status + .conditions + .filter(({status}) => status === "True") + .map(({type}) => type) + .join(" "); } - getTaints() { + getTaints(): NodeTaint[] { return this.spec.taints || []; } - getRoleLabels() { + getRoleLabels(): string { const roleLabels = Object.keys(this.metadata.labels).filter(key => key.includes("node-role.kubernetes.io") - ).map(key => key.match(/([^/]+$)/)[0]) // all after last slash + ).map(key => key.match(/([^/]+$)/)[0]); // all after last slash if (this.metadata.labels["kubernetes.io/role"] != undefined) { - roleLabels.push(this.metadata.labels["kubernetes.io/role"]) + roleLabels.push(this.metadata.labels["kubernetes.io/role"]); } - return roleLabels.join(", ") + return roleLabels.join(", "); } - getCpuCapacity() { - if (!this.status.capacity || !this.status.capacity.cpu) return 0 - return cpuUnitsToNumber(this.status.capacity.cpu) + getCpuCapacity(): number { + if (!this.status.capacity || !this.status.capacity.cpu) { + return 0; + } + return cpuUnitsToNumber(this.status.capacity.cpu); } - getMemoryCapacity() { - if (!this.status.capacity || !this.status.capacity.memory) return 0 - return unitsToBytes(this.status.capacity.memory) + getMemoryCapacity(): number { + if (!this.status.capacity || !this.status.capacity.memory) { + return 0; + } + return unitsToBytes(this.status.capacity.memory); } - getConditions() { - const conditions = this.status.conditions || []; + getConditions(): NodeConditions[] { if (this.isUnschedulable()) { - return [{ type: "SchedulingDisabled", status: "True" }, ...conditions]; + return [{ type: "SchedulingDisabled", status: "True" }, ...this.status.conditions]; } - return conditions; + return this.status.conditions; } - getActiveConditions() { + getActiveConditions(): NodeConditions[] { return this.getConditions().filter(c => c.status === "True"); } - getWarningConditions() { - const goodConditions = ["Ready", "HostUpgrades", "SchedulingDisabled"]; - return this.getActiveConditions().filter(condition => { - return !goodConditions.includes(condition.type); - }); + getWarningConditions(): NodeConditions[] { + const goodConditions = new Set(["Ready", "HostUpgrades", "SchedulingDisabled"]); + return this.getActiveConditions().filter(condition => !goodConditions.has(condition.type)); } - getKubeletVersion() { + getKubeletVersion(): string { return this.status.nodeInfo.kubeletVersion; } getOperatingSystem(): string { - const label = this.getLabels().find(label => label.startsWith("kubernetes.io/os=")) + const label = this.getLabels().find(label => label.startsWith("kubernetes.io/os=")); if (label) { - return label.split("=", 2)[1] + return label.split("=", 2)[1]; } - return "linux" + return "linux"; } - isUnschedulable() { - return this.spec.unschedulable + isUnschedulable(): boolean { + return this.spec.unschedulable; } } diff --git a/dashboard/client/api/endpoints/persistent-volume-claims.api.ts b/dashboard/client/api/endpoints/persistent-volume-claims.api.ts index f6464cdf67..be79e95570 100644 --- a/dashboard/client/api/endpoints/persistent-volume-claims.api.ts +++ b/dashboard/client/api/endpoints/persistent-volume-claims.api.ts @@ -1,11 +1,12 @@ import { KubeObject } from "../kube-object"; import { autobind } from "../../utils"; -import { IMetrics, metricsApi } from "./metrics.api"; +import { MatchExpression } from "../workload-kube-object"; +import { Metrics, metricsApi } from "./metrics.api"; import { Pod } from "./pods.api"; import { KubeApi } from "../kube-api"; export class PersistentVolumeClaimsApi extends KubeApi { - getMetrics(pvcName: string, namespace: string): Promise { + getMetrics(pvcName: string, namespace: string): Promise { return metricsApi.getMetrics({ diskUsage: { category: 'pvc', pvc: pvcName }, diskCapacity: { category: 'pvc', pvc: pvcName } @@ -15,7 +16,7 @@ export class PersistentVolumeClaimsApi extends KubeApi { } } -export interface IPvcMetrics { +export interface PvcMetrics { [key: string]: T; diskUsage: T; diskCapacity: T; @@ -32,11 +33,7 @@ export class PersistentVolumeClaim extends KubeObject { matchLabels: { release: string; }; - matchExpressions: { - key: string; // environment, - operator: string; // In, - values: string[]; // [dev] - }[]; + matchExpressions: MatchExpression[]; }; resources: { requests: { @@ -49,34 +46,39 @@ export class PersistentVolumeClaim extends KubeObject { } getPods(allPods: Pod[]): Pod[] { - const pods = allPods.filter(pod => pod.getNs() === this.getNs()) + const pods = allPods.filter(pod => pod.getNs() === this.getNs()); return pods.filter(pod => { return pod.getVolumes().filter(volume => volume.persistentVolumeClaim && volume.persistentVolumeClaim.claimName === this.getName() - ).length > 0 - }) + ).length > 0; + }); } getStorage(): string { - if (!this.spec.resources || !this.spec.resources.requests) return "-"; + if (!this.spec.resources || !this.spec.resources.requests) { + return "-"; + } return this.spec.resources.requests.storage; } getMatchLabels(): string[] { - if (!this.spec.selector || !this.spec.selector.matchLabels) return []; + if (!this.spec.selector || !this.spec.selector.matchLabels) { + return []; + } return Object.entries(this.spec.selector.matchLabels) .map(([name, val]) => `${name}:${val}`); } - getMatchExpressions() { - if (!this.spec.selector || !this.spec.selector.matchExpressions) return []; - return this.spec.selector.matchExpressions; + getMatchExpressions(): MatchExpression[] { + return this.spec.selector?.matchExpressions; } getStatus(): string { - if (this.status) return this.status.phase; - return "-" + if (this.status) { + return this.status.phase; + } + return "-"; } } diff --git a/dashboard/client/api/endpoints/persistent-volume.api.ts b/dashboard/client/api/endpoints/persistent-volume.api.ts index 317ed1a726..3f275d9bf3 100644 --- a/dashboard/client/api/endpoints/persistent-volume.api.ts +++ b/dashboard/client/api/endpoints/persistent-volume.api.ts @@ -43,23 +43,23 @@ export class PersistentVolume extends KubeObject { reason?: string; } - getCapacity(inBytes = false) { + getCapacity(inBytes = false): number | string { const capacity = this.spec.capacity; if (capacity) { - if (inBytes) return unitsToBytes(capacity.storage) + if (inBytes) { + return unitsToBytes(capacity.storage); + } return capacity.storage; } return 0; } - getStatus() { - if (!this.status) return; - return this.status.phase || "-"; + getStatus(): string { + return this?.status.phase || "-"; } - getClaimRefName() { - const { claimRef } = this.spec; - return claimRef ? claimRef.name : ""; + getClaimRefName(): string { + return this.spec.claimRef.name; } } diff --git a/dashboard/client/api/endpoints/pods.api.ts b/dashboard/client/api/endpoints/pods.api.ts index 2cc1ebf1a7..910b0f8d56 100644 --- a/dashboard/client/api/endpoints/pods.api.ts +++ b/dashboard/client/api/endpoints/pods.api.ts @@ -1,17 +1,17 @@ -import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; +import { Affinity, WorkloadKubeObject } from "../workload-kube-object"; import { autobind } from "../../utils"; -import { IMetrics, metricsApi } from "./metrics.api"; +import { Metrics, metricsApi } from "./metrics.api"; import { KubeApi } from "../kube-api"; export class PodsApi extends KubeApi { - async getLogs(params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise { + async getLogs(params: { namespace: string; name: string }, query?: PodLogsQuery): Promise { const path = this.getUrl(params) + "/log"; return this.request.get(path, { query }); } - getMetrics(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise { + getMetrics(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise { const podSelector = pods.map(pod => pod.getName()).join("|"); - const opts = { category: "pods", pods: podSelector, namespace, selector } + const opts = { category: "pods", pods: podSelector, namespace, selector }; return metricsApi.getMetrics({ cpuUsage: opts, @@ -29,7 +29,7 @@ export class PodsApi extends KubeApi { } } -export interface IPodMetrics { +export interface PodMetricsData { [metric: string]: T; cpuUsage: T; cpuRequests: T; @@ -42,7 +42,7 @@ export interface IPodMetrics { networkTransmit: T; } -export interface IPodLogsQuery { +export interface PodLogsQuery { container?: string; tailLines?: number; timestamps?: boolean; @@ -58,7 +58,7 @@ export enum PodStatus { EVICTED = "Evicted" } -export interface IPodContainer { +export interface PodContainer { name: string; image: string; command?: string[]; @@ -106,12 +106,12 @@ export interface IPodContainer { readOnly: boolean; mountPath: string; }[]; - livenessProbe?: IContainerProbe; - readinessProbe?: IContainerProbe; + livenessProbe?: ContainerProbe; + readinessProbe?: ContainerProbe; imagePullPolicy: string; } -interface IContainerProbe { +interface ContainerProbe { httpGet?: { path?: string; port: number; @@ -131,7 +131,7 @@ interface IContainerProbe { failureThreshold?: number; } -export interface IPodContainerStatus { +export interface PodContainerStatus { name: string; state: { [index: string]: object; @@ -173,14 +173,14 @@ export class Pod extends WorkloadKubeObject { }; configMap: { name: string; - } + }; secret: { secretName: string; defaultMode: number; }; }[]; - initContainers: IPodContainer[]; - containers: IPodContainer[]; + initContainers: PodContainer[]; + containers: PodContainer[]; restartPolicy: string; terminationGracePeriodSeconds: number; dnsPolicy: string; @@ -200,7 +200,7 @@ export class Pod extends WorkloadKubeObject { effect: string; tolerationSeconds: number; }[]; - affinity: IAffinity; + affinity: Affinity; } status: { phase: string; @@ -213,34 +213,29 @@ export class Pod extends WorkloadKubeObject { hostIP: string; podIP: string; startTime: string; - initContainerStatuses?: IPodContainerStatus[]; - containerStatuses?: IPodContainerStatus[]; + initContainerStatuses?: PodContainerStatus[]; + containerStatuses?: PodContainerStatus[]; qosClass: string; reason?: string; } - getInitContainers() { - return this.spec.initContainers || []; + getAllContainers(): PodContainer[] { + return this.spec.containers.concat(this.spec.initContainers); } - getContainers() { - return this.spec.containers || []; + getRunningContainers(): PodContainer[] { + const activeContainers = new Set( + this.getContainerStatuses() + .filter(({ state }) => !!state.running) + .map(({ name }) => name) + ); + + return this.getAllContainers() + .filter(({ name }) => activeContainers.has(name)); } - getAllContainers() { - return this.getContainers().concat(this.getInitContainers()); - } - - getRunningContainers() { - const statuses = this.getContainerStatuses() - return this.getAllContainers().filter(container => { - return statuses.find(status => status.name === container.name && !!status.state["running"]) - } - ) - } - - getContainerStatuses(includeInitContainers = true) { - const statuses: IPodContainerStatus[] = []; + getContainerStatuses(includeInitContainers = true): PodContainerStatus[] { + const statuses: PodContainerStatus[] = []; const { containerStatuses, initContainerStatuses } = this.status; if (containerStatuses) { statuses.push(...containerStatuses); @@ -253,28 +248,22 @@ export class Pod extends WorkloadKubeObject { getRestartsCount(): number { const { containerStatuses } = this.status; - if (!containerStatuses) return 0; + if (!containerStatuses) { + return 0; + } return containerStatuses.reduce((count, item) => count + item.restartCount, 0); } - getQosClass() { - return this.status.qosClass || ""; - } - - getReason() { + getReason(): string { return this.status.reason || ""; } - getPriorityClassName() { - return this.spec.priorityClassName || ""; - } - // Returns one of 5 statuses: Running, Succeeded, Pending, Failed, Evicted - getStatus() { - const phase = this.getStatusPhase(); + getStatus(): PodStatus { + const phase = this.status.phase; const reason = this.getReason(); const goodConditions = ["Initialized", "Ready"].every(condition => - !!this.getConditions().find(item => item.type === condition && item.status === "True") + !!this.status.conditions.find(item => item.type === condition && item.status === "True") ); if (reason === PodStatus.EVICTED) { return PodStatus.EVICTED; @@ -292,9 +281,13 @@ export class Pod extends WorkloadKubeObject { } // Returns pod phase or container error if occured - getStatusMessage() { - if (this.getReason() === PodStatus.EVICTED) return "Evicted"; - if (this.getStatus() === PodStatus.RUNNING && this.metadata.deletionTimestamp) return "Terminating"; + getStatusMessage(): string { + if (this.getReason() === PodStatus.EVICTED) { + return "Evicted"; + } + if (this.getStatus() === PodStatus.RUNNING && this.metadata.deletionTimestamp) { + return "Terminating"; + } let message = ""; const statuses = this.getContainerStatuses(false); // not including initContainers @@ -309,74 +302,61 @@ export class Pod extends WorkloadKubeObject { const { reason } = state.terminated; message = reason ? reason : "Terminated"; } - }) + }); } - if (message) return message; - return this.getStatusPhase(); - } - - getStatusPhase() { + if (message) { + return message; + } + return this.status.phase; } - getConditions() { - return this.status.conditions || []; - } - - getVolumes() { - return this.spec.volumes || []; - } - getSecrets(): string[] { - return this.getVolumes() + return this.spec.volumes .filter(vol => vol.secret) .map(vol => vol.secret.secretName); } getNodeSelectors(): string[] { - const { nodeSelector } = this.spec - if (!nodeSelector) return [] - return Object.entries(nodeSelector).map(values => values.join(": ")) + const { nodeSelector } = this.spec; + if (!nodeSelector) { + return []; + } + return Object.entries(nodeSelector).map(values => values.join(": ")); } - getTolerations() { - return this.spec.tolerations || [] - } - - getAffinity(): IAffinity { - return this.spec.affinity - } - - hasIssues() { - const notReady = !!this.getConditions().find(condition => { - return condition.type == "Ready" && condition.status !== "True" + hasIssues(): boolean { + const notReady = !!this.status.conditions.find(condition => { + return condition.type == "Ready" && condition.status !== "True"; }); const crashLoop = !!this.getContainerStatuses().find(condition => { - const waiting = condition.state.waiting - return (waiting && waiting.reason == "CrashLoopBackOff") - }) + const waiting = condition.state.waiting; + return (waiting && waiting.reason == "CrashLoopBackOff"); + }); return ( notReady || crashLoop || - this.getStatusPhase() !== "Running" - ) + this.status.phase !== "Running" + ); } - getLivenessProbe(container: IPodContainer) { + getLivenessProbe(container: PodContainer): string[] { return this.getProbe(container.livenessProbe); } - getReadinessProbe(container: IPodContainer) { + getReadinessProbe(container: PodContainer): string[] { return this.getProbe(container.readinessProbe); } - getProbe(probeData: IContainerProbe) { - if (!probeData) return []; + getProbe(probeData: ContainerProbe): string[] { + if (!probeData) { + return []; + } const { httpGet, exec, tcpSocket, initialDelaySeconds, timeoutSeconds, periodSeconds, successThreshold, failureThreshold } = probeData; - const probe = []; + const probe: string[] = []; // HTTP Request if (httpGet) { const { path, port, host, scheme } = httpGet; @@ -403,15 +383,8 @@ export class Pod extends WorkloadKubeObject { return probe; } - getNodeName() { - return this.spec?.nodeName - } - - getSelectedNodeOs() { - if (!this.spec.nodeSelector) return - if (!this.spec.nodeSelector["kubernetes.io/os"] && !this.spec.nodeSelector["beta.kubernetes.io/os"]) return - - return this.spec.nodeSelector["kubernetes.io/os"] || this.spec.nodeSelector["beta.kubernetes.io/os"] + getSelectedNodeOs(): string | null { + return this.spec?.nodeSelector?.["kubernetes.io/os"] || this.spec?.nodeSelector?.["beta.kubernetes.io/os"] || null; } } diff --git a/dashboard/client/api/endpoints/podsecuritypolicy.api.ts b/dashboard/client/api/endpoints/podsecuritypolicy.api.ts index 4f1e807ba5..661e953108 100644 --- a/dashboard/client/api/endpoints/podsecuritypolicy.api.ts +++ b/dashboard/client/api/endpoints/podsecuritypolicy.api.ts @@ -2,6 +2,14 @@ import { autobind } from "../../utils"; import { KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; +export interface Rules { + fsGroup: string; + runAsGroup: string; + runAsUser: string; + supplementalGroups: string; + seLinux: string; +} + @autobind() export class PodSecurityPolicy extends KubeObject { static kind = "PodSecurityPolicy" @@ -66,22 +74,22 @@ export class PodSecurityPolicy extends KubeObject { volumes?: string[]; } - isPrivileged() { + isPrivileged(): boolean { return !!this.spec.privileged; } - getVolumes() { + getVolumes(): string[] { return this.spec.volumes || []; } - getRules() { + getRules(): Rules { const { fsGroup, runAsGroup, runAsUser, supplementalGroups, seLinux } = this.spec; return { - fsGroup: fsGroup ? fsGroup.rule : "", - runAsGroup: runAsGroup ? runAsGroup.rule : "", - runAsUser: runAsUser ? runAsUser.rule : "", - supplementalGroups: supplementalGroups ? supplementalGroups.rule : "", - seLinux: seLinux ? seLinux.rule : "", + fsGroup: fsGroup?.rule || "", + runAsGroup: runAsGroup?.rule || "", + runAsUser: runAsUser?.rule || "", + supplementalGroups: supplementalGroups?.rule || "", + seLinux: seLinux?.rule || "", }; } } diff --git a/dashboard/client/api/endpoints/replica-set.api.ts b/dashboard/client/api/endpoints/replica-set.api.ts index d8ac690ab6..e1444a2b6b 100644 --- a/dashboard/client/api/endpoints/replica-set.api.ts +++ b/dashboard/client/api/endpoints/replica-set.api.ts @@ -1,7 +1,7 @@ import get from "lodash/get"; import { autobind } from "../../utils"; -import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; -import { IPodContainer } from "./pods.api"; +import { Affinity, WorkloadKubeObject } from "../workload-kube-object"; +import { PodContainer } from "./pods.api"; import { KubeApi } from "../kube-api"; @autobind() @@ -15,10 +15,10 @@ export class ReplicaSet extends WorkloadKubeObject { [key: string]: string; }; }; - containers?: IPodContainer[]; + containers?: PodContainer[]; template?: { spec?: { - affinity?: IAffinity; + affinity?: Affinity; nodeSelector?: { [selector: string]: string; }; @@ -28,7 +28,7 @@ export class ReplicaSet extends WorkloadKubeObject { effect: string; tolerationSeconds: number; }[]; - containers: IPodContainer[]; + containers: PodContainer[]; }; }; restartPolicy?: string; @@ -44,9 +44,9 @@ export class ReplicaSet extends WorkloadKubeObject { observedGeneration: number; } - getImages() { - const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []) - return [...containers].map(container => container.image) + getImages(): string[] { + const containers: PodContainer[] = get(this, "spec.template.spec.containers", []); + return containers.map(container => container.image); } } diff --git a/dashboard/client/api/endpoints/resource-applier.api.ts b/dashboard/client/api/endpoints/resource-applier.api.ts index ffa2852b5e..ea27f94eef 100644 --- a/dashboard/client/api/endpoints/resource-applier.api.ts +++ b/dashboard/client/api/endpoints/resource-applier.api.ts @@ -1,4 +1,4 @@ -import jsYaml from "js-yaml" +import jsYaml from "js-yaml"; import { KubeObject } from "../kube-object"; import { KubeJsonApiData } from "../kube-json-api"; import { apiKubeResourceApplier } from "../index"; diff --git a/dashboard/client/api/endpoints/resource-quota.api.ts b/dashboard/client/api/endpoints/resource-quota.api.ts index 337834f5b0..d9507c4b47 100644 --- a/dashboard/client/api/endpoints/resource-quota.api.ts +++ b/dashboard/client/api/endpoints/resource-quota.api.ts @@ -2,7 +2,7 @@ import { KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; import { KubeJsonApiData } from "../kube-json-api"; -export interface IResourceQuotaValues { +export interface ResourceQuotaValues { [quota: string]: string; // Compute Resource Quota @@ -30,33 +30,34 @@ export interface IResourceQuotaValues { "count/deployments.extensions"?: string; } +interface MatchExpression { + operator: string; + scopeName: string; + values: string[]; +} + export class ResourceQuota extends KubeObject { static kind = "ResourceQuota" constructor(data: KubeJsonApiData) { super(data); - this.spec = this.spec || {} as any + this.spec = this.spec || {} as any; } spec: { - hard: IResourceQuotaValues; + hard: ResourceQuotaValues; scopeSelector?: { - matchExpressions: { - operator: string; - scopeName: string; - values: string[]; - }[]; + matchExpressions: MatchExpression[]; }; } status: { - hard: IResourceQuotaValues; - used: IResourceQuotaValues; + hard: ResourceQuotaValues; + used: ResourceQuotaValues; } - getScopeSelector() { - const { matchExpressions = [] } = this.spec.scopeSelector || {}; - return matchExpressions; + getScopeSelector(): MatchExpression[] { + return this.spec?.scopeSelector?.matchExpressions; } } diff --git a/dashboard/client/api/endpoints/role-binding.api.ts b/dashboard/client/api/endpoints/role-binding.api.ts index 438c07f9db..7333706719 100644 --- a/dashboard/client/api/endpoints/role-binding.api.ts +++ b/dashboard/client/api/endpoints/role-binding.api.ts @@ -2,7 +2,7 @@ import { autobind } from "../../utils"; import { KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; -export interface IRoleBindingSubject { +export interface RoleBindingSubject { kind: string; name: string; namespace?: string; @@ -13,19 +13,19 @@ export interface IRoleBindingSubject { export class RoleBinding extends KubeObject { static kind = "RoleBinding" - subjects?: IRoleBindingSubject[] + subjects?: RoleBindingSubject[] roleRef: { kind: string; name: string; apiGroup?: string; } - getSubjects() { + getSubjects(): RoleBindingSubject[] { return this.subjects || []; } getSubjectNames(): string { - return this.getSubjects().map(subject => subject.name).join(", ") + return this.getSubjects().map(subject => subject.name).join(", "); } } diff --git a/dashboard/client/api/endpoints/role.api.ts b/dashboard/client/api/endpoints/role.api.ts index e59f9cc273..20277a8347 100644 --- a/dashboard/client/api/endpoints/role.api.ts +++ b/dashboard/client/api/endpoints/role.api.ts @@ -1,18 +1,20 @@ import { KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; +export interface Rule { + verbs: string[]; + apiGroups: string[]; + resources: string[]; + resourceNames?: string[]; +} + export class Role extends KubeObject { static kind = "Role" - rules: { - verbs: string[]; - apiGroups: string[]; - resources: string[]; - resourceNames?: string[]; - }[] + rules: Rule[] - getRules() { - return this.rules || []; + getRules(): Rule[] { + return this.rules; } } diff --git a/dashboard/client/api/endpoints/secret.api.ts b/dashboard/client/api/endpoints/secret.api.ts index 884552c14e..845549e2f4 100644 --- a/dashboard/client/api/endpoints/secret.api.ts +++ b/dashboard/client/api/endpoints/secret.api.ts @@ -14,7 +14,7 @@ export enum SecretType { BootstrapToken = "bootstrap.kubernetes.io/token", } -export interface ISecretRef { +export interface SecretRef { key?: string; name: string; } @@ -37,10 +37,6 @@ export class Secret extends KubeObject { getKeys(): string[] { return Object.keys(this.data); } - - getToken() { - return this.data.token; - } } export const secretsApi = new KubeApi({ diff --git a/dashboard/client/api/endpoints/selfsubjectrulesreviews.api.ts b/dashboard/client/api/endpoints/selfsubjectrulesreviews.api.ts index 755dcfa54b..231274b229 100644 --- a/dashboard/client/api/endpoints/selfsubjectrulesreviews.api.ts +++ b/dashboard/client/api/endpoints/selfsubjectrulesreviews.api.ts @@ -12,7 +12,7 @@ export class SelfSubjectRulesReviewApi extends KubeApi { } } -export interface ISelfSubjectReviewRule { +export interface SelfSubjectReviewRule { verbs: string[]; apiGroups?: string[]; resources?: string[]; @@ -29,22 +29,22 @@ export class SelfSubjectRulesReview extends KubeObject { } status: { - resourceRules: ISelfSubjectReviewRule[]; - nonResourceRules: ISelfSubjectReviewRule[]; + resourceRules: SelfSubjectReviewRule[]; + nonResourceRules: SelfSubjectReviewRule[]; incomplete: boolean; } - getResourceRules() { + getResourceRules(): SelfSubjectReviewRule[] { const rules = this.status && this.status.resourceRules || []; return rules.map(rule => this.normalize(rule)); } - getNonResourceRules() { + getNonResourceRules(): SelfSubjectReviewRule[] { const rules = this.status && this.status.nonResourceRules || []; return rules.map(rule => this.normalize(rule)); } - protected normalize(rule: ISelfSubjectReviewRule): ISelfSubjectReviewRule { + protected normalize(rule: SelfSubjectReviewRule): SelfSubjectReviewRule { const { apiGroups = [], resourceNames = [], verbs = [], nonResourceURLs = [], resources = [] } = rule; return { apiGroups, @@ -56,7 +56,7 @@ export class SelfSubjectRulesReview extends KubeObject { const separator = apiGroup == "" ? "" : "."; return resource + separator + apiGroup; }) - } + }; } } diff --git a/dashboard/client/api/endpoints/service-accounts.api.ts b/dashboard/client/api/endpoints/service-accounts.api.ts index 94c3a13673..294e423827 100644 --- a/dashboard/client/api/endpoints/service-accounts.api.ts +++ b/dashboard/client/api/endpoints/service-accounts.api.ts @@ -2,22 +2,26 @@ import { autobind } from "../../utils"; import { KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; +export interface ImagePullSecret { + name: string; +} + +export interface Secret { + name: string; +} + @autobind() export class ServiceAccount extends KubeObject { static kind = "ServiceAccount"; - secrets?: { - name: string; - }[] - imagePullSecrets?: { - name: string; - }[] + secrets?: Secret[] + imagePullSecrets?: ImagePullSecret[] - getSecrets() { + getSecrets(): Secret[] { return this.secrets || []; } - getImagePullSecrets() { + getImagePullSecrets(): ImagePullSecret[] { return this.imagePullSecrets || []; } } diff --git a/dashboard/client/api/endpoints/service.api.ts b/dashboard/client/api/endpoints/service.api.ts index c3364d970b..8dfcb656a9 100644 --- a/dashboard/client/api/endpoints/service.api.ts +++ b/dashboard/client/api/endpoints/service.api.ts @@ -2,25 +2,25 @@ import { autobind } from "../../utils"; import { KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; -export interface IServicePort { +export interface ServicePort { name?: string; protocol: string; port: number; targetPort: number; } -export class ServicePort implements IServicePort { +export class ServicePort implements ServicePort { name?: string; protocol: string; port: number; targetPort: number; nodePort?: number; - constructor(data: IServicePort) { - Object.assign(this, data) + constructor(data: ServicePort) { + Object.assign(this, data); } - toString() { + toString(): string { if (this.nodePort) { return `${this.port}:${this.nodePort}/${this.protocol}`; } else { @@ -29,6 +29,13 @@ export class ServicePort implements IServicePort { } } +export interface LoadBalancer { + ingress?: { + ip?: string; + hostname?: string; + }[]; +} + @autobind() export class Service extends KubeObject { static kind = "Service" @@ -45,32 +52,23 @@ export class Service extends KubeObject { } status: { - loadBalancer?: { - ingress?: { - ip?: string; - hostname?: string; - }[]; - }; + loadBalancer?: LoadBalancer; } - getClusterIp() { - return this.spec.clusterIP; + getExternalIps(): string[] { + return this.status.loadBalancer?.ingress?.map((val): string => val.ip || val.hostname || "") + || this.spec.externalIPs + || []; } - getExternalIps() { - const lb = this.getLoadBalancer(); - if (lb && lb.ingress) { - return lb.ingress.map(val => val.ip || val.hostname) - } - return this.spec.externalIPs || []; - } - - getType() { + getType(): string { return this.spec.type || "-"; } getSelector(): string[] { - if (!this.spec.selector) return []; + if (!this.spec.selector) { + return []; + } return Object.entries(this.spec.selector).map(val => val.join("=")); } @@ -79,15 +77,11 @@ export class Service extends KubeObject { return ports.map(p => new ServicePort(p)); } - getLoadBalancer() { - return this.status.loadBalancer; - } - - isActive() { + isActive(): boolean { return this.getType() !== "LoadBalancer" || this.getExternalIps().length > 0; } - getStatus() { + getStatus(): "Active" | "Pending" { return this.isActive() ? "Active" : "Pending"; } } diff --git a/dashboard/client/api/endpoints/stateful-set.api.ts b/dashboard/client/api/endpoints/stateful-set.api.ts index 01509aa787..9dde286107 100644 --- a/dashboard/client/api/endpoints/stateful-set.api.ts +++ b/dashboard/client/api/endpoints/stateful-set.api.ts @@ -1,6 +1,6 @@ import get from "lodash/get"; -import { IPodContainer } from "./pods.api"; -import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; +import { PodContainer } from "./pods.api"; +import { Affinity, WorkloadKubeObject } from "../workload-kube-object"; import { autobind } from "../../utils"; import { KubeApi } from "../kube-api"; @@ -35,7 +35,7 @@ export class StatefulSet extends WorkloadKubeObject { mountPath: string; }[]; }[]; - affinity?: IAffinity; + affinity?: Affinity; nodeSelector?: { [selector: string]: string; }; @@ -70,9 +70,9 @@ export class StatefulSet extends WorkloadKubeObject { collisionCount: number; } - getImages() { - const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []) - return [...containers].map(container => container.image) + getImages(): string[] { + const containers: PodContainer[] = get(this, "spec.template.spec.containers", []); + return containers.map(container => container.image); } } diff --git a/dashboard/client/api/endpoints/storage-class.api.ts b/dashboard/client/api/endpoints/storage-class.api.ts index c4b0f02dbe..78cbc258d6 100644 --- a/dashboard/client/api/endpoints/storage-class.api.ts +++ b/dashboard/client/api/endpoints/storage-class.api.ts @@ -14,20 +14,20 @@ export class StorageClass extends KubeObject { [param: string]: string; // every provisioner has own set of these parameters } - isDefault() { + isDefault(): boolean { const annotations = this.metadata.annotations || {}; return ( annotations["storageclass.kubernetes.io/is-default-class"] === "true" || annotations["storageclass.beta.kubernetes.io/is-default-class"] === "true" - ) + ); } - getVolumeBindingMode() { - return this.volumeBindingMode || "-" + getVolumeBindingMode(): string { + return this.volumeBindingMode || "-"; } - getReclaimPolicy() { - return this.reclaimPolicy || "-" + getReclaimPolicy(): string { + return this.reclaimPolicy || "-"; } } diff --git a/dashboard/client/api/index.ts b/dashboard/client/api/index.ts index edceb5408d..118034b2bb 100644 --- a/dashboard/client/api/index.ts +++ b/dashboard/client/api/index.ts @@ -27,7 +27,7 @@ export const apiKubeResourceApplier = new KubeJsonApi({ }); // Common handler for HTTP api errors -function onApiError(error: JsonApiErrorParsed, res: Response) { +function onApiError(error: JsonApiErrorParsed, res: Response): void { switch (res.status) { case 403: error.isUsedForNotification = true; diff --git a/dashboard/client/api/json-api.ts b/dashboard/client/api/json-api.ts index 5f50296b0d..b14b882644 100644 --- a/dashboard/client/api/json-api.ts +++ b/dashboard/client/api/json-api.ts @@ -2,7 +2,7 @@ import { stringify } from "querystring"; import { EventEmitter } from "../utils/eventEmitter"; -import { cancelableFetch } from "../utils/cancelableFetch"; +import { cancelableFetch, CancelablePromise } from "../utils/cancelableFetch"; export interface JsonApiData { } @@ -31,6 +31,21 @@ export interface JsonApiConfig { debug?: boolean; } +export class JsonApiErrorParsed { + isUsedForNotification = false; + + constructor(private error: JsonApiError | DOMException, private messages: string[]) { + } + + get isAborted(): boolean { + return this.error.code === DOMException.ABORT_ERR; + } + + toString(): string { + return this.messages.join("\n"); + } +} + export class JsonApi { static reqInitDefault: RequestInit = { headers: { @@ -51,30 +66,30 @@ export class JsonApi { public onData = new EventEmitter<[D, Response]>(); public onError = new EventEmitter<[JsonApiErrorParsed, Response]>(); - get(path: string, params?: P, reqInit: RequestInit = {}) { + get(path: string, params?: P, reqInit: RequestInit = {}): CancelablePromise { return this.request(path, params, { ...reqInit, method: "get" }); } - post(path: string, params?: P, reqInit: RequestInit = {}) { + post(path: string, params?: P, reqInit: RequestInit = {}): CancelablePromise { return this.request(path, params, { ...reqInit, method: "post" }); } - put(path: string, params?: P, reqInit: RequestInit = {}) { + put(path: string, params?: P, reqInit: RequestInit = {}): CancelablePromise { return this.request(path, params, { ...reqInit, method: "put" }); } - patch(path: string, params?: P, reqInit: RequestInit = {}) { + patch(path: string, params?: P, reqInit: RequestInit = {}): CancelablePromise { return this.request(path, params, { ...reqInit, method: "patch" }); } - del(path: string, params?: P, reqInit: RequestInit = {}) { + del(path: string, params?: P, reqInit: RequestInit = {}): CancelablePromise { return this.request(path, params, { ...reqInit, method: "delete" }); } - protected request(path: string, params?: P, init: RequestInit = {}) { + protected request(path: string, params?: P, init: RequestInit = {}): CancelablePromise { let reqUrl = this.config.apiPrefix + path; const reqInit: RequestInit = { ...this.reqInit, ...init }; - const { data, query } = params || {} as P; + const { data, query } = params || {}; if (data && !reqInit.body) { reqInit.body = JSON.stringify(data); } @@ -110,46 +125,35 @@ export class JsonApi { } else { const error = new JsonApiErrorParsed(data, this.parseError(data, res)); this.onError.emit(error, res); - this.writeLog({ ...log, error }) + this.writeLog({ ...log, error }); throw error; } - }) + }); } protected parseError(error: JsonApiError | string, res: Response): string[] { if (typeof error === "string") { - return [error] + return [error]; + } else if (Array.isArray(error.errors)) { + return error.errors.map(error => error.title); + } else if (error.message) { + return [error.message]; } - else if (Array.isArray(error.errors)) { - return error.errors.map(error => error.title) - } - else if (error.message) { - return [error.message] - } - return [res.statusText || "Error!"] + return [res.statusText || "Error!"]; } - protected writeLog(log: JsonApiLog) { - if (!this.config.debug) return; + protected writeLog(log: JsonApiLog): void { + if (!this.config.debug) { + return; + } const { method, reqUrl, ...params } = log; let textStyle = 'font-weight: bold;'; - if (params.data) textStyle += 'background: green; color: white;'; - if (params.error) textStyle += 'background: red; color: white;'; + if (params.data) { + textStyle += 'background: green; color: white;'; + } + if (params.error) { + textStyle += 'background: red; color: white;'; + } console.log(`%c${method} ${reqUrl}`, textStyle, params); } } - -export class JsonApiErrorParsed { - isUsedForNotification = false; - - constructor(private error: JsonApiError | DOMException, private messages: string[]) { - } - - get isAborted() { - return this.error.code === DOMException.ABORT_ERR; - } - - toString() { - return this.messages.join("\n"); - } -} diff --git a/dashboard/client/api/kube-api.ts b/dashboard/client/api/kube-api.ts index e6668084f5..8e3e7177a6 100644 --- a/dashboard/client/api/kube-api.ts +++ b/dashboard/client/api/kube-api.ts @@ -1,15 +1,16 @@ // Base class for building all kubernetes apis -import merge from "lodash/merge" +import merge from "lodash/merge"; import { stringify } from "querystring"; import { IKubeObjectConstructor, KubeObject } from "./kube-object"; -import { IKubeObjectRef, KubeJsonApi, KubeJsonApiData, KubeJsonApiDataList } from "./kube-json-api"; +import { KubeObjectRef, KubeJsonApi, KubeJsonApiData, KubeJsonApiDataList } from "./kube-json-api"; import { apiKube } from "./index"; import { kubeWatchApi } from "./kube-watch-api"; import { apiManager } from "./api-manager"; import { split } from "../utils/arrays"; +import { CancelablePromise } from "client/utils/cancelableFetch"; -export interface IKubeApiOptions { +export interface KubeApiOptions { kind: string; // resource type within api-group, e.g. "Namespace" apiBase: string; // base api-path for listing all resources, e.g. "/api/v1/pods" isNamespaced: boolean; @@ -17,7 +18,7 @@ export interface IKubeApiOptions { request?: KubeJsonApi; } -export interface IKubeApiQueryParams { +export interface KubeApiQueryParams { watch?: boolean | number; resourceVersion?: string; timeoutSeconds?: number; @@ -25,7 +26,7 @@ export interface IKubeApiQueryParams { continue?: string; // might be used with ?limit from second request } -export interface IKubeApiLinkRef { +export interface KubeApiLinkRef { apiPrefix?: string; apiVersion: string; resource: string; @@ -33,14 +34,14 @@ export interface IKubeApiLinkRef { namespace?: string; } -export interface IKubeApiLinkBase extends IKubeApiLinkRef { +export interface KubeApiLinkBase extends KubeApiLinkRef { apiBase: string; apiGroup: string; apiVersionWithGroup: string; } export class KubeApi { - static parseApi(apiPath = ""): IKubeApiLinkBase { + static parseApi(apiPath = ""): KubeApiLinkBase { apiPath = new URL(apiPath, location.origin).pathname; const [, prefix, ...parts] = apiPath.split("/"); const apiPrefix = `/${prefix}`; @@ -91,11 +92,11 @@ export class KubeApi { */ if (left[0].includes('.') || left[1].match(/^v[0-9]/)) { [apiGroup, apiVersion] = left; - resource = left.slice(2).join("/") + resource = left.slice(2).join("/"); } else { apiGroup = ""; apiVersion = left[0]; - [resource, name] = left.slice(1) + [resource, name] = left.slice(1); } break; } @@ -105,7 +106,7 @@ export class KubeApi { const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/"); if (!apiBase) { - throw new Error(`invalid apiPath: ${apiPath}`) + throw new Error(`invalid apiPath: ${apiPath}`); } return { @@ -116,20 +117,20 @@ export class KubeApi { }; } - static createLink(ref: IKubeApiLinkRef): string { + static createLink(ref: KubeApiLinkRef): string { const { apiPrefix = "/apis", resource, apiVersion, name } = ref; let { namespace } = ref; if (namespace) { - namespace = `namespaces/${namespace}` + namespace = `namespaces/${namespace}`; } return [apiPrefix, apiVersion, namespace, resource, name] .filter(v => v) - .join("/") + .join("/"); } - static watchAll(...apis: KubeApi[]) { + static watchAll(...apis: KubeApi[]): () => void { const disposers = apis.map(api => api.watch()); - return () => disposers.forEach(unwatch => unwatch()); + return (): void => disposers.forEach(unwatch => unwatch()); } readonly kind: string @@ -145,7 +146,7 @@ export class KubeApi { protected request: KubeJsonApi; protected resourceVersions = new Map(); - constructor(protected options: IKubeApiOptions) { + constructor(protected options: KubeApiOptions) { const { kind, isNamespaced = false, @@ -169,19 +170,19 @@ export class KubeApi { apiManager.registerApi(apiBase, this); } - setResourceVersion(namespace = "", newVersion: string) { + setResourceVersion(namespace = "", newVersion: string): void { this.resourceVersions.set(namespace, newVersion); } - getResourceVersion(namespace = "") { + getResourceVersion(namespace = ""): string { return this.resourceVersions.get(namespace); } - async refreshResourceVersion(params?: { namespace: string }) { + async refreshResourceVersion(params?: { namespace: string }): Promise { return this.list(params, { limit: 1 }); } - getUrl({ name = "", namespace = "" } = {}, query?: Partial) { + getUrl({ name = "", namespace = "" } = {}, query?: Partial): string { const { apiPrefix, apiVersionWithGroup, apiResource } = this; const resourcePath = KubeApi.createLink({ apiPrefix: apiPrefix, @@ -208,7 +209,7 @@ export class KubeApi { kind: this.kind, apiVersion: apiVersion, ...item, - })) + })); } // custom apis might return array for list response, e.g. users, groups, etc. @@ -219,13 +220,13 @@ export class KubeApi { return data; } - async list({ namespace = "" } = {}, query?: IKubeApiQueryParams): Promise { + async list({ namespace = "" } = {}, query?: KubeApiQueryParams): Promise { return this.request .get(this.getUrl({ namespace }), { query }) .then(data => this.parseResponse(data, namespace)); } - async get({ name = "", namespace = "default" } = {}, query?: IKubeApiQueryParams): Promise { + async get({ name = "", namespace = "default" } = {}, query?: KubeApiQueryParams): Promise { return this.request .get(this.getUrl({ namespace, name }), { query }) .then(this.parseResponse); @@ -252,20 +253,20 @@ export class KubeApi { const apiUrl = this.getUrl({ namespace, name }); return this.request .put(apiUrl, { data }) - .then(this.parseResponse) + .then(this.parseResponse); } - async delete({ name = "", namespace = "default" }) { + delete({ name = "", namespace = "default" }): CancelablePromise { const apiUrl = this.getUrl({ namespace, name }); - return this.request.del(apiUrl) + return this.request.del(apiUrl); } - getWatchUrl(namespace = "", query: IKubeApiQueryParams = {}) { + getWatchUrl(namespace = "", query: KubeApiQueryParams = {}): string { return this.getUrl({ namespace }, { watch: 1, resourceVersion: this.getResourceVersion(namespace), ...query, - }) + }); } watch(): () => void { @@ -273,16 +274,16 @@ export class KubeApi { } } -export function lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): string { +export function lookupApiLink(ref: KubeObjectRef, parentObject: KubeObject): string { const { kind, apiVersion, name, namespace = parentObject.getNs() } = ref; // search in registered apis by 'kind' & 'apiVersion' - const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion) + const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion); if (api) { - return api.getUrl({ namespace, name }) + return api.getUrl({ namespace, name }); } // lookup api by generated resource link @@ -298,10 +299,10 @@ export function lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): st // resolve by kind only (hpa's might use refs to older versions of resources for example) const apiByKind = apiManager.getApi(api => api.kind === kind); if (apiByKind) { - return apiByKind.getUrl({ name, namespace }) + return apiByKind.getUrl({ name, namespace }); } // otherwise generate link with default prefix // resource still might exists in k8s, but api is not registered in the app - return KubeApi.createLink({ apiVersion, name, namespace, resource }) + return KubeApi.createLink({ apiVersion, name, namespace, resource }); } diff --git a/dashboard/client/api/kube-json-api.ts b/dashboard/client/api/kube-json-api.ts index ad7272cd90..0a4e136b96 100644 --- a/dashboard/client/api/kube-json-api.ts +++ b/dashboard/client/api/kube-json-api.ts @@ -31,7 +31,7 @@ export interface KubeJsonApiData extends JsonApiData { }; } -export interface IKubeObjectRef { +export interface KubeObjectRef { kind: string; apiVersion: string; name: string; @@ -49,7 +49,7 @@ export interface KubeJsonApiError extends JsonApiError { }; } -export interface IKubeJsonApiQuery { +export interface KubeJsonApiQuery { watch?: any; resourceVersion?: string; timeoutSeconds?: number; diff --git a/dashboard/client/api/kube-object.ts b/dashboard/client/api/kube-object.ts index 9343e7e2e5..23420ec506 100644 --- a/dashboard/client/api/kube-object.ts +++ b/dashboard/client/api/kube-object.ts @@ -7,12 +7,13 @@ import { ItemObject } from "../item.store"; import { apiKube } from "./index"; import { JsonApiParams } from "./json-api"; import { resourceApplierApi } from "./endpoints/resource-applier.api"; +import { CancelablePromise } from "client/utils/cancelableFetch"; export type IKubeObjectConstructor = (new (data: KubeJsonApiData | any) => T) & { kind?: string; }; -export interface IKubeObjectMetadata { +export interface KubeObjectMetadata { uid: string; name: string; namespace?: string; @@ -44,11 +45,7 @@ export type IKubeMetaField = keyof KubeObject["metadata"]; export class KubeObject implements ItemObject { static readonly kind: string; - static create(data: any) { - return new KubeObject(data); - } - - static isNonSystem(item: KubeJsonApiData | KubeObject) { + static isNonSystem(item: KubeJsonApiData | KubeObject): boolean { return !item.metadata.name.startsWith("system:"); } @@ -61,8 +58,10 @@ export class KubeObject implements ItemObject { } static stringifyLabels(labels: { [name: string]: string }): string[] { - if (!labels) return []; - return Object.entries(labels).map(([name, value]) => `${name}=${value}`) + if (!labels) { + return []; + } + return Object.entries(labels).map(([name, value]) => `${name}=${value}`); } constructor(data: KubeJsonApiData) { @@ -71,31 +70,27 @@ export class KubeObject implements ItemObject { apiVersion: string kind: string - metadata: IKubeObjectMetadata; + metadata: KubeObjectMetadata; - get selfLink() { - return this.metadata.selfLink + get selfLink(): string { + return this.metadata.selfLink; } - getId() { + getId(): string { return this.metadata.uid; } - getResourceVersion() { - return this.metadata.resourceVersion; - } - - getName() { + getName(): string { return this.metadata.name; } - getNs() { + getNs(): string | undefined { // avoid "null" serialization via JSON.stringify when post data return this.metadata.namespace || undefined; } // todo: refactor with named arguments - getAge(humanize = true, compact = true, fromNow = false) { + getAge(humanize = true, compact = true, fromNow = false): number | string { if (fromNow) { return moment(this.metadata.creationTimestamp).fromNow(); } @@ -119,26 +114,26 @@ export class KubeObject implements ItemObject { return labels.filter(label => { const skip = resourceApplierApi.annotations.some(key => label.startsWith(key)); return !skip; - }) + }); } - getOwnerRefs() { + getOwnerRefs(): Required { const refs = this.metadata.ownerReferences || []; return refs.map(ownerRef => ({ ...ownerRef, namespace: this.getNs(), - })) + })); } - getSearchFields() { - const { getName, getId, getNs, getAnnotations, getLabels } = this + getSearchFields(): string[] { + const { getName, getId, getNs, getAnnotations, getLabels } = this; return [ getName(), getNs(), getId(), ...getLabels(), ...getAnnotations(), - ] + ]; } toPlainObject(): object { @@ -146,14 +141,14 @@ export class KubeObject implements ItemObject { } // use unified resource-applier api for updating all k8s objects - async update(data: Partial) { + async update(data: Partial): Promise { return resourceApplierApi.update({ ...this.toPlainObject(), ...data, }); } - delete(params?: JsonApiParams) { + delete(params?: JsonApiParams): CancelablePromise { return apiKube.del(this.selfLink, params); } } \ No newline at end of file diff --git a/dashboard/client/api/kube-watch-api.ts b/dashboard/client/api/kube-watch-api.ts index 3628f9d790..55cbd86d96 100644 --- a/dashboard/client/api/kube-watch-api.ts +++ b/dashboard/client/api/kube-watch-api.ts @@ -1,7 +1,7 @@ // Kubernetes watch-api consumer import { computed, observable, reaction } from "mobx"; -import { stringify } from "querystring" +import { stringify } from "querystring"; import { autobind, EventEmitter, interval } from "../utils"; import { KubeJsonApiData } from "./kube-json-api"; import { IKubeWatchEvent, IKubeWatchRouteEvent, IKubeWatchRouteQuery } from "../../server/common/kubewatch"; @@ -12,7 +12,7 @@ import { apiManager } from "./api-manager"; export { IKubeWatchEvent -} +}; @autobind() export class KubeWatchApi { @@ -32,22 +32,25 @@ export class KubeWatchApi { }); } - @computed get activeApis() { + @computed get activeApis(): KubeApi[] { return Array.from(this.subscribers.keys()); } - getSubscribersCount(api: KubeApi) { + getSubscribersCount(api: KubeApi): number { return this.subscribers.get(api) || 0; } - subscribe(...apis: KubeApi[]) { + subscribe(...apis: KubeApi[]): () => void { apis.forEach(api => { this.subscribers.set(api, this.getSubscribersCount(api) + 1); }); - return () => apis.forEach(api => { + return (): void => apis.forEach(api => { const count = this.getSubscribersCount(api) - 1; - if (count <= 0) this.subscribers.delete(api); - else this.subscribers.set(api, count); + if (count <= 0) { + this.subscribers.delete(api); + } else { + this.subscribers.set(api, count); + } }); } @@ -55,16 +58,20 @@ export class KubeWatchApi { const { isClusterAdmin, allowedNamespaces } = configStore; return { api: this.activeApis.map(api => { - if (isClusterAdmin) return api.getWatchUrl(); - return allowedNamespaces.map(namespace => api.getWatchUrl(namespace)) + if (isClusterAdmin) { + return api.getWatchUrl(); + } + return allowedNamespaces.map(namespace => api.getWatchUrl(namespace)); }).flat() - } + }; } // todo: maybe switch to websocket to avoid often reconnects @autobind() - protected connect() { - if (this.evtSource) this.disconnect(); // close previous connection + protected connect(): void { + if (this.evtSource) { + this.disconnect(); + } // close previous connection if (!this.activeApis.length) { return; } @@ -76,32 +83,35 @@ export class KubeWatchApi { this.writeLog("CONNECTING", query.api); } - reconnect() { + reconnect(): void { if (!this.evtSource || this.evtSource.readyState !== EventSource.OPEN) { this.reconnectAttempts = this.maxReconnectsOnError; this.connect(); } } - protected disconnect() { - if (!this.evtSource) return; + protected disconnect(): void { + if (!this.evtSource) { + return; + } this.evtSource.close(); this.evtSource.onmessage = null; this.evtSource = null; } - protected onMessage(evt: MessageEvent) { - if (!evt.data) return; + protected onMessage(evt: MessageEvent): void { + if (!evt.data) { + return; + } const data = JSON.parse(evt.data); if ((data as IKubeWatchEvent).object) { this.onData.emit(data); - } - else { + } else { this.onRouteEvent(data); } } - protected async onRouteEvent({ type, url }: IKubeWatchRouteEvent) { + protected async onRouteEvent({ type, url }: IKubeWatchRouteEvent): Promise { if (type === "STREAM_END") { this.disconnect(); const { apiBase, namespace } = KubeApi.parseApi(url); @@ -111,13 +121,13 @@ export class KubeWatchApi { await api.refreshResourceVersion({ namespace }); this.reconnect(); } catch(error) { - console.debug("failed to refresh resource version", error) + console.debug("failed to refresh resource version", error); } } } } - protected onError(evt: MessageEvent) { + protected onError(evt: MessageEvent): void { const { reconnectAttempts: attemptsRemain, reconnectTimeoutMs } = this; if (evt.eventPhase === EventSource.CLOSED) { if (attemptsRemain > 0) { @@ -127,14 +137,14 @@ export class KubeWatchApi { } } - protected writeLog(...data: any[]) { + protected writeLog(...data: any[]): void { if (configStore.isDevelopment) { console.log('%cKUBE-WATCH-API:', `font-weight: bold`, ...data); } } - addListener(store: KubeObjectStore, callback: (evt: IKubeWatchEvent) => void) { - const listener = (evt: IKubeWatchEvent) => { + addListener(store: KubeObjectStore, callback: (evt: IKubeWatchEvent) => void): () => void { + const listener = (evt: IKubeWatchEvent): void => { const { selfLink, namespace, resourceVersion } = evt.object.metadata; const api = apiManager.getApi(selfLink); api.setResourceVersion(namespace, resourceVersion); @@ -144,10 +154,10 @@ export class KubeWatchApi { } }; this.onData.addListener(listener); - return () => this.onData.removeListener(listener); + return (): void => this.onData.removeListener(listener); } - reset() { + reset(): void { this.subscribers.clear(); } } diff --git a/dashboard/client/api/rbac.ts b/dashboard/client/api/rbac.ts index b15b83353e..9c143f73b9 100644 --- a/dashboard/client/api/rbac.ts +++ b/dashboard/client/api/rbac.ts @@ -1,7 +1,7 @@ import { configStore } from "../config.store"; import { isArray } from "util"; -export function isAllowedResource(resources: string|string[]) { +export function isAllowedResource(resources: string|string[]): boolean { if (!isArray(resources)) { resources = [resources]; } diff --git a/dashboard/client/api/terminal-api.ts b/dashboard/client/api/terminal-api.ts index aec97f34af..e1d7e25964 100644 --- a/dashboard/client/api/terminal-api.ts +++ b/dashboard/client/api/terminal-api.ts @@ -2,7 +2,7 @@ import { stringify } from "querystring"; import { autobind, base64, EventEmitter, interval } from "../utils"; import { WebSocketApi } from "./websocket-api"; import { configStore } from "../config.store"; -import isEqual from "lodash/isEqual" +import isEqual from "lodash/isEqual"; export enum TerminalChannels { STDIN = 0, @@ -24,7 +24,7 @@ enum TerminalColor { NO_COLOR = "\u001b[0m", } -export interface ITerminalApiOptions { +export interface TerminalApiOptions { id: string; node?: string; colorTheme?: "light" | "dark"; @@ -38,7 +38,7 @@ export class TerminalApi extends WebSocketApi { public onReady = new EventEmitter<[]>(); public isReady = false; - constructor(protected options: ITerminalApiOptions) { + constructor(protected options: TerminalApiOptions) { super({ logging: configStore.isDevelopment, flushOnOpen: false, @@ -46,7 +46,7 @@ export class TerminalApi extends WebSocketApi { }); } - async getUrl(token: string) { + getUrl(token: string): string { const { hostname, protocol } = location; const { id, node } = this.options; const apiPrefix = configStore.apiPrefix.TERMINAL; @@ -62,9 +62,9 @@ export class TerminalApi extends WebSocketApi { return `${wss}${hostname}${configStore.serverPort}${apiPrefix}/api?${stringify(queryParams)}`; } - async connect() { + async connect(): Promise { const token = await configStore.getToken(); - const apiUrl = await this.getUrl(token); + const apiUrl = this.getUrl(token); const { colorTheme } = this.options; this.emitStatus("Connecting ...", { color: colorTheme == "light" ? TerminalColor.GRAY : TerminalColor.LIGHT_GRAY @@ -76,29 +76,35 @@ export class TerminalApi extends WebSocketApi { } @autobind() - async sendNewToken() { + async sendNewToken(): Promise { const token = await configStore.getToken(); - if (!this.isReady || token == this.currentToken) return; + if (!this.isReady || token == this.currentToken) { + return; + } this.sendCommand(token, TerminalChannels.TOKEN); this.currentToken = token; } - destroy() { - if (!this.socket) return; + destroy(): void { + if (!this.socket) { + return; + } const exitCode = String.fromCharCode(4); // ctrl+d this.sendCommand(exitCode); this.tokenInterval.stop(); setTimeout(() => super.destroy(), 2000); } - removeAllListeners() { + removeAllListeners(): void { super.removeAllListeners(); this.onReady.removeAllListeners(); } @autobind() - protected _onReady(data: string) { - if (!data) return; + protected _onReady(data: string): boolean | undefined { + if (!data) { + return; + } this.isReady = true; this.onReady.emit(); this.onData.removeListener(this._onReady); @@ -107,16 +113,15 @@ export class TerminalApi extends WebSocketApi { return false; // prevent calling rest of listeners } - reconnect() { - const { reconnectDelaySeconds } = this.params; + reconnect(): void { super.reconnect(); } - sendCommand(key: string, channel = TerminalChannels.STDIN) { + sendCommand(key: string, channel = TerminalChannels.STDIN): any { return this.send(channel + base64.encode(key)); } - sendTerminalSize(cols: number, rows: number) { + sendTerminalSize(cols: number, rows: number): void { const newSize = { Width: cols, Height: rows }; if (!isEqual(this.size, newSize)) { this.sendCommand(JSON.stringify(newSize), TerminalChannels.TERMINAL_SIZE); @@ -124,24 +129,24 @@ export class TerminalApi extends WebSocketApi { } } - protected parseMessage(data: string) { + protected parseMessage(data: string): any { data = data.substr(1); // skip channel return base64.decode(data); } - protected _onOpen(evt: Event) { + protected _onOpen(evt: Event): void { // Client should send terminal size in special channel 4, // But this size will be changed by terminal.fit() this.sendTerminalSize(120, 80); super._onOpen(evt); } - protected _onClose(evt: CloseEvent) { + protected _onClose(evt: CloseEvent): void { super._onClose(evt); this.isReady = false; } - protected emitStatus(data: string, options: { color?: TerminalColor; showTime?: boolean } = {}) { + protected emitStatus(data: string, options: { color?: TerminalColor; showTime?: boolean } = {}): void { const { color, showTime } = options; if (color) { data = `${color}${data}${TerminalColor.NO_COLOR}`; @@ -153,7 +158,7 @@ export class TerminalApi extends WebSocketApi { this.onData.emit(`${showTime ? time : ""}${data}\r\n`); } - protected emitError(error: string) { + protected emitError(error: string): void { this.emitStatus(error, { color: TerminalColor.RED }); diff --git a/dashboard/client/api/websocket-api.ts b/dashboard/client/api/websocket-api.ts index daf46b286c..7f0f9c2f7c 100644 --- a/dashboard/client/api/websocket-api.ts +++ b/dashboard/client/api/websocket-api.ts @@ -1,7 +1,7 @@ import { observable } from "mobx"; import { EventEmitter } from "../utils/eventEmitter"; -interface IParams { +interface Params { url?: string; // connection url, starts with ws:// or wss:// autoConnect?: boolean; // auto-connect in constructor flushOnOpen?: boolean; // flush pending commands on open socket @@ -10,7 +10,7 @@ interface IParams { logging?: boolean; // show logs in console } -interface IMessage { +interface Message { id: string; data: string; } @@ -25,7 +25,7 @@ export enum WebSocketApiState { export class WebSocketApi { protected socket: WebSocket; - protected pendingCommands: IMessage[] = []; + protected pendingCommands: Message[] = []; protected reconnectTimer: any; protected pingTimer: any; protected pingMessage = "PING"; @@ -36,7 +36,7 @@ export class WebSocketApi { public onData = new EventEmitter<[string]>(); public onClose = new EventEmitter<[]>(); - static defaultParams: Partial = { + static defaultParams: Partial = { autoConnect: true, logging: false, reconnectDelaySeconds: 10, @@ -44,7 +44,7 @@ export class WebSocketApi { flushOnOpen: true, }; - constructor(protected params: IParams) { + constructor(protected params: Params) { this.params = Object.assign({}, WebSocketApi.defaultParams, params); const { autoConnect, pingIntervalSeconds } = this.params; if (autoConnect) { @@ -55,20 +55,20 @@ export class WebSocketApi { } } - get isConnected() { + get isConnected(): boolean { const state = this.socket ? this.socket.readyState : -1; return state === WebSocket.OPEN && this.isOnline; } - get isOnline() { + get isOnline(): boolean { return navigator.onLine; } - setParams(params: Partial) { + setParams(params: Partial): void { Object.assign(this.params, params); } - connect(url = this.params.url) { + connect(url = this.params.url): void { if (this.socket) { this.socket.close(); // close previous connection first } @@ -80,21 +80,27 @@ export class WebSocketApi { this.readyState = WebSocketApiState.CONNECTING; } - ping() { - if (!this.isConnected) return; + ping(): void { + if (!this.isConnected) { + return; + } this.send(this.pingMessage); } - reconnect() { + reconnect(): void { const { reconnectDelaySeconds } = this.params; - if (!reconnectDelaySeconds) return; + if (!reconnectDelaySeconds) { + return; + } this.writeLog('reconnect after', reconnectDelaySeconds + "ms"); this.reconnectTimer = setTimeout(() => this.connect(), reconnectDelaySeconds * 1000); this.readyState = WebSocketApiState.RECONNECTING; } - destroy() { - if (!this.socket) return; + destroy(): void { + if (!this.socket) { + return; + } this.socket.close(); this.socket = null; this.pendingCommands = []; @@ -104,64 +110,60 @@ export class WebSocketApi { this.readyState = WebSocketApiState.PENDING; } - removeAllListeners() { + removeAllListeners(): void { this.onOpen.removeAllListeners(); this.onData.removeAllListeners(); this.onClose.removeAllListeners(); } - send(command: string) { - const msg: IMessage = { + send(command: string): void { + const msg: Message = { id: (Math.random() * Date.now()).toString(16).replace(".", ""), data: command, }; if (this.isConnected) { this.socket.send(msg.data); - } - else { + } else { this.pendingCommands.push(msg); } } - protected flush() { + protected flush(): void { this.pendingCommands.forEach(msg => this.send(msg.data)); this.pendingCommands.length = 0; } - protected parseMessage(data: string) { - return data; - } - - protected _onOpen(evt: Event) { + protected _onOpen(evt: Event): void { this.onOpen.emit(); - if (this.params.flushOnOpen) this.flush(); + if (this.params.flushOnOpen) { + this.flush(); + } this.readyState = WebSocketApiState.OPEN; this.writeLog('%cOPEN', 'color:green;font-weight:bold;', evt); } - protected _onMessage(evt: MessageEvent) { - const data = this.parseMessage(evt.data); + protected _onMessage(evt: MessageEvent): void { + const data = evt.data; this.onData.emit(data); this.writeLog('%cMESSAGE', 'color:black;font-weight:bold;', data); } - protected _onError(evt: Event) { - this.writeLog('%cERROR', 'color:red;font-weight:bold;', evt) + protected _onError(evt: Event): void { + this.writeLog('%cERROR', 'color:red;font-weight:bold;', evt); } - protected _onClose(evt: CloseEvent) { + protected _onClose(evt: CloseEvent): void { const error = evt.code !== 1000 || !evt.wasClean; if (error) { this.reconnect(); - } - else { + } else { this.readyState = WebSocketApiState.CLOSED; this.onClose.emit(); } this.writeLog('%cCLOSE', `color:${error ? "red" : "black"};font-weight:bold;`, evt); } - protected writeLog(...data: any[]) { + protected writeLog(...data: any[]): void { if (this.params.logging) { console.log(...data); } diff --git a/dashboard/client/api/workload-kube-object.ts b/dashboard/client/api/workload-kube-object.ts index a32ccd99e9..7eda18c1c6 100644 --- a/dashboard/client/api/workload-kube-object.ts +++ b/dashboard/client/api/workload-kube-object.ts @@ -1,48 +1,48 @@ import get from "lodash/get"; -import { IKubeObjectMetadata, KubeObject } from "./kube-object"; +import { KubeObject } from "./kube-object"; -interface IToleration { +interface Toleration { key?: string; operator?: string; effect?: string; tolerationSeconds?: number; } -interface IMatchExpression { +interface MatchExpression { key: string; operator: string; values: string[]; } -interface INodeAffinity { +interface NodeAffinity { nodeSelectorTerms?: { - matchExpressions: IMatchExpression[]; + matchExpressions: MatchExpression[]; }[]; weight: number; preference: { - matchExpressions: IMatchExpression[]; + matchExpressions: MatchExpression[]; }; } -interface IPodAffinity { +interface PodAffinity { labelSelector: { - matchExpressions: IMatchExpression[]; + matchExpressions: MatchExpression[]; }; topologyKey: string; } -export interface IAffinity { +export interface Affinity { nodeAffinity?: { - requiredDuringSchedulingIgnoredDuringExecution?: INodeAffinity[]; - preferredDuringSchedulingIgnoredDuringExecution?: INodeAffinity[]; + requiredDuringSchedulingIgnoredDuringExecution?: NodeAffinity[]; + preferredDuringSchedulingIgnoredDuringExecution?: NodeAffinity[]; }; podAffinity?: { - requiredDuringSchedulingIgnoredDuringExecution?: IPodAffinity[]; - preferredDuringSchedulingIgnoredDuringExecution?: IPodAffinity[]; + requiredDuringSchedulingIgnoredDuringExecution?: PodAffinity[]; + preferredDuringSchedulingIgnoredDuringExecution?: PodAffinity[]; }; podAntiAffinity?: { - requiredDuringSchedulingIgnoredDuringExecution?: IPodAffinity[]; - preferredDuringSchedulingIgnoredDuringExecution?: IPodAffinity[]; + requiredDuringSchedulingIgnoredDuringExecution?: PodAffinity[]; + preferredDuringSchedulingIgnoredDuringExecution?: PodAffinity[]; }; } @@ -66,17 +66,19 @@ export class WorkloadKubeObject extends KubeObject { return KubeObject.stringifyLabels(labels); } - getTolerations(): IToleration[] { - return get(this, "spec.template.spec.tolerations", []) + getTolerations(): Toleration[] { + return get(this, "spec.template.spec.tolerations", []); } - getAffinity(): IAffinity { - return get(this, "spec.template.spec.affinity") + getAffinity(): Affinity { + return get(this, "spec.template.spec.affinity"); } - getAffinityNumber() { - const affinity = this.getAffinity() - if (!affinity) return 0 - return Object.keys(affinity).length + getAffinityNumber(): number { + const affinity = this.getAffinity(); + if (!affinity) { + return 0; + } + return Object.keys(affinity).length; } } \ No newline at end of file diff --git a/dashboard/client/browser-check.tsx b/dashboard/client/browser-check.tsx index 6b6e006c51..d8c0c08582 100644 --- a/dashboard/client/browser-check.tsx +++ b/dashboard/client/browser-check.tsx @@ -2,11 +2,11 @@ import * as React from "react"; import { Notifications } from "./components/notifications"; import { Trans } from "@lingui/macro"; -export function browserCheck() { - const ua = window.navigator.userAgent - const msie = ua.indexOf('MSIE ') // IE < 11 - const trident = ua.indexOf('Trident/') // IE 11 - const edge = ua.indexOf('Edge') // Edge +export function browserCheck(): void { + const ua = window.navigator.userAgent; + const msie = ua.indexOf('MSIE '); // IE < 11 + const trident = ua.indexOf('Trident/'); // IE 11 + const edge = ua.indexOf('Edge'); // Edge if (msie > 0 || trident > 0 || edge > 0) { Notifications.info(

@@ -15,6 +15,6 @@ export function browserCheck() { Please consider using another browser.

- ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/+404/index.ts b/dashboard/client/components/+404/index.ts index 7314f53562..67433b02c3 100644 --- a/dashboard/client/components/+404/index.ts +++ b/dashboard/client/components/+404/index.ts @@ -1 +1 @@ -export * from "./not-found" +export * from "./not-found"; diff --git a/dashboard/client/components/+404/not-found.tsx b/dashboard/client/components/+404/not-found.tsx index de4616e80e..22e1efd56f 100644 --- a/dashboard/client/components/+404/not-found.tsx +++ b/dashboard/client/components/+404/not-found.tsx @@ -3,13 +3,13 @@ import { Trans } from "@lingui/macro"; import { MainLayout } from "../layout/main-layout"; export class NotFound extends React.Component { - render() { + render(): JSX.Element { return (

Page not found

- ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/+apps-helm-charts/helm-chart-details.tsx b/dashboard/client/components/+apps-helm-charts/helm-chart-details.tsx index 32c6cd6a3e..8de4b358d0 100644 --- a/dashboard/client/components/+apps-helm-charts/helm-chart-details.tsx +++ b/dashboard/client/components/+apps-helm-charts/helm-chart-details.tsx @@ -16,6 +16,9 @@ import { createInstallChartTab } from "../dock/install-chart.store"; import { Badge } from "../badge"; import { _i18n } from "../../i18n"; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const placeholder = require("./helm-placeholder.svg"); + interface Props { chart: HelmChart; hideDetails(): void; @@ -31,7 +34,9 @@ export class HelmChartDetails extends Component { @disposeOnUnmount chartSelector = autorun(async () => { - if (!this.props.chart) return; + if (!this.props.chart) { + return; + } this.chartVersions = null; this.selectedChart = null; this.description = null; @@ -43,42 +48,43 @@ export class HelmChartDetails extends Component { }); }); - loadChartData(version?: string) { + loadChartData(version?: string): void { const { chart: { name, repo } } = this.props; - if (this.chartPromise) this.chartPromise.cancel(); + if (this.chartPromise) { + this.chartPromise.cancel(); + } this.chartPromise = helmChartsApi.get(repo, name, version); } @autobind() - onVersionChange(opt: SelectOption) { + onVersionChange(opt: SelectOption): void { const version = opt.value; this.selectedChart = this.chartVersions.find(chart => chart.version === version); this.description = null; this.loadChartData(version); this.chartPromise.then(data => { - this.description = data.readme + this.description = data.readme; }); } @autobind() - install() { + install(): void { createInstallChartTab(this.selectedChart); - this.props.hideDetails() + this.props.hideDetails(); } - renderIntroduction() { + renderIntroduction(): JSX.Element { const { selectedChart, chartVersions, onVersionChange } = this; - const placeholder = require("./helm-placeholder.svg"); return (
event.currentTarget.src = placeholder} + src={selectedChart.icon || placeholder} + onError={(event: React.SyntheticEvent): void => event.currentTarget.src = placeholder} />
- {selectedChart.getDescription()} + {selectedChart.description}
@@ -86,16 +92,16 @@ export class HelmChartDetails extends Component { themeName="outlined" menuPortalTarget={null} options={chartVersions.map(chart => chart.version)} - value={selectedChart.getVersion()} + value={selectedChart.version} onChange={onVersionChange} /> - {selectedChart.getHome()} + {selectedChart.home} {selectedChart.getMaintainers().map(({ name, email, url }) => - {name} + {name} )} {selectedChart.getKeywords().length > 0 && ( @@ -108,8 +114,10 @@ export class HelmChartDetails extends Component { ); } - renderContent() { - if (this.selectedChart === null || this.description === null) return ; + renderContent(): JSX.Element { + if (this.selectedChart === null || this.description === null) { + return ; + } return (
{this.renderIntroduction()} @@ -120,7 +128,7 @@ export class HelmChartDetails extends Component { ); } - render() { + render(): JSX.Element { const { chart, hideDetails } = this.props; const title = chart ? Chart: {chart.getFullName()} : ""; return ( diff --git a/dashboard/client/components/+apps-helm-charts/helm-chart.store.ts b/dashboard/client/components/+apps-helm-charts/helm-chart.store.ts index 995f802ab6..d9c42841d3 100644 --- a/dashboard/client/components/+apps-helm-charts/helm-chart.store.ts +++ b/dashboard/client/components/+apps-helm-charts/helm-chart.store.ts @@ -2,51 +2,48 @@ import { observable } from "mobx"; import { autobind } from "../../utils"; import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api"; import { ItemStore } from "../../item.store"; -import flatten from "lodash/flatten" +import flatten from "lodash/flatten"; import compareVersions from 'compare-versions'; -export interface IChartVersion { +export interface ChartVersion { repo: string; version: string; } @autobind() export class HelmChartStore extends ItemStore { - @observable versions = observable.map(); + @observable versions = observable.map(); - loadAll() { - return this.loadItems(() => helmChartsApi.list()); + async loadAll(): Promise { + await this.loadItems(() => helmChartsApi.list()); } - getByName(name: string, repo: string) { - return this.items.find(chart => chart.getName() === name && chart.getRepository() === repo); + getByName(desiredName: string, desiredRepo: string): HelmChart { + return this.items.find(({ name, repo }) => desiredName === name && desiredRepo === repo); } - protected sortVersions = (versions: IChartVersion[]) => { - return versions.sort((first, second) => { - return compareVersions(second.version, first.version) - }); + protected sortVersions = (versions: ChartVersion[]): ChartVersion[] => { + return versions.sort((first, second) => compareVersions(second.version, first.version)); }; - async getVersions(chartName: string, force?: boolean): Promise { + async getVersions(chartName: string, force?: boolean): Promise { let versions = this.versions.get(chartName); if (versions && !force) { return versions; } - const loadVersions = (repo: string) => { - return helmChartsApi.get(repo, chartName).then(({ versions }) => { - return versions.map(chart => ({ - repo: repo, - version: chart.getVersion() - })) - }) + const loadVersions = async (repo: string): Promise => { + const { versions } = await helmChartsApi.get(repo, chartName); + return versions.map(({version}) => ({ repo, version, })); }; + if (!this.isLoaded) { await this.loadAll(); } + const repos = this.items .filter(chart => chart.getName() === chartName) - .map(chart => chart.getRepository()); + .map(({repo}) => repo); + versions = await Promise.all(repos.map(loadVersions)) .then(flatten) .then(this.sortVersions); @@ -55,7 +52,7 @@ export class HelmChartStore extends ItemStore { return versions; } - reset() { + reset(): void { super.reset(); this.versions.clear(); } diff --git a/dashboard/client/components/+apps-helm-charts/helm-charts.route.ts b/dashboard/client/components/+apps-helm-charts/helm-charts.route.ts index 181f0c47f1..088ee4bb96 100644 --- a/dashboard/client/components/+apps-helm-charts/helm-charts.route.ts +++ b/dashboard/client/components/+apps-helm-charts/helm-charts.route.ts @@ -1,14 +1,14 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { appsRoute } from "../+apps/apps.route"; import { buildURL } from "../../navigation"; export const helmChartsRoute: RouteProps = { path: appsRoute.path + "/charts/:repo?/:chartName?" -} +}; -export interface IHelmChartsRouteParams { +export interface HelmChartsRouteParams { chartName?: string; repo?: string; } -export const helmChartsURL = buildURL(helmChartsRoute.path) \ No newline at end of file +export const helmChartsURL = buildURL(helmChartsRoute.path); \ No newline at end of file diff --git a/dashboard/client/components/+apps-helm-charts/helm-charts.tsx b/dashboard/client/components/+apps-helm-charts/helm-charts.tsx index 1e517847f9..f777f50a89 100644 --- a/dashboard/client/components/+apps-helm-charts/helm-charts.tsx +++ b/dashboard/client/components/+apps-helm-charts/helm-charts.tsx @@ -3,7 +3,7 @@ import "./helm-charts.scss"; import React, { Component } from "react"; import { RouteComponentProps } from "react-router"; import { observer } from "mobx-react"; -import { helmChartsURL, IHelmChartsRouteParams } from "./helm-charts.route"; +import { helmChartsURL, HelmChartsRouteParams } from "./helm-charts.route"; import { helmChartStore } from "./helm-chart.store"; import { HelmChart } from "../../api/endpoints/helm-charts.api"; import { HelmChartDetails } from "./helm-chart-details"; @@ -18,39 +18,38 @@ enum sortBy { repo = "repo", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class HelmCharts extends Component { - componentDidMount() { + componentDidMount(): void { helmChartStore.loadAll(); } - get selectedChart() { - const { match: { params: { chartName, repo } } } = this.props + get selectedChart(): HelmChart { + const { match: { params: { chartName, repo } } } = this.props; return helmChartStore.getByName(chartName, repo); } - showDetails = (chart: HelmChart) => { + showDetails = (chart: HelmChart): void => { if (!chart) { - navigation.merge(helmChartsURL()) - } - else { + navigation.merge(helmChartsURL()); + } else { navigation.merge(helmChartsURL({ params: { chartName: chart.getName(), - repo: chart.getRepository(), + repo: chart.repo, } - })) + })); } } - hideDetails = () => { + hideDetails = (): void => { this.showDetails(null); } - render() { + render(): JSX.Element { return ( <> { isClusterScoped={true} isSelectable={false} sortingCallbacks={{ - [sortBy.name]: (chart: HelmChart) => chart.getName(), - [sortBy.repo]: (chart: HelmChart) => chart.getRepository(), + [sortBy.name]: (chart: HelmChart): string => chart.getName(), + [sortBy.repo]: ({repo}: HelmChart): string => repo, }} searchFilters={[ - (chart: HelmChart) => chart.getName(), - (chart: HelmChart) => chart.getVersion(), - (chart: HelmChart) => chart.getAppVersion(), - (chart: HelmChart) => chart.getKeywords(), + (chart: HelmChart): string => chart.getName(), + ({ version }: HelmChart): string => version, + (chart: HelmChart): string => chart.getAppVersion(), + ({ keywords }: HelmChart): string[] => keywords, ]} filterItems={[ - (items: HelmChart[]) => items.filter(item => !item.deprecated) + (items: HelmChart[]): HelmChart[] => items.filter(item => !item.deprecated) ]} - customizeHeader={() => ( + customizeHeader={(): JSX.Element => ( )} renderTableHeader={[ @@ -83,18 +82,18 @@ export class HelmCharts extends Component { { title: Repository, className: "repository", sortBy: sortBy.repo }, ]} - renderTableContents={(chart: HelmChart) => [ -
+ renderTableContents={(chart: HelmChart): (HTMLElement | string | React.ReactNode)[] => [ +
evt.currentTarget.classList.add("visible")} + src={chart.icon || require("./helm-placeholder.svg")} + onLoad={(evt): void => evt.currentTarget.classList.add("visible")} />
, chart.getName(), - chart.getDescription(), - chart.getVersion(), + chart.description, + chart.version, chart.getAppVersion(), - { title: chart.getRepository(), className: chart.getRepository().toLowerCase() } + { title: chart.repo, className: chart.repo.toLowerCase() } ]} detailsItem={this.selectedChart} onDetails={this.showDetails} diff --git a/dashboard/client/components/+apps-releases/release-details.tsx b/dashboard/client/components/+apps-releases/release-details.tsx index cb7c7b8069..87f05cc233 100644 --- a/dashboard/client/components/+apps-releases/release-details.tsx +++ b/dashboard/client/components/+apps-releases/release-details.tsx @@ -7,7 +7,7 @@ import { observable, reaction } from "mobx"; import { Link } from "react-router-dom"; import { t, Trans } from "@lingui/macro"; import kebabCase from "lodash/kebabCase"; -import { HelmRelease, helmReleasesApi, IReleaseDetails } from "../../api/endpoints/helm-releases.api"; +import { HelmRelease, helmReleasesApi, ReleaseInfo } from "../../api/endpoints/helm-releases.api"; import { HelmReleaseMenu } from "./release-menu"; import { Drawer, DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; @@ -35,14 +35,16 @@ interface Props { @observer export class ReleaseDetails extends Component { - @observable details: IReleaseDetails; + @observable details: ReleaseInfo; @observable values = ""; @observable saving = false; @observable releaseSecret: Secret; @disposeOnUnmount releaseSelector = reaction(() => this.props.release, release => { - if (!release) return; + if (!release) { + return; + } this.loadDetails(); this.loadValues(); this.releaseSecret = null; @@ -51,33 +53,36 @@ export class ReleaseDetails extends Component { @disposeOnUnmount secretWatcher = reaction(() => secretsStore.items.toJS(), () => { - if (!this.props.release) return; + if (!this.props.release) { + return; + } const { getReleaseSecret } = releaseStore; const { release } = this.props; const secret = getReleaseSecret(release); if (this.releaseSecret) { - if (isEqual(this.releaseSecret.getLabels(), secret.getLabels())) return; + if (isEqual(this.releaseSecret.getLabels(), secret.getLabels())) { + return; + } this.loadDetails(); } this.releaseSecret = secret; }); - async loadDetails() { + async loadDetails(): Promise { const { release } = this.props; this.details = null; - this.details = await helmReleasesApi.get(release.getName(), release.getNs()); + this.details = await helmReleasesApi.get(release.getName(), release.namespace); } - async loadValues() { + async loadValues(): Promise { const { release } = this.props; this.values = ""; - this.values = await helmReleasesApi.getValues(release.getName(), release.getNs()); + this.values = await helmReleasesApi.getValues(release.getName(), release.namespace); } - updateValues = async () => { + updateValues = async (): Promise => { const { release } = this.props; - const name = release.getName(); - const namespace = release.getNs() + const { namespace, name} = release; const data = { chart: release.getChart(), repo: await release.getRepo(), @@ -96,13 +101,13 @@ export class ReleaseDetails extends Component { this.saving = false; } - upgradeVersion = () => { + upgradeVersion = (): void => { const { release, hideDetails } = this.props; createUpgradeChartTab(release); hideDetails(); } - renderValues() { + renderValues(): JSX.Element { const { values, saving } = this; return (
@@ -111,7 +116,7 @@ export class ReleaseDetails extends Component { this.values = values} + onChange={(values): string => this.values = values} />
- ) + ); } - renderNotes() { - if (!this.details.info?.notes) return null; + renderNotes(): JSX.Element { + if (!this.details.info?.notes) { + return null; + } const { notes } = this.details.info; return (
@@ -134,9 +141,11 @@ export class ReleaseDetails extends Component { ); } - renderResources() { + renderResources(): JSX.Element { const { resources } = this.details; - if (!resources) return null; + if (!resources) { + return null; + } const groups = groupBy(resources, item => item.kind); const tables = Object.entries(groups).map(([kind, items]) => { return ( @@ -177,10 +186,12 @@ export class ReleaseDetails extends Component { ); } - renderContent() { + renderContent(): JSX.Element { const { release } = this.props; const { details } = this; - if (!release) return null; + if (!release) { + return null; + } if (!details) { return ; } @@ -201,7 +212,7 @@ export class ReleaseDetails extends Component { {release.getUpdated()} ago ({release.updated}) Namespace}> - {release.getNs()} + {release.namespace} Version} onClick={stopPropagation}>
@@ -222,13 +233,13 @@ export class ReleaseDetails extends Component { {this.renderResources()}
- ) + ); } - render() { - const { release, hideDetails } = this.props - const title = release ? Release: {release.getName()} : "" - const toolbar = + render(): JSX.Element { + const { release, hideDetails } = this.props; + const title = release ? Release: {release.getName()} : ""; + const toolbar = ; return ( { > {this.renderContent()} - ) + ); } } diff --git a/dashboard/client/components/+apps-releases/release-menu.tsx b/dashboard/client/components/+apps-releases/release-menu.tsx index aaea69ade9..f041536a01 100644 --- a/dashboard/client/components/+apps-releases/release-menu.tsx +++ b/dashboard/client/components/+apps-releases/release-menu.tsx @@ -17,26 +17,28 @@ interface Props extends MenuActionsProps { export class HelmReleaseMenu extends React.Component { @autobind() - remove() { + remove(): Promise { return releaseStore.remove(this.props.release); } @autobind() - upgrade() { + upgrade(): void { const { release, hideDetails } = this.props; createUpgradeChartTab(release); hideDetails && hideDetails(); } @autobind() - rollback() { + rollback(): void { ReleaseRollbackDialog.open(this.props.release); } - renderContent() { + renderContent(): JSX.Element { const { release, toolbar } = this.props; - if (!release) return; - const hasRollback = release && release.getRevision() > 1; + if (!release) { + return; + } + const hasRollback = release && release.revision > 1; return ( <> {hasRollback && ( @@ -46,18 +48,19 @@ export class HelmReleaseMenu extends React.Component { )} - ) + ); } - render() { - const { className, release, ...menuProps } = this.props; + render(): JSX.Element { + const { className, release: _release, ...menuProps } = this.props; return ( + > + {this.renderContent()} + ); } } diff --git a/dashboard/client/components/+apps-releases/release-rollback-dialog.tsx b/dashboard/client/components/+apps-releases/release-rollback-dialog.tsx index 6a8f39dda3..56c23b97fc 100644 --- a/dashboard/client/components/+apps-releases/release-rollback-dialog.tsx +++ b/dashboard/client/components/+apps-releases/release-rollback-dialog.tsx @@ -6,11 +6,11 @@ import { observer } from "mobx-react"; import { Trans } from "@lingui/macro"; import { Dialog, DialogProps } from "../dialog"; import { Wizard, WizardStep } from "../wizard"; -import { HelmRelease, helmReleasesApi, IReleaseRevision } from "../../api/endpoints/helm-releases.api"; +import { HelmRelease, helmReleasesApi, ReleaseRevision } from "../../api/endpoints/helm-releases.api"; import { releaseStore } from "./release.store"; import { Select, SelectOption } from "../select"; import { Notifications } from "../notifications"; -import orderBy from "lodash/orderBy" +import orderBy from "lodash/orderBy"; interface Props extends DialogProps { } @@ -21,15 +21,15 @@ export class ReleaseRollbackDialog extends React.Component { @observable.ref static release: HelmRelease = null; @observable isLoading = false; - @observable revision: IReleaseRevision; - @observable revisions = observable.array(); + @observable revision: ReleaseRevision; + @observable revisions = observable.array(); - static open(release: HelmRelease) { + static open(release: HelmRelease): void { ReleaseRollbackDialog.isOpen = true; ReleaseRollbackDialog.release = release; } - static close() { + static close(): void { ReleaseRollbackDialog.isOpen = false; } @@ -37,10 +37,10 @@ export class ReleaseRollbackDialog extends React.Component { return ReleaseRollbackDialog.release; } - onOpen = async () => { + onOpen = async (): Promise => { this.isLoading = true; - const currentRevision = this.release.getRevision(); - let releases = await helmReleasesApi.getHistory(this.release.getName(), this.release.getNs()); + const currentRevision = this.release.revision; + let releases = await helmReleasesApi.getHistory(this.release.getName(), this.release.namespace); releases = releases.filter(item => item.revision !== currentRevision); // remove current releases = orderBy(releases, "revision", "desc"); // sort this.revisions.replace(releases); @@ -48,24 +48,24 @@ export class ReleaseRollbackDialog extends React.Component { this.isLoading = false; } - rollback = async () => { + rollback = async (): Promise => { const revisionNumber = this.revision.revision; try { - await releaseStore.rollback(this.release.getName(), this.release.getNs(), revisionNumber); + await releaseStore.rollback(this.release.getName(), this.release.namespace, revisionNumber); this.close(); } catch (err) { Notifications.error(err); } }; - close = () => { + close = (): void => { ReleaseRollbackDialog.close(); } - renderContent() { + renderContent(): JSX.Element { const { revision, revisions } = this; if (!revision) { - return

No revisions to rollback.

+ return

No revisions to rollback.

; } return (
@@ -74,17 +74,19 @@ export class ReleaseRollbackDialog extends React.Component { themeName="light" value={revision} options={revisions} - formatOptionLabel={({ value }: SelectOption) => `${value.revision} - ${value.chart}`} - onChange={({ value }: SelectOption) => this.revision = value} + formatOptionLabel={({ value }: SelectOption): string => `${value.revision} - ${value.chart}`} + onChange={({ value }: SelectOption): void => { + this.revision = value; + }} />
- ) + ); } - render() { + render(): JSX.Element { const { ...dialogProps } = this.props; const releaseName = this.release ? this.release.getName() : ""; - const header =
Rollback {releaseName}
+ const header =
Rollback {releaseName}
; return ( { - ) + ); } } diff --git a/dashboard/client/components/+apps-releases/release.route.ts b/dashboard/client/components/+apps-releases/release.route.ts index 673a875b08..0373d170ec 100644 --- a/dashboard/client/components/+apps-releases/release.route.ts +++ b/dashboard/client/components/+apps-releases/release.route.ts @@ -1,14 +1,14 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { appsRoute } from "../+apps/apps.route"; import { buildURL } from "../../navigation"; export const releaseRoute: RouteProps = { path: appsRoute.path + "/releases/:namespace?/:name?" -} +}; -export interface IReleaseRouteParams { +export interface ReleaseRouteParams { name?: string; namespace?: string; } -export const releaseURL = buildURL(releaseRoute.path); +export const releaseURL = buildURL(releaseRoute.path); diff --git a/dashboard/client/components/+apps-releases/release.store.ts b/dashboard/client/components/+apps-releases/release.store.ts index 6ef02fca6b..6a722c9d3d 100644 --- a/dashboard/client/components/+apps-releases/release.store.ts +++ b/dashboard/client/components/+apps-releases/release.store.ts @@ -1,11 +1,12 @@ import isEqual from "lodash/isEqual"; import { action, observable, when, IReactionDisposer, reaction } from "mobx"; import { autobind } from "../../utils"; -import { HelmRelease, helmReleasesApi, IReleaseCreatePayload, IReleaseUpdatePayload } from "../../api/endpoints/helm-releases.api"; +import { HelmRelease, helmReleasesApi, ReleaseCreatePayload, ReleaseUpdatePayload, ReleaseUpdateDetails } from "../../api/endpoints/helm-releases.api"; import { ItemStore } from "../../item.store"; import { configStore } from "../../config.store"; import { secretsStore } from "../+config-secrets/secrets.store"; import { Secret } from "../../api/endpoints"; +import { KubeJsonApiData } from "client/api/kube-json-api"; @autobind() export class ReleaseStore extends ItemStore { @@ -19,47 +20,51 @@ export class ReleaseStore extends ItemStore { }); } - watch() { + watch(): void { this.secretWatcher = reaction(() => secretsStore.items.toJS(), () => { - if (this.isLoading) return; + if (this.isLoading) { + return; + } const secrets = this.getReleaseSecrets(); const amountChanged = secrets.length !== this.releaseSecrets.length; const labelsChanged = this.releaseSecrets.some(item => { const secret = secrets.find(secret => secret.getId() == item.getId()); - if (!secret) return; + if (!secret) { + return; + } return !isEqual(item.getLabels(), secret.getLabels()); }); if (amountChanged || labelsChanged) { this.loadAll(); } this.releaseSecrets = [...secrets]; - }) + }); } - unwatch() { + unwatch(): void { this.secretWatcher(); } - getReleaseSecrets() { + getReleaseSecrets(): Secret[] { return secretsStore.getByLabel({ owner: "helm" }); } - getReleaseSecret(release: HelmRelease) { + getReleaseSecret(release: HelmRelease): Secret { const labels = { owner: "helm", name: release.getName() - } + }; return secretsStore.getByLabel(labels) - .filter(secret => secret.getNs() == release.getNs())[0]; + .filter(secret => secret.getNs() == release.namespace)[0]; } @action - async loadAll() { + async loadAll(): Promise { this.isLoading = true; - let items; + let items: HelmRelease[]; try { const { isClusterAdmin, allowedNamespaces } = configStore; - items = await this.loadItems(!isClusterAdmin ? allowedNamespaces : null); + items = await this.loadItems(...(!isClusterAdmin ? allowedNamespaces : [])); } finally { if (items) { items = this.sortItems(items); @@ -70,42 +75,49 @@ export class ReleaseStore extends ItemStore { } } - async loadItems(namespaces?: string[]) { + async loadItems(...namespaces: any[]): Promise { if (!namespaces) { return helmReleasesApi.list(); - } - else { + } else { return Promise .all(namespaces.map(namespace => helmReleasesApi.list(namespace))) .then(items => items.flat()); } } - async create(payload: IReleaseCreatePayload) { + async create(payload: ReleaseCreatePayload): Promise { const response = await helmReleasesApi.create(payload); - if (this.isLoaded) this.loadAll(); + if (this.isLoaded) { + this.loadAll(); + } return response; } - async update(name: string, namespace: string, payload: IReleaseUpdatePayload) { + async update(name: string, namespace: string, payload: ReleaseUpdatePayload): Promise { const response = await helmReleasesApi.update(name, namespace, payload); - if (this.isLoaded) this.loadAll(); + if (this.isLoaded) { + this.loadAll(); + } return response; } - async rollback(name: string, namespace: string, revision: number) { + async rollback(name: string, namespace: string, revision: number): Promise { const response = await helmReleasesApi.rollback(name, namespace, revision); - if (this.isLoaded) this.loadAll(); + if (this.isLoaded) { + this.loadAll(); + } return response; } - async remove(release: HelmRelease) { - return super.removeItem(release, () => helmReleasesApi.delete(release.getName(), release.getNs())); + async remove(release: HelmRelease): Promise { + return super.removeItem(release, () => helmReleasesApi.delete(release.getName(), release.namespace)); } - async removeSelectedItems() { - if (!this.selectedItems.length) return; - return Promise.all(this.selectedItems.map(this.remove)); + async removeSelectedItems(): Promise { + if (!this.selectedItems.length) { + return; + } + await Promise.all(this.selectedItems.map(this.remove)); } } diff --git a/dashboard/client/components/+apps-releases/releases.tsx b/dashboard/client/components/+apps-releases/releases.tsx index 234a812543..a891b479aa 100644 --- a/dashboard/client/components/+apps-releases/releases.tsx +++ b/dashboard/client/components/+apps-releases/releases.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react"; import { Trans } from "@lingui/macro"; import { RouteComponentProps } from "react-router"; import { releaseStore } from "./release.store"; -import { IReleaseRouteParams, releaseURL } from "./release.route"; +import { ReleaseRouteParams, releaseURL } from "./release.route"; import { HelmRelease } from "../../api/endpoints/helm-releases.api"; import { ReleaseDetails } from "./release-details"; import { ReleaseRollbackDialog } from "./release-rollback-dialog"; @@ -24,59 +24,58 @@ enum sortBy { updated = "update" } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class HelmReleases extends Component { - componentDidMount() { + componentDidMount(): void { // Watch for secrets associated with releases and react to their changes releaseStore.watch(); } - componentWillUnmount() { + componentWillUnmount(): void { releaseStore.unwatch(); } - get selectedRelease() { + get selectedRelease(): HelmRelease { const { match: { params: { name, namespace } } } = this.props; return releaseStore.items.find(release => { - return release.getName() == name && release.getNs() == namespace; + return release.getName() == name && release.namespace == namespace; }); } - showDetails = (item: HelmRelease) => { + showDetails = (item: HelmRelease): void => { if (!item) { - navigation.merge(releaseURL()) - } - else { + navigation.merge(releaseURL()); + } else { navigation.merge(releaseURL({ params: { name: item.getName(), - namespace: item.getNs() + namespace: item.namespace } - })) + })); } } - hideDetails = () => { + hideDetails = (): void => { this.showDetails(null); } - renderRemoveDialogMessage(selectedItems: HelmRelease[]) { + renderRemoveDialogMessage(selectedItems: HelmRelease[]): JSX.Element { const releaseNames = selectedItems.map(item => item.getName()).join(", "); return (
Remove {releaseNames}?

- Note: StatefulSet Volumes won't be deleted automatically + Note: StatefulSet Volumes won't be deleted automatically

- ) + ); } - render() { + render(): JSX.Element { return ( <> { store={releaseStore} dependentStores={[secretsStore]} sortingCallbacks={{ - [sortBy.name]: (release: HelmRelease) => release.getName(), - [sortBy.namespace]: (release: HelmRelease) => release.getNs(), - [sortBy.revision]: (release: HelmRelease) => release.getRevision(), - [sortBy.chart]: (release: HelmRelease) => release.getChart(), - [sortBy.status]: (release: HelmRelease) => release.getStatus(), - [sortBy.updated]: (release: HelmRelease) => release.getUpdated(false, false), + [sortBy.name]: (release: HelmRelease): string => release.getName(), + [sortBy.namespace]: (release: HelmRelease): string => release.namespace, + [sortBy.revision]: (release: HelmRelease): number => release.revision, + [sortBy.chart]: (release: HelmRelease): string => release.getChart(), + [sortBy.status]: (release: HelmRelease): string => release.status, + [sortBy.updated]: (release: HelmRelease): string | number => release.getUpdated(false, false), }} searchFilters={[ - (release: HelmRelease) => release.getName(), - (release: HelmRelease) => release.getNs(), - (release: HelmRelease) => release.getChart(), - (release: HelmRelease) => release.getStatus(), - (release: HelmRelease) => release.getVersion(), + (release: HelmRelease): string => release.getName(), + (release: HelmRelease): string => release.namespace, + (release: HelmRelease): string => release.getChart(), + (release: HelmRelease): string => release.getStatus(), + (release: HelmRelease): string | number => release.getVersion(), ]} renderHeaderTitle={Releases} renderTableHeader={[ @@ -109,30 +108,30 @@ export class HelmReleases extends Component { { title: Status, className: "status", sortBy: sortBy.status }, { title: Updated, className: "updated", sortBy: sortBy.updated }, ]} - renderTableContents={(release: HelmRelease) => { + renderTableContents={(release: HelmRelease): (string | number | React.ReactNode)[] => { const version = release.getVersion(); return [ release.getName(), - release.getNs(), + release.namespace, release.getChart(), - release.getRevision(), + release.revision, <> {version} , release.appVersion, { title: release.getStatus(), className: kebabCase(release.getStatus()) }, release.getUpdated(), - ] + ]; }} - renderItemMenu={(release: HelmRelease) => { + renderItemMenu={(release: HelmRelease): JSX.Element => { return ( - ) + ); }} - customizeRemoveDialog={(selectedItems: HelmRelease[]) => ({ + customizeRemoveDialog={(selectedItems: HelmRelease[]): {message: JSX.Element} => ({ message: this.renderRemoveDialogMessage(selectedItems) })} detailsItem={this.selectedRelease} diff --git a/dashboard/client/components/+apps/apps.tsx b/dashboard/client/components/+apps/apps.tsx index 8d54a0cee6..32cb47a25c 100644 --- a/dashboard/client/components/+apps/apps.tsx +++ b/dashboard/client/components/+apps/apps.tsx @@ -24,10 +24,10 @@ export class Apps extends React.Component { url: releaseURL({ query }), path: releaseRoute.path, }, - ] + ]; } - render() { + render(): JSX.Element { const tabRoutes = Apps.tabRoutes; return ( @@ -36,6 +36,6 @@ export class Apps extends React.Component { - ) + ); } } diff --git a/dashboard/client/components/+cluster/cluster-issues.tsx b/dashboard/client/components/+cluster/cluster-issues.tsx index 64077cba4a..49de9c53fe 100644 --- a/dashboard/client/components/+cluster/cluster-issues.tsx +++ b/dashboard/client/components/+cluster/cluster-issues.tsx @@ -1,4 +1,4 @@ -import "./cluster-issues.scss" +import "./cluster-issues.scss"; import * as React from "react"; import { observer } from "mobx-react"; @@ -20,7 +20,7 @@ interface Props { className?: string; } -interface IWarning extends ItemObject { +interface Warning extends ItemObject { kind: string; message: string; selfLink: string; @@ -34,16 +34,16 @@ enum sortBy { @observer export class ClusterIssues extends React.Component { private sortCallbacks = { - [sortBy.type]: (warning: IWarning) => warning.kind, - [sortBy.object]: (warning: IWarning) => warning.getName(), + [sortBy.type]: (warning: Warning): string => warning.kind, + [sortBy.object]: (warning: Warning): string => warning.getName(), }; - @computed get warnings() { - const warnings: IWarning[] = []; + @computed get warnings(): Warning[] { + const warnings: Warning[] = []; // Node bad conditions nodesStore.items.forEach(node => { - const { kind, selfLink, getId, getName } = node + const { kind, selfLink, getId, getName } = node; node.getWarningConditions().forEach(({ message }) => { warnings.push({ kind, @@ -51,8 +51,8 @@ export class ClusterIssues extends React.Component { getName, selfLink, message, - }) - }) + }); + }); }); // Warning events for Workloads @@ -67,13 +67,13 @@ export class ClusterIssues extends React.Component { kind, selfLink: lookupApiLink(involvedObject, error), }); - }) + }); return warnings; } @autobind() - getTableRow(uid: string) { + getTableRow(uid: string): JSX.Element { const { warnings } = this; const warning = warnings.find(warn => warn.getId() == uid); const { getId, getName, message, kind, selfLink } = warning; @@ -97,7 +97,7 @@ export class ClusterIssues extends React.Component { ); } - renderContent() { + renderContent(): JSX.Element { const { warnings } = this; if (!eventStore.isLoaded) { return ( @@ -139,7 +139,7 @@ export class ClusterIssues extends React.Component { ); } - render() { + render(): JSX.Element { return (
{this.renderContent()} diff --git a/dashboard/client/components/+cluster/cluster-metric-switchers.tsx b/dashboard/client/components/+cluster/cluster-metric-switchers.tsx index 76c9ca6a0c..ad5ab31a0b 100644 --- a/dashboard/client/components/+cluster/cluster-metric-switchers.tsx +++ b/dashboard/client/components/+cluster/cluster-metric-switchers.tsx @@ -21,7 +21,9 @@ export const ClusterMetricSwitchers = observer(() => { asButtons className={cssNames("RadioGroup flex gaps", { disabled: disableRoles })} value={metricNodeRole} - onChange={(metric: MetricNodeRole) => clusterStore.metricNodeRole = metric} + onChange={(metric: MetricNodeRole): void => { + clusterStore.metricNodeRole = metric; + }} > Master} value={MetricNodeRole.MASTER}/> Worker} value={MetricNodeRole.WORKER}/> @@ -32,7 +34,9 @@ export const ClusterMetricSwitchers = observer(() => { asButtons className={cssNames("RadioGroup flex gaps", { disabled: disableMetrics })} value={metricType} - onChange={(value: MetricType) => clusterStore.metricType = value} + onChange={(value: MetricType): void => { + clusterStore.metricType = value; + }} > CPU} value={MetricType.CPU}/> Memory} value={MetricType.MEMORY}/> diff --git a/dashboard/client/components/+cluster/cluster-metrics.tsx b/dashboard/client/components/+cluster/cluster-metrics.tsx index 077adde70a..a3b556ed53 100644 --- a/dashboard/client/components/+cluster/cluster-metrics.tsx +++ b/dashboard/client/components/+cluster/cluster-metrics.tsx @@ -34,13 +34,13 @@ export const ClusterMetrics = observer(() => { yAxes: [{ ticks: { suggestedMax: cpuCapacity, - callback: (value) => value + callback: (value): any => value } }] }, tooltips: { callbacks: { - label: ({ index }, data) => { + label: ({ index }, data): string => { const value = data.datasets[0].data[index] as ChartPoint; return value.y.toString(); } @@ -52,13 +52,13 @@ export const ClusterMetrics = observer(() => { yAxes: [{ ticks: { suggestedMax: memoryCapacity, - callback: (value: string) => !value ? 0 : bytesToUnits(parseInt(value)) + callback: (value: string): string | number => !value ? 0 : bytesToUnits(parseInt(value)) } }] }, tooltips: { callbacks: { - label: ({ index }, data) => { + label: ({ index }, data): string => { const value = data.datasets[0].data[index] as ChartPoint; return bytesToUnits(parseInt(value.y as string), 3); } @@ -67,12 +67,12 @@ export const ClusterMetrics = observer(() => { }; const options = metricType === MetricType.CPU ? cpuOptions : memoryOptions; - const renderMetrics = () => { + const renderMetrics = (): JSX.Element => { if ((!metricValues.length || !liveMetricValues.length) && !metricsLoaded) { return ; } if (!memoryCapacity || !cpuCapacity) { - return + return ; } return ( diff --git a/dashboard/client/components/+cluster/cluster-pie-charts.tsx b/dashboard/client/components/+cluster/cluster-pie-charts.tsx index 6b0bf562f8..0fc410a206 100644 --- a/dashboard/client/components/+cluster/cluster-pie-charts.tsx +++ b/dashboard/client/components/+cluster/cluster-pie-charts.tsx @@ -17,16 +17,16 @@ import { getMetricLastPoints } from "../../api/endpoints/metrics.api"; export const ClusterPieCharts = observer(() => { const { i18n } = useLingui(); - const renderLimitWarning = () => { + const renderLimitWarning = (): JSX.Element => { return (

Specified limits are higher than node capacity!

); - } + }; - const renderCharts = () => { + const renderCharts = (): JSX.Element => { const data = getMetricLastPoints(clusterStore.metrics); const { memoryUsage, memoryRequests, memoryCapacity, memoryLimits } = data; const { cpuUsage, cpuRequests, cpuCapacity, cpuLimits } = data; @@ -35,7 +35,9 @@ export const ClusterPieCharts = observer(() => { const memoryLimitsOverload = memoryLimits > memoryCapacity; const defaultColor = themeStore.activeTheme.colors.pieChartDefaultColor; - if (!memoryCapacity || !cpuCapacity || !podCapacity) return null; + if (!memoryCapacity || !cpuCapacity || !podCapacity) { + return null; + } const cpuData: ChartData = { datasets: [ { @@ -168,9 +170,9 @@ export const ClusterPieCharts = observer(() => {
); - } + }; - const renderContent = () => { + const renderContent = (): JSX.Element => { const { masterNodes, workerNodes } = nodesStore; const { metricNodeRole, metricsLoaded } = clusterStore; const nodes = metricNodeRole === MetricNodeRole.MASTER ? masterNodes : workerNodes; @@ -194,11 +196,11 @@ export const ClusterPieCharts = observer(() => { return ; } return renderCharts(); - } + }; return (
{renderContent()}
); -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+cluster/cluster.routes.ts b/dashboard/client/components/+cluster/cluster.routes.ts index f541d83945..01624f057b 100644 --- a/dashboard/client/components/+cluster/cluster.routes.ts +++ b/dashboard/client/components/+cluster/cluster.routes.ts @@ -3,6 +3,6 @@ import { buildURL } from "../../navigation"; export const clusterRoute: RouteProps = { path: "/cluster" -} +}; -export const clusterURL = buildURL(clusterRoute.path) +export const clusterURL = buildURL(clusterRoute.path); diff --git a/dashboard/client/components/+cluster/cluster.store.ts b/dashboard/client/components/+cluster/cluster.store.ts index 01c4e0f013..1de8f2acb3 100644 --- a/dashboard/client/components/+cluster/cluster.store.ts +++ b/dashboard/client/components/+cluster/cluster.store.ts @@ -1,8 +1,8 @@ import { observable, reaction, when } from "mobx"; import { KubeObjectStore } from "../../kube-object.store"; -import { Cluster, clusterApi, IClusterMetrics } from "../../api/endpoints"; -import { autobind, createStorage } from "../../utils"; -import { IMetricsReqParams, normalizeMetrics } from "../../api/endpoints/metrics.api"; +import { Cluster, clusterApi, ClusterMetrics } from "../../api/endpoints"; +import { autobind, StorageHelper } from "../../utils"; +import { MetricsReqParams, normalizeMetrics, Metrics } from "../../api/endpoints/metrics.api"; import { nodesStore } from "../+nodes/nodes.store"; import { apiManager } from "../../api/api-manager"; @@ -20,8 +20,8 @@ export enum MetricNodeRole { export class ClusterStore extends KubeObjectStore { api = clusterApi - @observable metrics: Partial = {}; - @observable liveMetrics: Partial = {}; + @observable metrics: Partial = {}; + @observable liveMetrics: Partial = {}; @observable metricsLoaded = false; @observable metricType: MetricType; @observable metricNodeRole: MetricNodeRole; @@ -31,18 +31,20 @@ export class ClusterStore extends KubeObjectStore { this.resetMetrics(); // sync user setting with local storage - const storage = createStorage("cluster_metric_switchers", {}); + const storage = new StorageHelper("cluster_metric_switchers", {}); Object.assign(this, storage.get()); reaction(() => { const { metricType, metricNodeRole } = this; - return { metricType, metricNodeRole } + return { metricType, metricNodeRole }; }, settings => storage.set(settings) ); // auto-update metrics reaction(() => this.metricNodeRole, () => { - if (!this.metricsLoaded) return; + if (!this.metricsLoaded) { + return; + } this.metrics = {}; this.liveMetrics = {}; this.metricsLoaded = false; @@ -52,29 +54,33 @@ export class ClusterStore extends KubeObjectStore { // check which node type to select reaction(() => nodesStore.items.length, () => { const { masterNodes, workerNodes } = nodesStore; - if (!masterNodes.length) this.metricNodeRole = MetricNodeRole.WORKER; - if (!workerNodes.length) this.metricNodeRole = MetricNodeRole.MASTER; + if (!masterNodes.length) { + this.metricNodeRole = MetricNodeRole.WORKER; + } + if (!workerNodes.length) { + this.metricNodeRole = MetricNodeRole.MASTER; + } }); } - async loadMetrics(params?: IMetricsReqParams) { + async loadMetrics(params?: MetricsReqParams): Promise> { await when(() => nodesStore.isLoaded); const { masterNodes, workerNodes } = nodesStore; const nodes = this.metricNodeRole === MetricNodeRole.MASTER && masterNodes.length ? masterNodes : workerNodes; return clusterApi.getMetrics(nodes.map(node => node.getName()), params); } - async getAllMetrics() { + async getAllMetrics(): Promise { await this.getMetrics(); await this.getLiveMetrics(); this.metricsLoaded = true; } - async getMetrics() { + async getMetrics(): Promise { this.metrics = await this.loadMetrics(); } - async getLiveMetrics() { + async getLiveMetrics(): Promise { const step = 3; const range = 15; const end = Date.now() / 1000; @@ -82,25 +88,25 @@ export class ClusterStore extends KubeObjectStore { this.liveMetrics = await this.loadMetrics({ start, end, step, range }); } - getMetricsValues(source: Partial): [number, string][] { + getMetricsValues(source: Partial): [number, string][] { switch (this.metricType) { case MetricType.CPU: - return normalizeMetrics(source.cpuUsage).data.result[0].values + return normalizeMetrics(source.cpuUsage).data.result[0].values; case MetricType.MEMORY: - return normalizeMetrics(source.memoryUsage).data.result[0].values + return normalizeMetrics(source.memoryUsage).data.result[0].values; default: return []; } } - resetMetrics() { + resetMetrics(): void { this.metrics = {}; this.metricsLoaded = false; this.metricType = MetricType.CPU; this.metricNodeRole = MetricNodeRole.WORKER; } - reset() { + reset(): void { super.reset(); this.resetMetrics(); } diff --git a/dashboard/client/components/+cluster/cluster.tsx b/dashboard/client/components/+cluster/cluster.tsx index 13df2cad0a..2558ba96e1 100644 --- a/dashboard/client/components/+cluster/cluster.tsx +++ b/dashboard/client/components/+cluster/cluster.tsx @@ -1,4 +1,4 @@ -import "./cluster.scss" +import "./cluster.scss"; import React from "react"; import { computed, reaction } from "mobx"; @@ -6,7 +6,7 @@ import { disposeOnUnmount, observer } from "mobx-react"; import { MainLayout } from "../layout/main-layout"; import { ClusterIssues } from "./cluster-issues"; import { Spinner } from "../spinner"; -import { cssNames, interval, isElectron } from "../../utils"; +import { cssNames, IntervalManager, isElectron } from "../../utils"; import { ClusterPieCharts } from "./cluster-pie-charts"; import { ClusterMetrics } from "./cluster-metrics"; import { nodesStore } from "../+nodes/nodes.store"; @@ -18,16 +18,16 @@ import { isAllowedResource } from "../../api/rbac"; @observer export class Cluster extends React.Component { private watchers = [ - interval(60, () => clusterStore.getMetrics()), - interval(20, () => eventStore.loadAll()) + new IntervalManager(60, () => clusterStore.getMetrics()), + new IntervalManager(20, () => eventStore.loadAll()) ]; private dependentStores = [nodesStore, podsStore]; - async componentDidMount() { + async componentDidMount(): Promise { const { dependentStores } = this; if (!isAllowedResource("nodes")) { - dependentStores.splice(dependentStores.indexOf(nodesStore), 1) + dependentStores.splice(dependentStores.indexOf(nodesStore), 1); } this.watchers.forEach(watcher => watcher.start(true)); @@ -38,22 +38,22 @@ export class Cluster extends React.Component { disposeOnUnmount(this, [ ...dependentStores.map(store => store.subscribe()), - () => this.watchers.forEach(watcher => watcher.stop()), + (): void => this.watchers.forEach(watcher => watcher.stop()), reaction( () => clusterStore.metricNodeRole, () => this.watchers.forEach(watcher => watcher.restart()) ) - ]) + ]); } - @computed get isLoaded() { + @computed get isLoaded(): boolean { return ( nodesStore.isLoaded && podsStore.isLoaded - ) + ); } - render() { + render(): JSX.Element { const { isLoaded } = this; return ( @@ -68,6 +68,6 @@ export class Cluster extends React.Component { )}
- ) + ); } } diff --git a/dashboard/client/components/+cluster/index.ts b/dashboard/client/components/+cluster/index.ts index dfc259440e..1568cd8248 100644 --- a/dashboard/client/components/+cluster/index.ts +++ b/dashboard/client/components/+cluster/index.ts @@ -1,2 +1,2 @@ -export * from "./cluster.routes" +export * from "./cluster.routes"; diff --git a/dashboard/client/components/+config-autoscalers/hpa-details.tsx b/dashboard/client/components/+config-autoscalers/hpa-details.tsx index db068885f5..81a8c60449 100644 --- a/dashboard/client/components/+config-autoscalers/hpa-details.tsx +++ b/dashboard/client/components/+config-autoscalers/hpa-details.tsx @@ -7,7 +7,7 @@ import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; import { KubeObjectDetailsProps } from "../kube-object"; import { cssNames } from "../../utils"; -import { HorizontalPodAutoscaler, hpaApi, HpaMetricType, IHpaMetric } from "../../api/endpoints/hpa.api"; +import { HorizontalPodAutoscaler, hpaApi, HpaMetricType, HpaMetric } from "../../api/endpoints/hpa.api"; import { KubeEventDetails } from "../+events/kube-event-details"; import { Trans } from "@lingui/macro"; import { Table, TableCell, TableHead, TableRow } from "../table"; @@ -21,10 +21,10 @@ interface Props extends KubeObjectDetailsProps { @observer export class HpaDetails extends React.Component { - renderMetrics() { + renderMetrics(): JSX.Element { const { object: hpa } = this.props; - const renderName = (metric: IHpaMetric) => { + const renderName = (metric: HpaMetric): JSX.Element => { switch (metric.type) { case HpaMetricType.Resource: const addition = metric.resource.targetAverageUtilization ? (as a percentage of request) : ""; @@ -51,7 +51,7 @@ export class HpaDetails extends React.Component { ); } - } + }; return ( @@ -60,7 +60,7 @@ export class HpaDetails extends React.Component { Current / Target { - hpa.getMetrics().map((metric, index) => { + hpa.spec.metrics.map((metric, index) => { const name = renderName(metric); const values = hpa.getMetricValues(metric); return ( @@ -68,16 +68,18 @@ export class HpaDetails extends React.Component { {name} {values} - ) + ); }) }
); } - render() { + render(): JSX.Element { const { object: hpa } = this.props; - if (!hpa) return; + if (!hpa) { + return; + } const { scaleTargetRef } = hpa.spec; return (
@@ -92,20 +94,22 @@ export class HpaDetails extends React.Component { Min Pods}> - {hpa.getMinPods()} + {hpa.spec.minReplicas} Max Pods}> - {hpa.getMaxPods()} + {hpa.spec.maxReplicas} Replicas}> - {hpa.getReplicas()} + {hpa.status.currentReplicas} Status} labelsOnly> {hpa.getConditions().map(({ type, tooltip, isReady }) => { - if (!isReady) return null; + if (!isReady) { + return null; + } return ( { tooltip={tooltip} className={cssNames({ [type.toLowerCase()]: isReady })} /> - ) + ); })} diff --git a/dashboard/client/components/+config-autoscalers/hpa.route.ts b/dashboard/client/components/+config-autoscalers/hpa.route.ts index 500b260062..c76789a713 100644 --- a/dashboard/client/components/+config-autoscalers/hpa.route.ts +++ b/dashboard/client/components/+config-autoscalers/hpa.route.ts @@ -3,9 +3,9 @@ import { buildURL } from "../../navigation"; export const hpaRoute: RouteProps = { path: "/hpa" +}; + +export interface HpaRouteParams { } -export interface IHpaRouteParams { -} - -export const hpaURL = buildURL(hpaRoute.path) +export const hpaURL = buildURL(hpaRoute.path); diff --git a/dashboard/client/components/+config-autoscalers/hpa.tsx b/dashboard/client/components/+config-autoscalers/hpa.tsx index 0017617d94..b6574b3b2a 100644 --- a/dashboard/client/components/+config-autoscalers/hpa.tsx +++ b/dashboard/client/components/+config-autoscalers/hpa.tsx @@ -1,4 +1,4 @@ -import "./hpa.scss" +import "./hpa.scss"; import * as React from "react"; import { observer } from "mobx-react"; @@ -6,7 +6,7 @@ import { RouteComponentProps } from "react-router"; import { Trans } from "@lingui/macro"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { KubeObjectListLayout } from "../kube-object"; -import { IHpaRouteParams } from "./hpa.route"; +import { HpaRouteParams } from "./hpa.route"; import { HorizontalPodAutoscaler, hpaApi } from "../../api/endpoints/hpa.api"; import { hpaStore } from "./hpa.store"; import { Badge } from "../badge"; @@ -22,32 +22,32 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class HorizontalPodAutoscalers extends React.Component { - getTargets(hpa: HorizontalPodAutoscaler) { - const metrics = hpa.getMetrics(); + getTargets(hpa: HorizontalPodAutoscaler): JSX.Element { + const { metrics } = hpa.spec; const metricsRemainCount = metrics.length - 1; const metricsRemain = metrics.length > 1 ? {metricsRemainCount} more... : null; const metricValues = hpa.getMetricValues(metrics[0]); return

{metricValues} {metricsRemain && "+"}{metricsRemain}

; } - render() { + render(): JSX.Element { return ( item.getName(), - [sortBy.namespace]: (item: HorizontalPodAutoscaler) => item.getNs(), - [sortBy.minPods]: (item: HorizontalPodAutoscaler) => item.getMinPods(), - [sortBy.maxPods]: (item: HorizontalPodAutoscaler) => item.getMaxPods(), - [sortBy.replicas]: (item: HorizontalPodAutoscaler) => item.getReplicas() + [sortBy.name]: (item: HorizontalPodAutoscaler): string => item.getName(), + [sortBy.namespace]: (item: HorizontalPodAutoscaler): string => item.getNs(), + [sortBy.minPods]: (item: HorizontalPodAutoscaler): number => item.spec.minReplicas, + [sortBy.maxPods]: (item: HorizontalPodAutoscaler): number => item.spec.maxReplicas, + [sortBy.replicas]: (item: HorizontalPodAutoscaler): number => item.status.currentReplicas }} searchFilters={[ - (item: HorizontalPodAutoscaler) => item.getSearchFields() + (item: HorizontalPodAutoscaler): string[] => item.getSearchFields() ]} renderHeaderTitle={Horizontal Pod Autoscalers} renderTableHeader={[ @@ -60,16 +60,18 @@ export class HorizontalPodAutoscalers extends React.Component { { title: Age, className: "age", sortBy: sortBy.age }, { title: Status, className: "status" }, ]} - renderTableContents={(hpa: HorizontalPodAutoscaler) => [ + renderTableContents={(hpa: HorizontalPodAutoscaler): (string | number | React.ReactNode)[] => [ hpa.getName(), hpa.getNs(), this.getTargets(hpa), - hpa.getMinPods(), - hpa.getMaxPods(), - hpa.getReplicas(), + hpa.spec.minReplicas, + hpa.spec.maxReplicas, + hpa.status.currentReplicas, hpa.getAge(), hpa.getConditions().map(({ type, tooltip, isReady }) => { - if (!isReady) return null; + if (!isReady) { + return null; + } return ( { tooltip={tooltip} className={cssNames(type.toLowerCase())} /> - ) + ); }) ]} - renderItemMenu={(item: HorizontalPodAutoscaler) => { - return + renderItemMenu={(item: HorizontalPodAutoscaler): JSX.Element => { + return ; }} /> ); } } -export function HpaMenu(props: KubeObjectMenuProps) { +export function HpaMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(hpaApi, { Menu: HpaMenu, -}) +}); diff --git a/dashboard/client/components/+config-autoscalers/index.ts b/dashboard/client/components/+config-autoscalers/index.ts index 4d3cedf89c..99d994add1 100644 --- a/dashboard/client/components/+config-autoscalers/index.ts +++ b/dashboard/client/components/+config-autoscalers/index.ts @@ -1,3 +1,3 @@ -export * from "./hpa" -export * from "./hpa-details" -export * from "./hpa.route" +export * from "./hpa"; +export * from "./hpa-details"; +export * from "./hpa.route"; diff --git a/dashboard/client/components/+config-maps/config-map-details.tsx b/dashboard/client/components/+config-maps/config-map-details.tsx index b676c1e1e4..cd856b83c9 100644 --- a/dashboard/client/components/+config-maps/config-map-details.tsx +++ b/dashboard/client/components/+config-maps/config-map-details.tsx @@ -23,7 +23,7 @@ export class ConfigMapDetails extends React.Component { @observable isSaving = false; @observable data = observable.map(); - async componentDidMount() { + componentDidMount(): void { disposeOnUnmount(this, [ autorun(() => { const { object: configMap } = this.props; @@ -31,10 +31,10 @@ export class ConfigMapDetails extends React.Component { this.data.replace(configMap.data); // refresh } }) - ]) + ]); } - save = async () => { + save = async (): Promise => { const { object: configMap } = this.props; try { this.isSaving = true; @@ -49,9 +49,11 @@ export class ConfigMapDetails extends React.Component { } } - render() { + render(): JSX.Element { const { object: configMap } = this.props; - if (!configMap) return null; + if (!configMap) { + return null; + } const data = Object.entries(this.data.toJSON()); return (
@@ -71,11 +73,13 @@ export class ConfigMapDetails extends React.Component { theme="round-black" className="box grow" value={value} - onChange={v => this.data.set(name, v)} + onChange={(v): void => { + this.data.set(name, v); + }} />
- ) + ); }) }
@@ -157,7 +166,7 @@ export class AddQuotaDialog extends React.Component { placeholder={_i18n._(t`Namespace`)} themeName="light" className="box grow" - onChange={({ value }) => this.namespace = value} + onChange={({ value }): void => this.namespace = value} /> Values}/> @@ -168,13 +177,15 @@ export class AddQuotaDialog extends React.Component { placeholder={_i18n._(t`Select a quota..`)} options={this.quotaOptions} value={this.quotaSelectValue} - onChange={({ value }) => this.quotaSelectValue = value} + onChange={({ value }): void => this.quotaSelectValue = value} /> this.quotaInputValue = v} + onChange={(v): void => { + this.quotaInputValue = v; + }} onKeyDown={this.onInputQuota} className="box grow" /> @@ -191,14 +202,16 @@ export class AddQuotaDialog extends React.Component {
{quota}
{value}
- this.quotas[quota] = ""}/> + { + this.quotas[quota] = ""; + }}/>
- ) + ); })} - ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/+config-resource-quotas/index.ts b/dashboard/client/components/+config-resource-quotas/index.ts index 792f8946c3..79fa3d8f0c 100644 --- a/dashboard/client/components/+config-resource-quotas/index.ts +++ b/dashboard/client/components/+config-resource-quotas/index.ts @@ -1,3 +1,3 @@ -export * from "./resource-quotas.route" -export * from "./resource-quotas" -export * from "./resource-quota-details" +export * from "./resource-quotas.route"; +export * from "./resource-quotas"; +export * from "./resource-quota-details"; diff --git a/dashboard/client/components/+config-resource-quotas/resource-quota-details.tsx b/dashboard/client/components/+config-resource-quotas/resource-quota-details.tsx index 6a2665e741..bd3ab991c4 100644 --- a/dashboard/client/components/+config-resource-quotas/resource-quota-details.tsx +++ b/dashboard/client/components/+config-resource-quotas/resource-quota-details.tsx @@ -17,22 +17,26 @@ interface Props extends KubeObjectDetailsProps { @observer export class ResourceQuotaDetails extends React.Component { - renderQuotas = (quota: ResourceQuota) => { - const { hard, used } = quota.status - if (!hard || !used) return null - const transformUnit = (name: string, value: string) => { + renderQuotas = (quota: ResourceQuota): JSX.Element[] => { + const { hard, used } = quota.status; + if (!hard || !used) { + return null; + } + const transformUnit = (name: string, value: string): number => { if (name.includes("memory") || name.includes("storage")) { - return unitsToBytes(value) + return unitsToBytes(value); } if (name.includes("cpu")) { - return cpuUnitsToNumber(value) + return cpuUnitsToNumber(value); } - return parseInt(value) - } + return parseInt(value); + }; return Object.entries(hard).map(([name, value]) => { - if (!used[name]) return null - const current = transformUnit(name, used[name]) - const max = transformUnit(name, value) + if (!used[name]) { + return null; + } + const current = transformUnit(name, used[name]); + const max = transformUnit(name, value); return (
{name} @@ -45,13 +49,15 @@ export class ResourceQuotaDetails extends React.Component { } />
- ) - }) + ); + }); } - render() { + render(): JSX.Element { const { object: quota } = this.props; - if (!quota) return null; + if (!quota) { + return null; + } return (
@@ -91,4 +97,4 @@ export class ResourceQuotaDetails extends React.Component { apiManager.registerViews(resourceQuotaApi, { Details: ResourceQuotaDetails -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+config-resource-quotas/resource-quotas.route.ts b/dashboard/client/components/+config-resource-quotas/resource-quotas.route.ts index 1040f2dc09..196705fcc2 100644 --- a/dashboard/client/components/+config-resource-quotas/resource-quotas.route.ts +++ b/dashboard/client/components/+config-resource-quotas/resource-quotas.route.ts @@ -3,9 +3,9 @@ import { buildURL } from "../../navigation"; export const resourceQuotaRoute: RouteProps = { path: "/resourcequotas" +}; + +export interface ResourceQuotaRouteParams { } -export interface IResourceQuotaRouteParams { -} - -export const resourceQuotaURL = buildURL(resourceQuotaRoute.path); +export const resourceQuotaURL = buildURL(resourceQuotaRoute.path); diff --git a/dashboard/client/components/+config-resource-quotas/resource-quotas.tsx b/dashboard/client/components/+config-resource-quotas/resource-quotas.tsx index 5cd62144a8..4b0fbec59d 100644 --- a/dashboard/client/components/+config-resource-quotas/resource-quotas.tsx +++ b/dashboard/client/components/+config-resource-quotas/resource-quotas.tsx @@ -9,7 +9,7 @@ import { KubeObjectListLayout } from "../kube-object"; import { ResourceQuota, resourceQuotaApi } from "../../api/endpoints/resource-quota.api"; import { AddQuotaDialog } from "./add-quota-dialog"; import { resourceQuotaStore } from "./resource-quotas.store"; -import { IResourceQuotaRouteParams } from "./resource-quotas.route"; +import { ResourceQuotaRouteParams } from "./resource-quotas.route"; import { apiManager } from "../../api/api-manager"; enum sortBy { @@ -18,24 +18,24 @@ enum sortBy { age = "age" } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class ResourceQuotas extends React.Component { - render() { + render(): JSX.Element { return ( <> item.getName(), - [sortBy.namespace]: (item: ResourceQuota) => item.getNs(), - [sortBy.age]: (item: ResourceQuota) => item.metadata.creationTimestamp, + [sortBy.name]: (item: ResourceQuota): string => item.getName(), + [sortBy.namespace]: (item: ResourceQuota): string => item.getNs(), + [sortBy.age]: (item: ResourceQuota): string => item.metadata.creationTimestamp, }} searchFilters={[ - (item: ResourceQuota) => item.getSearchFields(), - (item: ResourceQuota) => item.getName(), + (item: ResourceQuota): string[] => item.getSearchFields(), + (item: ResourceQuota): string => item.getName(), ]} renderHeaderTitle={Resource Quotas} renderTableHeader={[ @@ -43,16 +43,14 @@ export class ResourceQuotas extends React.Component { { title: Namespace, className: "namespace", sortBy: sortBy.namespace }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(resourceQuota: ResourceQuota) => [ + renderTableContents={(resourceQuota: ResourceQuota): (string | number)[] => [ resourceQuota.getName(), resourceQuota.getNs(), resourceQuota.getAge(), ]} - renderItemMenu={(item: ResourceQuota) => { - return - }} + renderItemMenu={(item: ResourceQuota): JSX.Element => } addRemoveButtons={{ - onAdd: () => AddQuotaDialog.open(), + onAdd: (): void => AddQuotaDialog.open(), addTooltip: Create new ResourceQuota }} /> @@ -62,7 +60,7 @@ export class ResourceQuotas extends React.Component { } } -export function ResourceQuotaMenu(props: KubeObjectMenuProps) { +export function ResourceQuotaMenu(props: KubeObjectMenuProps): JSX.Element { return ( ); @@ -70,4 +68,4 @@ export function ResourceQuotaMenu(props: KubeObjectMenuProps) { apiManager.registerViews(resourceQuotaApi, { Menu: ResourceQuotaMenu, -}) +}); diff --git a/dashboard/client/components/+config-secrets/add-secret-dialog.tsx b/dashboard/client/components/+config-secrets/add-secret-dialog.tsx index b41c34ee3b..3972446287 100644 --- a/dashboard/client/components/+config-secrets/add-secret-dialog.tsx +++ b/dashboard/client/components/+config-secrets/add-secret-dialog.tsx @@ -1,4 +1,4 @@ -import "./add-secret-dialog.scss" +import "./add-secret-dialog.scss"; import React from "react"; import { observable } from "mobx"; @@ -14,7 +14,7 @@ import { SubTitle } from "../layout/sub-title"; import { NamespaceSelect } from "../+namespaces/namespace-select"; import { Select, SelectOption } from "../select"; import { Icon } from "../icon"; -import { IKubeObjectMetadata } from "../../api/kube-object"; +import { KubeObjectMetadata } from "../../api/kube-object"; import { base64 } from "../../utils"; import { Notifications } from "../notifications"; import { showDetails } from "../../navigation"; @@ -23,34 +23,41 @@ import upperFirst from "lodash/upperFirst"; interface Props extends Partial { } -interface ISecretTemplateField { +interface SecretTemplateField { key: string; value?: string; required?: boolean; } -interface ISecretTemplate { - [field: string]: ISecretTemplateField[]; - annotations?: ISecretTemplateField[]; - labels?: ISecretTemplateField[]; - data?: ISecretTemplateField[]; +interface SecretTemplate { + [field: string]: SecretTemplateField[]; + annotations?: SecretTemplateField[]; + labels?: SecretTemplateField[]; + data?: SecretTemplateField[]; } -type ISecretField = keyof ISecretTemplate; +type SecretField = keyof SecretTemplate; + +type RecursivePartial = { + [P in keyof T]?: + T[P] extends (infer U)[] ? RecursivePartial[] : + T[P] extends object ? RecursivePartial : + T[P]; +}; @observer export class AddSecretDialog extends React.Component { @observable static isOpen = false; - static open() { + static open(): void { AddSecretDialog.isOpen = true; } - static close() { + static close(): void { AddSecretDialog.isOpen = false; } - private secretTemplate: { [p: string]: ISecretTemplate } = { + private secretTemplate: Partial> = { [SecretType.Opaque]: {}, [SecretType.ServiceAccountToken]: { annotations: [ @@ -60,7 +67,7 @@ export class AddSecretDialog extends React.Component { }, } - get types() { + get types(): SecretType[] { return Object.keys(this.secretTemplate) as SecretType[]; } @@ -69,38 +76,39 @@ export class AddSecretDialog extends React.Component { @observable namespace = "default"; @observable type = SecretType.Opaque; - reset = () => { + reset = (): void => { this.name = ""; this.secret = this.secretTemplate; } - close = () => { + close = (): void => { AddSecretDialog.close(); } - private getDataFromFields = (fields: ISecretTemplateField[] = [], processValue?: (val: string) => string) => { + private getDataFromFields = (fields: SecretTemplateField[] = [], processValue?: (val: string) => string): any => { return fields.reduce((data, field) => { const { key, value } = field; if (key) { data[key] = processValue ? processValue(value) : value; } return data; - }, {}) + }, {}); } - createSecret = async () => { + createSecret = async (): Promise => { const { name, namespace, type } = this; const { data = [], labels = [], annotations = [] } = this.secret[type]; + const metadata: Partial = { + name, + namespace, + annotations: this.getDataFromFields(annotations), + labels: this.getDataFromFields(labels), + }; const secret: Partial = { type: type, data: this.getDataFromFields(data, val => val ? base64.encode(val) : ""), - metadata: { - name: name, - namespace: namespace, - annotations: this.getDataFromFields(annotations), - labels: this.getDataFromFields(labels), - } as IKubeObjectMetadata - } + metadata: metadata as KubeObjectMetadata, + }; try { const newSecret = await secretsApi.create({ namespace, name }, secret); showDetails(newSecret.selfLink); @@ -111,18 +119,18 @@ export class AddSecretDialog extends React.Component { } } - addField = (field: ISecretField) => { + addField = (field: SecretField): void => { const fields = this.secret[this.type][field] || []; fields.push({ key: "", value: "" }); this.secret[this.type][field] = fields; } - removeField = (field: ISecretField, index: number) => { + removeField = (field: SecretField, index: number): void => { const fields = this.secret[this.type][field] || []; fields.splice(index, 1); } - renderFields(field: ISecretField) { + renderFields(field: SecretField): JSX.Element { const fields = this.secret[this.type][field] || []; return ( <> @@ -131,7 +139,7 @@ export class AddSecretDialog extends React.Component { small tooltip={_i18n._(t`Add field`)} material="add_circle_outline" - onClick={() => this.addField(field)} + onClick={(): void => this.addField(field)} />
@@ -145,14 +153,18 @@ export class AddSecretDialog extends React.Component { title={key} tabIndex={required ? -1 : 0} readOnly={required} - value={key} onChange={v => item.key = v} + value={key} onChange={(v): void => { + item.key = v; + }} /> item.value = v} + value={value} onChange={(v): void => { + item.value = v; + }} /> { tooltip={required ? Required field : Remove field} className="remove-icon" material="remove_circle_outline" - onClick={() => this.removeField(field, index)} + onClick={(): void => this.removeField(field, index)} />
- ) + ); })}
- ) + ); } - render() { + render(): JSX.Element { const { ...dialogProps } = this.props; const { namespace, name, type } = this; const header =
Create Secret
; @@ -189,7 +201,9 @@ export class AddSecretDialog extends React.Component { autoFocus required placeholder={_i18n._(t`Name`)} validators={systemName} - value={name} onChange={v => this.name = v} + value={name} onChange={(v): void => { + this.name = v; + }} />
@@ -198,7 +212,7 @@ export class AddSecretDialog extends React.Component { this.namespace = value} + onChange={({ value }): void => this.namespace = value} />
@@ -206,7 +220,7 @@ export class AddSecretDialog extends React.Component { { @observer export class NamespaceSelectFilter extends React.Component { - render() { + render(): JSX.Element { const { contextNs, hasContext, toggleContext } = namespaceStore; let placeholder = All namespaces; - if (contextNs.length == 1) placeholder = Namespace: {contextNs[0]} - if (contextNs.length >= 2) placeholder = Namespaces: {contextNs.join(", ")} + if (contextNs.length == 1) { + placeholder = Namespace: {contextNs[0]}; + } + if (contextNs.length >= 2) { + placeholder = Namespaces: {contextNs.join(", ")}; + } return ( false} + isOptionSelected={(): boolean => false} controlShouldRenderValue={false} - onChange={({ value: namespace }: SelectOption) => toggleContext(namespace)} - formatOptionLabel={({ value: namespace }: SelectOption) => { + onChange={({ value: namespace }: SelectOption): void => toggleContext(namespace)} + formatOptionLabel={({ value: namespace }: SelectOption): JSX.Element => { const isSelected = hasContext(namespace); return (
@@ -101,9 +113,9 @@ export class NamespaceSelectFilter extends React.Component { {namespace} {isSelected && }
- ) + ); }} /> - ) + ); } } diff --git a/dashboard/client/components/+namespaces/namespace.store.ts b/dashboard/client/components/+namespaces/namespace.store.ts index 61797212f8..99fbfb85eb 100644 --- a/dashboard/client/components/+namespaces/namespace.store.ts +++ b/dashboard/client/components/+namespaces/namespace.store.ts @@ -1,8 +1,8 @@ import { action, observable, reaction } from "mobx"; -import { autobind, createStorage } from "../../utils"; +import { autobind, StorageHelper } from "../../utils"; import { KubeObjectStore } from "../../kube-object.store"; import { Namespace, namespacesApi } from "../../api/endpoints"; -import { IQueryParams, navigation, setQueryParams } from "../../navigation"; +import { QueryParams, navigation, setQueryParams } from "../../navigation"; import { apiManager } from "../../api/api-manager"; import { isAllowedResource } from "../..//api/rbac"; @@ -11,9 +11,9 @@ export class NamespaceStore extends KubeObjectStore { api = namespacesApi; contextNs = observable.array(); - protected storage = createStorage("context_ns", this.contextNs); + protected storage = new StorageHelper("context_ns", this.contextNs); - get initNamespaces() { + get initNamespaces(): string[] { const fromUrl = navigation.searchParams.getAsArray("namespaces"); return fromUrl.length ? fromUrl : this.storage.get(); } @@ -33,36 +33,34 @@ export class NamespaceStore extends KubeObjectStore { }); } - getContextParams(): Partial { + getContextParams(): Partial { return { namespaces: this.contextNs - } + }; } - protected updateUrl(namespaces: string[]) { - setQueryParams({ namespaces }, { replace: true }) + protected updateUrl(namespaces: string[]): void { + setQueryParams({ namespaces }, { replace: true }); } - protected loadItems(namespaces?: string[]) { + protected loadItems(namespaces?: string[]): Promise { if (!isAllowedResource("namespaces")) { if (namespaces) { - return Promise.all(namespaces.map(name => this.getDummyNamespace(name))) - } - else { + return Promise.all(namespaces.map(name => this.getDummyNamespace(name))); + } else { return new Promise(() => { - return [] - }) + return []; + }); } } if (namespaces) { - return Promise.all(namespaces.map(name => this.api.get({ name }))) - } - else { + return Promise.all(namespaces.map(name => this.api.get({ name }))); + } else { return super.loadItems(); } } - protected getDummyNamespace(name: string) { + protected getDummyNamespace(name: string): Namespace { return new Namespace({ kind: "Namespace", apiVersion: "v1", @@ -72,25 +70,28 @@ export class NamespaceStore extends KubeObjectStore { resourceVersion: "", selfLink: `/api/v1/namespaces/${name}` } - }) + }); } - setContext(namespaces: string[]) { + setContext(namespaces: string[]): void { this.contextNs.replace(namespaces); } - hasContext(namespace: string | string[]) { + hasContext(namespace: string | string[]): boolean { const context = Array.isArray(namespace) ? namespace : [namespace]; return context.every(namespace => this.contextNs.includes(namespace)); } - toggleContext(namespace: string) { - if (this.hasContext(namespace)) this.contextNs.remove(namespace); - else this.contextNs.push(namespace); + toggleContext(namespace: string): void { + if (this.hasContext(namespace)) { + this.contextNs.remove(namespace); + } else { + this.contextNs.push(namespace); + } } @action - reset() { + reset(): void { super.reset(); this.contextNs.clear(); } diff --git a/dashboard/client/components/+namespaces/namespaces.route.ts b/dashboard/client/components/+namespaces/namespaces.route.ts index 3573968101..59c931bb29 100644 --- a/dashboard/client/components/+namespaces/namespaces.route.ts +++ b/dashboard/client/components/+namespaces/namespaces.route.ts @@ -1,11 +1,11 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { buildURL } from "../../navigation"; export const namespacesRoute: RouteProps = { path: "/namespaces" +}; + +export interface NamespacesRouteParams { } -export interface INamespacesRouteParams { -} - -export const namespacesURL = buildURL(namespacesRoute.path) +export const namespacesURL = buildURL(namespacesRoute.path); diff --git a/dashboard/client/components/+namespaces/namespaces.tsx b/dashboard/client/components/+namespaces/namespaces.tsx index fa83d49b98..8d38b13121 100644 --- a/dashboard/client/components/+namespaces/namespaces.tsx +++ b/dashboard/client/components/+namespaces/namespaces.tsx @@ -1,4 +1,4 @@ -import "./namespaces.scss" +import "./namespaces.scss"; import * as React from "react"; import { Trans } from "@lingui/macro"; @@ -9,7 +9,7 @@ import { Badge } from "../badge"; import { RouteComponentProps } from "react-router"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { KubeObjectListLayout } from "../kube-object"; -import { INamespacesRouteParams } from "./namespaces.route"; +import { NamespacesRouteParams } from "./namespaces.route"; import { namespaceStore } from "./namespace.store"; import { apiManager } from "../../api/api-manager"; @@ -20,25 +20,25 @@ enum sortBy { status = "status", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } export class Namespaces extends React.Component { - render() { + render(): JSX.Element { return ( ns.getName(), - [sortBy.labels]: (ns: Namespace) => ns.getLabels(), - [sortBy.age]: (ns: Namespace) => ns.metadata.creationTimestamp, - [sortBy.status]: (ns: Namespace) => ns.getStatus(), + [sortBy.name]: (ns: Namespace): string => ns.getName(), + [sortBy.labels]: (ns: Namespace): string[] => ns.getLabels(), + [sortBy.age]: (ns: Namespace): string => ns.metadata.creationTimestamp, + [sortBy.status]: (ns: Namespace): string => ns.getStatus(), }} searchFilters={[ - (item: Namespace) => item.getSearchFields(), - (item: Namespace) => item.getStatus() + (item: Namespace): string[] => item.getSearchFields(), + (item: Namespace): string => item.getStatus() ]} renderHeaderTitle={Namespaces} renderTableHeader={[ @@ -47,33 +47,33 @@ export class Namespaces extends React.Component { { title: Age, className: "age", sortBy: sortBy.age }, { title: Status, className: "status", sortBy: sortBy.status }, ]} - renderTableContents={(item: Namespace) => [ + renderTableContents={(item: Namespace): (string | JSX.Element[] | number | React.ReactNode)[] => [ item.getName(), item.getLabels().map(label => ), item.getAge(), { title: item.getStatus(), className: item.getStatus().toLowerCase() }, ]} - renderItemMenu={(item: Namespace) => { - return + renderItemMenu={(item: Namespace): JSX.Element => { + return ; }} addRemoveButtons={{ addTooltip: Add Namespace, - onAdd: () => AddNamespaceDialog.open(), + onAdd: (): void => AddNamespaceDialog.open(), }} - customizeTableRowProps={(item: Namespace) => ({ + customizeTableRowProps={(item: Namespace): { disabled: boolean } => ({ disabled: item.getStatus() === NamespaceStatus.TERMINATING, })} /> - ) + ); } } -export function NamespaceMenu(props: KubeObjectMenuProps) { +export function NamespaceMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(namespacesApi, { diff --git a/dashboard/client/components/+network-endpoints/endpoint-details.tsx b/dashboard/client/components/+network-endpoints/endpoint-details.tsx index 158c7674c4..28f1a9506a 100644 --- a/dashboard/client/components/+network-endpoints/endpoint-details.tsx +++ b/dashboard/client/components/+network-endpoints/endpoint-details.tsx @@ -1,10 +1,9 @@ -import "./endpoint-details.scss" +import "./endpoint-details.scss"; import React from "react"; import { observer } from "mobx-react"; -import { t, Trans } from "@lingui/macro"; -import { DrawerItem, DrawerTitle } from "../drawer"; -import { Badge } from "../badge"; +import { Trans } from "@lingui/macro"; +import { DrawerTitle } from "../drawer"; import { KubeEventDetails } from "../+events/kube-event-details"; import { KubeObjectDetailsProps } from "../kube-object"; import { Endpoint, endpointApi } from "../../api/endpoints"; @@ -18,19 +17,19 @@ interface Props extends KubeObjectDetailsProps { @observer export class EndpointDetails extends React.Component { - render() { + render(): JSX.Element { const { object: endpoint } = this.props; - if (!endpoint) return; + if (!endpoint) { + return; + } return (
Subsets}/> { - endpoint.getEndpointSubsets().map((subset) => { - return( - - ) - }) + endpoint.getEndpointSubsets().map((subset, index) => ( + + )) } @@ -41,4 +40,4 @@ export class EndpointDetails extends React.Component { apiManager.registerViews(endpointApi, { Details: EndpointDetails, -}) +}); diff --git a/dashboard/client/components/+network-endpoints/endpoint-subset-list.tsx b/dashboard/client/components/+network-endpoints/endpoint-subset-list.tsx index 4964947151..adc17f228f 100644 --- a/dashboard/client/components/+network-endpoints/endpoint-subset-list.tsx +++ b/dashboard/client/components/+network-endpoints/endpoint-subset-list.tsx @@ -1,10 +1,9 @@ -import "./endpoint-subset-list.scss" +import "./endpoint-subset-list.scss"; import React from "react"; import { observer } from "mobx-react"; import { EndpointSubset, Endpoint, EndpointAddress} from "../../api/endpoints"; import { _i18n } from "../../i18n"; -import { DrawerItem, DrawerTitle } from "../drawer"; import { Trans } from "@lingui/macro"; import { Table, TableCell, TableHead, TableRow } from "../table"; import { autobind } from "../../utils"; @@ -20,21 +19,21 @@ interface Props { @observer export class EndpointSubsetList extends React.Component { - getAddressTableRow(ip: string) { + getAddressTableRow(ip: string): JSX.Element { const { subset } = this.props; const address = subset.getAddresses().find(address => address.getId() == ip); - return this.renderAddressTableRow(address) + return this.renderAddressTableRow(address); } @autobind() - getNotReadyAddressTableRow(ip: string) { + getNotReadyAddressTableRow(ip: string): JSX.Element { const { subset} = this.props; const address = subset.getNotReadyAddresses().find(address => address.getId() == ip); - return this.renderAddressTableRow(address) + return this.renderAddressTableRow(address); } @autobind() - renderAddressTable(addresses: EndpointAddress[], virtual: boolean) { + renderAddressTable(addresses: EndpointAddress[], virtual: boolean): JSX.Element { return (
Addresses
@@ -56,11 +55,11 @@ export class EndpointSubsetList extends React.Component { }
- ) + ); } @autobind() - renderAddressTableRow(address: EndpointAddress) { + renderAddressTableRow(address: EndpointAddress): JSX.Element { const { endpoint } = this.props; return ( { {address.hostname} { address.targetRef && ( - - {address.targetRef.name} - + + {address.targetRef.name} + )} ); } - render() { + render(): JSX.Element { const { subset } = this.props; const addresses = subset.getAddresses(); const notReadyAddresses = subset.getNotReadyAddresses(); @@ -90,45 +89,45 @@ export class EndpointSubsetList extends React.Component { return(
{addresses.length > 0 && ( -
-
Addresses
- - - IP - Hostname - Target - - { !addressesVirtual && addresses.map(address => this.getAddressTableRow(address.getId())) } -
-
+
+
Addresses
+ + + IP + Hostname + Target + + { !addressesVirtual && addresses.map(address => this.getAddressTableRow(address.getId())) } +
+
)} {notReadyAddresses.length > 0 && ( -
-
Not Ready Addresses
- - - IP - Hostname - Target - - { !notReadyAddressesVirtual && notReadyAddresses.map(address => this.getNotReadyAddressTableRow(address.getId())) } -
-
+
+
Not Ready Addresses
+ + + IP + Hostname + Target + + { !notReadyAddressesVirtual && notReadyAddresses.map(address => this.getNotReadyAddressTableRow(address.getId())) } +
+
)}
Ports
diff --git a/dashboard/client/components/+network-endpoints/endpoints.route.ts b/dashboard/client/components/+network-endpoints/endpoints.route.ts index 932ff5eed5..7fde23ba44 100644 --- a/dashboard/client/components/+network-endpoints/endpoints.route.ts +++ b/dashboard/client/components/+network-endpoints/endpoints.route.ts @@ -1,11 +1,11 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { buildURL } from "../../navigation"; export const endpointRoute: RouteProps = { path: "/endpoints" -} +}; export interface EndpointRouteParams { } -export const endpointURL = buildURL(endpointRoute.path) +export const endpointURL = buildURL(endpointRoute.path); diff --git a/dashboard/client/components/+network-endpoints/endpoints.tsx b/dashboard/client/components/+network-endpoints/endpoints.tsx index 649f0ec00d..11e30d6dc1 100644 --- a/dashboard/client/components/+network-endpoints/endpoints.tsx +++ b/dashboard/client/components/+network-endpoints/endpoints.tsx @@ -1,10 +1,10 @@ -import "./endpoints.scss" +import "./endpoints.scss"; -import * as React from "react" +import * as React from "react"; import { observer } from "mobx-react"; -import { RouteComponentProps } from "react-router-dom" -import { EndpointRouteParams } from "./endpoints.route" -import { Endpoint, endpointApi } from "../../api/endpoints/endpoint.api" +import { RouteComponentProps } from "react-router-dom"; +import { EndpointRouteParams } from "./endpoints.route"; +import { Endpoint, endpointApi } from "../../api/endpoints/endpoint.api"; import { endpointStore } from "./endpoints.store"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { KubeObjectListLayout } from "../kube-object"; @@ -22,17 +22,17 @@ interface Props extends RouteComponentProps { @observer export class Endpoints extends React.Component { - render() { + render(): JSX.Element { return ( endpoint.getName(), - [sortBy.namespace]: (endpoint: Endpoint) => endpoint.getNs(), - [sortBy.age]: (endpoint: Endpoint) => endpoint.metadata.creationTimestamp, + [sortBy.name]: (endpoint: Endpoint): string => endpoint.getName(), + [sortBy.namespace]: (endpoint: Endpoint): string => endpoint.getNs(), + [sortBy.age]: (endpoint: Endpoint): string => endpoint.metadata.creationTimestamp, }} searchFilters={[ - (endpoint: Endpoint) => endpoint.getSearchFields() + (endpoint: Endpoint): string[] => endpoint.getSearchFields() ]} renderHeaderTitle={Endpoints} renderTableHeader={[ @@ -41,32 +41,32 @@ export class Endpoints extends React.Component { { title: Endpoints, className: "endpoints" }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(endpoint: Endpoint) => [ + renderTableContents={(endpoint: Endpoint): (string | number)[] => [ endpoint.getName(), endpoint.getNs(), endpoint.toString(), endpoint.getAge(), ]} - renderItemMenu={(item: Endpoint) => { - return + renderItemMenu={(item: Endpoint): JSX.Element => { + return ; }} tableProps={{ - customRowHeights: (item: Endpoint, lineHeight, paddings) => { + customRowHeights: (item: Endpoint, lineHeight, paddings): number => { const lines = item.getEndpointSubsets().length || 1; return lines * lineHeight + paddings; } }} /> - ) + ); } } -export function EndpointMenu(props: KubeObjectMenuProps) { +export function EndpointMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(endpointApi, { Menu: EndpointMenu -}) +}); diff --git a/dashboard/client/components/+network-endpoints/index.ts b/dashboard/client/components/+network-endpoints/index.ts index 9a9da148d6..eb91009613 100644 --- a/dashboard/client/components/+network-endpoints/index.ts +++ b/dashboard/client/components/+network-endpoints/index.ts @@ -1,3 +1,3 @@ -export * from "./endpoints.route" -export * from "./endpoints" -export * from "./endpoint-details" +export * from "./endpoints.route"; +export * from "./endpoints"; +export * from "./endpoint-details"; diff --git a/dashboard/client/components/+network-ingresses/index.ts b/dashboard/client/components/+network-ingresses/index.ts index 46769f9275..3977538fc7 100644 --- a/dashboard/client/components/+network-ingresses/index.ts +++ b/dashboard/client/components/+network-ingresses/index.ts @@ -1,3 +1,3 @@ -export * from "./ingresses.route" -export * from "./ingresses" -export * from "./ingress-details" +export * from "./ingresses.route"; +export * from "./ingresses"; +export * from "./ingress-details"; diff --git a/dashboard/client/components/+network-ingresses/ingress-charts.tsx b/dashboard/client/components/+network-ingresses/ingress-charts.tsx index 89ceb86b3d..1dac04f256 100644 --- a/dashboard/client/components/+network-ingresses/ingress-charts.tsx +++ b/dashboard/client/components/+network-ingresses/ingress-charts.tsx @@ -2,21 +2,25 @@ import React, { useContext } from "react"; import { t } from "@lingui/macro"; import { observer } from "mobx-react"; import { ChartOptions, ChartPoint } from "chart.js"; -import { IIngressMetrics, Ingress } from "../../api/endpoints"; +import { IngressMetrics, Ingress } from "../../api/endpoints"; import { BarChart, memoryOptions } from "../chart"; import { normalizeMetrics, isMetricsEmpty } from "../../api/endpoints/metrics.api"; import { NoMetrics } from "../resource-metrics/no-metrics"; -import { ResourceMetricsContext, IResourceMetricsValue } from "../resource-metrics"; +import { ResourceMetricsContext, ResourceMetricsValue } from "../resource-metrics"; import { _i18n } from "../../i18n"; -type IContext = IResourceMetricsValue; +type IContext = ResourceMetricsValue; export const IngressCharts = observer(() => { const { params: { metrics }, tabId, object } = useContext(ResourceMetricsContext); const id = object.getId(); - if (!metrics) return null; - if (isMetricsEmpty(metrics)) return ; + if (!metrics) { + return null; + } + if (isMetricsEmpty(metrics)) { + return ; + } const values = Object.values(metrics) .map(normalizeMetrics) @@ -69,13 +73,13 @@ export const IngressCharts = observer(() => { scales: { yAxes: [{ ticks: { - callback: value => value + callback: (value): any => value } }] }, tooltips: { callbacks: { - label: ({ datasetIndex, index }, { datasets }) => { + label: ({ datasetIndex, index }, { datasets }): string => { const { label, data } = datasets[datasetIndex]; const value = data[index] as ChartPoint; const chartTooltipSec = _i18n._(t`sec`); @@ -94,4 +98,4 @@ export const IngressCharts = observer(() => { data={{ datasets: datasets[tabId] }} /> ); -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+network-ingresses/ingress-details.tsx b/dashboard/client/components/+network-ingresses/ingress-details.tsx index 83dfd831e2..9de57aff4b 100644 --- a/dashboard/client/components/+network-ingresses/ingress-details.tsx +++ b/dashboard/client/components/+network-ingresses/ingress-details.tsx @@ -25,13 +25,15 @@ export class IngressDetails extends React.Component { ingressStore.reset(); }); - componentWillUnmount() { + componentWillUnmount(): void { ingressStore.reset(); } - renderPaths(ingress: Ingress) { - const { spec: { rules } } = ingress - if (!rules || !rules.length) return null + renderPaths(ingress: Ingress): JSX.Element[] { + const { spec: { rules } } = ingress; + if (!rules || !rules.length) { + return null; + } return rules.map((rule, index) => { return (
@@ -48,7 +50,7 @@ export class IngressDetails extends React.Component { { rule.http.paths.map((path, index) => { - const backend = `${path.backend.serviceName}:${path.backend.servicePort}` + const backend = `${path.backend.serviceName}:${path.backend.servicePort}`; return ( {path.path || ""} @@ -56,17 +58,17 @@ export class IngressDetails extends React.Component {

{backend}

- ) + ); }) } )}
- ) - }) + ); + }); } - render() { + render(): JSX.Element { const { object: ingress } = this.props; if (!ingress) { return null; @@ -74,13 +76,13 @@ export class IngressDetails extends React.Component { const { spec } = ingress; const { metrics } = ingressStore; const metricTabs = [ - Network, - Duration, + Network, + Duration, ]; return (
ingressStore.loadMetrics(ingress)} + loader={(): Promise => ingressStore.loadMetrics(ingress)} tabs={metricTabs} object={ingress} params={{ metrics }} > @@ -104,10 +106,10 @@ export class IngressDetails extends React.Component {
- ) + ); } } apiManager.registerViews(ingressApi, { Details: IngressDetails, -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+network-ingresses/ingress.store.ts b/dashboard/client/components/+network-ingresses/ingress.store.ts index 5f299c51e8..1972b3c273 100644 --- a/dashboard/client/components/+network-ingresses/ingress.store.ts +++ b/dashboard/client/components/+network-ingresses/ingress.store.ts @@ -1,19 +1,19 @@ import { observable } from "mobx"; import { KubeObjectStore } from "../../kube-object.store"; import { autobind } from "../../utils"; -import { IIngressMetrics, Ingress, ingressApi } from "../../api/endpoints"; +import { IngressMetrics, Ingress, ingressApi } from "../../api/endpoints"; import { apiManager } from "../../api/api-manager"; @autobind() export class IngressStore extends KubeObjectStore { api = ingressApi; - @observable metrics: IIngressMetrics = null; + @observable metrics: IngressMetrics = null; - async loadMetrics(ingress: Ingress) { + async loadMetrics(ingress: Ingress): Promise { this.metrics = await this.api.getMetrics(ingress.getName(), ingress.getNs()); } - reset() { + reset(): void { this.metrics = null; } } diff --git a/dashboard/client/components/+network-ingresses/ingresses.route.ts b/dashboard/client/components/+network-ingresses/ingresses.route.ts index 9a21ad56c5..2d33e46700 100644 --- a/dashboard/client/components/+network-ingresses/ingresses.route.ts +++ b/dashboard/client/components/+network-ingresses/ingresses.route.ts @@ -1,11 +1,11 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { buildURL } from "../../navigation"; export const ingressRoute: RouteProps = { path: "/ingresses" -} +}; export interface IngressRouteParams { } -export const ingressURL = buildURL(ingressRoute.path) +export const ingressURL = buildURL(ingressRoute.path); diff --git a/dashboard/client/components/+network-ingresses/ingresses.tsx b/dashboard/client/components/+network-ingresses/ingresses.tsx index 2c166bb25b..e7e4b33629 100644 --- a/dashboard/client/components/+network-ingresses/ingresses.tsx +++ b/dashboard/client/components/+network-ingresses/ingresses.tsx @@ -1,10 +1,10 @@ -import "./ingresses.scss" +import "./ingresses.scss"; -import * as React from "react" +import * as React from "react"; import { observer } from "mobx-react"; -import { RouteComponentProps } from "react-router-dom" -import { IngressRouteParams } from "./ingresses.route" -import { Ingress, ingressApi } from "../../api/endpoints/ingress.api" +import { RouteComponentProps } from "react-router-dom"; +import { IngressRouteParams } from "./ingresses.route"; +import { Ingress, ingressApi } from "../../api/endpoints/ingress.api"; import { ingressStore } from "./ingress.store"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { KubeObjectListLayout } from "../kube-object"; @@ -22,18 +22,18 @@ interface Props extends RouteComponentProps { @observer export class Ingresses extends React.Component { - render() { + render(): JSX.Element { return ( ingress.getName(), - [sortBy.namespace]: (ingress: Ingress) => ingress.getNs(), - [sortBy.age]: (ingress: Ingress) => ingress.metadata.creationTimestamp, + [sortBy.name]: (ingress: Ingress): string => ingress.getName(), + [sortBy.namespace]: (ingress: Ingress): string => ingress.getNs(), + [sortBy.age]: (ingress: Ingress): string => ingress.metadata.creationTimestamp, }} searchFilters={[ - (ingress: Ingress) => ingress.getSearchFields(), - (ingress: Ingress) => ingress.getPorts(), + (ingress: Ingress): string[] => ingress.getSearchFields(), + (ingress: Ingress): string => ingress.getPorts(), ]} renderHeaderTitle={Ingresses} renderTableHeader={[ @@ -42,32 +42,32 @@ export class Ingresses extends React.Component { { title: Rules, className: "rules" }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(ingress: Ingress) => [ + renderTableContents={(ingress: Ingress): (string | number | JSX.Element[])[] => [ ingress.getName(), ingress.getNs(), ingress.getRoutes().map(route =>

{route}

), ingress.getAge(), ]} - renderItemMenu={(item: Ingress) => { - return + renderItemMenu={(item: Ingress): JSX.Element => { + return ; }} tableProps={{ - customRowHeights: (item: Ingress, lineHeight, paddings) => { + customRowHeights: (item: Ingress, lineHeight, paddings): number => { const lines = item.getRoutes().length || 1; return lines * lineHeight + paddings; } }} /> - ) + ); } } -export function IngressMenu(props: KubeObjectMenuProps) { +export function IngressMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(ingressApi, { Menu: IngressMenu -}) +}); diff --git a/dashboard/client/components/+network-policies/index.ts b/dashboard/client/components/+network-policies/index.ts index 0521db2765..f0092b8bcc 100644 --- a/dashboard/client/components/+network-policies/index.ts +++ b/dashboard/client/components/+network-policies/index.ts @@ -1,3 +1,3 @@ -export * from "./network-policies.route" -export * from "./network-policies" -export * from "./network-policy-details" +export * from "./network-policies.route"; +export * from "./network-policies"; +export * from "./network-policy-details"; diff --git a/dashboard/client/components/+network-policies/network-policies.route.ts b/dashboard/client/components/+network-policies/network-policies.route.ts index 60203ed79c..c55e10fb5b 100644 --- a/dashboard/client/components/+network-policies/network-policies.route.ts +++ b/dashboard/client/components/+network-policies/network-policies.route.ts @@ -1,11 +1,11 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { buildURL } from "../../navigation"; export const networkPoliciesRoute: RouteProps = { path: "/network-policies" +}; + +export interface NetworkPoliciesRouteParams { } -export interface INetworkPoliciesRouteParams { -} - -export const networkPoliciesURL = buildURL(networkPoliciesRoute.path); +export const networkPoliciesURL = buildURL(networkPoliciesRoute.path); diff --git a/dashboard/client/components/+network-policies/network-policies.tsx b/dashboard/client/components/+network-policies/network-policies.tsx index 3da991856f..999618ca5d 100644 --- a/dashboard/client/components/+network-policies/network-policies.tsx +++ b/dashboard/client/components/+network-policies/network-policies.tsx @@ -1,4 +1,4 @@ -import "./network-policies.scss" +import "./network-policies.scss"; import * as React from "react"; import { observer } from "mobx-react"; @@ -7,7 +7,7 @@ import { RouteComponentProps } from "react-router-dom"; import { NetworkPolicy, networkPolicyApi } from "../../api/endpoints/network-policy.api"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { KubeObjectListLayout } from "../kube-object"; -import { INetworkPoliciesRouteParams } from "./network-policies.route"; +import { NetworkPoliciesRouteParams } from "./network-policies.route"; import { networkPolicyStore } from "./network-policy.store"; import { apiManager } from "../../api/api-manager"; @@ -17,22 +17,22 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class NetworkPolicies extends React.Component { - render() { + render(): JSX.Element { return ( item.getName(), - [sortBy.namespace]: (item: NetworkPolicy) => item.getNs(), - [sortBy.age]: (item: NetworkPolicy) => item.metadata.creationTimestamp, + [sortBy.name]: (item: NetworkPolicy): string => item.getName(), + [sortBy.namespace]: (item: NetworkPolicy): string => item.getNs(), + [sortBy.age]: (item: NetworkPolicy): string => item.metadata.creationTimestamp, }} searchFilters={[ - (item: NetworkPolicy) => item.getSearchFields(), + (item: NetworkPolicy): string[] => item.getSearchFields(), ]} renderHeaderTitle={Network Policies} renderTableHeader={[ @@ -41,26 +41,26 @@ export class NetworkPolicies extends React.Component { { title: Policy Types, className: "type" }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(item: NetworkPolicy) => [ + renderTableContents={(item: NetworkPolicy): (string | number)[] => [ item.getName(), item.getNs(), item.getTypes().join(", "), item.getAge(), ]} - renderItemMenu={(item: NetworkPolicy) => { - return + renderItemMenu={(item: NetworkPolicy): JSX.Element => { + return ; }} /> - ) + ); } } -export function NetworkPolicyMenu(props: KubeObjectMenuProps) { +export function NetworkPolicyMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(networkPolicyApi, { Menu: NetworkPolicyMenu, -}) +}); diff --git a/dashboard/client/components/+network-policies/network-policy-details.tsx b/dashboard/client/components/+network-policies/network-policy-details.tsx index 8476550343..f677413bab 100644 --- a/dashboard/client/components/+network-policies/network-policy-details.tsx +++ b/dashboard/client/components/+network-policies/network-policy-details.tsx @@ -1,10 +1,10 @@ -import "./network-policy-details.scss" +import "./network-policy-details.scss"; import get from "lodash/get"; import React, { Fragment } from "react"; import { t, Trans } from "@lingui/macro"; import { DrawerItem, DrawerTitle } from "../drawer"; -import { IPolicyEgress, IPolicyIngress, IPolicyIpBlock, IPolicySelector, NetworkPolicy, networkPolicyApi } from "../../api/endpoints/network-policy.api"; +import { PolicyEgress, PolicyIngress, PolicyIpBlock, PolicySelector, NetworkPolicy, networkPolicyApi } from "../../api/endpoints/network-policy.api"; import { Badge } from "../badge"; import { SubTitle } from "../layout/sub-title"; import { KubeEventDetails } from "../+events/kube-event-details"; @@ -19,18 +19,22 @@ interface Props extends KubeObjectDetailsProps { @observer export class NetworkPolicyDetails extends React.Component { - renderIngressFrom(ingress: IPolicyIngress) { + renderIngressFrom(ingress: PolicyIngress): JSX.Element { const { from } = ingress; - if (!from) return null; + if (!from) { + return null; + } return ( <> From}/> {from.map(item => Object.keys(item).map(key => { - const data = get(item, key) + const data = get(item, key); if (key === "ipBlock") { - const { cidr, except } = data as IPolicyIpBlock; - if (!cidr) return + const { cidr, except } = data as PolicyIpBlock; + if (!cidr) { + return; + } return ( cidr: {cidr}, {" "} @@ -38,9 +42,9 @@ export class NetworkPolicyDetails extends React.Component { `except: ${except.join(", ")}` } - ) + ); } - const selector: IPolicySelector = data + const selector: PolicySelector = data; if (selector.matchLabels) { return ( @@ -51,9 +55,8 @@ export class NetworkPolicyDetails extends React.Component { .join(", ") } - ) - } - else { + ); + } else { return ((empty)); } }) @@ -62,17 +65,23 @@ export class NetworkPolicyDetails extends React.Component { ); } - renderEgressTo(egress: IPolicyEgress) { + renderEgressTo(egress: PolicyEgress): JSX.Element | null { const { to } = egress; - if (!to) return null; + if (!to) { + return null; + } return ( <> To}/> {to.map(item => { - const { ipBlock } = item - if (!ipBlock) return - const { cidr, except } = ipBlock - if (!cidr) return + const { ipBlock } = item; + if (!ipBlock) { + return; + } + const { cidr, except } = ipBlock; + if (!cidr) { + return; + } return ( cidr: {cidr}, {" "} @@ -80,13 +89,13 @@ export class NetworkPolicyDetails extends React.Component { `except: ${except.join(", ")}` } - ) + ); })} ); } - render() { + render(): JSX.Element | null { const { object: policy } = this.props; if (!policy) { return null; @@ -116,7 +125,7 @@ export class NetworkPolicyDetails extends React.Component { {this.renderIngressFrom(ingress)} - ) + ); })} )} @@ -133,7 +142,7 @@ export class NetworkPolicyDetails extends React.Component { {this.renderEgressTo(egress)} - ) + ); })} )} @@ -146,4 +155,4 @@ export class NetworkPolicyDetails extends React.Component { apiManager.registerViews(networkPolicyApi, { Details: NetworkPolicyDetails -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+network-services/index.ts b/dashboard/client/components/+network-services/index.ts index 4f64dcf2c9..de2b4282d5 100644 --- a/dashboard/client/components/+network-services/index.ts +++ b/dashboard/client/components/+network-services/index.ts @@ -1,3 +1,3 @@ -export * from "./services.route" -export * from "./services" -export * from "./service-details" +export * from "./services.route"; +export * from "./services"; +export * from "./service-details"; diff --git a/dashboard/client/components/+network-services/service-details-endpoint.tsx b/dashboard/client/components/+network-services/service-details-endpoint.tsx index 7bcc043431..6b4e4de2a2 100644 --- a/dashboard/client/components/+network-services/service-details-endpoint.tsx +++ b/dashboard/client/components/+network-services/service-details-endpoint.tsx @@ -14,36 +14,38 @@ interface Props { @observer export class ServiceDetailsEndpoint extends React.Component { - render() { - const { endpoint } = this.props - if (!endpoint && !endpointStore.isLoaded) return ( -
- ); + render(): JSX.Element { + const { endpoint } = this.props; + if (!endpoint && !endpointStore.isLoaded) { + return ( +
+ ); + } if (!endpoint) { - return null + return null; } return ( -
+
- - Name - Endpoints - - showDetails(endpoint.selfLink, false))} - > - {endpoint.getName()} - { endpoint.toString()} - + + Name + Endpoints + + showDetails(endpoint.selfLink, false))} + > + {endpoint.getName()} + { endpoint.toString()} +
- ) + ); } } diff --git a/dashboard/client/components/+network-services/service-details.tsx b/dashboard/client/components/+network-services/service-details.tsx index 0c158c9e6b..d78f19da6f 100644 --- a/dashboard/client/components/+network-services/service-details.tsx +++ b/dashboard/client/components/+network-services/service-details.tsx @@ -1,4 +1,4 @@ -import "./service-details.scss" +import "./service-details.scss"; import React from "react"; import { observer } from "mobx-react"; @@ -20,18 +20,20 @@ interface Props extends KubeObjectDetailsProps { @observer export class ServiceDetails extends React.Component { - componentDidMount() { + async componentDidMount(): Promise { if (!endpointStore.isLoaded) { - endpointStore.loadAll(); + await endpointStore.loadAll(); } - endpointApi.watch() + endpointApi.watch(); } - render() { + render(): JSX.Element { const { object: service } = this.props; - if (!service) return; + if (!service) { + return; + } const { spec } = service; - const endpoint = endpointStore.getByName(service.getName(), service.getNs()) + const endpoint = endpointStore.getByName(service.getName(), service.getNs()); return (
@@ -81,4 +83,4 @@ export class ServiceDetails extends React.Component { apiManager.registerViews(serviceApi, { Details: ServiceDetails, -}) +}); diff --git a/dashboard/client/components/+network-services/service-ports.tsx b/dashboard/client/components/+network-services/service-ports.tsx index 3335be6907..51bc811064 100644 --- a/dashboard/client/components/+network-services/service-ports.tsx +++ b/dashboard/client/components/+network-services/service-ports.tsx @@ -1,15 +1,15 @@ -import "./service-ports.scss" +import "./service-ports.scss"; import React from "react"; import { observer } from "mobx-react"; import { t } from "@lingui/macro"; import { Service, ServicePort } from "../../api/endpoints"; import { _i18n } from "../../i18n"; -import { apiBase } from "../../api" +import { apiBase } from "../../api"; import { observable } from "mobx"; import { cssNames } from "../../utils"; import { Notifications } from "../notifications"; -import { Spinner } from "../spinner" +import { Spinner } from "../spinner"; interface Props { service: Service; @@ -19,19 +19,20 @@ interface Props { export class ServicePorts extends React.Component { @observable waiting = false; - async portForward(port: ServicePort) { + async portForward(port: ServicePort): Promise { const { service } = this.props; - this.waiting = true; - apiBase.post(`/services/${service.getNs()}/${service.getName()}/port-forward/${port.port}`, {}) - .catch(error => { - Notifications.error(error); - }) - .finally(() => { - this.waiting = false; - }); + + try { + this.waiting = true; + await apiBase.post(`/services/${service.getNs()}/${service.getName()}/port-forward/${port.port}`, {}); + } catch (error) { + Notifications.error(error); + } finally { + this.waiting = false; + } } - render() { + render(): JSX.Element { const { service } = this.props; return (
@@ -39,7 +40,10 @@ export class ServicePorts extends React.Component { service.getPorts().map((port) => { return(

- this.portForward(port) }> + => this.portForward(port) } + > {port.toString()} {this.waiting && ( diff --git a/dashboard/client/components/+network-services/services.route.ts b/dashboard/client/components/+network-services/services.route.ts index 8bc6adf3f5..796d79306b 100644 --- a/dashboard/client/components/+network-services/services.route.ts +++ b/dashboard/client/components/+network-services/services.route.ts @@ -1,11 +1,11 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { buildURL } from "../../navigation"; export const servicesRoute: RouteProps = { path: "/services" +}; + +export interface ServicesRouteParams { } -export interface IServicesRouteParams { -} - -export const servicesURL = buildURL(servicesRoute.path); +export const servicesURL = buildURL(servicesRoute.path); diff --git a/dashboard/client/components/+network-services/services.tsx b/dashboard/client/components/+network-services/services.tsx index 29764c139b..aef57e0a53 100644 --- a/dashboard/client/components/+network-services/services.tsx +++ b/dashboard/client/components/+network-services/services.tsx @@ -1,10 +1,10 @@ -import "./services.scss" +import "./services.scss"; import * as React from "react"; import { observer } from "mobx-react"; import { Trans } from "@lingui/macro"; import { RouteComponentProps } from "react-router"; -import { IServicesRouteParams } from "./services.route"; +import { ServicesRouteParams } from "./services.route"; import { Service, serviceApi } from "../../api/endpoints/service.api"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { KubeObjectListLayout } from "../kube-object"; @@ -23,29 +23,29 @@ enum sortBy { status = "status", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class Services extends React.Component { - render() { + render(): JSX.Element { return ( service.getName(), - [sortBy.namespace]: (service: Service) => service.getNs(), - [sortBy.selector]: (service: Service) => service.getSelector(), - [sortBy.ports]: (service: Service) => (service.spec.ports || []).map(({ port }) => port)[0], - [sortBy.clusterIp]: (service: Service) => service.getClusterIp(), - [sortBy.type]: (service: Service) => service.getType(), - [sortBy.age]: (service: Service) => service.metadata.creationTimestamp, - [sortBy.status]: (service: Service) => service.getStatus(), + [sortBy.name]: (service: Service): string => service.getName(), + [sortBy.namespace]: (service: Service): string => service.getNs(), + [sortBy.selector]: (service: Service): string[] => service.getSelector(), + [sortBy.ports]: (service: Service): number => (service.spec.ports || []).map(({ port }) => port)[0], + [sortBy.clusterIp]: (service: Service): string => service.getClusterIp(), + [sortBy.type]: (service: Service): string => service.getType(), + [sortBy.age]: (service: Service): string => service.metadata.creationTimestamp, + [sortBy.status]: (service: Service): string => service.getStatus(), }} searchFilters={[ - (service: Service) => service.getSearchFields(), - (service: Service) => service.getSelector().join(" "), - (service: Service) => service.getPorts().join(" "), + (service: Service): string[] => service.getSearchFields(), + (service: Service): string => service.getSelector().join(" "), + (service: Service): string => service.getPorts().join(" "), ]} renderHeaderTitle={Services} renderTableHeader={[ @@ -59,31 +59,31 @@ export class Services extends React.Component { { title: Age, className: "age", sortBy: sortBy.age }, { title: Status, className: "status", sortBy: sortBy.status }, ]} - renderTableContents={(service: Service) => [ + renderTableContents={(service: Service): (string | number | JSX.Element[] | React.ReactNode)[] => [ service.getName(), service.getNs(), service.getType(), - service.getClusterIp(), + service.spec.clusterIP, service.getPorts().join(", "), service.getExternalIps().join(", ") || "-", service.getSelector().map(label => ), service.getAge(), { title: service.getStatus(), className: service.getStatus().toLowerCase() }, ]} - renderItemMenu={(item: Service) => { - return + renderItemMenu={(item: Service): JSX.Element => { + return ; }} /> - ) + ); } } -export function ServiceMenu(props: KubeObjectMenuProps) { +export function ServiceMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(serviceApi, { Menu: ServiceMenu -}) +}); diff --git a/dashboard/client/components/+network/index.ts b/dashboard/client/components/+network/index.ts index c5282c5b0e..9a58dc59a6 100644 --- a/dashboard/client/components/+network/index.ts +++ b/dashboard/client/components/+network/index.ts @@ -1,2 +1,2 @@ -export * from "./network.route" -export * from "./network" +export * from "./network.route"; +export * from "./network"; diff --git a/dashboard/client/components/+network/network.route.ts b/dashboard/client/components/+network/network.route.ts index adbf909a27..db56c47183 100644 --- a/dashboard/client/components/+network/network.route.ts +++ b/dashboard/client/components/+network/network.route.ts @@ -1,12 +1,12 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { Network } from "./network"; import { servicesURL } from "../+network-services"; -import { IURLParams } from "../../navigation"; +import { URLParams } from "../../navigation"; export const networkRoute: RouteProps = { get path() { - return Network.tabRoutes.map(({ path }) => path).flat() + return Network.tabRoutes.map(({ path }) => path).flat(); } -} +}; -export const networkURL = (params?: IURLParams) => servicesURL(params); +export const networkURL = (params?: URLParams): string => servicesURL(params); diff --git a/dashboard/client/components/+network/network.tsx b/dashboard/client/components/+network/network.tsx index 768d732547..45e12ab2b5 100644 --- a/dashboard/client/components/+network/network.tsx +++ b/dashboard/client/components/+network/network.tsx @@ -1,4 +1,4 @@ -import "./network.scss" +import "./network.scss"; import * as React from "react"; import { observer } from "mobx-react"; @@ -20,7 +20,7 @@ interface Props extends RouteComponentProps<{}> { @observer export class Network extends React.Component { static get tabRoutes(): TabRoute[] { - const query = namespaceStore.getContextParams() + const query = namespaceStore.getContextParams(); const routes: TabRoute[] = []; if (isAllowedResource("services")) { routes.push({ @@ -28,7 +28,7 @@ export class Network extends React.Component { component: Services, url: servicesURL({ query }), path: servicesRoute.path, - }) + }); } if (isAllowedResource("endpoints")) { routes.push({ @@ -36,7 +36,7 @@ export class Network extends React.Component { component: Endpoints, url: endpointURL({ query }), path: endpointRoute.path, - }) + }); } if (isAllowedResource("ingresses")) { routes.push({ @@ -44,7 +44,7 @@ export class Network extends React.Component { component: Ingresses, url: ingressURL({ query }), path: ingressRoute.path, - }) + }); } if (isAllowedResource("networkpolicies")) { routes.push({ @@ -52,12 +52,12 @@ export class Network extends React.Component { component: NetworkPolicies, url: networkPoliciesURL({ query }), path: networkPoliciesRoute.path, - }) + }); } - return routes + return routes; } - render() { + render(): JSX.Element { const tabRoutes = Network.tabRoutes; return ( @@ -66,6 +66,6 @@ export class Network extends React.Component { - ) + ); } } diff --git a/dashboard/client/components/+nodes/index.ts b/dashboard/client/components/+nodes/index.ts index 0a4b16e944..3d9e5bb47d 100644 --- a/dashboard/client/components/+nodes/index.ts +++ b/dashboard/client/components/+nodes/index.ts @@ -1,4 +1,4 @@ -export * from "./nodes" -export * from "./nodes.route" -export * from "./node-menu" -export * from "./node-details" \ No newline at end of file +export * from "./nodes"; +export * from "./nodes.route"; +export * from "./node-menu"; +export * from "./node-details"; \ No newline at end of file diff --git a/dashboard/client/components/+nodes/node-charts.tsx b/dashboard/client/components/+nodes/node-charts.tsx index 4271181fcd..2d87396e74 100644 --- a/dashboard/client/components/+nodes/node-charts.tsx +++ b/dashboard/client/components/+nodes/node-charts.tsx @@ -1,24 +1,28 @@ import React, { useContext } from "react"; import { t } from "@lingui/macro"; -import { IClusterMetrics, Node } from "../../api/endpoints"; +import { ClusterMetrics, Node } from "../../api/endpoints"; import { BarChart, cpuOptions, memoryOptions } from "../chart"; import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api"; import { NoMetrics } from "../resource-metrics/no-metrics"; -import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics"; +import { ResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics"; import { observer } from "mobx-react"; import { ChartOptions, ChartPoint } from "chart.js"; import { themeStore } from "../../theme.store"; import { _i18n } from "../../i18n"; -type IContext = IResourceMetricsValue; +type IContext = ResourceMetricsValue; export const NodeCharts = observer(() => { const { params: { metrics }, tabId, object } = useContext(ResourceMetricsContext); const id = object.getId(); const { chartCapacityColor } = themeStore.activeTheme.colors; - if (!metrics) return null; - if (isMetricsEmpty(metrics)) return ; + if (!metrics) { + return null; + } + if (isMetricsEmpty(metrics)) { + return ; + } const values = Object.values(metrics).map(metric => normalizeMetrics(metric).data.result[0].values @@ -26,11 +30,11 @@ export const NodeCharts = observer(() => { const [ memoryUsage, memoryRequests, - memoryLimits, + _memoryLimits, memoryCapacity, cpuUsage, cpuRequests, - cpuLimits, + _cpuLimits, cpuCapacity, podUsage, podCapacity, @@ -127,20 +131,20 @@ export const NodeCharts = observer(() => { scales: { yAxes: [{ ticks: { - callback: value => value + callback: (value): any => value } }] }, tooltips: { callbacks: { - label: ({ datasetIndex, index }, { datasets }) => { + label: ({ datasetIndex, index }, { datasets }): string => { const { label, data } = datasets[datasetIndex]; const value = data[index] as ChartPoint; return `${label}: ${value.y}`; } } } - } + }; const options = [cpuOptions, memoryOptions, memoryOptions, podOptions]; @@ -151,4 +155,4 @@ export const NodeCharts = observer(() => { data={{ datasets: datasets[tabId] }} /> ); -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+nodes/node-details.tsx b/dashboard/client/components/+nodes/node-details.tsx index 259c5c2c1a..11c74fec94 100644 --- a/dashboard/client/components/+nodes/node-details.tsx +++ b/dashboard/client/components/+nodes/node-details.tsx @@ -30,36 +30,38 @@ export class NodeDetails extends React.Component { nodesStore.nodeMetrics = null; }); - async componentDidMount() { + async componentDidMount(): Promise { if (!podsStore.isLoaded) { - podsStore.loadAll(); + await podsStore.loadAll(); } } - componentWillUnmount() { + componentWillUnmount(): void { nodesStore.nodeMetrics = null; } - render() { + render(): JSX.Element { const { object: node } = this.props; - if (!node) return; - const { status } = node - const { nodeInfo, addresses, capacity, allocatable } = status + if (!node) { + return; + } + const { status } = node; + const { nodeInfo, addresses, capacity, allocatable } = status; const conditions = node.getActiveConditions(); - const taints = node.getTaints() - const childPods = podsStore.getPodsByNode(node.getName()) - const metrics = nodesStore.nodeMetrics + const taints = node.getTaints(); + const childPods = podsStore.getPodsByNode(node.getName()); + const metrics = nodesStore.nodeMetrics; const metricTabs = [ - CPU, - Memory, - Disk, - Pods, + CPU, + Memory, + Disk, + Pods, ]; return (

{podsStore.isLoaded && ( nodesStore.loadMetrics(node.getName())} + loader={(): Promise => nodesStore.loadMetrics(node.getName())} tabs={metricTabs} object={node} params={{ metrics }} > @@ -121,7 +123,7 @@ export class NodeDetails extends React.Component { Conditions} className="conditions" labelsOnly> { conditions.map(condition => { - const { type } = condition + const { type } = condition; return ( { } /> - ) + ); }) } @@ -151,7 +153,7 @@ export class NodeDetails extends React.Component { />
- ) + ); } } diff --git a/dashboard/client/components/+nodes/node-menu.tsx b/dashboard/client/components/+nodes/node-menu.tsx index 0cb69e11fb..520b0a3d1e 100644 --- a/dashboard/client/components/+nodes/node-menu.tsx +++ b/dashboard/client/components/+nodes/node-menu.tsx @@ -9,36 +9,38 @@ import { Icon } from "../icon"; import { _i18n } from "../../i18n"; import { hideDetails } from "../../navigation"; -export function NodeMenu(props: KubeObjectMenuProps) { +export function NodeMenu(props: KubeObjectMenuProps): JSX.Element { const { object: node, toolbar } = props; - if (!node) return null; + if (!node) { + return null; + } const nodeName = node.getName(); - const sendToTerminal = (command: string) => { + const sendToTerminal = (command: string): void => { terminalStore.sendCommand(command, { enter: true, newTab: true, }); hideDetails(); - } + }; - const shell = () => { + const shell = (): void => { createTerminalTab({ title: _i18n._(t`Node`) + `: ${nodeName}`, node: nodeName, }); hideDetails(); - } + }; - const cordon = () => { + const cordon = (): void => { sendToTerminal(`kubectl cordon ${nodeName}`); - } + }; - const unCordon = () => { - sendToTerminal(`kubectl uncordon ${nodeName}`) - } + const unCordon = (): void => { + sendToTerminal(`kubectl uncordon ${nodeName}`); + }; - const drain = () => { + const drain = (): void => { const command = `kubectl drain ${nodeName} --delete-local-data --ignore-daemonsets --force`; ConfirmDialog.open({ ok: () => sendToTerminal(command), @@ -48,8 +50,8 @@ export function NodeMenu(props: KubeObjectMenuProps) { Are you sure you want to drain {nodeName}?

), - }) - } + }); + }; return ( diff --git a/dashboard/client/components/+nodes/nodes.route.ts b/dashboard/client/components/+nodes/nodes.route.ts index 3faf749acd..4473070e1e 100644 --- a/dashboard/client/components/+nodes/nodes.route.ts +++ b/dashboard/client/components/+nodes/nodes.route.ts @@ -1,11 +1,11 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { buildURL } from "../../navigation"; export const nodesRoute: RouteProps = { path: "/nodes" +}; + +export interface NodesRouteParams { } -export interface INodesRouteParams { -} - -export const nodesURL = buildURL(nodesRoute.path) +export const nodesURL = buildURL(nodesRoute.path); diff --git a/dashboard/client/components/+nodes/nodes.store.ts b/dashboard/client/components/+nodes/nodes.store.ts index 7892b005a8..6484714deb 100644 --- a/dashboard/client/components/+nodes/nodes.store.ts +++ b/dashboard/client/components/+nodes/nodes.store.ts @@ -1,5 +1,5 @@ -import { action, computed, observable } from "mobx" -import { clusterApi, IClusterMetrics, INodeMetrics, Node, nodesApi } from "../../api/endpoints"; +import { action, computed, observable } from "mobx"; +import { clusterApi, ClusterMetrics, NodeMetrics, Node, nodesApi } from "../../api/endpoints"; import { autobind } from "../../utils"; import { KubeObjectStore } from "../../kube-object.store"; import { apiManager } from "../../api/api-manager"; @@ -8,13 +8,13 @@ import { apiManager } from "../../api/api-manager"; export class NodesStore extends KubeObjectStore { api = nodesApi - @observable metrics: Partial = {}; - @observable nodeMetrics: Partial = null; + @observable metrics: Partial = {}; + @observable nodeMetrics: Partial = null; @observable metricsLoading = false; @observable metricsLoaded = false; @action - async loadUsageMetrics() { + async loadUsageMetrics(): Promise { this.metricsLoading = true; try { this.metrics = await nodesApi.getMetrics(); @@ -25,16 +25,16 @@ export class NodesStore extends KubeObjectStore { } @action - async loadMetrics(nodeName: string) { + async loadMetrics(nodeName: string): Promise { this.nodeMetrics = await clusterApi.getMetrics([nodeName]); } - @computed get masterNodes() { - return this.items.filter(node => node.getRoleLabels().includes("master")) + @computed get masterNodes(): Node[] { + return this.items.filter(node => node.getRoleLabels().includes("master")); } - @computed get workerNodes() { - return this.items.filter(node => !node.getRoleLabels().includes("master")) + @computed get workerNodes(): Node[] { + return this.items.filter(node => !node.getRoleLabels().includes("master")); } getLastMetricValues(node: Node, metricNames: string[]): number[] { @@ -50,7 +50,7 @@ export class NodesStore extends KubeObjectStore { result.metric.node, result.metric.instance, result.metric.kubernetes_node, - ].includes(nodeName) + ].includes(nodeName); }); return result ? parseFloat(result.values.slice(-1)[0][1]) : 0; } catch (e) { @@ -59,7 +59,7 @@ export class NodesStore extends KubeObjectStore { }); } - reset() { + reset(): void { super.reset(); this.metrics = {}; this.nodeMetrics = null; @@ -68,5 +68,5 @@ export class NodesStore extends KubeObjectStore { } } -export const nodesStore = new NodesStore() +export const nodesStore = new NodesStore(); apiManager.registerStore(nodesApi, nodesStore); diff --git a/dashboard/client/components/+nodes/nodes.tsx b/dashboard/client/components/+nodes/nodes.tsx index cd97f8c66a..1d728b4279 100644 --- a/dashboard/client/components/+nodes/nodes.tsx +++ b/dashboard/client/components/+nodes/nodes.tsx @@ -4,12 +4,12 @@ import * as React from "react"; import { observer } from "mobx-react"; import { RouteComponentProps } from "react-router"; import { t, Trans } from "@lingui/macro"; -import { cssNames, interval } from "../../utils"; +import { cssNames, IntervalManager } from "../../utils"; import { MainLayout } from "../layout/main-layout"; import { nodesStore } from "./nodes.store"; import { podsStore } from "../+workloads-pods/pods.store"; import { KubeObjectListLayout } from "../kube-object"; -import { INodesRouteParams } from "./nodes.route"; +import { NodesRouteParams } from "./nodes.route"; import { Node, nodesApi } from "../../api/endpoints/nodes.api"; import { NodeMenu } from "./node-menu"; import { LineProgress } from "../line-progress"; @@ -33,24 +33,26 @@ enum sortBy { status = "status", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class Nodes extends React.Component { - private metricsWatcher = interval(30, () => nodesStore.loadUsageMetrics()); + private metricsWatcher = new IntervalManager(30, () => nodesStore.loadUsageMetrics()); - componentDidMount() { + componentDidMount(): void { this.metricsWatcher.start(true); } - componentWillUnmount() { + componentWillUnmount(): void { this.metricsWatcher.stop(); } - renderCpuUsage(node: Node) { + renderCpuUsage(node: Node): JSX.Element { const metrics = nodesStore.getLastMetricValues(node, ["cpuUsage", "cpuCapacity"]); - if (!metrics || !metrics[1]) return ; + if (!metrics || !metrics[1]) { + return ; + } const usage = metrics[0]; const cores = metrics[1]; return ( @@ -59,12 +61,14 @@ export class Nodes extends React.Component { value={usage} tooltip={_i18n._(t`CPU:`) + ` ${Math.ceil(usage * 100) / cores}\%, ` + _i18n._(t`cores:`) + ` ${cores}`} /> - ) + ); } - renderMemoryUsage(node: Node) { + renderMemoryUsage(node: Node): JSX.Element { const metrics = nodesStore.getLastMetricValues(node, ["memoryUsage", "memoryCapacity"]); - if (!metrics || !metrics[1]) return ; + if (!metrics || !metrics[1]) { + return ; + } const usage = metrics[0]; const capacity = metrics[1]; return ( @@ -73,12 +77,14 @@ export class Nodes extends React.Component { value={usage} tooltip={_i18n._(t`Memory:`) + ` ${Math.ceil(usage * 100 / capacity)}%, ${bytesToUnits(usage, 3)}`} /> - ) + ); } - renderDiskUsage(node: Node): any { + renderDiskUsage(node: Node): JSX.Element { const metrics = nodesStore.getLastMetricValues(node, ["fsUsage", "fsSize"]); - if (!metrics || !metrics[1]) return ; + if (!metrics || !metrics[1]) { + return ; + } const usage = metrics[0]; const capacity = metrics[1]; return ( @@ -87,17 +93,17 @@ export class Nodes extends React.Component { value={usage} tooltip={_i18n._(t`Disk:`) + ` ${Math.ceil(usage * 100 / capacity)}%, ${bytesToUnits(usage, 3)}`} /> - ) + ); } - renderConditions(node: Node) { + renderConditions(node: Node): JSX.Element[] { if (!node.status.conditions) { - return null + return null; } const conditions = node.getActiveConditions(); return conditions.map(condition => { - const { type } = condition - const tooltipId = `node-${node.getName()}-condition-${type}` + const { type } = condition; + const tooltipId = `node-${node.getName()}-condition-${type}`; return (
{type} @@ -111,11 +117,11 @@ export class Nodes extends React.Component { )} -
) - }) +
); + }); } - render() { + render(): JSX.Element { return ( { dependentStores={[podsStore]} isSelectable={false} sortingCallbacks={{ - [sortBy.name]: (node: Node) => node.getName(), - [sortBy.cpu]: (node: Node) => nodesStore.getLastMetricValues(node, ["cpuUsage"]), - [sortBy.memory]: (node: Node) => nodesStore.getLastMetricValues(node, ["memoryUsage"]), - [sortBy.disk]: (node: Node) => nodesStore.getLastMetricValues(node, ["fsUsage"]), - [sortBy.conditions]: (node: Node) => node.getNodeConditionText(), - [sortBy.taints]: (node: Node) => node.getTaints().length, - [sortBy.roles]: (node: Node) => node.getRoleLabels(), - [sortBy.age]: (node: Node) => node.metadata.creationTimestamp, - [sortBy.version]: (node: Node) => node.getKubeletVersion(), + [sortBy.name]: (node: Node): string => node.getName(), + [sortBy.cpu]: (node: Node): number[] => nodesStore.getLastMetricValues(node, ["cpuUsage"]), + [sortBy.memory]: (node: Node): number[] => nodesStore.getLastMetricValues(node, ["memoryUsage"]), + [sortBy.disk]: (node: Node): number[] => nodesStore.getLastMetricValues(node, ["fsUsage"]), + [sortBy.conditions]: (node: Node): string => node.getNodeConditionText(), + [sortBy.taints]: (node: Node): number => node.getTaints().length, + [sortBy.roles]: (node: Node): string => node.getRoleLabels(), + [sortBy.age]: (node: Node): string => node.metadata.creationTimestamp, + [sortBy.version]: (node: Node): string => node.getKubeletVersion(), }} searchFilters={[ - (node: Node) => node.getSearchFields(), - (node: Node) => node.getRoleLabels(), - (node: Node) => node.getKubeletVersion(), - (node: Node) => node.getNodeConditionText(), + (node: Node): string[] => node.getSearchFields(), + (node: Node): string => node.getRoleLabels(), + (node: Node): string => node.getKubeletVersion(), + (node: Node): string => node.getNodeConditionText(), ]} renderHeaderTitle={Nodes} renderTableHeader={[ @@ -153,7 +159,7 @@ export class Nodes extends React.Component { { title: Age, className: "age", sortBy: sortBy.age }, { title: Conditions, className: "conditions", sortBy: sortBy.conditions }, ]} - renderTableContents={(node: Node) => { + renderTableContents={(node: Node): (string | number | JSX.Element | JSX.Element[])[] => { const tooltipId = `node-taints-${node.getId()}`; return [ node.getName(), @@ -170,14 +176,14 @@ export class Nodes extends React.Component { node.status.nodeInfo.kubeletVersion, node.getAge(), this.renderConditions(node), - ] + ]; }} - renderItemMenu={(item: Node) => { - return + renderItemMenu={(item: Node): JSX.Element => { + return ; }} /> - ) + ); } } diff --git a/dashboard/client/components/+pod-security-policies/index.ts b/dashboard/client/components/+pod-security-policies/index.ts index d9401de3c8..d037873b5b 100644 --- a/dashboard/client/components/+pod-security-policies/index.ts +++ b/dashboard/client/components/+pod-security-policies/index.ts @@ -1,3 +1,3 @@ -export * from "./pod-security-policies.route" -export * from "./pod-security-policies" -export * from "./pod-security-policy-details" \ No newline at end of file +export * from "./pod-security-policies.route"; +export * from "./pod-security-policies"; +export * from "./pod-security-policy-details"; \ No newline at end of file diff --git a/dashboard/client/components/+pod-security-policies/pod-security-policies.route.ts b/dashboard/client/components/+pod-security-policies/pod-security-policies.route.ts index f1a82f4c10..7ec90a3286 100644 --- a/dashboard/client/components/+pod-security-policies/pod-security-policies.route.ts +++ b/dashboard/client/components/+pod-security-policies/pod-security-policies.route.ts @@ -1,8 +1,8 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { buildURL } from "../../navigation"; export const podSecurityPoliciesRoute: RouteProps = { path: "/pod-security-policies" -} +}; -export const podSecurityPoliciesURL = buildURL(podSecurityPoliciesRoute.path) +export const podSecurityPoliciesURL = buildURL(podSecurityPoliciesRoute.path); diff --git a/dashboard/client/components/+pod-security-policies/pod-security-policies.store.ts b/dashboard/client/components/+pod-security-policies/pod-security-policies.store.ts index d79b3d5d10..6cbd928f01 100644 --- a/dashboard/client/components/+pod-security-policies/pod-security-policies.store.ts +++ b/dashboard/client/components/+pod-security-policies/pod-security-policies.store.ts @@ -8,5 +8,5 @@ export class PodSecurityPoliciesStore extends KubeObjectStore api = pspApi } -export const podSecurityPoliciesStore = new PodSecurityPoliciesStore() +export const podSecurityPoliciesStore = new PodSecurityPoliciesStore(); apiManager.registerStore(pspApi, podSecurityPoliciesStore); diff --git a/dashboard/client/components/+pod-security-policies/pod-security-policies.tsx b/dashboard/client/components/+pod-security-policies/pod-security-policies.tsx index b9581000b6..60686e5b0a 100644 --- a/dashboard/client/components/+pod-security-policies/pod-security-policies.tsx +++ b/dashboard/client/components/+pod-security-policies/pod-security-policies.tsx @@ -18,22 +18,22 @@ enum sortBy { @observer export class PodSecurityPolicies extends React.Component { - render() { + render(): JSX.Element { return ( item.getName(), - [sortBy.volumes]: (item: PodSecurityPolicy) => item.getVolumes(), - [sortBy.privileged]: (item: PodSecurityPolicy) => +item.isPrivileged(), - [sortBy.age]: (item: PodSecurityPolicy) => item.metadata.creationTimestamp, + [sortBy.name]: (item: PodSecurityPolicy): string => item.getName(), + [sortBy.volumes]: (item: PodSecurityPolicy): string[] => item.getVolumes(), + [sortBy.privileged]: (item: PodSecurityPolicy): number => +item.isPrivileged(), + [sortBy.age]: (item: PodSecurityPolicy): string => item.metadata.creationTimestamp, }} searchFilters={[ - (item: PodSecurityPolicy) => item.getSearchFields(), - (item: PodSecurityPolicy) => item.getVolumes(), - (item: PodSecurityPolicy) => Object.values(item.getRules()), + (item: PodSecurityPolicy): string[] => item.getSearchFields(), + (item: PodSecurityPolicy): string[] => item.getVolumes(), + (item: PodSecurityPolicy): string[] => Object.values(item.getRules()), ]} renderHeaderTitle={Pod Security Policies} renderTableHeader={[ @@ -42,28 +42,28 @@ export class PodSecurityPolicies extends React.Component { { title: Volumes, className: "volumes", sortBy: sortBy.volumes }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(item: PodSecurityPolicy) => { + renderTableContents={(item: PodSecurityPolicy): (string | number | JSX.Element)[] => { return [ item.getName(), item.isPrivileged() ? Yes : No, item.getVolumes().join(", "), item.getAge(), - ] + ]; }} - renderItemMenu={(item: PodSecurityPolicy) => { - return + renderItemMenu={(item: PodSecurityPolicy): JSX.Element => { + return ; }} /> - ) + ); } } -export function PodSecurityPolicyMenu(props: KubeObjectMenuProps) { +export function PodSecurityPolicyMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(pspApi, { Menu: PodSecurityPolicyMenu, -}) +}); diff --git a/dashboard/client/components/+pod-security-policies/pod-security-policy-details.tsx b/dashboard/client/components/+pod-security-policies/pod-security-policy-details.tsx index fd99b68a6e..3f1936376f 100644 --- a/dashboard/client/components/+pod-security-policies/pod-security-policy-details.tsx +++ b/dashboard/client/components/+pod-security-policies/pod-security-policy-details.tsx @@ -21,8 +21,10 @@ export class PodSecurityPolicyDetails extends React.Component { group: { rule: string; ranges?: { max: number; min: number }[]; - }) { - if (!group) return; + }): JSX.Element { + if (!group) { + return; + } const { rule, ranges } = group; return ( <> @@ -33,24 +35,24 @@ export class PodSecurityPolicyDetails extends React.Component { {ranges && ( Ranges (Min-Max)} labelsOnly> {ranges.map(({ min, max }, index) => { - return + return ; })} )} - ) + ); } - render() { + render(): JSX.Element { const { object: psp } = this.props; if (!psp) { return null; } const { allowedHostPaths, allowedCapabilities, allowedCSIDrivers, allowedFlexVolumes, allowedProcMountTypes, - allowedUnsafeSysctls, allowPrivilegeEscalation, defaultAddCapabilities, defaultAllowPrivilegeEscalation, - forbiddenSysctls, fsGroup, hostIPC, hostNetwork, hostPID, hostPorts, privileged, readOnlyRootFilesystem, - requiredDropCapabilities, runAsGroup, runAsUser, runtimeClass, seLinux, supplementalGroups, volumes + allowedUnsafeSysctls, allowPrivilegeEscalation, defaultAddCapabilities, forbiddenSysctls, fsGroup, + hostIPC, hostNetwork, hostPID, hostPorts, privileged, readOnlyRootFilesystem, requiredDropCapabilities, + runAsGroup, runAsUser, runtimeClass, seLinux, supplementalGroups, volumes } = psp.spec; return (
@@ -137,7 +139,7 @@ export class PodSecurityPolicyDetails extends React.Component { {hostPorts && ( Host Ports (Min-Max)} labelsOnly> {hostPorts.map(({ min, max }, index) => { - return + return ; })} )} @@ -156,7 +158,7 @@ export class PodSecurityPolicyDetails extends React.Component { {pathPrefix} {readOnly ? Yes : No} - ) + ); })} @@ -205,7 +207,7 @@ export class PodSecurityPolicyDetails extends React.Component { )}
- ) + ); } } diff --git a/dashboard/client/components/+storage-classes/index.ts b/dashboard/client/components/+storage-classes/index.ts index 923c64adc9..3a0b106256 100644 --- a/dashboard/client/components/+storage-classes/index.ts +++ b/dashboard/client/components/+storage-classes/index.ts @@ -1,4 +1,4 @@ -export * from "./storage-classes.route" -export * from "./storage-classes" -export * from "./storage-class-details" +export * from "./storage-classes.route"; +export * from "./storage-classes"; +export * from "./storage-class-details"; diff --git a/dashboard/client/components/+storage-classes/storage-class-details.tsx b/dashboard/client/components/+storage-classes/storage-class-details.tsx index cc354902f7..c8c46d403a 100644 --- a/dashboard/client/components/+storage-classes/storage-class-details.tsx +++ b/dashboard/client/components/+storage-classes/storage-class-details.tsx @@ -18,9 +18,11 @@ interface Props extends KubeObjectDetailsProps { @observer export class StorageClassDetails extends React.Component { - render() { + render(): JSX.Element { const { object: storageClass } = this.props; - if (!storageClass) return null; + if (!storageClass) { + return null; + } const { provisioner, parameters, mountOptions } = storageClass; return (
@@ -64,4 +66,4 @@ export class StorageClassDetails extends React.Component { apiManager.registerViews(storageClassApi, { Details: StorageClassDetails -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+storage-classes/storage-classes.route.ts b/dashboard/client/components/+storage-classes/storage-classes.route.ts index d47d8be7eb..46f98e3e08 100644 --- a/dashboard/client/components/+storage-classes/storage-classes.route.ts +++ b/dashboard/client/components/+storage-classes/storage-classes.route.ts @@ -1,12 +1,12 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { buildURL } from "../../navigation"; export const storageClassesRoute: RouteProps = { path: "/storage-classes" +}; + +export interface StorageClassesRouteParams { } -export interface IStorageClassesRouteParams { -} - -export const storageClassesURL = buildURL(storageClassesRoute.path) +export const storageClassesURL = buildURL(storageClassesRoute.path); diff --git a/dashboard/client/components/+storage-classes/storage-classes.tsx b/dashboard/client/components/+storage-classes/storage-classes.tsx index 639968119b..7ca6b9f1b4 100644 --- a/dashboard/client/components/+storage-classes/storage-classes.tsx +++ b/dashboard/client/components/+storage-classes/storage-classes.tsx @@ -1,4 +1,4 @@ -import "./storage-classes.scss" +import "./storage-classes.scss"; import * as React from "react"; import { RouteComponentProps } from "react-router-dom"; @@ -7,7 +7,7 @@ import { Trans } from "@lingui/macro"; import { StorageClass, storageClassApi } from "../../api/endpoints/storage-class.api"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { KubeObjectListLayout } from "../kube-object"; -import { IStorageClassesRouteParams } from "./storage-classes.route"; +import { StorageClassesRouteParams } from "./storage-classes.route"; import { storageClassStore } from "./storage-class.store"; import { apiManager } from "../../api/api-manager"; @@ -18,25 +18,25 @@ enum sortBy { reclaimPolicy = "reclaim", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class StorageClasses extends React.Component { - render() { + render(): JSX.Element { return ( item.getName(), - [sortBy.age]: (item: StorageClass) => item.metadata.creationTimestamp, - [sortBy.provisioner]: (item: StorageClass) => item.provisioner, - [sortBy.reclaimPolicy]: (item: StorageClass) => item.reclaimPolicy, + [sortBy.name]: (item: StorageClass): string => item.getName(), + [sortBy.age]: (item: StorageClass): string => item.metadata.creationTimestamp, + [sortBy.provisioner]: (item: StorageClass): string => item.provisioner, + [sortBy.reclaimPolicy]: (item: StorageClass): string => item.reclaimPolicy, }} searchFilters={[ - (item: StorageClass) => item.getSearchFields(), - (item: StorageClass) => item.provisioner, + (item: StorageClass): string[] => item.getSearchFields(), + (item: StorageClass): string => item.provisioner, ]} renderHeaderTitle={Storage Classes} renderTableHeader={[ @@ -46,27 +46,27 @@ export class StorageClasses extends React.Component { { title: Default, className: "is-default" }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(storageClass: StorageClass) => [ + renderTableContents={(storageClass: StorageClass): (string | JSX.Element | number)[] => [ storageClass.getName(), storageClass.provisioner, storageClass.getReclaimPolicy(), storageClass.isDefault() ? Yes : null, storageClass.getAge(), ]} - renderItemMenu={(item: StorageClass) => { - return + renderItemMenu={(item: StorageClass): JSX.Element => { + return ; }} /> - ) + ); } } -export function StorageClassMenu(props: KubeObjectMenuProps) { +export function StorageClassMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(storageClassApi, { Menu: StorageClassMenu, -}) +}); diff --git a/dashboard/client/components/+storage-volume-claims/index.ts b/dashboard/client/components/+storage-volume-claims/index.ts index 3a8d9949b2..4f9055c1d7 100644 --- a/dashboard/client/components/+storage-volume-claims/index.ts +++ b/dashboard/client/components/+storage-volume-claims/index.ts @@ -1,4 +1,4 @@ -export * from "./volume-claims.route" -export * from "./volume-claims" -export * from "./volume-claim-details" +export * from "./volume-claims.route"; +export * from "./volume-claims"; +export * from "./volume-claim-details"; diff --git a/dashboard/client/components/+storage-volume-claims/volume-claim-details.tsx b/dashboard/client/components/+storage-volume-claims/volume-claim-details.tsx index 0cc1c58094..2c4af1944a 100644 --- a/dashboard/client/components/+storage-volume-claims/volume-claim-details.tsx +++ b/dashboard/client/components/+storage-volume-claims/volume-claim-details.tsx @@ -1,4 +1,4 @@ -import "./volume-claim-details.scss" +import "./volume-claim-details.scss"; import React, { Fragment } from "react"; import { reaction } from "mobx"; @@ -29,11 +29,11 @@ export class PersistentVolumeClaimDetails extends React.Component { volumeClaimStore.reset(); }); - componentWillUnmount() { + componentWillUnmount(): void { volumeClaimStore.reset(); } - render() { + render(): JSX.Element { const { object: volumeClaim } = this.props; if (!volumeClaim) { return null; @@ -42,12 +42,12 @@ export class PersistentVolumeClaimDetails extends React.Component { const { metrics } = volumeClaimStore; const pods = volumeClaim.getPods(podsStore.items); const metricTabs = [ - Disk + Disk ]; return (
volumeClaimStore.loadMetrics(volumeClaim)} + loader={(): Promise => volumeClaimStore.loadMetrics(volumeClaim)} tabs={metricTabs} object={volumeClaim} params={{ metrics }} > @@ -97,4 +97,4 @@ export class PersistentVolumeClaimDetails extends React.Component { apiManager.registerViews(pvcApi, { Details: PersistentVolumeClaimDetails, -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+storage-volume-claims/volume-claim-disk-chart.tsx b/dashboard/client/components/+storage-volume-claims/volume-claim-disk-chart.tsx index 295efb3de8..51b5b9a49d 100644 --- a/dashboard/client/components/+storage-volume-claims/volume-claim-disk-chart.tsx +++ b/dashboard/client/components/+storage-volume-claims/volume-claim-disk-chart.tsx @@ -1,23 +1,27 @@ import React, { useContext } from "react"; import { observer } from "mobx-react"; import { t } from "@lingui/macro"; -import { IPvcMetrics, PersistentVolumeClaim } from "../../api/endpoints"; +import { PvcMetrics, PersistentVolumeClaim } from "../../api/endpoints"; import { BarChart, ChartDataSet, memoryOptions } from "../chart"; import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api"; import { NoMetrics } from "../resource-metrics/no-metrics"; -import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics"; +import { ResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics"; import { _i18n } from "../../i18n"; import { themeStore } from "../../theme.store"; -type IContext = IResourceMetricsValue; +type IContext = ResourceMetricsValue; export const VolumeClaimDiskChart = observer(() => { const { params: { metrics }, object } = useContext(ResourceMetricsContext); const { chartCapacityColor } = themeStore.activeTheme.colors; const id = object.getId(); - if (!metrics) return null; - if (isMetricsEmpty(metrics)) return ; + if (!metrics) { + return null; + } + if (isMetricsEmpty(metrics)) { + return ; + } const { diskUsage, diskCapacity } = metrics; const usage = normalizeMetrics(diskUsage).data.result[0].values; @@ -38,7 +42,7 @@ export const VolumeClaimDiskChart = observer(() => { borderColor: chartCapacityColor, data: capacity.map(([x, y]) => ({ x, y })) } - ] + ]; return ( { api = pvcApi - @observable metrics: IPvcMetrics = null; + @observable metrics: PvcMetrics = null; @action - async loadMetrics(pvc: PersistentVolumeClaim) { + async loadMetrics(pvc: PersistentVolumeClaim): Promise { this.metrics = await pvcApi.getMetrics(pvc.getName(), pvc.getNs()); } - reset() { + reset(): void { this.metrics = null; } } diff --git a/dashboard/client/components/+storage-volume-claims/volume-claims.route.ts b/dashboard/client/components/+storage-volume-claims/volume-claims.route.ts index 7b2496366e..3cfbe383bb 100644 --- a/dashboard/client/components/+storage-volume-claims/volume-claims.route.ts +++ b/dashboard/client/components/+storage-volume-claims/volume-claims.route.ts @@ -1,11 +1,11 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { buildURL } from "../../navigation"; export const volumeClaimsRoute: RouteProps = { path: "/persistent-volume-claims" +}; + +export interface VolumeClaimsRouteParams { } -export interface IVolumeClaimsRouteParams { -} - -export const volumeClaimsURL = buildURL(volumeClaimsRoute.path) +export const volumeClaimsURL = buildURL(volumeClaimsRoute.path); diff --git a/dashboard/client/components/+storage-volume-claims/volume-claims.tsx b/dashboard/client/components/+storage-volume-claims/volume-claims.tsx index f2dcd976ae..1ce8257a5e 100644 --- a/dashboard/client/components/+storage-volume-claims/volume-claims.tsx +++ b/dashboard/client/components/+storage-volume-claims/volume-claims.tsx @@ -1,4 +1,4 @@ -import "./volume-claims.scss" +import "./volume-claims.scss"; import * as React from "react"; import { observer } from "mobx-react"; @@ -9,7 +9,7 @@ import { PersistentVolumeClaim, pvcApi } from "../../api/endpoints/persistent-vo import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { podsStore } from "../+workloads-pods/pods.store"; import { KubeObjectListLayout } from "../kube-object"; -import { IVolumeClaimsRouteParams } from "./volume-claims.route"; +import { VolumeClaimsRouteParams } from "./volume-claims.route"; import { unitsToBytes } from "../../utils/convertMemory"; import { stopPropagation } from "../../utils"; import { getDetailsUrl } from "../../navigation"; @@ -26,29 +26,29 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class PersistentVolumeClaims extends React.Component { - render() { + render(): JSX.Element { return ( pvc.getName(), - [sortBy.namespace]: (pvc: PersistentVolumeClaim) => pvc.getNs(), - [sortBy.pods]: (pvc: PersistentVolumeClaim) => pvc.getPods(podsStore.items).map(pod => pod.getName()), - [sortBy.status]: (pvc: PersistentVolumeClaim) => pvc.getStatus(), - [sortBy.size]: (pvc: PersistentVolumeClaim) => unitsToBytes(pvc.getStorage()), - [sortBy.storageClass]: (pvc: PersistentVolumeClaim) => pvc.spec.storageClassName, - [sortBy.age]: (pvc: PersistentVolumeClaim) => pvc.metadata.creationTimestamp, + [sortBy.name]: (pvc: PersistentVolumeClaim): string => pvc.getName(), + [sortBy.namespace]: (pvc: PersistentVolumeClaim): string => pvc.getNs(), + [sortBy.pods]: (pvc: PersistentVolumeClaim): string[] => pvc.getPods(podsStore.items).map(pod => pod.getName()), + [sortBy.status]: (pvc: PersistentVolumeClaim): string => pvc.getStatus(), + [sortBy.size]: (pvc: PersistentVolumeClaim): number => unitsToBytes(pvc.getStorage()), + [sortBy.storageClass]: (pvc: PersistentVolumeClaim): string => pvc.spec.storageClassName, + [sortBy.age]: (pvc: PersistentVolumeClaim): string => pvc.metadata.creationTimestamp, }} searchFilters={[ - (item: PersistentVolumeClaim) => item.getSearchFields(), - (item: PersistentVolumeClaim) => item.getPods(podsStore.items).map(pod => pod.getName()), + (item: PersistentVolumeClaim): string[] => item.getSearchFields(), + (item: PersistentVolumeClaim): string[] => item.getPods(podsStore.items).map(pod => pod.getName()), ]} renderHeaderTitle={Persistent Volume Claims} renderTableHeader={[ @@ -60,7 +60,7 @@ export class PersistentVolumeClaims extends React.Component { { title: Age, className: "age", sortBy: sortBy.age }, { title: Status, className: "status", sortBy: sortBy.status }, ]} - renderTableContents={(pvc: PersistentVolumeClaim) => { + renderTableContents={(pvc: PersistentVolumeClaim): (string | number | JSX.Element | JSX.Element[] | React.ReactNode)[] => { const pods = pvc.getPods(podsStore.items); const { storageClassName } = pvc.spec; const storageClassDetailsUrl = getDetailsUrl(storageClassApi.getUrl({ @@ -69,7 +69,7 @@ export class PersistentVolumeClaims extends React.Component { return [ pvc.getName(), pvc.getNs(), - + {storageClassName} , pvc.getStorage(), @@ -80,22 +80,22 @@ export class PersistentVolumeClaims extends React.Component { )), pvc.getAge(), { title: pvc.getStatus(), className: pvc.getStatus().toLowerCase() }, - ] + ]; }} - renderItemMenu={(item: PersistentVolumeClaim) => { - return + renderItemMenu={(item: PersistentVolumeClaim): JSX.Element => { + return ; }} /> - ) + ); } } -export function PersistentVolumeClaimMenu(props: KubeObjectMenuProps) { +export function PersistentVolumeClaimMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(pvcApi, { Menu: PersistentVolumeClaimMenu, -}) +}); diff --git a/dashboard/client/components/+storage-volumes/index.ts b/dashboard/client/components/+storage-volumes/index.ts index 743de7af98..895ab6444a 100644 --- a/dashboard/client/components/+storage-volumes/index.ts +++ b/dashboard/client/components/+storage-volumes/index.ts @@ -1,3 +1,3 @@ -export * from "./volumes.route" -export * from "./volumes" -export * from "./volume-details" +export * from "./volumes.route"; +export * from "./volumes"; +export * from "./volume-details"; diff --git a/dashboard/client/components/+storage-volumes/volume-details.tsx b/dashboard/client/components/+storage-volumes/volume-details.tsx index 6235ad893b..867485078b 100644 --- a/dashboard/client/components/+storage-volumes/volume-details.tsx +++ b/dashboard/client/components/+storage-volumes/volume-details.tsx @@ -1,5 +1,5 @@ -import startCase from "lodash/startCase" -import "./volume-details.scss" +import startCase from "lodash/startCase"; +import "./volume-details.scss"; import React from "react"; import { Trans } from "@lingui/macro"; @@ -19,7 +19,7 @@ interface Props extends KubeObjectDetailsProps { @observer export class PersistentVolumeDetails extends React.Component { - render() { + render(): JSX.Element { const { object: volume } = this.props; if (!volume) { return null; @@ -105,4 +105,4 @@ export class PersistentVolumeDetails extends React.Component { apiManager.registerViews(persistentVolumeApi, { Details: PersistentVolumeDetails -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+storage-volumes/volumes.route.ts b/dashboard/client/components/+storage-volumes/volumes.route.ts index 693c4e5ea3..a739cb021c 100644 --- a/dashboard/client/components/+storage-volumes/volumes.route.ts +++ b/dashboard/client/components/+storage-volumes/volumes.route.ts @@ -1,11 +1,11 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { buildURL } from "../../navigation"; export const volumesRoute: RouteProps = { path: "/persistent-volumes" +}; + +export interface VolumesRouteParams { } -export interface IVolumesRouteParams { -} - -export const volumesURL = buildURL(volumesRoute.path); +export const volumesURL = buildURL(volumesRoute.path); diff --git a/dashboard/client/components/+storage-volumes/volumes.tsx b/dashboard/client/components/+storage-volumes/volumes.tsx index 1008595869..a93d6fe0c4 100644 --- a/dashboard/client/components/+storage-volumes/volumes.tsx +++ b/dashboard/client/components/+storage-volumes/volumes.tsx @@ -1,4 +1,4 @@ -import "./volumes.scss" +import "./volumes.scss"; import * as React from "react"; import { observer } from "mobx-react"; @@ -7,7 +7,7 @@ import { Link, RouteComponentProps } from "react-router-dom"; import { PersistentVolume, persistentVolumeApi } from "../../api/endpoints/persistent-volume.api"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { KubeObjectListLayout } from "../kube-object"; -import { IVolumesRouteParams } from "./volumes.route"; +import { VolumesRouteParams } from "./volumes.route"; import { stopPropagation } from "../../utils"; import { getDetailsUrl } from "../../navigation"; import { volumesStore } from "./volumes.store"; @@ -22,26 +22,26 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class PersistentVolumes extends React.Component { - render() { + render(): JSX.Element { return ( item.getName(), - [sortBy.storageClass]: (item: PersistentVolume) => item.spec.storageClassName, - [sortBy.capacity]: (item: PersistentVolume) => item.getCapacity(true), - [sortBy.status]: (item: PersistentVolume) => item.getStatus(), - [sortBy.age]: (item: PersistentVolume) => item.metadata.creationTimestamp, + [sortBy.name]: (item: PersistentVolume): string => item.getName(), + [sortBy.storageClass]: (item: PersistentVolume): string => item.spec.storageClassName, + [sortBy.capacity]: (item: PersistentVolume): string | number => item.getCapacity(true), + [sortBy.status]: (item: PersistentVolume): string => item.getStatus(), + [sortBy.age]: (item: PersistentVolume): string => item.metadata.creationTimestamp, }} searchFilters={[ - (item: PersistentVolume) => item.getSearchFields(), - (item: PersistentVolume) => item.getClaimRefName(), + (item: PersistentVolume): string[] => item.getSearchFields(), + (item: PersistentVolume): string => item.getClaimRefName(), ]} renderHeaderTitle={Persistent Volumes} renderTableHeader={[ @@ -52,14 +52,14 @@ export class PersistentVolumes extends React.Component { { title: Age, className: "age", sortBy: sortBy.age }, { title: Status, className: "status", sortBy: sortBy.status }, ]} - renderTableContents={(volume: PersistentVolume) => { + renderTableContents={(volume: PersistentVolume): (string | number | JSX.Element | React.ReactNode)[] => { const { claimRef, storageClassName } = volume.spec; const storageClassDetailsUrl = getDetailsUrl(storageClassApi.getUrl({ name: storageClassName })); return [ volume.getName(), - + {storageClassName} , volume.getCapacity(), @@ -70,22 +70,22 @@ export class PersistentVolumes extends React.Component { ), volume.getAge(), { title: volume.getStatus(), className: volume.getStatus().toLowerCase() } - ] + ]; }} - renderItemMenu={(item: PersistentVolume) => { - return + renderItemMenu={(item: PersistentVolume): JSX.Element => { + return ; }} /> - ) + ); } } -export function PersistentVolumeMenu(props: KubeObjectMenuProps) { +export function PersistentVolumeMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(persistentVolumeApi, { Menu: PersistentVolumeMenu, -}) +}); diff --git a/dashboard/client/components/+storage/index.ts b/dashboard/client/components/+storage/index.ts index eb218e9174..58bae55b33 100644 --- a/dashboard/client/components/+storage/index.ts +++ b/dashboard/client/components/+storage/index.ts @@ -1,2 +1,2 @@ -export * from "./storage.route" -export * from "./storage" +export * from "./storage.route"; +export * from "./storage"; diff --git a/dashboard/client/components/+storage/storage.route.ts b/dashboard/client/components/+storage/storage.route.ts index c5cc36bb24..4304c1b0d0 100644 --- a/dashboard/client/components/+storage/storage.route.ts +++ b/dashboard/client/components/+storage/storage.route.ts @@ -1,12 +1,12 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { volumeClaimsURL } from "../+storage-volume-claims"; import { Storage } from "./storage"; -import { IURLParams } from "../../navigation"; +import { URLParams } from "../../navigation"; export const storageRoute: RouteProps = { get path() { - return Storage.tabRoutes.map(({ path }) => path).flat() + return Storage.tabRoutes.map(({ path }) => path).flat(); } -} +}; -export const storageURL = (params?: IURLParams) => volumeClaimsURL(params); +export const storageURL = (params?: URLParams): string => volumeClaimsURL(params); diff --git a/dashboard/client/components/+storage/storage.tsx b/dashboard/client/components/+storage/storage.tsx index bf8205ebd1..da1f20d9be 100644 --- a/dashboard/client/components/+storage/storage.tsx +++ b/dashboard/client/components/+storage/storage.tsx @@ -1,4 +1,4 @@ -import "./storage.scss" +import "./storage.scss"; import * as React from "react"; import { observer } from "mobx-react"; @@ -18,17 +18,17 @@ interface Props extends RouteComponentProps<{}> { @observer export class Storage extends React.Component { - static get tabRoutes() { + static get tabRoutes(): TabRoute[] { const tabRoutes: TabRoute[] = []; const { allowedResources } = configStore; - const query = namespaceStore.getContextParams() + const query = namespaceStore.getContextParams(); tabRoutes.push({ title: Persistent Volume Claims, component: PersistentVolumeClaims, url: volumeClaimsURL({ query }), path: volumeClaimsRoute.path, - }) + }); if (allowedResources.includes('persistentvolumes')) { tabRoutes.push({ @@ -45,12 +45,12 @@ export class Storage extends React.Component { component: StorageClasses, url: storageClassesURL(), path: storageClassesRoute.path, - }) + }); } return tabRoutes; } - render() { + render(): JSX.Element { const tabRoutes = Storage.tabRoutes; return ( @@ -59,6 +59,6 @@ export class Storage extends React.Component { - ) + ); } } diff --git a/dashboard/client/components/+user-management-roles-bindings/add-role-binding-dialog.tsx b/dashboard/client/components/+user-management-roles-bindings/add-role-binding-dialog.tsx index 4225d7b96f..b6dcdf1595 100644 --- a/dashboard/client/components/+user-management-roles-bindings/add-role-binding-dialog.tsx +++ b/dashboard/client/components/+user-management-roles-bindings/add-role-binding-dialog.tsx @@ -1,4 +1,4 @@ -import "./add-role-binding-dialog.scss" +import "./add-role-binding-dialog.scss"; import React from "react"; import { computed, observable } from "mobx"; @@ -9,7 +9,7 @@ import { Dialog, DialogProps } from "../dialog"; import { Wizard, WizardStep } from "../wizard"; import { Select, SelectOption } from "../select"; import { SubTitle } from "../layout/sub-title"; -import { IRoleBindingSubject, RoleBinding, ServiceAccount, Role } from "../../api/endpoints"; +import { RoleBindingSubject, RoleBinding, ServiceAccount, Role } from "../../api/endpoints"; import { Icon } from "../icon"; import { Input } from "../input"; import { NamespaceSelect } from "../+namespaces/namespace-select"; @@ -25,7 +25,7 @@ import { roleBindingsStore } from "./role-bindings.store"; interface BindingSelectOption extends SelectOption { value: string; // binding name item?: ServiceAccount | any; - subject?: IRoleBindingSubject; // used for new user/group when users-management-api not available + subject?: RoleBindingSubject; // used for new user/group when users-management-api not available } interface Props extends Partial { @@ -36,12 +36,12 @@ export class AddRoleBindingDialog extends React.Component { @observable static isOpen = false; @observable static data: RoleBinding = null; - static open(roleBinding?: RoleBinding) { + static open(roleBinding?: RoleBinding): void { AddRoleBindingDialog.isOpen = true; AddRoleBindingDialog.data = roleBinding; } - static close() { + static close(): void { AddRoleBindingDialog.isOpen = false; } @@ -56,36 +56,41 @@ export class AddRoleBindingDialog extends React.Component { @observable bindContext = ""; // empty value means "cluster-wide", otherwise bind to namespace @observable selectedAccounts = observable.array([], { deep: false }); - @computed get isEditing() { + @computed get isEditing(): boolean { return !!this.roleBinding; } - @computed get selectedRole() { + @computed get selectedRole(): Role { return rolesStore.items.find(role => role.getId() === this.selectedRoleId); } - @computed get selectedBindings() { + @computed get selectedBindings(): ServiceAccount[] { return [ ...this.selectedAccounts, - ] + ]; } - close = () => { + close = (): void => { AddRoleBindingDialog.close(); } - async loadData() { + async loadData(): Promise { const stores = [ namespaceStore, rolesStore, serviceAccountsStore, ]; - this.isLoading = true; - await Promise.all(stores.map(store => store.loadAll())); - this.isLoading = false; + try { + this.isLoading = true; + await Promise.all(stores.map(store => store.loadAll())); + } catch (error) { + Notifications.error(error); + } finally { + this.isLoading = false; + } } - onOpen = async () => { + onOpen = async (): Promise => { await this.loadData(); if (this.roleBinding) { @@ -98,13 +103,13 @@ export class AddRoleBindingDialog extends React.Component { } } - reset = () => { + reset = (): void => { this.selectedRoleId = ""; this.bindContext = ""; this.selectedAccounts.clear(); } - onBindContextChange = (namespace: string) => { + onBindContextChange = (namespace: string): void => { this.bindContext = namespace; const roleContext = this.selectedRole && this.selectedRole.getNs() || ""; if (this.bindContext && this.bindContext !== roleContext) { @@ -112,16 +117,16 @@ export class AddRoleBindingDialog extends React.Component { } } - createBindings = async () => { + createBindings = async (): Promise => { const { selectedRole, bindContext: namespace, selectedBindings, bindingName, useRoleForBindingName } = this; - const subjects = selectedBindings.map((item: KubeObject | IRoleBindingSubject) => { + const subjects = selectedBindings.map((item: KubeObject | RoleBindingSubject) => { if (item instanceof KubeObject) { return { name: item.getName(), kind: item.kind, namespace: item.getNs(), - } + }; } return item; }); @@ -133,8 +138,7 @@ export class AddRoleBindingDialog extends React.Component { roleBinding: this.roleBinding, addSubjects: subjects, }); - } - else { + } else { const name = useRoleForBindingName ? selectedRole.getName() : bindingName; roleBinding = await roleBindingsStore.create({ name, namespace }, { subjects: subjects, @@ -152,7 +156,7 @@ export class AddRoleBindingDialog extends React.Component { }; @computed get roleOptions(): BindingSelectOption[] { - let roles = rolesStore.items as Role[] + let roles = rolesStore.items as Role[]; if (this.bindContext) { // show only cluster-roles or roles for selected context namespace roles = roles.filter(role => !role.getNs() || role.getNs() === this.bindContext); @@ -163,8 +167,8 @@ export class AddRoleBindingDialog extends React.Component { return { value: role.getId(), label: name + (namespace ? ` (${namespace})` : "") - } - }) + }; + }); } @computed get serviceAccountOptions(): BindingSelectOption[] { @@ -175,12 +179,12 @@ export class AddRoleBindingDialog extends React.Component { item: account, value: name, label: <> {name} ({namespace}) - } - }) + }; + }); } - renderContents() { - const unwrapBindings = (options: BindingSelectOption[]) => options.map(option => option.item || option.subject); + renderContents(): JSX.Element { + const unwrapBindings = (options: BindingSelectOption[]): any[] => options.map(option => option.item || option.subject); return ( <> Context}/> @@ -189,7 +193,7 @@ export class AddRoleBindingDialog extends React.Component { themeName="light" isDisabled={this.isEditing} value={this.bindContext} - onChange={({ value }) => this.onBindContextChange(value)} + onChange={({ value }): void => this.onBindContextChange(value)} /> Role}/> @@ -200,7 +204,7 @@ export class AddRoleBindingDialog extends React.Component { isDisabled={this.isEditing} options={this.roleOptions} value={this.selectedRoleId} - onChange={({ value }) => this.selectedRoleId = value} + onChange={({ value }): void => this.selectedRoleId = value} /> { !this.isEditing && ( @@ -209,7 +213,9 @@ export class AddRoleBindingDialog extends React.Component { theme="light" label={Use same name for RoleBinding} value={this.useRoleForBindingName} - onChange={v => this.useRoleForBindingName = v} + onChange={(v): void => { + this.useRoleForBindingName = v; + }} /> { !this.useRoleForBindingName && ( @@ -218,7 +224,9 @@ export class AddRoleBindingDialog extends React.Component { placeholder={_i18n._(t`Name`)} disabled={this.isEditing} value={this.bindingName} - onChange={v => this.bindingName = v} + onChange={(v): void => { + this.bindingName = v; + }} /> ) } @@ -233,17 +241,19 @@ export class AddRoleBindingDialog extends React.Component { placeholder={_i18n._(t`Select service accounts`)} autoConvertOptions={false} options={this.serviceAccountOptions} - onChange={(opts: BindingSelectOption[]) => { - if (!opts) opts = []; + onChange={(opts: BindingSelectOption[]): void => { + if (!opts) { + opts = []; + } this.selectedAccounts.replace(unwrapBindings(opts)); }} maxMenuHeight={200} /> - ) + ); } - render() { + render(): JSX.Element { const { ...dialogProps } = this.props; const { isEditing, roleBinding, selectedRole, selectedBindings } = this; const roleBindingName = roleBinding ? roleBinding.getName() : ""; @@ -275,6 +285,6 @@ export class AddRoleBindingDialog extends React.Component { - ) + ); } } diff --git a/dashboard/client/components/+user-management-roles-bindings/index.ts b/dashboard/client/components/+user-management-roles-bindings/index.ts index ca90adf052..cb69a92323 100644 --- a/dashboard/client/components/+user-management-roles-bindings/index.ts +++ b/dashboard/client/components/+user-management-roles-bindings/index.ts @@ -1,3 +1,3 @@ -export * from "./role-bindings" -export * from "./role-binding-details" -export * from "./add-role-binding-dialog" +export * from "./role-bindings"; +export * from "./role-binding-details"; +export * from "./add-role-binding-dialog"; diff --git a/dashboard/client/components/+user-management-roles-bindings/role-binding-details.tsx b/dashboard/client/components/+user-management-roles-bindings/role-binding-details.tsx index 8db88b1680..1fc3207b75 100644 --- a/dashboard/client/components/+user-management-roles-bindings/role-binding-details.tsx +++ b/dashboard/client/components/+user-management-roles-bindings/role-binding-details.tsx @@ -1,9 +1,9 @@ -import "./role-binding-details.scss" +import "./role-binding-details.scss"; import * as React from "react"; import { t, Trans } from "@lingui/macro"; import { AddRemoveButtons } from "../add-remove-buttons"; -import { clusterRoleBindingApi, IRoleBindingSubject, RoleBinding, roleBindingApi } from "../../api/endpoints"; +import { clusterRoleBindingApi, RoleBindingSubject, RoleBinding, roleBindingApi } from "../../api/endpoints"; import { autobind, prevDefault } from "../../utils"; import { Table, TableCell, TableHead, TableRow } from "../table"; import { ConfirmDialog } from "../confirm-dialog"; @@ -23,28 +23,28 @@ interface Props extends KubeObjectDetailsProps { @observer export class RoleBindingDetails extends React.Component { - @observable selectedSubjects = observable.array([], { deep: false }); + @observable selectedSubjects = observable.array([], { deep: false }); - async componentDidMount() { + async componentDidMount(): Promise { disposeOnUnmount(this, [ - reaction(() => this.props.object, (obj) => { + reaction(() => this.props.object, (_obj) => { this.selectedSubjects.clear(); }) - ]) + ]); } - selectSubject(subject: IRoleBindingSubject) { + selectSubject(subject: RoleBindingSubject): void { const { selectedSubjects } = this; const isSelected = selectedSubjects.includes(subject); selectedSubjects.replace( isSelected ? selectedSubjects.filter(sub => sub !== subject) // unselect : selectedSubjects.concat(subject) // select - ) + ); } @autobind() - removeSelectedSubjects() { + removeSelectedSubjects(): void { const { object: roleBinding } = this.props; const { selectedSubjects } = this; ConfirmDialog.open({ @@ -53,10 +53,10 @@ export class RoleBindingDetails extends React.Component { message: (

Remove selected bindings for {roleBinding.getName()}?

) - }) + }); } - render() { + render(): JSX.Element { const { selectedSubjects } = this; const { object: roleBinding } = this.props; if (!roleBinding) { @@ -104,7 +104,7 @@ export class RoleBindingDetails extends React.Component { {kind} {namespace || "-"} - ) + ); }) } @@ -112,13 +112,13 @@ export class RoleBindingDetails extends React.Component { AddRoleBindingDialog.open(roleBinding)} + onAdd={(): void => AddRoleBindingDialog.open(roleBinding)} onRemove={selectedSubjects.length ? this.removeSelectedSubjects : null} addTooltip={Add bindings to {name}} removeTooltip={Remove selected bindings from ${name}} />
- ) + ); } } diff --git a/dashboard/client/components/+user-management-roles-bindings/role-bindings.store.ts b/dashboard/client/components/+user-management-roles-bindings/role-bindings.store.ts index 9678657f19..f0e61eda61 100644 --- a/dashboard/client/components/+user-management-roles-bindings/role-bindings.store.ts +++ b/dashboard/client/components/+user-management-roles-bindings/role-bindings.store.ts @@ -1,6 +1,6 @@ -import difference from "lodash/difference" -import uniqBy from "lodash/uniqBy" -import { clusterRoleBindingApi, IRoleBindingSubject, RoleBinding, roleBindingApi } from "../../api/endpoints"; +import difference from "lodash/difference"; +import uniqBy from "lodash/uniqBy"; +import { clusterRoleBindingApi, RoleBindingSubject, RoleBinding, roleBindingApi } from "../../api/endpoints"; import { KubeObjectStore } from "../../kube-object.store"; import { autobind } from "../../utils"; import { apiManager } from "../../api/api-manager"; @@ -9,57 +9,58 @@ import { apiManager } from "../../api/api-manager"; export class RoleBindingsStore extends KubeObjectStore { api = clusterRoleBindingApi - subscribe() { - return super.subscribe([clusterRoleBindingApi, roleBindingApi]) + subscribe(): () => void { + return super.subscribe([clusterRoleBindingApi, roleBindingApi]); } - protected sortItems(items: RoleBinding[]) { + protected sortItems(items: RoleBinding[]): RoleBinding[] { return super.sortItems(items, [ - roleBinding => roleBinding.kind, - roleBinding => roleBinding.getName() - ]) + (roleBinding): string => roleBinding.kind, + (roleBinding): string => roleBinding.getName() + ]); } - protected loadItem(params: { name: string; namespace?: string }) { - if (params.namespace) return roleBindingApi.get(params) - return clusterRoleBindingApi.get(params) - } - - protected loadItems(namespaces?: string[]) { - if (namespaces) { - return Promise.all( - namespaces.map(namespace => roleBindingApi.list({ namespace })) - ).then(items => items.flat()) - } - else { - return Promise.all([clusterRoleBindingApi.list(), roleBindingApi.list()]) - .then(items => items.flat()) - } - } - - protected async createItem(params: { name: string; namespace?: string }, data?: Partial) { + protected loadItem(params: { name: string; namespace?: string }): Promise { if (params.namespace) { - return roleBindingApi.create(params, data) + return roleBindingApi.get(params); } - else { - return clusterRoleBindingApi.create(params, data) + + return clusterRoleBindingApi.get(params); + } + + protected async loadItems(namespaces?: string[]): Promise { + if (namespaces) { + return ( + await Promise.all(namespaces.map(namespace => roleBindingApi.list({ namespace }))) + ).flat(); } + + return ( + await Promise.all([clusterRoleBindingApi.list(), roleBindingApi.list()]) + ).flat(); + } + + protected async createItem(params: { name: string; namespace?: string }, data?: Partial): Promise { + if (params.namespace) { + return roleBindingApi.create(params, data); + } + + return clusterRoleBindingApi.create(params, data); } async updateSubjects(params: { roleBinding: RoleBinding; - addSubjects?: IRoleBindingSubject[]; - removeSubjects?: IRoleBindingSubject[]; - }) { + addSubjects?: RoleBindingSubject[]; + removeSubjects?: RoleBindingSubject[]; + }): Promise { const { roleBinding, addSubjects, removeSubjects } = params; const currentSubjects = roleBinding.getSubjects(); let newSubjects = currentSubjects; if (addSubjects) { newSubjects = uniqBy(currentSubjects.concat(addSubjects), ({ kind, name, namespace }) => { return [kind, name, namespace].join("-"); - }) - } - else if (removeSubjects) { + }); + } else if (removeSubjects) { newSubjects = difference(currentSubjects, removeSubjects); } return this.update(roleBinding, { diff --git a/dashboard/client/components/+user-management-roles-bindings/role-bindings.tsx b/dashboard/client/components/+user-management-roles-bindings/role-bindings.tsx index d026421afc..f0a9ea107d 100644 --- a/dashboard/client/components/+user-management-roles-bindings/role-bindings.tsx +++ b/dashboard/client/components/+user-management-roles-bindings/role-bindings.tsx @@ -1,17 +1,15 @@ -import "./role-bindings.scss" +import "./role-bindings.scss"; import * as React from "react"; import { observer } from "mobx-react"; import { Trans } from "@lingui/macro"; import { RouteComponentProps } from "react-router"; -import { Icon } from "../icon"; -import { IRoleBindingsRouteParams } from "../+user-management/user-management.routes"; +import { RoleBindingsRouteParams } from "../+user-management/user-management.routes"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { clusterRoleBindingApi, RoleBinding, roleBindingApi } from "../../api/endpoints"; import { roleBindingsStore } from "./role-bindings.store"; import { KubeObjectListLayout } from "../kube-object"; import { AddRoleBindingDialog } from "./add-role-binding-dialog"; -import { KubeObject } from "../../api/kube-object"; import { apiManager } from "../../api/api-manager"; enum sortBy { @@ -21,25 +19,25 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class RoleBindings extends React.Component { - render() { + render(): JSX.Element { return ( binding.getName(), - [sortBy.namespace]: (binding: RoleBinding) => binding.getNs(), - [sortBy.bindings]: (binding: RoleBinding) => binding.getSubjectNames(), - [sortBy.age]: (binding: RoleBinding) => binding.metadata.creationTimestamp, + [sortBy.name]: (binding: RoleBinding): string => binding.getName(), + [sortBy.namespace]: (binding: RoleBinding): string => binding.getNs(), + [sortBy.bindings]: (binding: RoleBinding): string => binding.getSubjectNames(), + [sortBy.age]: (binding: RoleBinding): string => binding.metadata.creationTimestamp, }} searchFilters={[ - (binding: RoleBinding) => binding.getSearchFields(), - (binding: RoleBinding) => binding.getSubjectNames(), + (binding: RoleBinding): string[] => binding.getSearchFields(), + (binding: RoleBinding): string => binding.getSubjectNames(), ]} renderHeaderTitle={Role Bindings} renderTableHeader={[ @@ -48,30 +46,30 @@ export class RoleBindings extends React.Component { { title: Namespace, className: "namespace", sortBy: sortBy.namespace }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(binding: RoleBinding) => [ + renderTableContents={(binding: RoleBinding): (string | number)[] => [ binding.getName(), binding.getSubjectNames(), binding.getNs() || "-", binding.getAge(), ]} - renderItemMenu={(item: RoleBinding) => { - return + renderItemMenu={(item: RoleBinding): JSX.Element => { + return ; }} addRemoveButtons={{ - onAdd: () => AddRoleBindingDialog.open(), + onAdd: (): void => AddRoleBindingDialog.open(), addTooltip: Create new RoleBinding, }} /> - ) + ); } } -export function RoleBindingMenu(props: KubeObjectMenuProps) { +export function RoleBindingMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews([roleBindingApi, clusterRoleBindingApi], { Menu: RoleBindingMenu, -}) +}); diff --git a/dashboard/client/components/+user-management-roles/add-role-dialog.tsx b/dashboard/client/components/+user-management-roles/add-role-dialog.tsx index 284cfb8c7b..340cb840ce 100644 --- a/dashboard/client/components/+user-management-roles/add-role-dialog.tsx +++ b/dashboard/client/components/+user-management-roles/add-role-dialog.tsx @@ -1,4 +1,4 @@ -import "./add-role-dialog.scss" +import "./add-role-dialog.scss"; import * as React from "react"; import { observable } from "mobx"; @@ -21,23 +21,23 @@ export class AddRoleDialog extends React.Component { @observable roleName = ""; - static open() { + static open(): void { AddRoleDialog.isOpen = true; } - static close() { + static close(): void { AddRoleDialog.isOpen = false; } - close = () => { + close = (): void => { AddRoleDialog.close(); } - reset = () => { - this.roleName = "" + reset = (): void => { + this.roleName = ""; } - createRole = async () => { + createRole = async (): Promise => { try { const role = await rolesStore.create({ name: this.roleName }); showDetails(role.selfLink); @@ -48,7 +48,7 @@ export class AddRoleDialog extends React.Component { } } - render() { + render(): JSX.Element { const { ...dialogProps } = this.props; const header =
Create Role
; return ( @@ -69,11 +69,13 @@ export class AddRoleDialog extends React.Component { placeholder={_i18n._(t`Role name`)} iconLeft="supervisor_account" value={this.roleName} - onChange={v => this.roleName = v} + onChange={(v): void => { + this.roleName = v; + }} /> - ) + ); } } diff --git a/dashboard/client/components/+user-management-roles/index.ts b/dashboard/client/components/+user-management-roles/index.ts index dd5752f4be..26eaea3b7f 100644 --- a/dashboard/client/components/+user-management-roles/index.ts +++ b/dashboard/client/components/+user-management-roles/index.ts @@ -1,3 +1,3 @@ -export * from "./roles" -export * from "./role-details" -export * from "./add-role-dialog" +export * from "./roles"; +export * from "./role-details"; +export * from "./add-role-dialog"; diff --git a/dashboard/client/components/+user-management-roles/role-details.tsx b/dashboard/client/components/+user-management-roles/role-details.tsx index ac13d538a9..7595822637 100644 --- a/dashboard/client/components/+user-management-roles/role-details.tsx +++ b/dashboard/client/components/+user-management-roles/role-details.tsx @@ -1,4 +1,4 @@ -import "./role-details.scss" +import "./role-details.scss"; import React from "react"; import { Trans } from "@lingui/macro"; @@ -15,9 +15,11 @@ interface Props extends KubeObjectDetailsProps { @observer export class RoleDetails extends React.Component { - render() { + render(): JSX.Element { const { object: role } = this.props; - if (!role) return; + if (!role) { + return; + } const rules = role.getRules(); return (
@@ -57,15 +59,15 @@ export class RoleDetails extends React.Component { )}
- ) + ); })}
- ) + ); } } apiManager.registerViews([roleApi, clusterRoleApi], { Details: RoleDetails, -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+user-management-roles/roles.store.ts b/dashboard/client/components/+user-management-roles/roles.store.ts index 90658b8339..60c0ffca94 100644 --- a/dashboard/client/components/+user-management-roles/roles.store.ts +++ b/dashboard/client/components/+user-management-roles/roles.store.ts @@ -7,41 +7,43 @@ import { apiManager } from "../../api/api-manager"; export class RolesStore extends KubeObjectStore { api = clusterRoleApi - subscribe() { - return super.subscribe([roleApi, clusterRoleApi]) + subscribe(): () => void { + return super.subscribe([roleApi, clusterRoleApi]); } - protected sortItems(items: Role[]) { + protected sortItems(items: Role[]): Role[] { return super.sortItems(items, [ - role => role.kind, - role => role.getName(), - ]) + (role): string => role.kind, + (role): string => role.getName(), + ]); } - protected loadItem(params: { name: string; namespace?: string }) { - if (params.namespace) return roleApi.get(params) - return clusterRoleApi.get(params) - } - - protected loadItems(namespaces?: string[]): Promise { - if (namespaces) { - return Promise.all( - namespaces.map(namespace => roleApi.list({ namespace })) - ).then(items => items.flat()) - } - else { - return Promise.all([clusterRoleApi.list(), roleApi.list()]) - .then(items => items.flat()) - } - } - - protected async createItem(params: { name: string; namespace?: string }, data?: Partial) { + protected loadItem(params: { name: string; namespace?: string }): Promise { if (params.namespace) { - return roleApi.create(params, data) + return roleApi.get(params); } - else { - return clusterRoleApi.create(params, data) + + return clusterRoleApi.get(params); + } + + protected async loadItems(namespaces?: string[]): Promise { + if (namespaces) { + return ( + await Promise.all(namespaces.map(namespace => roleApi.list({ namespace }))) + ).flat(); } + + return ( + await Promise.all([clusterRoleApi.list(), roleApi.list()]) + ).flat(); + } + + protected async createItem(params: { name: string; namespace?: string }, data?: Partial): Promise { + if (params.namespace) { + return roleApi.create(params, data); + } + + return clusterRoleApi.create(params, data); } } diff --git a/dashboard/client/components/+user-management-roles/roles.tsx b/dashboard/client/components/+user-management-roles/roles.tsx index 56abf18785..15e66f05ba 100644 --- a/dashboard/client/components/+user-management-roles/roles.tsx +++ b/dashboard/client/components/+user-management-roles/roles.tsx @@ -1,10 +1,10 @@ -import "./roles.scss" +import "./roles.scss"; import * as React from "react"; import { observer } from "mobx-react"; import { Trans } from "@lingui/macro"; import { RouteComponentProps } from "react-router"; -import { IRolesRouteParams } from "../+user-management/user-management.routes"; +import { RolesRouteParams } from "../+user-management/user-management.routes"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { rolesStore } from "./roles.store"; import { clusterRoleApi, Role, roleApi } from "../../api/endpoints"; @@ -18,24 +18,24 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class Roles extends React.Component { - render() { + render(): JSX.Element { return ( <> role.getName(), - [sortBy.namespace]: (role: Role) => role.getNs(), - [sortBy.age]: (role: Role) => role.metadata.creationTimestamp, + [sortBy.name]: (role: Role): string => role.getName(), + [sortBy.namespace]: (role: Role): string => role.getNs(), + [sortBy.age]: (role: Role): string => role.metadata.creationTimestamp, }} searchFilters={[ - (role: Role) => role.getSearchFields(), + (role: Role): string[] => role.getSearchFields(), ]} renderHeaderTitle={Roles} renderTableHeader={[ @@ -43,29 +43,29 @@ export class Roles extends React.Component { { title: Namespace, className: "namespace", sortBy: sortBy.namespace }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(role: Role) => [ + renderTableContents={(role: Role): (string | number)[] => [ role.getName(), role.getNs() || "-", role.getAge(), ]} - renderItemMenu={(item: Role) => { - return + renderItemMenu={(item: Role): JSX.Element => { + return ; }} addRemoveButtons={{ - onAdd: () => AddRoleDialog.open(), + onAdd: (): void => AddRoleDialog.open(), addTooltip: Create new Role, }} /> - ) + ); } } -export function RoleMenu(props: KubeObjectMenuProps) { +export function RoleMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews([roleApi, clusterRoleApi], { diff --git a/dashboard/client/components/+user-management-service-accounts/create-service-account-dialog.tsx b/dashboard/client/components/+user-management-service-accounts/create-service-account-dialog.tsx index 13fe372e6b..fb2efb0805 100644 --- a/dashboard/client/components/+user-management-service-accounts/create-service-account-dialog.tsx +++ b/dashboard/client/components/+user-management-service-accounts/create-service-account-dialog.tsx @@ -25,19 +25,19 @@ export class CreateServiceAccountDialog extends React.Component { @observable name = "" @observable namespace = "default" - static open() { + static open(): void { CreateServiceAccountDialog.isOpen = true; } - static close() { + static close(): void { CreateServiceAccountDialog.isOpen = false; } - close = () => { + close = (): void => { CreateServiceAccountDialog.close(); } - createAccount = async () => { + createAccount = async (): Promise => { const { name, namespace } = this; try { const serviceAccount = await serviceAccountsStore.create({ namespace, name }); @@ -49,10 +49,10 @@ export class CreateServiceAccountDialog extends React.Component { } } - render() { + render(): JSX.Element { const { ...dialogProps } = this.props; const { name, namespace } = this; - const header =
Create Service Account
+ const header =
Create Service Account
; return ( { autoFocus required placeholder={_i18n._(t`Enter a name`)} validators={systemName} - value={name} onChange={v => this.name = v.toLowerCase()} + value={name} onChange={(v): void => { + this.name = v.toLowerCase(); + }} /> Namespace}/> this.namespace = value} + onChange={({ value }): void => this.namespace = value} /> - ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/+user-management-service-accounts/index.ts b/dashboard/client/components/+user-management-service-accounts/index.ts index fb40e7e15e..fd45e28288 100644 --- a/dashboard/client/components/+user-management-service-accounts/index.ts +++ b/dashboard/client/components/+user-management-service-accounts/index.ts @@ -1,3 +1,3 @@ -export * from "./service-accounts" -export * from "./service-accounts-details" -export * from "./create-service-account-dialog" \ No newline at end of file +export * from "./service-accounts"; +export * from "./service-accounts-details"; +export * from "./create-service-account-dialog"; \ No newline at end of file diff --git a/dashboard/client/components/+user-management-service-accounts/service-accounts-details.tsx b/dashboard/client/components/+user-management-service-accounts/service-accounts-details.tsx index abe3abbc8d..742a662b0e 100644 --- a/dashboard/client/components/+user-management-service-accounts/service-accounts-details.tsx +++ b/dashboard/client/components/+user-management-service-accounts/service-accounts-details.tsx @@ -33,34 +33,33 @@ export class ServiceAccountsDetails extends React.Component { const namespace = serviceAccount.getNs(); const secrets = serviceAccount.getSecrets().map(({ name }) => { const secret = secretsStore.getByName(name, namespace); - if (!secret) return secretsStore.load({ name, namespace }); + if (!secret) { + return secretsStore.load({ name, namespace }); + } return secret; }); this.secrets = await Promise.all(secrets); }) - renderSecrets() { + renderSecrets(): JSX.Element | JSX.Element[] { const { secrets } = this; if (!secrets) { - return + return ; } return secrets.map(secret => - ) + ); } - renderSecretLinks(secrets: Secret[]) { - return secrets.map(secret => { - return ( - - {secret.getName()} - - ) - } - ) + renderSecretLinks(secrets: Secret[]): JSX.Element[] { + return secrets.map(secret => ( + + {secret.getName()} + + )); } - render() { + render(): JSX.Element { const { object: serviceAccount } = this.props; if (!serviceAccount) { return null; @@ -68,10 +67,10 @@ export class ServiceAccountsDetails extends React.Component { const tokens = secretsStore.items.filter(secret => secret.getNs() == serviceAccount.getNs() && secret.getAnnotations().some(annot => annot == `kubernetes.io/service-account.name: ${serviceAccount.getName()}`) - ) + ); const imagePullSecrets = serviceAccount.getImagePullSecrets().map(({ name }) => secretsStore.getByName(name, serviceAccount.getNs()) - ) + ); return (
@@ -94,10 +93,10 @@ export class ServiceAccountsDetails extends React.Component {
- ) + ); } } apiManager.registerViews(serviceAccountsApi, { Details: ServiceAccountsDetails -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+user-management-service-accounts/service-accounts-secret.tsx b/dashboard/client/components/+user-management-service-accounts/service-accounts-secret.tsx index 0e63c80936..7264dc30f1 100644 --- a/dashboard/client/components/+user-management-service-accounts/service-accounts-secret.tsx +++ b/dashboard/client/components/+user-management-service-accounts/service-accounts-secret.tsx @@ -1,4 +1,4 @@ -import "./service-accounts-secret.scss" +import "./service-accounts-secret.scss"; import * as React from "react"; import * as moment from "moment"; @@ -20,9 +20,9 @@ export class ServiceAccountsSecret extends React.Component { showToken: false, } - renderSecretValue() { - const { secret } = this.props - const { showToken } = this.state + renderSecretValue(): JSX.Element { + const { secret } = this.props; + const { showToken } = this.state; return ( <> {!showToken && ( @@ -36,13 +36,13 @@ export class ServiceAccountsSecret extends React.Component { )} {showToken && ( - {secret.getToken()} + {secret.data.token} )} - ) + ); } - render() { + render(): JSX.Element { const { metadata: { name, creationTimestamp }, type } = this.props.secret; return (
@@ -65,6 +65,6 @@ export class ServiceAccountsSecret extends React.Component { {type}
- ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/+user-management-service-accounts/service-accounts.store.ts b/dashboard/client/components/+user-management-service-accounts/service-accounts.store.ts index a0b387508f..a97bf91036 100644 --- a/dashboard/client/components/+user-management-service-accounts/service-accounts.store.ts +++ b/dashboard/client/components/+user-management-service-accounts/service-accounts.store.ts @@ -7,7 +7,7 @@ import { apiManager } from "../../api/api-manager"; export class ServiceAccountsStore extends KubeObjectStore { api = serviceAccountsApi - protected async createItem(params: { name: string; namespace?: string }) { + protected async createItem(params: { name: string; namespace?: string }): Promise { await super.createItem(params); return this.api.get(params); // hackfix: load freshly created account, cause it doesn't have "secrets" field yet } diff --git a/dashboard/client/components/+user-management-service-accounts/service-accounts.tsx b/dashboard/client/components/+user-management-service-accounts/service-accounts.tsx index 6ebf7588be..c7888ae5cf 100644 --- a/dashboard/client/components/+user-management-service-accounts/service-accounts.tsx +++ b/dashboard/client/components/+user-management-service-accounts/service-accounts.tsx @@ -10,7 +10,7 @@ import { MenuItem } from "../menu"; import { openServiceAccountKubeConfig } from "../kubeconfig-dialog"; import { Icon } from "../icon"; import { KubeObjectListLayout } from "../kube-object"; -import { IServiceAccountsRouteParams } from "../+user-management"; +import { ServiceAccountsRouteParams } from "../+user-management"; import { serviceAccountsStore } from "./service-accounts.store"; import { CreateServiceAccountDialog } from "./create-service-account-dialog"; import { apiManager } from "../../api/api-manager"; @@ -21,23 +21,23 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class ServiceAccounts extends React.Component { - render() { + render(): JSX.Element { return ( <> account.getName(), - [sortBy.namespace]: (account: ServiceAccount) => account.getNs(), - [sortBy.age]: (account: ServiceAccount) => account.metadata.creationTimestamp, + [sortBy.name]: (account: ServiceAccount): string => account.getName(), + [sortBy.namespace]: (account: ServiceAccount): string => account.getNs(), + [sortBy.age]: (account: ServiceAccount): string => account.metadata.creationTimestamp, }} searchFilters={[ - (account: ServiceAccount) => account.getSearchFields(), + (account: ServiceAccount): string[] => account.getSearchFields(), ]} renderHeaderTitle={Service Accounts} renderTableHeader={[ @@ -45,37 +45,37 @@ export class ServiceAccounts extends React.Component { { title: Namespace, className: "namespace", sortBy: sortBy.namespace }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(account: ServiceAccount) => [ + renderTableContents={(account: ServiceAccount): (string | number)[] => [ account.getName(), account.getNs(), account.getAge(), ]} - renderItemMenu={(item: ServiceAccount) => { - return + renderItemMenu={(item: ServiceAccount): JSX.Element => { + return ; }} addRemoveButtons={{ - onAdd: () => CreateServiceAccountDialog.open(), + onAdd: (): void => CreateServiceAccountDialog.open(), addTooltip: Create new Service Account, }} /> - ) + ); } } -export function ServiceAccountMenu(props: KubeObjectMenuProps) { +export function ServiceAccountMenu(props: KubeObjectMenuProps): JSX.Element { const { object, toolbar } = props; return ( - openServiceAccountKubeConfig(object)}> + openServiceAccountKubeConfig(object)}> Kubeconfig - ) + ); } apiManager.registerViews(serviceAccountsApi, { Menu: ServiceAccountMenu, -}) +}); diff --git a/dashboard/client/components/+user-management/index.ts b/dashboard/client/components/+user-management/index.ts index f9b243aa50..1d4fed34cd 100644 --- a/dashboard/client/components/+user-management/index.ts +++ b/dashboard/client/components/+user-management/index.ts @@ -1,2 +1,2 @@ -export * from "./user-management" -export * from "./user-management.routes" \ No newline at end of file +export * from "./user-management"; +export * from "./user-management.routes"; \ No newline at end of file diff --git a/dashboard/client/components/+user-management/user-management.routes.ts b/dashboard/client/components/+user-management/user-management.routes.ts index 77493e46d8..3d8666b75e 100644 --- a/dashboard/client/components/+user-management/user-management.routes.ts +++ b/dashboard/client/components/+user-management/user-management.routes.ts @@ -1,38 +1,36 @@ import { RouteProps } from "react-router"; -import { UserManagement } from "./user-management" -import { buildURL, IURLParams } from "../../navigation"; +import { UserManagement } from "./user-management"; +import { buildURL, URLParams } from "../../navigation"; export const usersManagementRoute: RouteProps = { get path() { - return UserManagement.tabRoutes.map(({ path }) => path).flat() + return UserManagement.tabRoutes.map(({ path }) => path).flat(); } -} +}; // Routes export const serviceAccountsRoute: RouteProps = { path: "/service-accounts" -} +}; export const rolesRoute: RouteProps = { path: "/roles" -} +}; export const roleBindingsRoute: RouteProps = { path: "/role-bindings" -} +}; // Route params -export interface IServiceAccountsRouteParams { +export interface ServiceAccountsRouteParams { } -export interface IRoleBindingsRouteParams { +export interface RoleBindingsRouteParams { } -export interface IRolesRouteParams { +export interface RolesRouteParams { } // URL-builders -export const serviceAccountsURL = buildURL(serviceAccountsRoute.path) -export const roleBindingsURL = buildURL(roleBindingsRoute.path) -export const rolesURL = buildURL(rolesRoute.path) -export const usersManagementURL = (params?: IURLParams) => { - return serviceAccountsURL(params); -}; +export const serviceAccountsURL = buildURL(serviceAccountsRoute.path); +export const roleBindingsURL = buildURL(roleBindingsRoute.path); +export const rolesURL = buildURL(rolesRoute.path); +export const usersManagementURL = (params?: URLParams): string => serviceAccountsURL(params); diff --git a/dashboard/client/components/+user-management/user-management.tsx b/dashboard/client/components/+user-management/user-management.tsx index 3806cc37d7..06f306dad6 100644 --- a/dashboard/client/components/+user-management/user-management.tsx +++ b/dashboard/client/components/+user-management/user-management.tsx @@ -1,4 +1,4 @@ -import "./user-management.scss" +import "./user-management.scss"; import React from "react"; import { observer } from "mobx-react"; @@ -19,10 +19,10 @@ interface Props extends RouteComponentProps<{}> { @observer export class UserManagement extends React.Component { - static get tabRoutes() { + static get tabRoutes(): TabRoute[] { const tabRoutes: TabRoute[] = []; const { allowedResources } = configStore; - const query = namespaceStore.getContextParams() + const query = namespaceStore.getContextParams(); tabRoutes.push( { title: Service Accounts, @@ -42,19 +42,19 @@ export class UserManagement extends React.Component { url: rolesURL({ query }), path: rolesRoute.path, }, - ) + ); if (allowedResources.includes("podsecuritypolicies")) { tabRoutes.push({ title: Pod Security Policies, component: PodSecurityPolicies, url: podSecurityPoliciesURL(), path: podSecurityPoliciesRoute.path, - }) + }); } return tabRoutes; } - render() { + render(): JSX.Element { const tabRoutes = UserManagement.tabRoutes; return ( @@ -63,6 +63,6 @@ export class UserManagement extends React.Component { - ) + ); } } diff --git a/dashboard/client/components/+workloads-cronjobs/cronjob-details.tsx b/dashboard/client/components/+workloads-cronjobs/cronjob-details.tsx index 254189090b..69dee66444 100644 --- a/dashboard/client/components/+workloads-cronjobs/cronjob-details.tsx +++ b/dashboard/client/components/+workloads-cronjobs/cronjob-details.tsx @@ -21,16 +21,18 @@ interface Props extends KubeObjectDetailsProps { @observer export class CronJobDetails extends React.Component { - async componentDidMount() { + async componentDidMount(): Promise { if (!jobStore.isLoaded) { - jobStore.loadAll(); + return jobStore.loadAll(); } } - render() { + render(): JSX.Element { const { object: cronJob } = this.props; - if (!cronJob) return null; - const childJobs = jobStore.getJobsByOwner(cronJob) + if (!cronJob) { + return null; + } + const childJobs = jobStore.getJobsByOwner(cronJob); return (
@@ -54,8 +56,8 @@ export class CronJobDetails extends React.Component { <> Jobs}/> {childJobs.map((job: Job) => { - const selectors = job.getSelectors() - const condition = job.getCondition() + const selectors = job.getSelectors(); + const condition = job.getCondition(); return (
@@ -77,16 +79,17 @@ export class CronJobDetails extends React.Component { }
- )}) + ); + }) } }
- ) + ); } } apiManager.registerViews(cronJobApi, { Details: CronJobDetails, -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+workloads-cronjobs/cronjob.store.ts b/dashboard/client/components/+workloads-cronjobs/cronjob.store.ts index 9e75c25121..f249ffe7f0 100644 --- a/dashboard/client/components/+workloads-cronjobs/cronjob.store.ts +++ b/dashboard/client/components/+workloads-cronjobs/cronjob.store.ts @@ -3,28 +3,30 @@ import { autobind } from "../../utils"; import { CronJob, cronJobApi } from "../../api/endpoints/cron-job.api"; import { jobStore } from "../+workloads-jobs/job.store"; import { apiManager } from "../../api/api-manager"; +import { Dictionary } from "lodash"; @autobind() export class CronJobStore extends KubeObjectStore { api = cronJobApi - getStatuses(cronJobs?: CronJob[]) { - const status = { failed: 0, running: 0 } + getStatuses(cronJobs?: CronJob[]): Dictionary { + const status = { failed: 0, running: 0 }; cronJobs.forEach(cronJob => { if (cronJob.spec.suspend) { - status.failed++ + status.failed++; + } else { + status.running++; } - else { - status.running++ - } - }) - return status + }); + return status; } - getActiveJobsNum(cronJob: CronJob) { + getActiveJobsNum(cronJob: CronJob): number { // Active jobs are jobs without any condition 'Complete' nor 'Failed' const jobs = jobStore.getJobsByOwner(cronJob); - if (!jobs.length) return 0; + if (!jobs.length) { + return 0; + } return jobs.filter(job => !job.getCondition()).length; } } diff --git a/dashboard/client/components/+workloads-cronjobs/cronjobs.tsx b/dashboard/client/components/+workloads-cronjobs/cronjobs.tsx index 4221302116..ab1b089f96 100644 --- a/dashboard/client/components/+workloads-cronjobs/cronjobs.tsx +++ b/dashboard/client/components/+workloads-cronjobs/cronjobs.tsx @@ -9,10 +9,11 @@ import { cronJobStore } from "./cronjob.store"; import { jobStore } from "../+workloads-jobs/job.store"; import { eventStore } from "../+events/event.store"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; -import { ICronJobsRouteParams } from "../+workloads"; +import { CronJobsRouteParams } from "../+workloads"; import { KubeObjectListLayout } from "../kube-object"; import { KubeEventIcon } from "../+events/kube-event-icon"; import { apiManager } from "../../api/api-manager"; +import { KubeEvent } from "client/api/endpoints/events.api"; enum sortBy { name = "name", @@ -23,27 +24,27 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class CronJobs extends React.Component { - render() { + render(): JSX.Element { return ( cronJob.getName(), - [sortBy.namespace]: (cronJob: CronJob) => cronJob.getNs(), - [sortBy.suspend]: (cronJob: CronJob) => cronJob.getSuspendFlag(), - [sortBy.active]: (cronJob: CronJob) => cronJobStore.getActiveJobsNum(cronJob), - [sortBy.lastSchedule]: (cronJob: CronJob) => cronJob.getLastScheduleTime(), - [sortBy.age]: (cronJob: CronJob) => cronJob.metadata.creationTimestamp, + [sortBy.name]: (cronJob: CronJob): string => cronJob.getName(), + [sortBy.namespace]: (cronJob: CronJob): string => cronJob.getNs(), + [sortBy.suspend]: (cronJob: CronJob): string => cronJob.getSuspendFlag(), + [sortBy.active]: (cronJob: CronJob): number => cronJobStore.getActiveJobsNum(cronJob), + [sortBy.lastSchedule]: (cronJob: CronJob): string => cronJob.getLastScheduleTime(), + [sortBy.age]: (cronJob: CronJob): string => cronJob.metadata.creationTimestamp, }} searchFilters={[ - (cronJob: CronJob) => cronJob.getSearchFields(), - (cronJob: CronJob) => cronJob.getSchedule(), + (cronJob: CronJob): string[] => cronJob.getSearchFields(), + (cronJob: CronJob): string => cronJob.getSchedule(), ]} renderHeaderTitle={Cron Jobs} renderTableHeader={[ @@ -56,10 +57,12 @@ export class CronJobs extends React.Component { { title: Last schedule, className: "last-schedule", sortBy: sortBy.lastSchedule }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(cronJob: CronJob) => [ + renderTableContents={(cronJob: CronJob): (string | number | JSX.Element)[] => [ cronJob.getName(), - { - if (!cronJob.isNeverRun()) return events; + { + if (!cronJob.isNeverRun()) { + return events; + } return events.filter(event => event.reason != "FailedNeedsStart"); } }/>, @@ -70,20 +73,20 @@ export class CronJobs extends React.Component { cronJob.getLastScheduleTime(), cronJob.getAge(), ]} - renderItemMenu={(item: CronJob) => { - return + renderItemMenu={(item: CronJob): JSX.Element => { + return ; }} /> - ) + ); } } -export function CronJobMenu(props: KubeObjectMenuProps) { +export function CronJobMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(cronJobApi, { Menu: CronJobMenu, -}) +}); diff --git a/dashboard/client/components/+workloads-cronjobs/index.ts b/dashboard/client/components/+workloads-cronjobs/index.ts index 71c59dcfee..3884050448 100644 --- a/dashboard/client/components/+workloads-cronjobs/index.ts +++ b/dashboard/client/components/+workloads-cronjobs/index.ts @@ -1,2 +1,2 @@ -export * from "./cronjobs" -export * from "./cronjob-details" +export * from "./cronjobs"; +export * from "./cronjob-details"; diff --git a/dashboard/client/components/+workloads-daemonsets/daemonset-details.tsx b/dashboard/client/components/+workloads-daemonsets/daemonset-details.tsx index 69c4ed4d69..8d7651c3db 100644 --- a/dashboard/client/components/+workloads-daemonsets/daemonset-details.tsx +++ b/dashboard/client/components/+workloads-daemonsets/daemonset-details.tsx @@ -12,13 +12,14 @@ import { KubeEventDetails } from "../+events/kube-event-details"; import { daemonSetStore } from "./daemonsets.store"; import { podsStore } from "../+workloads-pods/pods.store"; import { KubeObjectDetailsProps } from "../kube-object"; -import { DaemonSet, daemonSetApi } from "../../api/endpoints"; +import { DaemonSet, daemonSetApi, PodMetricsData } from "../../api/endpoints"; import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { reaction } from "mobx"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { apiManager } from "../../api/api-manager"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; +import { Metrics } from "client/api/endpoints/metrics.api"; interface Props extends KubeObjectDetailsProps { } @@ -30,30 +31,32 @@ export class DaemonSetDetails extends React.Component { daemonSetStore.reset(); }); - componentDidMount() { + componentDidMount(): Promise { if (!podsStore.isLoaded) { - podsStore.loadAll(); + return podsStore.loadAll(); } } - componentWillUnmount() { + componentWillUnmount(): void { daemonSetStore.reset(); } - render() { + render(): JSX.Element { const { object: daemonSet } = this.props; - if (!daemonSet) return null; - const { spec } = daemonSet + if (!daemonSet) { + return null; + } + const { spec } = daemonSet; const selectors = daemonSet.getSelectors(); - const images = daemonSet.getImages() - const nodeSelector = daemonSet.getNodeSelectors() - const childPods = daemonSetStore.getChildPods(daemonSet) - const metrics = daemonSetStore.metrics + const images = daemonSet.getImages(); + const nodeSelector = daemonSet.getNodeSelectors(); + const childPods = daemonSetStore.getChildPods(daemonSet); + const metrics = daemonSetStore.metrics; return (
{podsStore.isLoaded && ( daemonSetStore.loadMetrics(daemonSet)} + loader={(): Promise> => daemonSetStore.loadMetrics(daemonSet)} tabs={podMetricTabs} object={daemonSet} params={{ metrics }} > @@ -93,10 +96,10 @@ export class DaemonSetDetails extends React.Component {
- ) + ); } } apiManager.registerViews(daemonSetApi, { Details: DaemonSetDetails, -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+workloads-daemonsets/daemonsets.store.ts b/dashboard/client/components/+workloads-daemonsets/daemonsets.store.ts index 3e9acdd036..2c9ac4f254 100644 --- a/dashboard/client/components/+workloads-daemonsets/daemonsets.store.ts +++ b/dashboard/client/components/+workloads-daemonsets/daemonsets.store.ts @@ -1,45 +1,44 @@ import { observable } from "mobx"; import { KubeObjectStore } from "../../kube-object.store"; import { autobind } from "../../utils"; -import { DaemonSet, daemonSetApi, IPodMetrics, Pod, podsApi, PodStatus } from "../../api/endpoints"; +import { DaemonSet, daemonSetApi, PodMetricsData, Pod, podsApi, PodStatus } from "../../api/endpoints"; import { podsStore } from "../+workloads-pods/pods.store"; import { apiManager } from "../../api/api-manager"; +import { Metrics } from "client/api/endpoints/metrics.api"; +import { Dictionary } from "lodash"; @autobind() export class DaemonSetStore extends KubeObjectStore { api = daemonSetApi - @observable metrics: IPodMetrics = null; + @observable metrics: PodMetricsData = null; - loadMetrics(daemonSet: DaemonSet) { + async loadMetrics(daemonSet: DaemonSet): Promise> { const pods = this.getChildPods(daemonSet); - return podsApi.getMetrics(pods, daemonSet.getNs(), "").then(metrics => - this.metrics = metrics - ); + const metrics = await podsApi.getMetrics(pods, daemonSet.getNs(), ""); + return this.metrics = metrics; } getChildPods(daemonSet: DaemonSet): Pod[] { - return podsStore.getPodsByOwner(daemonSet) + return podsStore.getPodsByOwner(daemonSet); } - getStatuses(daemonSets?: DaemonSet[]) { - const status = { failed: 0, pending: 0, running: 0 } + getStatuses(daemonSets?: DaemonSet[]): Dictionary { + const status = { failed: 0, pending: 0, running: 0 }; daemonSets.forEach(daemonSet => { - const pods = this.getChildPods(daemonSet) + const pods = this.getChildPods(daemonSet); if (pods.some(pod => pod.getStatus() === PodStatus.FAILED)) { - status.failed++ + status.failed++; + } else if (pods.some(pod => pod.getStatus() === PodStatus.PENDING)) { + status.pending++; + } else { + status.running++; } - else if (pods.some(pod => pod.getStatus() === PodStatus.PENDING)) { - status.pending++ - } - else { - status.running++ - } - }) - return status + }); + return status; } - reset() { + reset(): void { this.metrics = null; } } diff --git a/dashboard/client/components/+workloads-daemonsets/daemonsets.tsx b/dashboard/client/components/+workloads-daemonsets/daemonsets.tsx index 6c7c5821cc..cab4eab6fc 100644 --- a/dashboard/client/components/+workloads-daemonsets/daemonsets.tsx +++ b/dashboard/client/components/+workloads-daemonsets/daemonsets.tsx @@ -10,7 +10,7 @@ import { daemonSetStore } from "./daemonsets.store"; import { podsStore } from "../+workloads-pods/pods.store"; import { nodesStore } from "../+nodes/nodes.store"; import { KubeObjectListLayout } from "../kube-object"; -import { IDaemonSetsRouteParams } from "../+workloads"; +import { DaemonSetsRouteParams } from "../+workloads"; import { Trans } from "@lingui/macro"; import { Badge } from "../badge"; import { KubeEventIcon } from "../+events/kube-event-icon"; @@ -23,35 +23,35 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class DaemonSets extends React.Component { - getPodsLength(daemonSet: DaemonSet) { + getPodsLength(daemonSet: DaemonSet): number { return daemonSetStore.getChildPods(daemonSet).length; } - renderNodeSelector(daemonSet: DaemonSet) { + renderNodeSelector(daemonSet: DaemonSet): JSX.Element[] { return daemonSet.getNodeSelectors().map(selector => ( - )) + )); } - render() { + render(): JSX.Element { return ( daemonSet.getName(), - [sortBy.namespace]: (daemonSet: DaemonSet) => daemonSet.getNs(), - [sortBy.pods]: (daemonSet: DaemonSet) => this.getPodsLength(daemonSet), - [sortBy.age]: (daemonSet: DaemonSet) => daemonSet.metadata.creationTimestamp, + [sortBy.name]: (daemonSet: DaemonSet): string => daemonSet.getName(), + [sortBy.namespace]: (daemonSet: DaemonSet): string => daemonSet.getNs(), + [sortBy.pods]: (daemonSet: DaemonSet): number => this.getPodsLength(daemonSet), + [sortBy.age]: (daemonSet: DaemonSet): string => daemonSet.metadata.creationTimestamp, }} searchFilters={[ - (daemonSet: DaemonSet) => daemonSet.getSearchFields(), - (daemonSet: DaemonSet) => daemonSet.getLabels(), + (daemonSet: DaemonSet): string[] => daemonSet.getSearchFields(), + (daemonSet: DaemonSet): string[] => daemonSet.getLabels(), ]} renderHeaderTitle={Daemon Sets} renderTableHeader={[ @@ -62,28 +62,28 @@ export class DaemonSets extends React.Component { { title: Node Selector, className: "labels" }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(daemonSet: DaemonSet) => [ + renderTableContents={(daemonSet: DaemonSet): (string | number | JSX.Element | JSX.Element[])[] => [ daemonSet.getName(), daemonSet.getNs(), this.getPodsLength(daemonSet), - , + , this.renderNodeSelector(daemonSet), daemonSet.getAge(), ]} - renderItemMenu={(item: DaemonSet) => { - return + renderItemMenu={(item: DaemonSet): JSX.Element => { + return ; }} /> - ) + ); } } -export function DaemonSetMenu(props: KubeObjectMenuProps) { +export function DaemonSetMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(daemonSetApi, { Menu: DaemonSetMenu, -}) +}); diff --git a/dashboard/client/components/+workloads-daemonsets/index.ts b/dashboard/client/components/+workloads-daemonsets/index.ts index 7f32ffdf2f..d007a0065d 100644 --- a/dashboard/client/components/+workloads-daemonsets/index.ts +++ b/dashboard/client/components/+workloads-daemonsets/index.ts @@ -1,2 +1,2 @@ -export * from "./daemonsets" -export * from "./daemonset-details" +export * from "./daemonsets"; +export * from "./daemonset-details"; diff --git a/dashboard/client/components/+workloads-deployments/deployment-details.tsx b/dashboard/client/components/+workloads-deployments/deployment-details.tsx index 27fa952eda..37d27221be 100644 --- a/dashboard/client/components/+workloads-deployments/deployment-details.tsx +++ b/dashboard/client/components/+workloads-deployments/deployment-details.tsx @@ -6,7 +6,7 @@ import { disposeOnUnmount, observer } from "mobx-react"; import { t, Trans } from "@lingui/macro"; import { DrawerItem } from "../drawer"; import { Badge } from "../badge"; -import { Deployment, deploymentApi } from "../../api/endpoints"; +import { Deployment, deploymentApi, PodMetricsData } from "../../api/endpoints"; import { cssNames } from "../../utils"; import { PodDetailsTolerations } from "../+workloads-pods/pod-details-tolerations"; import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities"; @@ -23,6 +23,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { ReplicaSets } from "../+workloads-replicasets"; import { apiManager } from "../../api/api-manager"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; +import { Metrics } from "client/api/endpoints/metrics.api"; interface Props extends KubeObjectDetailsProps { } @@ -34,33 +35,37 @@ export class DeploymentDetails extends React.Component { deploymentStore.reset(); }); - componentDidMount() { + async componentDidMount(): Promise { + const promises = []; if (!podsStore.isLoaded) { - podsStore.loadAll(); + promises.push(podsStore.loadAll()); } if (!replicaSetStore.isLoaded) { - replicaSetStore.loadAll(); + promises.push(replicaSetStore.loadAll()); } + await Promise.all(promises); } - componentWillUnmount() { + componentWillUnmount(): void { deploymentStore.reset(); } - render() { + render(): JSX.Element { const { object: deployment } = this.props; - if (!deployment) return null - const { status, spec } = deployment - const nodeSelector = deployment.getNodeSelectors() + if (!deployment) { + return null; + } + const { status, spec } = deployment; + const nodeSelector = deployment.getNodeSelectors(); const selectors = deployment.getSelectors(); - const childPods = deploymentStore.getChildPods(deployment) - const replicaSets = replicaSetStore.getReplicaSetsByOwner(deployment) - const metrics = deploymentStore.metrics + const childPods = deploymentStore.getChildPods(deployment); + const replicaSets = replicaSetStore.getReplicaSetsByOwner(deployment); + const metrics = deploymentStore.metrics; return (
{podsStore.isLoaded && ( deploymentStore.loadMetrics(deployment)} + loader={(): Promise> => deploymentStore.loadMetrics(deployment)} tabs={podMetricTabs} object={deployment} params={{ metrics }} > @@ -94,7 +99,7 @@ export class DeploymentDetails extends React.Component { Conditions} className="conditions" labelsOnly> { deployment.getConditions().map(condition => { - const { type, message, lastTransitionTime, status } = condition + const { type, message, lastTransitionTime, status } = condition; return ( {
- ) + ); } } apiManager.registerViews(deploymentApi, { Details: DeploymentDetails -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+workloads-deployments/deployment-scale-dialog.tsx b/dashboard/client/components/+workloads-deployments/deployment-scale-dialog.tsx index d421f4692e..faaf72e7d8 100644 --- a/dashboard/client/components/+workloads-deployments/deployment-scale-dialog.tsx +++ b/dashboard/client/components/+workloads-deployments/deployment-scale-dialog.tsx @@ -24,24 +24,24 @@ export class DeploymentScaleDialog extends Component { @observable currentReplicas = 0; @observable desiredReplicas = 0; - static open(deployment: Deployment) { + static open(deployment: Deployment): void { DeploymentScaleDialog.isOpen = true; DeploymentScaleDialog.data = deployment; } - static close() { + static close(): void { DeploymentScaleDialog.isOpen = false; } - get deployment() { + get deployment(): Deployment { return DeploymentScaleDialog.data; } - close = () => { + close = (): void => { DeploymentScaleDialog.close(); } - @computed get scaleMax() { + @computed get scaleMax(): number { const { currentReplicas } = this; const defaultMax = 50; return currentReplicas <= defaultMax @@ -49,7 +49,7 @@ export class DeploymentScaleDialog extends Component { : currentReplicas * 2; } - onOpen = async () => { + onOpen = async (): Promise => { const { deployment } = this; this.currentReplicas = await deploymentApi.getReplicas({ namespace: deployment.getNs(), @@ -59,15 +59,15 @@ export class DeploymentScaleDialog extends Component { this.ready = true; } - onClose = () => { + onClose = (): void => { this.ready = false; } - onChange = (evt: React.ChangeEvent, value: number) => { + onChange = (evt: React.ChangeEvent, value: number): void => { this.desiredReplicas = value; } - scale = async () => { + scale = async (): Promise => { const { deployment } = this; const { currentReplicas, desiredReplicas, close } = this; try { @@ -83,7 +83,7 @@ export class DeploymentScaleDialog extends Component { } } - renderContents() { + renderContents(): JSX.Element { const { currentReplicas, desiredReplicas, onChange, scaleMax } = this; const warning = currentReplicas < 10 && desiredReplicas > 90; return ( @@ -106,10 +106,10 @@ export class DeploymentScaleDialog extends Component {
} - ) + ); } - render() { + render(): JSX.Element { const { className, ...dialogProps } = this.props; const deploymentName = this.deployment ? this.deployment.getName() : ""; const header = ( diff --git a/dashboard/client/components/+workloads-deployments/deployments.store.ts b/dashboard/client/components/+workloads-deployments/deployments.store.ts index 59acd6c100..d8b410df85 100644 --- a/dashboard/client/components/+workloads-deployments/deployments.store.ts +++ b/dashboard/client/components/+workloads-deployments/deployments.store.ts @@ -1,52 +1,51 @@ import { observable } from "mobx"; -import { Deployment, deploymentApi, IPodMetrics, podsApi, PodStatus } from "../../api/endpoints"; +import { Deployment, deploymentApi, PodMetricsData, podsApi, PodStatus, Pod } from "../../api/endpoints"; import { KubeObjectStore } from "../../kube-object.store"; import { autobind } from "../../utils"; import { podsStore } from "../+workloads-pods/pods.store"; import { apiManager } from "../../api/api-manager"; +import { Metrics } from "client/api/endpoints/metrics.api"; +import { Dictionary } from "lodash"; @autobind() export class DeploymentStore extends KubeObjectStore { api = deploymentApi - @observable metrics: IPodMetrics = null; + @observable metrics: PodMetricsData = null; - protected sortItems(items: Deployment[]) { + protected sortItems(items: Deployment[]): Deployment[] { return super.sortItems(items, [ - item => item.getReplicas(), + (item): number => item.getReplicas(), ], "desc"); } - loadMetrics(deployment: Deployment) { + async loadMetrics(deployment: Deployment): Promise> { const pods = this.getChildPods(deployment); - return podsApi.getMetrics(pods, deployment.getNs(), "").then(metrics => - this.metrics = metrics - ); + const metrics = await podsApi.getMetrics(pods, deployment.getNs(), ""); + return this.metrics = metrics; } - getStatuses(deployments?: Deployment[]) { - const status = { failed: 0, pending: 0, running: 0 } + getStatuses(deployments?: Deployment[]): Dictionary { + const status = { failed: 0, pending: 0, running: 0 }; deployments.forEach(deployment => { const pods = this.getChildPods(deployment); if (pods.some(pod => pod.getStatus() === PodStatus.FAILED)) { - status.failed++ + status.failed++; + } else if (pods.some(pod => pod.getStatus() === PodStatus.PENDING)) { + status.pending++; + } else { + status.running++; } - else if (pods.some(pod => pod.getStatus() === PodStatus.PENDING)) { - status.pending++ - } - else { - status.running++ - } - }) - return status + }); + return status; } - getChildPods(deployment: Deployment) { + getChildPods(deployment: Deployment): Pod[] { return podsStore .getByLabel(deployment.getTemplateLabels()) - .filter(pod => pod.getNs() === deployment.getNs()) + .filter(pod => pod.getNs() === deployment.getNs()); } - reset() { + reset(): void { this.metrics = null; } } diff --git a/dashboard/client/components/+workloads-deployments/deployments.tsx b/dashboard/client/components/+workloads-deployments/deployments.tsx index 32fb1ec10c..7b14aa3421 100644 --- a/dashboard/client/components/+workloads-deployments/deployments.tsx +++ b/dashboard/client/components/+workloads-deployments/deployments.tsx @@ -1,4 +1,4 @@ -import "./deployments.scss" +import "./deployments.scss"; import React from "react"; import { observer } from "mobx-react"; @@ -15,7 +15,7 @@ import { podsStore } from "../+workloads-pods/pods.store"; import { nodesStore } from "../+nodes/nodes.store"; import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object"; -import { IDeploymentsRouteParams } from "../+workloads"; +import { DeploymentsRouteParams } from "../+workloads"; import { _i18n } from "../../i18n"; import { cssNames } from "../../utils"; import kebabCase from "lodash/kebabCase"; @@ -31,40 +31,40 @@ enum sortBy { condition = "condition", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class Deployments extends React.Component { - renderPods(deployment: Deployment) { - const { replicas, availableReplicas } = deployment.status - return `${availableReplicas || 0}/${replicas || 0}` + renderPods(deployment: Deployment): string { + const { replicas, availableReplicas } = deployment.status; + return `${availableReplicas || 0}/${replicas || 0}`; } - renderConditions(deployment: Deployment) { - const conditions = orderBy(deployment.getConditions(true), "type", "asc") + renderConditions(deployment: Deployment): JSX.Element[] { + const conditions = orderBy(deployment.getConditions(true), "type", "asc"); return conditions.map(({ type, message }) => ( {type} - )) + )); } - render() { + render(): JSX.Element { return ( deployment.getName(), - [sortBy.namespace]: (deployment: Deployment) => deployment.getNs(), - [sortBy.replicas]: (deployment: Deployment) => deployment.getReplicas(), - [sortBy.age]: (deployment: Deployment) => deployment.metadata.creationTimestamp, - [sortBy.condition]: (deployment: Deployment) => deployment.getConditionsText(), + [sortBy.name]: (deployment: Deployment): string => deployment.getName(), + [sortBy.namespace]: (deployment: Deployment): string => deployment.getNs(), + [sortBy.replicas]: (deployment: Deployment): number => deployment.getReplicas(), + [sortBy.age]: (deployment: Deployment): string => deployment.metadata.creationTimestamp, + [sortBy.condition]: (deployment: Deployment): string => deployment.getConditionsText(), }} searchFilters={[ - (deployment: Deployment) => deployment.getSearchFields(), - (deployment: Deployment) => deployment.getConditionsText(), + (deployment: Deployment): string[] => deployment.getSearchFields(), + (deployment: Deployment): string => deployment.getConditionsText(), ]} renderHeaderTitle={Deployments} renderTableHeader={[ @@ -76,33 +76,33 @@ export class Deployments extends React.Component { { title: Age, className: "age", sortBy: sortBy.age }, { title: Conditions, className: "conditions", sortBy: sortBy.condition }, ]} - renderTableContents={(deployment: Deployment) => [ + renderTableContents={(deployment: Deployment): (string | number | JSX.Element | JSX.Element[])[] => [ deployment.getName(), deployment.getNs(), this.renderPods(deployment), deployment.getReplicas(), - , + , deployment.getAge(), this.renderConditions(deployment), ]} - renderItemMenu={(item: Deployment) => { - return + renderItemMenu={(item: Deployment): JSX.Element => { + return ; }} /> - ) + ); } } -export function DeploymentMenu(props: KubeObjectMenuProps) { +export function DeploymentMenu(props: KubeObjectMenuProps): JSX.Element { const { object, toolbar } = props; return ( - DeploymentScaleDialog.open(object)}> + DeploymentScaleDialog.open(object)}> Scale - ) + ); } apiManager.registerViews(deploymentApi, { diff --git a/dashboard/client/components/+workloads-deployments/index.ts b/dashboard/client/components/+workloads-deployments/index.ts index 1a8a81abb5..322a39b8e6 100644 --- a/dashboard/client/components/+workloads-deployments/index.ts +++ b/dashboard/client/components/+workloads-deployments/index.ts @@ -1,2 +1,2 @@ -export * from "./deployments" -export * from "./deployment-details" +export * from "./deployments"; +export * from "./deployment-details"; diff --git a/dashboard/client/components/+workloads-jobs/index.ts b/dashboard/client/components/+workloads-jobs/index.ts index 8389bf51cd..d4cf561141 100644 --- a/dashboard/client/components/+workloads-jobs/index.ts +++ b/dashboard/client/components/+workloads-jobs/index.ts @@ -1,2 +1,2 @@ -export * from "./jobs" -export * from "./job-details" +export * from "./jobs"; +export * from "./job-details"; diff --git a/dashboard/client/components/+workloads-jobs/job-details.tsx b/dashboard/client/components/+workloads-jobs/job-details.tsx index a7f683df8a..2d71824a76 100644 --- a/dashboard/client/components/+workloads-jobs/job-details.tsx +++ b/dashboard/client/components/+workloads-jobs/job-details.tsx @@ -26,21 +26,23 @@ interface Props extends KubeObjectDetailsProps { @observer export class JobDetails extends React.Component { - async componentDidMount() { + async componentDidMount(): Promise { if (!podsStore.isLoaded) { - podsStore.loadAll(); + return podsStore.loadAll(); } } - render() { + render(): JSX.Element { const { object: job } = this.props; - if (!job) return null; - const selectors = job.getSelectors() - const nodeSelector = job.getNodeSelectors() - const images = job.getImages() - const childPods = jobStore.getChildPods(job) - const ownerRefs = job.getOwnerRefs() - const condition = job.getCondition() + if (!job) { + return null; + } + const selectors = job.getSelectors(); + const nodeSelector = job.getNodeSelectors(); + const images = job.getImages(); + const childPods = jobStore.getChildPods(job); + const ownerRefs = job.getOwnerRefs(); + const condition = job.getCondition(); return (
@@ -70,7 +72,7 @@ export class JobDetails extends React.Component { { ownerRefs.map(ref => { const { name, kind } = ref; - const detailsUrl = getDetailsUrl(lookupApiLink(ref, job)) + const detailsUrl = getDetailsUrl(lookupApiLink(ref, job)); return (

{kind} {name} @@ -93,7 +95,7 @@ export class JobDetails extends React.Component { {job.getDesiredCompletions()} Parallelism}> - {job.getParallelism()} + {job.spec.parallelism} @@ -103,7 +105,7 @@ export class JobDetails extends React.Component {

- ) + ); } } diff --git a/dashboard/client/components/+workloads-jobs/job.store.ts b/dashboard/client/components/+workloads-jobs/job.store.ts index 592347062c..0fc1196c97 100644 --- a/dashboard/client/components/+workloads-jobs/job.store.ts +++ b/dashboard/client/components/+workloads-jobs/job.store.ts @@ -4,40 +4,38 @@ import { Job, jobApi } from "../../api/endpoints/job.api"; import { CronJob, Pod, PodStatus } from "../../api/endpoints"; import { podsStore } from "../+workloads-pods/pods.store"; import { apiManager } from "../../api/api-manager"; +import { Dictionary } from "lodash"; @autobind() export class JobStore extends KubeObjectStore { api = jobApi getChildPods(job: Job): Pod[] { - return podsStore.getPodsByOwner(job) + return podsStore.getPodsByOwner(job); } - getJobsByOwner(cronJob: CronJob) { + getJobsByOwner(cronJob: CronJob): Job[] { return this.items.filter(job => job.getNs() == cronJob.getNs() && job.getOwnerRefs().find(ref => ref.name === cronJob.getName() && ref.kind === cronJob.kind) - ) + ); } - getStatuses(jobs?: Job[]) { - const status = { failed: 0, pending: 0, running: 0, succeeded: 0 } + getStatuses(jobs?: Job[]): Dictionary { + const status = { failed: 0, pending: 0, running: 0, succeeded: 0 }; jobs.forEach(job => { - const pods = this.getChildPods(job) + const pods = this.getChildPods(job); if (pods.some(pod => pod.getStatus() === PodStatus.FAILED)) { - status.failed++ + status.failed++; + } else if (pods.some(pod => pod.getStatus() === PodStatus.PENDING)) { + status.pending++; + } else if (pods.some(pod => pod.getStatus() === PodStatus.RUNNING)) { + status.running++; + } else { + status.succeeded++; } - else if (pods.some(pod => pod.getStatus() === PodStatus.PENDING)) { - status.pending++ - } - else if (pods.some(pod => pod.getStatus() === PodStatus.RUNNING)) { - status.running++ - } - else { - status.succeeded++ - } - }) - return status + }); + return status; } } diff --git a/dashboard/client/components/+workloads-jobs/jobs.tsx b/dashboard/client/components/+workloads-jobs/jobs.tsx index a324531a08..87e2aa4000 100644 --- a/dashboard/client/components/+workloads-jobs/jobs.tsx +++ b/dashboard/client/components/+workloads-jobs/jobs.tsx @@ -10,7 +10,7 @@ import { eventStore } from "../+events/event.store"; import { Job, jobApi } from "../../api/endpoints/job.api"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { KubeObjectListLayout } from "../kube-object"; -import { IJobsRouteParams } from "../+workloads"; +import { JobsRouteParams } from "../+workloads"; import { KubeEventIcon } from "../+events/kube-event-icon"; import kebabCase from "lodash/kebabCase"; import { apiManager } from "../../api/api-manager"; @@ -22,24 +22,24 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class Jobs extends React.Component { - render() { + render(): JSX.Element { return ( job.getName(), - [sortBy.namespace]: (job: Job) => job.getNs(), - [sortBy.conditions]: (job: Job) => job.getCondition() != null ? job.getCondition().type : "", - [sortBy.age]: (job: Job) => job.metadata.creationTimestamp, + [sortBy.name]: (job: Job): string => job.getName(), + [sortBy.namespace]: (job: Job): string => job.getNs(), + [sortBy.conditions]: (job: Job): string => job.getCondition() != null ? job.getCondition().type : "", + [sortBy.age]: (job: Job): string => job.metadata.creationTimestamp, }} searchFilters={[ - (job: Job) => job.getSearchFields(), + (job: Job): string[]=> job.getSearchFields(), ]} renderHeaderTitle={Jobs} renderTableHeader={[ @@ -50,34 +50,34 @@ export class Jobs extends React.Component { { title: Age, className: "age", sortBy: sortBy.age }, { title: Conditions, className: "conditions", sortBy: sortBy.conditions }, ]} - renderTableContents={(job: Job) => { + renderTableContents={(job: Job): (string | React.ReactNode | JSX.Element)[] => { const condition = job.getCondition(); return [ job.getName(), job.getNs(), `${job.getCompletions()} / ${job.getDesiredCompletions()}`, - , + , job.getAge(), condition && { title: condition.type, className: kebabCase(condition.type), } - ] + ]; }} - renderItemMenu={(item: Job) => { - return + renderItemMenu={(item: Job): JSX.Element => { + return ; }} /> - ) + ); } } -export function JobMenu(props: KubeObjectMenuProps) { +export function JobMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(jobApi, { Menu: JobMenu, -}) +}); diff --git a/dashboard/client/components/+workloads-overview/overview-statuses.tsx b/dashboard/client/components/+workloads-overview/overview-statuses.tsx index 83575fec86..03e183f31c 100644 --- a/dashboard/client/components/+workloads-overview/overview-statuses.tsx +++ b/dashboard/client/components/+workloads-overview/overview-statuses.tsx @@ -1,4 +1,4 @@ -import "./overview-statuses.scss" +import "./overview-statuses.scss"; import React from "react"; import { observer } from "mobx-react"; @@ -15,13 +15,11 @@ import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; import { namespaceStore } from "../+namespaces/namespace.store"; import { PageFiltersList } from "../item-object-list/page-filters-list"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select"; -import { configStore } from "../../config.store"; import { isAllowedResource } from "../../api/rbac"; @observer export class OverviewStatuses extends React.Component { - render() { - const { allowedResources } = configStore; + render(): JSX.Element { const { contextNs } = namespaceStore; const pods = isAllowedResource("pods") ? podsStore.getAllByNs(contextNs) : []; const deployments = isAllowedResource("deployments") ? deploymentStore.getAllByNs(contextNs) : []; @@ -75,6 +73,6 @@ export class OverviewStatuses extends React.Component { }
- ) + ); } } diff --git a/dashboard/client/components/+workloads-overview/overview-workload-status.tsx b/dashboard/client/components/+workloads-overview/overview-workload-status.tsx index dcd85d7ec9..fde92af35b 100644 --- a/dashboard/client/components/+workloads-overview/overview-workload-status.tsx +++ b/dashboard/client/components/+workloads-overview/overview-workload-status.tsx @@ -20,18 +20,21 @@ interface Props { export class OverviewWorkloadStatus extends React.Component { @observable elem: HTMLElement - componentDidMount() { - this.elem = findDOMNode(this) as HTMLElement + componentDidMount(): void { + // eslint-disable-next-line react/no-find-dom-node + this.elem = findDOMNode(this) as HTMLElement; } - getStatusColor(status: string) { + getStatusColor(status: string): string { return cssVar(this.elem).get(`--workload-status-${status.toLowerCase()}`).toString(); } - renderChart() { - if (!this.elem) return null - const { status } = this.props - const statuses = Object.entries(status) + renderChart(): JSX.Element { + if (!this.elem) { + return null; + } + const { status } = this.props; + const statuses = Object.entries(status); const chartData: Partial = { labels: [] as string[], datasets: [{ @@ -39,21 +42,21 @@ export class OverviewWorkloadStatus extends React.Component { backgroundColor: [themeStore.activeTheme.colors.pieChartDefaultColor], label: "Empty" }] - } - if (statuses.some(([key, val]) => val > 0)) { + }; + if (statuses.some(([_key, val]) => val > 0)) { const dataset: any = { data: [], backgroundColor: [], label: "Status", - } + }; statuses.forEach(([key, val]) => { if (val !== 0) { - dataset.data.push(val) - dataset.backgroundColor.push(this.getStatusColor(key)) - chartData.labels.push(capitalize(key) + ": " + val) + dataset.data.push(val); + dataset.backgroundColor.push(this.getStatusColor(key)); + chartData.labels.push(capitalize(key) + ": " + val); } - }) - chartData.datasets[0] = dataset + }); + chartData.datasets[0] = dataset; } const options = { elements: { @@ -61,19 +64,19 @@ export class OverviewWorkloadStatus extends React.Component { borderWidth: 0, }, }, - } + }; return ( - ) + ); } - render() { + render(): JSX.Element { return (
{this.renderChart()}
- ) + ); } } diff --git a/dashboard/client/components/+workloads-overview/overview.tsx b/dashboard/client/components/+workloads-overview/overview.tsx index c3f08d13c2..a1244f2f3a 100644 --- a/dashboard/client/components/+workloads-overview/overview.tsx +++ b/dashboard/client/components/+workloads-overview/overview.tsx @@ -1,11 +1,11 @@ -import "./overview.scss" +import "./overview.scss"; import React from "react"; import { observable, when } from "mobx"; import { observer } from "mobx-react"; import { OverviewStatuses } from "./overview-statuses"; import { RouteComponentProps } from "react-router"; -import { IWorkloadsOverviewRouteParams } from "../+workloads"; +import { WorkloadsOverviewRouteParams } from "../+workloads"; import { eventStore } from "../+events/event.store"; import { podsStore } from "../+workloads-pods/pods.store"; import { deploymentStore } from "../+workloads-deployments/deployments.store"; @@ -17,9 +17,9 @@ import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; import { Spinner } from "../spinner"; import { Events } from "../+events"; import { KubeObjectStore } from "../../kube-object.store"; -import { isAllowedResource } from "../../api/rbac" +import { isAllowedResource } from "../../api/rbac"; -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer @@ -27,7 +27,7 @@ export class WorkloadsOverview extends React.Component { @observable isReady = false; @observable isUnmounting = false; - async componentDidMount() { + async componentDidMount(): Promise { const stores: KubeObjectStore[] = []; if (isAllowedResource("pods")) { stores.push(podsStore); @@ -61,13 +61,13 @@ export class WorkloadsOverview extends React.Component { unsubscribeList.forEach(dispose => dispose()); } - componentWillUnmount() { + componentWillUnmount(): void { this.isUnmounting = true; } - renderContents() { + renderContents(): JSX.Element { if (!this.isReady) { - return + return ; } return ( <> @@ -78,14 +78,14 @@ export class WorkloadsOverview extends React.Component { className="box grow" /> } - ) + ); } - render() { + render(): JSX.Element { return (
{this.renderContents()}
- ) + ); } } diff --git a/dashboard/client/components/+workloads-pods/container-charts.tsx b/dashboard/client/components/+workloads-pods/container-charts.tsx index f879fa4662..7e4f3b915e 100644 --- a/dashboard/client/components/+workloads-pods/container-charts.tsx +++ b/dashboard/client/components/+workloads-pods/container-charts.tsx @@ -1,21 +1,25 @@ import React, { useContext } from "react"; import { t } from "@lingui/macro"; -import { IPodMetrics } from "../../api/endpoints"; +import { PodMetricsData } from "../../api/endpoints"; import { BarChart, cpuOptions, memoryOptions } from "../chart"; import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api"; import { NoMetrics } from "../resource-metrics/no-metrics"; -import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics"; +import { ResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics"; import { _i18n } from "../../i18n"; import { themeStore } from "../../theme.store"; -type IContext = IResourceMetricsValue; +type IContext = ResourceMetricsValue; -export const ContainerCharts = () => { +export const ContainerCharts = (): JSX.Element => { const { params: { metrics }, tabId } = useContext(ResourceMetricsContext); const { chartCapacityColor } = themeStore.activeTheme.colors; - if (!metrics) return null; - if (isMetricsEmpty(metrics)) return ; + if (!metrics) { + return null; + } + if (isMetricsEmpty(metrics)) { + return ; + } const values = Object.values(metrics) .map(normalizeMetrics) @@ -100,4 +104,4 @@ export const ContainerCharts = () => { data={{ datasets: datasets[tabId] }} /> ); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/dashboard/client/components/+workloads-pods/index.ts b/dashboard/client/components/+workloads-pods/index.ts index 3b9241ecca..f3181cb3a2 100644 --- a/dashboard/client/components/+workloads-pods/index.ts +++ b/dashboard/client/components/+workloads-pods/index.ts @@ -1,2 +1,2 @@ -export * from "./pods" -export * from "./pod-details" \ No newline at end of file +export * from "./pods"; +export * from "./pod-details"; \ No newline at end of file diff --git a/dashboard/client/components/+workloads-pods/pod-charts.tsx b/dashboard/client/components/+workloads-pods/pod-charts.tsx index 39439d9678..5607e764ed 100644 --- a/dashboard/client/components/+workloads-pods/pod-charts.tsx +++ b/dashboard/client/components/+workloads-pods/pod-charts.tsx @@ -1,31 +1,35 @@ import React, { useContext } from "react"; import { t, Trans } from "@lingui/macro"; import { observer } from "mobx-react"; -import { IPodMetrics } from "../../api/endpoints"; +import { PodMetricsData } from "../../api/endpoints"; import { BarChart, cpuOptions, memoryOptions } from "../chart"; import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api"; import { NoMetrics } from "../resource-metrics/no-metrics"; -import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics"; +import { ResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics"; import { _i18n } from "../../i18n"; import { WorkloadKubeObject } from "../../api/workload-kube-object"; import { themeStore } from "../../theme.store"; export const podMetricTabs = [ - CPU, - Memory, - Network, - Filesystem, + CPU, + Memory, + Network, + Filesystem, ]; -type IContext = IResourceMetricsValue; +type IContext = ResourceMetricsValue; export const PodCharts = observer(() => { const { params: { metrics }, tabId, object } = useContext(ResourceMetricsContext); const { chartCapacityColor } = themeStore.activeTheme.colors; const id = object.getId(); - if (!metrics) return null; - if (isMetricsEmpty(metrics)) return ; + if (!metrics) { + return null; + } + if (isMetricsEmpty(metrics)) { + return ; + } const options = tabId == 0 ? cpuOptions : memoryOptions; const values = Object.values(metrics) @@ -128,4 +132,4 @@ export const PodCharts = observer(() => { data={{ datasets: datasets[tabId] }} /> ); -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+workloads-pods/pod-container-env.tsx b/dashboard/client/components/+workloads-pods/pod-container-env.tsx index f5cce6fa8c..50ab29c3ef 100644 --- a/dashboard/client/components/+workloads-pods/pod-container-env.tsx +++ b/dashboard/client/components/+workloads-pods/pod-container-env.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useState } from "react"; import flatten from "lodash/flatten"; import { observer } from "mobx-react"; import { Trans } from "@lingui/macro"; -import { IPodContainer, Secret } from "../../api/endpoints"; +import { PodContainer, Secret } from "../../api/endpoints"; import { DrawerItem } from "../drawer"; import { autorun } from "mobx"; import { secretsStore } from "../+config-secrets/secrets.store"; @@ -13,45 +13,45 @@ import { Icon } from "../icon"; import { base64, cssNames } from "../../utils"; interface Props { - container: IPodContainer; + container: PodContainer; namespace: string; } export const ContainerEnvironment = observer((props: Props) => { - const { container: { env, envFrom }, namespace } = props + const { container: { env, envFrom }, namespace } = props; useEffect( () => autorun(() => { env && env.forEach(variable => { - const { valueFrom } = variable + const { valueFrom } = variable; if (valueFrom && valueFrom.configMapKeyRef) { - configMapsStore.load({ name: valueFrom.configMapKeyRef.name, namespace }) + configMapsStore.load({ name: valueFrom.configMapKeyRef.name, namespace }); } - }) + }); envFrom && envFrom.forEach(item => { - const { configMapRef } = item + const { configMapRef } = item; if (configMapRef && configMapRef.name) { - configMapsStore.load({ name: configMapRef.name, namespace }) + configMapsStore.load({ name: configMapRef.name, namespace }); } - }) + }); }), [] - ) + ); - const renderEnv = () => { + const renderEnv = (): JSX.Element[] => { return env.map(variable => { - const { name, value, valueFrom } = variable - let secretValue = null + const { name, value, valueFrom } = variable; + let secretValue = null; if (value) { - secretValue = value + secretValue = value; } if (valueFrom) { - const { fieldRef, secretKeyRef, configMapKeyRef } = valueFrom + const { fieldRef, secretKeyRef, configMapKeyRef } = valueFrom; if (fieldRef) { - const { apiVersion, fieldPath } = fieldRef - secretValue = `fieldRef(${apiVersion}:${fieldPath})` + const { apiVersion, fieldPath } = fieldRef; + secretValue = `fieldRef(${apiVersion}:${fieldPath})`; } if (secretKeyRef) { secretValue = ( @@ -59,14 +59,14 @@ export const ContainerEnvironment = observer((props: Props) => { reference={secretKeyRef} namespace={namespace} /> - ) + ); } if (configMapKeyRef) { - const { name, key } = configMapKeyRef - const configMap = configMapsStore.getByName(name, namespace) + const { name, key } = configMapKeyRef; + const configMap = configMapsStore.getByName(name, namespace); secretValue = configMap ? configMap.data[key] : - `configMapKeyRef(${name}${key})` + `configMapKeyRef(${name}${key})`; } } @@ -74,31 +74,35 @@ export const ContainerEnvironment = observer((props: Props) => {
{name}: {secretValue}
- ) - }) - } + ); + }); + }; - const renderEnvFrom = () => { + const renderEnvFrom = (): JSX.Element[] => { const envVars = envFrom.map(vars => { - if (!vars.configMapRef || !vars.configMapRef.name) return - const configMap = configMapsStore.getByName(vars.configMapRef.name, namespace) - if (!configMap) return + if (!vars.configMapRef || !vars.configMapRef.name) { + return; + } + const configMap = configMapsStore.getByName(vars.configMapRef.name, namespace); + if (!configMap) { + return; + } return Object.entries(configMap.data).map(([name, value]) => (
{name}: {value}
- )) - }) - return flatten(envVars) - } + )); + }); + return flatten(envVars); + }; return ( Environment} className="ContainerEnvironment"> {env && renderEnv()} {envFrom && renderEnvFrom()} - ) -}) + ); +}); interface SecretKeyProps { reference: { @@ -108,20 +112,20 @@ interface SecretKeyProps { namespace: string; } -const SecretKey = (props: SecretKeyProps) => { - const { reference: { name, key }, namespace } = props - const [loading, setLoading] = useState(false) - const [secret, setSecret] = useState() +const SecretKey = (props: SecretKeyProps): JSX.Element => { + const { reference: { name, key }, namespace } = props; + const [loading, setLoading] = useState(false); + const [secret, setSecret] = useState(); - const showKey = async () => { - setLoading(true) + const showKey = async (): Promise => { + setLoading(true); const secret = await secretsStore.load({ name, namespace }); - setLoading(false) - setSecret(secret) - } + setLoading(false); + setSecret(secret); + }; if (secret?.data?.[key]) { - return <>{base64.decode(secret.data[key])} + return <>{base64.decode(secret.data[key])}; } return ( @@ -134,5 +138,5 @@ const SecretKey = (props: SecretKeyProps) => { onClick={showKey} /> - ) -} + ); +}; diff --git a/dashboard/client/components/+workloads-pods/pod-details-affinities.tsx b/dashboard/client/components/+workloads-pods/pod-details-affinities.tsx index 06c992a0b8..f236d92dbd 100644 --- a/dashboard/client/components/+workloads-pods/pod-details-affinities.tsx +++ b/dashboard/client/components/+workloads-pods/pod-details-affinities.tsx @@ -11,11 +11,13 @@ interface Props { } export class PodDetailsAffinities extends React.Component { - render() { - const { workload } = this.props - const affinitiesNum = workload.getAffinityNumber() - const affinities = workload.getAffinity() - if (!affinitiesNum) return null + render(): JSX.Element { + const { workload } = this.props; + const affinitiesNum = workload.getAffinityNumber(); + const affinities = workload.getAffinity(); + if (!affinitiesNum) { + return null; + } return ( Affinities} className="PodDetailsAffinities"> @@ -29,6 +31,6 @@ export class PodDetailsAffinities extends React.Component {
- ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/+workloads-pods/pod-details-container.tsx b/dashboard/client/components/+workloads-pods/pod-details-container.tsx index 4218695a51..c3e3397f92 100644 --- a/dashboard/client/components/+workloads-pods/pod-details-container.tsx +++ b/dashboard/client/components/+workloads-pods/pod-details-container.tsx @@ -1,39 +1,41 @@ -import "./pod-details-container.scss" +import "./pod-details-container.scss"; import * as React from "react"; import { t, Trans } from "@lingui/macro"; -import { IPodContainer, Pod } from "../../api/endpoints"; +import { PodContainer, Pod } from "../../api/endpoints"; import { DrawerItem } from "../drawer"; import { cssNames } from "../../utils"; import { StatusBrick } from "../status-brick"; import { Badge } from "../badge"; import { ContainerEnvironment } from "./pod-container-env"; import { ResourceMetrics } from "../resource-metrics"; -import { IMetrics } from "../../api/endpoints/metrics.api"; +import { Metrics } from "../../api/endpoints/metrics.api"; import { ContainerCharts } from "./container-charts"; import { _i18n } from "../../i18n"; interface Props { pod: Pod; - container: IPodContainer; - metrics?: { [key: string]: IMetrics }; + container: PodContainer; + metrics?: { [key: string]: Metrics }; } export class PodDetailsContainer extends React.Component { - render() { - const { pod, container, metrics } = this.props - if (!pod || !container) return null - const { name, image, imagePullPolicy, ports, volumeMounts, command, args } = container - const status = pod.getContainerStatuses().find(status => status.name === container.name) - const state = status ? Object.keys(status.state)[0] : "" - const ready = status ? status.ready : "" - const liveness = pod.getLivenessProbe(container) - const readiness = pod.getReadinessProbe(container) - const isInitContainer = !!pod.getInitContainers().find(c => c.name == name); + render(): JSX.Element { + const { pod, container, metrics } = this.props; + if (!pod || !container) { + return null; + } + const { name, image, imagePullPolicy, ports, volumeMounts, command, args } = container; + const status = pod.getContainerStatuses().find(status => status.name === container.name); + const state = status ? Object.keys(status.state)[0] : ""; + const ready = status ? status.ready : ""; + const liveness = pod.getLivenessProbe(container); + const readiness = pod.getReadinessProbe(container); + const isInitContainer = !!pod.spec.initContainers.find(c => c.name == name); const metricTabs = [ - CPU, - Memory, - Filesystem, + CPU, + Memory, + Filesystem, ]; return (
@@ -66,12 +68,12 @@ export class PodDetailsContainer extends React.Component { { ports.map(port => { const { name, containerPort, protocol } = port; - const key = `${container.name}-port-${containerPort}-${protocol}` + const key = `${container.name}-port-${containerPort}-${protocol}`; return (
{name ? name + ': ' : ''}{containerPort}/{protocol}
- ) + ); }) } @@ -87,7 +89,7 @@ export class PodDetailsContainer extends React.Component { {mountPath} from {name} ({readOnly ? 'ro' : 'rw'}) - ) + ); }) } @@ -122,6 +124,6 @@ export class PodDetailsContainer extends React.Component { }
- ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/+workloads-pods/pod-details-list.tsx b/dashboard/client/components/+workloads-pods/pod-details-list.tsx index 313c505565..9326934038 100644 --- a/dashboard/client/components/+workloads-pods/pod-details-list.tsx +++ b/dashboard/client/components/+workloads-pods/pod-details-list.tsx @@ -6,7 +6,7 @@ import { disposeOnUnmount, observer } from "mobx-react"; import { Trans } from "@lingui/macro"; import { podsStore } from "./pods.store"; import { Pod } from "../../api/endpoints"; -import { autobind, bytesToUnits, cssNames, interval, prevDefault } from "../../utils"; +import { autobind, bytesToUnits, cssNames, IntervalManager, prevDefault } from "../../utils"; import { KubeEventIcon } from "../+events/kube-event-icon"; import { LineProgress } from "../line-progress"; import { KubeObject } from "../../api/kube-object"; @@ -40,36 +40,38 @@ export class PodDetailsList extends React.Component { showTitle: true } - private metricsWatcher = interval(120, () => { + private metricsWatcher = new IntervalManager(120, () => { podsStore.loadKubeMetrics(this.props.owner.getNs()); }); private sortingCallbacks = { - [sortBy.name]: (pod: Pod) => pod.getName(), - [sortBy.namespace]: (pod: Pod) => pod.getNs(), - [sortBy.cpu]: (pod: Pod) => podsStore.getPodKubeMetrics(pod).cpu, - [sortBy.memory]: (pod: Pod) => podsStore.getPodKubeMetrics(pod).memory, + [sortBy.name]: (pod: Pod): string => pod.getName(), + [sortBy.namespace]: (pod: Pod): string => pod.getNs(), + [sortBy.cpu]: (pod: Pod): number => podsStore.getPodKubeMetrics(pod).cpu, + [sortBy.memory]: (pod: Pod): number => podsStore.getPodKubeMetrics(pod).memory, } - componentDidMount() { + componentDidMount(): void { this.metricsWatcher.start(true); disposeOnUnmount(this, [ reaction(() => this.props.owner, () => this.metricsWatcher.restart(true)) - ]) + ]); } - componentWillUnmount() { + componentWillUnmount(): void { this.metricsWatcher.stop(); } - renderCpuUsage(id: string, usage: number) { + renderCpuUsage(id: string, usage: number): (JSX.Element | string | number) { const { maxCpu } = this.props; const value = usage.toFixed(3); const tooltip = (

CPU: {Math.ceil(usage * 100) / maxCpu}%
{usage.toFixed(3)}

); if (!maxCpu) { - if (parseFloat(value) === 0) return 0; + if (parseFloat(value) === 0) { + return 0; + } return value; } return ( @@ -80,12 +82,14 @@ export class PodDetailsList extends React.Component { ); } - renderMemoryUsage(id: string, usage: number) { + renderMemoryUsage(id: string, usage: number): (JSX.Element | number | string) { const { maxMemory } = this.props; const tooltip = (

Memory: {Math.ceil(usage * 100 / maxMemory)}%
{bytesToUnits(usage, 3)}

); - if (!maxMemory) return usage ? bytesToUnits(usage) : 0; + if (!maxMemory) { + return usage ? bytesToUnits(usage) : 0; + } return ( { } @autobind() - getTableRow(uid: string) { + getTableRow(uid: string): JSX.Element { const { pods } = this.props; const pod = pods.find(pod => pod.getId() == uid); const metrics = podsStore.getPodKubeMetrics(pod); @@ -116,13 +120,17 @@ export class PodDetailsList extends React.Component { ); } - render() { + render(): JSX.Element { const { pods, showTitle } = this.props; const virtual = pods.length > 100; - if (!pods.length && !podsStore.isLoaded) return ( -
- ); - if (!pods.length) return null; + if (!pods.length && !podsStore.isLoaded) { + return ( +
+ ); + } + if (!pods.length) { + return null; + } return (
{showTitle && Pods}/>} diff --git a/dashboard/client/components/+workloads-pods/pod-details-secrets.tsx b/dashboard/client/components/+workloads-pods/pod-details-secrets.tsx index 3b940e6855..53a964ce7b 100644 --- a/dashboard/client/components/+workloads-pods/pod-details-secrets.tsx +++ b/dashboard/client/components/+workloads-pods/pod-details-secrets.tsx @@ -26,7 +26,7 @@ export class PodDetailsSecrets extends Component { ); }); - render() { + render(): JSX.Element { return (
{ diff --git a/dashboard/client/components/+workloads-pods/pod-details-statuses.tsx b/dashboard/client/components/+workloads-pods/pod-details-statuses.tsx index 49dc634bf6..e3473ddf76 100644 --- a/dashboard/client/components/+workloads-pods/pod-details-statuses.tsx +++ b/dashboard/client/components/+workloads-pods/pod-details-statuses.tsx @@ -9,10 +9,12 @@ interface Props { } export class PodDetailsStatuses extends React.Component { - render() { - const { pods } = this.props - if (!pods.length) return null - const statuses = countBy(pods.map(pod => pod.getStatus())) + render(): JSX.Element { + const { pods } = this.props; + if (!pods.length) { + return null; + } + const statuses = countBy(pods.map(pod => pod.getStatus())); return (
{ @@ -23,6 +25,6 @@ export class PodDetailsStatuses extends React.Component { )) }
- ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/+workloads-pods/pod-details-tolerations.tsx b/dashboard/client/components/+workloads-pods/pod-details-tolerations.tsx index 20afcf7329..49507a3be3 100644 --- a/dashboard/client/components/+workloads-pods/pod-details-tolerations.tsx +++ b/dashboard/client/components/+workloads-pods/pod-details-tolerations.tsx @@ -9,16 +9,18 @@ interface Props { } export class PodDetailsTolerations extends React.Component { - render() { - const { workload } = this.props - const tolerations = workload.getTolerations() - if (!tolerations.length) return null + render(): JSX.Element { + const { workload } = this.props; + const tolerations = workload.getTolerations(); + if (!tolerations.length) { + return null; + } return ( Tolerations} className="PodDetailsTolerations"> { tolerations.map((toleration, index) => { - const { key, operator, effect, tolerationSeconds } = toleration + const { key, operator, effect, tolerationSeconds } = toleration; return (
Key}>{key} @@ -26,11 +28,11 @@ export class PodDetailsTolerations extends React.Component { {effect && Effect}>{effect}} {!!tolerationSeconds && Effect}>{tolerationSeconds}}
- ) + ); }) }
- ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/+workloads-pods/pod-details.tsx b/dashboard/client/components/+workloads-pods/pod-details.tsx index 549d5232f6..ebeb5f6495 100644 --- a/dashboard/client/components/+workloads-pods/pod-details.tsx +++ b/dashboard/client/components/+workloads-pods/pod-details.tsx @@ -1,4 +1,4 @@ -import "./pod-details.scss" +import "./pod-details.scss"; import * as React from "react"; import kebabCase from "lodash/kebabCase"; @@ -6,7 +6,7 @@ import { disposeOnUnmount, observer } from "mobx-react"; import { Link } from "react-router-dom"; import { autorun, observable, reaction, toJS } from "mobx"; import { Trans } from "@lingui/macro"; -import { IPodMetrics, nodesApi, Pod, podsApi, pvcApi, configMapApi } from "../../api/endpoints"; +import { PodMetricsData, nodesApi, Pod, podsApi, pvcApi, configMapApi } from "../../api/endpoints"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; import { autobind, cssNames, interval } from "../../utils"; @@ -22,7 +22,6 @@ import { getDetailsUrl } from "../../navigation"; import { KubeObjectDetailsProps } from "../kube-object"; import { getItemMetrics } from "../../api/endpoints/metrics.api"; import { PodCharts, podMetricTabs } from "./pod-charts"; -import { lookupApiLink } from "../../api/kube-api"; import { apiManager } from "../../api/api-manager"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; @@ -31,11 +30,11 @@ interface Props extends KubeObjectDetailsProps { @observer export class PodDetails extends React.Component { - @observable containerMetrics: IPodMetrics; + @observable containerMetrics: PodMetricsData; private watcher = interval(60, () => this.loadMetrics()); - componentDidMount() { + componentDidMount(): void { disposeOnUnmount(this, [ autorun(() => { this.containerMetrics = null; @@ -48,30 +47,31 @@ export class PodDetails extends React.Component { this.watcher.start(); } - componentWillUnmount() { + componentWillUnmount(): void { podsStore.reset(); } @autobind() - async loadMetrics() { + async loadMetrics(): Promise { const { object: pod } = this.props; this.containerMetrics = await podsStore.loadContainerMetrics(pod); } - render() { + render(): JSX.Element { const { object: pod } = this.props; - if (!pod) return null; + if (!pod) { + return null; + } const { status, spec } = pod; const { conditions, podIP } = status; const { nodeName } = spec; const nodeSelector = pod.getNodeSelectors(); - const volumes = pod.getVolumes(); - const labels = pod.getLabels(); + const volumes = pod.spec.volumes; const metrics = podsStore.metrics; return (
podsStore.loadMetrics(pod)} + loader={(): Promise => podsStore.loadMetrics(pod)} tabs={podMetricTabs} object={pod} params={{ metrics }} > @@ -91,10 +91,10 @@ export class PodDetails extends React.Component { {podIP} Priority Class}> - {pod.getPriorityClassName()} + {pod.spec.priorityClassName} QoS Class}> - {pod.getQosClass()} + {pod.status.qosClass} {conditions && Conditions} className="conditions" labelsOnly> @@ -108,7 +108,7 @@ export class PodDetails extends React.Component { className={cssNames({ disabled: status === "False" })} tooltip={Last transition time: {lastTransitionTime}} /> - ) + ); }) } @@ -131,17 +131,17 @@ export class PodDetails extends React.Component { )} - {pod.getInitContainers() && pod.getInitContainers().length > 0 && + {pod.spec.initContainers?.length > 0 && Init Containers}/> } { - pod.getInitContainers() && pod.getInitContainers().map(container => { - return + pod.spec.initContainers?.map((container): JSX.Element => { + return ; }) } Containers}/> { - pod.getContainers().map(container => { + pod.spec.containers.map((container): JSX.Element => { const { name } = container; const metrics = getItemMetrics(toJS(this.containerMetrics), name); return ( @@ -151,7 +151,7 @@ export class PodDetails extends React.Component { container={container} metrics={metrics} /> - ) + ); }) } @@ -161,7 +161,7 @@ export class PodDetails extends React.Component { {volumes.map(volume => { const claimName = volume.persistentVolumeClaim ? volume.persistentVolumeClaim.claimName : null; const configMap = volume.configMap ? volume.configMap.name : null; - const type = Object.keys(volume)[1] + const type = Object.keys(volume)[1]; return (
@@ -174,28 +174,28 @@ export class PodDetails extends React.Component { { type == "configMap" && (
- {configMap && ( - Name}> - {configMap} - - - )} + {configMap && ( + Name}> + {configMap} + + + )}
)} { type === "emptyDir" && (
{ volume.emptyDir.medium && ( - Medium}> - {volume.emptyDir.medium} - + Medium}> + {volume.emptyDir.medium} + )} { volume.emptyDir.sizeLimit && ( Size Limit}> - {volume.emptyDir.sizeLimit} + {volume.emptyDir.sizeLimit} )}
@@ -213,16 +213,16 @@ export class PodDetails extends React.Component { )}
- ) + ); })} )}
- ) + ); } } apiManager.registerViews(podsApi, { Details: PodDetails -}) +}); diff --git a/dashboard/client/components/+workloads-pods/pod-logs-dialog.tsx b/dashboard/client/components/+workloads-pods/pod-logs-dialog.tsx index b754559b27..58f498a4fd 100644 --- a/dashboard/client/components/+workloads-pods/pod-logs-dialog.tsx +++ b/dashboard/client/components/+workloads-pods/pod-logs-dialog.tsx @@ -7,17 +7,17 @@ import { t, Trans } from "@lingui/macro"; import { _i18n } from "../../i18n"; import { Dialog, DialogProps } from "../dialog"; import { Wizard, WizardStep } from "../wizard"; -import { IPodContainer, Pod, podsApi } from "../../api/endpoints"; +import { PodContainer, Pod, podsApi } from "../../api/endpoints"; import { Icon } from "../icon"; import { Select, SelectOption } from "../select"; import { Spinner } from "../spinner"; -import { cssNames, downloadFile, interval } from "../../utils"; +import { cssNames, downloadFile, IntervalManager } from "../../utils"; import { default as AnsiUp } from "ansi_up"; -import DOMPurify from "dompurify" +import DOMPurify from "dompurify"; -interface IPodLogsDialogData { +interface PodLogsDialogData { pod: Pod; - container?: IPodContainer; + container?: PodContainer; } interface Props extends Partial { @@ -26,32 +26,32 @@ interface Props extends Partial { @observer export class PodLogsDialog extends React.Component { @observable static isOpen = false; - @observable static data: IPodLogsDialogData = null; + @observable static data: PodLogsDialogData = null; - static open(pod: Pod, container?: IPodContainer) { + static open(pod: Pod, container?: PodContainer): void { PodLogsDialog.isOpen = true; PodLogsDialog.data = { pod, container }; } - static close() { + static close(): void { PodLogsDialog.isOpen = false; } - get data() { + get data(): PodLogsDialogData { return PodLogsDialog.data; } private logsArea: HTMLDivElement; - private refresher = interval(5, () => this.load()); - private containers: IPodContainer[] = [] - private initContainers: IPodContainer[] = [] + private refresher = new IntervalManager(5, () => this.load()); + private containers: PodContainer[] = [] + private initContainers: PodContainer[] = [] private lastLineIsShown = true; // used for proper auto-scroll content after refresh private colorConverter = new AnsiUp(); @observable logs = ""; // latest downloaded logs for pod @observable newLogs = ""; // new logs since dialog is open @observable logsReady = false; - @observable selectedContainer: IPodContainer; + @observable selectedContainer: PodContainer; @observable showTimestamps = true; @observable tailLines = 1000; @@ -62,31 +62,33 @@ export class PodLogsDialog extends React.Component { { label: 100000, value: 100000 }, ] - onOpen = async () => { + onOpen = async (): Promise => { const { pod, container } = this.data; - this.containers = pod.getContainers(); - this.initContainers = pod.getInitContainers(); + this.containers = pod.spec.containers; + this.initContainers = pod.spec.initContainers; this.selectedContainer = container || this.containers[0]; await this.load(); this.refresher.start(); } - onClose = () => { + onClose = (): void => { this.resetLogs(); this.refresher.stop(); } - close = () => { + close = (): void => { PodLogsDialog.close(); } - load = async () => { - if (!this.data) return; + load = async (): Promise => { + if (!this.data) { + return; + } const { pod } = this.data; try { // if logs already loaded, check the latest timestamp for getting updates only from this point const logsTimestamps = this.getTimestamps(this.newLogs || this.logs); - let lastLogDate = new Date(0) + let lastLogDate = new Date(0); if (logsTimestamps) { lastLogDate = new Date(logsTimestamps.slice(-1)[0]); lastLogDate.setSeconds(lastLogDate.getSeconds() + 1); // avoid duplicates from last second @@ -101,27 +103,26 @@ export class PodLogsDialog extends React.Component { }); if (!this.logs) { this.logs = logs; - } - else if (logs) { + } else if (logs) { this.newLogs = `${this.newLogs}\n${logs}`.trim(); } } catch (error) { this.logs = [ _i18n._(t`Failed to load logs: ${error.message}`), _i18n._(t`Reason: ${error.reason} (${error.code})`), - ].join("\n") + ].join("\n"); } this.logsReady = true; } - reload = async () => { + reload = async (): Promise => { this.resetLogs(); this.refresher.stop(); await this.load(); this.refresher.start(); } - componentDidUpdate() { + componentDidUpdate(): void { // scroll logs only when it's already in the end, // otherwise it can interrupt reading by jumping after loading new logs update if (this.logsArea && this.lastLineIsShown) { @@ -129,83 +130,85 @@ export class PodLogsDialog extends React.Component { } } - onScroll = (evt: React.UIEvent) => { + onScroll = (evt: React.UIEvent): void => { const logsArea = evt.currentTarget; const { scrollHeight, clientHeight, scrollTop } = logsArea; this.lastLineIsShown = clientHeight + scrollTop === scrollHeight; }; - getLogs() { + getLogs(): { logs: string; newLogs: string } { const { logs, newLogs, showTimestamps } = this; return { logs: showTimestamps ? logs : this.removeTimestamps(logs), newLogs: showTimestamps ? newLogs : this.removeTimestamps(newLogs), - } + }; } - getTimestamps(logs: string) { + getTimestamps(logs: string): RegExpMatchArray { return logs.match(/^\d+\S+/gm); } - removeTimestamps(logs: string) { + removeTimestamps(logs: string): string { return logs.replace(/^\d+.*?\s/gm, ""); } - resetLogs() { + resetLogs(): void { this.logs = ""; this.newLogs = ""; this.lastLineIsShown = true; this.logsReady = false; } - onContainerChange = (option: SelectOption) => { + onContainerChange = (option: SelectOption): void => { this.selectedContainer = this.containers .concat(this.initContainers) .find(container => container.name === option.value); this.reload(); } - onTailLineChange = (option: SelectOption) => { + onTailLineChange = (option: SelectOption): void => { this.tailLines = option.value; this.reload(); } - formatOptionLabel = (option: SelectOption) => { + formatOptionLabel = (option: SelectOption): JSX.Element | {} => { const { value, label } = option; return label || <> {value}; } - toggleTimestamps = () => { + toggleTimestamps = (): void => { this.showTimestamps = !this.showTimestamps; } - downloadLogs = () => { + downloadLogs = (): void => { const { logs, newLogs } = this.getLogs(); const fileName = this.selectedContainer.name + ".log"; const fileContents = logs + newLogs; downloadFile(fileName, fileContents, "text/plain"); } - get containerSelectOptions() { + get containerSelectOptions(): React.ReactNode[] { return [ { label: _i18n._(t`Containers`), options: this.containers.map(container => { - return { value: container.name } + return { value: container.name }; }), }, { label: _i18n._(t`Init Containers`), options: this.initContainers.map(container => { - return { value: container.name } + return { value: container.name }; }), } ]; } - renderControlsPanel() { + renderControlsPanel(): JSX.Element { const { logsReady, showTimestamps } = this; - if (!logsReady) return; + if (!logsReady) { + return; + } const timestamps = this.getTimestamps(this.logs + this.newLogs); let from = ""; let to = ""; @@ -232,16 +235,16 @@ export class PodLogsDialog extends React.Component { />
- ) + ); } - renderLogs() { + renderLogs(): JSX.Element { if (!this.logsReady) { - return + return ; } const { logs, newLogs } = this.getLogs(); if (!logs && !newLogs) { - return

There are no logs available for container.

+ return

There are no logs available for container.

; } return ( <> @@ -256,7 +259,7 @@ export class PodLogsDialog extends React.Component { ); } - render() { + render(): JSX.Element { const { ...dialogProps } = this.props; const { selectedContainer, tailLines } = this; const podName = this.data ? this.data.pod.getName() : ""; @@ -296,12 +299,14 @@ export class PodLogsDialog extends React.Component {
{this.renderControlsPanel()} -
this.logsArea = e}> +
{ + this.logsArea = e; + }}> {this.renderLogs()}
- ) + ); } } diff --git a/dashboard/client/components/+workloads-pods/pod-menu.tsx b/dashboard/client/components/+workloads-pods/pod-menu.tsx index 42425c09c6..d58a5db2e1 100644 --- a/dashboard/client/components/+workloads-pods/pod-menu.tsx +++ b/dashboard/client/components/+workloads-pods/pod-menu.tsx @@ -3,7 +3,7 @@ import "./pod-menu.scss"; import * as React from "react"; import { t, Trans } from "@lingui/macro"; import { MenuItem, SubMenu } from "../menu"; -import { IPodContainer, Pod, nodesApi } from "../../api/endpoints"; +import { PodContainer, Pod } from "../../api/endpoints"; import { Icon } from "../icon"; import { StatusBrick } from "../status-brick"; import { PodLogsDialog } from "./pod-logs-dialog"; @@ -17,18 +17,18 @@ interface Props extends KubeObjectMenuProps { } export class PodMenu extends React.Component { - async execShell(container?: string) { + async execShell(container?: string): Promise { hideDetails(); - const { object: pod } = this.props - const containerParam = container ? `-c ${container}` : "" - let command = `kubectl exec -i -t -n ${pod.getNs()} ${pod.getName()} ${containerParam} "--"` + const { object: pod } = this.props; + const containerParam = container ? `-c ${container}` : ""; + let command = `kubectl exec -i -t -n ${pod.getNs()} ${pod.getName()} ${containerParam} "--"`; if (window.navigator.platform !== "Win32") { - command = `exec ${command}` + command = `exec ${command}`; } if (pod.getSelectedNodeOs() === "windows") { - command = `${command} powershell` + command = `${command} powershell`; } else { - command = `${command} sh -c "clear; (bash || ash || sh)"` + command = `${command} sh -c "clear; (bash || ash || sh)"`; } const shell = createTerminalTab({ @@ -41,14 +41,16 @@ export class PodMenu extends React.Component { }); } - showLogs(container: IPodContainer) { + showLogs(container: PodContainer): void { PodLogsDialog.open(this.props.object, container); } - renderShellMenu() { - const { object: pod, toolbar } = this.props + renderShellMenu(): JSX.Element { + const { object: pod, toolbar } = this.props; const containers = pod.getRunningContainers(); - if (!containers.length) return; + if (!containers.length) { + return; + } return ( this.execShell(containers[0].name))}> @@ -65,21 +67,23 @@ export class PodMenu extends React.Component { {name} - ) + ); }) } )} - ) + ); } - renderLogsMenu() { - const { object: pod, toolbar } = this.props + renderLogsMenu(): JSX.Element { + const { object: pod, toolbar } = this.props; const containers = pod.getAllContainers(); const statuses = pod.getContainerStatuses(); - if (!containers.length) return; + if (!containers.length) { + return; + } return ( this.showLogs(containers[0]))}> @@ -90,35 +94,35 @@ export class PodMenu extends React.Component { { containers.map(container => { - const { name } = container + const { name } = container; const status = statuses.find(status => status.name === name); const brick = status ? ( - ) : null + ) : null; return ( this.showLogs(container))} className="flex align-center"> {brick} {name} - ) + ); }) } )} - ) + ); } - render() { + render(): JSX.Element { const { ...menuProps } = this.props; return ( {this.renderShellMenu()} {this.renderLogsMenu()} - ) + ); } } diff --git a/dashboard/client/components/+workloads-pods/pods.store.ts b/dashboard/client/components/+workloads-pods/pods.store.ts index fe95ba4e48..10ec260db5 100644 --- a/dashboard/client/components/+workloads-pods/pods.store.ts +++ b/dashboard/client/components/+workloads-pods/pods.store.ts @@ -2,75 +2,86 @@ import countBy from "lodash/countBy"; import { action, observable } from "mobx"; import { KubeObjectStore } from "../../kube-object.store"; import { autobind, cpuUnitsToNumber, unitsToBytes } from "../../utils"; -import { IPodMetrics, Pod, PodMetrics, podMetricsApi, podsApi } from "../../api/endpoints"; +import { PodMetricsData, Pod, podMetricsApi, podsApi } from "../../api/endpoints"; +import { PodMetrics } from "../../api/endpoints/pod-metrics.api"; import { WorkloadKubeObject } from "../../api/workload-kube-object"; import { apiManager } from "../../api/api-manager"; +import { Dictionary } from "lodash"; +import { Metrics } from "client/api/endpoints/metrics.api"; + +export interface PodKubeMetrics { + cpu: number; + memory: number; +} @autobind() export class PodsStore extends KubeObjectStore { api = podsApi; - @observable metrics: IPodMetrics = null; + @observable metrics: PodMetricsData = null; @observable kubeMetrics = observable.array([]); @action - async loadMetrics(pod: Pod) { - this.metrics = await podsApi.getMetrics([pod], pod.getNs()); + async loadMetrics(pod: Pod): Promise { + this.metrics = await this.api.getMetrics([pod], pod.getNs()); } - loadContainerMetrics(pod: Pod) { - return podsApi.getMetrics([pod], pod.getNs(), "container, namespace"); + loadContainerMetrics(pod: Pod): Promise> { + return this.api.getMetrics([pod], pod.getNs(), "container, namespace"); } - async loadKubeMetrics(namespace?: string) { + async loadKubeMetrics(namespace?: string): Promise { const metrics = await podMetricsApi.list({ namespace }); this.kubeMetrics.replace(metrics); } getPodsByOwner(workload: WorkloadKubeObject): Pod[] { - if (!workload) return []; + if (!workload) { + return []; + } return this.items.filter(pod => { - const owners = pod.getOwnerRefs() - if (!owners.length) return - return owners.find(owner => owner.uid === workload.getId()) - }) - } - - getPodsByNode(node: string) { - if (!this.isLoaded) return [] - return this.items.filter(pod => pod.spec.nodeName === node) - } - - getStatuses(pods: Pod[]) { - return countBy(pods.map(pod => pod.getStatus())) - } - - getPodKubeMetrics(pod: Pod) { - const containers = pod.getContainers(); - const empty = { cpu: 0, memory: 0 }; - const metrics = this.kubeMetrics.find(metric => { - return [ - metric.getName() === pod.getName(), - metric.getNs() === pod.getNs() - ].every(v => v); + const owners = pod.getOwnerRefs(); + if (!owners.length) { + return; + } + return owners.find(owner => owner.uid === workload.getId()); }); - if (!metrics) return empty; + } + + getPodsByNode(node: string): Pod[] { + if (!this.isLoaded) { + return []; + } + return this.items.filter(pod => pod.spec.nodeName === node); + } + + getStatuses(pods: Pod[]): Dictionary { + return countBy(pods.map(pod => pod.getStatus())); + } + + getPodKubeMetrics(pod: Pod): PodKubeMetrics { + const containers = pod.spec.containers; + const empty = { cpu: 0, memory: 0 }; + const metrics = this.kubeMetrics.find(metric => metric.getName() === pod.getName() && metric.getNs() === pod.getNs()); + if (!metrics) { + return empty; + } return containers.reduce((total, container) => { const metric = metrics.containers.find(item => item.name == container.name); - let cpu = "0" - let memory = "0" + let cpu = "0"; + let memory = "0"; if (metric && metric.usage) { - cpu = metric.usage.cpu || "0" - memory = metric.usage.memory || "0" + cpu = metric.usage.cpu || "0"; + memory = metric.usage.memory || "0"; } return { cpu: total.cpu + cpuUnitsToNumber(cpu), memory: total.memory + unitsToBytes(memory) - } + }; }, empty); } - reset() { + reset(): void { this.metrics = null; } } diff --git a/dashboard/client/components/+workloads-pods/pods.tsx b/dashboard/client/components/+workloads-pods/pods.tsx index 890a13fe41..1d8b83d317 100644 --- a/dashboard/client/components/+workloads-pods/pods.tsx +++ b/dashboard/client/components/+workloads-pods/pods.tsx @@ -1,4 +1,4 @@ -import "./pods.scss" +import "./pods.scss"; import React, { Fragment } from "react"; import { observer } from "mobx-react"; @@ -7,7 +7,7 @@ import { Trans } from "@lingui/macro"; import { podsStore } from "./pods.store"; import { RouteComponentProps } from "react-router"; import { volumeClaimStore } from "../+storage-volume-claims/volume-claim.store"; -import { IPodsRouteParams } from "../+workloads"; +import { PodsRouteParams } from "../+workloads"; import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object"; import { Pod, podsApi } from "../../api/endpoints"; @@ -34,57 +34,57 @@ enum sortBy { status = "status", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class Pods extends React.Component { - renderContainersStatus(pod: Pod) { - return pod.getContainerStatuses().map(containerStatus => { - const { name, state, ready } = containerStatus; - const tooltip = ( - - {Object.keys(state).map(status => ( - -
- {name} ({status}{ready ? ", ready" : ""}) -
- {toPairs(state[status]).map(([name, value]) => ( -
-
{startCase(name)}
-
{value}
+ renderContainersStatus(pod: Pod): JSX.Element[] { + return pod.getContainerStatuses() + .map(({ name, state, ready }) => { + const tooltip = ( + + {Object.keys(state).map(status => ( + +
+ {name} ({status}{ready ? ", ready" : ""})
- ))} -
- ))} -
- ); - return ( - - - - ) - }); + {toPairs(state[status]).map(([name, value]) => ( +
+
{startCase(name)}
+
{value}
+
+ ))} + + ))} + + ); + return ( + + + + ); + }); } - render() { + render(): JSX.Element { return ( pod.getName(), - [sortBy.namespace]: (pod: Pod) => pod.getNs(), - [sortBy.containers]: (pod: Pod) => pod.getContainers().length, - [sortBy.restarts]: (pod: Pod) => pod.getRestartsCount(), - [sortBy.owners]: (pod: Pod) => pod.getOwnerRefs().map(ref => ref.kind), - [sortBy.qos]: (pod: Pod) => pod.getQosClass(), - [sortBy.age]: (pod: Pod) => pod.metadata.creationTimestamp, - [sortBy.status]: (pod: Pod) => pod.getStatusMessage(), + [sortBy.name]: (pod: Pod): string => pod.getName(), + [sortBy.namespace]: (pod: Pod): string => pod.getNs(), + [sortBy.containers]: (pod: Pod): number => pod.spec.containers.length, + [sortBy.restarts]: (pod: Pod): number => pod.getRestartsCount(), + [sortBy.owners]: (pod: Pod): string[] => pod.getOwnerRefs().map(ref => ref.kind), + [sortBy.qos]: (pod: Pod): string => pod.status.qosClass, + [sortBy.age]: (pod: Pod): string => pod.metadata.creationTimestamp, + [sortBy.status]: (pod: Pod): string => pod.getStatusMessage(), }} searchFilters={[ - (pod: Pod) => pod.getSearchFields(), - (pod: Pod) => pod.getStatusMessage(), + (pod: Pod): string[] => pod.getSearchFields(), + (pod: Pod): string => pod.getStatusMessage(), ]} renderHeaderTitle={Pods} renderTableHeader={[ @@ -98,7 +98,7 @@ export class Pods extends React.Component { { title: Age, className: "age", sortBy: sortBy.age }, { title: Status, className: "status", sortBy: sortBy.status }, ]} - renderTableContents={(pod: Pod) => [ + renderTableContents={(pod: Pod): (string | number | JSX.Element[] | JSX.Element | React.ReactNode)[] => [ pod.getName(), pod.hasIssues() && , pod.getNs(), @@ -111,20 +111,20 @@ export class Pods extends React.Component { {kind} - ) + ); }), - pod.getQosClass(), + pod.status.qosClass, pod.getAge(), { title: pod.getStatusMessage(), className: kebabCase(pod.getStatusMessage()) } ]} - renderItemMenu={(item: Pod) => { - return + renderItemMenu={(item: Pod): JSX.Element => { + return ; }} /> - ) + ); } } apiManager.registerViews(podsApi, { Menu: PodMenu, -}) +}); diff --git a/dashboard/client/components/+workloads-replicasets/index.ts b/dashboard/client/components/+workloads-replicasets/index.ts index 4053a1b9aa..2cf750a113 100644 --- a/dashboard/client/components/+workloads-replicasets/index.ts +++ b/dashboard/client/components/+workloads-replicasets/index.ts @@ -1,2 +1,2 @@ -export * from "./replicasets" -export * from "./replicaset-details" +export * from "./replicasets"; +export * from "./replicaset-details"; diff --git a/dashboard/client/components/+workloads-replicasets/replicaset-details.tsx b/dashboard/client/components/+workloads-replicasets/replicaset-details.tsx index 5f40995bec..74387bac42 100644 --- a/dashboard/client/components/+workloads-replicasets/replicaset-details.tsx +++ b/dashboard/client/components/+workloads-replicasets/replicaset-details.tsx @@ -12,12 +12,13 @@ import { KubeEventDetails } from "../+events/kube-event-details"; import { disposeOnUnmount, observer } from "mobx-react"; import { podsStore } from "../+workloads-pods/pods.store"; import { KubeObjectDetailsProps } from "../kube-object"; -import { ReplicaSet, replicaSetApi } from "../../api/endpoints"; +import { ReplicaSet, replicaSetApi, PodMetricsData } from "../../api/endpoints"; import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { apiManager } from "../../api/api-manager"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; +import { Metrics } from "client/api/endpoints/metrics.api"; interface Props extends KubeObjectDetailsProps { } @@ -29,31 +30,33 @@ export class ReplicaSetDetails extends React.Component { replicaSetStore.reset(); }); - async componentDidMount() { + async componentDidMount(): Promise { if (!podsStore.isLoaded) { - podsStore.loadAll(); + return podsStore.loadAll(); } } - componentWillUnmount() { + componentWillUnmount(): void { replicaSetStore.reset(); } - render() { - const { object: replicaSet } = this.props - if (!replicaSet) return null - const { metrics } = replicaSetStore - const { status } = replicaSet - const { availableReplicas, replicas } = status - const selectors = replicaSet.getSelectors() - const nodeSelector = replicaSet.getNodeSelectors() - const images = replicaSet.getImages() - const childPods = replicaSetStore.getChildPods(replicaSet) + render(): JSX.Element { + const { object: replicaSet } = this.props; + if (!replicaSet) { + return null; + } + const { metrics } = replicaSetStore; + const { status } = replicaSet; + const { availableReplicas, replicas } = status; + const selectors = replicaSet.getSelectors(); + const nodeSelector = replicaSet.getNodeSelectors(); + const images = replicaSet.getImages(); + const childPods = replicaSetStore.getChildPods(replicaSet); return (
{podsStore.isLoaded && ( replicaSetStore.loadMetrics(replicaSet)} + loader={(): Promise> => replicaSetStore.loadMetrics(replicaSet)} tabs={podMetricTabs} object={replicaSet} params={{ metrics }} > @@ -62,23 +65,17 @@ export class ReplicaSetDetails extends React.Component { {selectors.length > 0 && Selector} labelsOnly> - { - selectors.map(label => ) - } + { selectors.map(label => ) } } {nodeSelector.length > 0 && Node Selector} labelsOnly> - { - nodeSelector.map(label => ) - } + { nodeSelector.map(label => ) } } {images.length > 0 && Images}> - { - images.map(image =>

{image}

) - } + { images.map(image =>

{image}

) }
} Replicas}> @@ -93,10 +90,10 @@ export class ReplicaSetDetails extends React.Component {
- ) + ); } } apiManager.registerViews(replicaSetApi, { Details: ReplicaSetDetails, -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+workloads-replicasets/replicasets.store.ts b/dashboard/client/components/+workloads-replicasets/replicasets.store.ts index cf658e36f7..fa4ef97f2d 100644 --- a/dashboard/client/components/+workloads-replicasets/replicasets.store.ts +++ b/dashboard/client/components/+workloads-replicasets/replicasets.store.ts @@ -1,33 +1,34 @@ import { observable } from "mobx"; import { autobind } from "../../utils"; import { KubeObjectStore } from "../../kube-object.store"; -import { Deployment, IPodMetrics, podsApi, ReplicaSet, replicaSetApi } from "../../api/endpoints"; +import { Deployment, PodMetricsData, podsApi, ReplicaSet, replicaSetApi, Pod } from "../../api/endpoints"; import { podsStore } from "../+workloads-pods/pods.store"; import { apiManager } from "../../api/api-manager"; +import { Metrics } from "client/api/endpoints/metrics.api"; @autobind() export class ReplicaSetStore extends KubeObjectStore { api = replicaSetApi - @observable metrics: IPodMetrics = null; + @observable metrics: PodMetricsData = null; - loadMetrics(replicaSet: ReplicaSet) { + loadMetrics(replicaSet: ReplicaSet): Promise> { const pods = this.getChildPods(replicaSet); return podsApi.getMetrics(pods, replicaSet.getNs(), "").then(metrics => this.metrics = metrics ); } - getChildPods(replicaSet: ReplicaSet) { + getChildPods(replicaSet: ReplicaSet): Pod[] { return podsStore.getPodsByOwner(replicaSet); } - getReplicaSetsByOwner(deployment: Deployment) { + getReplicaSetsByOwner(deployment: Deployment): ReplicaSet[] { return this.items.filter(replicaSet => !!replicaSet.getOwnerRefs().find(owner => owner.uid === deployment.getId()) - ) + ); } - reset() { + reset(): void { this.metrics = null; } } diff --git a/dashboard/client/components/+workloads-replicasets/replicasets.tsx b/dashboard/client/components/+workloads-replicasets/replicasets.tsx index bba85ffd03..60af844d80 100644 --- a/dashboard/client/components/+workloads-replicasets/replicasets.tsx +++ b/dashboard/client/components/+workloads-replicasets/replicasets.tsx @@ -27,22 +27,22 @@ interface Props { @observer export class ReplicaSets extends React.Component { private sortingCallbacks = { - [sortBy.name]: (replicaSet: ReplicaSet) => replicaSet.getName(), - [sortBy.namespace]: (replicaSet: ReplicaSet) => replicaSet.getNs(), - [sortBy.age]: (replicaSet: ReplicaSet) => replicaSet.metadata.creationTimestamp, - [sortBy.pods]: (replicaSet: ReplicaSet) => this.getPodsLength(replicaSet), + [sortBy.name]: (replicaSet: ReplicaSet): string => replicaSet.getName(), + [sortBy.namespace]: (replicaSet: ReplicaSet): string => replicaSet.getNs(), + [sortBy.age]: (replicaSet: ReplicaSet): string => replicaSet.metadata.creationTimestamp, + [sortBy.pods]: (replicaSet: ReplicaSet): number => replicaSetStore.getChildPods(replicaSet).length, } - getPodsLength(replicaSet: ReplicaSet) { - return replicaSetStore.getChildPods(replicaSet).length; - } - - render() { + render(): JSX.Element { const { replicaSets } = this.props; - if (!replicaSets.length && !replicaSetStore.isLoaded) return ( -
- ); - if (!replicaSets.length) return null; + if (!replicaSets.length && !replicaSetStore.isLoaded) { + return ( +
+ ); + } + if (!replicaSets.length) { + return null; + } return (
Deploy Revisions}/> @@ -72,13 +72,13 @@ export class ReplicaSets extends React.Component { > {replica.getName()} {replica.getNs()} - {this.getPodsLength(replica)} + {replicaSetStore.getChildPods(replica).length} {replica.getAge()} - ) + ); }) } @@ -87,10 +87,10 @@ export class ReplicaSets extends React.Component { } } -export function ReplicaSetMenu(props: KubeObjectMenuProps) { +export function ReplicaSetMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(replicaSetApi, { diff --git a/dashboard/client/components/+workloads-statefulsets/index.ts b/dashboard/client/components/+workloads-statefulsets/index.ts index 725851d22e..1cb72d701a 100644 --- a/dashboard/client/components/+workloads-statefulsets/index.ts +++ b/dashboard/client/components/+workloads-statefulsets/index.ts @@ -1,2 +1,2 @@ -export * from "./statefulsets" -export * from "./statefulset-details" \ No newline at end of file +export * from "./statefulsets"; +export * from "./statefulset-details"; \ No newline at end of file diff --git a/dashboard/client/components/+workloads-statefulsets/statefulset-details.tsx b/dashboard/client/components/+workloads-statefulsets/statefulset-details.tsx index 83fd4f6210..f1d4448d7f 100644 --- a/dashboard/client/components/+workloads-statefulsets/statefulset-details.tsx +++ b/dashboard/client/components/+workloads-statefulsets/statefulset-details.tsx @@ -13,7 +13,7 @@ import { KubeEventDetails } from "../+events/kube-event-details"; import { podsStore } from "../+workloads-pods/pods.store"; import { statefulSetStore } from "./statefulset.store"; import { KubeObjectDetailsProps } from "../kube-object"; -import { StatefulSet, statefulSetApi } from "../../api/endpoints"; +import { StatefulSet, statefulSetApi, PodMetricsData } from "../../api/endpoints"; import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; @@ -30,29 +30,31 @@ export class StatefulSetDetails extends React.Component { statefulSetStore.reset(); }); - componentDidMount() { + async componentDidMount(): Promise { if (!podsStore.isLoaded) { - podsStore.loadAll(); + return podsStore.loadAll(); } } - componentWillUnmount() { + componentWillUnmount(): void { statefulSetStore.reset(); } - render() { + render(): JSX.Element { const { object: statefulSet } = this.props; - if (!statefulSet) return null - const images = statefulSet.getImages() - const selectors = statefulSet.getSelectors() - const nodeSelector = statefulSet.getNodeSelectors() - const childPods = statefulSetStore.getChildPods(statefulSet) - const metrics = statefulSetStore.metrics + if (!statefulSet) { + return null; + } + const images = statefulSet.getImages(); + const selectors = statefulSet.getSelectors(); + const nodeSelector = statefulSet.getNodeSelectors(); + const childPods = statefulSetStore.getChildPods(statefulSet); + const metrics = statefulSetStore.metrics; return (
{podsStore.isLoaded && ( statefulSetStore.loadMetrics(statefulSet)} + loader={(): Promise => statefulSetStore.loadMetrics(statefulSet)} tabs={podMetricTabs} object={statefulSet} params={{ metrics }} > @@ -91,10 +93,10 @@ export class StatefulSetDetails extends React.Component {
- ) + ); } } apiManager.registerViews(statefulSetApi, { Details: StatefulSetDetails -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/dashboard/client/components/+workloads-statefulsets/statefulset.store.ts b/dashboard/client/components/+workloads-statefulsets/statefulset.store.ts index 5119ce6e00..99812f83ec 100644 --- a/dashboard/client/components/+workloads-statefulsets/statefulset.store.ts +++ b/dashboard/client/components/+workloads-statefulsets/statefulset.store.ts @@ -1,44 +1,42 @@ import { observable } from "mobx"; import { autobind } from "../../utils"; import { KubeObjectStore } from "../../kube-object.store"; -import { IPodMetrics, podsApi, PodStatus, StatefulSet, statefulSetApi } from "../../api/endpoints"; +import { PodMetricsData, podsApi, PodStatus, StatefulSet, statefulSetApi, Pod } from "../../api/endpoints"; import { podsStore } from "../+workloads-pods/pods.store"; import { apiManager } from "../../api/api-manager"; +import { Dictionary } from "lodash"; @autobind() export class StatefulSetStore extends KubeObjectStore { api = statefulSetApi - @observable metrics: IPodMetrics = null; + @observable metrics: PodMetricsData = null; - loadMetrics(statefulSet: StatefulSet) { + async loadMetrics(statefulSet: StatefulSet): Promise { const pods = this.getChildPods(statefulSet); - return podsApi.getMetrics(pods, statefulSet.getNs(), "").then(metrics => - this.metrics = metrics - ); + const metrics = await podsApi.getMetrics(pods, statefulSet.getNs(), ""); + return this.metrics = metrics; } - getChildPods(statefulSet: StatefulSet) { - return podsStore.getPodsByOwner(statefulSet) + getChildPods(statefulSet: StatefulSet): Pod[] { + return podsStore.getPodsByOwner(statefulSet); } - getStatuses(statefulSets: StatefulSet[]) { - const status = { failed: 0, pending: 0, running: 0 } + getStatuses(statefulSets: StatefulSet[]): Dictionary { + const status = { failed: 0, pending: 0, running: 0 }; statefulSets.forEach(statefulSet => { - const pods = this.getChildPods(statefulSet) + const pods = this.getChildPods(statefulSet); if (pods.some(pod => pod.getStatus() === PodStatus.FAILED)) { - status.failed++ + status.failed++; + } else if (pods.some(pod => pod.getStatus() === PodStatus.PENDING)) { + status.pending++; + } else { + status.running++; } - else if (pods.some(pod => pod.getStatus() === PodStatus.PENDING)) { - status.pending++ - } - else { - status.running++ - } - }) - return status + }); + return status; } - reset() { + reset(): void { this.metrics = null; } } diff --git a/dashboard/client/components/+workloads-statefulsets/statefulsets.tsx b/dashboard/client/components/+workloads-statefulsets/statefulsets.tsx index 8010bb90cb..29b109cd87 100644 --- a/dashboard/client/components/+workloads-statefulsets/statefulsets.tsx +++ b/dashboard/client/components/+workloads-statefulsets/statefulsets.tsx @@ -11,7 +11,7 @@ import { nodesStore } from "../+nodes/nodes.store"; import { eventStore } from "../+events/event.store"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; import { KubeObjectListLayout } from "../kube-object"; -import { IStatefulSetsRouteParams } from "../+workloads"; +import { StatefulSetsRouteParams } from "../+workloads"; import { KubeEventIcon } from "../+events/kube-event-icon"; import { apiManager } from "../../api/api-manager"; @@ -22,28 +22,24 @@ enum sortBy { age = "age", } -interface Props extends RouteComponentProps { +interface Props extends RouteComponentProps { } @observer export class StatefulSets extends React.Component { - getPodsLength(statefulSet: StatefulSet) { - return statefulSetStore.getChildPods(statefulSet).length; - } - - render() { + render(): JSX.Element { return ( statefulSet.getName(), - [sortBy.namespace]: (statefulSet: StatefulSet) => statefulSet.getNs(), - [sortBy.age]: (statefulSet: StatefulSet) => statefulSet.metadata.creationTimestamp, - [sortBy.pods]: (statefulSet: StatefulSet) => this.getPodsLength(statefulSet), + [sortBy.name]: (statefulSet: StatefulSet): string => statefulSet.getName(), + [sortBy.namespace]: (statefulSet: StatefulSet): string => statefulSet.getNs(), + [sortBy.age]: (statefulSet: StatefulSet): string => statefulSet.metadata.creationTimestamp, + [sortBy.pods]: (statefulSet: StatefulSet): number => statefulSetStore.getChildPods(statefulSet).length, }} searchFilters={[ - (statefulSet: StatefulSet) => statefulSet.getSearchFields(), + (statefulSet: StatefulSet): string[] => statefulSet.getSearchFields(), ]} renderHeaderTitle={Stateful Sets} renderTableHeader={[ @@ -53,27 +49,27 @@ export class StatefulSets extends React.Component { { className: "warning" }, { title: Age, className: "age", sortBy: sortBy.age }, ]} - renderTableContents={(statefulSet: StatefulSet) => [ + renderTableContents={(statefulSet: StatefulSet): (string | number | JSX.Element)[] => [ statefulSet.getName(), statefulSet.getNs(), - this.getPodsLength(statefulSet), - , + statefulSetStore.getChildPods(statefulSet).length, + , statefulSet.getAge(), ]} - renderItemMenu={(item: StatefulSet) => { - return + renderItemMenu={(item: StatefulSet): JSX.Element => { + return ; }} /> - ) + ); } } -export function StatefulSetMenu(props: KubeObjectMenuProps) { +export function StatefulSetMenu(props: KubeObjectMenuProps): JSX.Element { return ( - ) + ); } apiManager.registerViews(statefulSetApi, { Menu: StatefulSetMenu, -}) +}); diff --git a/dashboard/client/components/+workloads/index.ts b/dashboard/client/components/+workloads/index.ts index 300747c6af..883ad5c2ba 100644 --- a/dashboard/client/components/+workloads/index.ts +++ b/dashboard/client/components/+workloads/index.ts @@ -1,3 +1,3 @@ -export * from "./workloads.route" -export * from "./workloads" +export * from "./workloads.route"; +export * from "./workloads"; diff --git a/dashboard/client/components/+workloads/workloads.route.ts b/dashboard/client/components/+workloads/workloads.route.ts index 94a7afd6ba..d5a65e06f8 100644 --- a/dashboard/client/components/+workloads/workloads.route.ts +++ b/dashboard/client/components/+workloads/workloads.route.ts @@ -1,64 +1,64 @@ -import { RouteProps } from "react-router" +import { RouteProps } from "react-router"; import { Workloads } from "./workloads"; -import { buildURL, IURLParams } from "../../navigation"; +import { buildURL, URLParams } from "../../navigation"; export const workloadsRoute: RouteProps = { get path() { - return Workloads.tabRoutes.map(({ path }) => path).flat() + return Workloads.tabRoutes.map(({ path }) => path).flat(); } -} +}; // Routes export const overviewRoute: RouteProps = { path: "/workloads" -} +}; export const podsRoute: RouteProps = { path: "/pods" -} +}; export const deploymentsRoute: RouteProps = { path: "/deployments" -} +}; export const daemonSetsRoute: RouteProps = { path: "/daemonsets" -} +}; export const statefulSetsRoute: RouteProps = { path: "/statefulsets" -} +}; export const jobsRoute: RouteProps = { path: "/jobs" -} +}; export const cronJobsRoute: RouteProps = { path: "/cronjobs" -} +}; // Route params -export interface IWorkloadsOverviewRouteParams { +export interface WorkloadsOverviewRouteParams { } -export interface IPodsRouteParams { +export interface PodsRouteParams { } -export interface IDeploymentsRouteParams { +export interface DeploymentsRouteParams { } -export interface IDaemonSetsRouteParams { +export interface DaemonSetsRouteParams { } -export interface IStatefulSetsRouteParams { +export interface StatefulSetsRouteParams { } -export interface IJobsRouteParams { +export interface JobsRouteParams { } -export interface ICronJobsRouteParams { +export interface CronJobsRouteParams { } // URL-builders -export const workloadsURL = (params?: IURLParams) => overviewURL(params); -export const overviewURL = buildURL(overviewRoute.path) -export const podsURL = buildURL(podsRoute.path) -export const deploymentsURL = buildURL(deploymentsRoute.path) -export const daemonSetsURL = buildURL(daemonSetsRoute.path) -export const statefulSetsURL = buildURL(statefulSetsRoute.path) -export const jobsURL = buildURL(jobsRoute.path) -export const cronJobsURL = buildURL(cronJobsRoute.path) +export const overviewURL = buildURL(overviewRoute.path); +export const workloadsURL = (params?: URLParams): string => overviewURL(params); +export const podsURL = buildURL(podsRoute.path); +export const deploymentsURL = buildURL(deploymentsRoute.path); +export const daemonSetsURL = buildURL(daemonSetsRoute.path); +export const statefulSetsURL = buildURL(statefulSetsRoute.path); +export const jobsURL = buildURL(jobsRoute.path); +export const cronJobsURL = buildURL(cronJobsRoute.path); diff --git a/dashboard/client/components/+workloads/workloads.tsx b/dashboard/client/components/+workloads/workloads.tsx index 895f1ab3a2..1fc950155f 100644 --- a/dashboard/client/components/+workloads/workloads.tsx +++ b/dashboard/client/components/+workloads/workloads.tsx @@ -1,4 +1,4 @@ -import "./workloads.scss" +import "./workloads.scss"; import * as React from "react"; import { observer } from "mobx-react"; @@ -15,7 +15,7 @@ import { DaemonSets } from "../+workloads-daemonsets"; import { StatefulSets } from "../+workloads-statefulsets"; import { Jobs } from "../+workloads-jobs"; import { CronJobs } from "../+workloads-cronjobs"; -import { isAllowedResource } from "../../api/rbac" +import { isAllowedResource } from "../../api/rbac"; interface Props extends RouteComponentProps { } @@ -31,14 +31,14 @@ export class Workloads extends React.Component { url: overviewURL({ query }), path: overviewRoute.path } - ] + ]; if (isAllowedResource("pods")) { routes.push({ title: Pods, component: Pods, url: podsURL({ query }), path: podsRoute.path - }) + }); } if (isAllowedResource("deployments")) { routes.push({ @@ -46,7 +46,7 @@ export class Workloads extends React.Component { component: Deployments, url: deploymentsURL({ query }), path: deploymentsRoute.path, - }) + }); } if (isAllowedResource("daemonsets")) { routes.push({ @@ -54,7 +54,7 @@ export class Workloads extends React.Component { component: DaemonSets, url: daemonSetsURL({ query }), path: daemonSetsRoute.path, - }) + }); } if (isAllowedResource("statefulsets")) { routes.push({ @@ -62,7 +62,7 @@ export class Workloads extends React.Component { component: StatefulSets, url: statefulSetsURL({ query }), path: statefulSetsRoute.path, - }) + }); } if (isAllowedResource("jobs")) { routes.push({ @@ -70,7 +70,7 @@ export class Workloads extends React.Component { component: Jobs, url: jobsURL({ query }), path: jobsRoute.path, - }) + }); } if (isAllowedResource("cronjobs")) { routes.push({ @@ -78,12 +78,12 @@ export class Workloads extends React.Component { component: CronJobs, url: cronJobsURL({ query }), path: cronJobsRoute.path, - }) + }); } return routes; }; - render() { + render(): JSX.Element { const tabRoutes = Workloads.tabRoutes; return ( @@ -92,6 +92,6 @@ export class Workloads extends React.Component { - ) + ); } } diff --git a/dashboard/client/components/ace-editor/ace-editor.tsx b/dashboard/client/components/ace-editor/ace-editor.tsx index 3bd6d65e21..761f879408 100644 --- a/dashboard/client/components/ace-editor/ace-editor.tsx +++ b/dashboard/client/components/ace-editor/ace-editor.tsx @@ -1,11 +1,11 @@ // Ace code editor - https://ace.c9.io // Playground - https://ace.c9.io/build/kitchen-sink.html -import "./ace-editor.scss" +import "./ace-editor.scss"; -import * as React from "react" +import * as React from "react"; import { observable } from "mobx"; import { observer } from "mobx-react"; -import { Ace } from "ace-builds" +import { Ace } from "ace-builds"; import { autobind, cssNames } from "../../utils"; import { Spinner } from "../spinner"; import { themeStore } from "../../theme.store"; @@ -42,43 +42,48 @@ export class AceEditor extends React.Component { @observable ready = false; - async loadEditor() { - return await import( + async loadEditor(): Promise { + return import( /* webpackChunkName: "ace" */ "ace-builds" ); } - loadTheme(theme: string) { + loadTheme(theme: string): Promise { return import( /* webpackChunkName: "ace/[request]" */ `ace-builds/src-min-noconflict/theme-${theme}` ); } - loadExtension(ext: string) { + loadExtension(ext: string): Promise { return import( /* webpackChunkName: "ace/[request]" */ `ace-builds/src-min-noconflict/ext-${ext}` ); } - loadMode(mode: string) { + loadMode(mode: string): Promise { return import( /* webpackChunkName: "ace/[request]" */ `ace-builds/src-min-noconflict/mode-${mode}` - ) + ); } - get theme() { - return themeStore.activeTheme.type == "light" - ? "dreamweaver" : "terminal"; + get theme(): string { + return themeStore.activeTheme.type == "light" ? "dreamweaver" : "terminal"; } - async componentDidMount() { + async componentDidMount(): Promise { const { - mode, autoFocus, className, hidden, cursorPos, - onChange, onCursorPosChange, children, + mode, + autoFocus, + cursorPos, + className: _className, + hidden: _hidden, + onChange: _onChange, + onCursorPosChange: _onCursorPosChange, + children: _children, ...options } = this.props; @@ -102,12 +107,16 @@ export class AceEditor extends React.Component { // load extensions this.loadExtension("searchbox"); - if (autoFocus) this.focus(); + if (autoFocus) { + this.focus(); + } this.ready = true; } - componentDidUpdate() { - if (!this.editor) return; + componentDidUpdate(): void { + if (!this.editor) { + return; + } const { value, cursorPos } = this.props; if (value !== this.getValue()) { this.editor.setValue(value); @@ -116,44 +125,46 @@ export class AceEditor extends React.Component { } } - componentWillUnmount() { + componentWillUnmount(): void { if (this.editor) { this.editor.destroy(); } } - resize() { + resize(): void { if (this.editor) { this.editor.resize(); } } - focus() { + focus(): void { if (this.editor) { this.editor.focus(); } } - getValue() { - return this.editor.getValue() + getValue(): string { + return this.editor.getValue(); } - setValue(value: string, cursorPos?: number) { + setValue(value: string, cursorPos?: number): string { return this.editor.setValue(value, cursorPos); } - async setMode(mode: string) { + async setMode(mode: string): Promise { await this.loadMode(mode); this.editor.session.setMode(`ace/mode/${mode}`); } - async setTheme(theme: string) { + async setTheme(theme: string): Promise { await this.loadTheme(theme); this.editor.setTheme(`ace/theme/${theme}`); } - setCursorPos(pos: Ace.Point) { - if (!pos) return; + setCursorPos(pos: Ace.Point): void { + if (!pos) { + return; + } const { row, column } = pos; this.editor.moveCursorToPosition(pos); requestAnimationFrame(() => { @@ -162,7 +173,7 @@ export class AceEditor extends React.Component { } @autobind() - onCursorPosChange() { + onCursorPosChange(): void { const { onCursorPosChange } = this.props; if (onCursorPosChange) { onCursorPosChange(this.editor.getCursorPosition()); @@ -170,21 +181,23 @@ export class AceEditor extends React.Component { } @autobind() - onChange(delta: Ace.Delta) { + onChange(delta: Ace.Delta): void { const { onChange } = this.props; if (onChange) { onChange(this.getValue(), delta); } } - render() { + render(): JSX.Element { const { className, hidden } = this.props; const themeType = themeStore.activeTheme.type; return ( - ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/app.tsx b/dashboard/client/components/app.tsx index 220409ec12..b12e40f370 100755 --- a/dashboard/client/components/app.tsx +++ b/dashboard/client/components/app.tsx @@ -4,7 +4,7 @@ import React from "react"; import { render } from "react-dom"; import { Redirect, Route, Router, Switch } from "react-router"; import { observer } from "mobx-react"; -import { I18nProvider } from '@lingui/react' +import { I18nProvider } from '@lingui/react'; import { _i18n, i18nStore } from "../i18n"; import { browserHistory } from "../navigation"; import { Notifications } from "./notifications"; @@ -38,7 +38,7 @@ import { isAllowedResource } from "../api/rbac"; class App extends React.Component { static rootElem = document.getElementById('app'); - static async init() { + static async init(): Promise { await i18nStore.init(); await configStore.load(); @@ -46,7 +46,7 @@ class App extends React.Component { render(, App.rootElem); }; - render() { + render(): JSX.Element { const homeUrl = (isAllowedResource(["events", "nodes", "pods"])) ? clusterURL() : workloadsURL(); return ( @@ -79,7 +79,7 @@ class App extends React.Component { - ) + ); } } diff --git a/dashboard/client/components/badge/badge.tsx b/dashboard/client/components/badge/badge.tsx index 875dbe48fe..7cd4009bc3 100644 --- a/dashboard/client/components/badge/badge.tsx +++ b/dashboard/client/components/badge/badge.tsx @@ -1,4 +1,4 @@ -import "./badge.scss" +import "./badge.scss"; import * as React from "react"; import { cssNames } from "../../utils/cssNames"; @@ -11,7 +11,7 @@ interface Props extends React.HTMLAttributes, TooltipDecoratorProps { @withTooltip export class Badge extends React.Component { - render() { + render(): JSX.Element { const { className, label, small, children, ...elemProps } = this.props; return ( diff --git a/dashboard/client/components/badge/index.ts b/dashboard/client/components/badge/index.ts index 051fa6ea23..80844a4e3f 100644 --- a/dashboard/client/components/badge/index.ts +++ b/dashboard/client/components/badge/index.ts @@ -1 +1 @@ -export * from "./badge" +export * from "./badge"; diff --git a/dashboard/client/components/button/button.tsx b/dashboard/client/components/button/button.tsx index c0cb8dcc49..4ef3a365df 100644 --- a/dashboard/client/components/button/button.tsx +++ b/dashboard/client/components/button/button.tsx @@ -22,10 +22,26 @@ export class Button extends React.PureComponent { private link: HTMLAnchorElement; private button: HTMLButtonElement; - render() { - const { className, waiting, label, primary, accent, plain, hidden, active, big, round, tooltip, children, ...props } = this.props; + render(): JSX.Element { + const { + className, + waiting, + label, + primary, + accent, + plain, + hidden, + active, + big, + round, + tooltip: _tooltip, + children, + ...props + } = this.props; const btnProps = props as Partial; - if (hidden) return null; + if (hidden) { + return null; + } btnProps.className = cssNames('Button', className, { waiting, primary, accent, plain, active, big, round, @@ -41,17 +57,21 @@ export class Button extends React.PureComponent { // render as link if (this.props.href) { return ( - this.link = e}> + { + this.link = e; + }}> {btnContent} - ) + ); } // render as button return ( - - ) + ); } } diff --git a/dashboard/client/components/button/index.ts b/dashboard/client/components/button/index.ts index 14757e7223..eaf5eea7f1 100644 --- a/dashboard/client/components/button/index.ts +++ b/dashboard/client/components/button/index.ts @@ -1 +1 @@ -export * from './button' +export * from './button'; diff --git a/dashboard/client/components/chart/background-block.plugin.ts b/dashboard/client/components/chart/background-block.plugin.ts index f2efe607d0..07b5514688 100644 --- a/dashboard/client/components/chart/background-block.plugin.ts +++ b/dashboard/client/components/chart/background-block.plugin.ts @@ -6,24 +6,26 @@ const defaultOptions = { coverBars: 3, borderColor: "#44474A", backgroundColor: "#00000033" -} +}; export const BackgroundBlock = { options: {}, - getOptions(chart: ChartJS) { + getOptions(chart: ChartJS): any { return get(chart, "options.plugins.BackgroundBlock"); }, - afterInit(chart: ChartJS) { + afterInit(chart: ChartJS): void { this.options = { ...defaultOptions, ...this.getOptions(chart) - } + }; }, - beforeDraw(chart: ChartJS) { - if (!chart.chartArea) return; + beforeDraw(chart: ChartJS): void { + if (!chart.chartArea) { + return; + } const { interval, coverBars, borderColor, backgroundColor } = this.options; const { ctx, chartArea } = chart; const { left, right, top, bottom } = chartArea; @@ -39,4 +41,4 @@ export const BackgroundBlock = { ctx.stroke(); ctx.restore(); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/dashboard/client/components/chart/bar-chart.tsx b/dashboard/client/components/chart/bar-chart.tsx index 3e0792b585..6b0c85941a 100644 --- a/dashboard/client/components/chart/bar-chart.tsx +++ b/dashboard/client/components/chart/bar-chart.tsx @@ -18,7 +18,7 @@ interface Props extends ChartProps { timeLabelStep?: number; // Minute labels appearance step } -interface IChartContext { +interface ChartContext { chart: ChartJS; dataIndex?: number; dataset?: ChartDataSet; @@ -29,23 +29,28 @@ const defaultProps: Partial = { plugins: [ZebraStripes] }; -BarChart.defaultProps = defaultProps; - -export function BarChart(props: Props) { - const { name, data, className, timeLabelStep, plugins, options: customOptions, ...settings } = props; +export function BarChart({ + name: _name, + data, + className, + timeLabelStep, + plugins, + options: customOptions, + ...settings +}: Props): JSX.Element { const { textColorPrimary, borderFaintColor, chartStripesColor } = themeStore.activeTheme.colors; const savedName = useRef(); useEffect(() => { - savedName.current = props.name; + savedName.current = name; }); - const getBarColor = (ctx: IChartContext) => { + const getBarColor = (ctx: ChartContext): string => { const { dataset } = ctx; const color = dataset.borderColor; return Color(color).alpha(0.2).string(); - } + }; // Remove empty sets and insert default data const chartData: ChartData = { @@ -59,15 +64,19 @@ export function BarChart(props: Props) { barPercentage: 1, categoryPercentage: 1, ...item - } + }; }) }; - const formatTimeLabels = (timestamp: string, index: number) => { + const formatTimeLabels = (timestamp: string, index: number): string => { const label = moment(parseInt(timestamp)).format("HH:mm"); const offset = " "; - if (index == 0) return offset + label; - if (index == 60) return label + offset; + if (index == 0) { + return offset + label; + } + if (index == 60) { + return label + offset; + } return index % timeLabelStep == 0 ? label : ""; }; @@ -98,7 +107,7 @@ export function BarChart(props: Props) { displayFormats: { minute: "x" }, - parser: timestamp => moment.unix(parseInt(timestamp)) + parser: (timestamp): moment.Moment => moment.unix(parseInt(timestamp)) } }], yAxes: [{ @@ -122,16 +131,18 @@ export function BarChart(props: Props) { mode: "index", position: "cursor", callbacks: { - title: tooltipItems => { - const now = new Date().getTime() - if (new Date(tooltipItems[0].xLabel).getTime() > now) return ""; - return `${tooltipItems[0].xLabel}` + title: (tooltipItems): string => { + const now = new Date().getTime(); + if (new Date(tooltipItems[0].xLabel).getTime() > now) { + return ""; + } + return `${tooltipItems[0].xLabel}`; }, - labelColor: ({ datasetIndex }) => { + labelColor: ({ datasetIndex }): ChartJS.ChartTooltipLabelColor => { return { borderColor: "darkgray", backgroundColor: chartData.datasets[datasetIndex].borderColor as string - } + }; } } }, @@ -151,7 +162,7 @@ export function BarChart(props: Props) { }; const options = merge(barOptions, customOptions); if (chartData.datasets.length == 0) { - return + return ; } return ( - ) + ); } +BarChart.defaultProps = defaultProps; + // Default options for all charts containing memory units (network, disk, memory, etc) export const memoryOptions: ChartOptions = { scales: { @@ -188,14 +201,14 @@ export const memoryOptions: ChartOptions = { }, tooltips: { callbacks: { - label: ({ datasetIndex, index }, { datasets }) => { + label: ({ datasetIndex, index }, { datasets }): string => { const { label, data } = datasets[datasetIndex]; const value = data[index] as ChartPoint; return `${label}: ${bytesToUnits(parseInt(value.y.toString()), 3)}`; } } } -} +}; // Default options for all charts with cpu units or other decimal numbers export const cpuOptions: ChartOptions = { @@ -204,9 +217,15 @@ export const cpuOptions: ChartOptions = { ticks: { callback: (value: number | string): string => { const float = parseFloat(`${value}`); - if (float == 0) return "0"; - if (float < 10) return float.toFixed(3); - if (float < 100) return float.toFixed(2); + if (float == 0) { + return "0"; + } + if (float < 10) { + return float.toFixed(3); + } + if (float < 100) { + return float.toFixed(2); + } return float.toFixed(1); } } @@ -214,11 +233,11 @@ export const cpuOptions: ChartOptions = { }, tooltips: { callbacks: { - label: ({ datasetIndex, index }, { datasets }) => { + label: ({ datasetIndex, index }, { datasets }): string => { const { label, data } = datasets[datasetIndex]; const value = data[index] as ChartPoint; return `${label}: ${parseFloat(value.y as string).toPrecision(2)}`; } } } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/dashboard/client/components/chart/chart.tsx b/dashboard/client/components/chart/chart.tsx index ec8037bb3d..d6153027dd 100644 --- a/dashboard/client/components/chart/chart.tsx +++ b/dashboard/client/components/chart/chart.tsx @@ -60,88 +60,92 @@ export class Chart extends PureComponent { // We clone new data prop into currentChartData to compare props and prevProps private currentChartData: ChartData - componentDidMount() { - const { showChart } = this.props - if (!showChart) return - this.renderChart() + componentDidMount(): void { + const { showChart } = this.props; + if (!showChart) { + return; + } + this.renderChart(); } - componentDidUpdate(prevProps: ChartProps) { - const { data, showChart, redraw } = this.props + componentDidUpdate(prevProps: ChartProps): void { + const { data, showChart, redraw } = this.props; if (redraw) { - this.chart.destroy() - this.renderChart() - return + this.chart.destroy(); + this.renderChart(); + return; } if (!isEqual(prevProps.data, data) && showChart) { - if (!this.chart) this.renderChart() - else this.updateChart() + if (!this.chart) { + this.renderChart(); + } else { + this.updateChart(); + } } } - memoizeDataProps() { - const { data } = this.props + memoizeDataProps(): void { + const { data } = this.props; this.currentChartData = { ...data, - datasets: data.datasets && data.datasets.map(set => { - return { - ...set - } - }) - } + datasets: data.datasets && data.datasets.map(set => ({ ...set })) + }; } - updateChart() { - const { options } = this.props + updateChart(): void { + const { options } = this.props; - if (!this.chart) return + if (!this.chart) { + return; + } - this.chart.options = ChartJS.helpers.configMerge(this.chart.options, options) + this.chart.options = ChartJS.helpers.configMerge(this.chart.options, options); - this.memoizeDataProps() + this.memoizeDataProps(); - const datasets: ChartDataSet[] = this.chart.config.data.datasets - const nextDatasets: ChartDataSet[] = this.currentChartData.datasets || [] + const datasets: ChartDataSet[] = this.chart.config.data.datasets; + const nextDatasets: ChartDataSet[] = this.currentChartData.datasets || []; // Remove stale datasets if they're not available in nextDatasets if (datasets.length > nextDatasets.length) { const sets = [...datasets]; sets.forEach(set => { if (!nextDatasets.find(next => next.id === set.id)) { - remove(datasets, (item => item.id === set.id)) + remove(datasets, (item => item.id === set.id)); } - }) + }); } // Mutating inner chart datasets to enable seamless transitions nextDatasets.forEach((next, datasetIndex) => { - const index = datasets.findIndex(set => set.id === next.id) + const index = datasets.findIndex(set => set.id === next.id); if (index !== -1) { - datasets[index].data = datasets[index].data.slice() // "Clean" mobx observables data to use in ChartJS - datasets[index].data.splice(next.data.length) + datasets[index].data = datasets[index].data.slice(); // "Clean" mobx observables data to use in ChartJS + datasets[index].data.splice(next.data.length); next.data.forEach((point: any, dataIndex: number) => { - datasets[index].data[dataIndex] = next.data[dataIndex] - }) + datasets[index].data[dataIndex] = next.data[dataIndex]; + }); // Merge other fields - const { data, ...props } = next + const { data: _data, ...props } = next; datasets[index] = { ...datasets[index], ...props - } + }; + } else { + datasets[datasetIndex] = next; } - else { - datasets[datasetIndex] = next - } - }) - this.chart.update() + }); + this.chart.update(); } - renderLegend() { - if (!this.props.showLegend) return null - const { data, legendColors } = this.props - const { labels, datasets } = data - const labelElem = (title: string, color: string, tooltip?: string) => ( + renderLegend(): JSX.Element { + if (!this.props.showLegend) { + return null; + } + const { data, legendColors } = this.props; + const { labels, datasets } = data; + const labelElem = (title: string, color: string, tooltip?: string): JSX.Element => ( { )} tooltip={tooltip} /> - ) + ); return (
{labels && labels.map((label: string, index) => { - const { backgroundColor } = datasets[0] as any - const color = legendColors ? legendColors[index] : backgroundColor[index] - return labelElem(label, color) + const { backgroundColor } = datasets[0] as any; + const color = legendColors ? legendColors[index] : backgroundColor[index]; + return labelElem(label, color); })} {!labels && datasets.map(({ borderColor, label, tooltip }) => labelElem(label, borderColor as any, tooltip) )}
- ) + ); } - renderChart() { - const { type, options, plugins } = this.props - this.memoizeDataProps() + renderChart(): void { + const { type, options, plugins } = this.props; + this.memoizeDataProps(); this.chart = new ChartJS(this.canvas.current, { type, plugins, @@ -181,11 +185,11 @@ export class Chart extends PureComponent { }, }, data: this.currentChartData, - }) + }); } - render() { - const { width, height, showChart, title, className } = this.props + render(): JSX.Element { + const { width, height, showChart, title, className } = this.props; return ( <>
@@ -203,6 +207,6 @@ export class Chart extends PureComponent { {this.renderLegend()}
- ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/chart/index.ts b/dashboard/client/components/chart/index.ts index 920ff6c53e..a9db66c298 100644 --- a/dashboard/client/components/chart/index.ts +++ b/dashboard/client/components/chart/index.ts @@ -1,3 +1,3 @@ -export * from "./chart" -export * from "./pie-chart" -export * from "./bar-chart" \ No newline at end of file +export * from "./chart"; +export * from "./pie-chart"; +export * from "./bar-chart"; \ No newline at end of file diff --git a/dashboard/client/components/chart/pie-chart.tsx b/dashboard/client/components/chart/pie-chart.tsx index 8960bf67f5..a760de1bb6 100644 --- a/dashboard/client/components/chart/pie-chart.tsx +++ b/dashboard/client/components/chart/pie-chart.tsx @@ -1,7 +1,7 @@ import "./pie-chart.scss"; import * as React from "react"; -import * as ChartJS from "chart.js"; -import { ChartData, ChartOptions } from "chart.js"; +import ChartJS from "chart.js"; +import { ChartOptions } from "chart.js"; import { Chart, ChartProps } from "./chart"; import { cssNames } from "../../utils"; import { themeStore } from "../../theme.store"; @@ -11,27 +11,31 @@ interface Props extends ChartProps { } export class PieChart extends React.Component { - render() { - const { data, className, options, ...settings } = this.props + render(): JSX.Element { + const { data, className, options, ...settings } = this.props; const { contentColor } = themeStore.activeTheme.colors; - const cutouts = [88, 76, 63] + const cutouts = [88, 76, 63]; const opts: ChartOptions = this.props.showChart === false ? {} : { maintainAspectRatio: false, tooltips: { mode: "index", callbacks: { - title: () => "", - label: (tooltipItem, data) => { - const dataset: any = data["datasets"][tooltipItem.datasetIndex] - const metaData = Object.values<{ total: number }>(dataset["_meta"])[0] - const percent = Math.round((dataset["data"][tooltipItem["index"]] / metaData.total) * 100) - if (isNaN(percent)) return "N/A"; + title: (): string => "", + label: (tooltipItem, data): string => { + const dataset: any = data["datasets"][tooltipItem.datasetIndex]; + const metaData = Object.values<{ total: number }>(dataset["_meta"])[0]; + const percent = Math.round((dataset["data"][tooltipItem["index"]] / metaData.total) * 100); + if (isNaN(percent)) { + return "N/A"; + } return percent + "%"; }, }, - filter: ({ datasetIndex, index }, { datasets }) => { + filter: ({ datasetIndex, index }, { datasets }): boolean => { const { data } = datasets[datasetIndex]; - if (datasets.length === 1) return true; + if (datasets.length === 1) { + return true; + } return index !== data.length - 1; }, position: "cursor", @@ -45,7 +49,7 @@ export class PieChart extends React.Component { cutoutPercentage: cutouts[data.datasets.length - 1] || 50, responsive: true, ...options - } + }; return ( { options={opts} {...settings} /> - ) + ); } } -ChartJS.Tooltip.positioners.cursor = function (elements: any, position: { x: number; y: number }) { +ChartJS.Tooltip.positioners.cursor = function (elements: any, position: { x: number; y: number }): {x: number; y: number } { return position; }; \ No newline at end of file diff --git a/dashboard/client/components/chart/useRealTimeMetrics.ts b/dashboard/client/components/chart/useRealTimeMetrics.ts index 96a4118dfa..1809aa29f4 100644 --- a/dashboard/client/components/chart/useRealTimeMetrics.ts +++ b/dashboard/client/components/chart/useRealTimeMetrics.ts @@ -2,15 +2,18 @@ import moment from "moment"; import { useState, useEffect } from "react"; import { useInterval } from "../../hooks"; -type IMetricValues = [number, string][]; -type IChartData = { x: number; y: string }[] +type MetricValue = [number, string]; +interface ChartDatum { + x: number; + y: string; +} const defaultParams = { fetchInterval: 15, updateInterval: 5 -} +}; -export function useRealTimeMetrics(metrics: IMetricValues, chartData: IChartData, params = defaultParams) { +export function useRealTimeMetrics(metrics: MetricValue[], chartData: ChartDatum[], params = defaultParams): ChartDatum[] { const [index, setIndex] = useState(0); const { fetchInterval, updateInterval } = params; const rangeMetrics = metrics.slice(-updateInterval); diff --git a/dashboard/client/components/chart/zebra-stripes.plugin.ts b/dashboard/client/components/chart/zebra-stripes.plugin.ts index b9355b004f..47fb422199 100644 --- a/dashboard/client/components/chart/zebra-stripes.plugin.ts +++ b/dashboard/client/components/chart/zebra-stripes.plugin.ts @@ -9,33 +9,37 @@ const defaultOptions = { interval: 61, stripeMinutes: 10, stripeColor: "#ffffff08", -} +}; export const ZebraStripes = { updated: null as Moment, // timestamp which all stripe movements based on options: {}, - getOptions(chart: ChartJS) { + getOptions(chart: ChartJS): any { return get(chart, "options.plugins.ZebraStripes"); }, - getLastUpdate(chart: ChartJS) { + getLastUpdate(chart: ChartJS): moment.Moment { const data = chart.data.datasets[0].data[0] as ChartPoint; return moment.unix(parseInt(data.x as string)); }, - getStripesElem(chart: ChartJS) { + getStripesElem(chart: ChartJS): Element { return chart.canvas.parentElement.querySelector(".zebra-cover"); }, - removeStripesElem(chart: ChartJS) { + removeStripesElem(chart: ChartJS): void { const elem = this.getStripesElem(chart); - if (!elem) return; + if (!elem) { + return; + } chart.canvas.parentElement.removeChild(elem); }, - renderStripes(chart: ChartJS) { - if (!chart.data.datasets.length) return; + renderStripes(chart: ChartJS): void { + if (!chart.data.datasets.length) { + return; + } const { interval, stripeMinutes, stripeColor } = this.options; const { top, left, bottom, right } = chart.chartArea; const step = (right - left) / interval; @@ -43,7 +47,9 @@ export const ZebraStripes = { const cover = document.createElement("div"); const styles = cover.style; - if (this.getStripesElem(chart)) return; + if (this.getStripesElem(chart)) { + return; + } cover.className = "zebra-cover"; styles.width = right - left + "px"; @@ -57,25 +63,29 @@ export const ZebraStripes = { chart.canvas.parentElement.appendChild(cover); }, - afterInit(chart: ChartJS) { - if (!chart.data.datasets.length) return; + afterInit(chart: ChartJS): void { + if (!chart.data.datasets.length) { + return; + } this.options = { ...defaultOptions, ...this.getOptions(chart) - } + }; this.updated = this.getLastUpdate(chart); }, - afterUpdate(chart: ChartJS) { + afterUpdate(chart: ChartJS): void { this.renderStripes(chart); }, - resize(chart: ChartJS) { + resize(chart: ChartJS): void { this.removeStripesElem(chart); }, afterDatasetUpdate(chart: ChartJS): void { - if (!this.updated) this.updated = this.getLastUpdate(chart); + if (!this.updated) { + this.updated = this.getLastUpdate(chart); + } const { interval } = this.options; const { left, right } = chart.chartArea; @@ -92,4 +102,4 @@ export const ZebraStripes = { cover.style.backgroundPositionX = -step * minutes + "px"; } } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/dashboard/client/components/checkbox/checkbox.tsx b/dashboard/client/components/checkbox/checkbox.tsx index 505f71b70b..71b302766f 100644 --- a/dashboard/client/components/checkbox/checkbox.tsx +++ b/dashboard/client/components/checkbox/checkbox.tsx @@ -1,5 +1,5 @@ -import './checkbox.scss' -import React from 'react' +import './checkbox.scss'; +import React from 'react'; import { autobind, cssNames } from "../../utils"; interface Props { @@ -16,18 +16,20 @@ export class Checkbox extends React.PureComponent { private input: HTMLInputElement; @autobind() - onChange(evt: React.ChangeEvent) { + onChange(evt: React.ChangeEvent): void { if (this.props.onChange) { - this.props.onChange(this.input.checked, evt) + this.props.onChange(this.input.checked, evt); } } - getValue() { - if (this.props.value !== undefined) return this.props.value; + getValue(): boolean { + if (this.props.value !== undefined) { + return this.props.value; + } return this.input.checked; } - render() { + render(): JSX.Element { const { label, inline, className, value, theme, children, ...inputProps } = this.props; const componentClass = cssNames('Checkbox flex', className, { inline: inline, @@ -40,7 +42,9 @@ export class Checkbox extends React.PureComponent { this.input = e} + ref={(e): void => { + this.input = e; + }} /> {label ? {label} : null} diff --git a/dashboard/client/components/checkbox/index.ts b/dashboard/client/components/checkbox/index.ts index 527ce01c6c..7d2e810506 100644 --- a/dashboard/client/components/checkbox/index.ts +++ b/dashboard/client/components/checkbox/index.ts @@ -1 +1 @@ -export * from './checkbox' \ No newline at end of file +export * from './checkbox'; \ No newline at end of file diff --git a/dashboard/client/components/confirm-dialog/confirm-dialog.tsx b/dashboard/client/components/confirm-dialog/confirm-dialog.tsx index a48e9dd5d0..d65b6df3e7 100644 --- a/dashboard/client/components/confirm-dialog/confirm-dialog.tsx +++ b/dashboard/client/components/confirm-dialog/confirm-dialog.tsx @@ -9,7 +9,7 @@ import { Button } from "../button"; import { Dialog, DialogProps } from "../dialog"; import { Icon } from "../icon"; -export interface IConfirmDialogParams { +export interface ConfirmDialogParams { ok?: () => void; labelOk?: ReactNode; labelCancel?: ReactNode; @@ -23,31 +23,31 @@ interface Props extends Partial { @observer export class ConfirmDialog extends React.Component { @observable static isOpen = false; - @observable.ref static params: IConfirmDialogParams; + @observable.ref static params: ConfirmDialogParams; @observable isSaving = false; - static open(params: IConfirmDialogParams) { + static open(params: ConfirmDialogParams): void { ConfirmDialog.isOpen = true; ConfirmDialog.params = params; } - static close() { + static close(): void { ConfirmDialog.isOpen = false; } - public defaultParams: IConfirmDialogParams = { + public defaultParams: ConfirmDialogParams = { ok: noop, labelOk: Ok, labelCancel: Cancel, icon: "warning", }; - get params(): IConfirmDialogParams { + get params(): ConfirmDialogParams { return Object.assign({}, this.defaultParams, ConfirmDialog.params); } - ok = async () => { + ok = async (): Promise => { try { this.isSaving = true; await Promise.resolve(this.params.ok()).catch(noop); @@ -57,15 +57,15 @@ export class ConfirmDialog extends React.Component { this.close(); } - onClose = () => { + onClose = (): void => { this.isSaving = false; } - close = () => { + close = (): void => { ConfirmDialog.close(); } - render() { + render(): JSX.Element { const { className, ...dialogProps } = this.props; const { icon, labelOk, labelCancel, message } = this.params; return ( @@ -96,6 +96,6 @@ export class ConfirmDialog extends React.Component { />
- ) + ); } } diff --git a/dashboard/client/components/confirm-dialog/index.ts b/dashboard/client/components/confirm-dialog/index.ts index de21b827c1..2c2bf03c63 100644 --- a/dashboard/client/components/confirm-dialog/index.ts +++ b/dashboard/client/components/confirm-dialog/index.ts @@ -1 +1 @@ -export * from './confirm-dialog' \ No newline at end of file +export * from './confirm-dialog'; \ No newline at end of file diff --git a/dashboard/client/components/dialog/dialog.tsx b/dashboard/client/components/dialog/dialog.tsx index 10cca096a0..2befb5e8ce 100644 --- a/dashboard/client/components/dialog/dialog.tsx +++ b/dashboard/client/components/dialog/dialog.tsx @@ -47,63 +47,75 @@ export class Dialog extends React.PureComponent { isOpen: this.props.isOpen, } - get elem() { + get elem(): HTMLElement { + // eslint-disable-next-line react/no-find-dom-node return findDOMNode(this) as HTMLElement; } - get isOpen() { + get isOpen(): boolean { return this.state.isOpen; } - componentDidMount() { - if (this.isOpen) this.onOpen(); + componentDidMount(): void { + if (this.isOpen) { + this.onOpen(); + } } - componentDidUpdate(prevProps: DialogProps) { + componentDidUpdate(prevProps: DialogProps): void { const { isOpen } = this.props; if (isOpen !== prevProps.isOpen) { this.toggle(isOpen); } } - componentWillUnmount() { - if (this.isOpen) this.onClose(); + componentWillUnmount(): void { + if (this.isOpen) { + this.onClose(); + } } - toggle(isOpen: boolean) { - if (isOpen) this.open(); - else this.close(); + toggle(isOpen: boolean): void { + if (isOpen) { + this.open(); + } else { + this.close(); + } } - open() { + open(): void { requestAnimationFrame(this.onOpen); // wait for render(), bind close-event to this.elem this.setState({ isOpen: true }); this.props.open(); } - close() { + close(): void { this.onClose(); // must be first to get access to dialog's content from outside this.setState({ isOpen: false }); this.props.close(); } - onOpen = () => { + onOpen = (): void => { this.props.onOpen(); if (!this.props.pinned) { - if (this.elem) this.elem.addEventListener('click', this.onClickOutside); + if (this.elem) { + this.elem.addEventListener('click', this.onClickOutside); + } window.addEventListener('keydown', this.onEscapeKey); } } - onClose = () => { + onClose = (): void => { this.props.onClose(); if (!this.props.pinned) { - if (this.elem) this.elem.removeEventListener('click', this.onClickOutside); + if (this.elem) { + this.elem.removeEventListener('click', this.onClickOutside); + } window.removeEventListener('keydown', this.onEscapeKey); } } - onEscapeKey = (evt: KeyboardEvent) => { + onEscapeKey = (evt: KeyboardEvent): void=> { const escapeKey = evt.code === "Escape"; if (escapeKey) { this.close(); @@ -111,7 +123,7 @@ export class Dialog extends React.PureComponent { } } - onClickOutside = (evt: MouseEvent) => { + onClickOutside = (evt: MouseEvent): void => { const target = evt.target as HTMLElement; if (!this.contentElem.contains(target)) { this.close(); @@ -119,13 +131,15 @@ export class Dialog extends React.PureComponent { } } - render() { + render(): JSX.Element { const { modal, animated, pinned } = this.props; let { className } = this.props; className = cssNames("Dialog flex center", className, { modal, pinned }); let dialog = (
-
this.contentElem = e}> +
{ + this.contentElem = e; + }}> {this.props.children}
@@ -136,8 +150,7 @@ export class Dialog extends React.PureComponent { {dialog} ); - } - else if (!this.isOpen) { + } else if (!this.isOpen) { return null; } return createPortal(dialog, document.body); diff --git a/dashboard/client/components/dialog/index.ts b/dashboard/client/components/dialog/index.ts index 20033cb0d2..20da8e550a 100644 --- a/dashboard/client/components/dialog/index.ts +++ b/dashboard/client/components/dialog/index.ts @@ -1 +1 @@ -export * from './dialog' +export * from './dialog'; diff --git a/dashboard/client/components/dialog/logs-dialog.tsx b/dashboard/client/components/dialog/logs-dialog.tsx index 7e50de9c79..041d8306f2 100644 --- a/dashboard/client/components/dialog/logs-dialog.tsx +++ b/dashboard/client/components/dialog/logs-dialog.tsx @@ -18,15 +18,15 @@ interface Props extends DialogProps { export class LogsDialog extends React.Component { public logsElem: HTMLElement; - copyToClipboard = () => { + copyToClipboard = (): void => { if (copyToClipboard(this.logsElem)) { - Notifications.ok(_i18n._(t`Logs copied to clipboard.`)) + Notifications.ok(_i18n._(t`Logs copied to clipboard.`)); } } - render() { + render(): JSX.Element { const { title, logs, ...dialogProps } = this.props; - const header =
{title}
+ const header =
{title}
; const customButtons = (
- ) + ); return ( - this.logsElem = e}> + { + this.logsElem = e; + }}> {logs || There are no logs available.} - ) + ); } } diff --git a/dashboard/client/components/dock/create-resource.store.ts b/dashboard/client/components/dock/create-resource.store.ts index 6b614db00b..76c8be0107 100644 --- a/dashboard/client/components/dock/create-resource.store.ts +++ b/dashboard/client/components/dock/create-resource.store.ts @@ -1,6 +1,6 @@ import { autobind } from "../../utils"; import { DockTabStore } from "./dock-tab.store"; -import { dockStore, IDockTab, TabKind } from "./dock.store"; +import { dockStore, DockTabData, TabKind } from "./dock.store"; @autobind() export class CreateResourceStore extends DockTabStore { @@ -13,7 +13,7 @@ export class CreateResourceStore extends DockTabStore { export const createResourceStore = new CreateResourceStore(); -export function createResourceTab(tabParams: Partial = {}) { +export function createResourceTab(tabParams: Partial = {}): DockTabData { return dockStore.createTab({ kind: TabKind.CREATE_RESOURCE, title: "Create resource", @@ -21,6 +21,6 @@ export function createResourceTab(tabParams: Partial = {}) { }); } -export function isCreateResourceTab(tab: IDockTab) { - return tab && tab.kind === TabKind.CREATE_RESOURCE; +export function isCreateResourceTab(tab: DockTabData): boolean { + return tab?.kind === TabKind.CREATE_RESOURCE; } diff --git a/dashboard/client/components/dock/create-resource.tsx b/dashboard/client/components/dock/create-resource.tsx index 6d89455b3b..3ee85446b7 100644 --- a/dashboard/client/components/dock/create-resource.tsx +++ b/dashboard/client/components/dock/create-resource.tsx @@ -1,13 +1,13 @@ import "./create-resource.scss"; import React from "react"; -import jsYaml from "js-yaml" +import jsYaml from "js-yaml"; import { observable } from "mobx"; import { observer } from "mobx-react"; import { Plural, t, Trans } from "@lingui/macro"; import { cssNames } from "../../utils"; import { createResourceStore } from "./create-resource.store"; -import { IDockTab } from "./dock.store"; +import { DockTabData } from "./dock.store"; import { EditorPanel } from "./editor-panel"; import { InfoPanel } from "./info-panel"; import { resourceApplierApi } from "../../api/endpoints/resource-applier.api"; @@ -17,52 +17,56 @@ import { Notifications } from "../notifications"; interface Props { className?: string; - tab: IDockTab; + tab: DockTabData; } @observer export class CreateResource extends React.Component { @observable error = "" - get tabId() { - return this.props.tab.id; + get tabId(): string { + return this.props.tab.id || ""; } - get data() { + get data(): string { return createResourceStore.getData(this.tabId); } - onChange = (value: string, error?: string) => { + onChange = (value: string, error?: string): void => { createResourceStore.setData(this.tabId, value); this.error = error; } - create = async () => { - if (this.error) return; + create = async (): Promise => { + if (this.error) { + return; + } const resources = jsYaml.safeLoadAll(this.data) - .filter(v => !!v) // skip empty documents if "---" pasted at the beginning or end + .filter(v => !!v); // skip empty documents if "---" pasted at the beginning or end const createdResources: string[] = []; const errors: string[] = []; await Promise.all( resources.map(data => { return resourceApplierApi.update(data) .then(item => createdResources.push(item.getName())) - .catch((err: JsonApiErrorParsed) => errors.push(err.toString())) + .catch((err: JsonApiErrorParsed) => errors.push(err.toString())); }) ); if (errors.length) { errors.forEach(Notifications.error); - if (!createdResources.length) throw errors[0]; + if (!createdResources.length) { + throw errors[0]; + } } return (

{" "} {createdResources.join(", ")} successfully created

- ) + ); } - render() { + render(): JSX.Element { const { tabId, data, error, create, onChange } = this; const { className } = this.props; return ( @@ -80,6 +84,6 @@ export class CreateResource extends React.Component { showNotifications={false} />
- ) + ); } } diff --git a/dashboard/client/components/dock/dock-tab.store.ts b/dashboard/client/components/dock/dock-tab.store.ts index d644b628b7..aab7bbfc42 100644 --- a/dashboard/client/components/dock/dock-tab.store.ts +++ b/dashboard/client/components/dock/dock-tab.store.ts @@ -1,5 +1,5 @@ import { autorun, observable, reaction } from "mobx"; -import { autobind, createStorage } from "../../utils"; +import { autobind, StorageHelper } from "../../utils"; import { dockStore, TabId } from "./dock.store"; interface Options { @@ -16,7 +16,7 @@ export class DockTabStore { // auto-save to local-storage if (storageName) { - const storage = createStorage<[TabId, T][]>(storageName, []); + const storage = new StorageHelper<[TabId, T][]>(storageName, []); this.data.replace(storage.get()); reaction(() => this.serializeData(), (data: T | any) => storage.set(data)); } @@ -28,31 +28,34 @@ export class DockTabStore { if (!currentTabs.includes(tabId)) { this.clearData(tabId); } - }) + }); }); } - protected serializeData() { + protected serializeData(): [string, Partial][] { const { storageSerializer } = this.options; - return Array.from(this.data).map(([tabId, tabData]) => { - if (storageSerializer) return [tabId, storageSerializer(tabData)] - return [tabId, tabData]; - }) + return Array.from(this.data) + .map(([tabId, tabData]) => { + if (storageSerializer) { + return [tabId, storageSerializer(tabData)]; + } + return [tabId, tabData]; + }); } - getData(tabId: TabId) { + getData(tabId: TabId): T { return this.data.get(tabId); } - setData(tabId: TabId, data: T) { + setData(tabId: TabId, data: T): void { this.data.set(tabId, data); } - clearData(tabId: TabId) { + clearData(tabId: TabId): void { this.data.delete(tabId); } - reset() { + reset(): void { this.data.clear(); } } diff --git a/dashboard/client/components/dock/dock-tab.tsx b/dashboard/client/components/dock/dock-tab.tsx index 41c595432c..70c8c5b644 100644 --- a/dashboard/client/components/dock/dock-tab.tsx +++ b/dashboard/client/components/dock/dock-tab.tsx @@ -1,30 +1,30 @@ -import "./dock-tab.scss" +import "./dock-tab.scss"; -import React from "react" +import React from "react"; import { observer } from "mobx-react"; import { t } from "@lingui/macro"; import { autobind, cssNames, prevDefault } from "../../utils"; -import { dockStore, IDockTab } from "./dock.store"; +import { dockStore, DockTabData } from "./dock.store"; import { Tab, TabProps } from "../tabs"; import { Icon } from "../icon"; import { _i18n } from "../../i18n"; -export interface DockTabProps extends TabProps { +export interface DockTabProps extends TabProps { moreActions?: React.ReactNode; } @observer export class DockTab extends React.Component { - get tabId() { - return this.props.value.id; + get tabId(): string { + return this.props.value.id || ""; } @autobind() - close() { + close(): void { dockStore.closeTab(this.tabId); } - render() { + render(): JSX.Element { const { className, moreActions, ...tabProps } = this.props; const { title, pinned } = tabProps.value; const label = ( @@ -39,13 +39,13 @@ export class DockTab extends React.Component { /> )}
- ) + ); return ( - ) + ); } } \ No newline at end of file diff --git a/dashboard/client/components/dock/dock.store.ts b/dashboard/client/components/dock/dock.store.ts index c1b70cc925..2c33b93e25 100644 --- a/dashboard/client/components/dock/dock.store.ts +++ b/dashboard/client/components/dock/dock.store.ts @@ -1,7 +1,7 @@ import MD5 from "crypto-js/md5"; -import { action, computed, IReactionOptions, observable, reaction } from "mobx"; -import { autobind, createStorage } from "../../utils"; -import throttle from "lodash/throttle" +import { action, computed, IReactionOptions, observable, reaction, IReactionDisposer } from "mobx"; +import { autobind, StorageHelper } from "../../utils"; +import throttle from "lodash/throttle"; export type TabId = string; @@ -13,7 +13,7 @@ export enum TabKind { UPGRADE_CHART = "upgrade-chart", } -export interface IDockTab { +export interface DockTabData { id?: TabId; kind: TabKind; title?: string; @@ -22,29 +22,29 @@ export interface IDockTab { @autobind() export class DockStore { - protected initialTabs: IDockTab[] = [ + protected initialTabs: DockTabData[] = [ { id: "terminal", kind: TabKind.TERMINAL, title: "Terminal" }, ]; - protected storage = createStorage("dock", {}); // keep settings in localStorage + protected storage = new StorageHelper("dock", {}); // keep settings in localStorage public defaultTabId = this.initialTabs[0].id; public minHeight = 100; @observable isOpen = false; @observable fullSize = false; @observable height = this.defaultHeight; - @observable tabs = observable.array(this.initialTabs); + @observable tabs = observable.array(this.initialTabs); @observable selectedTabId = this.defaultTabId; - @computed get selectedTab() { + @computed get selectedTab(): DockTabData { return this.tabs.find(tab => tab.id === this.selectedTabId); } - get defaultHeight() { + get defaultHeight(): number { return Math.round(window.innerHeight / 2.5); } - get maxHeight() { + get maxHeight(): number { const mainLayoutHeader = 40; const mainLayoutTabs = 33; const mainLayoutMargin = 16; @@ -69,7 +69,7 @@ export class DockStore { window.addEventListener("resize", throttle(this.checkMaxHeight, 250)); } - protected checkMaxHeight() { + protected checkMaxHeight(): void { if (!this.height) { this.setHeight(this.defaultHeight || this.minHeight); } @@ -78,20 +78,20 @@ export class DockStore { } } - onResize(callback: () => void, options?: IReactionOptions) { + onResize(callback: () => void, options?: IReactionOptions): IReactionDisposer { return reaction(() => [this.height, this.fullSize], callback, options); } - onTabChange(callback: (tabId: TabId) => void, options?: IReactionOptions) { + onTabChange(callback: (tabId: TabId) => void, options?: IReactionOptions): IReactionDisposer { return reaction(() => this.selectedTabId, callback, options); } - hasTabs() { + hasTabs(): boolean { return this.tabs.length > 0; } @action - open(fullSize?: boolean) { + open(fullSize?: boolean): void { this.isOpen = true; if (typeof fullSize === "boolean") { this.fullSize = fullSize; @@ -99,27 +99,32 @@ export class DockStore { } @action - close() { + close(): void { this.isOpen = false; } @action - toggle() { - if (this.isOpen) this.close(); - else this.open(); + toggle(): void { + if (this.isOpen) { + this.close(); + } else { + this.open(); + } } @action - toggleFillSize() { - if (!this.isOpen) this.open(); + toggleFillSize(): void { + if (!this.isOpen) { + this.open(); + } this.fullSize = !this.fullSize; } - getTabById(tabId: TabId) { + getTabById(tabId: TabId): DockTabData { return this.tabs.find(tab => tab.id === tabId); } - protected getNewTabNumber(kind: TabKind) { + protected getNewTabNumber(kind: TabKind): number { const tabNumbers = this.tabs .filter(tab => tab.kind === kind) .map(tab => { @@ -127,17 +132,21 @@ export class DockStore { return tabNumber === 0 ? 1 : tabNumber; // tab without a number is first }); for (let i = 1; ; i++) { - if (!tabNumbers.includes(i)) return i; + if (!tabNumbers.includes(i)) { + return i; + } } } @action - createTab(anonTab: IDockTab, addNumber = true): IDockTab { + createTab(anonTab: DockTabData, addNumber = true): DockTabData { const tabId = MD5(Math.random().toString() + Date.now()).toString(); - const tab: IDockTab = { id: tabId, ...anonTab }; + const tab: DockTabData = { id: tabId, ...anonTab }; if (addNumber) { const tabNumber = this.getNewTabNumber(tab.kind); - if (tabNumber > 1) tab.title += ` (${tabNumber})` + if (tabNumber > 1) { + tab.title += ` (${tabNumber})`; + } } this.tabs.push(tab); this.selectTab(tab.id); @@ -146,7 +155,7 @@ export class DockStore { } @action - async closeTab(tabId: TabId) { + async closeTab(tabId: TabId): Promise { const tab = this.getTabById(tabId); if (!tab || tab.pinned) { return; @@ -158,11 +167,12 @@ export class DockStore { if (newTab.kind === TabKind.TERMINAL) { // close the dock when selected sibling inactive terminal tab const { terminalStore } = await import("./terminal.store"); - if (!terminalStore.isConnected(newTab.id)) this.close(); + if (!terminalStore.isConnected(newTab.id)) { + this.close(); + } } this.selectTab(newTab.id); - } - else { + } else { this.selectedTabId = null; this.close(); } @@ -170,18 +180,18 @@ export class DockStore { } @action - selectTab(tabId: TabId) { + selectTab(tabId: TabId): void { const tab = this.getTabById(tabId); this.selectedTabId = tab ? tab.id : null; } @action - setHeight(height: number) { + setHeight(height: number): void { this.height = Math.max(0, Math.min(height, this.maxHeight)); } @action - reset() { + reset(): void { this.selectedTabId = this.defaultTabId; this.tabs.replace(this.initialTabs); this.height = this.defaultHeight; diff --git a/dashboard/client/components/dock/dock.tsx b/dashboard/client/components/dock/dock.tsx index d384c36608..b8bb10dd4c 100644 --- a/dashboard/client/components/dock/dock.tsx +++ b/dashboard/client/components/dock/dock.tsx @@ -9,7 +9,7 @@ import { Icon } from "../icon"; import { Tabs } from "../tabs/tabs"; import { MenuItem } from "../menu"; import { MenuActions } from "../menu/menu-actions"; -import { dockStore, IDockTab } from "./dock.store"; +import { dockStore, DockTabData } from "./dock.store"; import { DockTab } from "./dock-tab"; import { TerminalTab } from "./terminal-tab"; import { TerminalWindow } from "./terminal-window"; @@ -29,7 +29,7 @@ interface Props { @observer export class Dock extends React.Component { - onResizeStart = () => { + onResizeStart = (): void => { const { isOpen, open, setHeight, minHeight } = dockStore; if (!isOpen) { open(); @@ -37,53 +37,59 @@ export class Dock extends React.Component { } } - onResize = ({ offsetY }: DraggableState) => { + onResize = ({ offsetY }: DraggableState): void => { const { isOpen, close, height, setHeight, minHeight, defaultHeight } = dockStore; const newHeight = height + offsetY; if (height > newHeight && newHeight < minHeight) { setHeight(defaultHeight); close(); - } - else if (isOpen) { + } else if (isOpen) { setHeight(newHeight); } } - onKeydown = (evt: React.KeyboardEvent) => { - const { close, closeTab, selectedTab, fullSize, toggleFillSize } = dockStore; - if (!selectedTab) return; + onKeydown = (evt: React.KeyboardEvent): void => { + const { close, closeTab, selectedTab } = dockStore; + if (!selectedTab) { + return; + } const { code, ctrlKey, shiftKey } = evt.nativeEvent; if (shiftKey && code === "Escape") { close(); } if (ctrlKey && code === "KeyW") { - if (selectedTab.pinned) close(); - else closeTab(selectedTab.id); + if (selectedTab.pinned) { + close(); + } else { + closeTab(selectedTab.id); + } } } - onChangeTab = (tab: IDockTab) => { + onChangeTab = (tab: DockTabData): void => { const { open, selectTab } = dockStore; open(); selectTab(tab.id); } @autobind() - renderTab(tab: IDockTab) { + renderTab(tab: DockTabData): JSX.Element { if (isTerminalTab(tab)) { - return + return ; } if (isCreateResourceTab(tab) || isEditResourceTab(tab)) { - return + return ; } if (isInstallChartTab(tab) || isUpgradeChartTab(tab)) { - return }/> + return }/>; } } - renderTabContent() { + renderTabContent(): JSX.Element { const { isOpen, height, selectedTab: tab } = dockStore; - if (!isOpen || !tab) return; + if (!isOpen || !tab) { + return; + } return (
{isCreateResourceTab(tab) && } @@ -92,10 +98,10 @@ export class Dock extends React.Component { {isUpgradeChartTab(tab) && } {isTerminalTab(tab) && }
- ) + ); } - render() { + render(): JSX.Element { const { className } = this.props; const { isOpen, toggle, tabs, toggleFillSize, selectedTab, hasTabs, fullSize } = dockStore; return ( @@ -115,16 +121,21 @@ export class Dock extends React.Component { autoFocus={isOpen} className="dock-tabs" value={selectedTab} onChange={this.onChangeTab} - children={tabs.map(tab => {this.renderTab(tab)})} - /> + > + {tabs.map(tab => {this.renderTab(tab)})} +
New tab }} closeOnScroll={false}> - createTerminalTab()}> + { + createTerminalTab(); + }}> Terminal session - createResourceTab()}> + { + createResourceTab(); + }}> Create resource @@ -148,6 +159,6 @@ export class Dock extends React.Component {
{this.renderTabContent()}
- ) + ); } } diff --git a/dashboard/client/components/dock/edit-resource.store.ts b/dashboard/client/components/dock/edit-resource.store.ts index d01d604d37..2ae3b8433b 100644 --- a/dashboard/client/components/dock/edit-resource.store.ts +++ b/dashboard/client/components/dock/edit-resource.store.ts @@ -1,7 +1,7 @@ import { autobind, noop } from "../../utils"; import { DockTabStore } from "./dock-tab.store"; import { autorun, IReactionDisposer } from "mobx"; -import { dockStore, IDockTab, TabKind } from "./dock.store"; +import { dockStore, DockTabData, TabKind } from "./dock.store"; import { KubeObject } from "../../api/kube-object"; import { apiManager } from "../../api/api-manager"; @@ -17,7 +17,7 @@ export class EditResourceStore extends DockTabStore { constructor() { super({ storageName: "edit_resource_store", - storageSerializer: ({ draft, ...data }) => data, // skip saving draft in local-storage + storageSerializer: ({ draft: _draft, ...data }) => data, // skip saving draft in local-storage }); autorun(() => { @@ -33,38 +33,37 @@ export class EditResourceStore extends DockTabStore { // preload resource for editing if (!obj && !store.isLoaded && !store.isLoading && isActiveTab) { store.loadFromPath(resource).catch(noop); - } - // auto-close tab when resource removed from store - else if (!obj && store.isLoaded) { + } else if (!obj && store.isLoaded) { + // auto-close tab when resource removed from store dockStore.closeTab(tabId); } } }, { delay: 100 // make sure all stores initialized })); - }) + }); }); } - getTabByResource(object: KubeObject): IDockTab { - const [tabId] = Array.from(this.data).find(([tabId, { resource }]) => { + getTabByResource(object: KubeObject): DockTabData { + const [tabId] = Array.from(this.data).find(([_tabId, { resource }]) => { return object.selfLink === resource; }) || []; return dockStore.getTabById(tabId); } - reset() { + reset(): void { super.reset(); Array.from(this.watchers).forEach(([tabId, dispose]) => { this.watchers.delete(tabId); dispose(); - }) + }); } } export const editResourceStore = new EditResourceStore(); -export function editResourceTab(object: KubeObject, tabParams: Partial = {}) { +export function editResourceTab(object: KubeObject, tabParams: Partial = {}): DockTabData { // use existing tab if already opened let tab = editResourceStore.getTabByResource(object); if (tab) { @@ -85,6 +84,6 @@ export function editResourceTab(object: KubeObject, tabParams: Partial return tab; } -export function isEditResourceTab(tab: IDockTab) { - return tab && tab.kind === TabKind.EDIT_RESOURCE; +export function isEditResourceTab(tab: DockTabData): boolean { + return tab?.kind === TabKind.EDIT_RESOURCE; } \ No newline at end of file diff --git a/dashboard/client/components/dock/edit-resource.tsx b/dashboard/client/components/dock/edit-resource.tsx index 8168af28bc..6dccb29ef2 100644 --- a/dashboard/client/components/dock/edit-resource.tsx +++ b/dashboard/client/components/dock/edit-resource.tsx @@ -3,11 +3,11 @@ import "./edit-resource.scss"; import React from "react"; import { autorun, observable } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; -import jsYaml from "js-yaml" +import jsYaml from "js-yaml"; import { t, Trans } from "@lingui/macro"; -import { IDockTab } from "./dock.store"; +import { DockTabData } from "./dock.store"; import { cssNames } from "../../utils"; -import { editResourceStore } from "./edit-resource.store"; +import { editResourceStore, KubeEditResource } from "./edit-resource.store"; import { InfoPanel } from "./info-panel"; import { Badge } from "../badge"; import { EditorPanel } from "./editor-panel"; @@ -18,7 +18,7 @@ import { KubeObject } from "../../api/kube-object"; interface Props { className?: string; - tab: IDockTab; + tab: DockTabData; } @observer @@ -27,17 +27,19 @@ export class EditResource extends React.Component { @disposeOnUnmount autoDumpResourceOnInit = autorun(() => { - if (!this.tabData) return; + if (!this.tabData) { + return; + } if (this.tabData.draft === undefined && this.resource) { this.saveDraft(this.resource); } }); - get tabId() { - return this.props.tab.id; + get tabId(): string { + return this.props.tab.id || ""; } - get tabData() { + get tabData(): KubeEditResource { return editResourceStore.getData(this.tabId); } @@ -49,7 +51,7 @@ export class EditResource extends React.Component { } } - saveDraft(draft: string | object) { + saveDraft(draft: string | object): void { if (typeof draft === "object") { draft = draft ? jsYaml.dump(draft) : undefined; } @@ -59,12 +61,12 @@ export class EditResource extends React.Component { }); } - onChange = (draft: string, error?: string) => { + onChange = (draft: string, error?: string): void => { this.error = error; this.saveDraft(draft); } - save = async () => { + save = async (): Promise => { if (this.error) { return; } @@ -81,7 +83,7 @@ export class EditResource extends React.Component { ); } - render() { + render(): JSX.Element { const { tabId, resource, tabData, error, onChange, save } = this; const { draft } = tabData; if (!resource || draft === undefined) { @@ -110,6 +112,6 @@ export class EditResource extends React.Component { )} />
- ) + ); } } diff --git a/dashboard/client/components/dock/editor-panel.tsx b/dashboard/client/components/dock/editor-panel.tsx index bf57eb10ca..384f79bdc1 100644 --- a/dashboard/client/components/dock/editor-panel.tsx +++ b/dashboard/client/components/dock/editor-panel.tsx @@ -1,5 +1,5 @@ import React from "react"; -import jsYaml from "js-yaml" +import jsYaml from "js-yaml"; import { observable } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { cssNames } from "../../utils"; @@ -23,17 +23,17 @@ export class EditorPanel extends React.Component { @observable yamlError = "" - componentDidMount() { + componentDidMount(): void { // validate and run callback with optional error this.onChange(this.props.value || ""); disposeOnUnmount(this, [ dockStore.onTabChange(this.onTabChange, { delay: 250 }), dockStore.onResize(this.onResize, { delay: 250 }), - ]) + ]); } - validate(value: string) { + validate(value: string): void { try { jsYaml.safeLoadAll(value); this.yamlError = ""; @@ -42,26 +42,26 @@ export class EditorPanel extends React.Component { } } - onTabChange = () => { + onTabChange = (): void => { this.editor.focus(); } - onResize = () => { + onResize = (): void => { this.editor.resize(); } - onCursorPosChange = (pos: Ace.Point) => { + onCursorPosChange = (pos: Ace.Point): void => { EditorPanel.cursorPos.setData(this.props.tabId, pos); } - onChange = (value: string) => { + onChange = (value: string): void => { this.validate(value); if (this.props.onChange) { this.props.onChange(value, this.yamlError); } } - render() { + render(): JSX.Element { const { value, tabId } = this.props; let { className } = this.props; className = cssNames("EditorPanel", className); @@ -74,8 +74,10 @@ export class EditorPanel extends React.Component { cursorPos={cursorPos} onChange={this.onChange} onCursorPosChange={this.onCursorPosChange} - ref={e => this.editor = e} + ref={(e): void => { + this.editor = e; + }} /> - ) + ); } } diff --git a/dashboard/client/components/dock/index.ts b/dashboard/client/components/dock/index.ts index 08cc7287b9..c51aa2a3cd 100644 --- a/dashboard/client/components/dock/index.ts +++ b/dashboard/client/components/dock/index.ts @@ -1 +1 @@ -export * from "./dock" +export * from "./dock"; diff --git a/dashboard/client/components/dock/info-panel.tsx b/dashboard/client/components/dock/info-panel.tsx index 69e79ad8de..613212dd2c 100644 --- a/dashboard/client/components/dock/info-panel.tsx +++ b/dashboard/client/components/dock/info-panel.tsx @@ -42,21 +42,21 @@ export class InfoPanel extends Component { @observable error = ""; @observable waiting = false; - componentDidMount() { + componentDidMount(): void { disposeOnUnmount(this, [ reaction(() => this.props.tabId, () => { - this.result = "" - this.error = "" - this.waiting = false + this.result = ""; + this.error = ""; + this.waiting = false; }) - ]) + ]); } - @computed get errorInfo() { + @computed get errorInfo(): string { return this.error || this.props.error; } - submit = async () => { + submit = async (): Promise => { const { showNotifications } = this.props; this.result = ""; this.error = ""; @@ -65,24 +65,28 @@ export class InfoPanel extends Component { this.result = await this.props.submit().finally(() => { this.waiting = false; }); - if (showNotifications) Notifications.ok(this.result); + if (showNotifications) { + Notifications.ok(this.result); + } } catch (error) { this.error = error.toString(); - if (showNotifications) Notifications.error(this.error); + if (showNotifications) { + Notifications.error(this.error); + } throw error; } } - submitAndClose = async () => { + submitAndClose = async (): Promise => { await this.submit(); this.close(); } - close = () => { + close = (): void => { dockStore.closeTab(this.props.tabId); } - renderInfo() { + renderInfo(): JSX.Element { if (!this.props.showInlineInfo) { return; } @@ -101,10 +105,10 @@ export class InfoPanel extends Component { )} - ) + ); } - render() { + render(): JSX.Element { const { className, controls, submitLabel, disableSubmit, error, submittingMessage, showSubmitClose } = this.props; const { submit, close, submitAndClose, waiting } = this; const isDisabled = !!(disableSubmit || waiting || error); diff --git a/dashboard/client/components/dock/install-chart.store.ts b/dashboard/client/components/dock/install-chart.store.ts index 1aeb9f4398..137079007d 100644 --- a/dashboard/client/components/dock/install-chart.store.ts +++ b/dashboard/client/components/dock/install-chart.store.ts @@ -1,12 +1,12 @@ import { action, autorun } from "mobx"; -import { dockStore, IDockTab, TabId, TabKind } from "./dock.store"; +import { dockStore, DockTabData, TabId, TabKind } from "./dock.store"; import { DockTabStore } from "./dock-tab.store"; import { t } from "@lingui/macro"; import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api"; -import { IReleaseUpdateDetails } from "../../api/endpoints/helm-releases.api"; +import { ReleaseUpdateDetails } from "../../api/endpoints/helm-releases.api"; import { _i18n } from "../../i18n"; -export interface IChartInstallData { +export interface ChartInstallData { name: string; repo: string; version: string; @@ -17,9 +17,13 @@ export interface IChartInstallData { lastVersion?: boolean; } -export class InstallChartStore extends DockTabStore { +export function isInstallChartTab(tab: DockTabData): boolean { + return tab?.kind === TabKind.INSTALL_CHART; +} + +export class InstallChartStore extends DockTabStore { public versions = new DockTabStore(); - public details = new DockTabStore(); + public details = new DockTabStore(); constructor() { super({ @@ -27,25 +31,27 @@ export class InstallChartStore extends DockTabStore { }); autorun(() => { const { selectedTab, isOpen } = dockStore; - if (!isInstallChartTab(selectedTab)) return; + if (!isInstallChartTab(selectedTab)) { + return; + } if (isOpen) { this.loadData(); } - }, { delay: 250 }) + }, { delay: 250 }); } @action - async loadData(tabId = dockStore.selectedTabId) { + async loadData(tabId = dockStore.selectedTabId): Promise { const { values } = this.getData(tabId); const versions = this.versions.getData(tabId); - return Promise.all([ + await Promise.all([ !versions && this.loadVersions(tabId), !values && this.loadValues(tabId), - ]) + ]); } @action - async loadVersions(tabId: TabId) { + async loadVersions(tabId: TabId): Promise { const { repo, name } = this.getData(tabId); this.versions.clearData(tabId); // reset const charts = await helmChartsApi.get(repo, name); @@ -54,13 +60,15 @@ export class InstallChartStore extends DockTabStore { } @action - async loadValues(tabId: TabId) { + async loadValues(tabId: TabId): Promise { const data = this.getData(tabId); const { repo, name, version } = data; let values = ""; - const fetchValues = async (retry = 1, maxRetries = 3) => { + const fetchValues = async (retry = 1, maxRetries = 3): Promise => { values = await helmChartsApi.getValues(repo, name, version); - if (values || retry == maxRetries) return; + if (values || retry == maxRetries) { + return; + } await fetchValues(retry + 1); }; this.setData(tabId, { ...data, values: undefined }); // reset @@ -71,7 +79,7 @@ export class InstallChartStore extends DockTabStore { export const installChartStore = new InstallChartStore(); -export function createInstallChartTab(chart: HelmChart, tabParams: Partial = {}) { +export function createInstallChartTab(chart: HelmChart, tabParams: Partial = {}): DockTabData { const { name, repo, version } = chart; const tab = dockStore.createTab({ @@ -91,7 +99,3 @@ export function createInstallChartTab(chart: HelmChart, tabParams: Partial { @observable error = ""; @observable showNotes = false; - get values() { - return this.chartData.values; + get values(): string { + return this.chartData.values || ""; } - get chartData() { + get chartData(): ChartInstallData { return installChartStore.getData(this.tabId); } - get tabId() { - return this.props.tab.id; + get tabId(): string { + return this.props.tab.id || ""; } - get versions() { + get versions(): string[] { return installChartStore.versions.getData(this.tabId); } - get releaseDetails() { + get releaseDetails(): ReleaseUpdateDetails { return installChartStore.details.getData(this.tabId); } @autobind() - viewRelease() { + viewRelease(): void { const { release } = this.releaseDetails; navigate(releaseURL({ params: { @@ -64,35 +65,35 @@ export class InstallChart extends Component { } @autobind() - save(data: Partial) { + save(data: Partial): void { const chart = { ...this.chartData, ...data }; installChartStore.setData(this.tabId, chart); } @autobind() - onVersionChange(option: SelectOption) { + onVersionChange(option: SelectOption): void { const version = option.value; this.save({ version, values: "" }); installChartStore.loadValues(this.tabId); } @autobind() - onValuesChange(values: string, error?: string) { + onValuesChange(values: string, error?: string): void { this.error = error; this.save({ values }); } @autobind() - onNamespaceChange(opt: SelectOption) { + onNamespaceChange(opt: SelectOption): void { this.save({ namespace: opt.value }); } @autobind() - onReleaseNameChange(name: string) { + onReleaseNameChange(name: string): void { this.save({ releaseName: name }); } - install = async () => { + install = async (): Promise => { const { repo, name, version, namespace, values, releaseName } = this.chartData; const details = await releaseStore.create({ name: releaseName || undefined, @@ -105,7 +106,7 @@ export class InstallChart extends Component { ); } - render() { + render(): JSX.Element { const { tabId, chartData, values, versions, install } = this; if (!chartData || chartData.values === undefined || !versions) { return ; @@ -127,17 +128,21 @@ export class InstallChart extends Component {