mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add eslint rule padding-line-between-statements (#1593)
Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>
This commit is contained in:
1 parent
7b77f18376
commit
dcf253e7d5
401 files changed
+2018
-40
No files matched your search
@@ -25,6 +25,7 @@ export class ApiManager {
|
||||
|
||||
protected resolveApi(api: string | KubeApi): KubeApi {
|
||||
if (typeof api === "string") return this.getApi(api);
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
@@ -33,6 +34,7 @@ export class ApiManager {
|
||||
else {
|
||||
const apis = Array.from(this.apis.entries());
|
||||
const entry = apis.find(entry => entry[1] === api);
|
||||
|
||||
if (entry) this.unregisterApi(entry[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ export class Cluster extends KubeObject {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ export class CustomResourceDefinition extends KubeObject {
|
||||
|
||||
getResourceApiBase() {
|
||||
const { group } = this.spec;
|
||||
|
||||
return `/apis/${group}/${this.getVersion()}/${this.getPluralName()}`;
|
||||
}
|
||||
|
||||
@@ -88,6 +89,7 @@ export class CustomResourceDefinition extends KubeObject {
|
||||
|
||||
getResourceTitle() {
|
||||
const name = this.getPluralName();
|
||||
|
||||
return name[0].toUpperCase() + name.substr(1);
|
||||
}
|
||||
|
||||
@@ -124,6 +126,7 @@ export class CustomResourceDefinition extends KubeObject {
|
||||
const columns = this.spec.versions.find(a => this.getVersion() == a.name)?.additionalPrinterColumns
|
||||
?? this.spec.additionalPrinterColumns?.map(({ JSONPath, ...rest }) => ({ ...rest, jsonPath: JSONPath })) // map to V1 shape
|
||||
?? [];
|
||||
|
||||
return columns
|
||||
.filter(column => column.name != "Age")
|
||||
.filter(column => ignorePriority ? true : !column.priority);
|
||||
@@ -135,8 +138,10 @@ export class CustomResourceDefinition extends KubeObject {
|
||||
|
||||
getConditions() {
|
||||
if (!this.status?.conditions) return [];
|
||||
|
||||
return this.status.conditions.map(condition => {
|
||||
const { message, reason, lastTransitionTime, status } = condition;
|
||||
|
||||
return {
|
||||
...condition,
|
||||
isReady: status === "True",
|
||||
|
||||
@@ -71,6 +71,7 @@ export class CronJob extends KubeObject {
|
||||
getLastScheduleTime() {
|
||||
if (!this.status.lastScheduleTime) return "-";
|
||||
const diff = moment().diff(this.status.lastScheduleTime);
|
||||
|
||||
return formatDuration(diff, true);
|
||||
}
|
||||
|
||||
@@ -84,7 +85,9 @@ export class CronJob extends KubeObject {
|
||||
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;
|
||||
|
||||
return day > daysInMonth[month - 1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ export class DaemonSet extends WorkloadKubeObject {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,10 +171,13 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,11 +73,13 @@ export class EndpointSubset implements IEndpointSubset {
|
||||
|
||||
getAddresses(): EndpointAddress[] {
|
||||
const addresses = this.addresses || [];
|
||||
|
||||
return addresses.map(a => new EndpointAddress(a));
|
||||
}
|
||||
|
||||
getNotReadyAddresses(): EndpointAddress[] {
|
||||
const notReadyAddresses = this.notReadyAddresses || [];
|
||||
|
||||
return notReadyAddresses.map(a => new EndpointAddress(a));
|
||||
}
|
||||
|
||||
@@ -85,10 +87,12 @@ export class EndpointSubset implements IEndpointSubset {
|
||||
if(!this.addresses) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return this.addresses.map(address => {
|
||||
if (!this.ports) {
|
||||
return address.ip;
|
||||
}
|
||||
|
||||
return this.ports.map(port => {
|
||||
return `${address.ip}:${port.port}`;
|
||||
}).join(", ");
|
||||
@@ -106,6 +110,7 @@ export class Endpoint extends KubeObject {
|
||||
|
||||
getEndpointSubsets(): EndpointSubset[] {
|
||||
const subsets = this.subsets || [];
|
||||
|
||||
return subsets.map(s => new EndpointSubset(s));
|
||||
}
|
||||
|
||||
|
||||
@@ -39,16 +39,19 @@ export class KubeEvent extends KubeObject {
|
||||
|
||||
getSource() {
|
||||
const { component, host } = this.source;
|
||||
|
||||
return `${component} ${host || ""}`;
|
||||
}
|
||||
|
||||
getFirstSeenTime() {
|
||||
const diff = moment().diff(this.firstTimestamp);
|
||||
|
||||
return formatDuration(diff, true);
|
||||
}
|
||||
|
||||
getLastSeenTime() {
|
||||
const diff = moment().diff(this.lastTimestamp);
|
||||
|
||||
return formatDuration(diff, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,13 @@ export const helmChartsApi = {
|
||||
|
||||
get(repo: string, name: string, readmeVersion?: string) {
|
||||
const path = endpoint({ repo, name });
|
||||
|
||||
return apiBase
|
||||
.get<IHelmChartDetails>(`${path}?${stringify({ version: readmeVersion })}`)
|
||||
.then(data => {
|
||||
const versions = data.versions.map(HelmChart.create);
|
||||
const readme = data.readme;
|
||||
|
||||
return {
|
||||
readme,
|
||||
versions,
|
||||
|
||||
@@ -76,9 +76,11 @@ export const helmReleasesApi = {
|
||||
|
||||
get(name: string, namespace: string) {
|
||||
const path = endpoint({ name, namespace });
|
||||
|
||||
return apiBase.get<IReleaseRawDetails>(path).then(details => {
|
||||
const items: KubeObject[] = JSON.parse(details.resources).items;
|
||||
const resources = items.map(item => KubeObject.create(item));
|
||||
|
||||
return {
|
||||
...details,
|
||||
resources
|
||||
@@ -88,35 +90,43 @@ export const helmReleasesApi = {
|
||||
|
||||
create(payload: IReleaseCreatePayload): Promise<IReleaseUpdateDetails> {
|
||||
const { repo, ...data } = payload;
|
||||
|
||||
data.chart = `${repo}/${data.chart}`;
|
||||
data.values = jsYaml.safeLoad(data.values);
|
||||
|
||||
return apiBase.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 apiBase.put(endpoint({ name, namespace }), { data });
|
||||
},
|
||||
|
||||
async delete(name: string, namespace: string) {
|
||||
const path = endpoint({ name, namespace });
|
||||
|
||||
return apiBase.del(path);
|
||||
},
|
||||
|
||||
getValues(name: string, namespace: string) {
|
||||
const path = `${endpoint({ name, namespace })}/values`;
|
||||
|
||||
return apiBase.get<string>(path);
|
||||
},
|
||||
|
||||
getHistory(name: string, namespace: string): Promise<IReleaseRevision[]> {
|
||||
const path = `${endpoint({ name, namespace })}/history`;
|
||||
|
||||
return apiBase.get(path);
|
||||
},
|
||||
|
||||
rollback(name: string, namespace: string, revision: number) {
|
||||
const path = `${endpoint({ name, namespace })}/rollback`;
|
||||
|
||||
return apiBase.put(path, {
|
||||
data: {
|
||||
revision
|
||||
@@ -157,10 +167,13 @@ export class HelmRelease implements ItemObject {
|
||||
|
||||
getChart(withVersion = false) {
|
||||
let chart = this.chart;
|
||||
|
||||
if(!withVersion && this.getVersion() != "" ) {
|
||||
const search = new RegExp(`-${this.getVersion()}`);
|
||||
|
||||
chart = chart.replace(search, "");
|
||||
}
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
@@ -174,6 +187,7 @@ export class HelmRelease implements ItemObject {
|
||||
|
||||
getVersion() {
|
||||
const versions = this.chart.match(/(v?\d+)[^-].*$/);
|
||||
|
||||
if (versions) {
|
||||
return versions[0];
|
||||
}
|
||||
@@ -187,9 +201,11 @@ export class HelmRelease implements ItemObject {
|
||||
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();
|
||||
const diff = now - updatedDate;
|
||||
|
||||
if (humanize) {
|
||||
return formatDuration(diff, compact);
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
@@ -200,6 +216,7 @@ export class HelmRelease implements ItemObject {
|
||||
const version = this.getVersion();
|
||||
const versions = await helmChartStore.getVersions(chartName);
|
||||
const chartVersion = versions.find(chartVersion => chartVersion.version === version);
|
||||
|
||||
return chartVersion ? chartVersion.repo : "";
|
||||
}
|
||||
}
|
||||
@@ -80,8 +80,10 @@ export class HorizontalPodAutoscaler extends KubeObject {
|
||||
|
||||
getConditions() {
|
||||
if (!this.status.conditions) return [];
|
||||
|
||||
return this.status.conditions.map(condition => {
|
||||
const { message, reason, lastTransitionTime, status } = condition;
|
||||
|
||||
return {
|
||||
...condition,
|
||||
isReady: status === "True",
|
||||
@@ -100,6 +102,7 @@ export class HorizontalPodAutoscaler extends KubeObject {
|
||||
|
||||
protected getMetricName(metric: IHpaMetric): string {
|
||||
const { type, resource, pods, object, external } = metric;
|
||||
|
||||
switch (type) {
|
||||
case HpaMetricType.Resource:
|
||||
return resource.name;
|
||||
@@ -122,14 +125,17 @@ export class HorizontalPodAutoscaler extends KubeObject {
|
||||
const target = metric[metricType];
|
||||
let currentValue = "unknown";
|
||||
let targetValue = "unknown";
|
||||
|
||||
if (current) {
|
||||
currentValue = current.currentAverageUtilization || current.currentAverageValue || current.currentValue;
|
||||
if (current.currentAverageUtilization) currentValue += "%";
|
||||
}
|
||||
|
||||
if (target) {
|
||||
targetValue = target.targetAverageUtilization || target.targetAverageValue || target.targetValue;
|
||||
if (target.targetAverageUtilization) targetValue += "%";
|
||||
}
|
||||
|
||||
return `${currentValue} / ${targetValue}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { KubeApi } from "../kube-api";
|
||||
export class IngressApi extends KubeApi<Ingress> {
|
||||
getMetrics(ingress: string, namespace: string): Promise<IIngressMetrics> {
|
||||
const opts = { category: "ingress", ingress };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
bytesSentSuccess: opts,
|
||||
bytesSentFailure: opts,
|
||||
@@ -98,15 +99,18 @@ export class Ingress extends KubeObject {
|
||||
|
||||
getRoutes() {
|
||||
const { spec: { tls, rules } } = this;
|
||||
|
||||
if (!rules) return [];
|
||||
|
||||
let protocol = "http";
|
||||
const routes: string[] = [];
|
||||
|
||||
if (tls && tls.length > 0) {
|
||||
protocol += "s";
|
||||
}
|
||||
rules.map(rule => {
|
||||
const host = rule.host ? rule.host : "*";
|
||||
|
||||
if (rule.http && rule.http.paths) {
|
||||
rule.http.paths.forEach(path => {
|
||||
const { serviceName, servicePort } = getBackendServiceNamePort(path.backend);
|
||||
@@ -132,7 +136,9 @@ export class Ingress extends KubeObject {
|
||||
|
||||
getHosts() {
|
||||
const { spec: { rules } } = this;
|
||||
|
||||
if (!rules) return [];
|
||||
|
||||
return rules.filter(rule => rule.host).map(rule => rule.host);
|
||||
}
|
||||
|
||||
@@ -141,7 +147,6 @@ export class Ingress extends KubeObject {
|
||||
const { spec: { tls, rules, backend, defaultBackend } } = this;
|
||||
const httpPort = 80;
|
||||
const tlsPort = 443;
|
||||
|
||||
// Note: not using the port name (string)
|
||||
const servicePort = defaultBackend?.service.port.number ?? backend?.servicePort;
|
||||
|
||||
@@ -152,6 +157,7 @@ export class Ingress extends KubeObject {
|
||||
} else if (servicePort !== undefined) {
|
||||
ports.push(Number(servicePort));
|
||||
}
|
||||
|
||||
if (tls && tls.length > 0) {
|
||||
ports.push(tlsPort);
|
||||
}
|
||||
|
||||
@@ -86,12 +86,15 @@ export class Job extends WorkloadKubeObject {
|
||||
// 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");
|
||||
}
|
||||
|
||||
getImages() {
|
||||
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []);
|
||||
|
||||
return [...containers].map(container => container.image);
|
||||
}
|
||||
|
||||
@@ -99,6 +102,7 @@ export class Job extends WorkloadKubeObject {
|
||||
const params: JsonApiParams = {
|
||||
query: { propagationPolicy: "Background" }
|
||||
};
|
||||
|
||||
return super.delete(params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export const metricsApi = {
|
||||
if (!start && !end) {
|
||||
const timeNow = Date.now() / 1000;
|
||||
const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes
|
||||
|
||||
start = now - range;
|
||||
end = now;
|
||||
}
|
||||
@@ -76,8 +77,10 @@ export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
|
||||
// fill the gaps
|
||||
result.forEach(res => {
|
||||
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"]);
|
||||
}
|
||||
});
|
||||
@@ -101,14 +104,17 @@ export function isMetricsEmpty(metrics: { [key: string]: IMetrics }) {
|
||||
export function getItemMetrics(metrics: { [key: string]: IMetrics }, itemName: string): { [key: string]: IMetrics } {
|
||||
if (!metrics) return;
|
||||
const itemMetrics = { ...metrics };
|
||||
|
||||
for (const metric in metrics) {
|
||||
if (!metrics[metric]?.data?.result) {
|
||||
continue;
|
||||
}
|
||||
const results = metrics[metric].data.result;
|
||||
const result = results.find(res => Object.values(res.metric)[0] == itemName);
|
||||
|
||||
itemMetrics[metric].data.result = result ? [result] : [];
|
||||
}
|
||||
|
||||
return itemMetrics;
|
||||
}
|
||||
|
||||
@@ -118,11 +124,13 @@ export function getMetricLastPoints(metrics: { [key: string]: IMetrics }) {
|
||||
Object.keys(metrics).forEach(metricName => {
|
||||
try {
|
||||
const metric = metrics[metricName];
|
||||
|
||||
if (metric.data.result.length) {
|
||||
result[metricName] = +metric.data.result[0].values.slice(-1)[0][1];
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ export class NetworkPolicy extends KubeObject {
|
||||
|
||||
getMatchLabels(): string[] {
|
||||
if (!this.spec.podSelector || !this.spec.podSelector.matchLabels) return [];
|
||||
|
||||
return Object
|
||||
.entries(this.spec.podSelector.matchLabels)
|
||||
.map(data => data.join(":"));
|
||||
@@ -62,6 +63,7 @@ export class NetworkPolicy extends KubeObject {
|
||||
|
||||
getTypes(): string[] {
|
||||
if (!this.spec.policyTypes) return [];
|
||||
|
||||
return this.spec.policyTypes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,9 +87,12 @@ 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}`;
|
||||
}, "");
|
||||
}
|
||||
@@ -112,19 +115,23 @@ export class Node extends KubeObject {
|
||||
|
||||
getCpuCapacity() {
|
||||
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);
|
||||
}
|
||||
|
||||
getConditions() {
|
||||
const conditions = this.status.conditions || [];
|
||||
|
||||
if (this.isUnschedulable()) {
|
||||
return [{ type: "SchedulingDisabled", status: "True" }, ...conditions];
|
||||
}
|
||||
|
||||
return conditions;
|
||||
}
|
||||
|
||||
@@ -134,6 +141,7 @@ export class Node extends KubeObject {
|
||||
|
||||
getWarningConditions() {
|
||||
const goodConditions = ["Ready", "HostUpgrades", "SchedulingDisabled"];
|
||||
|
||||
return this.getActiveConditions().filter(condition => {
|
||||
return !goodConditions.includes(condition.type);
|
||||
});
|
||||
@@ -145,6 +153,7 @@ export class Node extends KubeObject {
|
||||
|
||||
getOperatingSystem(): string {
|
||||
const label = this.getLabels().find(label => label.startsWith("kubernetes.io/os="));
|
||||
|
||||
if (label) {
|
||||
return label.split("=", 2)[1];
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ export class PersistentVolumeClaim extends KubeObject {
|
||||
|
||||
getPods(allPods: Pod[]): Pod[] {
|
||||
const pods = allPods.filter(pod => pod.getNs() === this.getNs());
|
||||
|
||||
return pods.filter(pod => {
|
||||
return pod.getVolumes().filter(volume =>
|
||||
volume.persistentVolumeClaim &&
|
||||
@@ -62,22 +63,26 @@ export class PersistentVolumeClaim extends KubeObject {
|
||||
|
||||
getStorage(): string {
|
||||
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 [];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
getStatus(): string {
|
||||
if (this.status) return this.status.phase;
|
||||
|
||||
return "-";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,20 +47,25 @@ export class PersistentVolume extends KubeObject {
|
||||
|
||||
getCapacity(inBytes = false) {
|
||||
const capacity = this.spec.capacity;
|
||||
|
||||
if (capacity) {
|
||||
if (inBytes) return unitsToBytes(capacity.storage);
|
||||
|
||||
return capacity.storage;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
if (!this.status) return;
|
||||
|
||||
return this.status.phase || "-";
|
||||
}
|
||||
|
||||
getClaimRefName() {
|
||||
const { claimRef } = this.spec;
|
||||
|
||||
return claimRef ? claimRef.name : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export class PodDisruptionBudget extends KubeObject {
|
||||
|
||||
getSelectors() {
|
||||
const selector = this.spec.selector;
|
||||
|
||||
return KubeObject.stringifyLabels(selector ? selector.matchLabels : null);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { KubeApi } from "../kube-api";
|
||||
export class PodsApi extends KubeApi<Pod> {
|
||||
async getLogs(params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> {
|
||||
const path = `${this.getUrl(params)}/log`;
|
||||
|
||||
return this.request.get(path, { query });
|
||||
}
|
||||
|
||||
@@ -247,6 +248,7 @@ export class Pod extends WorkloadKubeObject {
|
||||
|
||||
getRunningContainers() {
|
||||
const statuses = this.getContainerStatuses();
|
||||
|
||||
return this.getAllContainers().filter(container => {
|
||||
return statuses.find(status => status.name === container.name && !!status.state["running"]);
|
||||
}
|
||||
@@ -256,18 +258,23 @@ export class Pod extends WorkloadKubeObject {
|
||||
getContainerStatuses(includeInitContainers = true) {
|
||||
const statuses: IPodContainerStatus[] = [];
|
||||
const { containerStatuses, initContainerStatuses } = this.status;
|
||||
|
||||
if (containerStatuses) {
|
||||
statuses.push(...containerStatuses);
|
||||
}
|
||||
|
||||
if (includeInitContainers && initContainerStatuses) {
|
||||
statuses.push(...initContainerStatuses);
|
||||
}
|
||||
|
||||
return statuses;
|
||||
}
|
||||
|
||||
getRestartsCount(): number {
|
||||
const { containerStatuses } = this.status;
|
||||
|
||||
if (!containerStatuses) return 0;
|
||||
|
||||
return containerStatuses.reduce((count, item) => count + item.restartCount, 0);
|
||||
}
|
||||
|
||||
@@ -290,18 +297,23 @@ export class Pod extends WorkloadKubeObject {
|
||||
const goodConditions = ["Initialized", "Ready"].every(condition =>
|
||||
!!this.getConditions().find(item => item.type === condition && item.status === "True")
|
||||
);
|
||||
|
||||
if (reason === PodStatus.EVICTED) {
|
||||
return PodStatus.EVICTED;
|
||||
}
|
||||
|
||||
if (phase === PodStatus.FAILED) {
|
||||
return PodStatus.FAILED;
|
||||
}
|
||||
|
||||
if (phase === PodStatus.SUCCEEDED) {
|
||||
return PodStatus.SUCCEEDED;
|
||||
}
|
||||
|
||||
if (phase === PodStatus.RUNNING && goodConditions) {
|
||||
return PodStatus.RUNNING;
|
||||
}
|
||||
|
||||
return PodStatus.PENDING;
|
||||
}
|
||||
|
||||
@@ -312,20 +324,26 @@ export class Pod extends WorkloadKubeObject {
|
||||
|
||||
let message = "";
|
||||
const statuses = this.getContainerStatuses(false); // not including initContainers
|
||||
|
||||
if (statuses.length) {
|
||||
statuses.forEach(status => {
|
||||
const { state } = status;
|
||||
|
||||
if (state.waiting) {
|
||||
const { reason } = state.waiting;
|
||||
|
||||
message = reason ? reason : "Waiting";
|
||||
}
|
||||
|
||||
if (state.terminated) {
|
||||
const { reason } = state.terminated;
|
||||
|
||||
message = reason ? reason : "Terminated";
|
||||
}
|
||||
});
|
||||
}
|
||||
if (message) return message;
|
||||
|
||||
return this.getStatusPhase();
|
||||
}
|
||||
|
||||
@@ -349,7 +367,9 @@ export class Pod extends WorkloadKubeObject {
|
||||
|
||||
getNodeSelectors(): string[] {
|
||||
const { nodeSelector } = this.spec;
|
||||
|
||||
if (!nodeSelector) return [];
|
||||
|
||||
return Object.entries(nodeSelector).map(values => values.join(": "));
|
||||
}
|
||||
|
||||
@@ -367,8 +387,10 @@ export class Pod extends WorkloadKubeObject {
|
||||
});
|
||||
const crashLoop = !!this.getContainerStatuses().find(condition => {
|
||||
const waiting = condition.state.waiting;
|
||||
|
||||
return (waiting && waiting.reason == "CrashLoopBackOff");
|
||||
});
|
||||
|
||||
return (
|
||||
notReady ||
|
||||
crashLoop ||
|
||||
@@ -391,18 +413,22 @@ export class Pod extends WorkloadKubeObject {
|
||||
periodSeconds, successThreshold, failureThreshold
|
||||
} = probeData;
|
||||
const probe = [];
|
||||
|
||||
// HTTP Request
|
||||
if (httpGet) {
|
||||
const { path, port, host, scheme } = httpGet;
|
||||
|
||||
probe.push(
|
||||
"http-get",
|
||||
`${scheme.toLowerCase()}://${host || ""}:${port || ""}${path || ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Command
|
||||
if (exec && exec.command) {
|
||||
probe.push(`exec [${exec.command.join(" ")}]`);
|
||||
}
|
||||
|
||||
// TCP Probe
|
||||
if (tcpSocket && tcpSocket.port) {
|
||||
probe.push(`tcp-socket :${tcpSocket.port}`);
|
||||
@@ -414,6 +440,7 @@ export class Pod extends WorkloadKubeObject {
|
||||
`#success=${successThreshold || "0"}`,
|
||||
`#failure=${failureThreshold || "0"}`,
|
||||
);
|
||||
|
||||
return probe;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ export class PodSecurityPolicy extends KubeObject {
|
||||
|
||||
getRules() {
|
||||
const { fsGroup, runAsGroup, runAsUser, supplementalGroups, seLinux } = this.spec;
|
||||
|
||||
return {
|
||||
fsGroup: fsGroup ? fsGroup.rule : "",
|
||||
runAsGroup: runAsGroup ? runAsGroup.rule : "",
|
||||
|
||||
@@ -48,6 +48,7 @@ export class ReplicaSet extends WorkloadKubeObject {
|
||||
|
||||
getImages() {
|
||||
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []);
|
||||
|
||||
return [...containers].map(container => container.image);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,20 @@ export const resourceApplierApi = {
|
||||
if (typeof resource === "string") {
|
||||
resource = jsYaml.safeLoad(resource);
|
||||
}
|
||||
|
||||
return apiBase
|
||||
.post<KubeJsonApiData[]>("/stack", { data: resource })
|
||||
.then(data => {
|
||||
const items = data.map(obj => {
|
||||
const api = apiManager.getApi(obj.metadata.selfLink);
|
||||
|
||||
if (api) {
|
||||
return new api.objectConstructor(obj);
|
||||
} else {
|
||||
return new KubeObject(obj);
|
||||
}
|
||||
});
|
||||
|
||||
return items.length === 1 ? items[0] : items;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export class ResourceQuota extends KubeObject {
|
||||
|
||||
getScopeSelector() {
|
||||
const { matchExpressions = [] } = this.spec.scopeSelector || {};
|
||||
|
||||
return matchExpressions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,16 +38,19 @@ export class SelfSubjectRulesReview extends KubeObject {
|
||||
|
||||
getResourceRules() {
|
||||
const rules = this.status && this.status.resourceRules || [];
|
||||
|
||||
return rules.map(rule => this.normalize(rule));
|
||||
}
|
||||
|
||||
getNonResourceRules() {
|
||||
const rules = this.status && this.status.nonResourceRules || [];
|
||||
|
||||
return rules.map(rule => this.normalize(rule));
|
||||
}
|
||||
|
||||
protected normalize(rule: ISelfSubjectReviewRule): ISelfSubjectReviewRule {
|
||||
const { apiGroups = [], resourceNames = [], verbs = [], nonResourceURLs = [], resources = [] } = rule;
|
||||
|
||||
return {
|
||||
apiGroups,
|
||||
nonResourceURLs,
|
||||
@@ -56,6 +59,7 @@ export class SelfSubjectRulesReview extends KubeObject {
|
||||
resources: resources.map((resource, index) => {
|
||||
const apiGroup = apiGroups.length >= index + 1 ? apiGroups[index] : apiGroups.slice(-1)[0];
|
||||
const separator = apiGroup == "" ? "" : ".";
|
||||
|
||||
return resource + separator + apiGroup;
|
||||
})
|
||||
};
|
||||
|
||||
@@ -61,9 +61,11 @@ export class Service extends KubeObject {
|
||||
|
||||
getExternalIps() {
|
||||
const lb = this.getLoadBalancer();
|
||||
|
||||
if (lb && lb.ingress) {
|
||||
return lb.ingress.map(val => val.ip || val.hostname);
|
||||
}
|
||||
|
||||
return this.spec.externalIPs || [];
|
||||
}
|
||||
|
||||
@@ -73,11 +75,13 @@ export class Service extends KubeObject {
|
||||
|
||||
getSelector(): string[] {
|
||||
if (!this.spec.selector) return [];
|
||||
|
||||
return Object.entries(this.spec.selector).map(val => val.join("="));
|
||||
}
|
||||
|
||||
getPorts(): ServicePort[] {
|
||||
const ports = this.spec.ports || [];
|
||||
|
||||
return ports.map(p => new ServicePort(p));
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ export class StatefulSet extends WorkloadKubeObject {
|
||||
|
||||
getImages() {
|
||||
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []);
|
||||
|
||||
return [...containers].map(container => container.image);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export class StorageClass extends KubeObject {
|
||||
|
||||
isDefault() {
|
||||
const annotations = this.metadata.annotations || {};
|
||||
|
||||
return (
|
||||
annotations["storageclass.kubernetes.io/is-default-class"] === "true" ||
|
||||
annotations["storageclass.beta.kubernetes.io/is-default-class"] === "true"
|
||||
|
||||
@@ -75,11 +75,14 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
|
||||
let reqUrl = this.config.apiBase + path;
|
||||
const reqInit: RequestInit = { ...this.reqInit, ...init };
|
||||
const { data, query } = params || {} as P;
|
||||
|
||||
if (data && !reqInit.body) {
|
||||
reqInit.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
if (query) {
|
||||
const queryString = stringify(query);
|
||||
|
||||
reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString;
|
||||
}
|
||||
const infoLog: JsonApiLog = {
|
||||
@@ -87,6 +90,7 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
|
||||
reqUrl,
|
||||
reqInit,
|
||||
};
|
||||
|
||||
return cancelableFetch(reqUrl, reqInit).then(res => {
|
||||
return this.parseResponse<D>(res, infoLog);
|
||||
});
|
||||
@@ -94,21 +98,26 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
|
||||
|
||||
protected parseResponse<D>(res: Response, log: JsonApiLog): Promise<D> {
|
||||
const { status } = res;
|
||||
|
||||
return res.text().then(text => {
|
||||
let data;
|
||||
|
||||
try {
|
||||
data = text ? JSON.parse(text) : ""; // DELETE-requests might not have response-body
|
||||
} catch (e) {
|
||||
data = text;
|
||||
}
|
||||
|
||||
if (status >= 200 && status < 300) {
|
||||
this.onData.emit(data, res);
|
||||
this.writeLog({ ...log, data });
|
||||
|
||||
return data;
|
||||
} else if (log.method === "GET" && res.status === 403) {
|
||||
this.writeLog({ ...log, data });
|
||||
} else {
|
||||
const error = new JsonApiErrorParsed(data, this.parseError(data, res));
|
||||
|
||||
this.onError.emit(error, res);
|
||||
this.writeLog({ ...log, error });
|
||||
throw error;
|
||||
@@ -126,6 +135,7 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
|
||||
else if (error.message) {
|
||||
return [error.message];
|
||||
}
|
||||
|
||||
return [res.statusText || "Error!"];
|
||||
}
|
||||
|
||||
@@ -133,6 +143,7 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
|
||||
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;";
|
||||
console.log(`%c${method} ${reqUrl}`, textStyle, params);
|
||||
|
||||
@@ -29,7 +29,6 @@ export function parseKubeApi(path: string): IKubeApiParsed {
|
||||
path = new URL(path, location.origin).pathname;
|
||||
const [, prefix, ...parts] = path.split("/");
|
||||
const apiPrefix = `/${prefix}`;
|
||||
|
||||
const [left, right, namespaced] = splitArray(parts, "namespaces");
|
||||
let apiGroup, apiVersion, namespace, resource, name;
|
||||
|
||||
@@ -107,9 +106,11 @@ export function parseKubeApi(path: string): IKubeApiParsed {
|
||||
export function createKubeApiURL(ref: IKubeApiLinkRef): string {
|
||||
const { apiPrefix = "/apis", resource, apiVersion, name } = ref;
|
||||
let { namespace } = ref;
|
||||
|
||||
if (namespace) {
|
||||
namespace = `namespaces/${namespace}`;
|
||||
}
|
||||
|
||||
return [apiPrefix, apiVersion, namespace, resource, name]
|
||||
.filter(v => v)
|
||||
.join("/");
|
||||
@@ -125,6 +126,7 @@ export function lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): st
|
||||
|
||||
// search in registered apis by 'kind' & 'apiVersion'
|
||||
const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion);
|
||||
|
||||
if (api) {
|
||||
return api.getUrl({ namespace, name });
|
||||
}
|
||||
@@ -132,8 +134,10 @@ export function lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): st
|
||||
// lookup api by generated resource link
|
||||
const apiPrefixes = ["/apis", "/api"];
|
||||
const resource = kind.endsWith("s") ? `${kind.toLowerCase()}es` : `${kind.toLowerCase()}s`;
|
||||
|
||||
for (const apiPrefix of apiPrefixes) {
|
||||
const apiLink = createKubeApiURL({ apiPrefix, apiVersion, name, namespace, resource });
|
||||
|
||||
if (apiManager.getApi(apiLink)) {
|
||||
return apiLink;
|
||||
}
|
||||
@@ -141,6 +145,7 @@ 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 });
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ export function forCluster<T extends KubeObject>(cluster: IKubeApiCluster, kubeC
|
||||
"X-Cluster-ID": cluster.id
|
||||
}
|
||||
});
|
||||
|
||||
return new KubeApi({
|
||||
objectConstructor: kubeClass,
|
||||
request
|
||||
@@ -83,6 +84,7 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
|
||||
static watchAll(...apis: KubeApi[]) {
|
||||
const disposers = apis.map(api => api.watch());
|
||||
|
||||
return () => disposers.forEach(unwatch => unwatch());
|
||||
}
|
||||
|
||||
@@ -106,6 +108,7 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
kind = options.objectConstructor?.kind,
|
||||
isNamespaced = options.objectConstructor?.namespaced
|
||||
} = options || {};
|
||||
|
||||
if (!options.apiBase) {
|
||||
options.apiBase = objectConstructor.apiBase;
|
||||
}
|
||||
@@ -205,6 +208,7 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
});
|
||||
|
||||
const res = await this.request.get<IKubePreferredVersion>(`${this.apiPrefix}/${this.apiGroup}`);
|
||||
|
||||
Object.defineProperty(this, "apiVersionPreferred", {
|
||||
value: res?.preferredVersion?.version ?? null,
|
||||
});
|
||||
@@ -236,6 +240,7 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
namespace: this.isNamespaced ? namespace : undefined,
|
||||
name,
|
||||
});
|
||||
|
||||
return resourcePath + (query ? `?${stringify(this.normalizeQuery(query))}` : "");
|
||||
}
|
||||
|
||||
@@ -243,14 +248,17 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
if (query.labelSelector) {
|
||||
query.labelSelector = [query.labelSelector].flat().join(",");
|
||||
}
|
||||
|
||||
if (query.fieldSelector) {
|
||||
query.fieldSelector = [query.fieldSelector].flat().join(",");
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
protected parseResponse(data: KubeJsonApiData | KubeJsonApiData[] | KubeJsonApiDataList, namespace?: string): any {
|
||||
const KubeObjectConstructor = this.objectConstructor;
|
||||
|
||||
if (KubeObject.isJsonApiData(data)) {
|
||||
return new KubeObjectConstructor(data);
|
||||
}
|
||||
@@ -258,8 +266,10 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
// process items list response
|
||||
if (KubeObject.isJsonApiDataList(data)) {
|
||||
const { apiVersion, items, metadata } = data;
|
||||
|
||||
this.setResourceVersion(namespace, metadata.resourceVersion);
|
||||
this.setResourceVersion("", metadata.resourceVersion);
|
||||
|
||||
return items.map(item => new KubeObjectConstructor({
|
||||
kind: this.kind,
|
||||
apiVersion,
|
||||
@@ -277,6 +287,7 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
|
||||
async list({ namespace = "" } = {}, query?: IKubeApiQueryParams): Promise<T[]> {
|
||||
await this.checkPreferredVersion();
|
||||
|
||||
return this.request
|
||||
.get(this.getUrl({ namespace }), { query })
|
||||
.then(data => this.parseResponse(data, namespace));
|
||||
@@ -284,6 +295,7 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
|
||||
async get({ name = "", namespace = "default" } = {}, query?: IKubeApiQueryParams): Promise<T> {
|
||||
await this.checkPreferredVersion();
|
||||
|
||||
return this.request
|
||||
.get(this.getUrl({ namespace, name }), { query })
|
||||
.then(this.parseResponse);
|
||||
@@ -310,6 +322,7 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
async update({ name = "", namespace = "default" } = {}, data?: Partial<T>): Promise<T> {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.getUrl({ namespace, name });
|
||||
|
||||
return this.request
|
||||
.put(apiUrl, { data })
|
||||
.then(this.parseResponse);
|
||||
@@ -318,6 +331,7 @@ export class KubeApi<T extends KubeObject = any> {
|
||||
async delete({ name = "", namespace = "default" }) {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.getUrl({ namespace, name });
|
||||
|
||||
return this.request.del(apiUrl);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,11 @@ export interface KubeJsonApiError extends JsonApiError {
|
||||
export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
|
||||
protected parseError(error: KubeJsonApiError | any, res: Response): string[] {
|
||||
const { status, reason, message } = error;
|
||||
|
||||
if (status && reason) {
|
||||
return [message || `${status}: ${reason}`];
|
||||
}
|
||||
|
||||
return super.parseError(error, res);
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,7 @@ export class KubeObject implements ItemObject {
|
||||
|
||||
static stringifyLabels(labels: { [name: string]: string }): string[] {
|
||||
if (!labels) return [];
|
||||
|
||||
return Object.entries(labels).map(([name, value]) => `${name}=${value}`);
|
||||
}
|
||||
|
||||
@@ -104,9 +105,11 @@ export class KubeObject implements ItemObject {
|
||||
return moment(this.metadata.creationTimestamp).fromNow();
|
||||
}
|
||||
const diff = new Date().getTime() - new Date(this.metadata.creationTimestamp).getTime();
|
||||
|
||||
if (humanize) {
|
||||
return formatDuration(diff, compact);
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
@@ -120,14 +123,17 @@ export class KubeObject implements ItemObject {
|
||||
|
||||
getAnnotations(filter = false): string[] {
|
||||
const labels = KubeObject.stringifyLabels(this.metadata.annotations);
|
||||
|
||||
return filter ? labels.filter(label => {
|
||||
const skip = resourceApplierApi.annotations.some(key => label.startsWith(key));
|
||||
|
||||
return !skip;
|
||||
}) : labels;
|
||||
}
|
||||
|
||||
getOwnerRefs() {
|
||||
const refs = this.metadata.ownerReferences || [];
|
||||
|
||||
return refs.map(ownerRef => ({
|
||||
...ownerRef,
|
||||
namespace: this.getNs(),
|
||||
@@ -136,6 +142,7 @@ export class KubeObject implements ItemObject {
|
||||
|
||||
getSearchFields() {
|
||||
const { getName, getId, getNs, getAnnotations, getLabels } = this;
|
||||
|
||||
return [
|
||||
getName(),
|
||||
getNs(),
|
||||
|
||||
@@ -53,8 +53,10 @@ export class KubeWatchApi {
|
||||
apis.forEach(api => {
|
||||
this.subscribers.set(api, this.getSubscribersCount(api) + 1);
|
||||
});
|
||||
|
||||
return () => apis.forEach(api => {
|
||||
const count = this.getSubscribersCount(api) - 1;
|
||||
|
||||
if (count <= 0) this.subscribers.delete(api);
|
||||
else this.subscribers.set(api, count);
|
||||
});
|
||||
@@ -62,9 +64,11 @@ export class KubeWatchApi {
|
||||
|
||||
protected getQuery(): Partial<IKubeWatchRouteQuery> {
|
||||
const { isAdmin, allowedNamespaces } = getHostedCluster();
|
||||
|
||||
return {
|
||||
api: this.activeApis.map(api => {
|
||||
if (isAdmin) return api.getWatchUrl();
|
||||
|
||||
return allowedNamespaces.map(namespace => api.getWatchUrl(namespace));
|
||||
}).flat()
|
||||
};
|
||||
@@ -74,11 +78,13 @@ export class KubeWatchApi {
|
||||
@autobind()
|
||||
protected connect() {
|
||||
if (this.evtSource) this.disconnect(); // close previous connection
|
||||
|
||||
if (!this.activeApis.length) {
|
||||
return;
|
||||
}
|
||||
const query = this.getQuery();
|
||||
const apiUrl = `${apiPrefix}/watch?${stringify(query)}`;
|
||||
|
||||
this.evtSource = new EventSource(apiUrl);
|
||||
this.evtSource.onmessage = this.onMessage;
|
||||
this.evtSource.onerror = this.onError;
|
||||
@@ -102,6 +108,7 @@ export class KubeWatchApi {
|
||||
protected onMessage(evt: MessageEvent) {
|
||||
if (!evt.data) return;
|
||||
const data = JSON.parse(evt.data);
|
||||
|
||||
if ((data as IKubeWatchEvent).object) {
|
||||
this.onData.emit(data);
|
||||
} else {
|
||||
@@ -114,12 +121,14 @@ export class KubeWatchApi {
|
||||
this.disconnect();
|
||||
const { apiBase, namespace } = KubeApi.parseApi(event.url);
|
||||
const api = apiManager.getApi(apiBase);
|
||||
|
||||
if (api) {
|
||||
try {
|
||||
await api.refreshResourceVersion({ namespace });
|
||||
this.reconnect();
|
||||
} catch (error) {
|
||||
console.error("failed to refresh resource version", error);
|
||||
|
||||
if (this.subscribers.size > 0) {
|
||||
setTimeout(() => {
|
||||
this.onRouteEvent(event);
|
||||
@@ -132,6 +141,7 @@ export class KubeWatchApi {
|
||||
|
||||
protected onError(evt: MessageEvent) {
|
||||
const { reconnectAttempts: attemptsRemain, reconnectTimeoutMs } = this;
|
||||
|
||||
if (evt.eventPhase === EventSource.CLOSED) {
|
||||
if (attemptsRemain > 0) {
|
||||
this.reconnectAttempts--;
|
||||
@@ -150,13 +160,17 @@ export class KubeWatchApi {
|
||||
const listener = (evt: IKubeWatchEvent<KubeJsonApiData>) => {
|
||||
const { selfLink, namespace, resourceVersion } = evt.object.metadata;
|
||||
const api = apiManager.getApi(selfLink);
|
||||
|
||||
api.setResourceVersion(namespace, resourceVersion);
|
||||
api.setResourceVersion("", resourceVersion);
|
||||
|
||||
if (store == apiManager.getStore(api)) {
|
||||
callback(evt);
|
||||
}
|
||||
};
|
||||
|
||||
this.onData.addListener(listener);
|
||||
|
||||
return () => this.onData.removeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,26 +50,32 @@ export class TerminalApi extends WebSocketApi {
|
||||
const { id, node } = this.options;
|
||||
const wss = `ws${protocol === "https:" ? "s" : ""}://`;
|
||||
const query: TerminalApiQuery = { id };
|
||||
|
||||
if (port) {
|
||||
port = `:${port}`;
|
||||
}
|
||||
|
||||
if (node) {
|
||||
query.node = node;
|
||||
query.type = "node";
|
||||
}
|
||||
|
||||
return `${wss}${hostname}${port}/api?${stringify(query)}`;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
const apiUrl = await this.getUrl();
|
||||
|
||||
this.emitStatus("Connecting ...");
|
||||
this.onData.addListener(this._onReady, { prepend: true });
|
||||
|
||||
return super.connect(apiUrl);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (!this.socket) return;
|
||||
const exitCode = String.fromCharCode(4); // ctrl+d
|
||||
|
||||
this.sendCommand(exitCode);
|
||||
setTimeout(() => super.destroy(), 2000);
|
||||
}
|
||||
@@ -87,6 +93,7 @@ export class TerminalApi extends WebSocketApi {
|
||||
this.onData.removeListener(this._onReady);
|
||||
this.flush();
|
||||
this.onData.emit(data); // re-emit data
|
||||
|
||||
return false; // prevent calling rest of listeners
|
||||
}
|
||||
|
||||
@@ -100,6 +107,7 @@ export class TerminalApi extends WebSocketApi {
|
||||
|
||||
sendTerminalSize(cols: number, rows: number) {
|
||||
const newSize = { Width: cols, Height: rows };
|
||||
|
||||
if (!isEqual(this.size, newSize)) {
|
||||
this.sendCommand(JSON.stringify(newSize), TerminalChannels.TERMINAL_SIZE);
|
||||
this.size = newSize;
|
||||
@@ -108,6 +116,7 @@ export class TerminalApi extends WebSocketApi {
|
||||
|
||||
protected parseMessage(data: string) {
|
||||
data = data.substr(1); // skip channel
|
||||
|
||||
return base64.decode(data);
|
||||
}
|
||||
|
||||
@@ -125,10 +134,12 @@ export class TerminalApi extends WebSocketApi {
|
||||
|
||||
protected emitStatus(data: string, options: { color?: TerminalColor; showTime?: boolean } = {}) {
|
||||
const { color, showTime } = options;
|
||||
|
||||
if (color) {
|
||||
data = `${color}${data}${TerminalColor.NO_COLOR}`;
|
||||
}
|
||||
let time;
|
||||
|
||||
if (showTime) {
|
||||
time = `${(new Date()).toLocaleString()} `;
|
||||
}
|
||||
|
||||
@@ -47,9 +47,11 @@ export class WebSocketApi {
|
||||
constructor(protected params: IParams) {
|
||||
this.params = Object.assign({}, WebSocketApi.defaultParams, params);
|
||||
const { autoConnect, pingIntervalSeconds } = this.params;
|
||||
|
||||
if (autoConnect) {
|
||||
setTimeout(() => this.connect());
|
||||
}
|
||||
|
||||
if (pingIntervalSeconds) {
|
||||
this.pingTimer = setInterval(() => this.ping(), pingIntervalSeconds * 1000);
|
||||
}
|
||||
@@ -57,6 +59,7 @@ export class WebSocketApi {
|
||||
|
||||
get isConnected() {
|
||||
const state = this.socket ? this.socket.readyState : -1;
|
||||
|
||||
return state === WebSocket.OPEN && this.isOnline;
|
||||
}
|
||||
|
||||
@@ -87,6 +90,7 @@ export class WebSocketApi {
|
||||
|
||||
reconnect() {
|
||||
const { reconnectDelaySeconds } = this.params;
|
||||
|
||||
if (!reconnectDelaySeconds) return;
|
||||
this.writeLog("reconnect after", `${reconnectDelaySeconds}ms`);
|
||||
this.reconnectTimer = setTimeout(() => this.connect(), reconnectDelaySeconds * 1000);
|
||||
@@ -115,6 +119,7 @@ export class WebSocketApi {
|
||||
id: (Math.random() * Date.now()).toString(16).replace(".", ""),
|
||||
data: command,
|
||||
};
|
||||
|
||||
if (this.isConnected) {
|
||||
this.socket.send(msg.data);
|
||||
}
|
||||
@@ -141,6 +146,7 @@ export class WebSocketApi {
|
||||
|
||||
protected _onMessage(evt: MessageEvent) {
|
||||
const data = this.parseMessage(evt.data);
|
||||
|
||||
this.onData.emit(data);
|
||||
this.writeLog("%cMESSAGE", "color:black;font-weight:bold;", data);
|
||||
}
|
||||
@@ -151,6 +157,7 @@ export class WebSocketApi {
|
||||
|
||||
protected _onClose(evt: CloseEvent) {
|
||||
const error = evt.code !== 1000 || !evt.wasClean;
|
||||
|
||||
if (error) {
|
||||
this.reconnect();
|
||||
}
|
||||
|
||||
@@ -51,16 +51,19 @@ export class WorkloadKubeObject extends KubeObject {
|
||||
|
||||
getSelectors(): string[] {
|
||||
const selector = this.spec.selector;
|
||||
|
||||
return KubeObject.stringifyLabels(selector ? selector.matchLabels : null);
|
||||
}
|
||||
|
||||
getNodeSelectors(): string[] {
|
||||
const nodeSelector = get(this, "spec.template.spec.nodeSelector");
|
||||
|
||||
return KubeObject.stringifyLabels(nodeSelector);
|
||||
}
|
||||
|
||||
getTemplateLabels(): string[] {
|
||||
const labels = get(this, "spec.template.metadata.labels");
|
||||
|
||||
return KubeObject.stringifyLabels(labels);
|
||||
}
|
||||
|
||||
@@ -74,7 +77,9 @@ export class WorkloadKubeObject extends KubeObject {
|
||||
|
||||
getAffinityNumber() {
|
||||
const affinity = this.getAffinity();
|
||||
|
||||
if (!affinity) return 0;
|
||||
|
||||
return Object.keys(affinity).length;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user