mirror of https://github.com/rancher/dashboard.git
489 lines
12 KiB
JavaScript
489 lines
12 KiB
JavaScript
import { convert, matching, convertSelectorObj } from '@shell/utils/selector';
|
|
import jsyaml from 'js-yaml';
|
|
import isEmpty from 'lodash/isEmpty';
|
|
import { escapeHtml } from '@shell/utils/string';
|
|
import { FLEET } from '@shell/config/types';
|
|
import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
import { addObject, addObjects, findBy, insertAt } from '@shell/utils/array';
|
|
import { set } from '@shell/utils/object';
|
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
import {
|
|
colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, STATES_ENUM, stateSort,
|
|
} from '@shell/plugins/dashboard-store/resource-class';
|
|
import { NAME } from '@shell/config/product/explorer';
|
|
import FleetUtils from '@shell/utils/fleet';
|
|
|
|
function quacksLikeAHash(str) {
|
|
if (str.match(/^[a-f0-9]{40,}$/i)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function normalizeStateCounts(data) {
|
|
if (isEmpty(data)) {
|
|
return {
|
|
total: 0,
|
|
states: {},
|
|
};
|
|
}
|
|
const { desiredReady, ...rest } = data ;
|
|
const states = Object.entries(rest).reduce((res, [key, value]) => {
|
|
res[mapStateToEnum(key)] = value;
|
|
|
|
return res;
|
|
}, {});
|
|
|
|
return {
|
|
total: desiredReady,
|
|
states,
|
|
};
|
|
}
|
|
|
|
export default class GitRepo extends SteveModel {
|
|
applyDefaults() {
|
|
const spec = this.spec || {};
|
|
const meta = this.metadata || {};
|
|
|
|
meta.namespace = this.$rootGetters['workspace'];
|
|
|
|
spec.repo = spec.repo || '';
|
|
|
|
if (!spec.branch && !spec.revision) {
|
|
spec.branch = 'master';
|
|
}
|
|
|
|
spec.paths = spec.paths || [];
|
|
spec.clientSecretName = spec.clientSecretName || null;
|
|
|
|
spec['correctDrift'] = { enabled: false };
|
|
|
|
set(this, 'spec', spec);
|
|
set(this, 'metadata', meta);
|
|
}
|
|
|
|
get _availableActions() {
|
|
const out = super._availableActions;
|
|
|
|
insertAt(out, 0, {
|
|
action: 'pause',
|
|
label: 'Pause',
|
|
icon: 'icon icon-pause',
|
|
bulkable: true,
|
|
enabled: !!this.links.update && !this.spec?.paused
|
|
});
|
|
|
|
insertAt(out, 1, {
|
|
action: 'unpause',
|
|
label: 'Unpause',
|
|
icon: 'icon icon-play',
|
|
bulkable: true,
|
|
enabled: !!this.links.update && this.spec?.paused === true
|
|
});
|
|
|
|
insertAt(out, 2, {
|
|
action: 'forceUpdate',
|
|
label: 'Force Update',
|
|
icon: 'icon icon-refresh',
|
|
bulkable: true,
|
|
enabled: !!this.links.update
|
|
});
|
|
|
|
insertAt(out, 3, { divider: true });
|
|
|
|
return out;
|
|
}
|
|
|
|
pause() {
|
|
this.spec.paused = true;
|
|
this.save();
|
|
}
|
|
|
|
unpause() {
|
|
this.spec.paused = false;
|
|
this.save();
|
|
}
|
|
|
|
forceUpdate() {
|
|
const now = this.spec.forceSyncGeneration || 1;
|
|
|
|
this.spec.forceSyncGeneration = now + 1;
|
|
this.save();
|
|
}
|
|
|
|
get state() {
|
|
if (this.spec?.paused === true) {
|
|
return 'paused';
|
|
}
|
|
|
|
return this.metadata?.state?.name || 'unknown';
|
|
}
|
|
|
|
get targetClusters() {
|
|
const workspace = this.$getters['byId'](FLEET.WORKSPACE, this.metadata.namespace);
|
|
const clusters = workspace?.clusters || [];
|
|
const groups = workspace?.clusterGroups || [];
|
|
|
|
if (workspace?.id === 'fleet-local') {
|
|
// should we be getting the clusters from workspace.clusters instead of having to rely on the groups,
|
|
// which takes an additional request to be done on the Fleet dashboard screen?
|
|
const local = findBy(groups, 'id', 'fleet-local/default');
|
|
|
|
if (local) {
|
|
return local.targetClusters;
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
if (!this.spec.targets) {
|
|
return [];
|
|
}
|
|
|
|
const out = [];
|
|
|
|
for (const tgt of this.spec.targets) {
|
|
if (tgt.clusterName) {
|
|
const cluster = findBy(clusters, 'metadata.name', tgt.clusterName);
|
|
|
|
if (cluster) {
|
|
addObject(out, cluster);
|
|
}
|
|
} else if (tgt.clusterGroup) {
|
|
const group = findBy(groups, {
|
|
'metadata.namespace': this.metadata.namespace,
|
|
'metadata.name': tgt.clusterGroup,
|
|
});
|
|
|
|
if (group) {
|
|
addObjects(out, group.targetClusters);
|
|
}
|
|
} else if (tgt.clusterGroupSelector) {
|
|
const expressions = convertSelectorObj(tgt.clusterGroupSelector);
|
|
const matchingGroups = matching(groups, expressions);
|
|
|
|
for (const group of matchingGroups) {
|
|
addObjects(out, group.targetClusters);
|
|
}
|
|
} else if (tgt.clusterSelector) {
|
|
const expressions = convertSelectorObj(tgt.clusterSelector);
|
|
const matchingClusters = matching(clusters, expressions);
|
|
|
|
addObjects(out, matchingClusters);
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
get github() {
|
|
const match = this.spec.repo.match(/^https?:\/\/github\.com\/(.*?)(\.git)?\/*$/);
|
|
|
|
if (match) {
|
|
return match[1];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
get repoIcon() {
|
|
if (this.github) {
|
|
return 'icon icon-github';
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
get repoDisplay() {
|
|
let repo = this.spec.repo;
|
|
|
|
if (!repo) {
|
|
return null;
|
|
}
|
|
|
|
repo = repo.replace(/.git$/, '');
|
|
repo = repo.replace(/^https:\/\//, '');
|
|
repo = repo.replace(/\/+$/, '');
|
|
|
|
if (this.github) {
|
|
return this.github;
|
|
}
|
|
|
|
return repo;
|
|
}
|
|
|
|
get commitDisplay() {
|
|
const spec = this.spec;
|
|
const hash = this.status?.commit?.substr(0, 7);
|
|
|
|
if (!spec || !spec.repo) {
|
|
return null;
|
|
}
|
|
|
|
if (spec.revision && quacksLikeAHash(spec.revision)) {
|
|
return spec.revision.substr(0, 7);
|
|
} else if (spec.revision) {
|
|
return spec.revision;
|
|
} else if (spec.branch) {
|
|
return spec.branch + (hash ? ` @ ${ hash }` : '');
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
get targetInfo() {
|
|
let mode = null;
|
|
let cluster = null;
|
|
let clusterGroup = null;
|
|
let advanced = null;
|
|
|
|
const targets = this.spec.targets || [];
|
|
|
|
advanced = jsyaml.dump(targets);
|
|
|
|
if (advanced === '[]\n') {
|
|
advanced = `# - name:
|
|
# clusterSelector:
|
|
# matchLabels:
|
|
# foo: bar
|
|
# matchExpressions:
|
|
# - key: foo
|
|
# op: In
|
|
# values: [bar, baz]
|
|
# clusterGroup: foo
|
|
# clusterGroupSelector:
|
|
# matchLabels:
|
|
# foo: bar
|
|
# matchExpressions:
|
|
# - key: foo
|
|
# op: In
|
|
# values: [bar, baz]
|
|
`;
|
|
}
|
|
|
|
if (this.metadata.namespace === 'fleet-local') {
|
|
mode = 'local';
|
|
} else if (!targets.length) {
|
|
mode = 'none';
|
|
} else if (targets.length === 1) {
|
|
const target = targets[0];
|
|
|
|
if (Object.keys(target).length > 1) {
|
|
// There are multiple properties in a single target, so use the 'advanced' mode
|
|
// (otherwise any existing content is nuked for what we provide)
|
|
mode = 'advanced';
|
|
} else if (target.clusterGroup) {
|
|
clusterGroup = target.clusterGroup;
|
|
|
|
if (!mode) {
|
|
mode = 'clusterGroup';
|
|
}
|
|
} else if (target.clusterName) {
|
|
mode = 'cluster';
|
|
cluster = target.clusterName;
|
|
} else if (target.clusterSelector) {
|
|
if (Object.keys(target.clusterSelector).length === 0) {
|
|
mode = 'all';
|
|
} else {
|
|
const expressions = convert(target.clusterSelector.matchLabels, target.clusterSelector.matchExpressions);
|
|
|
|
if (expressions.length === 1 &&
|
|
expressions[0].key === FLEET_ANNOTATIONS.CLUSTER_NAME &&
|
|
expressions[0].operator === 'In' &&
|
|
expressions[0].values.length === 1
|
|
) {
|
|
cluster = expressions[0].values[0];
|
|
if (!mode) {
|
|
mode = 'cluster';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!mode) {
|
|
mode = 'advanced';
|
|
}
|
|
|
|
return {
|
|
mode,
|
|
modeDisplay: this.t(`fleet.gitRepo.targetDisplay."${ mode }"`),
|
|
cluster,
|
|
clusterGroup,
|
|
advanced
|
|
};
|
|
}
|
|
|
|
get groupByLabel() {
|
|
const name = this.metadata.namespace;
|
|
|
|
if (name) {
|
|
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.workspace', { name: escapeHtml(name) });
|
|
} else {
|
|
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.notInAWorkspace');
|
|
}
|
|
}
|
|
|
|
get bundles() {
|
|
return this.$getters['matching'](FLEET.BUNDLE, { 'fleet.cattle.io/repo-name': this.name }, this.namespace);
|
|
}
|
|
|
|
get bundleDeployments() {
|
|
const bds = this.$getters['all'](FLEET.BUNDLE_DEPLOYMENT);
|
|
|
|
return bds.filter((bd) => bd.metadata?.labels?.['fleet.cattle.io/repo-name'] === this.name);
|
|
}
|
|
|
|
get allBundlesStatuses() {
|
|
const bundleDeploymentCountsPerBundle = this.bundleDeployments.reduce((acc, bd) => {
|
|
const bundleId = FleetUtils.bundleIdFromBundleDeploymentLabels(bd.metadata?.labels);
|
|
const state = mapStateToEnum(FleetUtils.bundleDeploymentState(bd));
|
|
|
|
if (!acc[bundleId]) {
|
|
acc[bundleId] = {
|
|
total: 0,
|
|
states: { [STATES_ENUM.READY]: 0 },
|
|
};
|
|
}
|
|
acc[bundleId].total++;
|
|
|
|
if (!acc[bundleId].states[state]) {
|
|
acc[bundleId].states[state] = 0;
|
|
}
|
|
acc[bundleId].states[state]++;
|
|
|
|
return acc;
|
|
}, {});
|
|
const bundleIds = Object.keys(bundleDeploymentCountsPerBundle);
|
|
|
|
return bundleIds.reduce((acc, bundleId) => {
|
|
const state = primaryDisplayStatusFromCount(bundleDeploymentCountsPerBundle[bundleId].states);
|
|
|
|
if (!acc.states[state]) {
|
|
acc.states[state] = 0;
|
|
}
|
|
acc.states[state]++;
|
|
|
|
return acc;
|
|
}, { total: bundleIds.length, states: { [STATES_ENUM.READY]: 0 } } );
|
|
}
|
|
|
|
get allResourceStatuses() {
|
|
return normalizeStateCounts(this.status?.resourceCounts || {});
|
|
}
|
|
|
|
statusResourceCountsForCluster(clusterId) {
|
|
if (!this.targetClusters.some((c) => c.id === clusterId)) {
|
|
return {};
|
|
}
|
|
|
|
return this.bundleDeployments
|
|
.filter((bd) => FleetUtils.clusterIdFromBundleDeploymentLabels(bd.metadata?.labels) === clusterId)
|
|
.map((bd) => FleetUtils.resourcesFromBundleDeploymentStatus(bd.status))
|
|
.flat()
|
|
.map((r) => r.state)
|
|
.reduce((prev, state) => {
|
|
if (!prev[state]) {
|
|
prev[state] = 0;
|
|
}
|
|
prev[state]++;
|
|
prev.desiredReady++;
|
|
|
|
return prev;
|
|
}, { desiredReady: 0 });
|
|
}
|
|
|
|
get resourcesStatuses() {
|
|
const bundleDeployments = this.bundleDeployments || [];
|
|
const clusters = (this.targetClusters || []).reduce((res, c) => {
|
|
res[c.id] = c;
|
|
|
|
return res;
|
|
}, {});
|
|
|
|
const out = [];
|
|
|
|
for (const bd of bundleDeployments) {
|
|
const clusterId = FleetUtils.clusterIdFromBundleDeploymentLabels(bd.metadata?.labels);
|
|
const c = clusters[clusterId];
|
|
|
|
if (!c) {
|
|
continue;
|
|
}
|
|
|
|
const resources = FleetUtils.resourcesFromBundleDeploymentStatus(bd.status);
|
|
|
|
resources.forEach((r) => {
|
|
const id = FleetUtils.resourceId(r);
|
|
const type = FleetUtils.resourceType(r);
|
|
const state = r.state;
|
|
|
|
const color = colorForState(state).replace('text-', 'bg-');
|
|
const display = stateDisplay(state);
|
|
|
|
const detailLocation = {
|
|
name: `c-cluster-product-resource${ r.namespace ? '-namespace' : '' }-id`,
|
|
params: {
|
|
product: NAME,
|
|
cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME], // explorer uses the "management" Cluster name, which differs from the Fleet Cluster name
|
|
resource: type,
|
|
namespace: r.namespace,
|
|
id: r.name,
|
|
}
|
|
};
|
|
|
|
const key = `${ c.id }-${ type }-${ r.namespace }-${ r.name }`;
|
|
|
|
out.push({
|
|
key,
|
|
tableKey: key,
|
|
|
|
// Needed?
|
|
id,
|
|
type,
|
|
clusterId: c.id,
|
|
|
|
// columns, see FleetResources.vue
|
|
state: mapStateToEnum(state),
|
|
clusterName: c.nameDisplay,
|
|
apiVersion: r.apiVersion,
|
|
kind: r.kind,
|
|
name: r.name,
|
|
namespace: r.namespace,
|
|
creationTimestamp: r.createdAt,
|
|
|
|
// other properties
|
|
stateBackground: color,
|
|
stateDisplay: display,
|
|
stateSort: stateSort(color, display),
|
|
detailLocation,
|
|
});
|
|
});
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
get clusterInfo() {
|
|
const ready = this.status?.readyClusters || 0;
|
|
const total = this.status?.desiredReadyClusters || 0;
|
|
|
|
return {
|
|
ready,
|
|
unready: total - ready,
|
|
total,
|
|
};
|
|
}
|
|
|
|
clusterState(clusterId) {
|
|
const resourceCounts = this.statusResourceCountsForCluster(clusterId);
|
|
|
|
return primaryDisplayStatusFromCount(resourceCounts) || STATES_ENUM.ACTIVE;
|
|
}
|
|
|
|
get clustersList() {
|
|
return this.$getters['all'](FLEET.CLUSTER);
|
|
}
|
|
}
|