mirror of https://github.com/rancher/dashboard.git
502 lines
11 KiB
JavaScript
502 lines
11 KiB
JavaScript
import day from 'dayjs';
|
|
import { insertAt, filterBy } from '@/utils/array';
|
|
import {
|
|
ADD_SIDECAR, _FLAGGED, MODE, _CREATE, _CLONE, _STAGE
|
|
} from '@/config/query-params';
|
|
import { escapeHtml } from '@/utils/string';
|
|
import { DATE_FORMAT, TIME_FORMAT } from '@/store/prefs';
|
|
import { PRIVATE } from '@/plugins/steve/resource-proxy';
|
|
import { RIO } from '@/config/types';
|
|
import { formatSi } from '@/utils/units';
|
|
import { get } from '@/utils/object';
|
|
|
|
const EMPTY = {};
|
|
|
|
export default {
|
|
applyDefaults(ctx, mode) {
|
|
const spec = this.spec;
|
|
|
|
if ( mode === _CREATE || mode === _CLONE ) {
|
|
delete spec.app;
|
|
spec.version = 'v0';
|
|
} else if ( mode === _STAGE ) {
|
|
spec.app = this.app;
|
|
delete spec.version;
|
|
}
|
|
|
|
if ( mode === _CREATE ) {
|
|
spec.weight = 10000;
|
|
} else if ( mode === _CLONE ) {
|
|
delete spec.weight;
|
|
} else if ( mode === _STAGE ) {
|
|
spec.weight = 0;
|
|
}
|
|
},
|
|
|
|
app() {
|
|
const spec = this.spec || EMPTY;
|
|
const status = this.status || EMPTY;
|
|
const metadata = this.metadata || EMPTY;
|
|
|
|
return spec.app || status.computedApp || metadata.name;
|
|
},
|
|
|
|
version() {
|
|
const spec = this.spec || EMPTY;
|
|
const status = this.status || EMPTY;
|
|
const uid = ((this.metadata || EMPTY)['uid'] || '').replace(/-.*$/, '');
|
|
|
|
return spec.version || status.computedVersion || uid || '?';
|
|
},
|
|
|
|
nameDisplay() {
|
|
const version = this.version;
|
|
|
|
if ( version === 'v0' ) {
|
|
return this.app;
|
|
}
|
|
|
|
return `${ this.app }@${ this.version }`;
|
|
},
|
|
|
|
namespaceApp() {
|
|
return `${ this.metadata.namespace }:${ this.app }`;
|
|
},
|
|
|
|
imageDisplay() {
|
|
if ( this.spec.build && !this.spec.image ) {
|
|
return 'Building from Git...';
|
|
}
|
|
|
|
return (this.spec.image || '')
|
|
.replace(/^(index\.)?docker.io\/(library\/)?/i, '')
|
|
.replace(/@sha256:[0-9a-f]+$/i, '')
|
|
.replace(/:latest$/i, '')
|
|
.replace(/localhost:5442\/(.*)/i, '$1 (local)');
|
|
},
|
|
|
|
createdDisplay() {
|
|
const dateFormat = escapeHtml( this.$rootGetters['prefs/get'](DATE_FORMAT));
|
|
const timeFormat = escapeHtml( this.$rootGetters['prefs/get'](TIME_FORMAT));
|
|
|
|
return day(this.metadata.creationTimestamp).format(`${ dateFormat } ${ timeFormat }`);
|
|
},
|
|
|
|
versionWithDateDisplay() {
|
|
return `${ this.version } (${ this.createdDisplay })`;
|
|
},
|
|
|
|
scales() {
|
|
const status = this.status || {};
|
|
const scaleStatus = status.scaleStatus || {};
|
|
const auto = !!this.spec.autoscale;
|
|
const fixed = (typeof this.spec.replicas === 'undefined' ? 1 : this.spec.replicas || 0);
|
|
const available = scaleStatus.available || 0;
|
|
const current = (typeof status.computedReplicas === 'undefined' ? available : status.computedReplicas || 0);
|
|
const unavailable = scaleStatus.unavailable || 0;
|
|
const global = this.spec.global === true;
|
|
|
|
let desired = fixed;
|
|
let min, max;
|
|
|
|
if ( auto ) {
|
|
min = this.spec.autoscale.minReplicas;
|
|
max = this.spec.autoscale.maxReplicas;
|
|
desired = `${ min } - ${ max }`;
|
|
}
|
|
|
|
if ( global ) {
|
|
desired = current;
|
|
} else if ( typeof this[PRIVATE].pendingScale === 'number' ) {
|
|
desired = this[PRIVATE].pendingScale;
|
|
}
|
|
|
|
const missing = Math.max(0, desired - available - unavailable);
|
|
|
|
return {
|
|
global,
|
|
auto,
|
|
min,
|
|
max,
|
|
current,
|
|
desired,
|
|
available,
|
|
unavailable,
|
|
starting: missing > 0 ? missing : 0,
|
|
stopping: missing < 0 ? -1 * missing : 0,
|
|
};
|
|
},
|
|
|
|
showDesiredScale() {
|
|
const scales = this.scales;
|
|
|
|
return !scales.global && scales.current !== scales.desired;
|
|
},
|
|
|
|
complexScale() {
|
|
const { stopping, starting, unavailable } = this.scales;
|
|
|
|
return stopping !== 0 || starting !== 0 || unavailable !== 0;
|
|
},
|
|
|
|
scaleParts() {
|
|
const {
|
|
available, unavailable, starting, stopping
|
|
} = this.scales;
|
|
const out = [
|
|
{
|
|
label: 'Available',
|
|
color: 'bg-success',
|
|
textColor: 'text-success',
|
|
value: available
|
|
},
|
|
|
|
{
|
|
label: 'Unavailable',
|
|
color: 'bg-error',
|
|
textColor: 'text-error',
|
|
value: unavailable
|
|
},
|
|
];
|
|
|
|
if ( starting ) {
|
|
out.push({
|
|
label: 'Starting',
|
|
color: 'bg-info',
|
|
textColor: 'text-info',
|
|
value: starting
|
|
});
|
|
}
|
|
|
|
if ( stopping ) {
|
|
out.push({
|
|
label: 'Stopping',
|
|
color: 'bg-warning',
|
|
textColor: 'text-warning',
|
|
value: stopping
|
|
});
|
|
}
|
|
|
|
return out;
|
|
},
|
|
|
|
scaleUp() {
|
|
return () => {
|
|
let scale;
|
|
|
|
if ( this.scales.global ) {
|
|
return;
|
|
}
|
|
|
|
if ( this[PRIVATE].scaleTimer ) {
|
|
scale = this[PRIVATE].pendingScale;
|
|
} else {
|
|
scale = this.scales.desired;
|
|
}
|
|
|
|
scale = scale || 0;
|
|
|
|
this[PRIVATE].pendingScale = scale + 1;
|
|
this.saveScale();
|
|
};
|
|
},
|
|
|
|
scaleDown() {
|
|
return () => {
|
|
let scale;
|
|
|
|
if ( this.scales.global ) {
|
|
return;
|
|
}
|
|
|
|
if ( this[PRIVATE].scaleTimer ) {
|
|
scale = this[PRIVATE].pendingScale;
|
|
} else {
|
|
scale = this.scales.desired;
|
|
}
|
|
|
|
scale = scale || 1;
|
|
|
|
this[PRIVATE].pendingScale = Math.max(scale - 1, 0);
|
|
this.saveScale();
|
|
};
|
|
},
|
|
|
|
saveScale() {
|
|
return () => {
|
|
if ( this[PRIVATE].scaleTimer ) {
|
|
clearTimeout(this[PRIVATE].scaleTimer);
|
|
}
|
|
|
|
this[PRIVATE].scaleTimer = setTimeout(async() => {
|
|
try {
|
|
await this.patch([{
|
|
op: 'replace',
|
|
path: '/spec/replicas',
|
|
value: this[PRIVATE].pendingScale
|
|
}]);
|
|
} catch (err) {
|
|
this.$dispatch('growl/fromError', { title: 'Error updating scale', err }, { root: true });
|
|
}
|
|
|
|
this[PRIVATE].scaleTimer = null;
|
|
this[PRIVATE].pendingScale = null;
|
|
}, 500);
|
|
};
|
|
},
|
|
|
|
allVersions() {
|
|
const services = this.$getters['all'](RIO.SERVICE);
|
|
|
|
const out = filterBy(services, {
|
|
app: this.app,
|
|
'metadata.namespace': this.metadata.namespace,
|
|
});
|
|
|
|
return out;
|
|
},
|
|
|
|
weightsOfApp() {
|
|
let desired = 0;
|
|
let current = 0;
|
|
let count = 0;
|
|
|
|
for ( const service of this.allVersions ) {
|
|
const weights = service.weights;
|
|
|
|
desired += weights.desired || 0;
|
|
current += weights.current || 0;
|
|
count++;
|
|
}
|
|
|
|
return {
|
|
desired,
|
|
current,
|
|
count
|
|
};
|
|
},
|
|
|
|
weights() {
|
|
let current = 0;
|
|
let desired = 0;
|
|
|
|
const fromSpec = this.spec.weight;
|
|
|
|
if ( this.status ) {
|
|
const fromStatus = this.status.computedWeight;
|
|
|
|
if ( typeof fromStatus === 'number' ) {
|
|
current = fromStatus;
|
|
} else if ( typeof fromSpec === 'number' ) {
|
|
current = fromSpec;
|
|
}
|
|
|
|
if ( typeof fromSpec === 'number' ) {
|
|
desired = fromSpec;
|
|
} else if ( typeof fromStatus === 'number' ) {
|
|
desired = fromStatus;
|
|
}
|
|
}
|
|
|
|
return { current, desired };
|
|
},
|
|
|
|
weightsPercent() {
|
|
const self = this.weights;
|
|
const app = this.weightsOfApp;
|
|
|
|
let desired = 0;
|
|
let current = 0;
|
|
|
|
if ( self.desired && app.desired ) {
|
|
desired = self.desired / app.desired * 100;
|
|
}
|
|
|
|
if ( self.current && app.current ) {
|
|
current = self.current / app.current * 100;
|
|
}
|
|
|
|
return { current, desired };
|
|
},
|
|
|
|
saveWeightPercent() {
|
|
return (newPercent) => {
|
|
const appInfo = this.weightsOfApp;
|
|
const totalWeight = appInfo.desired;
|
|
const currentPercent = (totalWeight === 0 ? 0 : this.weights.desired / totalWeight);
|
|
const currentWeight = this.spec.weight || 0;
|
|
const totalOfOthers = totalWeight - currentWeight;
|
|
const count = appInfo.count;
|
|
|
|
if ( currentPercent === 100 ) {
|
|
if ( newPercent === 100 ) {
|
|
return;
|
|
} else if ( newPercent === 0 ) {
|
|
return this.saveWeight(0);
|
|
}
|
|
|
|
const weight = newWeight(100 - newPercent) / (count - 1);
|
|
|
|
for ( const svc of this.allVersions ) {
|
|
if ( svc.id === this.id ) {
|
|
continue;
|
|
}
|
|
|
|
svc.saveWeight(weight);
|
|
}
|
|
} else if ( totalOfOthers === 0 || newPercent === 100 ) {
|
|
this.saveWeight(10000);
|
|
|
|
for ( const svc of this.allVersions ) {
|
|
if ( svc.id === this.id ) {
|
|
continue;
|
|
}
|
|
|
|
svc.saveWeight(0);
|
|
}
|
|
} else {
|
|
const weight = newWeight(newPercent);
|
|
|
|
this.saveWeight(weight);
|
|
}
|
|
|
|
function newWeight(percent) {
|
|
if ( percent === 0 ) {
|
|
return 0;
|
|
}
|
|
|
|
const out = Math.round(totalOfOthers / (1 - (percent / 100))) - totalOfOthers;
|
|
|
|
return out;
|
|
}
|
|
};
|
|
},
|
|
|
|
saveWeight() {
|
|
return async(neu) => {
|
|
console.log('Save Weight', this.spec.app, this.spec.version, neu); // eslint-disable-line no-console
|
|
try {
|
|
await this.patch([{
|
|
op: 'replace',
|
|
path: '/spec/weight',
|
|
value: neu
|
|
}]);
|
|
} catch (err) {
|
|
this.$dispatch('growl/fromError', { title: 'Error updating weight', err }, { root: true });
|
|
}
|
|
};
|
|
},
|
|
|
|
pauseOrResume() {
|
|
return async(pause = true) => {
|
|
try {
|
|
await this.patch([{
|
|
op: 'replace',
|
|
path: '/spec/rollout/pause',
|
|
value: pause
|
|
}]);
|
|
} catch (err) {
|
|
this.$dispatch('growl/fromError', { title: 'Error updating pause', err }, { root: true });
|
|
}
|
|
};
|
|
},
|
|
|
|
pause() {
|
|
this.pauseOrResume(true);
|
|
},
|
|
|
|
resume() {
|
|
this.pauseOrResume(false);
|
|
},
|
|
|
|
goToStage() {
|
|
return (moreQuery = {}) => {
|
|
const location = this.detailLocation;
|
|
|
|
location.query = {
|
|
...location.query,
|
|
[MODE]: _STAGE,
|
|
...moreQuery
|
|
};
|
|
|
|
this.currentRouter().push(location);
|
|
};
|
|
},
|
|
|
|
_availableActions() {
|
|
const links = this.links || {};
|
|
const out = this._standardActions;
|
|
|
|
let isPaused = false;
|
|
|
|
if ( this.spec.rollout && this.spec.rollout.pause ) {
|
|
isPaused = true;
|
|
}
|
|
|
|
insertAt(out, 2, {
|
|
action: 'pause',
|
|
label: 'Pause Rollout',
|
|
icon: 'icon icon-gear',
|
|
enabled: !!links.update && !isPaused,
|
|
});
|
|
|
|
insertAt(out, 2, {
|
|
action: 'resume',
|
|
label: 'Resume Rollout',
|
|
icon: 'icon icon-gear',
|
|
enabled: !!links.update && isPaused,
|
|
});
|
|
|
|
insertAt(out, 2, {
|
|
action: 'addSidecar',
|
|
label: 'Add a Sidecar',
|
|
icon: 'icon icon-circle-plus',
|
|
enabled: !!links.update,
|
|
});
|
|
|
|
insertAt(out, 2, {
|
|
action: 'goToStage',
|
|
label: 'Stage New Version',
|
|
icon: 'icon icon-copy',
|
|
enabled: !!links.update,
|
|
});
|
|
|
|
insertAt(out, 2, { divider: true });
|
|
|
|
return out;
|
|
},
|
|
|
|
addSidecar() {
|
|
return () => {
|
|
return this.goToEdit({ [ADD_SIDECAR]: _FLAGGED });
|
|
};
|
|
},
|
|
|
|
networkBytes() {
|
|
const read = get(this, 'metadata.computed.fields.readBytesPerSecond') || 0;
|
|
const write = get(this, 'metadata.computed.fields.writeBytesPerSecond') || 0;
|
|
|
|
return read + write;
|
|
},
|
|
|
|
networkDisplay() {
|
|
return formatSi(this.networkBytes, { suffix: 'Bps' });
|
|
},
|
|
|
|
p95() {
|
|
const out = get(this, 'metadata.computed.fields.p95') || 0;
|
|
|
|
return out;
|
|
},
|
|
|
|
p95Display() {
|
|
return `${ this.p95 }ms`;
|
|
},
|
|
|
|
connections() {
|
|
const out = get(this, 'metadata.computed.fields.openConnections') || 0;
|
|
|
|
return out;
|
|
},
|
|
};
|