+
-
+
@@ -191,11 +190,10 @@ export default {
-
+
import { isEmpty } from 'lodash';
+import throttle from 'lodash/throttle';
import ArrayList from '@/components/form/ArrayList';
import CreateEditView from '@/mixins/create-edit-view';
import KeyValue from '@/components/form/KeyValue';
@@ -16,15 +17,17 @@ import CruResource from '@/components/CruResource';
import Banner from '@/components/Banner';
import Labels from '@/components/form/Labels';
import { clone } from '@/utils/object';
+import { POD } from '@/config/types';
+import { matching } from '@/utils/selector';
const SESSION_AFFINITY_ACTION_VALUES = {
NONE: 'None',
- CLIENTIP: 'ClientIP'
+ CLIENTIP: 'ClientIP',
};
const SESSION_AFFINITY_ACTION_LABELS = {
NONE: 'servicesPage.affinity.actionLabels.none',
- CLIENTIP: 'servicesPage.affinity.actionLabels.clientIp'
+ CLIENTIP: 'servicesPage.affinity.actionLabels.clientIp',
};
const SESSION_STICKY_TIME_DEFAULT = 10800;
@@ -45,22 +48,36 @@ export default {
ServicePorts,
Tab,
Tabbed,
- UnitInput
+ UnitInput,
},
mixins: [CreateEditView],
+ fetch() {
+ return this.loadPods();
+ },
+
data() {
if (!this?.value?.spec?.type) {
if (!this.value?.spec) {
this.$set(this.value, 'spec', {
ports: [],
- sessionAffinity: 'None'
+ sessionAffinity: 'None',
});
}
}
+ const matchingPods = {
+ matched: 0,
+ matches: [],
+ none: true,
+ sample: null,
+ total: 0,
+ };
+
return {
+ matchingPods,
+ allPods: [],
defaultServiceTypes: DEFAULT_SERVICE_TYPES,
saving: false,
sessionAffinityActionLabels: Object.values(SESSION_AFFINITY_ACTION_LABELS)
@@ -68,7 +85,7 @@ export default {
.map(ucFirst),
sessionAffinityActionOptions: Object.values(
SESSION_AFFINITY_ACTION_VALUES
- )
+ ),
};
},
@@ -112,18 +129,26 @@ export default {
this.$set(this.value.spec, 'type', serviceType);
}
- }
+ },
},
showAffinityTimeout() {
- return this.value.spec.sessionAffinity === 'ClientIP' && !isEmpty(this.value.spec.sessionAffinityConfig);
+ return (
+ this.value.spec.sessionAffinity === 'ClientIP' &&
+ !isEmpty(this.value.spec.sessionAffinityConfig)
+ );
},
hasClusterIp() {
- return this.checkTypeIs('ClusterIP') || this.checkTypeIs('LoadBalancer') || this.checkTypeIs('NodePort');
- }
+ return (
+ this.checkTypeIs('ClusterIP') ||
+ this.checkTypeIs('LoadBalancer') ||
+ this.checkTypeIs('NodePort')
+ );
+ },
},
watch: {
+ 'value.spec.selector': 'updateMatchingPods',
'value.spec.sessionAffinity'(val) {
if (val === 'ClientIP') {
this.value.spec.sessionAffinityConfig = { clientIP: { timeoutSeconds: null } };
@@ -139,7 +164,7 @@ export default {
) {
delete this.value.spec.sessionAffinityConfig.clientIP.timeoutSeconds;
}
- }
+ },
},
created() {
@@ -153,6 +178,37 @@ export default {
},
methods: {
+ updateMatchingPods: throttle(function() {
+ const { allPods, value: { spec: { selector = { } } } } = this;
+
+ if (isEmpty(selector)) {
+ this.matchingPods = {
+ matched: 0,
+ total: allPods.length,
+ none: true,
+ sample: null,
+ };
+ } else {
+ const match = matching(allPods, selector);
+
+ this.matchingPods = {
+ matched: match.length,
+ total: allPods.length,
+ none: match.length === 0,
+ sample: match[0] ? match[0].nameDisplay : null,
+ };
+ }
+ }, 250, { leading: true }),
+
+ async loadPods() {
+ try {
+ const inStore = this.$store.getters['currentProduct'].inStore;
+
+ this.allPods = await this.$store.dispatch(`${ inStore }/findAll`, { type: POD });
+ this.matchingPods.total = this.allPods.length;
+ } catch (e) { }
+ },
+
checkTypeIs(typeIn) {
const { serviceType } = this;
@@ -187,8 +243,7 @@ export default {
this.value.spec.ports = this.targetPortsStrOrInt(this.value.spec.ports);
}
},
-
- }
+ },
};
@@ -201,10 +256,10 @@ export default {
:subtypes="defaultServiceTypes"
:validation-passed="true"
:errors="errors"
- @error="e=>errors = e"
+ @error="(e) => (errors = e)"
@finish="save"
@cancel="done"
- @select-type="(st) => serviceType = st"
+ @select-type="(st) => (serviceType = st)"
@apply-hooks="() => applyHooks('_beforeSaveHooks')"
>
@@ -226,12 +281,18 @@ export default {
:mode="mode"
:label="t('servicesPage.externalName.input.label')"
:placeholder="t('servicesPage.externalName.placeholder')"
+ :required="true"
type="text"
/>
-
+
@@ -258,24 +321,27 @@ export default {
:mode="mode"
:initial-empty-row="true"
:protip="false"
- @input="e=>$set(value.spec, 'selector', e)"
+ @input="(e) => $set(value.spec, 'selector', e)"
/>
-
-
+
+
$set(value.spec, 'clusterIP', e)"
+ :tooltip-key="
+ hasClusterIp ? 'servicesPage.ips.clusterIpHelpText' : null
+ "
+ @input="(e) => $set(value.spec, 'clusterIP', e)"
/>
@@ -288,7 +354,7 @@ export default {
:value-placeholder="t('servicesPage.ips.external.placeholder')"
:mode="mode"
:protip="false"
- @input="e=>$set(value.spec, 'externalIPs', e)"
+ @input="(e) => $set(value.spec, 'externalIPs', e)"
/>
@@ -313,10 +379,22 @@ export default {
$set(value.spec.sessionAffinityConfig.clientIP, 'timeoutSeconds', e)"
+ @input="
+ (e) =>
+ $set(
+ value.spec.sessionAffinityConfig.clientIP,
+ 'timeoutSeconds',
+ e
+ )
+ "
/>
diff --git a/mixins/auth.js b/mixins/auth.js
new file mode 100644
index 0000000000..0dd6eaaa5e
--- /dev/null
+++ b/mixins/auth.js
@@ -0,0 +1,165 @@
+import { NORMAN, MANAGEMENT } from '@/config/types';
+import { addObject, findBy } from '@/utils/array';
+
+export default {
+ async fetch() {
+ const NAME = this.$route.params.id;
+ const originalModel = await this.$store.dispatch('rancher/find', {
+ type: NORMAN.AUTH_CONFIG,
+ id: NAME,
+ opt: { url: `/v3/${ NORMAN.AUTH_CONFIG }/${ NAME }`, force: true }
+ });
+
+ const serverUrl = await this.$store.dispatch('management/find', {
+ type: MANAGEMENT.SETTING,
+ id: 'server-url',
+ opt: { url: `/v1/{ MANAGEMENT.SETTING }/server-url` }
+ });
+
+ if ( serverUrl ) {
+ this.serverSetting = serverUrl.value;
+ }
+
+ this.model = await this.$store.dispatch(`rancher/clone`, { resource: originalModel });
+ if (NAME === 'shibboleth' && !this.model.openLdapConfig) {
+ this.model.openLdapConfig = {};
+ this.showLdap = false;
+ }
+ },
+
+ data() {
+ return { isSaving: false };
+ },
+
+ computed: {
+ me() {
+ const out = findBy(this.principals, 'me', true);
+
+ return out;
+ },
+
+ serverUrl() {
+ if ( this.serverSetting ) {
+ return this.serverSetting;
+ } else if ( process.client ) {
+ return window.location.origin;
+ }
+
+ return '';
+ },
+
+ principal() {
+ return this.$store.getters['rancher/byId'](NORMAN.PRINCIPAL, this.$store.getters['auth/principalId']) || {};
+ },
+
+ displayName() {
+ return this.t(`model.authConfig.provider.${ this.NAME }`);
+ },
+
+ NAME() {
+ return this.$route.params.id;
+ },
+
+ AUTH_CONFIG() {
+ return MANAGEMENT.AUTH_CONFIG;
+ }
+ },
+
+ methods: {
+ async save(btnCb) {
+ const configType = this.value.configType;
+
+ this.isSaving = true;
+ this.errors = [];
+ const wasEnabled = this.model.enabled;
+ let obj = this.toSave;
+
+ if (!obj) {
+ obj = this.model;
+ }
+
+ try {
+ if ( !wasEnabled ) {
+ if (configType === 'oauth') {
+ const code = await this.$store.dispatch('auth/test', { provider: this.model.id, body: this.model });
+
+ this.model.enabled = true;
+ obj.code = code;
+ }
+ if (configType === 'saml') {
+ this.model.enabled = true;
+ if (!this.model.accessMode) {
+ this.model.accessMode = 'unrestricted';
+ }
+ await this.model.save();
+ await this.model.doAction('testAndEnable', obj);
+ } else {
+ this.model.enabled = true;
+ if (!this.model.accessMode) {
+ this.model.accessMode = 'unrestricted';
+ }
+ await this.model.doAction('testAndApply', obj);
+ }
+
+ // Reload principals to get the new ones from the provider
+ this.principals = await this.$store.dispatch('rancher/findAll', {
+ type: NORMAN.PRINCIPAL,
+ opt: { url: '/v3/principals', force: true }
+ });
+
+ this.model.allowedPrincipalIds = this.model.allowedPrincipalIds || [];
+ if ( this.me && !this.model.allowedPrincipalIds.includes(this.me.id) ) {
+ addObject(this.model.allowedPrincipalIds, this.me.id);
+ }
+ }
+
+ if (configType === 'oauth') {
+ await this.model.save();
+ await this.reloadModel();
+ }
+ this.isSaving = false;
+ btnCb(true);
+ if ( wasEnabled ) {
+ this.done();
+ }
+ // this.$router.applyQuery( { mode: 'view' } );
+ } catch (err) {
+ this.errors = [err];
+ btnCb(false);
+ this.model.enabled = wasEnabled;
+ this.isSaving = false;
+ }
+ },
+
+ async disable(btnCb) {
+ try {
+ if (this.model.hasAction('disable')) {
+ await this.model.doAction('disable');
+ } else {
+ const clone = await this.$store.dispatch(`rancher/clone`, { resource: this.model });
+
+ clone.enabled = false;
+ await clone.save();
+ }
+ await this.reloadModel();
+
+ btnCb(true);
+ } catch (err) {
+ this.errors = [err];
+ btnCb(false);
+ }
+ },
+
+ async reloadModel() {
+ this.originalModel = await this.$store.dispatch('rancher/find', {
+ type: NORMAN.AUTH_CONFIG,
+ id: this.NAME,
+ opt: { url: `/v3/${ NORMAN.AUTH_CONFIG }/${ this.NAME }`, force: true }
+ });
+
+ this.model = await this.$store.dispatch(`rancher/clone`, { resource: this.originalModel });
+
+ return this.model;
+ },
+ },
+};
diff --git a/models/management.cattle.io.authconfig.js b/models/management.cattle.io.authconfig.js
index bc28e13bc3..cfcc4efb2a 100644
--- a/models/management.cattle.io.authconfig.js
+++ b/models/management.cattle.io.authconfig.js
@@ -1,4 +1,5 @@
import { insertAt } from '@/utils/array';
+import { set } from '@/utils/object';
const configType = {
activedirectory: 'ldap',
@@ -61,5 +62,27 @@ export default {
await this.save();
this.currentRouter().push({ name: 'c-cluster-auth-config' });
};
+ },
+
+ applyDefaults() {
+ return () => {
+ switch (this.configType) {
+ case 'saml':
+ set(this, 'accessMode', 'unrestricted');
+
+ if (this.id === 'shibboleth' && !this.openLdapConfig) {
+ set(this, 'openLdapConfig', {});
+ }
+ break;
+ case 'ldap':
+ set(this, 'servers', []);
+ set(this, 'accessMode', 'unrestricted');
+ set(this, 'starttls', false);
+
+ break;
+ default:
+ break;
+ }
+ };
}
};
diff --git a/models/namespace.js b/models/namespace.js
index ef58932174..101f312c95 100644
--- a/models/namespace.js
+++ b/models/namespace.js
@@ -1,9 +1,41 @@
import SYSTEM_NAMESPACES from '@/config/system-namespaces';
-import { PROJECT, SYSTEM_NAMESPACE } from '@/config/labels-annotations';
-import { MANAGEMENT } from '@/config/types';
+import { PROJECT, SYSTEM_NAMESPACE, ISTIO as ISTIO_LABELS } from '@/config/labels-annotations';
+import { ISTIO, MANAGEMENT } from '@/config/types';
+
import { escapeHtml } from '@/utils/string';
+import { insertAt, isArray } from '@/utils/array';
export default {
+
+ _availableActions() {
+ const out = this._standardActions;
+
+ insertAt(out, 0, { divider: true });
+ if (this.istioInstalled) {
+ insertAt(out, 0, {
+ action: 'enableAutoInjection',
+ label: this.t('namespace.enableAutoInjection'),
+ bulkable: true,
+ bulkAction: 'enableAutoInjection',
+ enabled: !this.injectionEnabled,
+ icon: 'icon icon-plus',
+ weight: 2
+
+ });
+ insertAt(out, 0, {
+ action: 'disableAutoInjection',
+ label: this.t('namespace.disableAutoInjection'),
+ bulkable: true,
+ bulkAction: 'disableAutoInjection',
+ enabled: this.injectionEnabled,
+ icon: 'icon icon-minus',
+ weight: 1,
+ });
+ }
+
+ return out;
+ },
+
isSystem() {
if ( this.metadata?.annotations?.[SYSTEM_NAMESPACE] === 'true' ) {
return true;
@@ -51,5 +83,40 @@ export default {
projectNameSort() {
return this.project?.nameSort || '';
- }
+ },
+
+ istioInstalled() {
+ const schema = this.$rootGetters['cluster/schemaFor'](ISTIO.GATEWAY);
+
+ return !!schema;
+ },
+
+ injectionEnabled() {
+ return this.labels[ISTIO_LABELS.AUTO_INJECTION] === 'enabled';
+ },
+
+ enableAutoInjection() {
+ return (namespaces = this, enable = true) => {
+ if (!isArray(namespaces)) {
+ namespaces = [namespaces];
+ }
+ namespaces.forEach((ns) => {
+ if (!enable && ns?.metadata?.labels) {
+ delete ns.metadata.labels[ISTIO_LABELS.AUTO_INJECTION];
+ } else {
+ if (!ns.metadata.labels) {
+ ns.metadata.labels = {};
+ }
+ ns.metadata.labels[ISTIO_LABELS.AUTO_INJECTION] = 'enabled';
+ }
+ ns.save();
+ });
+ };
+ },
+
+ disableAutoInjection() {
+ return (namespaces = this) => {
+ this.enableAutoInjection(namespaces, false);
+ };
+ },
};
diff --git a/models/resources.cattle.io.backup.js b/models/resources.cattle.io.backup.js
new file mode 100644
index 0000000000..d14bb93377
--- /dev/null
+++ b/models/resources.cattle.io.backup.js
@@ -0,0 +1,28 @@
+import { colorForState, stateDisplay } from '@/plugins/steve/resource-instance';
+import { findBy } from '@/utils/array';
+import { get } from '@/utils/object';
+
+export default {
+ readyMessage() {
+ const conditions = get(this, 'status.conditions');
+ const readyMessage = (findBy(conditions, 'type', 'Ready') || {}).message ;
+
+ return readyMessage;
+ },
+ colorForState() {
+ if (this.readyMessage) {
+ return colorForState(this.readyMessage);
+ }
+
+ return colorForState();
+ },
+
+ stateDisplay() {
+ if (this.readyMessage) {
+ return stateDisplay(this.readyMessage);
+ }
+
+ return stateDisplay();
+ }
+
+};
diff --git a/models/resources.cattle.io.restore.js b/models/resources.cattle.io.restore.js
index 37961f6ed4..43d32c3e11 100644
--- a/models/resources.cattle.io.restore.js
+++ b/models/resources.cattle.io.restore.js
@@ -1,5 +1,30 @@
+import { colorForState, stateDisplay } from '@/plugins/steve/resource-instance';
+import { findBy } from '@/utils/array';
+import { get } from '@/utils/object';
export default {
canUpdate() {
return this?.metadata?.state?.error;
},
+ readyMessage() {
+ const conditions = get(this, 'status.conditions');
+ const readyMessage = (findBy(conditions, 'type', 'Ready') || {}).message ;
+
+ return readyMessage;
+ },
+ colorForState() {
+ if (this.readyMessage) {
+ return colorForState(this.readyMessage);
+ }
+
+ return colorForState();
+ },
+
+ stateDisplay() {
+ if (this.readyMessage) {
+ return stateDisplay(this.readyMessage);
+ }
+
+ return stateDisplay();
+ }
+
};
diff --git a/pages/account/index.vue b/pages/account/index.vue
index 15300bbd9d..6c9b806f9d 100644
--- a/pages/account/index.vue
+++ b/pages/account/index.vue
@@ -1,38 +1,67 @@
+
+
+