diff --git a/frontend/src/component-details-card.js b/frontend/src/component-details-card.js index 27d380f..611f12c 100644 --- a/frontend/src/component-details-card.js +++ b/frontend/src/component-details-card.js @@ -1,10 +1,14 @@ import { LitElement, html, css } from "lit"; import { - getConditions, getConditionsAsSpans, getName, getNamespace, + getCreated, + getAgeStr, + getNodeName, + getDesiredReplicas, + getReadyReplicas, } from "./helpers.js"; export class ComponentDetailsCard extends LitElement { @@ -40,7 +44,9 @@ export class ComponentDetailsCard extends LitElement { Created -
${entity.attributes.metadata.creation_timestamp}
+
+ ${getCreated(entity).toLocaleString()} (${getAgeStr(entity)} ago) +
`; @@ -83,7 +89,7 @@ export class ComponentDetailsCard extends LitElement { return html` Conditions - ${getConditionsAsSpans(getConditions(entity))} + ${getConditionsAsSpans(entity)} `; } @@ -180,18 +186,8 @@ export class ComponentDetailsCard extends LitElement { Pods -
- Desired: - ${entity.attributes.status.replicas ?? - entity.attributes.status.desired_number_scheduled ?? - 0} -
-
- Ready: - ${entity.attributes.status.ready_replicas ?? - entity.attributes.status.number_ready ?? - 0} -
+
Desired: ${getDesiredReplicas(entity)}
+
Ready: ${getReadyReplicas(entity)}
Updated: ${entity.attributes.status.updated_replicas ?? @@ -246,12 +242,12 @@ export class ComponentDetailsCard extends LitElement { } renderNode(entity) { - if (!entity.attributes.spec.node_name) return; + if (!getNodeName(entity)) return; return html` Node -
${entity.attributes.spec.node_name}
+
${getNodeName(entity)}
`; diff --git a/frontend/src/helpers.js b/frontend/src/helpers.js index 35b4b7e..f9c7bca 100644 --- a/frontend/src/helpers.js +++ b/frontend/src/helpers.js @@ -1,9 +1,12 @@ import { html } from "lit"; -import { state } from "lit-element"; -const stateError = "error"; -const stateWarning = "warning"; -const stateSuccess = "success"; +export const stateError = "error"; +export const stateWarning = "warning"; +export const stateSuccess = "success"; + +export function getState(entity_row) { + return entity_row.state; +} export function getName(entity_row) { return entity_row.attributes.metadata.name; @@ -13,27 +16,49 @@ export function getNamespace(entity_row) { return entity_row.attributes.metadata.namespace; } -export function getConditions(entity_row) { - return entity_row.attributes.status.conditions; +export function getConditions(entity_row, filter) { + let conditions = entity_row.attributes.status.conditions; + + if (filter) { + conditions = conditions.filter((condition) => { + let condition_filter = filter[condition.type]; + let condition_state = getConditionStateMapper(condition); + // prettier-ignore + return (!condition_filter) || + (condition_filter == stateSuccess) || + (condition_filter == stateWarning && (condition_state == stateWarning || condition_state == stateError)) || + (condition_filter == stateError && condition_state == stateError) + }); + } + + return conditions; } export function getNodeSchedulable(entity_row) { return !entity_row.attributes.spec.unschedulable; } -export function getNodeSchedulableIcon(value) { - if (value) { - return html` `; - } else { - return html` `; - } +export function getCreated(entity_row) { + return new Date(entity_row.attributes.metadata.creation_timestamp); } -export function getNodeSchedulableStateClass(entity_row) { +export function getAge(entity_row) { + return Date.now() - getCreated(entity_row); +} + +export function getAgeStr(entity_row) { + var ms = getAge(entity_row); + var d = Math.floor(ms / 86400000); + var h = Math.floor((ms / 3600000) % 24); + + return `${d}d${h}h`; +} + +export function getNodeSchedulableIcon(entity_row) { if (getNodeSchedulable(entity_row)) { - return stateSuccess; + return html` `; } else { - return stateError; + return html` `; } } @@ -83,10 +108,15 @@ export function getConditionStateMapper(condition) { }, }; - return conditionStateMap[condition.type][condition.reason] ?? conditionStateMap[condition.type][condition.status]; + return ( + conditionStateMap[condition.type][condition.reason] ?? + conditionStateMap[condition.type][condition.status] + ); } -export function getConditionsAsSpans(conditions) { +export function getConditionsAsSpans(entity_row) { + let conditions = getConditions(entity_row, this); + return html` ${conditions.map((condition) => { return html` @@ -97,3 +127,39 @@ export function getConditionsAsSpans(conditions) { })} `; } + +export function getConditionsStrings(entity_row) { + let conditions = getConditions(entity_row, this); + + return ` + ${conditions.map((condition) => { + return ` + ${condition.reason ? condition.reason : condition.type} + `; + })} + `; +} + +export function getNodeName(entity_row) { + return entity_row.attributes.spec.node_name; +} + +export function getDesiredReplicas(entity_row) { + return ( + entity_row.attributes.status.replicas ?? + entity_row.attributes.status.desired_number_scheduled ?? + 0 + ); +} + +export function getReadyReplicas(entity_row) { + return ( + entity_row.attributes.status.ready_replicas ?? + entity_row.attributes.status.number_ready ?? + 0 + ); +} + +export function getReplicaStr(entity_row) { + return `${getReadyReplicas(entity_row)}/${getDesiredReplicas(entity_row)}`; +} diff --git a/frontend/src/panel.js b/frontend/src/panel.js index c843fae..e035b21 100644 --- a/frontend/src/panel.js +++ b/frontend/src/panel.js @@ -6,15 +6,24 @@ import "./table-card.js"; import "./component-details-card.js"; import "./namespace-selector.js"; import { + stateSuccess, + stateWarning, + stateError, getName, getNamespace, - getConditions, getConditionsAsSpans, + getConditionsStrings, getNodeSchedulable, getNodeSchedulableIcon, - getNodeSchedulableStateClass, + getAge, + getAgeStr, + getState, + getNodeName, + getReplicaStr, } from "./helpers.js"; +const componentDetailsCardName = "custom:k8s-component-details"; + class KubernetesPanel extends LitElement { constructor() { super(); @@ -43,77 +52,130 @@ class KubernetesPanel extends LitElement { switch (this._page) { case "Node": + var conditionFilter = { + NetworkUnavailable: stateWarning, + MemoryPressure: stateWarning, + DiskPressure: stateWarning, + PIDPressure: stateWarning, + }; + return { popUpCard: { - type: "custom:k8s-component-details", + type: componentDetailsCardName, }, columns: { Name: { - function: getName, + value: getName, + sort_value: getName, }, Schedulable: { - function: getNodeSchedulable, - transformation: getNodeSchedulableIcon, - state_function: getNodeSchedulableStateClass, + value: getNodeSchedulableIcon, + sort_value: getNodeSchedulable, + }, + Age: { + value: getAgeStr, + sort_value: getAge, }, Conditions: { - function: getConditions, - transformation: getConditionsAsSpans, + value: getConditionsAsSpans.bind(conditionFilter), + sort_value: getConditionsStrings.bind(conditionFilter), }, }, filter_functions: [filterByPage], }; case "Deployment": + var conditionFilter = { + Progressing: stateWarning, + }; + return { popUpCard: { - type: "custom:k8s-component-details", + type: componentDetailsCardName, }, columns: { Name: { - function: getName, + value: getName, + sort_value: getName, }, Namespace: { - function: getNamespace, + value: getNamespace, + sort_value: getNamespace, + }, + Replicas: { + value: getReplicaStr, + sort_value: getReplicaStr, + }, + Age: { + value: getAgeStr, + sort_value: getAge, + }, + Conditions: { + value: getConditionsAsSpans.bind(conditionFilter), + sort_value: getConditionsStrings.bind(conditionFilter), }, - State: { function: "return entity_row.state" }, }, filter_functions: [filterByPage, filterByNamespace], }; case "DaemonSet": return { popUpCard: { - type: "custom:k8s-component-details", + type: componentDetailsCardName, }, columns: { Name: { - function: getName, + value: getName, + sort_value: getName, }, Namespace: { - function: getNamespace, + value: getNamespace, + sort_value: getNamespace, + }, + Replicas: { + value: getReplicaStr, + sort_value: getReplicaStr, + }, + Age: { + value: getAgeStr, + sort_value: getAge, }, - State: { function: "return entity_row.state" }, }, filter_functions: [filterByPage, filterByNamespace], }; case "Pod": + var conditionFilter = { + Initialized: stateWarning, + ContainersReady: stateWarning, + PodScheduled: stateWarning, + }; + return { popUpCard: { - type: "custom:k8s-component-details", + type: componentDetailsCardName, }, columns: { Name: { - function: getName, + value: getName, + sort_value: getName, }, Namespace: { - function: getNamespace, + value: getNamespace, + sort_value: getNamespace, }, Node: { - function: "return entity_row.attributes.spec.node_name", + value: getNodeName, + sort_value: getNodeName, + }, + State: { + value: getState, + sort_value: getState, + }, + Age: { + value: getAgeStr, + sort_value: getAge, }, - State: { function: "return entity_row.state" }, Conditions: { - function: getConditions, - transformation: getConditionsAsSpans, + value: getConditionsAsSpans.bind(conditionFilter), + sort_value: getConditionsStrings.bind(conditionFilter), }, }, filter_functions: [filterByPage, filterByNamespace], diff --git a/frontend/src/table-card.js b/frontend/src/table-card.js index e96a3e9..1548ad9 100644 --- a/frontend/src/table-card.js +++ b/frontend/src/table-card.js @@ -76,11 +76,10 @@ export class TableCard extends LitElement { _entityID: state.entity_id, }; for (const [header, column] of Object.entries(this.config.columns)) { - var func = this.getAsFunction(column.function, "", "entity_row"); obj[header] = { - value: this.getAsFunction(column.function, "", "entity_row")(state), - state: this.getAsFunction( - column.state_function, + value: this.getAsFunction(column.value, "", "entity_row")(state), + sortValue: this.getAsFunction( + column.sort_value, "", "entity_row" )(state), @@ -99,8 +98,8 @@ export class TableCard extends LitElement { } data.sort((a, b) => { - var valA = this.sort.by in a ? a[this.sort.by].value : 0; - var valB = this.sort.by in b ? b[this.sort.by].value : 0; + var valA = this.sort.by in a ? a[this.sort.by].sortValue : 0; + var valB = this.sort.by in b ? b[this.sort.by].sortValue : 0; if (valA < valB) { return this.sort.DESC ? 1 : -1; } @@ -162,13 +161,7 @@ export class TableCard extends LitElement { > ${Object.keys(this.config.columns).map((header) => { return html` - - ${this.getAsFunction( - this.config.columns[header].transformation, - html`${row[header].value}`, - "value" - )(row[header].value)} - + ${row[header].value} `; })} `;