Live metrics for cluster/node/workload/pod/container

https://github.com/rancher/rancher/issues/15703
https://github.com/rancher/rancher/issues/15705
This commit is contained in:
loganhz 2018-12-01 20:24:47 +08:00
parent f71b4cba6f
commit b46b1cebb3
189 changed files with 4362 additions and 2354 deletions

View File

@ -61,7 +61,6 @@ const App = Application.extend({
'authenticated.project': 'authenticated.project',
'authenticated.prefs': 'authenticated.prefs',
'authenticated.cluster.nodes': 'authenticated.cluster.nodes',
'authenticated.cluster.nodes.node': 'authenticated.cluster.nodes.node',
'authenticated.cluster.security.members.index': 'authenticated.cluster.security.members.index',
'logout': 'logout'
}
@ -99,7 +98,6 @@ const App = Application.extend({
'authenticated.project': 'authenticated.project',
'authenticated.prefs': 'authenticated.prefs',
'authenticated.cluster.nodes': 'authenticated.cluster.nodes',
'authenticated.cluster.nodes.node': 'authenticated.cluster.nodes.node',
'authenticated.cluster.security.members.index': 'authenticated.cluster.security.members.index',
'logout': 'logout'
}
@ -185,6 +183,24 @@ const App = Application.extend({
}
}
},
monitoring: {
dependencies: {
services: [
'app',
'intl',
'grafana',
'scope',
'session',
'modal',
'globalStore',
'router',
'k8s',
'clusterStore',
'tooltip',
],
externalRoutes: {}
}
},
}
});

View File

@ -25,4 +25,3 @@
{{component tooltip tooltipTemplate=tooltipTemplate class="container-tooltip" id="tooltip-base" role="tooltip" aria-hidden="false"}}
{{modal-root}}
{{! svg-gradients}}

View File

@ -1,26 +1,7 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { set } from '@ember/object';
import { on } from '@ember/object/evented';
import C from 'ui/utils/constants';
export default Route.extend({
globalStore: service(),
scope: service(),
model() {
return this.get('globalStore').findAll('node')
.then((nodes) => {
const cluster = this.modelFor('authenticated.cluster');
return {
cluster,
nodes,
};
});
redirect() {
this.replaceWith('authenticated.cluster.monitoring');
},
setDefaultRoute: on('activate', function() {
set(this, `session.${ C.SESSION.CLUSTER_ROUTE }`, 'authenticated.cluster.index');
}),
});

View File

@ -1,57 +0,0 @@
<section class="header clearfix">
<h1 class="pull-left">{{t 'clusterDashboard.title'}}: {{model.cluster.displayName}}</h1>
<div class="right-buttons">
<button class="btn bg-primary btn-sm icon-btn hide" {{action "dashboard"}}>
<span class="darken"><i class="icon icon-external-link"></i></span>
<span>{{t 'k8sPage.dashboard.button'}}</span>
</button>
<button class="btn bg-primary btn-sm icon-btn ml-10" disabled={{not model.cluster.isReady}} onClick={{action "kubectl" allowedKeys="meta"}}>
<span class="darken"><i class="icon icon-terminal"></i></span>
<span>{{t 'k8sPage.shell.button'}}</span>
</button>
<button class="btn bg-primary btn-sm icon-btn ml-10" disabled={{not model.cluster.isReady}} {{action "kubeconfig"}}>
<span class="darken"><i class="icon icon-file"></i></span>
<span>{{t 'k8sPage.configFile.button'}}</span>
</button>
{{action-menu size="sm" classNames="pull-right" model=model.cluster}}
</div>
</section>
{{#if model.cluster.description}}
<div class="row mb-30">
{{banner-message color='bg-secondary mb-0 mt-10' message=(linkify model.cluster.description)}}
</div>
{{/if}}
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'clustersPage.provider.label'}}</label>
{{model.cluster.displayProvider}} {{copy-to-clipboard clipboardText=model.cluster.displayProvider size="small"}}
</div>
{{#if model.cluster.version.gitVersion}}
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'clustersPage.version.label'}}</label>
{{model.cluster.version.gitVersion}} {{copy-to-clipboard clipboardText=model.cluster.version.gitVersion size="small"}}
</div>
{{/if}}
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'clustersPage.nodes.label'}}</label>
{{#if (eq model.cluster.state "inactive")}}
{{t 'clusterRow.noHosts'}}
{{else}}
{{model.cluster.machines.length}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.created'}}</label>
{{date-calendar model.cluster.created}}
</div>
</div>
<section class="text-center">
{{cluster-dashboard nodes=currentClusterNodes}}
</section>

View File

@ -1 +0,0 @@
Networking

View File

@ -7,7 +7,7 @@ import C from 'ui/utils/constants';
const VALID_ROUTES = ['authenticated.cluster.nodes', 'authenticated.cluster.storage.classes',
'authenticated.cluster.storage.persistent-volumes', 'authenticated.cluster.notifier',
'authenticated.cluster.alert', 'authenticated.cluster.logging',
'authenticated.cluster.alert', 'authenticated.cluster.logging', 'authenticated.cluster.monitoring',
'authenticated.cluster.security.members.index', 'authenticated.cluster.projects',
'authenticated.cluster.quotas'];

View File

@ -6,8 +6,8 @@
<section>
<div class="row">
<div class="row-same-height row-full-height">
<div class="col span-6 col span-height col span-full-height col-top" style="margin-bottom: 20px;">
<div class="no-padding-margin" style="height: 100%;">
<div class="col span-6 col span-height col full-height col-top mb-20">
<div class="no-padding-margin full-height">
<div>
<div>
<label class="text-muted">{{t 'certificatesPage.valid'}}:</label>
@ -58,7 +58,7 @@
</div>
</section>
{{/if}}
<div class="no-padding-margin" style="height: 100%;">
<div class="no-padding-margin full-height">
<div>
<label class="section">{{t 'certificatesPage.domainNames.labelText'}}</label>
<div>

View File

@ -10,7 +10,8 @@ const VALID_ROUTES = ['apps-tab', 'authenticated.project.security.members.index'
'authenticated.project.ns', 'authenticated.project.certificates',
'authenticated.project.secrets', 'authenticated.project.config-maps',
'authenticated.project.registries', 'authenticated.project.alert',
'authenticated.project.logging', 'authenticated.project.pipeline.settings'];
'authenticated.project.logging', 'authenticated.project.pipeline.settings',
'authenticated.project.monitoring.project-setting'];
export default Route.extend(Preload, {
access: service(),

View File

@ -0,0 +1,35 @@
import Component from '@ember/component';
import layout from './template';
export default Component.extend({
layout,
model: null,
expandOnInit: true,
sortBy: 'displayState',
descending: true,
initExpand: true,
headers: [
{
name: 'displayState',
sort: ['displayState'],
translationKey: 'generic.state',
width: 120
},
{
name: 'name',
sort: ['name'],
translationKey: 'generic.name',
},
{
name: 'image',
sort: ['image'],
translationKey: 'generic.image',
},
{
name: 'restarts',
sort: ['restarts'],
translationKey: 'generic.restarts',
width: 100
},
],
});

View File

@ -0,0 +1,62 @@
{{#accordion-list-item
title=(t 'containersSection.title')
detail=(t 'containersSection.detail')
expandAll=expandAll
expand=(action expandFn)
expandOnInit=expandOnInit
componentName='sortable-table'
as | parent |
}}
{{#sortable-table
tableClassNames="double-rows"
body=containers
bulkActions=false
descending=descending
sortBy=sortBy
stickyHeader=stickyHeader
fullRows=true
search=search
headers=headers as |sortable kind inst dt|
}}
{{#if (eq kind "row")}}
<tr class="main-row">
<td data-title="{{dt.displayState}}" class="state">
{{badge-state model=inst}}
</td>
<td data-title="{{dt.name}}">
<a href="{{href-to "container" inst.podId inst.name}}">{{inst.displayName}}</a>
{{#if inst.initContainer}}
<div>
<small class="text-muted">{{t 'containersSection.initContainer'}}</small>
</div>
{{/if}}
</td>
<td data-title="{{dt.image}}">
{{inst.image}}
</td>
<td data-title="{{dt.restarts}}">
{{inst.restarts}}
</td>
<td data-title="{{dt.actions}}" class="actions">
{{action-menu model=inst}}
</td>
</tr>
{{#if inst.showTransitioningMessage}}
<tr class="sub-row no-top auto-height">
<td class="pb-5" colspan="4">
<div>
<small class="text-small {{inst.stateColor}}">{{uc-first inst.transitioningMessage}}</small>
</div>
</td>
<td colspan="1"></td>
</tr>
{{/if}}
{{else if (eq kind "nomatch")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted lacsso pt-20 pb-20">{{t 'containersSection.noMatch'}}</td></tr>
{{else if (eq kind "norows")}}
<tr>
<td colspan="{{sortable.fullColspan}}" class="text-center text-muted pt-20 pb-20">{{t 'containersSection.noData'}}</td>
</tr>
{{/if}}
{{/sortable-table}}
{{/accordion-list-item}}

View File

@ -5,7 +5,7 @@ import layout from './template';
export default Component.extend(ManageLabels, {
layout,
model: null,
initExpandAll: true,
expandOnInit: true,
sortBy: 'displayState',
showKind: true,
descending: true,
@ -21,6 +21,7 @@ export default Component.extend(ManageLabels, {
name: 'name',
sort: ['name'],
translationKey: 'generic.name',
width: 400
},
{
name: 'displayImage',
@ -31,18 +32,7 @@ export default Component.extend(ManageLabels, {
name: 'node',
sort: ['displayName'],
translationKey: 'generic.node',
},
{
name: 'displayIp',
sort: ['displayIp'],
translationKey: 'generic.ipAddress',
width: 180
},
],
expandAllObserve: function() {
let expandAll = this.get('expandAll');
this.set('initExpandAll', expandAll);
}.observes('expandAll')
});

View File

@ -1,8 +1,9 @@
{{#accordion-list-item
title=(t 'podsSection.title')
detail=(t 'podsSection.detail')
expandAll=initExpandAll
expandAll=expandAll
expand=(action expandFn)
expandOnInit=expandOnInit
componentName='sortable-table'
as | parent |
}}
@ -28,25 +29,25 @@
{{badge-state model=inst}}
</td>
<td data-title="{{dt.name}}">
<a href="{{href-to "container" inst.id}}">{{inst.displayName}}</a>
<a href="{{href-to "pod" inst.id}}">{{inst.displayName}}</a>
</td>
<td data-title="{{dt.displayImage}}">
<small>{{inst.displayImage}}</small>
<p data-title="{{t 'generic.details'}}" class="text-small text-muted m-0 clip">
{{#if inst.displayIp}}
{{inst.displayIp}} /
{{/if}}
{{t 'generic.createdDate' date=(date-from-now inst.created) htmlSafe=true}} / {{t 'generic.restarts'}} {{inst.restarts}}
</p>
</td>
<td data-title="{{dt.node}}">
{{#if (and inst.node.id inst.node.clusterId)}}
<a href="{{href-to 'node' inst.node.clusterId inst.node.id}}">{{inst.node.displayName}}</a>
<a href="{{href-to 'authenticated.cluster.monitoring.node-detail' inst.node.clusterId inst.node.id}}">{{inst.node.displayName}}</a>
{{#if (or inst.node.externalIpAddress inst.node.ipAddress)}}
{{node-ip model=inst.node}}
{{/if}}
{{/if}}
</td>
<td data-title="{{dt.displayIp}}">
{{inst.displayIp}}
</td>
<td data-title="{{dt.actions}}" class="actions">
{{action-menu model=inst}}
</td>
@ -54,7 +55,7 @@
{{#if inst.dislayContainerMessage}}
<tr class="sub-row no-top auto-height">
<td colspan="1"></td>
<td class="pb-5" colspan="5">
<td class="pb-5" colspan="4">
{{#each inst.containers as |container|}}
{{#if container.showTransitioningMessage}}
<div>

View File

@ -1,82 +0,0 @@
import C from 'ui/utils/constants';
import Component from '@ember/component';
import { set, get, computed, observer } from '@ember/object';
import { alias } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import layout from './template';
export default Component.extend({
intl: service(),
scope: service(),
layout,
nodes: null,
components: null,
componentStatuses: alias('scope.currentCluster.componentStatuses'),
init() {
this._super(...arguments);
this.setComponents();
},
updateComponentsStatus: observer('componentStatuses.@each.conditions', 'nodes.@each.{state}', function() {
this.setComponents();
}),
showDashboard: computed('scope.currentCluster.isReady', 'nodes.[]', function() {
return get(this, 'nodes').length && get(this, 'scope.currentCluster.isReady')
}),
inactiveNodes: computed('nodes.@each.state', function() {
return get(this, 'nodes').filter( (n) => C.ACTIVEISH_STATES.indexOf(get(n, 'state')) === -1 );
}),
unhealthyComponents: computed('componentStatuses.@each.conditions', function() {
return (get(this, 'componentStatuses') || [])
.filter((s) => !s.conditions.any((c) => c.status === 'True'));
}),
setComponents() {
const etcd = this.getEtcdComponent();
const controller = this.getControllerComponent();
const scheduler = this.getSchedulerComponent();
const node = this.getNodeComponent();
set(this, 'components', [etcd, controller, scheduler, node]);
},
getEtcdComponent() {
return {
name: get(this, 'intl').t('clusterDashboard.etcd'),
healthy: this.isHealthy('etcd'),
};
},
getControllerComponent() {
return {
name: get(this, 'intl').t('clusterDashboard.controllerManager'),
healthy: this.isHealthy('controller-manager'),
};
},
getSchedulerComponent() {
return {
name: get(this, 'intl').t('clusterDashboard.scheduler'),
healthy: this.isHealthy('scheduler'),
};
},
getNodeComponent() {
return {
name: get(this, 'intl').t('clusterDashboard.node'),
healthy: get(this, 'inactiveNodes.length') === 0,
};
},
isHealthy(field) {
return (get(this, 'componentStatuses') || [])
.filter((s) => s.name.startsWith(field))
.any((s) => s.conditions.any((c) => c.status === 'True'));
},
});

View File

@ -1,45 +0,0 @@
{{#if showDashboard}}
{{node-gauges nodes=nodes}}
<div class="row">
{{#each components as |c|}}
<div class="col span-3">
<div class="banner {{if c.healthy 'bg-success' 'bg-error'}}">
<div class="banner-icon"><i class="icon icon-2x {{if c.healthy 'icon-check' 'icon-alert'}}" /></div>
<div class="banner-message text-left"><p>{{c.name}}</p></div>
</div>
</div>
{{/each}}
</div>
<div class="row">
{{#each unhealthyComponents as |component|}}
<div class="banner bg-error mt-30">
<div class="banner-icon">
<i class="icon icon-alert"></i>
</div>
<div class="banner-message text-left">
<p>{{t 'clusterDashboard.alert.component' component=component.name}}</p>
</div>
</div>
{{/each}}
{{#each inactiveNodes as |node|}}
<div class="banner bg-error mt-30">
<div class="banner-icon">
<i class="icon icon-alert"></i>
</div>
<div class="banner-message text-left">
<p>{{t 'clusterDashboard.alert.node' node=node.displayName}}</p>
</div>
</div>
{{/each}}
</div>
{{else}}
{{empty-table
resource="container"
showNew=scope.currentCluster.canAddNode
newRoute="authenticated.cluster.nodes.templates"
newTranslationKey="nodesPage.addNode"
disabled=(rbac-prevents resource="machine" scope="global" permission="create")
ctx=""
}}
{{/if}}

View File

@ -31,7 +31,7 @@ export default Component.extend({
},
details(/* event*/) {
var route = 'container';
var route = 'pod';
if ( this.get('model.isVm') ) {
route = 'virtualmachine';

View File

@ -0,0 +1,20 @@
import Component from '@ember/component';
import Metrics from 'shared/mixins/metrics';
import layout from './template';
import { get, set } from '@ember/object';
export default Component.extend(Metrics, {
layout,
filters: { resourceType: 'container' },
projectScope: true,
init() {
this._super(...arguments);
set(this, 'metricParams', {
podName: get(this, 'podId'),
containerName: get(this, 'resourceId')
});
},
});

View File

@ -0,0 +1,8 @@
<section>
{{metrics-action
queryAction="query"
allowDetail=false
state=state
}}
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
</section>

View File

@ -1,103 +0,0 @@
import { alias } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import ContainerChoices from 'ui/mixins/container-choices';
import {
STATUS,
STATUS_INTL_KEY,
classForStatus
} from 'shared/components/accordion-list-item/component';
import layout from './template';
const headers = [
{
name: 'name',
translationKey: 'formContainerLinks.name.label',
},
{
name: 'alias',
translationKey: 'formContainerLinks.alias.label',
},
];
export default Component.extend(ContainerChoices, {
router: service(),
growl: service(),
layout,
// Inputs
editing: null,
instance: null,
tagName: '',
errors: null,
headers,
statusClass: null,
linksArray: alias('instance.instanceLinks'),
actions: {
addLink() {
let links = this.get('linksArray');
if ( !links ) {
links = [];
this.set('linksArray', links);
}
links.pushObject(this.get('store').createRecord({
type: 'link',
name: '',
alias: '',
}));
},
removeLink(obj) {
this.get('linksArray').removeObject(obj);
},
followLink(str) {
let stack, stackName, containerName;
if ( str.includes('/')) {
[stackName, containerName] = name.split('/');
let stacks = this.get('store').all('stack');
stack = stacks.findBy('name', stackName);
} else {
stack = this.get('stack');
containerName = str;
}
if ( stack ) {
let container = stack.get('instances').findBy('name', containerName);
if ( container ) {
this.get('router').transitionTo('container', container.get('id'));
return;
}
}
this.get('growl').fromError(`Unable to find container for "${ name }"`);
},
},
status: function() {
let k = STATUS.NONE;
let count = (this.get('linksArray') || []).filterBy('name').get('length') || 0;
if ( count ) {
if ( this.get('errors.length') ) {
k = STATUS.INCOMPLETE;
} else {
k = STATUS.COUNTCONFIGURED;
}
}
this.set('statusClass', classForStatus(k));
return this.get('intl').t(`${ STATUS_INTL_KEY }.${ k }`, { count });
}.property('linksArray.@each.name'),
});

View File

@ -1,66 +0,0 @@
{{#accordion-list-item
title=(t 'formContainerLinks.title')
detail=(t 'formContainerLinks.detail' appName=settings.appName)
status=status
statusClass=statusClass
expandAll=expandAll
expand=(action expandFn)
}}
{{#if editing}}
<button class="btn bg-link icon-btn" {{action "addLink"}}>
<span class="darken"><i class="icon icon-plus text-small"/></span>
<span>{{t 'formContainerLinks.addActionLabel'}}</span>
</button>
<table class="table fixed no-lines no-top-padding mt-10">
<tr class="acc-label">
<th>{{t 'formContainerLinks.name.label'}}</th>
<th width="30">&nbsp;</th>
<th>{{t 'formContainerLinks.alias.label'}}</th>
<th width="40">&nbsp;</th>
</tr>
{{#each linksArray as |link|}}
<tr>
<td>
{{schema/input-container
value=link.name
exclude=launchConfig.id
stack=stack
}}
</td>
<td class="text-center">
<p><i class="icon icon-chevron-right"></i></p>
</td>
<td>
{{input class="form-control input-sm" type="text" value=link.alias placeholder=(t 'formContainerLinks.alias.placeholder')}}
</td>
<td class="text-right">
{{#unless link.existing}}
<button class="btn bg-primary btn-sm" {{action "removeLink" link}}><i class="icon icon-minus"/><span class="sr-only">{{t 'generic.remove'}}</span></button>
{{/unless}}
</td>
</tr>
{{/each}}
</table>
{{else}}
{{#sortable-table
classNames="grid sortable-table"
body=linksArray
descending=descending
bulkActions=false
pagingLabel="pagination.link"
headers=headers as |sortable kind row dt|}}
{{#if (eq kind "row")}}
<tr>
<td><a {{action "followLink" row.name}}>{{row.name}}</a></td>
<td>{{row.alias}}</td>
</tr>
{{else if (eq kind "nomatch")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted lacsso pt-20 pb-20">{{t 'formContainerLinks.noMatch'}}</td></tr>
{{else if (eq kind "norows")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted lacsso pt-20 pb-20">{{t 'formContainerLinks.noData'}}</td></tr>
{{/if}}
{{/sortable-table}}
{{/if}}
{{/accordion-list-item}}

View File

@ -0,0 +1,52 @@
import { inject as service } from '@ember/service';
import { get, set, observer } from '@ember/object';
import Component from '@ember/component';
import layout from './template';
const HTTPS = 'HTTPS';
const HTTP = 'HTTP'
const OPTIONS = [
{
label: HTTP,
value: HTTP
},
{
label: HTTPS,
value: HTTPS
}
];
export default Component.extend({
scope: service(),
layout,
editing: false,
protocolOptions: OPTIONS,
init() {
this._super(...arguments);
set(this, 'metrics', get(this, 'workload.workloadMetrics') || []);
},
actions: {
add() {
get(this, 'metrics').pushObject({
path: '',
port: '',
schema: HTTP
});
},
remove(obj) {
get(this, 'metrics').removeObject(obj);
},
},
metricsChanged: observer('metrics.@each.{port,path,schema}', function() {
set(this, 'workload.workloadMetrics', get(this, 'metrics').filter((metric) => get(metric, 'port')));
})
});

View File

@ -0,0 +1,77 @@
{{#accordion-list-item
title=(t 'formCustomMetrics.title')
detail=(t 'formCustomMetrics.detail')
expandAll=expandAll
expand=(action expandFn)
}}
<div class="clearfix {{unless editing 'box'}}">
{{#if metrics.length}}
<table class="table fixed no-lines small mb-10">
<thead>
<tr class="hidden-sm">
<th class="{{unless editing 'acc-label'}}">{{t 'formCustomMetrics.port.label'}}{{#if editing}}{{field-required}}{{/if}}</th>
<th width="10"></th>
<th class="{{unless editing 'acc-label'}}">{{t 'formCustomMetrics.path.label'}}</th>
<th width="10"></th>
<th class="{{unless editing 'acc-label'}}" width="80">{{t 'formCustomMetrics.protocol.label'}}</th>
<th width="40">&nbsp;</th>
</tr>
</thead>
<tbody>
{{#each metrics as |metric|}}
<tr>
<td data-title="{{t 'formCustomMetrics.port.label'}}">
{{#if editing}}
{{input-integer class="form-control input-sm public" min="1" max="65535" value=metric.port placeholder=(t 'formCustomMetrics.port.placeholder')}}
{{else}}
{{metric.port}}
{{/if}}
</td>
<td>&nbsp;</td>
<td data-title="{{t 'formCustomMetrics.path.label'}}">
{{#input-or-display editable=editing value=metric.path}}
{{input class="form-control input-sm" type="text" value=metric.path placeholder=(t 'formCustomMetrics.path.placeholder')}}
{{/input-or-display}}
</td>
<td>&nbsp;</td>
<td data-title="{{t 'formCustomMetrics.protocol.label'}}">
{{#if editing}}
{{new-select
class="form-control input-sm"
content=protocolOptions
value=metric.schema
}}
{{else}}
{{metric.schema}}
{{/if}}
</td>
<td>
{{#if editing}}
<button class="btn bg-primary btn-sm" {{action "remove" metric}}>
<i class="icon icon-minus"/><span class="sr-only">{{t 'generic.remove'}}</span>
</button>
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
{{#unless editing}}
<span class="text-center text-muted">{{t 'formCustomMetrics.noPorts'}}</span>
{{/unless}}
{{/if}}
</div>
<div>
{{#if editing}}
<button class="btn bg-link icon-btn p-0" {{action "add"}}>
<span class="darken"><i class="icon icon-plus text-small"/></span>
<span>{{t 'formCustomMetrics.addActionLabel'}}</span>
</button>
{{/if}}
</div>
{{/accordion-list-item}}

View File

@ -15,6 +15,7 @@ export default Component.extend(NewOrEdit, ChildHook, {
clusterStore: service(),
intl: service(),
prefs: service(),
scope: service(),
settings: service(),
layout,

View File

@ -248,6 +248,18 @@
expandFn=expandFn
expanded=securitySectionExpanded
}}
{{#unless isSidekick}}
{{#if scope.currentCluster.enableClusterMonitoring}}
{{container/form-custom-metrics
classNames="accordion-wrapper"
workload=service
editing=true
expandAll=al.expandAll
expandFn=expandFn
}}
{{/if}}
{{/unless}}
{{/advanced-section}}
{{/accordion-list}}

View File

@ -1,45 +0,0 @@
import Component from '@ember/component';
import layout from './template';
export default Component.extend({
layout,
model: null,
mode: 'small',
smallWidth: 60,
smallHeight: 25,
largeTargetId: null,
linkName: 'containerStats',
tagName: '',
cpuFields: [{
key: 'cpuUser',
displayName: 'infoMultiStats.cpuSection.user'
}, {
key: 'cpuSystem',
displayName: 'infoMultiStats.cpuSection.system'
}],
memoryFields: [{
key: 'memory',
displayName: 'infoMultiStats.memorySection.used'
}],
networkFields: [{
key: 'networkTx',
displayName: 'infoMultiStats.networkSection.transmit'
}, {
key: 'networkRx',
displayName: 'infoMultiStats.networkSection.receive'
}],
storageFields: [{
key: 'storageWrite',
displayName: 'infoMultiStats.storageSection.write'
}, {
key: 'storageRead',
displayName: 'infoMultiStats.storageSection.read'
}],
actions: {
toggle() {
this.set('mode', (this.get('mode') === 'small' ? 'large' : 'small'));
},
},
});

View File

@ -1,112 +0,0 @@
{{#multi-container-stats model=model linkName=linkName emitMaps=true as |stats|}}
{{#liquid-if (eq mode "small") class=(if stats.loading 'child-loading')}}
{{#if stats.loading}}
<div class="text-muted text-small vertical-middle">Connecting&hellip;</div>
{{else if stats.active}}
{{spark-line
data=stats.cpuTotal
width=smallWidth height=smallHeight
prefix="containersPage.table.sparkPrefixCpu"
formatter="percent"
gradient="cpu"
minMax=100
}}
{{spark-line
data=stats.memory
width=smallWidth height=smallHeight
prefix="containersPage.table.sparkPrefixMemory"
formatter="mib"
gradient="memory"
maxDoubleInital=true
}}
{{spark-line
data=stats.networkTotal
width=smallWidth height=smallHeight
prefix="containersPage.table.sparkPrefixNetwork"
formatter="kbps"
gradient="network"
minMax=100
}}
{{spark-line
data=stats.storageTotal
width=smallWidth height=smallHeight
prefix="containersPage.table.sparkPrefixStorage"
formatter="kbps"
gradient="storage"
minMax=100
}}
<button class="btn btn-sm bg-transparent" style="vertical-align: top" {{action "toggle"}}><i class="icon icon-plus-circle"></i></button>
{{else}}
<div class="text-muted text-small">Stats not available</div>
{{/if}}
{{/liquid-if}}
{{#if (eq mode "large")}}
{{#ember-wormhole to=largeTargetId}}
<div>
{{#if stats.loading}}
<div class="row">
<div class="col span-12 text-center">
<div class="text-muted text-small vertical-middle">Connecting&hellip;</div>
</div>
</div>
{{else if stats.active}}
<div class="row">
<div class="col span-6">
<h2>{{t 'infoMultiStats.cpuSection.labelText'}}</h2>
{{graph-area
model=stats
fields=cpuFields
formatter="percent"
gradient="cpu"
minMax=100
}}
</div>
<div class="col span-6">
<h2>{{t 'infoMultiStats.memorySection.labelText'}}</h2>
{{graph-area
model=stats
fields=memoryFields
formatter="mib"
gradient="memory"
maxDoubleInital=true
}}
</div>
</div>
<div class="row">
<div class="col span-6">
<h2>{{t 'infoMultiStats.networkSection.labelText'}}</h2>
{{graph-area
model=stats
fields=networkFields
formatter="kbps"
gradient="network"
minMax=100
}}
</div>
<div class="col span-6">
<h2>{{t 'infoMultiStats.storageSection.labelText'}}</h2>
{{graph-area
model=stats
fields=storageFields
formatter="kbps"
gradient="storage"
minMax=100
}}
</div>
</div>
<button class="btn btn-sm bg-transparent" style="vertical-align: top" {{action "toggle"}}><i class="icon icon-minus-circle"></i></button>
{{else}}
<div class="row">
<div class="col span-12 text-center">
<div class="text-muted text-small">Stats not available</div>
</div>
</div>
{{/if}}
</div>
{{/ember-wormhole}}
{{/if}}
{{/multi-container-stats}}

View File

@ -20,7 +20,7 @@
{{marked-down markdown=appReadmeContent}}
{{else if (not noAppReadme)}}
<div class="text-center">
<i class="icon icon-spinner icon-spin" style="font-size:36px;"></i>
<i class="icon icon-spinner icon-spin icon-3x"></i>
</div>
{{else if noAppReadme}}
<h1 class="mb-10 text-capitalize">
@ -122,7 +122,7 @@
{{#if getTemplate.isRunning}}
<section class="row">
<div class="text-center">
<i class="icon icon-spinner icon-spin" style="font-size:36px;"></i>
<i class="icon icon-spinner icon-spin icon-3x"></i>
</div>
</section>
{{else}}
@ -167,7 +167,7 @@
{{#if decoding}}
<section class="row">
<div class="text-center">
<i class="icon icon-spinner icon-spin" style="font-size:36px;"></i>
<i class="icon icon-spinner icon-spin icon-3x"></i>
</div>
</section>
{{else}}

View File

@ -1,14 +0,0 @@
<div class="row">
{{#each conditions as |c|}}
<div class="col span-3">
<div class="banner col span-3 m-0 {{if c.healthy 'bg-success' 'bg-error'}}">
<div class="banner-icon">
<i class="icon icon-check"></i>
</div>
<div class="banner-message">
<p>{{c.name}}</p>
</div>
</div>
</div>
{{/each}}
</div>

View File

@ -1,5 +0,0 @@
<div class="row">
{{#each gauges as |gauge|}}
<div class="col span-4">{{percent-gauge value=gauge.value title=gauge.title subtitle=gauge.subtitle ticks=gauge.ticks}}</div>
{{/each}}
</div>

View File

@ -1,7 +1,7 @@
<tr class="group-row">
<td colspan={{sub fullColspan 2}} class="pl-10">
{{#if (and model.id model.clusterId)}}
<a href="{{href-to 'node' model.clusterId model.id}}">
<a href="{{href-to 'authenticated.cluster.monitoring.node-detail' model.clusterId model.id}}">
{{t 'nodeGroup.label' name=model.displayName}}
</a>
{{else}}

View File

@ -10,12 +10,12 @@
<td data-title="{{dt.name}}" class="clip">
{{#if (eq view "global")}}
{{#if model.clusterId}}
<a href="{{href-to 'node' model.clusterId model.id}}">{{model.displayName}}</a>
<a href="{{href-to 'authenticated.cluster.monitoring.node-detail' model.clusterId model.id}}">{{model.displayName}}</a>
{{else}}
{{model.displayName}}
{{/if}}
{{else}}
<a href="{{href-to 'node' model.clusterId model.id}}">{{model.displayName}}</a>
<a href="{{href-to 'authenticated.cluster.monitoring.node-detail' model.clusterId model.id}}">{{model.displayName}}</a>
{{/if}}
{{#if (or model.externalIpAddress model.ipAddress)}}
{{node-ip model=model}}

View File

@ -0,0 +1,17 @@
import Component from '@ember/component';
import Metrics from 'shared/mixins/metrics';
import layout from './template';
import { get, set } from '@ember/object';
export default Component.extend(Metrics, {
layout,
filters: { resourceType: 'pod' },
projectScope: true,
init() {
this._super(...arguments);
set(this, 'metricParams', { podName: get(this, 'resourceId') });
},
});

View File

@ -0,0 +1,7 @@
<section>
{{metrics-action
queryAction="query"
state=state
}}
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
</section>

View File

@ -16,7 +16,7 @@
{{badge-state model=model}}
</td>
<td data-title="{{dt.name}}" class="clip">
<a href="{{href-to "container" model.id}}">{{model.displayName}}</a>
<a href="{{href-to "pod" model.id}}">{{model.displayName}}</a>
{{#if model.showTransitioningMessage}}
<div class="clip text-small {{model.stateColor}}">{{uc-first model.transitioningMessage}}</div>
{{else if model.displayEndpoints}}
@ -32,7 +32,7 @@
{{#copy-inline clipboardText=model.displayIp}}{{format-ip model.displayIp}}{{/copy-inline}} /
{{/if}}
{{#if (and showNode model.node)}}
<a class="text-vertical-top" href="{{href-to "node" scope.currentCluster.id model.node.id}}">{{model.node.displayName}}</a> /
<a class="text-vertical-top" href="{{href-to "authenticated.cluster.monitoring.node-detail" scope.currentCluster.id model.node.id}}">{{model.node.displayName}}</a> /
{{/if}}
{{t 'generic.createdDate' date=(date-from-now model.created) htmlSafe=true}} /
{{t 'generic.restarts'}} {{model.restarts}}

View File

@ -76,7 +76,7 @@ export default Component.extend({
obj.css = (`width: ${ obj.percent }%`).htmlSafe();
});
return out;
return out.filter((obj) => obj.percent);
}));
valueDep = `tooltipValues.@each.{${ labelKey },${ valueKey }}`;

View File

@ -9,9 +9,7 @@
}}
<div class="progress">
{{~#each pieces as |obj|~}}
{{#if obj.percent}}
<div class="progress-bar {{obj.color}}" style={{obj.css}}></div>
{{/if}}
<div class="progress-bar {{obj.color}}" style={{obj.css}}></div>
{{~/each~}}
</div>
{{/tooltip-element}}

View File

@ -1,49 +0,0 @@
import Component from '@ember/component';
import layout from './template';
export default Component.extend({
layout,
tagName: 'div',
classNames: ['progress'],
color: '',
min: 0,
value: 0,
max: 100,
zIndex: null,
didInsertElement() {
this.percentDidChange();
this.zIndexDidChange();
},
percent: function() {
var min = this.get('min');
var max = this.get('max');
var value = Math.max(min, Math.min(max, this.get('value')));
var per = value / (max - min) * 100; // Percent 0-100
per = Math.round(per * 100) / 100; // Round to 2 decimal places
return per;
}.property('min', 'max', 'value'),
colorClass: function() {
var color = this.get('color');
if ( !color ) {
return;
}
return `progress-bar-${ color.replace(/^progress-bar-/, '') }`;
}.property('color'),
percentDidChange: function() {
this.$('.progress-bar').css('width', `${ this.get('percent') }%`);
}.observes('percent'),
zIndexDidChange: function() {
this.$().css('zIndex', this.get('zIndex') || 'inherit');
}.observes('zIndex'),
});

View File

@ -1,2 +0,0 @@
<div class="progress-bar {{colorClass}}"></div>
<div class="progress-label">{{textLabel}}</div>

View File

@ -48,7 +48,7 @@
{{/component}}
{{else}}
<div class="text-center mt-20">
<i class="icon icon-spinner icon-spin" style="font-size:20px;"></i>
<i class="icon icon-spinner icon-spin icon-3x"></i>
</div>
{{/if}}
{{/accordion-list-item}}

View File

@ -1,213 +0,0 @@
import { htmlSafe } from '@ember/string';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { GRADIENT_COLORS } from 'shared/components/svg-gradients/component';
import { formatPercent, formatMib, formatKbps }
from 'ui/utils/util';
import {
select,
scale,
min as d3Min,
max as d3Max,
} from 'd3';
import layout from './template';
const FORMATTERS = {
value: (value) => value,
percent: formatPercent,
mib: formatMib,
kbps: formatKbps
};
export default Component.extend({
intl: service(),
layout,
tagName: 'svg',
classNames: ['spark-line'],
attributeBindings: ['cssSize:style'],
data: null,
width: null,
height: 20,
margin: 2,
min: 0,
minMax: null, // lower bound on how small automatic max can be
max: null, // set an explicit max
maxDoubleInital: false, // if true, set max to double the initial non-zero data point
scaleDown: false, // if true, max is allowed to go back down. If false it can only go up.
gradient: null,
colorIdx: 0,
interpolation: 'basis', // 'step-after',
formatter: 'value',
svg: null,
line: null,
dot: null,
text: null,
textBg: null,
x: null,
y: null,
observedMax: null, // The largest max seen so far
hasData: function() {
if (this.get('data.length') > 0 && !this.get('svg')) {
this.create();
}
}.observes('data.length'),
cssSize: function() {
let margin = parseInt(this.get('margin', 10));
let width = (parseInt(this.get('width'), 10) + 2 * margin);
let height = (parseInt(this.get('height'), 10) + 2 * margin);
return new htmlSafe(`width: ${ width }px; height: ${ height }px`);
}.property('width', 'height'),
lastValue: function() {
var data = this.get('data');
if (data && data.get('length')) {
return data.objectAt(data.get('length') - 1);
}
}.property('data.[]'),
updateLine: function() {
var line = this.get('line');
var interp = this.get('interpolation');
if (line) {
line.interpolate(interp);
}
}.observes('interpolation'),
update: function() {
var svg = this.get('svg');
var data = (this.get('data') || []).slice();
var x = this.get('x');
var y = this.get('y');
var line = this.get('line');
var text = this.get('text');
var textBg = this.get('textBg');
var width = this.get('width');
var height = this.get('height');
var margin = this.get('margin');
if (svg && data && x && y && line) {
x.domain([0, data.get('length') - 1]);
x.range([0, width - margin]);
var min = this.get('min') === null ? d3Min(data) : this.get('min');
var max = this.adjustMax(d3Max(data));
y.domain([min, max]);
y.range([height - margin, margin]);
y.rangeRound([height - margin, margin]);
// console.log('update', data[data.length-2], data[data.length-1], x.domain(), x.range(), y.domain(), y.range());
svg.selectAll('path')
.data([data])
.attr('d', line);
this.get('dot')
.attr('cx', x(data.length - 1) || 0 )
.attr('cy', y(data[data.length - 1]) || 0);
var str = FORMATTERS[this.get('formatter')](this.get('lastValue'));
text.text(str);
textBg.text(str);
text
.attr('x', width / 2)
.attr('y', height)
textBg
.attr('x', width / 2)
.attr('y', height);
}
}.observes('data', 'data.[]'),
create() {
let margin = this.get('margin');
var svg = select(this.$()[0])
.attr('transform', `translate(${ margin },${ margin })`);
this.set('svg', svg);
this.set('x', scale.linear());
this.set('y', scale.linear());
var line = svg.line()
.defined((d) => (typeof d === 'number'))
.x((d, i) => this.get('x')(i))
.y((d) => this.get('y')(d));
this.set('line', line);
this.updateLine();
let path = svg.append('path')
.attr('class', `spark-path`)
.attr('d', line(this.get('data')));
if ( this.get('gradient') ) {
path.style('stroke', GRADIENT_COLORS[this.get('gradient')][this.get('colorIdx')])
}
var dot = svg.append('circle')
.attr('class', 'spark-dot')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', 2);
this.set('dot', dot);
var textBg = svg.append('text')
.attr('class', `spark-text-bg`)
.attr('alignment-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('x', 0)
.attr('y', 0);
this.set('textBg', textBg);
var text = svg.append('text')
.attr('class', `spark-text`)
.attr('class', `spark-text`)
.attr('alignment-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('x', 0)
.attr('y', 0);
this.set('text', text);
},
adjustMax(dataMax) {
let optMinMax = this.get('minMax');
let optMax = this.get('max');
let optScaleDown = this.get('scaleDown');
let observedMax = this.get('observedMax');
let out = dataMax;
if ( optMax ) {
out = optMax;
} else if ( optMinMax ) {
out = Math.max(optMinMax, out);
}
if ( observedMax && !optScaleDown ) {
out = Math.max(observedMax, out);
}
if ( !observedMax && out > 0 && this.get('maxDoubleInital') ) {
out *= 2;
}
this.set('observedMax', out);
return out;
},
});

View File

@ -1 +0,0 @@
{{yield}}

View File

@ -1,50 +0,0 @@
import Component from '@ember/component';
import { select } from 'd3';
import layout from './template';
export const GRADIENT_COLORS = {
'cpu': ['#2ECC71', '#DBE8B1'],
'memory': ['#00558B', '#AED6F1'],
'network': ['#E49701', '#F1C40F'],
'storage': ['#3A6F81', '#ABCED3'],
};
export default Component.extend({
layout,
tagName: '',
didInsertElement() {
var svg = select('body').append('svg:svg')
.attr('id', 'svg-gradients')
.attr('height', '0')
.attr('height', '0')
.attr('width', '0')
.style('position', 'absolute');
var defs = svg.append('svg:defs');
Object.keys(GRADIENT_COLORS).forEach((name) => {
GRADIENT_COLORS[name].forEach((val, idx) => {
var gradient = defs.append('svg:linearGradient')
.attr('id', `${ name }-${ idx }-gradient`)
.attr('x1', '0%')
.attr('y1', '0%')
.attr('x2', '0%')
.attr('y2', '100%')
.attr('spreadMethod', 'pad');
gradient.append('svg:stop')
.attr('offset', '0%')
.attr('stop-color', val)
.attr('stop-opacity', '0.5');
gradient.append('svg:stop')
.attr('offset', '10%')
.attr('stop-color', val)
.attr('stop-opacity', '0.25');
gradient.append('svg:stop')
.attr('offset', '100%')
.attr('stop-color', val)
.attr('stop-opacity', '0.1');
});
});
},
});

View File

@ -1 +0,0 @@
{{yield}}

View File

@ -0,0 +1,18 @@
import Component from '@ember/component';
import Metrics from 'shared/mixins/metrics';
import layout from './template';
import { get, set } from '@ember/object';
export default Component.extend(Metrics, {
layout,
filters: { resourceType: 'workload' },
projectScope: true,
init() {
this._super(...arguments);
set(this, 'metricParams', { workloadName: get(this, 'resourceId') });
},
});

View File

@ -0,0 +1,7 @@
<section>
{{metrics-action
queryAction="query"
state=state
}}
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
</section>

View File

@ -1,26 +1,18 @@
import { inject as service } from '@ember/service';
import Controller from '@ember/controller';
import { get, set, observer, computed } from '@ember/object';
import { once } from '@ember/runloop';
import { get, computed, observer } from '@ember/object';
import { alias } from '@ember/object/computed';
import C from 'ui/utils/constants';
export default Controller.extend({
scope: service(),
router: service(),
selectedContainer: null,
monitoringEnabled: alias('scope.currentCluster.isMonitoringReady'),
actions: {
select(container) {
set(this, 'selectedContainer', container);
}
},
containerDidChange: observer('model.containers.[]', function() {
once(() => set(this, 'selectedContainer', get(this, 'model.containers.firstObject')));
}),
podStateDidChange: observer('model.state', function() {
if ( get(this, 'model.state') === 'removed' && get(this, 'router.currentRouteName') === 'container' ) {
const workloadId = get(this, 'model.workloadId');
podStateDidChange: observer('model.pod.state', function() {
if ( C.REMOVEDISH_STATES.includes(get(this, 'model.pod.state')) && get(this, 'router.currentRouteName') === 'container' ) {
const workloadId = get(this, 'model.pod.workloadId');
if ( workloadId ) {
this.transitionToRoute('workload', workloadId);
@ -29,9 +21,10 @@ export default Controller.extend({
}
}
}),
displayEnvironmentVars: computed('selectedContainer', function() {
displayEnvironmentVars: computed('model.environment', function() {
var envs = [];
var environment = this.get('selectedContainer.environment') || {};
var environment = get(this, 'model.environment') || {};
Object.keys(environment).forEach((key) => {
envs.pushObject({

View File

@ -1,5 +1,6 @@
import { get } from '@ember/object';
import Route from '@ember/routing/route';
import { hash } from 'rsvp';
export default Route.extend({
beforeModel() {
@ -14,12 +15,16 @@ export default Route.extend({
}
},
model(params) {
const pod = get(this, 'store').find('pod', params.container_id);
const pod = get(this, 'store').find('pod', params.pod_id);
if ( !pod ) {
this.replaceWith('authenticated.project.index');
}
return hash({ pod }).then((hash) => {
const container = get(hash, 'pod.containers').findBy('name', params.container_name);
return pod;
if ( !container ) {
this.replaceWith('authenticated.project.index');
}
return container;
});
},
});

View File

@ -1,7 +1,7 @@
<section class="header clearfix">
<div class="pull-left">
<h1 class="vertical-middle">
{{t 'podPage.header' name=model.displayName}}
{{t 'containerPage.header' name=model.displayName}}
</h1>
</div>
<div class="right-buttons">
@ -9,6 +9,7 @@
{{action-menu model=model showPrimary=false classNames="ml-10 inline-block" size="sm"}}
</div>
</section>
{{#if model.description}}
{{banner-message color='bg-secondary mb-0 mt-10' message=(linkify model.description)}}
{{/if}}
@ -17,230 +18,144 @@
<div class="{{model.stateColor}}"><p>{{uc-first model.transitioningMessage}}</p></div>
{{/if}}
<section>
<div class="row banner bg-info basics">
{{#if (eq model.containers.length 1)}}
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0"> {{t 'podPage.image'}}:</label>
{{selectedContainer.image}} {{copy-to-clipboard clipboardText=container.image size="small"}}
</div>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.namespace'}}:</label>
{{#if model.pod.namespaceId}}
{{#copy-inline clipboardText=model.pod.namespaceId}}
{{model.pod.namespaceId}}
{{/copy-inline}}
{{else}}
{{t 'generic.none'}}
{{/if}}
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.namespace'}}</label>
{{#if model.namespaceId}}
{{#copy-inline clipboardText=model.namespaceId}}
{{model.namespaceId}}
{{/copy-inline}}
{{else}}
{{t 'generic.none'}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'dnsPage.type.workload'}}:</label>
{{#if model.workload}}
{{#link-to "workload" model.workloadId}}{{model.workloadId}}{{/link-to}}
{{else if model.workloadId}}
{{model.workloadId}}
{{else}}
{{t 'generic.none'}}
{{/if}}
</div>
</div>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'podPage.podIp'}}:</label>
{{#if model.displayIp}}
{{#copy-inline clipboardText=model.displayIp}}
{{model.displayIp}}
{{/copy-inline}}
{{else}}
{{t 'generic.none'}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'podPage.nodeIp'}}:</label>
{{#if (and model.node.id model.node.clusterId)}}
<div class="inline">
<a href="{{href-to 'node' model.node.clusterId model.node.id}}">{{model.node.displayName}}</a>
{{#if (or model.node.externalIpAddress model.node.ipAddress)}}
{{node-ip model=model.node}}
{{/if}}
</div>
{{else}}
{{t 'generic.unknown'}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.created'}}:</label>
{{date-calendar model.created}} <small class="text-muted">({{t 'generic.restarts'}} {{model.restarts}})</small>
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'podPage.image'}}:</label>
{{model.image}} {{copy-to-clipboard clipboardText=model.image size="small"}}
</div>
</section>
<section>
{{#accordion-list as |al expandFn|}}
{{#if model.workload}}
{{container/form-scheduling
initialHostId=model.workload.nodeId
scheduling=model.workload.scheduling
editing=false
expandAll=al.expandAll
expandFn=expandFn
classNames="accordion"
}}
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'containerPage.pod'}}:</label> {{#if model.pod}}
{{#link-to "pod" model.podId}}{{model.podId}}{{/link-to}}
{{else if model.podId}}
{{model.podId}}
{{else}}
{{t 'generic.none'}}
{{/if}}
</div>
</div>
{{container/form-networking
classNames="accordion-wrapper"
service=model
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{form-labels-annotations
classNames="accordion-wrapper"
model=model
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{resource-condition-list
resourceType=(t 'generic.pod')
conditions=model.status.conditions
expandAll=al.expandAll
expandFn=expandFn
}}
{{resource-event-list
resourceType=(t 'generic.pod')
expandAll=al.expandAll
expandFn=expandFn
namespaceId=model.namespaceId
name=model.name
kind="Pod"
}}
{{/accordion-list}}
</section>
<section class="header has-tabs clearfix pb-0">
<ul class="tab-nav">
{{#each model.containers as |container|}}
<li>
<a class="{{if (eq container.name selectedContainer.name) 'active'}} hand" {{action 'select' container}}>
<i class="{{container.stateIcon}} {{container.stateColor}} dot"></i>
{{container.name}}
</a>
</li>
{{/each}}
</ul>
</section>
{{#each model.containers as |container|}}
<section class="{{if (eq container.name selectedContainer.name) '' 'hide'}}">
{{#if container.showTransitioningMessage}}
<div class="{{container.stateColor}}"><p>{{uc-first container.transitioningMessage}}</p></div>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'containerPage.initContainer.label'}}:</label>
{{#if model.initContainer}}
{{t 'generic.yes'}}
{{else}}
{{t 'generic.no'}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.restarts'}}:</label>
{{model.restarts}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.created'}}:</label>
{{date-calendar model.pod.created}}
</div>
</div>
<section class="header clearfix" style="border-bottom: 0;">
<div class="pull-left">
<h1 class="vertical-middle">
{{t 'podPage.image'}}:
{{container.image}} {{copy-to-clipboard clipboardText=container.image size="small"}}
</h1>
{{#accordion-list as |al expandFn|}}
{{#if scope.currentCluster.isMonitoringReady}}
{{#metrics-summary
expandAll=al.expandAll
expandFn=expandFn
grafanaUrl=model.grafanaUrl
title=(t "metricsAction.sections.container")
}}
{{container-metrics podId=model.pod.id resourceId=model.name}}
{{/metrics-summary}}
{{/if}}
{{#accordion-list-item
title=(t 'containerPage.portsTab.header')
detail=(t 'containerPage.portsTab.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
{{container/form-ports
initialPorts=model.ports
editing=false
}}
{{/accordion-list-item}}
{{container/form-volumes
launchConfig=model
workload=model.pod
namespace=model.pod.namespace
loggingEnabled=true
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{#accordion-list-item
title=(t 'containerPage.envTab.header')
detail=(t 'containerPage.envTab.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
{{form-env-var
model=displayEnvironmentVars
}}
{{/accordion-list-item}}
{{container/form-command
instance=model
service=model.pod
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{#accordion-list-item
title=(t 'formHealthCheck.title')
detail=(t 'formHealthCheck.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
<div class="row">
<div class="col span-6 mt-0 mb-0">
<label class="acc-label">{{t 'formHealthCheck.readiness'}}</label>
</div>
<div class="right-buttons">
{{badge-state model=container}}
{{action-menu model=container showPrimary=false classNames="ml-10 inline-block" size="sm"}}
<div class="col span-6 mt-0 mb-0">
{{#if model.livenessProbe}}
<label class="acc-label">{{t 'formHealthCheck.liveness'}}</label>
{{/if}}
</div>
</section>
{{#accordion-list as |al expandFn|}}
{{#accordion-list-item
title=(t 'containerPage.portsTab.header')
detail=(t 'containerPage.portsTab.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
{{container/form-ports
initialPorts=container.ports
editing=false
</div>
<div class="row">
<div class="col {{if model.livenessProbe 'span-6' 'span-12'}}">
{{form-healthcheck
initialCheck=model.readinessProbe
editing=false
}}
{{/accordion-list-item}}
{{container/form-volumes
launchConfig=container
workload=model
namespace=model.namespace
loggingEnabled=true
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{#accordion-list-item
title=(t 'containerPage.envTab.header')
detail=(t 'containerPage.envTab.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
{{form-env-var
model=displayEnvironmentVars
}}
{{/accordion-list-item}}
{{container/form-command
instance=container
service=model
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{#accordion-list-item
title=(t 'formHealthCheck.title')
detail=(t 'formHealthCheck.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
<div class="row">
<div class="col span-6 mt-0 mb-0">
<label class="acc-label">{{t 'formHealthCheck.readiness'}}</label>
</div>
<div class="col span-6 mt-0 mb-0">
{{#if container.livenessProbe}}
<label class="acc-label">{{t 'formHealthCheck.liveness'}}</label>
{{/if}}
</div>
</div>
{{#if model.livenessProbe}}
<div class="col span-6">
{{form-healthcheck
initialCheck=model.livenessProbe
editing=false
isLiveness=true
}}
</div>
<div class="row">
<div class="col {{if container.livenessProbe 'span-6' 'span-12'}}">
{{form-healthcheck
initialCheck=container.readinessProbe
editing=false
}}
</div>
{{#if container.livenessProbe}}
<div class="col span-6">
{{form-healthcheck
initialCheck=container.livenessProbe
editing=false
isLiveness=true
}}
</div>
{{/if}}
</div>
{{/accordion-list-item}}
{{/if}}
</div>
{{/accordion-list-item}}
{{container/form-security
instance=container
service=model
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{/accordion-list}}
</section>
{{/each}}
{{container/form-security
instance=model
service=model.pod
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{/accordion-list}}

View File

@ -1,4 +1,5 @@
import { getProjectId, getClusterId, bulkAdd } from 'ui/utils/navigation-tree';
import { get } from '@ember/object';
const rootNav = [
// Project
@ -34,17 +35,8 @@ const rootNav = [
id: 'infra',
localizedLabel: 'nav.infra.tab',
ctx: [getProjectId],
route: 'authenticated.project.alert',
route: 'authenticated.project.certificates',
submenu: [
{
id: 'tools-alerts',
localizedLabel: 'nav.tools.alerts',
icon: 'icon icon-alert',
route: 'authenticated.project.alert',
resource: [],
ctx: [getProjectId],
resourceScope: 'global',
},
{
id: 'infra-certificates',
localizedLabel: 'nav.infra.certificates',
@ -63,24 +55,6 @@ const rootNav = [
resource: ['configmap'],
resourceScope: 'project',
},
{
id: 'tools-logging',
localizedLabel: 'nav.tools.logging',
icon: 'icon icon-files',
route: 'authenticated.project.logging',
resourceScope: 'global',
resource: [],
ctx: [getProjectId],
},
{
id: 'tools-pipeline',
localizedLabel: 'nav.tools.pipeline',
icon: 'icon icon-garbage',
route: 'authenticated.project.pipeline.settings',
resource: ['sourcecodeproviderconfig'],
resourceScope: 'project',
ctx: [getProjectId],
},
{
id: 'infra-registries',
localizedLabel: 'nav.infra.registries',
@ -120,12 +94,58 @@ const rootNav = [
resourceScope: 'global',
ctx: [getProjectId],
},
{
scope: 'project',
id: 'project-tools',
localizedLabel: 'nav.tools.tab',
ctx: [getProjectId],
resource: [],
resourceScope: 'global',
route: 'authenticated.project.alert',
submenu: [
{
id: 'tools-alerts',
localizedLabel: 'nav.tools.alerts',
route: 'authenticated.project.alert',
resource: [],
ctx: [getProjectId],
resourceScope: 'global',
},
{
id: 'tools-logging',
localizedLabel: 'nav.tools.logging',
route: 'authenticated.project.logging',
resourceScope: 'global',
resource: [],
ctx: [getProjectId],
},
{
id: 'tools-monitoring',
localizedLabel: 'nav.tools.monitoring',
route: 'authenticated.project.monitoring.project-setting',
resourceScope: 'global',
resource: [],
ctx: [getProjectId],
condition() {
return !get(this, 'project.isSystemProject') && get(this, 'cluster.enableClusterMonitoring')
}
},
{
id: 'tools-pipeline',
localizedLabel: 'nav.tools.pipeline',
route: 'authenticated.project.pipeline.settings',
resource: ['sourcecodeproviderconfig'],
resourceScope: 'project',
ctx: [getProjectId],
},
]
},
// Cluster
{
scope: 'cluster',
id: 'cluster-k8s',
localizedLabel: 'nav.cluster.dashboard',
route: 'authenticated.cluster.index',
route: 'authenticated.cluster.monitoring',
ctx: [getClusterId],
resource: ['node'],
resourceScope: 'global',
@ -217,22 +237,27 @@ const rootNav = [
{
id: 'cluster-tools-notifiers',
localizedLabel: 'nav.tools.notifiers',
// icon: 'icon icon-key',
route: 'authenticated.cluster.notifier',
resourceScope: 'global',
resource: [],
ctx: [getClusterId],
},
{ divider: true },
{
id: 'cluster-tools-logging',
localizedLabel: 'nav.tools.logging',
// icon: 'icon icon-key',
route: 'authenticated.cluster.logging',
resourceScope: 'global',
resource: [],
ctx: [getClusterId],
},
{
id: 'cluster-tools-monitoring',
localizedLabel: 'nav.tools.monitoring',
route: 'authenticated.cluster.monitoring.cluster-setting',
resourceScope: 'global',
resource: [],
ctx: [getClusterId],
},
],
},

View File

@ -3,25 +3,26 @@ import { inject as service } from '@ember/service';
import Resource from 'ember-api-store/models/resource';
import { hasMany } from 'ember-api-store/utils/denormalize';
import ResourceUsage from 'shared/mixins/resource-usage';
import Grafana from 'shared/mixins/grafana';
import { equal, alias } from '@ember/object/computed';
import { resolve } from 'rsvp';
import C from 'ui/utils/constants';
export default Resource.extend(ResourceUsage, {
globalStore: service(),
growl: service(),
scope: service(),
router: service(),
export default Resource.extend(Grafana, ResourceUsage, {
globalStore: service(),
growl: service(),
scope: service(),
router: service(),
namespaces: hasMany('id', 'namespace', 'clusterId'),
projects: hasMany('id', 'project', 'clusterId'),
nodes: hasMany('id', 'node', 'clusterId'),
nodePools: hasMany('id', 'nodePool', 'clusterId'),
clusterRoleTemplateBindings: hasMany('id', 'clusterRoleTemplateBinding', 'clusterId'),
grafanaDashboardName: 'Cluster',
machines: alias('nodes'),
roleTemplateBindings: alias('clusterRoleTemplateBindings'),
isGKE: equal('driver', 'googleKubernetesEngine'),
getAltActionDelete: computed('action.remove', function() { // eslint-disable-line
return get(this, 'canBulkRemove') ? 'delete' : null;
}),
@ -56,6 +57,21 @@ export default Resource.extend(ResourceUsage, {
return null;
}),
isMonitoringReady: computed('monitoringStatus.@each.conditions', function() {
if ( !get(this, 'enableClusterMonitoring') ) {
return false;
}
const conditions = get(this, 'monitoringStatus.conditions') || [];
if ( get(conditions, 'length') > 0 ) {
const ready = conditions.filterBy('status', 'True') || [] ;
return get(ready, 'length') === get(conditions, 'length');
}
return false;
}),
isReady: computed('conditions.@each.status', function() {
return this.hasCondition('Ready');
}),
@ -117,8 +133,14 @@ export default Resource.extend(ResourceUsage, {
}
}),
systemProject: computed('projects.@each.isSystemProject', function() {
let projects = (get(this, 'projects') || []).filterBy('isSystemProject', true);
return get(projects, 'firstObject');
}),
defaultProject: computed('projects.@each.{name,clusterOwner}', function() {
let projects = this.get('projects');
let projects = get(this, 'projects');
let out = projects.findBy('isDefault');
@ -152,7 +174,7 @@ export default Resource.extend(ResourceUsage, {
},
edit() {
this.get('router').transitionTo('authenticated.cluster.edit', this.get('id'));
get(this, 'router').transitionTo('authenticated.cluster.edit', get(this, 'id'));
},
scaleDownPool(id) {
@ -187,8 +209,8 @@ export default Resource.extend(ResourceUsage, {
const promise = this._super.apply(this, arguments);
return promise.then((/* resp */) => {
if (this.get('scope.currentCluster.id') === this.get('id')) {
this.get('router').transitionTo('global-admin.clusters');
if (get(this, 'scope.currentCluster.id') === get(this, 'id')) {
get(this, 'router').transitionTo('global-admin.clusters');
}
});
},

View File

@ -3,9 +3,11 @@ import { reference } from 'ember-api-store/utils/denormalize';
import DisplayImage from 'shared/mixins/display-image';
import { get, computed } from '@ember/object';
import { inject as service } from '@ember/service';
import Grafana from 'shared/mixins/grafana';
import { alias } from '@ember/object/computed';
import { later } from '@ember/runloop';
var Container = Resource.extend(DisplayImage, {
var Container = Resource.extend(Grafana, DisplayImage, {
modalService: service('modal'),
intl: service(),
scope: service(),
@ -14,7 +16,17 @@ var Container = Resource.extend(DisplayImage, {
links: {},
type: 'container',
pod: reference('podId'),
grafanaDashboardName: 'Pods',
pod: reference('podId'),
grafanaResourceId: alias('name'),
podName: computed('pod.name', function() {
return get(this, 'pod.name');
}),
namespaceId: computed('pod.namespaceId', function() {
return get(this, 'pod.namespaceId');
}),
availableActions: computed('state', function() {
let isRunning = get(this, 'state') === 'running';
@ -39,6 +51,14 @@ var Container = Resource.extend(DisplayImage, {
return choices;
}),
restarts: computed('pod.status.containerStatuses.@each.restartCount', function() {
const state = (get(this, 'pod.status.containerStatuses') || []).findBy('name', get(this, 'name'));
if ( state ) {
return get(state, 'restartCount');
}
}),
validateQuota() {
const projectLimit = get(this, 'scope.currentProject.resourceQuota.limit');

View File

@ -1,5 +1,5 @@
import { computed, get } from '@ember/object';
import { or } from '@ember/object/computed';
import { or, alias } from '@ember/object/computed';
import Resource from 'ember-api-store/models/resource';
import { download } from 'shared/utils/util';
import C from 'ui/utils/constants';
@ -7,11 +7,12 @@ import StateCounts from 'ui/mixins/state-counts';
import { inject as service } from '@ember/service';
import { reference } from 'ember-api-store/utils/denormalize';
import ResourceUsage from 'shared/mixins/resource-usage';
import Grafana from 'shared/mixins/grafana';
const UNSCHEDULABLE_KEYS = ['node-role.kubernetes.io/etcd', 'node-role.kubernetes.io/controlplane'];
const UNSCHEDULABLE_EFFECTS = ['NoExecute', 'NoSchedule'];
var Node = Resource.extend(StateCounts, ResourceUsage, {
var Node = Resource.extend(Grafana, StateCounts, ResourceUsage, {
modalService: service('modal'),
settings: service(),
prefs: service(),
@ -22,6 +23,9 @@ var Node = Resource.extend(StateCounts, ResourceUsage, {
type: 'node',
grafanaDashboardName: 'Nodes',
grafanaResourceId: alias('ipAddress'),
cluster: reference('clusterId', 'cluster'),
nodePool: reference('nodePoolId'),

View File

@ -1,5 +1,6 @@
import C from 'ui/utils/constants';
import Resource from 'ember-api-store/models/resource';
import { alias } from '@ember/object/computed';
import { reference } from 'ember-api-store/utils/denormalize';
import { get, computed } from '@ember/object';
import { inject as service } from '@ember/service';
@ -7,9 +8,10 @@ import { strPad } from 'ui/utils/util';
import { formatSi } from 'shared/utils/parse-unit';
import { later } from '@ember/runloop';
import { gt } from '@ember/object/computed';
import Grafana from 'shared/mixins/grafana';
import DisplayImage from 'shared/mixins/display-image';
var Pod = Resource.extend(DisplayImage, {
var Pod = Resource.extend(Grafana, DisplayImage, {
router: service(),
modalService: service('modal'),
globalStore: service(),
@ -22,6 +24,9 @@ var Pod = Resource.extend(DisplayImage, {
canEdit: false,
canClone: false,
grafanaDashboardName: 'Pods',
grafanaResourceId: alias('name'),
namespace: reference('namespaceId', 'namespace', 'clusterStore'),
node: reference('nodeId', 'node', 'globalStore'),
workload: reference('workloadId'),

View File

@ -47,6 +47,21 @@ export default Resource.extend({
return labels[SYSTEM_PROJECT_LABEL] === 'true';
}),
isMonitoringReady: computed('monitoringStatus.@each.conditions', function() {
if ( !get(this, 'enableProjectMonitoring') ) {
return false;
}
const conditions = get(this, 'monitoringStatus.conditions') || [];
if ( get(conditions, 'length') > 0 ) {
const ready = conditions.filterBy('status', 'True') || [] ;
return get(ready, 'length') === get(conditions, 'length');
}
return false;
}),
active: computed('scope.currentProject.id', 'id', function() {
return get(this, 'scope.currentProject.id') === get(this, 'id');
}),

View File

@ -32,7 +32,7 @@ export default Resource.extend({
let linkEndpoint = `${ location.origin }/k8s/clusters/${ get(this, 'scope.currentCluster.id') }/api/v1/namespaces/${ get(this, 'namespaceId') }/services/`;
if ( get(port, 'name') === 'http' || get(port, 'name') === 'https' ) {
linkEndpoint = `${ get(port, 'name') }:`;
linkEndpoint += `${ get(port, 'name') }:`;
}
linkEndpoint += `${ get(this, 'name') }:${ get(port, 'port') }/proxy/`;

View File

@ -1,5 +1,6 @@
import { later, cancel } from '@ember/runloop';
import { computed, get, set } from '@ember/object';
import Grafana from 'shared/mixins/grafana';
import { alias, gt, not } from '@ember/object/computed';
import Resource from 'ember-api-store/models/resource';
import { sortableNumericSuffix } from 'shared/utils/util';
@ -13,7 +14,7 @@ import C from 'shared/utils/constants';
const WORKLOAD_CONFIG_FIELDS = ['cronJobConfig', 'daemonSetConfig', 'deploymentConfig', 'jobConfig', 'replicaSetConfig', 'replicationControllerConfig', 'statefulSetConfig']
var Workload = Resource.extend(DisplayImage, StateCounts, EndpointPorts, {
var Workload = Resource.extend(Grafana, DisplayImage, StateCounts, EndpointPorts, {
intl: service(),
growl: service(),
modalService: service('modal'),
@ -37,8 +38,9 @@ var Workload = Resource.extend(DisplayImage, StateCounts, EndpointPorts, {
canHaveEnvironment: true,
canHaveHealthCheck: true,
isBalancer: false,
canBalanceTo: true,
canBalanceTo: true,
grafanaResourceId: alias('name'),
namespace: reference('namespaceId', 'namespace', 'clusterStore'),
canClone: not('hasSidekicks'),

View File

@ -1,100 +0,0 @@
<section class="header clearfix">
<div class="pull-left">
<h1 class="vertical-middle">
{{t 'hostsPage.hostPage.header.title' name=model.node.displayName}}
</h1>
</div>
<div class="right-buttons">
{{badge-state model=model.node}}
{{action-menu model=model.node showPrimary=false classNames="ml-10 inline-block" size="sm"}}
</div>
</section>
{{#if model.node.description}}
{{banner-message color='bg-secondary mb-0 mt-10' message=(linkify model.node.description)}}
{{/if}}
<section>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.hostname'}}</label>
{{model.node.hostname}} {{copy-to-clipboard clipboardText=model.node.hostname size="small"}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.ipAddress'}}</label>
<div class="inline-block">
{{node-ip model=model.node textMuted=false}}
</div>
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.kubeletVersion'}}</label>
{{model.node.info.kubernetes.kubeletVersion}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.kubeProxyVersion'}}</label>
{{model.node.info.kubernetes.kubeProxyVersion}}
</div>
</div>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.dockerVersion'}}</label>
{{model.node.info.os.dockerVersion}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.kernelVersion'}}</label>
{{model.node.info.os.kernelVersion}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.operatingSystem'}}</label>
{{model.node.osBlurb}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.created'}}</label>
{{date-calendar model.node.created}}
</div>
</div>
</section>
{{node-gauges nodes=model.nodes}}
{{node-conditions conditionsSource=model.node.conditions}}
<section>
{{#accordion-list as |al expandFn|}}
{{resource-condition-list
resourceType=(t 'generic.node')
conditions=model.node.conditions
expandAll=al.expandAll
expandFn=expandFn
}}
<div class="mt-20">
{{system-info-section
node=model.node
expandAll=al.expandAll
expandFn=expandFn
}}
</div>
<div class="mt-20">
{{labels-section
model=model.node
showKind=false
expandAll=al.expandAll
expandFn=expandFn
}}
</div>
<div class="mt-20">
{{annotations-section
model=model.node
expandAll=al.expandAll
expandFn=expandFn
}}
</div>
<div class="mt-20">
{{taints-section
taints=model.node.taints
expandAll=al.expandAll
expandFn=expandFn
}}
</div>
{{/accordion-list}}
</section>

23
app/pod/controller.js Normal file
View File

@ -0,0 +1,23 @@
import { inject as service } from '@ember/service';
import Controller from '@ember/controller';
import { get, observer } from '@ember/object';
import { alias } from '@ember/object/computed';
import C from 'ui/utils/constants';
export default Controller.extend({
router: service(),
scope: service(),
monitoringEnabled: alias('scope.currentCluster.isMonitoringReady'),
podStateDidChange: observer('model.state', function() {
if ( C.REMOVEDISH_STATES.includes(get(this, 'model.state')) && get(this, 'router.currentRouteName') === 'pod' ) {
const workloadId = get(this, 'model.workloadId');
if ( workloadId ) {
this.transitionToRoute('workload', workloadId);
} else {
this.transitionToRoute('authenticated.project.index');
}
}
}),
});

25
app/pod/route.js Normal file
View File

@ -0,0 +1,25 @@
import { get } from '@ember/object';
import Route from '@ember/routing/route';
export default Route.extend({
beforeModel() {
if (window.ShellQuote) {
return;
} else {
return import('shell-quote').then( (module) => {
window.ShellQuote = module.default;
return module.default;
})
}
},
model(params) {
const pod = get(this, 'store').find('pod', params.pod_id);
if ( !pod ) {
this.replaceWith('authenticated.project.index');
}
return pod;
},
});

137
app/pod/template.hbs Normal file
View File

@ -0,0 +1,137 @@
<section class="header clearfix">
<div class="pull-left">
<h1 class="vertical-middle">
{{t 'podPage.header' name=model.displayName}}
</h1>
</div>
<div class="right-buttons">
{{badge-state model=model}}
{{action-menu model=model showPrimary=false classNames="ml-10 inline-block" size="sm"}}
</div>
</section>
{{#if model.description}}
{{banner-message color='bg-secondary mb-0 mt-10' message=(linkify model.description)}}
{{/if}}
{{#if model.showTransitioningMessage}}
<div class="{{model.stateColor}}"><p>{{uc-first model.transitioningMessage}}</p></div>
{{/if}}
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.namespace'}}:</label>
{{#if model.namespaceId}}
{{#copy-inline clipboardText=model.namespaceId}}
{{model.namespaceId}}
{{/copy-inline}}
{{else}}
{{t 'generic.none'}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'podPage.image'}}:</label>
{{model.displayImage}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'dnsPage.type.workload'}}:</label>
{{#if model.workload}}
{{#link-to "workload" model.workloadId}}{{model.workloadId}}{{/link-to}}
{{else if model.workloadId}}
{{model.workloadId}}
{{else}}
{{t 'generic.none'}}
{{/if}}
</div>
</div>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'podPage.podIp'}}:</label>
{{#if model.displayIp}}
{{#copy-inline clipboardText=model.displayIp}}
{{model.displayIp}}
{{/copy-inline}}
{{else}}
{{t 'generic.none'}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'podPage.nodeIp'}}:</label>
{{#if (and model.node.id model.node.clusterId)}}
<div class="inline">
<a href="{{href-to 'authenticated.cluster.monitoring.node-detail' model.node.clusterId model.node.id}}">{{model.node.displayName}}</a>
{{#if (or model.node.externalIpAddress model.node.ipAddress)}}
{{node-ip model=model.node}}
{{/if}}
</div>
{{else}}
{{t 'generic.unknown'}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.created'}}:</label>
{{date-calendar model.created}}
<div class="text-muted text-small">{{t 'generic.restarts'}} {{model.restarts}}</div>
</div>
</div>
{{#accordion-list as |al expandFn|}}
{{accordion-container
containers=model.containers
expandAll=al.expandAll
expandFn=expandFn
}}
{{#if scope.currentCluster.isMonitoringReady}}
{{#metrics-summary
expandAll=al.expandAll
expandFn=expandFn
grafanaUrl=model.grafanaUrl
title=(t "metricsAction.sections.pod")}}
{{pod-metrics resourceId=model.id}}
{{/metrics-summary}}
{{/if}}
{{resource-event-list
resourceType=(t 'generic.pod')
expandAll=al.expandAll
expandFn=expandFn
namespaceId=model.namespaceId
name=model.name
kind="Pod"
}}
{{resource-condition-list
resourceType=(t 'generic.pod')
conditions=model.status.conditions
expandAll=al.expandAll
expandFn=expandFn
}}
{{#if model.workload}}
{{container/form-scheduling
initialHostId=model.workload.nodeId
scheduling=model.workload.scheduling
editing=false
expandAll=al.expandAll
expandFn=expandFn
classNames="accordion"
}}
{{/if}}
{{container/form-networking
classNames="accordion-wrapper"
service=model
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{form-labels-annotations
classNames="accordion-wrapper"
model=model
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{/accordion-list}}

View File

@ -59,12 +59,10 @@ Router.map(function() {
this.route('nodes', function() {
this.route('index', { path: '/' });
this.route('node', {
path: '/:node_id',
resetNamespace: true
});
});
this.mount('monitoring');
this.route('projects', { path: '/projects-namespaces' }, function() {
this.route('index', { path: '/' });
this.route('edit', { path: '/project/:project_id' });
@ -122,6 +120,7 @@ Router.map(function() {
this.mount('alert', { path: '/alerts' });
this.mount('pipeline');
this.mount('monitoring');
// Workload
this.route('containers', {
@ -131,10 +130,15 @@ Router.map(function() {
this.route('run', { path: '/run' });
this.route('index', { path: '/' });
this.route('container', {
path: '/:container_id',
this.route('pod', {
path: '/:pod_id',
resetNamespace: true
});
this.route('container', {
path: '/:pod_id/container/:container_name',
resetNamespace: true
})
});
this.route('ingresses', { resetNamespace: true }, function() {

View File

@ -29,15 +29,13 @@
@import "app/styles/components/slider";
@import "app/styles/components/badges";
@import "app/styles/components/badge-state";
@import "app/styles/components/component-badge";
@import "app/styles/components/tables";
@import "app/styles/components/growl";
@import "app/styles/components/login";
@import "app/styles/components/pod";
@import "app/styles/components/metrics";
@import "app/styles/components/github-avatar";
@import "app/styles/components/spark-line";
@import "app/styles/components/graph-area";
@import "app/styles/components/percent-gauge";
@import "app/styles/components/tooltip";
@import "app/styles/components/container-shell";
@import "app/styles/components/modals";
@ -52,6 +50,7 @@
@import "app/styles/components/pagination";
@import "app/styles/components/theme-toggle";
@import "app/styles/components/progress";
@import "app/styles/components/percent-gauge";
@import "app/styles/components/namespace-app";
@import "app/styles/components/page-header";
@import "app/styles/components/over-hr";

View File

@ -91,3 +91,7 @@ hr {
border-top: 1px solid $border;
border-bottom: 1px solid $border;
}
.flatpickr-months .flatpickr-month {
height: 36px !important;
}

View File

@ -289,3 +289,15 @@
.link[disabled] {
pointer-events: none;
}
a.disabled {
cursor: default;
transition: ease-in-out all .25s;
}
.sticky {
position: sticky;
top: 0px;
display:inline-block;
vertical-align: top;
}

View File

@ -1,23 +0,0 @@
.component-badge {
.healthy.circle{
fill: $healthy-circle-color;
}
.healthy.background{
fill: $healthy-background-color;
}
.healthy.text{
fill: $healthy-text-color;
stroke: $healthy-text-color;
}
.unhealthy.circle{
fill: $unhealthy-circle-color;
}
.unhealthy.background{
fill: $unhealthy-background-color;
}
.unhealthy.text{
fill: $unhealthy-text-color;
stroke: $unhealthy-text-color;
}
}

View File

@ -1,82 +0,0 @@
.graph-area {
font-size: 12px;
.line {
fill: none;
stroke: $link-color;
stroke-width: 2px;
}
.axis {
fill: #9da0ae;
}
.axis line,
.axis path {
fill: none;
}
.odd {
fill: $mid-grey;
fill-opacity: .1;
}
.even {
fill: $dark-grey;
fill-opacity: .1;
}
.area {
fill-opacity: .7;
}
.hover-line {
stroke: #9da0ae;
}
.hover-label {
position: absolute;
pointer-events: none;
top: 0px;
z-index: 20;
}
.graph-area-tooltip {
border-collapse: collapse;
border-spacing: 0;
background-color: #fff;
empty-cells: show;
box-shadow: 7px 7px 12px -9px #777;
}
.graph-area-tooltip th {
background-color: #aaa;
font-size: 14px;
padding: 2px 5px;
text-align: left;
color: #FFF;
}
.graph-area-tooltip tr {
border: 1px solid #CCC;
}
.graph-area-tooltip td {
font-size: 13px;
height: 25px;
padding: 3px 6px;
background-color: #fff;
border-left: 1px dotted #999;
}
.graph-area-tooltip td > span {
display: inline-block;
width: 10px;
height: 10px;
margin-right: 6px;
}
.graph-area-tooltip td.value {
text-align: right;
}
}

View File

@ -0,0 +1,51 @@
$accordion-border: darken($bg-default, 5) !default;
.graph-area {
height: 200px
}
.grafana-link {
a:hover {
text-decoration: none;
}
margin: 10px;
}
.metrics-summary {
.metrics-accordion {
@include no-select;
@extend .clearfix;
background: transparent;
border: solid $accordion-border thin;
display: table;
vertical-align: middle;
width: 100%;
.title {
display: table-cell;
height: 100%;
vertical-align: middle;
background: transparent;
text-align: left;
}
div {
&:focus {
outline: 0 !important;
}
}
.btn {
&:focus {
outline: 0 !important;
}
padding: 15px 25px;
}
}
.metrics-accordion-content {
position: relative;
padding: 20px;
border: solid 1px $border;
border-top: 0;
}
}

View File

@ -81,6 +81,10 @@
.elasticsearch {
background-image: url('images/providers/elasticsearch.svg');
}
.prometheus {
background-image: url('images/providers/prometheus.svg');
background-size: 80px 80px;
}
.splunk {
background-image: url('images/providers/splunk.svg');
}

View File

@ -36,3 +36,7 @@ $progress-height : 10px !default;
background-color: lighten($success, 50);
border: solid lighten($success, 30) 1px;
}
.progress-bar-primary {
background-color: $primary;
}

View File

@ -108,3 +108,10 @@
overflow-y: scroll;
margin-bottom: 15px;
}
.grafana {
background-image: url('images/providers/grafana.svg');
background-size: 20px;
width: 20px;
height: 20px;
}

View File

@ -1,7 +1,7 @@
<div class="tooltip-content-inner tooltip-dot">
{{action-menu model=model showPrimary=false inTooltip=true class="pull-right tooltip-more-actions"}}
<div class="display-name"><a href="{{href-to 'container' model.id}}">{{displayName}}</a></div>
<div class="display-name"><a href="{{href-to 'pod' model.id}}">{{displayName}}</a></div>
<div class="clearfix">
<div class="pull-right">{{badge-state classNames="btn-xs" model=model}}</div>

View File

@ -2,11 +2,15 @@ import { computed } from '@ember/object';
import { alias } from '@ember/object/computed';
import Controller from '@ember/controller';
import { get } from '@ember/object';
import { inject as service } from '@ember/service';
export default Controller.extend({
scope: service(),
launchConfig: null,
service: alias('model.workload'),
service: alias('model.workload'),
monitoringEnabled: alias('scope.currentCluster.isMonitoringReady'),
displayEnvironmentVars: computed('service.launchConfig.environment', function() {
var envs = [];

View File

@ -3,11 +3,6 @@
<h1 class="vertical-middle">
{{t 'servicePage.header' name=service.displayName}}
</h1>
{{#if (and false service.canHaveContainers (not service.isSelector))}}
<div class="vertical-middle" style="height: 30px;">
{{info-multi-stats model=service largeTargetId="largeStats"}}
</div>
{{/if}}
</div>
<div class="right-buttons">
{{badge-state model=service}}
@ -15,9 +10,6 @@
</div>
</section>
<div id="largeStats">
</div>
{{#if service.description}}
{{banner-message color='bg-secondary mb-0 mt-10' message=(linkify service.description)}}
{{/if}}
@ -26,211 +18,225 @@
<div class="{{service.stateColor}}"><p>{{uc-first service.transitioningMessage}}</p></div>
{{/if}}
<section>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.namespace'}}</label>
{{service.namespace.displayName}}
</div>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.namespace'}}:</label>
{{service.namespace.displayName}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.image'}}:</label>
{{launchConfig.image}} {{copy-to-clipboard clipboardText=launchConfig.image size="small"}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.type'}}:</label>
{{service.displayType}}
</div>
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.image'}}</label>
{{launchConfig.image}} {{copy-to-clipboard clipboardText=launchConfig.image size="small"}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.type'}}</label>
{{service.displayType}}
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.endpoints'}}:</label>
{{#if service.displayEndpoints}}
<label class="clip text-small vertical-middle">
{{service.displayEndpoints}}
</label>
{{else}}
{{t 'generic.na'}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.scale'}}:</label>
<span class="pr-5">
{{service.displayScale}}
</span>
<div class="btn-group btn-group-xs p-0">
<button class="btn btn-xs bg-primary" {{action "scaleDown" target=service}} disabled={{not service.canScaleDown}}><i class="icon icon-minus icon-fw"/></button>
<button style="margin-left: -1px;" class="btn btn-xs bg-primary" {{action "scaleUp" target=service}} disabled={{not service.canScaleUp}}><i class="icon icon-plus icon-fw"/></button>
</div>
</div>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.endpoints'}}</label>
{{#if service.displayEndpoints}}
<label class="clip text-small">
{{service.displayEndpoints}}
</label>
{{else}}
{{t 'generic.na'}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.scale'}}</label>
<span class="pr-5">
{{service.displayScale}}
</span>
<div class="btn-group btn-group-xs p-0">
<button class="btn btn-xs bg-primary" {{action "scaleDown" target=service}} disabled={{not service.canScaleDown}}><i class="icon icon-minus icon-fw"/></button>
<button style="margin-left: -1px;" class="btn btn-xs bg-primary" {{action "scaleUp" target=service}} disabled={{not service.canScaleUp}}><i class="icon icon-plus icon-fw"/></button>
</div>
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.created'}}</label>
{{date-calendar service.created}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.created'}}:</label>
{{date-calendar service.created}}
</div>
</section>
</div>
<section>
{{#accordion-list as |al expandFn|}}
{{accordion-pod
pods=service.pods
{{#accordion-list as |al expandFn|}}
{{accordion-pod
pods=service.pods
expandAll=al.expandAll
expandFn=expandFn
}}
{{#if scope.currentCluster.isMonitoringReady}}
{{#metrics-summary
expandAll=al.expandAll
expandFn=expandFn
grafanaUrl=service.grafanaUrl
title=(t "metricsAction.sections.workload")
}}
{{workload-metrics resourceId=service.id}}
{{/metrics-summary}}
{{/if}}
{{resource-event-list
resourceType=service.displayType
expandAll=al.expandAll
expandFn=expandFn
namespaceId=service.namespaceId
name=service.name
kind=service.type
}}
{{resource-event-list
resourceType=service.displayType
expandAll=al.expandAll
expandFn=expandFn
namespaceId=service.namespaceId
name=service.name
kind=service.type
}}
{{#if (or (eq service.type 'job') (eq service.type 'cronJob'))}}
{{container/form-job-config
workload=service
scaleMode=service.type
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{/if}}
{{#if service.canHaveEnvironment}}
{{#accordion-list-item
title=(t 'formEnvVar.title')
detail=(t 'formEnvVar.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
{{form-env-var
model=displayEnvironmentVars
}}
<hr class="mt-30 mb-30" />
{{container/form-sources
namespace=service.namespace
sources=launchConfig.environmentFrom
editing=false
}}
{{/accordion-list-item}}
{{/if}}
{{#accordion-list-item
title=(t 'containerPage.portsTab.header')
detail=(t 'containerPage.portsTab.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
{{container/form-ports
initialPorts=service.launchConfig.ports
editing=false
}}
{{/accordion-list-item}}
{{container/form-scheduling
initialHostId=model.workload.nodeId
service=service
scheduling=service.scheduling
editing=false
expandAll=al.expandAll
expandFn=expandFn
classNames="accordion"
}}
{{#if service.canHaveHealthCheck}}
{{#accordion-list-item
title=(t 'formHealthCheck.title')
detail=(t 'formHealthCheck.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
<div class="row">
<div class="col span-6 mt-0 mb-0">
<label class="acc-label">{{t 'formHealthCheck.readiness'}}</label>
</div>
<div class="col span-6 mt-0 mb-0">
{{#if service.launchConfig.livenessProbe}}
<label class="acc-label">{{t 'formHealthCheck.liveness'}}</label>
{{/if}}
</div>
</div>
<div class="row">
<div class="col {{if service.launchConfig.livenessProbe 'span-6' 'span-12'}}">
{{form-healthcheck
initialCheck=service.launchConfig.readinessProbe
editing=false
}}
</div>
{{#if service.launchConfig.livenessProbe}}
<div class="col span-6">
{{form-healthcheck
initialCheck=service.launchConfig.livenessProbe
editing=false
isLiveness=true
}}
</div>
{{/if}}
</div>
{{/accordion-list-item}}
{{/if}}
{{container/form-upgrade
{{#if (or (eq service.type 'job') (eq service.type 'cronJob'))}}
{{container/form-job-config
workload=service
scaleMode=service.type
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{/if}}
{{container/form-volumes
launchConfig=launchConfig
workload=service
namespace=service.namespace
loggingEnabled=true
editing=false
{{#if service.canHaveEnvironment}}
{{#accordion-list-item
title=(t 'formEnvVar.title')
detail=(t 'formEnvVar.detail')
expandAll=al.expandAll
expandFn=expandFn
expand=(action expandFn)
}}
{{container/form-command
tagName=''
instance=launchConfig
service=model.workload
initialLabels=launchConfig.labels
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{container/form-networking
classNames="accordion-wrapper"
instance=launchConfig
service=service
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{form-labels-annotations
classNames="accordion-wrapper"
model=service
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{#if service.canChangeSecurity}}
{{container/form-security
instance=launchConfig
editing=false
expandAll=al.expandAll
expandFn=expandFn
{{form-env-var
model=displayEnvironmentVars
}}
{{/if}}
<hr class="mt-30 mb-30" />
{{container/form-sources
namespace=service.namespace
sources=launchConfig.environmentFrom
editing=false
}}
{{/accordion-list-item}}
{{/if}}
{{/accordion-list}}
</section>
{{#accordion-list-item
title=(t 'containerPage.portsTab.header')
detail=(t 'containerPage.portsTab.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
{{container/form-ports
initialPorts=service.launchConfig.ports
editing=false
}}
{{/accordion-list-item}}
{{container/form-scheduling
initialHostId=model.workload.nodeId
service=service
scheduling=service.scheduling
editing=false
expandAll=al.expandAll
expandFn=expandFn
classNames="accordion"
}}
{{#if service.canHaveHealthCheck}}
{{#accordion-list-item
title=(t 'formHealthCheck.title')
detail=(t 'formHealthCheck.detail')
expandAll=al.expandAll
expand=(action expandFn)
}}
<div class="row">
<div class="col span-6 mt-0 mb-0">
<label class="acc-label">{{t 'formHealthCheck.readiness'}}</label>
</div>
<div class="col span-6 mt-0 mb-0">
{{#if service.launchConfig.livenessProbe}}
<label class="acc-label">{{t 'formHealthCheck.liveness'}}</label>
{{/if}}
</div>
</div>
<div class="row">
<div class="col {{if service.launchConfig.livenessProbe 'span-6' 'span-12'}}">
{{form-healthcheck
initialCheck=service.launchConfig.readinessProbe
editing=false
}}
</div>
{{#if service.launchConfig.livenessProbe}}
<div class="col span-6">
{{form-healthcheck
initialCheck=service.launchConfig.livenessProbe
editing=false
isLiveness=true
}}
</div>
{{/if}}
</div>
{{/accordion-list-item}}
{{/if}}
{{container/form-upgrade
workload=service
scaleMode=service.type
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{container/form-volumes
launchConfig=launchConfig
workload=service
namespace=service.namespace
loggingEnabled=true
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{container/form-command
tagName=''
instance=launchConfig
service=model.workload
initialLabels=launchConfig.labels
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{container/form-networking
classNames="accordion-wrapper"
instance=launchConfig
service=service
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{form-labels-annotations
classNames="accordion-wrapper"
model=service
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{#if service.canChangeSecurity}}
{{container/form-security
instance=launchConfig
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{/if}}
{{#if scope.currentCluster.enableClusterMonitoring}}
{{container/form-custom-metrics
classNames="accordion-wrapper"
workload=service
editing=false
expandAll=al.expandAll
expandFn=expandFn
}}
{{/if}}
{{/accordion-list}}

View File

@ -174,11 +174,19 @@ export default Controller.extend({
);
}),
systemLibrary: computed('model.@each.{name,url,branch}', function() {
return this.findMatch(
C.CATALOG.SYSTEM_LIBRARY_KEY,
C.CATALOG.SYSTEM_LIBRARY_VALUE,
);
}),
custom: computed('library', 'helmStable', 'helmIncubator', function() {
const hide = [
get(this, 'library'),
get(this, 'helmStable'),
get(this, 'helmIncubator')
get(this, 'helmIncubator'),
get(this, 'systemLibrary'),
];
return get(this, 'model').filter((x) => !hide.includes(x));

View File

@ -39,7 +39,6 @@ const Eng = Engine.extend({
'authenticated.project',
'authenticated.prefs',
'authenticated.cluster.nodes',
'authenticated.cluster.nodes.node',
'authenticated.cluster.security.members.index',
'logout'
]

View File

@ -0,0 +1,13 @@
import Route from '@ember/routing/route';
import { set } from '@ember/object';
import { inject as service } from '@ember/service';
import { on } from '@ember/object/evented';
import C from 'ui/utils/constants';
export default Route.extend({
session: service(),
setDefaultRoute: on('activate', function() {
set(this, `session.${ C.SESSION.CLUSTER_ROUTE }`, 'authenticated.cluster.monitoring.cluster-setting');
}),
});

View File

@ -0,0 +1,4 @@
<section class="header clearfix">
<h1 class="pull-left">{{t 'monitoringPage.cluster.title'}}</h1>
</section>
{{enable-monitoring}}

View File

@ -0,0 +1,8 @@
import Component from '@ember/component';
import layout from './template';
export default Component.extend({
layout,
cluster: null,
});

View File

@ -0,0 +1,39 @@
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'clustersPage.provider.label'}}:</label>
<span>{{cluster.displayProvider}}</span>
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'clustersPage.version.label'}}:</label>
<span>{{cluster.version.gitVersion}}</span>
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'clustersPage.nodes.label'}}:</label>
<span>
{{#if (eq cluster.state "inactive")}}
{{t 'clusterRow.noHosts'}}
{{else}}
{{cluster.machines.length}}
{{/if}}
</span>
</div>
</div>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.cpu'}}:</label>
<span>{{cluster.cpuTotal}}</span>
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.memory'}}:</label>
<span>{{cluster.memoryTotal}}</span>
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.created'}}:</label>
<span>{{date-calendar model.cluster.created}}</span>
</div>
</div>

View File

@ -0,0 +1,12 @@
import Component from '@ember/component';
import layout from './template';
import { inject as service } from '@ember/service';
export default Component.extend({
scope: service(),
settings: service(),
grafana: service(),
layout,
classNames: 'row',
});

View File

@ -0,0 +1,42 @@
{{#accordion-list as |al expandFn|}}
{{#metrics-summary
expandAll=al.expandAll
expandFn=expandFn
grafanaUrl=scope.currentCluster.grafanaUrl
title=(t "clusterDashboard.sections.cluster")
}}
{{cluster-metrics}}
{{/metrics-summary}}
{{#if scope.currentCluster.isRKE}}
{{#metrics-summary
expandAll=al.expandAll
expandFn=expandFn
grafanaUrl=grafana.etcdUrl
classNames='mt-20'
title=(t "clusterDashboard.sections.etcd")
}}
{{etcd-metrics}}
{{/metrics-summary}}
{{/if}}
{{#metrics-summary
expandAll=al.expandAll
expandFn=expandFn
grafanaUrl=grafana.k8sUrl
classNames='mt-20'
title=(t "clusterDashboard.sections.k8s")
}}
{{k8s-metrics}}
{{/metrics-summary}}
{{#metrics-summary
expandAll=al.expandAll
expandFn=expandFn
grafanaUrl=grafana.rancherUrl
classNames='mt-20 mb-20'
title=(t "clusterDashboard.sections.rancher" appName=settings.appName)
}}
{{rancher-metrics noDataLabel='clusterDashboard.noRancherComponents'}}
{{/metrics-summary}}
{{/accordion-list}}

View File

@ -0,0 +1,56 @@
import C from 'ui/utils/constants';
import { on } from '@ember/object/evented';
import Component from '@ember/component';
import { get, computed, observer, setProperties } from '@ember/object';
import { alias } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import layout from './template';
export default Component.extend({
intl: service(),
scope: service(),
grafana: service(),
globalStore: service(),
router: service(),
layout,
nodes: null,
components: null,
monitoringEnabled: alias('scope.currentCluster.enableClusterMonitoring'),
componentStatuses: alias('scope.currentCluster.componentStatuses'),
actions: {
enalbeMonitoring() {
get(this, 'router').transitionTo('authenticated.cluster.monitoring.cluster-setting');
},
},
setComponents: on('init', observer('componentStatuses.@each.conditions', 'nodes.@each.{state}', function() {
setProperties(this, {
etcdHealthy: this.isHealthy('etcd'),
controllerHealthy: this.isHealthy('controller-manager'),
schedulerHealthy: this.isHealthy('scheduler'),
nodesHealthy: get(this, 'inactiveNodes.length') === 0
})
})),
showDashboard: computed('scope.currentCluster.isReady', 'nodes.[]', function() {
return get(this, 'nodes').length && get(this, 'scope.currentCluster.isReady')
}),
inactiveNodes: computed('nodes.@each.state', function() {
return get(this, 'nodes').filter( (n) => C.ACTIVEISH_STATES.indexOf(get(n, 'state')) === -1 );
}),
unhealthyComponents: computed('componentStatuses.@each.conditions', function() {
return (get(this, 'componentStatuses') || [])
.filter((s) => !s.conditions.any((c) => c.status === 'True'));
}),
isHealthy(field) {
return (get(this, 'componentStatuses') || [])
.filter((s) => s.name.startsWith(field))
.any((s) => s.conditions.any((c) => c.status === 'True'));
}
});

View File

@ -0,0 +1,54 @@
{{#if showDashboard}}
{{cluster-basic-info cluster=cluster}}
{{#unless scope.currentCluster.isMonitoringReady}}
{{#if monitoringEnabled}}
<div class="row mt-0 mb-0">
<div class="pull-right text-small text-error">
{{t 'clusterDashboard.monitoringNotReady'}}
</div>
</div>
{{else}}
<div class="row mt-0 mb-0">
<div class="pull-right text-small">
<button class="btn bg-transparent p-0" {{action "enalbeMonitoring"}}>{{t 'clusterDashboard.enalbeMonitoring'}}</button>
</div>
</div>
{{/if}}
{{/unless}}
{{nodes-reservation nodes=nodes}}
<div class="row mt-0">
{{k8s-component-status label='clusterDashboard.etcd' healthy=etcdHealthy url=grafana.etcdUrl}}
{{k8s-component-status label='clusterDashboard.controllerManager' healthy=controllerHealthy url=grafana.controllerUrl}}
{{k8s-component-status label='clusterDashboard.scheduler' healthy=schedulerHealthy url=grafana.schedulerUrl}}
{{k8s-component-status label='clusterDashboard.node' healthy=nodesHealthy url=grafana.nodesUrl}}
</div>
<div class="row">
{{#each unhealthyComponents as |component|}}
{{#banner-message icon='icon-alert' color='text-left bg-error mt-30'}}
<p>{{t 'clusterDashboard.alert.component' component=component.name}}</p>
{{/banner-message}}
{{/each}}
{{#each inactiveNodes as |node|}}
{{#banner-message icon='icon-alert' color='text-left bg-error mt-30'}}
<p>{{t 'clusterDashboard.alert.node' node=node.displayName}}</p>
{{/banner-message}}
{{/each}}
</div>
{{#if scope.currentCluster.isMonitoringReady}}
{{cluster-dashboard-tabs dashboards=dashboards}}
{{/if}}
{{else}}
{{empty-table
resource="container"
showNew=scope.currentCluster.canAddNode
newRoute="authenticated.cluster.nodes.templates"
newTranslationKey="nodesPage.addNode"
disabled=(rbac-prevents resource="machine" scope="global" permission="create")
ctx=""
}}
{{/if}}

View File

@ -0,0 +1,9 @@
import Component from '@ember/component';
import Metrics from 'shared/mixins/metrics';
import layout from './template';
export default Component.extend(Metrics, {
layout,
filters: { resourceType: 'cluster' },
});

View File

@ -0,0 +1,7 @@
<section class="mt-15">
{{metrics-action
queryAction="query"
state=state
}}
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
</section>

View File

@ -0,0 +1,134 @@
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { set, get, computed } from '@ember/object';
import { alias } from '@ember/object/computed';
import layout from './template';
const PROMETHEUS = 'prometheus';
const NONE = 'none';
export default Component.extend({
scope: service(),
growl: service(),
layout,
selected: NONE,
level: 'cluster',
justDeployed: false,
enablePrometheusPersistence: false,
enableGrafanaPersistence: false,
prometheusPersistenceSize: '50Gi',
grafanaPersistenceSize: '10Gi',
prometheusStorageClass: null,
grafanaStorageClass: null,
nodeSelectors: null,
retention: 360,
port: 9100,
cluster: alias('scope.currentCluster'),
project: alias('scope.currentProject'),
init() {
this._super(...arguments);
set(this, 'selected', get(this, 'enabled') ? PROMETHEUS : NONE);
},
actions: {
changeSelected(selected) {
set(this, 'selected', selected);
},
save(cb) {
if ( get(this, 'selected') === NONE ) {
this.send('disable', cb);
} else {
this.send('enable', cb);
}
},
enable(cb) {
const resource = get(this, 'level') === 'cluster' ? get(this, 'cluster') : get(this, 'project');
let answers;
if ( get(this, 'level') === 'cluster' ) {
answers = {
'_key-Data_Retention': 'prometheus.retention',
'_key-Node_Exporter_Host_Port': 'exporter-node.ports.metrics.port',
'_key-Grafana_Storage_Enabled': 'grafana.persistence.enabled',
'_key-Grafana_Storage_Size': 'grafana.persistence.size',
'_key-Grafana_Storage_Class': 'grafana.persistence.storageClass',
'_key-Prometheus_Storage_Enabled': 'prometheus.persistence.enabled',
'_key-Prometheus_Storage_Size': 'prometheus.persistence.size',
'_key-Prometheus_Storage_Class': 'prometheus.persistence.storageClass',
'_tpl-Node_Selector': 'nodeSelector#(prometheus,grafana,exporter-kube-state)',
'prometheus.retention': `${ get(this, 'retention') }h`,
'exporter-node.ports.metrics.port': `${ get(this, 'port') }`,
'grafana.persistence.enabled': `${ get(this, 'enableGrafanaPersistence') }`,
'prometheus.persistence.enabled': `${ get(this, 'enablePrometheusPersistence') }`,
'prometheus.persistence.storageClass': `${ get(this, 'prometheusStorageClass') === null ? 'default' : get(this, 'prometheusStorageClass') }`,
'grafana.persistence.storageClass': `${ get(this, 'grafanaStorageClass') === null ? 'default' : get(this, 'grafanaStorageClass') }`,
'grafana.persistence.size': `${ get(this, 'grafanaPersistenceSize') }`,
'prometheus.persistence.size': `${ get(this, 'prometheusPersistenceSize') }`,
};
} else {
answers = {
'_key-Grafana_Storage_Enabled': 'grafana.persistence.enabled',
'_key-Grafana_Storage_Size': 'grafana.persistence.size',
'_key-Grafana_Storage_Class': 'grafana.persistence.storageClass',
'_tpl-Node_Selector': 'nodeSelector#(grafana)',
'grafana.persistence.enabled': `${ get(this, 'enableGrafanaPersistence') }`,
'grafana.persistence.size': `${ get(this, 'grafanaPersistenceSize') }`,
'grafana.persistence.storageClass': `${ get(this, 'grafanaStorageClass') === null ? 'default' : get(this, 'grafanaStorageClass') }`,
}
}
(get(this, 'nodeSelectors') || []).forEach((selector) => {
answers[`nodeSelector.${ get(selector, 'key') }`] = get(selector, 'value');
});
resource.doAction('enableMonitoring', { answers }).then(() => {
set(this, 'justDeployed', true);
cb(true);
}).catch((err) => {
get(this, 'growl').fromError(err);
cb(false);
});
},
disable(cb) {
const resource = get(this, 'level') === 'cluster' ? get(this, 'cluster') : get(this, 'project');
resource.doAction('disableMonitoring').then(() => {
cb(true);
}).catch((err) => {
get(this, 'growl').fromError(err);
cb(false);
});
}
},
enabled: computed('cluster.enableClusterMonitoring', 'project.enableProjectMonitoring', 'level', function() {
return get(this, 'level') === 'cluster' ? get(this, 'cluster.enableClusterMonitoring') : get(this, 'project.enableProjectMonitoring');
}),
isReady: computed('cluster.isMonitoringReady', 'project.isMonitoringReady', 'level', function() {
return get(this, 'level') === 'cluster' ? get(this, 'cluster.isMonitoringReady') : get(this, 'project.isMonitoringReady');
}),
status: computed('isReady', function() {
if ( get(this, 'isReady') ) {
return 'ready';
} else if ( get(this, 'justDeployed') ) {
return 'justDeployed'
} else {
return 'notReady'
}
}),
saveDisabled: computed('selected', 'enabled', function() {
return (get(this, 'selected') === NONE && !get(this, 'enabled')) || (get(this, 'selected') === PROMETHEUS && get(this, 'enabled'));
}),
});

View File

@ -0,0 +1,131 @@
{{#if enabled}}
{{#banner-message color=(if (or isReady justDeployed) 'bg-success' 'bg-error') icon=(if (or isReady justDeployed) 'icon-check' 'icon-alert')}}
{{#if (eq level 'cluster')}}
<p>
{{t (concat 'monitoringPage.cluster.' status) htmlSafe=true}} <a href="{{href-to 'apps-tab' cluster.systemProject.id}}">{{cluster.systemProject.displayName}}</a> {{t 'generic.project'}}.
</p>
{{else}}
<p>
{{t (concat 'monitoringPage.project.' status) htmlSafe=true}}
</p>
{{/if}}
{{/banner-message}}
{{else}}
{{banner-message color='bg-info' icon='icon-info' message=(t (concat 'monitoringPage.' level '.info') htmlSafe=true)}}
{{/if}}
<div class="row nav nav-boxes checked-current mv-30 logging-targets">
<a href="#" alt='none' class='col span-2 nav-box-item driver {{if (eq selected 'none') 'active' ''}}' {{action "changeSelected" "none"}}>
<div class="none"></div>
<p>{{t 'monitoringPage.config.types.none'}}</p>
</a>
<a href="#" alt='prometheus' class='col span-2 nav-box-item driver {{if enabled 'current' ''}} {{if (eq selected 'prometheus') 'active' ''}}' {{action "changeSelected" "prometheus"}}>
<div class="prometheus"></div>
<p>{{t 'monitoringPage.config.types.prometheus'}}</p>
</a>
</div>
<section>
<div class="text-center text-muted">
{{#if (eq selected 'none')}}
{{#if enabled}}
{{t (concat 'monitoringPage.' level '.toDisable')}}
{{else}}
{{t (concat 'monitoringPage.' level '.disabled')}}
{{/if}}
{{else if (eq selected 'prometheus')}}
{{#if enabled}}
{{t (concat 'monitoringPage.toUpdate.' level) htmlSafe=true}}
{{else}}
{{t 'monitoringPage.prometheus'}}
{{/if}}
{{/if}}
</div>
<hr/>
</section>
{{#if (and (eq selected 'prometheus') (not enabled))}}
<h2 class="mt-30"> {{t 'monitoringPage.config.header'}} </h2>
<hr/>
<div class="box mb-10">
<div class="row">
<div class="col span-6">
<label class="acc-label">{{t 'monitoringPage.config.retention.label'}}</label>
<div class="input-group">
{{input-integer min=0 value=retention}}
<span class="input-group-addon bg-default">{{t 'generic.hours'}}</span>
</div>
</div>
{{#if (eq level 'cluster')}}
<div class="col span-6">
<label class="acc-label">{{t 'monitoringPage.config.nodeexporter.label'}}</label>
{{input-integer min=1 min=65535 value=port}}
</div>
{{else}}
<div class="col span-6">
<label class="acc-label">{{t 'monitoringPage.config.grafana.enablePersistence.label'}}</label>
{{schema/input-boolean value=enableGrafanaPersistence}}
</div>
{{/if}}
</div>
{{#if (eq level 'cluster')}}
<div class="row">
<div class="col span-6">
<label class="acc-label">{{t 'monitoringPage.config.prometheus.enablePersistence.label'}}</label>
{{schema/input-boolean value=enablePrometheusPersistence}}
</div>
<div class="col span-6">
<label class="acc-label">{{t 'monitoringPage.config.grafana.enablePersistence.label'}}</label>
{{schema/input-boolean value=enableGrafanaPersistence}}
</div>
</div>
{{/if}}
{{#if enablePrometheusPersistence}}
<div class="row">
<div class="col span-6">
<label class="acc-label">{{t 'monitoringPage.config.prometheus.size.label'}}</label>
{{schema/input-string value=prometheusPersistenceSize placeholder=(t 'monitoringPage.config.prometheus.size.placeholder')}}
</div>
<div class="col span-6">
<label class="acc-label">{{t 'monitoringPage.config.prometheus.storageClass.label'}}</label>
{{schema/input-storageclass value=prometheusStorageClass}}
</div>
</div>
{{/if}}
{{#if enableGrafanaPersistence}}
<div class="row">
<div class="col span-6">
<label class="acc-label">{{t 'monitoringPage.config.grafana.size.label'}}</label>
{{schema/input-string value=grafanaPersistenceSize placeholder=(t 'monitoringPage.config.grafana.size.placeholder')}}
</div>
<div class="col span-6">
<label class="acc-label">{{t 'monitoringPage.config.grafana.storageClass.label'}}</label>
{{schema/input-storageclass value=grafanaStorageClass}}
</div>
</div>
{{/if}}
<div class="row">
<div class="col span-12">
<label class="acc-label">{{t 'monitoringPage.nodeSelector.helpText'}}</label>
{{form-key-value
changedArray=(action (mut nodeSelectors))
allowEmptyValue=true
addActionLabel="monitoringPage.nodeSelector.addSelectorLabel"
}}
</div>
</div>
</div>
{{/if}}
<div class="text-center">
{{save-cancel
saveDisabled=saveDisabled
cancelDisabled=true
editing=true
save="save"
}}
</div>

View File

@ -0,0 +1,31 @@
import Component from '@ember/component';
import Metrics from 'shared/mixins/metrics';
import layout from './template';
import { get, set, observer } from '@ember/object';
export default Component.extend(Metrics, {
layout,
filters: { resourceType: 'etcd' },
singleStatsDidChange: observer('single.[]', function() {
const leaderChange = (get(this, 'single') || []).findBy('graph.title', 'etcd-leader-change');
if ( leaderChange ) {
set(this, 'leaderChange', get(leaderChange, 'series.firstObject.points.firstObject.firstObject'));
}
const hasLeader = (get(this, 'single') || []).findBy('graph.title', 'etcd-server-leader-sum');
if ( hasLeader ) {
set(this, 'hasLeader', get(hasLeader, 'series.firstObject.points.firstObject.firstObject') === 1);
}
const failedProposals = (get(this, 'single') || []).findBy('graph.title', 'etcd-server-failed-proposal');
if ( failedProposals ) {
set(this, 'failedProposals', get(failedProposals, 'series.firstObject.points.firstObject.firstObject'));
}
})
});

View File

@ -0,0 +1,35 @@
<section>
{{metrics-action
queryAction="query"
state=state
}}
{{#if single.length}}
<div class="row">
<div class="col span-12 pl-20">
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'etcd.hasLeader.label'}}:</label>
{{#if hasLeader}}
{{t 'generic.yes'}}
{{else}}
{{t 'generic.no'}}
{{/if}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'etcd.leaderChange.label'}}:</label>
{{leaderChange}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'etcd.failedProposals.label'}}:</label>
{{failedProposals}}
</div>
</div>
</div>
</div>
{{/if}}
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
</section>

View File

@ -0,0 +1,30 @@
import Component from '@ember/component';
import layout from './template';
export default Component.extend({
layout,
data: null,
sortBy: 'time',
descending: true,
labelArray: null,
headers: [
{
name: 'host',
sort: ['host'],
translationKey: 'ingressResponse.host',
},
{
name: 'path',
sort: ['path', 'host'],
translationKey: 'ingressResponse.path',
},
{
name: 'time',
sort: ['time', 'path', 'host'],
translationKey: 'ingressResponse.responseTime',
},
],
});

View File

@ -0,0 +1,39 @@
{{#accordion-list-item
title=(t 'ingressResponse.title')
detail=(t 'ingressResponse.detail')
expandAll=expandAll
expand=(action expandFn)
expandOnInit=true
componentName='sortable-table'
as | parent |
}}
{{#component parent.intent
classNames="grid fixed mb-0 sortable-table"
bulkActions=false
rowActions=false
paging=false
search=true
sortBy=sortBy
stickyHeader=false
descending=descending
headers=headers
body=data
as |sortable kind inst|
}}
{{#if (eq kind "row")}}
<tr>
<td data-title="{{t 'ingressResponse.host'}}:" class="force-wrap">{{inst.host}}</td>
<td data-title="{{t 'ingressResponse.path'}}:" class="force-wrap">{{inst.path}}</td>
<td data-title="{{t 'ingressResponse.responseTime'}}:" class="force-wrap">{{inst.time}}</td>
</tr>
{{else if (eq kind "norows")}}
<tr>
<td colspan="{{sortable.fullColspan}}" class="text-center text-muted pt-20 pb-20">{{t 'ingressResponse.noData'}}</td>
</tr>
{{else if (eq kind "nomatch")}}
<tr>
<td colspan="{{sortable.fullColspan}}" class="text-center text-muted pt-20 pb-20">{{t 'ingressResponse.noMatch'}}</td>
</tr>
{{/if}}
{{/component}}
{{/accordion-list-item}}

View File

@ -0,0 +1,11 @@
import Component from '@ember/component';
import layout from './template';
export default Component.extend({
layout,
classNames: 'col span-3 mt-0',
label: null,
healthy: null,
url: null,
});

View File

@ -0,0 +1,12 @@
{{#banner-message icon=(if healthy 'icon-check' 'icon-alert') color=(if healthy 'bg-success mt-0' 'bg-error mt-0')}}
<p class="text-left">
{{t label}}
{{#if url}}
<span class="pull-right">
<a href={{url}} target="_blank" rel="nofollow noopener noreferrer">
<div class="grafana"></div>
</a>
</span>
{{/if}}
</p>
{{/banner-message}}

View File

@ -0,0 +1,37 @@
import Component from '@ember/component';
import Metrics from 'shared/mixins/metrics';
import layout from './template';
import { get, set, observer } from '@ember/object';
import { formatSecond } from 'shared/utils/util';
export default Component.extend(Metrics, {
layout,
filters: { displayResourceType: 'kube-component' },
singleStatsDidChange: observer('single.[]', function() {
const responseSeconds = (get(this, 'single') || []).findBy('graph.title', 'ingresscontroller-upstream-response-seconds');
if ( responseSeconds ) {
set(this, 'responseSeconds', (get(responseSeconds, 'series') || [])
.sortBy('series.firstObject.points.firstObject.firstObject').map((serie) => {
const name = get(serie, 'name') || '';
const tokens = name.substring(24, get(name, 'length') - 1).split(' ');
let host;
let path;
if ( get(tokens, 'length') === 2 ) {
host = tokens[0].substring(7, get(tokens[0], 'length'));
path = tokens[1].substring(5, get(tokens[1], 'length'));
}
return {
host,
path,
time: formatSecond(get(serie, 'points.firstObject.firstObject'))
}
}));
}
})
});

View File

@ -0,0 +1,20 @@
<section>
{{metrics-action
queryAction="query"
state=state
}}
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
{{#if single.length}}
{{#accordion-list as |al expandFn|}}
<div class="mt-20 text-left">
{{ingress-response-list
expandAll=al.expandAll
expandFn=expandFn
data=responseSeconds
}}
</div>
{{/accordion-list}}
{{/if}}
</section>

View File

@ -0,0 +1,8 @@
<div class="row">
{{#each conditions as |c|}}
<div class="col span-3">
{{banner-message color=(if c.healthy 'bg-success' 'bg-error') icon='icon-check' message=c.name}}
</div>
{{/each}}
</div>

View File

@ -0,0 +1,12 @@
import Component from '@ember/component';
import { alias } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import layout from './template';
export default Component.extend({
scope: service(),
layout,
model: null,
monitoringEnabled: alias('scope.currentCluster.enableClusterMonitoring'),
});

View File

@ -0,0 +1,98 @@
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.hostname'}}:</label>
{{model.node.hostname}} {{copy-to-clipboard clipboardText=model.node.hostname size="small"}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.ipAddress'}}:</label>
<div class="inline-block">{{node-ip model=model.node textMuted=false}}</div>
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.dockerVersion'}}:</label>
{{model.node.info.os.dockerVersion}}
</div>
</div>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.kubeletVersion'}}:</label>
{{model.node.info.kubernetes.kubeletVersion}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.kubeProxyVersion'}}:</label>
{{model.node.info.kubernetes.kubeProxyVersion}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'hostsPage.hostPage.operatingSystem'}}:</label>
{{model.node.osBlurb}} <span class="text-small text-muted">{{model.node.info.os.kernelVersion}}</span>
</div>
</div>
<div class="row banner bg-info basics">
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.cpu'}}:</label>
{{model.node.cpuTotal}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.memory'}}:</label>
{{model.node.memoryTotal}}
</div>
<div class="vertical-middle">
<label class="acc-label vertical-middle p-0">{{t 'generic.created'}}:</label>
{{date-calendar model.node.created}}
</div>
</div>
{{nodes-reservation nodes=model.nodes}}
{{node-conditions conditionsSource=model.node.conditions}}
<section>
{{#accordion-list as |al expandFn|}}
{{#if scope.currentCluster.isMonitoringReady}}
{{#metrics-summary
classNames="mb-20"
expandAll=al.expandAll
expandFn=expandFn
grafanaUrl=model.node.grafanaUrl
title=(t "clusterDashboard.sections.node")}}
{{node-metrics resourceId=model.node.id}}
{{/metrics-summary}}
{{/if}}
{{resource-condition-list
resourceType=(t 'generic.node')
conditions=model.node.conditions
expandAll=al.expandAll
expandFn=expandFn
}}
<div class="mt-20">
{{system-info-section
node=model.node
expandAll=al.expandAll
expandFn=expandFn
}}
</div>
<div class="mt-20">
{{labels-section
model=model.node
showKind=false
expandAll=al.expandAll
expandFn=expandFn
}}
</div>
<div class="mt-20">
{{annotations-section
model=model.node
expandAll=al.expandAll
expandFn=expandFn
}}
</div>
<div class="mt-20">
{{taints-section
taints=model.node.taints
expandAll=al.expandAll
expandFn=expandFn
}}
</div>
{{/accordion-list}}
</section>

View File

@ -0,0 +1,9 @@
import Component from '@ember/component';
import Metrics from 'shared/mixins/metrics';
import layout from './template';
export default Component.extend(Metrics, {
layout,
filters: { resourceType: 'node' },
});

View File

@ -0,0 +1,7 @@
<section>
{{metrics-action
queryAction="query"
state=state
}}
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
</section>

Some files were not shown because too many files have changed in this diff Show More