diff --git a/app/models/cluster.js b/app/models/cluster.js index 9d72f5d5d5..7596698de8 100644 --- a/app/models/cluster.js +++ b/app/models/cluster.js @@ -336,6 +336,10 @@ export default Resource.extend(Grafana, ResourceUsage, { .filter((s) => !(s.conditions || []).any((c) => c.status === 'True')); }), + masterNodes: computed('nodes.@each.{state,labels}', function() { + return (this.nodes || []).filter((node) => node.labels && node.labels[C.NODES.MASTER_NODE]); + }), + inactiveNodes: computed('nodes.@each.state', function() { return (get(this, 'nodes') || []).filter( (n) => C.ACTIVEISH_STATES.indexOf(get(n, 'state')) === -1 ); }), diff --git a/app/models/node.js b/app/models/node.js index 57cce9c042..210e060f00 100644 --- a/app/models/node.js +++ b/app/models/node.js @@ -158,6 +158,36 @@ var Node = Resource.extend(Grafana, StateCounts, ResourceUsage, { return taints.some((taint) => UNSCHEDULABLE_KEYS.includes(taint.key) && UNSCHEDULABLE_EFFECTS.includes(taint.effect)); }), + isK3sNode: computed('labels', function() { + const labels = get(this, 'labels') || {}; + + return Object.prototype.hasOwnProperty.call(labels, C.LABEL.NODE_INSTANCE_TYPE); + }), + + k3sNodeArgs: computed('annotations', function() { + const { annotations } = this; + const nodeArgs = annotations[C.LABEL.K3S_NODE_ARGS] ? JSON.parse(annotations[C.LABEL.K3S_NODE_ARGS]) : []; + + return nodeArgs.join(' '); + }), + + k3sNodeEnvVar: computed('annotations', function() { + const { annotations } = this; + const nodeEnv = annotations[C.LABEL.K3S_NODE_ENV] ? JSON.parse(annotations[C.LABEL.K3S_NODE_ENV]) : {}; + const nodeEnvArr = []; + + Object.keys(nodeEnv).forEach((envKey) => { + const out = { + key: envKey, + value: nodeEnv[envKey] + }; + + nodeEnvArr.push(out) + }) + + return nodeEnvArr; + }), + osBlurb: computed('info.os.operatingSystem', function() { var out = get(this, 'info.os.operatingSystem') || ''; diff --git a/lib/monitoring/addon/components/node-dashboard/component.js b/lib/monitoring/addon/components/node-dashboard/component.js index 0fd0a730d1..6d6cf42ab4 100644 --- a/lib/monitoring/addon/components/node-dashboard/component.js +++ b/lib/monitoring/addon/components/node-dashboard/component.js @@ -4,10 +4,13 @@ import { inject as service } from '@ember/service'; import layout from './template'; export default Component.extend({ - scope: service(), + scope: service(), layout, - model: null, + model: null, + sortBy: 'key', + descending: false, + isK3sNode: alias('model.node.isK3sNode'), monitoringEnabled: alias('scope.currentCluster.enableClusterMonitoring'), isMonitoringReady: alias('scope.currentCluster.isMonitoringReady'), }); diff --git a/lib/monitoring/addon/components/node-dashboard/template.hbs b/lib/monitoring/addon/components/node-dashboard/template.hbs index 6dc4d809f4..678d35c6f6 100644 --- a/lib/monitoring/addon/components/node-dashboard/template.hbs +++ b/lib/monitoring/addon/components/node-dashboard/template.hbs @@ -68,6 +68,22 @@ expandAll=al.expandAll expandFn=expandFn }} + {{#if isK3sNode}} +
+ {{#accordion-list-item + title=(t "clusterDashboard.k3sInfo.title") + detail=(t "clusterDashboard.k3sInfo.detail") + expandAll=expandAll + expand=(action expandFn) + as | parent | + }} +
+ + +
+ {{/accordion-list-item}} +
+ {{/if}}
{{system-info-section node=model.node diff --git a/lib/shared/addon/components/annotations-section/component.js b/lib/shared/addon/components/annotations-section/component.js index cbb3984ca6..b13039593a 100644 --- a/lib/shared/addon/components/annotations-section/component.js +++ b/lib/shared/addon/components/annotations-section/component.js @@ -3,6 +3,13 @@ import { alias } from '@ember/object/computed'; import Component from '@ember/component'; import ManageLabels from 'shared/mixins/manage-labels'; import layout from './template'; +import C from 'ui/utils/constants'; + +const K3S_LABELS_TO_IGNORE = [ + C.LABEL.K3S_NODE_ARGS, + C.LABEL.K3S_NODE_CONFIG_HASH, + C.LABEL.K3S_NODE_ENV +]; export default Component.extend(ManageLabels, { layout, @@ -25,11 +32,13 @@ export default Component.extend(ManageLabels, { ], annotationSource: alias('model.annotations'), + didReceiveAttrs() { - this.initLabels(this.get('annotationSource')); + this.initLabels(this.get('annotationSource'), null, null, null, K3S_LABELS_TO_IGNORE); }, + annotationsObserver: observer('model.annotations', function() { - this.initLabels(this.get('annotationSource')); + this.initLabels(this.get('annotationSource'), null, null, null, K3S_LABELS_TO_IGNORE); }), }); diff --git a/lib/shared/addon/components/cluster-driver/driver-import/component.js b/lib/shared/addon/components/cluster-driver/driver-import/component.js index 975f36f2dc..8827d1ce63 100644 --- a/lib/shared/addon/components/cluster-driver/driver-import/component.js +++ b/lib/shared/addon/components/cluster-driver/driver-import/component.js @@ -1,7 +1,7 @@ import Component from '@ember/component' import ClusterDriver from 'shared/mixins/cluster-driver'; import { inject as service } from '@ember/service'; -import { get, set, observer } from '@ember/object'; +import { computed, get, set, observer } from '@ember/object'; import { alias, equal } from '@ember/object/computed'; import layout from './template'; @@ -12,13 +12,15 @@ export default Component.extend(ClusterDriver, { settings: service(), layout, - configField: 'importedConfig', - step: 1, - loading: false, - clusterAdmin: CLUSTER_ADMIN, + configField: 'importedConfig', + step: 1, + loading: false, + nodeForInfo: null, + clusterAdmin: CLUSTER_ADMIN, isEdit: equal('mode', 'edit'), clusterState: alias('model.originalCluster.state'), + isK3sCluster: equal('model.cluster.driver', 'k3s'), didReceiveAttrs() { if ( get(this, 'isEdit') && @@ -26,6 +28,19 @@ export default Component.extend(ClusterDriver, { ) { this.loadToken(); } + + if ( get(this, 'isEdit') && this.isK3sCluster && this.model.cluster.masterNodes.length === 1 ) { + set(this, 'nodeForInfo', this.model.cluster.masterNodes.firstObject); + } + }, + + actions: { + driverUpdateK3sCluster() { + }, + + setActiveNodeForInfo(nodeId) { + set(this, 'nodeForInfo', this.nodes.findBy('id', nodeId)); + }, }, clusterChanged: observer('originalCluster.state', function() { @@ -40,6 +55,17 @@ export default Component.extend(ClusterDriver, { } }), + nodes: computed('model.cluster.masterNodes.@each.{state}', function() { + return this.model.cluster.masterNodes; + }), + + nodesOptions: computed('nodes.@each.{state}', function() { + return this.nodes.map((node) => ( { + id: node.id, + displayName: node.displayName + } )); + }), + doneSaving() { if ( get(this, 'isEdit') ) { if (this.close) { diff --git a/lib/shared/addon/components/cluster-driver/driver-import/template.hbs b/lib/shared/addon/components/cluster-driver/driver-import/template.hbs index b05fd86198..be957543ab 100644 --- a/lib/shared/addon/components/cluster-driver/driver-import/template.hbs +++ b/lib/shared/addon/components/cluster-driver/driver-import/template.hbs @@ -1,5 +1,66 @@ -{{#if (and (eq step 2) (eq originalCluster.state "pending"))}} +{{#if (and (eq step 1) isK3sCluster)}} + + + + + +
+
+ + {{new-select + id="master-node-select" + classNames="form-control" + optionValuePath="id" + optionLabelPath="displayName" + content=nodesOptions + value=nodeForInfo.id + action=(action "setActiveNodeForInfo") + prompt="k3sClusterInfo.nodeInfo.selectPrompt" + localizedPrompt=true + }} +
+ {{#if nodeForInfo}} +
+ + +
+ {{/if}} +
+
+
+ + + + + +{{else if (and (eq step 2) (eq originalCluster.state "pending"))}} {{#banner-message color="bg-info m-0"}}
{{t "clusterNew.import.command.instructionsAdminRole" diff --git a/lib/shared/addon/components/cru-cluster/component.js b/lib/shared/addon/components/cru-cluster/component.js index f6103701ea..ae6a6d0e8b 100644 --- a/lib/shared/addon/components/cru-cluster/component.js +++ b/lib/shared/addon/components/cru-cluster/component.js @@ -41,6 +41,7 @@ export default Component.extend(ViewNewEdit, ChildHook, { primaryResource: alias('model.cluster'), isCustom: equal('driverInfo.nodeWhich', 'custom'), + isK3sCluster: equal('model.cluster.driver', 'k3s'), init() { this._super(...arguments); @@ -252,6 +253,12 @@ export default Component.extend(ViewNewEdit, ChildHook, { preSave: true }); + out.push({ + name: 'k3s', + driver: 'import', + preSave: true + }); + out.forEach((driver) => { const key = `clusterNew.${ driver.name }.label`; diff --git a/lib/shared/addon/components/cru-cluster/template.hbs b/lib/shared/addon/components/cru-cluster/template.hbs index 1c1a9a087f..893e33df16 100644 --- a/lib/shared/addon/components/cru-cluster/template.hbs +++ b/lib/shared/addon/components/cru-cluster/template.hbs @@ -65,7 +65,7 @@ +
+ + +
+ {{#input-or-display + editable=editing + value=k3sConfig.kubernetesVersion.gitVersion + }} + {{input + id="node-kube-version" + class="form-control" + type="text" + value=k3sConfig.kubernetesVersion.gitVersion + }} + {{/input-or-display}} +
+
+
+
+
+ +
+ {{#input-or-display + editable=editing + value=k3sConfig.serverConcurrency + }} + {{input + id="node-server-concurrency" + class="form-control" + type="number" + value=k3sConfig.serverConcurrency + }} + {{/input-or-display}} +
+
+
+ +
+ {{#input-or-display + editable=editing + value=k3sConfig.workerConcurrency + }} + {{input + id="node-worker-concurrency" + class="form-control" + type="number" + value=k3sConfig.workerConcurrency + }} + {{/input-or-display}} +
+
+
\ No newline at end of file diff --git a/lib/shared/addon/components/k3s-node-args/component.js b/lib/shared/addon/components/k3s-node-args/component.js new file mode 100644 index 0000000000..9849028325 --- /dev/null +++ b/lib/shared/addon/components/k3s-node-args/component.js @@ -0,0 +1,10 @@ +import Component from '@ember/component'; +import layout from './template'; + +export default Component.extend({ + layout, + + classNames: ['col', 'span-12', 'box'], + + node: null, +}); diff --git a/lib/shared/addon/components/k3s-node-args/template.hbs b/lib/shared/addon/components/k3s-node-args/template.hbs new file mode 100644 index 0000000000..a7d262df63 --- /dev/null +++ b/lib/shared/addon/components/k3s-node-args/template.hbs @@ -0,0 +1,13 @@ + +

+ {{t "clusterDashboard.k3sInfo.nodeArgs.detail"}} +

+{{#if (gte node.k3sNodeArgs.length 1)}} +
{{node.k3sNodeArgs}}
+{{else}} +
+ {{t "clusterDashboard.k3sInfo.nodeArgs.noArgs"}} +
+{{/if}} \ No newline at end of file diff --git a/lib/shared/addon/components/k3s-node-env-var/component.js b/lib/shared/addon/components/k3s-node-env-var/component.js new file mode 100644 index 0000000000..cb43e75708 --- /dev/null +++ b/lib/shared/addon/components/k3s-node-env-var/component.js @@ -0,0 +1,24 @@ +import Component from '@ember/component'; +import layout from './template'; + +const HEADERS = [ + { + name: 'key', + sort: ['key'], + translationKey: 'annotationsSection.key', + }, + { + name: 'value', + sort: ['value', 'key'], + translationKey: 'annotationsSection.value', + }, +]; + +export default Component.extend({ + layout, + classNames: ['col', 'span-12', 'box'], + + node: null, + headers: HEADERS, + +}); diff --git a/lib/shared/addon/components/k3s-node-env-var/template.hbs b/lib/shared/addon/components/k3s-node-env-var/template.hbs new file mode 100644 index 0000000000..487a09bcb3 --- /dev/null +++ b/lib/shared/addon/components/k3s-node-env-var/template.hbs @@ -0,0 +1,42 @@ + +

+ {{t "k3sNodeEnvVarSection.detail"}} +

+{{#sortable-table + classNames="grid fixed mb-0 sortable-table" + bulkActions=false + rowActions=false + paging=false + search=true + sortBy=sortBy + stickyHeader=false + descending=descending + headers=headers + body=node.k3sNodeEnvVar + as |sortable kind label| +}} + {{#if (eq kind "row")}} + + + {{label.key}} + + + {{pretty-json value=label.value}} + + + {{else if (eq kind "norows")}} + + + {{t "k3sNodeEnvVarSection.noData"}} + + + {{else if (eq kind "nomatch")}} + + + {{t "k3sNodeEnvVarSection.noMatch"}} + + + {{/if}} +{{/sortable-table}} \ No newline at end of file diff --git a/lib/shared/addon/components/project-member-row/template.hbs b/lib/shared/addon/components/project-member-row/template.hbs index f26692acc4..bcb4016054 100644 --- a/lib/shared/addon/components/project-member-row/template.hbs +++ b/lib/shared/addon/components/project-member-row/template.hbs @@ -47,9 +47,9 @@  
- {{#unless isCreatorMember}} + {{#if (and (not isCreatorMember) editing)}} - {{/unless}} + {{/if}}
\ No newline at end of file diff --git a/lib/shared/addon/mixins/manage-labels.js b/lib/shared/addon/mixins/manage-labels.js index 535c9f561f..1614a28a98 100644 --- a/lib/shared/addon/mixins/manage-labels.js +++ b/lib/shared/addon/mixins/manage-labels.js @@ -3,6 +3,7 @@ import EmberObject, { set, setProperties, computed } from '@ember/object'; import Mixin from '@ember/object/mixin'; import C from 'ui/utils/constants'; import { debouncedObserver } from 'ui/utils/debounce'; +import { isEmpty } from '@ember/utils'; const USER = 'user'; const SYSTEM = 'system'; @@ -243,8 +244,13 @@ export default Mixin.create({ } }, - initLabels(obj, onlyOfType, onlyKeys, readonlyKeys) { + initLabels(obj, onlyOfType, onlyKeys, readonlyKeys, labelsToIgnore) { let out = []; + let ignoredLabels = [...C.LABELS_TO_IGNORE]; + + if (!isEmpty(labelsToIgnore)) { + ignoredLabels.pushObjects(labelsToIgnore); + } if ( onlyKeys && !isArray(onlyKeys) ) { onlyKeys = [onlyKeys]; @@ -263,7 +269,7 @@ export default Mixin.create({ type = 'system'; } - if ( C.LABELS_TO_IGNORE.indexOf(key) >= 0 ) { + if ( ignoredLabels.indexOf(key) >= 0 ) { // Skip ignored labels return; } diff --git a/lib/shared/addon/utils/constants.js b/lib/shared/addon/utils/constants.js index 8a4c3e0361..123a7106aa 100644 --- a/lib/shared/addon/utils/constants.js +++ b/lib/shared/addon/utils/constants.js @@ -165,6 +165,11 @@ var C = { // node driver special fields UI_HINTS: 'io.cattle.nodedriver/ui-field-hints', + + K3S_NODE_ARGS: 'k3s.io/node-args', + K3S_NODE_CONFIG_HASH: 'k3s.io/node-config-hash', + K3S_NODE_ENV: 'k3s.io/node-env', + NODE_INSTANCE_TYPE: 'node.kubernetes.io/instance-type', }, LANGUAGE: { @@ -929,4 +934,6 @@ C.FEATURES = { DASHBOARD: 'steve', } +C.NODES = { MASTER_NODE: 'node-role.kubernetes.io/master' }; + export default C; diff --git a/lib/shared/app/components/k3s-cluster-info/component.js b/lib/shared/app/components/k3s-cluster-info/component.js new file mode 100644 index 0000000000..bb3d072d0a --- /dev/null +++ b/lib/shared/app/components/k3s-cluster-info/component.js @@ -0,0 +1 @@ +export { default } from 'shared/components/k3s-cluster-info/component'; \ No newline at end of file diff --git a/lib/shared/app/components/k3s-node-args/component.js b/lib/shared/app/components/k3s-node-args/component.js new file mode 100644 index 0000000000..542bfa7a3f --- /dev/null +++ b/lib/shared/app/components/k3s-node-args/component.js @@ -0,0 +1 @@ +export { default } from 'shared/components/k3s-node-args/component'; \ No newline at end of file diff --git a/lib/shared/app/components/k3s-node-env-var/component.js b/lib/shared/app/components/k3s-node-env-var/component.js new file mode 100644 index 0000000000..cc680339a5 --- /dev/null +++ b/lib/shared/app/components/k3s-node-env-var/component.js @@ -0,0 +1 @@ +export { default } from 'shared/components/k3s-node-env-var/component'; diff --git a/translations/en-us.yaml b/translations/en-us.yaml index be6c390833..be3a50d18f 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -1472,6 +1472,13 @@ clusterDashboard: memoryPressure: "Alert: Node {node} has memory pressure." liveTitle: "{used} of {total} Used" reserved: Reserved + k3sInfo: + title: K3S Information + detail: Node system information for K3S imported clusters. + nodeArgs: + title: K3S Node Arguments + detail: A read only list of the K3S arguments for this node. + noArgs: No node arguments ingressResponse: @@ -7094,6 +7101,26 @@ containersSection: noMatch: No Containers match the current search initContainer: Init Container +k3sClusterInfo: + title: K3s Options + detail: Customize the K3S cluster options + workerConcurrency: Worker Concurrency + serverConcurrency: Server Concurrency + nodeInfo: + title: K3s Node Information + detail: Read-only view of K3S master node arguments and environment variables. + label: Master Node + datail: Additional K3S master node details + selectPrompt: Select node + +k3sNodeEnvVarSection: + title: K3S Node Environment Variables + detail: Read only list of environment variables for this K3S node. + key: Key + value: Value + noData: No environment variables + noMatch: No environment variables match the current search + labelsSection: kind: Kind title: Labels