mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
cleanup Lens repo with tighter linting
Signed-off-by: Sebastian Malton <smalton@mirantis.com>
This commit is contained in:
1 parent
e468105143
commit
b1ff34879a
470 files changed
+8447
-7443
No files matched your search
@@ -1,8 +1,8 @@
|
||||
import { KubeApi, IKubeApiLinkBase } from "../kube-api";
|
||||
import { KubeApi, KubeApiLinkBase } from "../kube-api";
|
||||
|
||||
interface ParseAPITest {
|
||||
url: string;
|
||||
expected: Required<IKubeApiLinkBase>;
|
||||
expected: Required<KubeApiLinkBase>;
|
||||
}
|
||||
|
||||
const tests: ParseAPITest[] = [
|
||||
|
||||
@@ -17,7 +17,7 @@ export class ApiManager {
|
||||
private stores = observable.map<KubeApi, KubeObjectStore>();
|
||||
private views = observable.map<KubeApi, ApiComponents>();
|
||||
|
||||
getApi(pathOrCallback: string | ((api: KubeApi) => boolean)) {
|
||||
getApi(pathOrCallback: string | ((api: KubeApi) => boolean)): KubeApi<any> {
|
||||
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)) || {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<Cluster> {
|
||||
async getMetrics(nodeNames: string[], params?: IMetricsReqParams): Promise<IClusterMetrics> {
|
||||
async getMetrics(nodeNames: string[], params?: MetricsReqParams): Promise<ClusterMetrics> {
|
||||
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<T = IMetrics> {
|
||||
export interface ClusterMetrics<T = Metrics> {
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IConfig>("/config")
|
||||
getConfig(): CancelablePromise<IConfig> {
|
||||
return apiBase.get<IConfig>("/config");
|
||||
},
|
||||
};
|
||||
@@ -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<CustomResourceDefinition["spec"]["additionalPrinterColumns"]> {
|
||||
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<CustomResourceDefinition["status"]["conditions"]> {
|
||||
return (this.status.conditions || []).map(condition => {
|
||||
const { message, reason, lastTransitionTime, status } = condition;
|
||||
return {
|
||||
...condition,
|
||||
isReady: status === "True",
|
||||
tooltip: `${message || reason} (${lastTransitionTime})`
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Deployment> {
|
||||
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<number> {
|
||||
return this.request
|
||||
.get(this.getScaleApiUrl(params))
|
||||
.then(({ status }: any) => status.replicas)
|
||||
async getReplicas(params: { namespace: string; name: string }): Promise<number> {
|
||||
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<KubeJsonApiData> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 "<none>"
|
||||
return "<none>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
});
|
||||
@@ -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<IHelmChartList>(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<IHelmChartDetails>(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<string>(`/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<HelmChart["maintainers"]> {
|
||||
return this.maintainers || [];
|
||||
}
|
||||
|
||||
getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
getRepository() {
|
||||
return this.repo;
|
||||
}
|
||||
|
||||
getAppVersion() {
|
||||
getAppVersion(): string {
|
||||
return this.appVersion || "";
|
||||
}
|
||||
|
||||
getKeywords() {
|
||||
getKeywords(): Required<HelmChart["keywords"]> {
|
||||
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<HelmChart[]> {
|
||||
return apiKubeHelm.get<HelmChartList>(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<HelmChartInfo> {
|
||||
const path = endpoint({ repo, name });
|
||||
|
||||
return apiKubeHelm.get<HelmChartDetails>(path + "?" + stringify({ version: readmeVersion }))
|
||||
.then(({ readme, versions }) => ({
|
||||
readme,
|
||||
versions: versions.map(data => new HelmChart(data))
|
||||
}));
|
||||
},
|
||||
|
||||
getValues(repo: string, name: string, version: string): CancelablePromise<string> {
|
||||
return apiKubeHelm
|
||||
.get<string>(`/v2/charts/${repo}/${name}/values?` + stringify({ version }));
|
||||
}
|
||||
};
|
||||
@@ -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<HelmRelease[]>(endpoint({ namespace }))
|
||||
.then(releases => releases.map(HelmRelease.create));
|
||||
},
|
||||
|
||||
get(name: string, namespace: string) {
|
||||
const path = endpoint({ name, namespace });
|
||||
return apiKubeHelm.get<IReleaseRawDetails>(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<IReleaseUpdateDetails> {
|
||||
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<IReleaseUpdateDetails> {
|
||||
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<string>(path);
|
||||
},
|
||||
|
||||
getHistory(name: string, namespace: string): Promise<IReleaseRevision[]> {
|
||||
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<string> {
|
||||
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<HelmRelease[]> {
|
||||
const releases = await apiKubeHelm.get<HelmRelease[]>(endpoint({ namespace }));
|
||||
return releases.map(data => new HelmRelease(data));
|
||||
},
|
||||
|
||||
async get(name: string, namespace: string): Promise<ReleaseInfo> {
|
||||
const path = endpoint({ name, namespace });
|
||||
const details = await apiKubeHelm.get<ReleaseRawDetails>(path);
|
||||
const items: KubeObject[] = JSON.parse(details.resources).items;
|
||||
const resources = items.map(item => new KubeObject(item));
|
||||
return {
|
||||
...details,
|
||||
resources
|
||||
};
|
||||
},
|
||||
|
||||
create(payload: ReleaseCreatePayload): Promise<ReleaseUpdateDetails> {
|
||||
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<ReleaseUpdateDetails> {
|
||||
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<KubeJsonApiData> {
|
||||
const path = endpoint({ name, namespace });
|
||||
return apiKubeHelm.del(path);
|
||||
},
|
||||
|
||||
getValues(name: string, namespace: string): CancelablePromise<string> {
|
||||
const path = endpoint({ name, namespace }) + "/values";
|
||||
return apiKubeHelm.get<string>(path);
|
||||
},
|
||||
|
||||
getHistory(name: string, namespace: string): Promise<ReleaseRevision[]> {
|
||||
const path = endpoint({ name, namespace }) + "/history";
|
||||
return apiKubeHelm.get(path);
|
||||
},
|
||||
|
||||
rollback(name: string, namespace: string, revision: number): CancelablePromise<KubeJsonApiData> {
|
||||
const path = endpoint({ name, namespace }) + "/rollback";
|
||||
return apiKubeHelm.put(path, {
|
||||
data: {
|
||||
revision: revision
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -22,7 +22,7 @@ export type IHpaMetricData<T = any> = 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}`;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
@@ -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<Ingress> {
|
||||
getMetrics(ingress: string, namespace: string): Promise<IIngressMetrics> {
|
||||
const opts = { category: "ingress", ingress }
|
||||
getMetrics(ingress: string, namespace: string): Promise<IngressMetrics> {
|
||||
const opts = { category: "ingress", ingress };
|
||||
return metricsApi.getMetrics({
|
||||
bytesSentSuccess: opts,
|
||||
bytesSentFailure: opts,
|
||||
@@ -17,7 +17,7 @@ export class IngressApi extends KubeApi<Ingress> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IIngressMetrics<T = IMetrics> {
|
||||
export interface IngressMetrics<T = Metrics> {
|
||||
[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(", ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<KubeJsonApiData> {
|
||||
const params: JsonApiParams = {
|
||||
query: { propagationPolicy: "Background" }
|
||||
}
|
||||
return super.delete(params)
|
||||
};
|
||||
return super.delete(params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<JsonApiData> {
|
||||
return apiBase.get("/kubeconfig/user");
|
||||
},
|
||||
|
||||
getServiceAccountConfig(account: string, namespace: string) {
|
||||
getServiceAccountConfig(account: string, namespace: string): CancelablePromise<JsonApiData> {
|
||||
return apiBase.get(`/kubeconfig/service-account/${namespace}/${account}`);
|
||||
},
|
||||
};
|
||||
@@ -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<T = IMetricsQuery>(query: T, reqParams: IMetricsReqParams = {}): Promise<T extends object ? { [K in keyof T]: IMetrics } : IMetrics> {
|
||||
async getMetrics<T = IMetricsQuery>(query: T, reqParams: MetricsReqParams = {}): Promise<T extends object ? { [K in keyof T]: Metrics } : Metrics> {
|
||||
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 => {
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Namespace extends KubeObject {
|
||||
phase: string;
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
getStatus(): string {
|
||||
return this.status ? this.status.phase : "-";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Node> {
|
||||
getMetrics(): Promise<INodeMetrics> {
|
||||
const opts = { category: "nodes"}
|
||||
getMetrics(): Promise<NodeMetrics> {
|
||||
const opts = { category: "nodes"};
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
memoryUsage: opts,
|
||||
@@ -18,7 +18,7 @@ export class NodesApi extends KubeApi<Node> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface INodeMetrics<T = IMetrics> {
|
||||
export interface NodeMetrics<T = Metrics> {
|
||||
[metric: string]: T;
|
||||
memoryUsage: T;
|
||||
memoryCapacity: T;
|
||||
@@ -28,6 +28,21 @@ export interface INodeMetrics<T = IMetrics> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PersistentVolumeClaim> {
|
||||
getMetrics(pvcName: string, namespace: string): Promise<IPvcMetrics> {
|
||||
getMetrics(pvcName: string, namespace: string): Promise<PvcMetrics> {
|
||||
return metricsApi.getMetrics({
|
||||
diskUsage: { category: 'pvc', pvc: pvcName },
|
||||
diskCapacity: { category: 'pvc', pvc: pvcName }
|
||||
@@ -15,7 +16,7 @@ export class PersistentVolumeClaimsApi extends KubeApi<PersistentVolumeClaim> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IPvcMetrics<T = IMetrics> {
|
||||
export interface PvcMetrics<T = Metrics> {
|
||||
[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 "-";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Pod> {
|
||||
async getLogs(params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> {
|
||||
async getLogs(params: { namespace: string; name: string }, query?: PodLogsQuery): Promise<string> {
|
||||
const path = this.getUrl(params) + "/log";
|
||||
return this.request.get(path, { query });
|
||||
}
|
||||
|
||||
getMetrics(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<IPodMetrics> {
|
||||
getMetrics(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<PodMetricsData> {
|
||||
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<Pod> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IPodMetrics<T = IMetrics> {
|
||||
export interface PodMetricsData<T = Metrics> {
|
||||
[metric: string]: T;
|
||||
cpuUsage: T;
|
||||
cpuRequests: T;
|
||||
@@ -42,7 +42,7 @@ export interface IPodMetrics<T = IMetrics> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 || "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(", ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -12,7 +12,7 @@ export class SelfSubjectRulesReviewApi extends KubeApi<SelfSubjectRulesReview> {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 || [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 || "-";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
|
||||
static reqInitDefault: RequestInit = {
|
||||
headers: {
|
||||
@@ -51,30 +66,30 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
|
||||
public onData = new EventEmitter<[D, Response]>();
|
||||
public onError = new EventEmitter<[JsonApiErrorParsed, Response]>();
|
||||
|
||||
get<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
|
||||
get<T = D>(path: string, params?: P, reqInit: RequestInit = {}): CancelablePromise<T> {
|
||||
return this.request<T>(path, params, { ...reqInit, method: "get" });
|
||||
}
|
||||
|
||||
post<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
|
||||
post<T = D>(path: string, params?: P, reqInit: RequestInit = {}): CancelablePromise<T> {
|
||||
return this.request<T>(path, params, { ...reqInit, method: "post" });
|
||||
}
|
||||
|
||||
put<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
|
||||
put<T = D>(path: string, params?: P, reqInit: RequestInit = {}): CancelablePromise<T> {
|
||||
return this.request<T>(path, params, { ...reqInit, method: "put" });
|
||||
}
|
||||
|
||||
patch<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
|
||||
patch<T = D>(path: string, params?: P, reqInit: RequestInit = {}): CancelablePromise<T> {
|
||||
return this.request<T>(path, params, { ...reqInit, method: "patch" });
|
||||
}
|
||||
|
||||
del<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
|
||||
del<T = D>(path: string, params?: P, reqInit: RequestInit = {}): CancelablePromise<T> {
|
||||
return this.request<T>(path, params, { ...reqInit, method: "delete" });
|
||||
}
|
||||
|
||||
protected request<D>(path: string, params?: P, init: RequestInit = {}) {
|
||||
protected request<D>(path: string, params?: P, init: RequestInit = {}): CancelablePromise<D> {
|
||||
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<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
@@ -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<T extends KubeObject> {
|
||||
export interface KubeApiOptions<T extends KubeObject> {
|
||||
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<T extends KubeObject> {
|
||||
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<T extends KubeObject = any> {
|
||||
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<T extends KubeObject = any> {
|
||||
*/
|
||||
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<T extends KubeObject = any> {
|
||||
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<T extends KubeObject = any> {
|
||||
};
|
||||
}
|
||||
|
||||
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<T extends KubeObject = any> {
|
||||
protected request: KubeJsonApi;
|
||||
protected resourceVersions = new Map<string, string>();
|
||||
|
||||
constructor(protected options: IKubeApiOptions<T>) {
|
||||
constructor(protected options: KubeApiOptions<T>) {
|
||||
const {
|
||||
kind,
|
||||
isNamespaced = false,
|
||||
@@ -169,19 +170,19 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
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<T[]> {
|
||||
return this.list(params, { limit: 1 });
|
||||
}
|
||||
|
||||
getUrl({ name = "", namespace = "" } = {}, query?: Partial<IKubeApiQueryParams>) {
|
||||
getUrl({ name = "", namespace = "" } = {}, query?: Partial<KubeApiQueryParams>): string {
|
||||
const { apiPrefix, apiVersionWithGroup, apiResource } = this;
|
||||
const resourcePath = KubeApi.createLink({
|
||||
apiPrefix: apiPrefix,
|
||||
@@ -208,7 +209,7 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
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<T extends KubeObject = any> {
|
||||
return data;
|
||||
}
|
||||
|
||||
async list({ namespace = "" } = {}, query?: IKubeApiQueryParams): Promise<T[]> {
|
||||
async list({ namespace = "" } = {}, query?: KubeApiQueryParams): Promise<T[]> {
|
||||
return this.request
|
||||
.get(this.getUrl({ namespace }), { query })
|
||||
.then(data => this.parseResponse(data, namespace));
|
||||
}
|
||||
|
||||
async get({ name = "", namespace = "default" } = {}, query?: IKubeApiQueryParams): Promise<T> {
|
||||
async get({ name = "", namespace = "default" } = {}, query?: KubeApiQueryParams): Promise<T> {
|
||||
return this.request
|
||||
.get(this.getUrl({ namespace, name }), { query })
|
||||
.then(this.parseResponse);
|
||||
@@ -252,20 +253,20 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
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<KubeJsonApiData> {
|
||||
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<T extends KubeObject = any> {
|
||||
}
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<T extends KubeObject = any> = (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<KubeObjectMetadata["ownerReferences"]> {
|
||||
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<T extends KubeObject>(data: Partial<T>) {
|
||||
async update<T extends KubeObject>(data: Partial<T>): Promise<T> {
|
||||
return resourceApplierApi.update<T>({
|
||||
...this.toPlainObject(),
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
delete(params?: JsonApiParams) {
|
||||
delete(params?: JsonApiParams): CancelablePromise<any> {
|
||||
return apiKube.del(this.selfLink, params);
|
||||
}
|
||||
}
|
||||
@@ -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<any>[] {
|
||||
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<void> {
|
||||
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<KubeJsonApiData>) => {
|
||||
addListener(store: KubeObjectStore, callback: (evt: IKubeWatchEvent) => void): () => void {
|
||||
const listener = (evt: IKubeWatchEvent<KubeJsonApiData>): 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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
|
||||
});
|
||||
|
||||
@@ -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<IParams> = {
|
||||
static defaultParams: Partial<Params> = {
|
||||
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<IParams>) {
|
||||
setParams(params: Partial<Params>): 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user