mirror of https://github.com/rancher/ui.git
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:
parent
f71b4cba6f
commit
b46b1cebb3
20
app/app.js
20
app/app.js
|
|
@ -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: {}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -25,4 +25,3 @@
|
|||
|
||||
{{component tooltip tooltipTemplate=tooltipTemplate class="container-tooltip" id="tooltip-base" role="tooltip" aria-hidden="false"}}
|
||||
{{modal-root}}
|
||||
{{! svg-gradients}}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -1 +0,0 @@
|
|||
Networking
|
||||
|
|
@ -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'];
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -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}}
|
||||
|
|
@ -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')
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
},
|
||||
});
|
||||
|
|
@ -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}}
|
||||
|
|
@ -31,7 +31,7 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
details(/* event*/) {
|
||||
var route = 'container';
|
||||
var route = 'pod';
|
||||
|
||||
if ( this.get('model.isVm') ) {
|
||||
route = 'virtualmachine';
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<section>
|
||||
{{metrics-action
|
||||
queryAction="query"
|
||||
allowDetail=false
|
||||
state=state
|
||||
}}
|
||||
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
|
||||
</section>
|
||||
|
|
@ -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'),
|
||||
});
|
||||
|
|
@ -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"> </th>
|
||||
<th>{{t 'formContainerLinks.alias.label'}}</th>
|
||||
<th width="40"> </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}}
|
||||
|
|
@ -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')));
|
||||
})
|
||||
});
|
||||
|
|
@ -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"> </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> </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> </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}}
|
||||
|
|
@ -15,6 +15,7 @@ export default Component.extend(NewOrEdit, ChildHook, {
|
|||
clusterStore: service(),
|
||||
intl: service(),
|
||||
prefs: service(),
|
||||
scope: service(),
|
||||
settings: service(),
|
||||
|
||||
layout,
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -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…</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…</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}}
|
||||
|
|
@ -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}}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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}}
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
|
|
|
|||
|
|
@ -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') });
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<section>
|
||||
{{metrics-action
|
||||
queryAction="query"
|
||||
state=state
|
||||
}}
|
||||
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
|
||||
</section>
|
||||
|
|
@ -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}}
|
||||
|
|
|
|||
|
|
@ -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 }}`;
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
||||
});
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
<div class="progress-bar {{colorClass}}"></div>
|
||||
<div class="progress-label">{{textLabel}}</div>
|
||||
|
|
@ -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}}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
||||
});
|
||||
|
|
@ -1 +0,0 @@
|
|||
{{yield}}
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -1 +0,0 @@
|
|||
{{yield}}
|
||||
|
|
@ -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') });
|
||||
},
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<section>
|
||||
{{metrics-action
|
||||
queryAction="query"
|
||||
state=state
|
||||
}}
|
||||
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
|
||||
</section>
|
||||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
|
|
@ -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],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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/`;
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
|
@ -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}}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -91,3 +91,7 @@ hr {
|
|||
border-top: 1px solid $border;
|
||||
border-bottom: 1px solid $border;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-month {
|
||||
height: 36px !important;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -230,4 +230,4 @@ fieldset[disabled] .btn {
|
|||
|
||||
.copy-btn.small {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 = [];
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}),
|
||||
});
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<section class="header clearfix">
|
||||
<h1 class="pull-left">{{t 'monitoringPage.cluster.title'}}</h1>
|
||||
</section>
|
||||
{{enable-monitoring}}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import Component from '@ember/component';
|
||||
import layout from './template';
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
|
||||
cluster: null,
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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',
|
||||
});
|
||||
|
|
@ -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}}
|
||||
|
|
@ -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'));
|
||||
}
|
||||
});
|
||||
|
|
@ -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}}
|
||||
|
|
@ -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' },
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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'));
|
||||
}),
|
||||
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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'));
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -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}}
|
||||
|
|
@ -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,
|
||||
});
|
||||
|
|
@ -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}}
|
||||
|
|
@ -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'))
|
||||
}
|
||||
}));
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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'),
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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' },
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue