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.project': 'authenticated.project',
|
||||||
'authenticated.prefs': 'authenticated.prefs',
|
'authenticated.prefs': 'authenticated.prefs',
|
||||||
'authenticated.cluster.nodes': 'authenticated.cluster.nodes',
|
'authenticated.cluster.nodes': 'authenticated.cluster.nodes',
|
||||||
'authenticated.cluster.nodes.node': 'authenticated.cluster.nodes.node',
|
|
||||||
'authenticated.cluster.security.members.index': 'authenticated.cluster.security.members.index',
|
'authenticated.cluster.security.members.index': 'authenticated.cluster.security.members.index',
|
||||||
'logout': 'logout'
|
'logout': 'logout'
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +98,6 @@ const App = Application.extend({
|
||||||
'authenticated.project': 'authenticated.project',
|
'authenticated.project': 'authenticated.project',
|
||||||
'authenticated.prefs': 'authenticated.prefs',
|
'authenticated.prefs': 'authenticated.prefs',
|
||||||
'authenticated.cluster.nodes': 'authenticated.cluster.nodes',
|
'authenticated.cluster.nodes': 'authenticated.cluster.nodes',
|
||||||
'authenticated.cluster.nodes.node': 'authenticated.cluster.nodes.node',
|
|
||||||
'authenticated.cluster.security.members.index': 'authenticated.cluster.security.members.index',
|
'authenticated.cluster.security.members.index': 'authenticated.cluster.security.members.index',
|
||||||
'logout': 'logout'
|
'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"}}
|
{{component tooltip tooltipTemplate=tooltipTemplate class="container-tooltip" id="tooltip-base" role="tooltip" aria-hidden="false"}}
|
||||||
{{modal-root}}
|
{{modal-root}}
|
||||||
{{! svg-gradients}}
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,7 @@
|
||||||
import Route from '@ember/routing/route';
|
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({
|
export default Route.extend({
|
||||||
globalStore: service(),
|
redirect() {
|
||||||
scope: service(),
|
this.replaceWith('authenticated.cluster.monitoring');
|
||||||
|
|
||||||
model() {
|
|
||||||
return this.get('globalStore').findAll('node')
|
|
||||||
.then((nodes) => {
|
|
||||||
const cluster = this.modelFor('authenticated.cluster');
|
|
||||||
|
|
||||||
return {
|
|
||||||
cluster,
|
|
||||||
nodes,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
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',
|
const VALID_ROUTES = ['authenticated.cluster.nodes', 'authenticated.cluster.storage.classes',
|
||||||
'authenticated.cluster.storage.persistent-volumes', 'authenticated.cluster.notifier',
|
'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.security.members.index', 'authenticated.cluster.projects',
|
||||||
'authenticated.cluster.quotas'];
|
'authenticated.cluster.quotas'];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
<section>
|
<section>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="row-same-height row-full-height">
|
<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="col span-6 col span-height col full-height col-top mb-20">
|
||||||
<div class="no-padding-margin" style="height: 100%;">
|
<div class="no-padding-margin full-height">
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<label class="text-muted">{{t 'certificatesPage.valid'}}:</label>
|
<label class="text-muted">{{t 'certificatesPage.valid'}}:</label>
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="no-padding-margin" style="height: 100%;">
|
<div class="no-padding-margin full-height">
|
||||||
<div>
|
<div>
|
||||||
<label class="section">{{t 'certificatesPage.domainNames.labelText'}}</label>
|
<label class="section">{{t 'certificatesPage.domainNames.labelText'}}</label>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ const VALID_ROUTES = ['apps-tab', 'authenticated.project.security.members.index'
|
||||||
'authenticated.project.ns', 'authenticated.project.certificates',
|
'authenticated.project.ns', 'authenticated.project.certificates',
|
||||||
'authenticated.project.secrets', 'authenticated.project.config-maps',
|
'authenticated.project.secrets', 'authenticated.project.config-maps',
|
||||||
'authenticated.project.registries', 'authenticated.project.alert',
|
'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, {
|
export default Route.extend(Preload, {
|
||||||
access: service(),
|
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, {
|
export default Component.extend(ManageLabels, {
|
||||||
layout,
|
layout,
|
||||||
model: null,
|
model: null,
|
||||||
initExpandAll: true,
|
expandOnInit: true,
|
||||||
sortBy: 'displayState',
|
sortBy: 'displayState',
|
||||||
showKind: true,
|
showKind: true,
|
||||||
descending: true,
|
descending: true,
|
||||||
|
|
@ -21,6 +21,7 @@ export default Component.extend(ManageLabels, {
|
||||||
name: 'name',
|
name: 'name',
|
||||||
sort: ['name'],
|
sort: ['name'],
|
||||||
translationKey: 'generic.name',
|
translationKey: 'generic.name',
|
||||||
|
width: 400
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'displayImage',
|
name: 'displayImage',
|
||||||
|
|
@ -31,18 +32,7 @@ export default Component.extend(ManageLabels, {
|
||||||
name: 'node',
|
name: 'node',
|
||||||
sort: ['displayName'],
|
sort: ['displayName'],
|
||||||
translationKey: 'generic.node',
|
translationKey: 'generic.node',
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'displayIp',
|
|
||||||
sort: ['displayIp'],
|
|
||||||
translationKey: 'generic.ipAddress',
|
|
||||||
width: 180
|
width: 180
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
expandAllObserve: function() {
|
|
||||||
let expandAll = this.get('expandAll');
|
|
||||||
|
|
||||||
this.set('initExpandAll', expandAll);
|
|
||||||
}.observes('expandAll')
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
{{#accordion-list-item
|
{{#accordion-list-item
|
||||||
title=(t 'podsSection.title')
|
title=(t 'podsSection.title')
|
||||||
detail=(t 'podsSection.detail')
|
detail=(t 'podsSection.detail')
|
||||||
expandAll=initExpandAll
|
expandAll=expandAll
|
||||||
expand=(action expandFn)
|
expand=(action expandFn)
|
||||||
|
expandOnInit=expandOnInit
|
||||||
componentName='sortable-table'
|
componentName='sortable-table'
|
||||||
as | parent |
|
as | parent |
|
||||||
}}
|
}}
|
||||||
|
|
@ -28,25 +29,25 @@
|
||||||
{{badge-state model=inst}}
|
{{badge-state model=inst}}
|
||||||
</td>
|
</td>
|
||||||
<td data-title="{{dt.name}}">
|
<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>
|
||||||
<td data-title="{{dt.displayImage}}">
|
<td data-title="{{dt.displayImage}}">
|
||||||
<small>{{inst.displayImage}}</small>
|
<small>{{inst.displayImage}}</small>
|
||||||
<p data-title="{{t 'generic.details'}}" class="text-small text-muted m-0 clip">
|
<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}}
|
{{t 'generic.createdDate' date=(date-from-now inst.created) htmlSafe=true}} / {{t 'generic.restarts'}} {{inst.restarts}}
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td data-title="{{dt.node}}">
|
<td data-title="{{dt.node}}">
|
||||||
{{#if (and inst.node.id inst.node.clusterId)}}
|
{{#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)}}
|
{{#if (or inst.node.externalIpAddress inst.node.ipAddress)}}
|
||||||
{{node-ip model=inst.node}}
|
{{node-ip model=inst.node}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
<td data-title="{{dt.displayIp}}">
|
|
||||||
{{inst.displayIp}}
|
|
||||||
</td>
|
|
||||||
<td data-title="{{dt.actions}}" class="actions">
|
<td data-title="{{dt.actions}}" class="actions">
|
||||||
{{action-menu model=inst}}
|
{{action-menu model=inst}}
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -54,7 +55,7 @@
|
||||||
{{#if inst.dislayContainerMessage}}
|
{{#if inst.dislayContainerMessage}}
|
||||||
<tr class="sub-row no-top auto-height">
|
<tr class="sub-row no-top auto-height">
|
||||||
<td colspan="1"></td>
|
<td colspan="1"></td>
|
||||||
<td class="pb-5" colspan="5">
|
<td class="pb-5" colspan="4">
|
||||||
{{#each inst.containers as |container|}}
|
{{#each inst.containers as |container|}}
|
||||||
{{#if container.showTransitioningMessage}}
|
{{#if container.showTransitioningMessage}}
|
||||||
<div>
|
<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*/) {
|
details(/* event*/) {
|
||||||
var route = 'container';
|
var route = 'pod';
|
||||||
|
|
||||||
if ( this.get('model.isVm') ) {
|
if ( this.get('model.isVm') ) {
|
||||||
route = 'virtualmachine';
|
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(),
|
clusterStore: service(),
|
||||||
intl: service(),
|
intl: service(),
|
||||||
prefs: service(),
|
prefs: service(),
|
||||||
|
scope: service(),
|
||||||
settings: service(),
|
settings: service(),
|
||||||
|
|
||||||
layout,
|
layout,
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,18 @@
|
||||||
expandFn=expandFn
|
expandFn=expandFn
|
||||||
expanded=securitySectionExpanded
|
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}}
|
{{/advanced-section}}
|
||||||
{{/accordion-list}}
|
{{/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}}
|
{{marked-down markdown=appReadmeContent}}
|
||||||
{{else if (not noAppReadme)}}
|
{{else if (not noAppReadme)}}
|
||||||
<div class="text-center">
|
<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>
|
</div>
|
||||||
{{else if noAppReadme}}
|
{{else if noAppReadme}}
|
||||||
<h1 class="mb-10 text-capitalize">
|
<h1 class="mb-10 text-capitalize">
|
||||||
|
|
@ -122,7 +122,7 @@
|
||||||
{{#if getTemplate.isRunning}}
|
{{#if getTemplate.isRunning}}
|
||||||
<section class="row">
|
<section class="row">
|
||||||
<div class="text-center">
|
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
@ -167,7 +167,7 @@
|
||||||
{{#if decoding}}
|
{{#if decoding}}
|
||||||
<section class="row">
|
<section class="row">
|
||||||
<div class="text-center">
|
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{{else}}
|
{{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">
|
<tr class="group-row">
|
||||||
<td colspan={{sub fullColspan 2}} class="pl-10">
|
<td colspan={{sub fullColspan 2}} class="pl-10">
|
||||||
{{#if (and model.id model.clusterId)}}
|
{{#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}}
|
{{t 'nodeGroup.label' name=model.displayName}}
|
||||||
</a>
|
</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@
|
||||||
<td data-title="{{dt.name}}" class="clip">
|
<td data-title="{{dt.name}}" class="clip">
|
||||||
{{#if (eq view "global")}}
|
{{#if (eq view "global")}}
|
||||||
{{#if model.clusterId}}
|
{{#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}}
|
{{else}}
|
||||||
{{model.displayName}}
|
{{model.displayName}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else}}
|
{{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}}
|
||||||
{{#if (or model.externalIpAddress model.ipAddress)}}
|
{{#if (or model.externalIpAddress model.ipAddress)}}
|
||||||
{{node-ip model=model}}
|
{{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}}
|
{{badge-state model=model}}
|
||||||
</td>
|
</td>
|
||||||
<td data-title="{{dt.name}}" class="clip">
|
<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}}
|
{{#if model.showTransitioningMessage}}
|
||||||
<div class="clip text-small {{model.stateColor}}">{{uc-first model.transitioningMessage}}</div>
|
<div class="clip text-small {{model.stateColor}}">{{uc-first model.transitioningMessage}}</div>
|
||||||
{{else if model.displayEndpoints}}
|
{{else if model.displayEndpoints}}
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
{{#copy-inline clipboardText=model.displayIp}}{{format-ip model.displayIp}}{{/copy-inline}} /
|
{{#copy-inline clipboardText=model.displayIp}}{{format-ip model.displayIp}}{{/copy-inline}} /
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (and showNode model.node)}}
|
{{#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}}
|
{{/if}}
|
||||||
{{t 'generic.createdDate' date=(date-from-now model.created) htmlSafe=true}} /
|
{{t 'generic.createdDate' date=(date-from-now model.created) htmlSafe=true}} /
|
||||||
{{t 'generic.restarts'}} {{model.restarts}}
|
{{t 'generic.restarts'}} {{model.restarts}}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export default Component.extend({
|
||||||
obj.css = (`width: ${ obj.percent }%`).htmlSafe();
|
obj.css = (`width: ${ obj.percent }%`).htmlSafe();
|
||||||
});
|
});
|
||||||
|
|
||||||
return out;
|
return out.filter((obj) => obj.percent);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
valueDep = `tooltipValues.@each.{${ labelKey },${ valueKey }}`;
|
valueDep = `tooltipValues.@each.{${ labelKey },${ valueKey }}`;
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@
|
||||||
}}
|
}}
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
{{~#each pieces as |obj|~}}
|
{{~#each pieces as |obj|~}}
|
||||||
{{#if obj.percent}}
|
<div class="progress-bar {{obj.color}}" style={{obj.css}}></div>
|
||||||
<div class="progress-bar {{obj.color}}" style={{obj.css}}></div>
|
|
||||||
{{/if}}
|
|
||||||
{{~/each~}}
|
{{~/each~}}
|
||||||
</div>
|
</div>
|
||||||
{{/tooltip-element}}
|
{{/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}}
|
{{/component}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="text-center mt-20">
|
<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>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/accordion-list-item}}
|
{{/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 { inject as service } from '@ember/service';
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { get, set, observer, computed } from '@ember/object';
|
import { get, computed, observer } from '@ember/object';
|
||||||
import { once } from '@ember/runloop';
|
import { alias } from '@ember/object/computed';
|
||||||
|
import C from 'ui/utils/constants';
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
|
scope: service(),
|
||||||
router: service(),
|
router: service(),
|
||||||
|
|
||||||
selectedContainer: null,
|
monitoringEnabled: alias('scope.currentCluster.isMonitoringReady'),
|
||||||
|
|
||||||
actions: {
|
podStateDidChange: observer('model.pod.state', function() {
|
||||||
select(container) {
|
if ( C.REMOVEDISH_STATES.includes(get(this, 'model.pod.state')) && get(this, 'router.currentRouteName') === 'container' ) {
|
||||||
set(this, 'selectedContainer', container);
|
const workloadId = get(this, 'model.pod.workloadId');
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
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');
|
|
||||||
|
|
||||||
if ( workloadId ) {
|
if ( workloadId ) {
|
||||||
this.transitionToRoute('workload', 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 envs = [];
|
||||||
var environment = this.get('selectedContainer.environment') || {};
|
var environment = get(this, 'model.environment') || {};
|
||||||
|
|
||||||
Object.keys(environment).forEach((key) => {
|
Object.keys(environment).forEach((key) => {
|
||||||
envs.pushObject({
|
envs.pushObject({
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { get } from '@ember/object';
|
import { get } from '@ember/object';
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
|
import { hash } from 'rsvp';
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
beforeModel() {
|
beforeModel() {
|
||||||
|
|
@ -14,12 +15,16 @@ export default Route.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
model(params) {
|
model(params) {
|
||||||
const pod = get(this, 'store').find('pod', params.container_id);
|
const pod = get(this, 'store').find('pod', params.pod_id);
|
||||||
|
|
||||||
if ( !pod ) {
|
return hash({ pod }).then((hash) => {
|
||||||
this.replaceWith('authenticated.project.index');
|
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">
|
<section class="header clearfix">
|
||||||
<div class="pull-left">
|
<div class="pull-left">
|
||||||
<h1 class="vertical-middle">
|
<h1 class="vertical-middle">
|
||||||
{{t 'podPage.header' name=model.displayName}}
|
{{t 'containerPage.header' name=model.displayName}}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-buttons">
|
<div class="right-buttons">
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
{{action-menu model=model showPrimary=false classNames="ml-10 inline-block" size="sm"}}
|
{{action-menu model=model showPrimary=false classNames="ml-10 inline-block" size="sm"}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{#if model.description}}
|
{{#if model.description}}
|
||||||
{{banner-message color='bg-secondary mb-0 mt-10' message=(linkify model.description)}}
|
{{banner-message color='bg-secondary mb-0 mt-10' message=(linkify model.description)}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
@ -17,230 +18,144 @@
|
||||||
<div class="{{model.stateColor}}"><p>{{uc-first model.transitioningMessage}}</p></div>
|
<div class="{{model.stateColor}}"><p>{{uc-first model.transitioningMessage}}</p></div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<section>
|
<div class="row banner bg-info basics">
|
||||||
<div class="row banner bg-info basics">
|
<div class="vertical-middle">
|
||||||
{{#if (eq model.containers.length 1)}}
|
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.namespace'}}:</label>
|
||||||
<div class="vertical-middle">
|
{{#if model.pod.namespaceId}}
|
||||||
<label class="acc-label vertical-middle p-0"> {{t 'podPage.image'}}:</label>
|
{{#copy-inline clipboardText=model.pod.namespaceId}}
|
||||||
{{selectedContainer.image}} {{copy-to-clipboard clipboardText=container.image size="small"}}
|
{{model.pod.namespaceId}}
|
||||||
</div>
|
{{/copy-inline}}
|
||||||
|
{{else}}
|
||||||
|
{{t 'generic.none'}}
|
||||||
{{/if}}
|
{{/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>
|
||||||
<div class="row banner bg-info basics">
|
<div class="vertical-middle">
|
||||||
<div class="vertical-middle">
|
<label class="acc-label vertical-middle p-0">{{t 'podPage.image'}}:</label>
|
||||||
<label class="acc-label vertical-middle p-0">{{t 'podPage.podIp'}}:</label>
|
{{model.image}} {{copy-to-clipboard clipboardText=model.image size="small"}}
|
||||||
{{#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>
|
</div>
|
||||||
</section>
|
<div class="vertical-middle">
|
||||||
|
<label class="acc-label vertical-middle p-0">{{t 'containerPage.pod'}}:</label> {{#if model.pod}}
|
||||||
<section>
|
{{#link-to "pod" model.podId}}{{model.podId}}{{/link-to}}
|
||||||
{{#accordion-list as |al expandFn|}}
|
{{else if model.podId}}
|
||||||
{{#if model.workload}}
|
{{model.podId}}
|
||||||
{{container/form-scheduling
|
{{else}}
|
||||||
initialHostId=model.workload.nodeId
|
{{t 'generic.none'}}
|
||||||
scheduling=model.workload.scheduling
|
|
||||||
editing=false
|
|
||||||
expandAll=al.expandAll
|
|
||||||
expandFn=expandFn
|
|
||||||
classNames="accordion"
|
|
||||||
}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{container/form-networking
|
<div class="row banner bg-info basics">
|
||||||
classNames="accordion-wrapper"
|
<div class="vertical-middle">
|
||||||
service=model
|
<label class="acc-label vertical-middle p-0">{{t 'containerPage.initContainer.label'}}:</label>
|
||||||
editing=false
|
{{#if model.initContainer}}
|
||||||
expandAll=al.expandAll
|
{{t 'generic.yes'}}
|
||||||
expandFn=expandFn
|
{{else}}
|
||||||
}}
|
{{t 'generic.no'}}
|
||||||
|
|
||||||
{{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>
|
|
||||||
{{/if}}
|
{{/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;">
|
{{#accordion-list as |al expandFn|}}
|
||||||
<div class="pull-left">
|
{{#if scope.currentCluster.isMonitoringReady}}
|
||||||
<h1 class="vertical-middle">
|
{{#metrics-summary
|
||||||
{{t 'podPage.image'}}:
|
expandAll=al.expandAll
|
||||||
{{container.image}} {{copy-to-clipboard clipboardText=container.image size="small"}}
|
expandFn=expandFn
|
||||||
</h1>
|
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>
|
||||||
<div class="right-buttons">
|
<div class="col span-6 mt-0 mb-0">
|
||||||
{{badge-state model=container}}
|
{{#if model.livenessProbe}}
|
||||||
{{action-menu model=container showPrimary=false classNames="ml-10 inline-block" size="sm"}}
|
<label class="acc-label">{{t 'formHealthCheck.liveness'}}</label>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
<div class="row">
|
||||||
{{#accordion-list as |al expandFn|}}
|
<div class="col {{if model.livenessProbe 'span-6' 'span-12'}}">
|
||||||
{{#accordion-list-item
|
{{form-healthcheck
|
||||||
title=(t 'containerPage.portsTab.header')
|
initialCheck=model.readinessProbe
|
||||||
detail=(t 'containerPage.portsTab.detail')
|
editing=false
|
||||||
expandAll=al.expandAll
|
|
||||||
expand=(action expandFn)
|
|
||||||
}}
|
|
||||||
{{container/form-ports
|
|
||||||
initialPorts=container.ports
|
|
||||||
editing=false
|
|
||||||
}}
|
}}
|
||||||
{{/accordion-list-item}}
|
</div>
|
||||||
|
{{#if model.livenessProbe}}
|
||||||
{{container/form-volumes
|
<div class="col span-6">
|
||||||
launchConfig=container
|
{{form-healthcheck
|
||||||
workload=model
|
initialCheck=model.livenessProbe
|
||||||
namespace=model.namespace
|
editing=false
|
||||||
loggingEnabled=true
|
isLiveness=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>
|
</div>
|
||||||
<div class="row">
|
{{/if}}
|
||||||
<div class="col {{if container.livenessProbe 'span-6' 'span-12'}}">
|
</div>
|
||||||
{{form-healthcheck
|
{{/accordion-list-item}}
|
||||||
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}}
|
|
||||||
|
|
||||||
{{container/form-security
|
{{container/form-security
|
||||||
instance=container
|
instance=model
|
||||||
service=model
|
service=model.pod
|
||||||
editing=false
|
editing=false
|
||||||
expandAll=al.expandAll
|
expandAll=al.expandAll
|
||||||
expandFn=expandFn
|
expandFn=expandFn
|
||||||
}}
|
}}
|
||||||
{{/accordion-list}}
|
{{/accordion-list}}
|
||||||
</section>
|
|
||||||
{{/each}}
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { getProjectId, getClusterId, bulkAdd } from 'ui/utils/navigation-tree';
|
import { getProjectId, getClusterId, bulkAdd } from 'ui/utils/navigation-tree';
|
||||||
|
import { get } from '@ember/object';
|
||||||
|
|
||||||
const rootNav = [
|
const rootNav = [
|
||||||
// Project
|
// Project
|
||||||
|
|
@ -34,17 +35,8 @@ const rootNav = [
|
||||||
id: 'infra',
|
id: 'infra',
|
||||||
localizedLabel: 'nav.infra.tab',
|
localizedLabel: 'nav.infra.tab',
|
||||||
ctx: [getProjectId],
|
ctx: [getProjectId],
|
||||||
route: 'authenticated.project.alert',
|
route: 'authenticated.project.certificates',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
|
||||||
id: 'tools-alerts',
|
|
||||||
localizedLabel: 'nav.tools.alerts',
|
|
||||||
icon: 'icon icon-alert',
|
|
||||||
route: 'authenticated.project.alert',
|
|
||||||
resource: [],
|
|
||||||
ctx: [getProjectId],
|
|
||||||
resourceScope: 'global',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'infra-certificates',
|
id: 'infra-certificates',
|
||||||
localizedLabel: 'nav.infra.certificates',
|
localizedLabel: 'nav.infra.certificates',
|
||||||
|
|
@ -63,24 +55,6 @@ const rootNav = [
|
||||||
resource: ['configmap'],
|
resource: ['configmap'],
|
||||||
resourceScope: 'project',
|
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',
|
id: 'infra-registries',
|
||||||
localizedLabel: 'nav.infra.registries',
|
localizedLabel: 'nav.infra.registries',
|
||||||
|
|
@ -120,12 +94,58 @@ const rootNav = [
|
||||||
resourceScope: 'global',
|
resourceScope: 'global',
|
||||||
ctx: [getProjectId],
|
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
|
// Cluster
|
||||||
{
|
{
|
||||||
scope: 'cluster',
|
scope: 'cluster',
|
||||||
id: 'cluster-k8s',
|
id: 'cluster-k8s',
|
||||||
localizedLabel: 'nav.cluster.dashboard',
|
localizedLabel: 'nav.cluster.dashboard',
|
||||||
route: 'authenticated.cluster.index',
|
route: 'authenticated.cluster.monitoring',
|
||||||
ctx: [getClusterId],
|
ctx: [getClusterId],
|
||||||
resource: ['node'],
|
resource: ['node'],
|
||||||
resourceScope: 'global',
|
resourceScope: 'global',
|
||||||
|
|
@ -217,22 +237,27 @@ const rootNav = [
|
||||||
{
|
{
|
||||||
id: 'cluster-tools-notifiers',
|
id: 'cluster-tools-notifiers',
|
||||||
localizedLabel: 'nav.tools.notifiers',
|
localizedLabel: 'nav.tools.notifiers',
|
||||||
// icon: 'icon icon-key',
|
|
||||||
route: 'authenticated.cluster.notifier',
|
route: 'authenticated.cluster.notifier',
|
||||||
resourceScope: 'global',
|
resourceScope: 'global',
|
||||||
resource: [],
|
resource: [],
|
||||||
ctx: [getClusterId],
|
ctx: [getClusterId],
|
||||||
},
|
},
|
||||||
{ divider: true },
|
|
||||||
{
|
{
|
||||||
id: 'cluster-tools-logging',
|
id: 'cluster-tools-logging',
|
||||||
localizedLabel: 'nav.tools.logging',
|
localizedLabel: 'nav.tools.logging',
|
||||||
// icon: 'icon icon-key',
|
|
||||||
route: 'authenticated.cluster.logging',
|
route: 'authenticated.cluster.logging',
|
||||||
resourceScope: 'global',
|
resourceScope: 'global',
|
||||||
resource: [],
|
resource: [],
|
||||||
ctx: [getClusterId],
|
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 Resource from 'ember-api-store/models/resource';
|
||||||
import { hasMany } from 'ember-api-store/utils/denormalize';
|
import { hasMany } from 'ember-api-store/utils/denormalize';
|
||||||
import ResourceUsage from 'shared/mixins/resource-usage';
|
import ResourceUsage from 'shared/mixins/resource-usage';
|
||||||
|
import Grafana from 'shared/mixins/grafana';
|
||||||
import { equal, alias } from '@ember/object/computed';
|
import { equal, alias } from '@ember/object/computed';
|
||||||
import { resolve } from 'rsvp';
|
import { resolve } from 'rsvp';
|
||||||
import C from 'ui/utils/constants';
|
import C from 'ui/utils/constants';
|
||||||
|
|
||||||
export default Resource.extend(ResourceUsage, {
|
export default Resource.extend(Grafana, ResourceUsage, {
|
||||||
globalStore: service(),
|
globalStore: service(),
|
||||||
growl: service(),
|
growl: service(),
|
||||||
scope: service(),
|
scope: service(),
|
||||||
router: service(),
|
router: service(),
|
||||||
|
|
||||||
namespaces: hasMany('id', 'namespace', 'clusterId'),
|
namespaces: hasMany('id', 'namespace', 'clusterId'),
|
||||||
projects: hasMany('id', 'project', 'clusterId'),
|
projects: hasMany('id', 'project', 'clusterId'),
|
||||||
nodes: hasMany('id', 'node', 'clusterId'),
|
nodes: hasMany('id', 'node', 'clusterId'),
|
||||||
nodePools: hasMany('id', 'nodePool', 'clusterId'),
|
nodePools: hasMany('id', 'nodePool', 'clusterId'),
|
||||||
clusterRoleTemplateBindings: hasMany('id', 'clusterRoleTemplateBinding', 'clusterId'),
|
clusterRoleTemplateBindings: hasMany('id', 'clusterRoleTemplateBinding', 'clusterId'),
|
||||||
|
grafanaDashboardName: 'Cluster',
|
||||||
machines: alias('nodes'),
|
machines: alias('nodes'),
|
||||||
roleTemplateBindings: alias('clusterRoleTemplateBindings'),
|
roleTemplateBindings: alias('clusterRoleTemplateBindings'),
|
||||||
isGKE: equal('driver', 'googleKubernetesEngine'),
|
isGKE: equal('driver', 'googleKubernetesEngine'),
|
||||||
|
|
||||||
getAltActionDelete: computed('action.remove', function() { // eslint-disable-line
|
getAltActionDelete: computed('action.remove', function() { // eslint-disable-line
|
||||||
return get(this, 'canBulkRemove') ? 'delete' : null;
|
return get(this, 'canBulkRemove') ? 'delete' : null;
|
||||||
}),
|
}),
|
||||||
|
|
@ -56,6 +57,21 @@ export default Resource.extend(ResourceUsage, {
|
||||||
return null;
|
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() {
|
isReady: computed('conditions.@each.status', function() {
|
||||||
return this.hasCondition('Ready');
|
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() {
|
defaultProject: computed('projects.@each.{name,clusterOwner}', function() {
|
||||||
let projects = this.get('projects');
|
let projects = get(this, 'projects');
|
||||||
|
|
||||||
let out = projects.findBy('isDefault');
|
let out = projects.findBy('isDefault');
|
||||||
|
|
||||||
|
|
@ -152,7 +174,7 @@ export default Resource.extend(ResourceUsage, {
|
||||||
},
|
},
|
||||||
|
|
||||||
edit() {
|
edit() {
|
||||||
this.get('router').transitionTo('authenticated.cluster.edit', this.get('id'));
|
get(this, 'router').transitionTo('authenticated.cluster.edit', get(this, 'id'));
|
||||||
},
|
},
|
||||||
|
|
||||||
scaleDownPool(id) {
|
scaleDownPool(id) {
|
||||||
|
|
@ -187,8 +209,8 @@ export default Resource.extend(ResourceUsage, {
|
||||||
const promise = this._super.apply(this, arguments);
|
const promise = this._super.apply(this, arguments);
|
||||||
|
|
||||||
return promise.then((/* resp */) => {
|
return promise.then((/* resp */) => {
|
||||||
if (this.get('scope.currentCluster.id') === this.get('id')) {
|
if (get(this, 'scope.currentCluster.id') === get(this, 'id')) {
|
||||||
this.get('router').transitionTo('global-admin.clusters');
|
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 DisplayImage from 'shared/mixins/display-image';
|
||||||
import { get, computed } from '@ember/object';
|
import { get, computed } from '@ember/object';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
|
import Grafana from 'shared/mixins/grafana';
|
||||||
|
import { alias } from '@ember/object/computed';
|
||||||
import { later } from '@ember/runloop';
|
import { later } from '@ember/runloop';
|
||||||
|
|
||||||
var Container = Resource.extend(DisplayImage, {
|
var Container = Resource.extend(Grafana, DisplayImage, {
|
||||||
modalService: service('modal'),
|
modalService: service('modal'),
|
||||||
intl: service(),
|
intl: service(),
|
||||||
scope: service(),
|
scope: service(),
|
||||||
|
|
@ -14,7 +16,17 @@ var Container = Resource.extend(DisplayImage, {
|
||||||
links: {},
|
links: {},
|
||||||
type: 'container',
|
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() {
|
availableActions: computed('state', function() {
|
||||||
let isRunning = get(this, 'state') === 'running';
|
let isRunning = get(this, 'state') === 'running';
|
||||||
|
|
@ -39,6 +51,14 @@ var Container = Resource.extend(DisplayImage, {
|
||||||
return choices;
|
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() {
|
validateQuota() {
|
||||||
const projectLimit = get(this, 'scope.currentProject.resourceQuota.limit');
|
const projectLimit = get(this, 'scope.currentProject.resourceQuota.limit');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { computed, get } from '@ember/object';
|
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 Resource from 'ember-api-store/models/resource';
|
||||||
import { download } from 'shared/utils/util';
|
import { download } from 'shared/utils/util';
|
||||||
import C from 'ui/utils/constants';
|
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 { inject as service } from '@ember/service';
|
||||||
import { reference } from 'ember-api-store/utils/denormalize';
|
import { reference } from 'ember-api-store/utils/denormalize';
|
||||||
import ResourceUsage from 'shared/mixins/resource-usage';
|
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_KEYS = ['node-role.kubernetes.io/etcd', 'node-role.kubernetes.io/controlplane'];
|
||||||
const UNSCHEDULABLE_EFFECTS = ['NoExecute', 'NoSchedule'];
|
const UNSCHEDULABLE_EFFECTS = ['NoExecute', 'NoSchedule'];
|
||||||
|
|
||||||
var Node = Resource.extend(StateCounts, ResourceUsage, {
|
var Node = Resource.extend(Grafana, StateCounts, ResourceUsage, {
|
||||||
modalService: service('modal'),
|
modalService: service('modal'),
|
||||||
settings: service(),
|
settings: service(),
|
||||||
prefs: service(),
|
prefs: service(),
|
||||||
|
|
@ -22,6 +23,9 @@ var Node = Resource.extend(StateCounts, ResourceUsage, {
|
||||||
|
|
||||||
type: 'node',
|
type: 'node',
|
||||||
|
|
||||||
|
grafanaDashboardName: 'Nodes',
|
||||||
|
grafanaResourceId: alias('ipAddress'),
|
||||||
|
|
||||||
cluster: reference('clusterId', 'cluster'),
|
cluster: reference('clusterId', 'cluster'),
|
||||||
nodePool: reference('nodePoolId'),
|
nodePool: reference('nodePoolId'),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import C from 'ui/utils/constants';
|
import C from 'ui/utils/constants';
|
||||||
import Resource from 'ember-api-store/models/resource';
|
import Resource from 'ember-api-store/models/resource';
|
||||||
|
import { alias } from '@ember/object/computed';
|
||||||
import { reference } from 'ember-api-store/utils/denormalize';
|
import { reference } from 'ember-api-store/utils/denormalize';
|
||||||
import { get, computed } from '@ember/object';
|
import { get, computed } from '@ember/object';
|
||||||
import { inject as service } from '@ember/service';
|
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 { formatSi } from 'shared/utils/parse-unit';
|
||||||
import { later } from '@ember/runloop';
|
import { later } from '@ember/runloop';
|
||||||
import { gt } from '@ember/object/computed';
|
import { gt } from '@ember/object/computed';
|
||||||
|
import Grafana from 'shared/mixins/grafana';
|
||||||
import DisplayImage from 'shared/mixins/display-image';
|
import DisplayImage from 'shared/mixins/display-image';
|
||||||
|
|
||||||
var Pod = Resource.extend(DisplayImage, {
|
var Pod = Resource.extend(Grafana, DisplayImage, {
|
||||||
router: service(),
|
router: service(),
|
||||||
modalService: service('modal'),
|
modalService: service('modal'),
|
||||||
globalStore: service(),
|
globalStore: service(),
|
||||||
|
|
@ -22,6 +24,9 @@ var Pod = Resource.extend(DisplayImage, {
|
||||||
canEdit: false,
|
canEdit: false,
|
||||||
canClone: false,
|
canClone: false,
|
||||||
|
|
||||||
|
grafanaDashboardName: 'Pods',
|
||||||
|
grafanaResourceId: alias('name'),
|
||||||
|
|
||||||
namespace: reference('namespaceId', 'namespace', 'clusterStore'),
|
namespace: reference('namespaceId', 'namespace', 'clusterStore'),
|
||||||
node: reference('nodeId', 'node', 'globalStore'),
|
node: reference('nodeId', 'node', 'globalStore'),
|
||||||
workload: reference('workloadId'),
|
workload: reference('workloadId'),
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,21 @@ export default Resource.extend({
|
||||||
return labels[SYSTEM_PROJECT_LABEL] === 'true';
|
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() {
|
active: computed('scope.currentProject.id', 'id', function() {
|
||||||
return get(this, 'scope.currentProject.id') === get(this, 'id');
|
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/`;
|
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' ) {
|
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/`;
|
linkEndpoint += `${ get(this, 'name') }:${ get(port, 'port') }/proxy/`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { later, cancel } from '@ember/runloop';
|
import { later, cancel } from '@ember/runloop';
|
||||||
import { computed, get, set } from '@ember/object';
|
import { computed, get, set } from '@ember/object';
|
||||||
|
import Grafana from 'shared/mixins/grafana';
|
||||||
import { alias, gt, not } from '@ember/object/computed';
|
import { alias, gt, not } from '@ember/object/computed';
|
||||||
import Resource from 'ember-api-store/models/resource';
|
import Resource from 'ember-api-store/models/resource';
|
||||||
import { sortableNumericSuffix } from 'shared/utils/util';
|
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']
|
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(),
|
intl: service(),
|
||||||
growl: service(),
|
growl: service(),
|
||||||
modalService: service('modal'),
|
modalService: service('modal'),
|
||||||
|
|
@ -37,8 +38,9 @@ var Workload = Resource.extend(DisplayImage, StateCounts, EndpointPorts, {
|
||||||
canHaveEnvironment: true,
|
canHaveEnvironment: true,
|
||||||
canHaveHealthCheck: true,
|
canHaveHealthCheck: true,
|
||||||
isBalancer: false,
|
isBalancer: false,
|
||||||
|
canBalanceTo: true,
|
||||||
|
|
||||||
canBalanceTo: true,
|
grafanaResourceId: alias('name'),
|
||||||
|
|
||||||
namespace: reference('namespaceId', 'namespace', 'clusterStore'),
|
namespace: reference('namespaceId', 'namespace', 'clusterStore'),
|
||||||
canClone: not('hasSidekicks'),
|
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('nodes', function() {
|
||||||
this.route('index', { path: '/' });
|
this.route('index', { path: '/' });
|
||||||
this.route('node', {
|
|
||||||
path: '/:node_id',
|
|
||||||
resetNamespace: true
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.mount('monitoring');
|
||||||
|
|
||||||
this.route('projects', { path: '/projects-namespaces' }, function() {
|
this.route('projects', { path: '/projects-namespaces' }, function() {
|
||||||
this.route('index', { path: '/' });
|
this.route('index', { path: '/' });
|
||||||
this.route('edit', { path: '/project/:project_id' });
|
this.route('edit', { path: '/project/:project_id' });
|
||||||
|
|
@ -122,6 +120,7 @@ Router.map(function() {
|
||||||
this.mount('alert', { path: '/alerts' });
|
this.mount('alert', { path: '/alerts' });
|
||||||
|
|
||||||
this.mount('pipeline');
|
this.mount('pipeline');
|
||||||
|
this.mount('monitoring');
|
||||||
|
|
||||||
// Workload
|
// Workload
|
||||||
this.route('containers', {
|
this.route('containers', {
|
||||||
|
|
@ -131,10 +130,15 @@ Router.map(function() {
|
||||||
this.route('run', { path: '/run' });
|
this.route('run', { path: '/run' });
|
||||||
this.route('index', { path: '/' });
|
this.route('index', { path: '/' });
|
||||||
|
|
||||||
this.route('container', {
|
this.route('pod', {
|
||||||
path: '/:container_id',
|
path: '/:pod_id',
|
||||||
resetNamespace: true
|
resetNamespace: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route('container', {
|
||||||
|
path: '/:pod_id/container/:container_name',
|
||||||
|
resetNamespace: true
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route('ingresses', { resetNamespace: true }, function() {
|
this.route('ingresses', { resetNamespace: true }, function() {
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,13 @@
|
||||||
@import "app/styles/components/slider";
|
@import "app/styles/components/slider";
|
||||||
@import "app/styles/components/badges";
|
@import "app/styles/components/badges";
|
||||||
@import "app/styles/components/badge-state";
|
@import "app/styles/components/badge-state";
|
||||||
@import "app/styles/components/component-badge";
|
|
||||||
@import "app/styles/components/tables";
|
@import "app/styles/components/tables";
|
||||||
@import "app/styles/components/growl";
|
@import "app/styles/components/growl";
|
||||||
@import "app/styles/components/login";
|
@import "app/styles/components/login";
|
||||||
@import "app/styles/components/pod";
|
@import "app/styles/components/pod";
|
||||||
|
@import "app/styles/components/metrics";
|
||||||
@import "app/styles/components/github-avatar";
|
@import "app/styles/components/github-avatar";
|
||||||
@import "app/styles/components/spark-line";
|
@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/tooltip";
|
||||||
@import "app/styles/components/container-shell";
|
@import "app/styles/components/container-shell";
|
||||||
@import "app/styles/components/modals";
|
@import "app/styles/components/modals";
|
||||||
|
|
@ -52,6 +50,7 @@
|
||||||
@import "app/styles/components/pagination";
|
@import "app/styles/components/pagination";
|
||||||
@import "app/styles/components/theme-toggle";
|
@import "app/styles/components/theme-toggle";
|
||||||
@import "app/styles/components/progress";
|
@import "app/styles/components/progress";
|
||||||
|
@import "app/styles/components/percent-gauge";
|
||||||
@import "app/styles/components/namespace-app";
|
@import "app/styles/components/namespace-app";
|
||||||
@import "app/styles/components/page-header";
|
@import "app/styles/components/page-header";
|
||||||
@import "app/styles/components/over-hr";
|
@import "app/styles/components/over-hr";
|
||||||
|
|
|
||||||
|
|
@ -91,3 +91,7 @@ hr {
|
||||||
border-top: 1px solid $border;
|
border-top: 1px solid $border;
|
||||||
border-bottom: 1px solid $border;
|
border-bottom: 1px solid $border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flatpickr-months .flatpickr-month {
|
||||||
|
height: 36px !important;
|
||||||
|
}
|
||||||
|
|
@ -289,3 +289,15 @@
|
||||||
.link[disabled] {
|
.link[disabled] {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.disabled {
|
||||||
|
cursor: default;
|
||||||
|
transition: ease-in-out all .25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky {
|
||||||
|
position: sticky;
|
||||||
|
top: 0px;
|
||||||
|
display:inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
.elasticsearch {
|
||||||
background-image: url('images/providers/elasticsearch.svg');
|
background-image: url('images/providers/elasticsearch.svg');
|
||||||
}
|
}
|
||||||
|
.prometheus {
|
||||||
|
background-image: url('images/providers/prometheus.svg');
|
||||||
|
background-size: 80px 80px;
|
||||||
|
}
|
||||||
.splunk {
|
.splunk {
|
||||||
background-image: url('images/providers/splunk.svg');
|
background-image: url('images/providers/splunk.svg');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,3 +36,7 @@ $progress-height : 10px !default;
|
||||||
background-color: lighten($success, 50);
|
background-color: lighten($success, 50);
|
||||||
border: solid lighten($success, 30) 1px;
|
border: solid lighten($success, 30) 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-bar-primary {
|
||||||
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
|
@ -108,3 +108,10 @@
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
margin-bottom: 15px;
|
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">
|
<div class="tooltip-content-inner tooltip-dot">
|
||||||
{{action-menu model=model showPrimary=false inTooltip=true class="pull-right tooltip-more-actions"}}
|
{{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="clearfix">
|
||||||
<div class="pull-right">{{badge-state classNames="btn-xs" model=model}}</div>
|
<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 { alias } from '@ember/object/computed';
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { get } from '@ember/object';
|
import { get } from '@ember/object';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
|
scope: service(),
|
||||||
|
|
||||||
launchConfig: null,
|
launchConfig: null,
|
||||||
|
|
||||||
service: alias('model.workload'),
|
service: alias('model.workload'),
|
||||||
|
monitoringEnabled: alias('scope.currentCluster.isMonitoringReady'),
|
||||||
|
|
||||||
displayEnvironmentVars: computed('service.launchConfig.environment', function() {
|
displayEnvironmentVars: computed('service.launchConfig.environment', function() {
|
||||||
var envs = [];
|
var envs = [];
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,6 @@
|
||||||
<h1 class="vertical-middle">
|
<h1 class="vertical-middle">
|
||||||
{{t 'servicePage.header' name=service.displayName}}
|
{{t 'servicePage.header' name=service.displayName}}
|
||||||
</h1>
|
</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>
|
||||||
<div class="right-buttons">
|
<div class="right-buttons">
|
||||||
{{badge-state model=service}}
|
{{badge-state model=service}}
|
||||||
|
|
@ -15,9 +10,6 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div id="largeStats">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if service.description}}
|
{{#if service.description}}
|
||||||
{{banner-message color='bg-secondary mb-0 mt-10' message=(linkify service.description)}}
|
{{banner-message color='bg-secondary mb-0 mt-10' message=(linkify service.description)}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
@ -26,211 +18,225 @@
|
||||||
<div class="{{service.stateColor}}"><p>{{uc-first service.transitioningMessage}}</p></div>
|
<div class="{{service.stateColor}}"><p>{{uc-first service.transitioningMessage}}</p></div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<section>
|
<div class="row banner bg-info basics">
|
||||||
<div class="row banner bg-info basics">
|
<div class="vertical-middle">
|
||||||
<div class="vertical-middle">
|
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.namespace'}}:</label>
|
||||||
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.namespace'}}</label>
|
{{service.namespace.displayName}}
|
||||||
{{service.namespace.displayName}}
|
</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>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="vertical-middle">
|
<div class="row banner bg-info basics">
|
||||||
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.image'}}</label>
|
<div class="vertical-middle">
|
||||||
{{launchConfig.image}} {{copy-to-clipboard clipboardText=launchConfig.image size="small"}}
|
<label class="acc-label vertical-middle p-0">{{t 'servicePage.endpoints'}}:</label>
|
||||||
</div>
|
{{#if service.displayEndpoints}}
|
||||||
|
<label class="clip text-small vertical-middle">
|
||||||
<div class="vertical-middle">
|
{{service.displayEndpoints}}
|
||||||
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.type'}}</label>
|
</label>
|
||||||
{{service.displayType}}
|
{{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>
|
</div>
|
||||||
<div class="row banner bg-info basics">
|
<div class="vertical-middle">
|
||||||
<div class="vertical-middle">
|
<label class="acc-label vertical-middle p-0">{{t 'servicePage.multistat.created'}}:</label>
|
||||||
<label class="acc-label vertical-middle p-0">{{t 'servicePage.endpoints'}}</label>
|
{{date-calendar service.created}}
|
||||||
{{#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>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
<section>
|
{{#accordion-list as |al expandFn|}}
|
||||||
{{#accordion-list as |al expandFn|}}
|
{{accordion-pod
|
||||||
{{accordion-pod
|
pods=service.pods
|
||||||
pods=service.pods
|
expandAll=al.expandAll
|
||||||
|
expandFn=expandFn
|
||||||
|
}}
|
||||||
|
|
||||||
|
{{#if scope.currentCluster.isMonitoringReady}}
|
||||||
|
{{#metrics-summary
|
||||||
expandAll=al.expandAll
|
expandAll=al.expandAll
|
||||||
expandFn=expandFn
|
expandFn=expandFn
|
||||||
|
grafanaUrl=service.grafanaUrl
|
||||||
|
title=(t "metricsAction.sections.workload")
|
||||||
}}
|
}}
|
||||||
|
{{workload-metrics resourceId=service.id}}
|
||||||
|
{{/metrics-summary}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{resource-event-list
|
{{resource-event-list
|
||||||
resourceType=service.displayType
|
resourceType=service.displayType
|
||||||
expandAll=al.expandAll
|
expandAll=al.expandAll
|
||||||
expandFn=expandFn
|
expandFn=expandFn
|
||||||
namespaceId=service.namespaceId
|
namespaceId=service.namespaceId
|
||||||
name=service.name
|
name=service.name
|
||||||
kind=service.type
|
kind=service.type
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{{#if (or (eq service.type 'job') (eq service.type 'cronJob'))}}
|
{{#if (or (eq service.type 'job') (eq service.type 'cronJob'))}}
|
||||||
{{container/form-job-config
|
{{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
|
|
||||||
workload=service
|
workload=service
|
||||||
scaleMode=service.type
|
scaleMode=service.type
|
||||||
editing=false
|
editing=false
|
||||||
expandAll=al.expandAll
|
expandAll=al.expandAll
|
||||||
expandFn=expandFn
|
expandFn=expandFn
|
||||||
}}
|
}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{container/form-volumes
|
{{#if service.canHaveEnvironment}}
|
||||||
launchConfig=launchConfig
|
{{#accordion-list-item
|
||||||
workload=service
|
title=(t 'formEnvVar.title')
|
||||||
namespace=service.namespace
|
detail=(t 'formEnvVar.detail')
|
||||||
loggingEnabled=true
|
|
||||||
editing=false
|
|
||||||
expandAll=al.expandAll
|
expandAll=al.expandAll
|
||||||
expandFn=expandFn
|
expand=(action expandFn)
|
||||||
}}
|
}}
|
||||||
|
{{form-env-var
|
||||||
{{container/form-command
|
model=displayEnvironmentVars
|
||||||
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}}
|
<hr class="mt-30 mb-30" />
|
||||||
|
{{container/form-sources
|
||||||
|
namespace=service.namespace
|
||||||
|
sources=launchConfig.environmentFrom
|
||||||
|
editing=false
|
||||||
|
}}
|
||||||
|
{{/accordion-list-item}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{/accordion-list}}
|
{{#accordion-list-item
|
||||||
</section>
|
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() {
|
custom: computed('library', 'helmStable', 'helmIncubator', function() {
|
||||||
const hide = [
|
const hide = [
|
||||||
get(this, 'library'),
|
get(this, 'library'),
|
||||||
get(this, 'helmStable'),
|
get(this, 'helmStable'),
|
||||||
get(this, 'helmIncubator')
|
get(this, 'helmIncubator'),
|
||||||
|
get(this, 'systemLibrary'),
|
||||||
];
|
];
|
||||||
|
|
||||||
return get(this, 'model').filter((x) => !hide.includes(x));
|
return get(this, 'model').filter((x) => !hide.includes(x));
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ const Eng = Engine.extend({
|
||||||
'authenticated.project',
|
'authenticated.project',
|
||||||
'authenticated.prefs',
|
'authenticated.prefs',
|
||||||
'authenticated.cluster.nodes',
|
'authenticated.cluster.nodes',
|
||||||
'authenticated.cluster.nodes.node',
|
|
||||||
'authenticated.cluster.security.members.index',
|
'authenticated.cluster.security.members.index',
|
||||||
'logout'
|
'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' },
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<section>
|
||||||
|
{{metrics-action
|
||||||
|
queryAction="query"
|
||||||
|
state=state
|
||||||
|
}}
|
||||||
|
{{metrics-graph graphs=graphs loading=state.loading noGraphs=state.noGraphs}}
|
||||||
|
</section>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue