@rancher/shell 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/images/providers/ovhcloudmks.svg +122 -0
- package/assets/images/providers/ovhcloudpubliccloud.svg +122 -0
- package/assets/styles/global/_layout.scss +99 -0
- package/assets/translations/en-us.yaml +30 -5
- package/assets/translations/zh-hans.yaml +1 -1
- package/babel.config.js +7 -1
- package/chart/monitoring/alerting/index.vue +7 -21
- package/chart/monitoring/grafana/index.vue +55 -0
- package/chart/monitoring/index.vue +51 -17
- package/chart/monitoring/prometheus/index.vue +37 -43
- package/chart/rancher-backup/index.vue +2 -1
- package/cloud-credential/azure.vue +4 -17
- package/components/Certificates.vue +164 -0
- package/components/CodeMirror.vue +19 -21
- package/components/CruResource.vue +1 -0
- package/components/EtcdInfoBanner.vue +1 -1
- package/components/ExplorerProjectsNamespaces.vue +25 -1
- package/components/IconOrSvg.vue +1 -1
- package/components/LandingPagePreference.vue +1 -4
- package/components/Questions/index.vue +1 -1
- package/components/ResourceDetail/Masthead.vue +16 -3
- package/components/ResourceTable.vue +14 -2
- package/components/ResourceYaml.vue +5 -0
- package/components/SideNav.vue +1 -1
- package/components/SingleClusterInfo.vue +1 -4
- package/components/Tabbed/index.vue +12 -0
- package/components/fleet/FleetRepos.vue +62 -27
- package/components/fleet/FleetResources.vue +6 -1
- package/components/form/ArrayListSelect.vue +10 -0
- package/components/form/KeyValue.vue +4 -0
- package/components/form/LabeledSelect.vue +4 -0
- package/components/formatter/Checked.vue +11 -3
- package/components/formatter/FleetClusterSummaryGraph.vue +27 -0
- package/components/formatter/FleetSummaryGraph.vue +23 -11
- package/components/formatter/LiveDuration.vue +1 -1
- package/components/formatter/PercentageBar.vue +1 -1
- package/components/formatter/__tests__/Checked.test.ts +19 -0
- package/components/nav/Group.vue +2 -2
- package/components/nav/Header.vue +0 -1
- package/components/nav/TopLevelMenu.vue +36 -6
- package/components/nav/Type.vue +1 -3
- package/components/nav/WindowManager/ContainerLogs.vue +101 -3
- package/components/nav/WindowManager/ContainerShell.vue +6 -1
- package/components/nav/WindowManager/__tests__/ContainerLogs.test.ts +186 -0
- package/components/nav/WindowManager/index.vue +11 -10
- package/components/nav/__tests__/TopLevelMenu.test.ts +33 -0
- package/components/nav/__tests__/Type.test.ts +1 -1
- package/components/nuxt/nuxt-child.js +14 -78
- package/components/nuxt/nuxt.js +1 -1
- package/{layouts → components/templates}/blank.vue +1 -1
- package/{layouts → components/templates}/default.vue +8 -98
- package/{layouts → components/templates}/error.vue +10 -19
- package/{layouts → components/templates}/home.vue +4 -1
- package/{layouts → components/templates}/plain.vue +4 -1
- package/{layouts → components/templates}/standalone.vue +1 -1
- package/{layouts → components/templates}/unauthenticated.vue +1 -1
- package/composables/useCompactInput.ts +20 -0
- package/composables/useLabeledFormElement.ts +138 -0
- package/config/harvester-manager-types.js +2 -0
- package/config/private-label.js +22 -0
- package/config/product/explorer.js +3 -0
- package/config/product/fleet.js +6 -1
- package/config/product/manager.js +8 -2
- package/config/query-params.js +1 -0
- package/config/router.js +385 -364
- package/config/settings.ts +1 -0
- package/config/store.js +1 -1
- package/config/system-namespaces.js +3 -0
- package/config/table-headers.js +47 -0
- package/core/plugin-routes.ts +56 -114
- package/core/plugin.ts +16 -10
- package/core/plugins-loader.js +7 -9
- package/core/plugins.js +0 -3
- package/creators/app/files/.gitlab-ci.yml +1 -1
- package/detail/fleet.cattle.io.cluster.vue +11 -1
- package/detail/provisioning.cattle.io.cluster.vue +4 -3
- package/dialog/ScaleMachineDownDialog.vue +34 -17
- package/edit/__tests__/service.test.ts +89 -0
- package/edit/auth/googleoauth.vue +1 -5
- package/edit/catalog.cattle.io.clusterrepo.vue +18 -0
- package/edit/cloudcredential.vue +2 -0
- package/edit/configmap.vue +2 -1
- package/edit/networking.k8s.io.networkpolicy/__tests__/PolicyRuleTarget.spec.ts +1 -1
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +15 -7
- package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +112 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +473 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/{CustomCommand.tests.ts → CustomCommand.test.ts} +4 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/DrainOptions.test.ts +1 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +73 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +7 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/utils/cluster.ts +386 -0
- package/edit/provisioning.cattle.io.cluster/import.vue +2 -2
- package/edit/provisioning.cattle.io.cluster/index.vue +92 -36
- package/edit/provisioning.cattle.io.cluster/rke2.vue +171 -583
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +137 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Advanced.vue +157 -0
- package/edit/provisioning.cattle.io.cluster/{Basics.vue → tabs/Basics.vue} +94 -19
- package/edit/provisioning.cattle.io.cluster/{MachinePool.vue → tabs/MachinePool.vue} +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +135 -0
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +189 -0
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +144 -0
- package/edit/provisioning.cattle.io.cluster/tabs/upgrade/index.vue +76 -0
- package/edit/service.vue +12 -0
- package/edit/workload/mixins/workload.js +1 -1
- package/initialize/App.js +25 -71
- package/initialize/client.js +21 -162
- package/initialize/index.js +27 -123
- package/list/management.cattle.io.feature.vue +1 -7
- package/list/node.vue +1 -0
- package/machine-config/__tests__/vmwarevsphere.test.ts +100 -21
- package/machine-config/vmwarevsphere.vue +73 -51
- package/middleware/authenticated.js +10 -17
- package/mixins/auth-config.js +2 -7
- package/mixins/brand.js +29 -41
- package/mixins/labeled-form-element.ts +6 -1
- package/models/__tests__/management.cattle.io.node.ts +85 -0
- package/models/__tests__/management.cattle.io.nodepool.ts +83 -0
- package/models/__tests__/namespace.test.ts +49 -9
- package/models/__tests__/workload.test.ts +91 -0
- package/models/cluster/node.js +4 -4
- package/models/cluster.x-k8s.io.machinedeployment.js +14 -0
- package/models/fleet.cattle.io.cluster.js +4 -0
- package/models/fleet.cattle.io.gitrepo.js +56 -13
- package/models/management.cattle.io.kontainerdriver.js +1 -1
- package/models/management.cattle.io.node.js +18 -14
- package/models/management.cattle.io.nodepool.js +17 -0
- package/models/namespace.js +1 -1
- package/models/pod.js +20 -0
- package/models/provisioning.cattle.io.cluster.js +20 -3
- package/models/secret.js +117 -18
- package/models/workload.js +16 -0
- package/models/workload.service.js +18 -0
- package/package.json +10 -9
- package/pages/about.vue +0 -1
- package/pages/account/create-key.vue +0 -1
- package/pages/account/index.vue +0 -1
- package/pages/auth/login.vue +0 -1
- package/pages/auth/logout.vue +0 -2
- package/pages/auth/setup.vue +0 -4
- package/pages/auth/verify.vue +14 -8
- package/pages/c/_cluster/apps/charts/install.vue +4 -4
- package/pages/c/_cluster/apps/index.vue +0 -2
- package/pages/c/_cluster/auth/index.vue +0 -2
- package/pages/c/_cluster/ecm/index.vue +0 -2
- package/pages/c/_cluster/explorer/index.vue +28 -2
- package/pages/c/_cluster/fleet/index.vue +1 -1
- package/pages/c/_cluster/index.vue +0 -2
- package/pages/c/_cluster/settings/banners.vue +0 -2
- package/pages/c/_cluster/settings/brand.vue +0 -2
- package/pages/c/_cluster/settings/index.vue +0 -2
- package/pages/c/_cluster/settings/links.vue +0 -1
- package/pages/c/_cluster/settings/performance.vue +0 -1
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +2 -1
- package/pages/c/_cluster/uiplugins/CatalogList/index.vue +10 -46
- package/pages/c/_cluster/uiplugins/index.vue +0 -2
- package/pages/diagnostic.vue +1 -2
- package/pages/fail-whale.vue +0 -1
- package/pages/prefs.vue +0 -1
- package/pages/support/index.vue +2 -8
- package/pkg/auto-import.js +1 -1
- package/plugins/axios.js +0 -36
- package/plugins/back-button.js +3 -5
- package/plugins/codemirror-loader.js +1 -1
- package/plugins/codemirror.js +41 -0
- package/plugins/dashboard-store/__tests__/{mutations.spec.ts → mutations.test.ts} +1 -1
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +49 -0
- package/plugins/dashboard-store/__tests__/utils/store-mocks.ts +7 -0
- package/plugins/dashboard-store/actions.js +30 -4
- package/plugins/dashboard-store/classify.js +1 -18
- package/plugins/dashboard-store/getters.js +10 -5
- package/plugins/dashboard-store/index.js +0 -12
- package/plugins/dashboard-store/mutations.js +0 -4
- package/plugins/dashboard-store/resource-class.js +59 -18
- package/plugins/steve/__tests__/steve-class.spec.ts +59 -0
- package/plugins/steve/__tests__/utils/steve-mocks.ts +31 -0
- package/plugins/steve/getters.js +4 -1
- package/plugins/steve/norman-class.js +19 -0
- package/plugins/steve/steve-class.js +22 -0
- package/plugins/steve/subscribe.js +4 -10
- package/rancher-components/Accordion/Accordion.test.ts +45 -0
- package/rancher-components/Accordion/Accordion.vue +85 -0
- package/rancher-components/Accordion/index.ts +1 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +19 -2
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +12 -1
- package/rancher-components/Form/Radio/RadioButton.test.ts +7 -3
- package/rancher-components/Form/Radio/RadioGroup.test.ts +30 -0
- package/rancher-components/Form/Radio/RadioGroup.vue +4 -0
- package/rancher-components/StringList/StringList.test.ts +270 -0
- package/rancher-components/StringList/StringList.vue +57 -18
- package/rancher-components/components/Accordion/Accordion.test.ts +45 -0
- package/rancher-components/components/Accordion/Accordion.vue +85 -0
- package/rancher-components/components/Accordion/index.ts +1 -0
- package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +19 -2
- package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +4 -1
- package/scripts/.gitlab/workflows/build-extension-catalog.gitlab-ci.yml +50 -0
- package/scripts/extension/parse-tag-name +2 -2
- package/scripts/publish-shell.sh +10 -0
- package/scripts/test-plugins-build.sh +85 -9
- package/server/har-file.js +183 -0
- package/store/catalog.js +1 -1
- package/store/features.js +1 -0
- package/store/i18n.js +11 -0
- package/store/index.js +10 -11
- package/store/prefs.js +33 -35
- package/store/type-map.js +8 -7
- package/tsconfig.json +35 -9
- package/tsconfig.paths.json +18 -0
- package/types/shell/index.d.ts +345 -214
- package/utils/__tests__/create-yaml.test.ts +60 -0
- package/utils/axios.js +0 -19
- package/utils/azure.js +24 -0
- package/utils/create-yaml.js +17 -10
- package/utils/monitoring.js +1 -1
- package/utils/nuxt.js +18 -39
- package/utils/object.js +14 -0
- package/utils/router.scrollBehavior.js +12 -14
- package/utils/time.js +1 -1
- package/utils/url.ts +1 -1
- package/vue.config.js +23 -2
- package/.DS_Store +0 -0
- package/assets/images/providers/aks-black.svg +0 -28
- package/assets/images/providers/aks.svg +0 -31
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +0 -234
- package/initialize/layouts.ts +0 -26
- package/mixins/fetch.server.js +0 -73
- package/pages/c/index.vue +0 -9
- package/pages/rio/mesh.vue +0 -508
- package/plugins/transitions.js +0 -4
- package/scripts/.DS_Store +0 -0
- package/scripts/verdaccio.log +0 -205
- package/tsconfig.default.json +0 -46
- package/yarn-error.log +0 -200
- /package/components/form/__tests__/{NameNsDescription.ts → NameNsDescription.test.ts} +0 -0
- /package/edit/networking.k8s.io.networkpolicy/__tests__/utils/{selectors.ts → selectors.test.ts} +0 -0
- /package/edit/provisioning.cattle.io.cluster/{AgentConfiguration.vue → tabs/AgentConfiguration.vue} +0 -0
- /package/edit/provisioning.cattle.io.cluster/{MemberRoles.vue → tabs/MemberRoles.vue} +0 -0
- /package/edit/provisioning.cattle.io.cluster/{S3Config.vue → tabs/etcd/S3Config.vue} +0 -0
- /package/edit/provisioning.cattle.io.cluster/{ACE.vue → tabs/networking/ACE.vue} +0 -0
- /package/edit/provisioning.cattle.io.cluster/{RegistryConfigs.vue → tabs/registries/RegistryConfigs.vue} +0 -0
- /package/edit/provisioning.cattle.io.cluster/{RegistryMirrors.vue → tabs/registries/RegistryMirrors.vue} +0 -0
- /package/edit/provisioning.cattle.io.cluster/{DrainOptions.vue → tabs/upgrade/DrainOptions.vue} +0 -0
- /package/plugins/dashboard-store/__tests__/{actions.spec.ts → actions.test.ts} +0 -0
- /package/plugins/dashboard-store/__tests__/{getters.spec.ts → getters.test.ts} +0 -0
|
@@ -23,10 +23,6 @@ function registerType(state, type) {
|
|
|
23
23
|
// Not enumerable so they don't get sent back to the client for SSR
|
|
24
24
|
Object.defineProperty(cache, 'map', { value: new Map() });
|
|
25
25
|
|
|
26
|
-
if ( process.server && !cache.list.__rehydrateAll ) {
|
|
27
|
-
Object.defineProperty(cache.list, '__rehydrateAll', { value: `${ state.config.namespace }/${ type }`, enumerable: true });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
26
|
Vue.set(state.types, type, cache);
|
|
31
27
|
}
|
|
32
28
|
|
|
@@ -105,6 +105,7 @@ export const STATES_ENUM = {
|
|
|
105
105
|
ERRORING: 'erroring',
|
|
106
106
|
ERRORS: 'errors',
|
|
107
107
|
EXPIRED: 'expired',
|
|
108
|
+
EXPIRING: 'expiring',
|
|
108
109
|
FAIL: 'fail',
|
|
109
110
|
FAILED: 'failed',
|
|
110
111
|
HEALTHY: 'healthy',
|
|
@@ -167,6 +168,13 @@ export const STATES_ENUM = {
|
|
|
167
168
|
WARNING: 'warning',
|
|
168
169
|
};
|
|
169
170
|
|
|
171
|
+
export function mapStateToEnum(statusString) {
|
|
172
|
+
// e.g. in fleet Status is Capitalized. This function will map it to the enum
|
|
173
|
+
return Object.values(STATES_ENUM).find((val) => {
|
|
174
|
+
return val.toLowerCase() === statusString.toLocaleLowerCase();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
170
178
|
export const STATES = {
|
|
171
179
|
[STATES_ENUM.IN_USE]: {
|
|
172
180
|
color: 'success', icon: 'dot-open', label: 'In Use', compoundIcon: 'checkmark'
|
|
@@ -253,7 +261,10 @@ export const STATES = {
|
|
|
253
261
|
color: 'error', icon: 'error', label: 'Errors', compoundIcon: 'error'
|
|
254
262
|
},
|
|
255
263
|
[STATES_ENUM.EXPIRED]: {
|
|
256
|
-
color: '
|
|
264
|
+
color: 'error', icon: 'error', label: 'Expired', compoundIcon: 'warning'
|
|
265
|
+
},
|
|
266
|
+
[STATES_ENUM.EXPIRING]: {
|
|
267
|
+
color: 'warning', icon: 'error', label: 'Expiring', compoundIcon: 'error'
|
|
257
268
|
},
|
|
258
269
|
[STATES_ENUM.FAIL]: {
|
|
259
270
|
color: 'error', icon: 'error', label: 'Fail', compoundIcon: 'error'
|
|
@@ -512,6 +523,28 @@ export function stateDisplay(state) {
|
|
|
512
523
|
return key.split(/-/).map(ucFirst).join('-');
|
|
513
524
|
}
|
|
514
525
|
|
|
526
|
+
export function primaryDisplayStatusFromCount(status) {
|
|
527
|
+
const statusOrder = [
|
|
528
|
+
STATES_ENUM.ERROR,
|
|
529
|
+
STATES_ENUM.FAILED,
|
|
530
|
+
STATES_ENUM.WARNING,
|
|
531
|
+
STATES_ENUM.MODIFIED,
|
|
532
|
+
STATES_ENUM.WAIT_APPLIED,
|
|
533
|
+
STATES_ENUM.ORPHANED,
|
|
534
|
+
STATES_ENUM.MISSING,
|
|
535
|
+
STATES_ENUM.UNKNOWN,
|
|
536
|
+
STATES_ENUM.NOT_READY,
|
|
537
|
+
STATES_ENUM.READY,
|
|
538
|
+
];
|
|
539
|
+
|
|
540
|
+
// sort status by order of statusOrder
|
|
541
|
+
const existingStatuses = Object.keys(status).filter((key) => {
|
|
542
|
+
return status[key] > 0 && statusOrder.includes(key.toLowerCase());
|
|
543
|
+
}).sort((a, b) => statusOrder.indexOf(a.toLowerCase()) - statusOrder.indexOf(b.toLowerCase()));
|
|
544
|
+
|
|
545
|
+
return existingStatuses[0] ? existingStatuses[0] : STATES_ENUM.UNKNOWN;
|
|
546
|
+
}
|
|
547
|
+
|
|
515
548
|
export function stateSort(color, display) {
|
|
516
549
|
color = color.replace(/^(text|bg)-/, '');
|
|
517
550
|
|
|
@@ -1089,6 +1122,16 @@ export default class Resource {
|
|
|
1089
1122
|
return this._save(...arguments);
|
|
1090
1123
|
}
|
|
1091
1124
|
|
|
1125
|
+
/**
|
|
1126
|
+
* Remove any unwanted properties from the object that will be saved
|
|
1127
|
+
*/
|
|
1128
|
+
cleanForSave(data, forNew) {
|
|
1129
|
+
delete data.__rehydrate;
|
|
1130
|
+
delete data.__clone;
|
|
1131
|
+
|
|
1132
|
+
return data;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1092
1135
|
/**
|
|
1093
1136
|
* Allow to handle the response of the save request
|
|
1094
1137
|
* @param {*} res Full request response
|
|
@@ -1096,9 +1139,6 @@ export default class Resource {
|
|
|
1096
1139
|
processSaveResponse(res) { }
|
|
1097
1140
|
|
|
1098
1141
|
async _save(opt = {}) {
|
|
1099
|
-
delete this.__rehydrate;
|
|
1100
|
-
delete this.__clone;
|
|
1101
|
-
|
|
1102
1142
|
const forNew = !this.id;
|
|
1103
1143
|
|
|
1104
1144
|
const errors = await this.validationErrors(this, opt.ignoreFields);
|
|
@@ -1145,22 +1185,24 @@ export default class Resource {
|
|
|
1145
1185
|
// @TODO remove this once the API maps steve _type <-> k8s type in both directions
|
|
1146
1186
|
opt.data = this.toSave() || { ...this };
|
|
1147
1187
|
|
|
1148
|
-
if (opt
|
|
1188
|
+
if (opt.data._type) {
|
|
1149
1189
|
opt.data.type = opt.data._type;
|
|
1150
1190
|
}
|
|
1151
1191
|
|
|
1152
|
-
if (opt
|
|
1192
|
+
if (opt.data._name) {
|
|
1153
1193
|
opt.data.name = opt.data._name;
|
|
1154
1194
|
}
|
|
1155
1195
|
|
|
1156
|
-
if (opt
|
|
1196
|
+
if (opt.data._labels) {
|
|
1157
1197
|
opt.data.labels = opt.data._labels;
|
|
1158
1198
|
}
|
|
1159
1199
|
|
|
1160
|
-
if (opt
|
|
1200
|
+
if (opt.data._annotations) {
|
|
1161
1201
|
opt.data.annotations = opt.data._annotations;
|
|
1162
1202
|
}
|
|
1163
1203
|
|
|
1204
|
+
opt.data = this.cleanForSave(opt.data, forNew);
|
|
1205
|
+
|
|
1164
1206
|
// handle "replace" opt as a query param _replace=true for norman PUT requests
|
|
1165
1207
|
if (opt?.replace && opt.method === 'put') {
|
|
1166
1208
|
const argParam = opt.url.includes('?') ? '&' : '?';
|
|
@@ -1218,19 +1260,11 @@ export default class Resource {
|
|
|
1218
1260
|
// ------------------------------------------------------------------
|
|
1219
1261
|
|
|
1220
1262
|
currentRoute() {
|
|
1221
|
-
|
|
1222
|
-
return this.$rootState.$route;
|
|
1223
|
-
} else {
|
|
1224
|
-
return window.$nuxt.$route;
|
|
1225
|
-
}
|
|
1263
|
+
return window.$nuxt.$route;
|
|
1226
1264
|
}
|
|
1227
1265
|
|
|
1228
1266
|
currentRouter() {
|
|
1229
|
-
|
|
1230
|
-
return this.$rootState.$router;
|
|
1231
|
-
} else {
|
|
1232
|
-
return window.$nuxt.$router;
|
|
1233
|
-
}
|
|
1267
|
+
return window.$nuxt.$router;
|
|
1234
1268
|
}
|
|
1235
1269
|
|
|
1236
1270
|
get listLocation() {
|
|
@@ -1931,4 +1965,11 @@ export default class Resource {
|
|
|
1931
1965
|
get creationTimestamp() {
|
|
1932
1966
|
return this.metadata?.creationTimestamp;
|
|
1933
1967
|
}
|
|
1968
|
+
|
|
1969
|
+
/**
|
|
1970
|
+
* Allows model to specify JSON Paths that should be folded in the YAML editor by default
|
|
1971
|
+
*/
|
|
1972
|
+
get yamlFolding() {
|
|
1973
|
+
return [];
|
|
1974
|
+
}
|
|
1934
1975
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import Steve from '@shell/plugins/steve/steve-class.js';
|
|
2
|
+
import { steveClassJunkObject } from './utils/steve-mocks';
|
|
3
|
+
|
|
4
|
+
describe('class: Steve', () => {
|
|
5
|
+
describe('given custom resource keys', () => {
|
|
6
|
+
const customResource = steveClassJunkObject;
|
|
7
|
+
|
|
8
|
+
it('should keep internal keys', () => {
|
|
9
|
+
const steve = new Steve(customResource, {
|
|
10
|
+
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
11
|
+
dispatch: jest.fn(),
|
|
12
|
+
rootGetters: { 'i18n/t': jest.fn() },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
expect({ ...steve }).toStrictEqual(customResource);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('method: save', () => {
|
|
19
|
+
it('should remove all the internal keys', async() => {
|
|
20
|
+
const dispatch = jest.fn();
|
|
21
|
+
const steve = new Steve(customResource, {
|
|
22
|
+
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
23
|
+
dispatch,
|
|
24
|
+
rootGetters: { 'i18n/t': jest.fn() },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const expectation = {
|
|
28
|
+
type: customResource.type,
|
|
29
|
+
metadata: {
|
|
30
|
+
resourceVersion: 'whatever',
|
|
31
|
+
fields: 'whatever',
|
|
32
|
+
clusterName: 'whatever',
|
|
33
|
+
deletionGracePeriodSeconds: 'whatever',
|
|
34
|
+
generateName: 'whatever',
|
|
35
|
+
},
|
|
36
|
+
spec: { versions: {} }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
await steve.save();
|
|
40
|
+
|
|
41
|
+
const opt = {
|
|
42
|
+
data: expectation,
|
|
43
|
+
headers: {
|
|
44
|
+
accept: 'application/json',
|
|
45
|
+
'content-type': 'application/json',
|
|
46
|
+
},
|
|
47
|
+
method: 'post',
|
|
48
|
+
url: undefined,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Data sent should have been cleaned
|
|
52
|
+
expect(dispatch).toHaveBeenCalledWith('request', { opt, type: customResource.type });
|
|
53
|
+
|
|
54
|
+
// Original workload model should remain unchanged
|
|
55
|
+
expect({ ...steve }).toStrictEqual(customResource);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { resourceClassJunkObject } from '@shell/plugins/dashboard-store/__tests__/utils/store-mocks';
|
|
2
|
+
|
|
3
|
+
const customType = 'asdasd';
|
|
4
|
+
|
|
5
|
+
export const steveClassJunkObject = {
|
|
6
|
+
...resourceClassJunkObject,
|
|
7
|
+
type: customType,
|
|
8
|
+
__clone: 'whatever',
|
|
9
|
+
metadata: {
|
|
10
|
+
clusterName: 'whatever',
|
|
11
|
+
creationTimestamp: 'whatever',
|
|
12
|
+
deletionGracePeriodSeconds: 'whatever',
|
|
13
|
+
deletionTimestamp: 'whatever',
|
|
14
|
+
fields: 'whatever',
|
|
15
|
+
finalizers: 'whatever',
|
|
16
|
+
generateName: 'whatever',
|
|
17
|
+
generation: 'whatever',
|
|
18
|
+
initializers: 'whatever',
|
|
19
|
+
managedFields: 'whatever',
|
|
20
|
+
ownerReferences: 'whatever',
|
|
21
|
+
relationships: 'whatever',
|
|
22
|
+
selfLink: 'whatever',
|
|
23
|
+
state: 'whatever',
|
|
24
|
+
uid: 'whatever',
|
|
25
|
+
resourceVersion: 'whatever',
|
|
26
|
+
},
|
|
27
|
+
spec: { versions: { schema: 'whatever' } },
|
|
28
|
+
links: 'whatever',
|
|
29
|
+
status: 'whatever',
|
|
30
|
+
stringData: 'whatever',
|
|
31
|
+
};
|
package/plugins/steve/getters.js
CHANGED
|
@@ -24,11 +24,14 @@ const GC_IGNORE_TYPES = {
|
|
|
24
24
|
[UI.NAV_LINK]: true,
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
// Include calls to /v1 AND /k8s/clusters/<cluster id>/v1
|
|
28
|
+
const steveRegEx = new RegExp('(/v1)|(\/k8s\/clusters\/[a-z0-9-]+\/v1)');
|
|
29
|
+
|
|
27
30
|
export default {
|
|
28
31
|
urlOptions: () => (url, opt) => {
|
|
29
32
|
opt = opt || {};
|
|
30
33
|
const parsedUrl = parse(url);
|
|
31
|
-
const isSteve = parsedUrl.path
|
|
34
|
+
const isSteve = steveRegEx.test(parsedUrl.path);
|
|
32
35
|
|
|
33
36
|
// Filter
|
|
34
37
|
if ( opt.filter ) {
|
|
@@ -3,6 +3,7 @@ import pickBy from 'lodash/pickBy';
|
|
|
3
3
|
import Vue from 'vue';
|
|
4
4
|
import { matchesSomeRegex } from '@shell/utils/string';
|
|
5
5
|
import Resource from '@shell/plugins/dashboard-store/resource-class';
|
|
6
|
+
import { findBy } from '@shell/utils/array';
|
|
6
7
|
|
|
7
8
|
export default class NormanModel extends Resource {
|
|
8
9
|
setLabels(val) {
|
|
@@ -56,4 +57,22 @@ export default class NormanModel extends Resource {
|
|
|
56
57
|
Vue.set(this, key, { ...spec[key] });
|
|
57
58
|
});
|
|
58
59
|
}
|
|
60
|
+
|
|
61
|
+
isCondition(condition, withStatus = 'True') {
|
|
62
|
+
if ( !this.conditions ) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const entry = findBy((this.conditions || []), 'type', condition);
|
|
67
|
+
|
|
68
|
+
if ( !entry ) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if ( !withStatus ) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (entry.status || '').toLowerCase() === `${ withStatus }`.toLowerCase();
|
|
77
|
+
}
|
|
59
78
|
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { DESCRIPTION } from '@shell/config/labels-annotations';
|
|
2
2
|
import HybridModel from './hybrid-class';
|
|
3
|
+
import { NEVER_ADD } from '@shell/utils/create-yaml';
|
|
4
|
+
import { deleteProperty } from '@shell/utils/object';
|
|
5
|
+
|
|
6
|
+
// Some fields that are removed for YAML (NEVER_ADD) are required via API
|
|
7
|
+
const STEVE_ADD = [
|
|
8
|
+
'metadata.resourceVersion',
|
|
9
|
+
'metadata.fields',
|
|
10
|
+
'metadata.clusterName',
|
|
11
|
+
'metadata.deletionGracePeriodSeconds',
|
|
12
|
+
'metadata.generateName',
|
|
13
|
+
];
|
|
14
|
+
const STEVE_NEVER_SAVE = NEVER_ADD.filter((na) => !STEVE_ADD.includes(na));
|
|
3
15
|
|
|
4
16
|
export default class SteveModel extends HybridModel {
|
|
5
17
|
get name() {
|
|
@@ -28,4 +40,14 @@ export default class SteveModel extends HybridModel {
|
|
|
28
40
|
|
|
29
41
|
this._description = value;
|
|
30
42
|
}
|
|
43
|
+
|
|
44
|
+
cleanForSave(data, forNew) {
|
|
45
|
+
const val = super.cleanForSave(data);
|
|
46
|
+
|
|
47
|
+
for (const field of STEVE_NEVER_SAVE) {
|
|
48
|
+
deleteProperty(val, field);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return val;
|
|
52
|
+
}
|
|
31
53
|
}
|
|
@@ -332,10 +332,6 @@ const sharedActions = {
|
|
|
332
332
|
|
|
333
333
|
commit('setWantSocket', true);
|
|
334
334
|
|
|
335
|
-
if ( process.server ) {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
335
|
state.debugSocket && console.info(`Subscribe [${ getters.storeName }]`); // eslint-disable-line no-console
|
|
340
336
|
|
|
341
337
|
const url = `${ state.config.baseUrl }/subscribe`;
|
|
@@ -601,7 +597,7 @@ const defaultActions = {
|
|
|
601
597
|
},
|
|
602
598
|
|
|
603
599
|
rehydrateSubscribe({ state, dispatch }) {
|
|
604
|
-
if (
|
|
600
|
+
if ( state.wantSocket && !state.socket ) {
|
|
605
601
|
dispatch('subscribe');
|
|
606
602
|
}
|
|
607
603
|
},
|
|
@@ -728,11 +724,9 @@ const defaultActions = {
|
|
|
728
724
|
}
|
|
729
725
|
|
|
730
726
|
// Try resending any frames that were attempted to be sent while the socket was down, once.
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
dispatch('sendImmediate', obj);
|
|
735
|
-
}
|
|
727
|
+
for ( const obj of state.pendingFrames.slice() ) {
|
|
728
|
+
commit('dequeuePendingFrame', obj);
|
|
729
|
+
dispatch('sendImmediate', obj);
|
|
736
730
|
}
|
|
737
731
|
},
|
|
738
732
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { Accordion } from './index';
|
|
3
|
+
|
|
4
|
+
describe('component: Accordion', () => {
|
|
5
|
+
it('is closed initially by default', () => {
|
|
6
|
+
const title = 'Test Title';
|
|
7
|
+
|
|
8
|
+
const wrapper = shallowMount(Accordion, { propsData: { title } });
|
|
9
|
+
|
|
10
|
+
expect(wrapper.find('[data-testid="accordion-body"]').isVisible()).toBe(false);
|
|
11
|
+
expect(wrapper.find('[data-testid="accordion-header"]').text()).toBe(title);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('is opened initially when openInitially prop is true', () => {
|
|
15
|
+
const wrapper = shallowMount(Accordion, { propsData: { openInitially: true } });
|
|
16
|
+
|
|
17
|
+
expect(wrapper.find('[data-testid="accordion-body"]').isVisible()).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('when closed, opens when the header is clicked', async() => {
|
|
21
|
+
const wrapper = shallowMount(Accordion, { });
|
|
22
|
+
|
|
23
|
+
await wrapper.find('[data-testid="accordion-header"]').trigger('click');
|
|
24
|
+
expect(wrapper.find('[data-testid="accordion-body"]').isVisible()).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('when open, closes when the header is clicked', async() => {
|
|
28
|
+
const wrapper = shallowMount(Accordion, { propsData: { openInitially: true } });
|
|
29
|
+
|
|
30
|
+
await wrapper.find('[data-testid="accordion-header"]').trigger('click');
|
|
31
|
+
expect(wrapper.find('[data-testid="accordion-body"]').isVisible()).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('displays a chevron when closed', async() => {
|
|
35
|
+
const wrapper = shallowMount(Accordion, { propsData: { } });
|
|
36
|
+
|
|
37
|
+
expect(wrapper.find('[data-testid="accordion-header"] .icon-chevron-up').exists()).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('displays an inverted chevron when open', async() => {
|
|
41
|
+
const wrapper = shallowMount(Accordion, { propsData: { openInitially: true } });
|
|
42
|
+
|
|
43
|
+
expect(wrapper.find('[data-testid="accordion-header"] .icon-chevron-down').exists()).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Vue from 'vue';
|
|
3
|
+
import { mapGetters } from 'vuex';
|
|
4
|
+
export default Vue.extend({
|
|
5
|
+
props: {
|
|
6
|
+
title: {
|
|
7
|
+
type: String,
|
|
8
|
+
default: ''
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
titleKey: {
|
|
12
|
+
type: String,
|
|
13
|
+
default: null
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
openInitially: {
|
|
17
|
+
type: Boolean,
|
|
18
|
+
default: false
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
data() {
|
|
23
|
+
return { isOpen: this.openInitially };
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
computed: { ...mapGetters({ t: 'i18n/t' }) },
|
|
27
|
+
|
|
28
|
+
methods: {
|
|
29
|
+
toggle() {
|
|
30
|
+
this.isOpen = !this.isOpen;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<div class="accordion-container">
|
|
38
|
+
<div
|
|
39
|
+
class="accordion-header"
|
|
40
|
+
data-testid="accordion-header"
|
|
41
|
+
@click="toggle"
|
|
42
|
+
>
|
|
43
|
+
<i
|
|
44
|
+
class="icon text-primary"
|
|
45
|
+
:class="{'icon-chevron-down':isOpen, 'icon-chevron-up':!isOpen}"
|
|
46
|
+
data-testid="accordion-chevron"
|
|
47
|
+
/>
|
|
48
|
+
<slot name="header">
|
|
49
|
+
<h4
|
|
50
|
+
data-testid="accordion-title-slot-content"
|
|
51
|
+
class="mb-0"
|
|
52
|
+
>
|
|
53
|
+
{{ titleKey ? t(titleKey) : title }}
|
|
54
|
+
</h4>
|
|
55
|
+
</slot>
|
|
56
|
+
</div>
|
|
57
|
+
<div
|
|
58
|
+
v-show="isOpen"
|
|
59
|
+
class="accordion-body"
|
|
60
|
+
data-testid="accordion-body"
|
|
61
|
+
>
|
|
62
|
+
<slot />
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<style lang="scss" scoped>
|
|
68
|
+
.accordion-container {
|
|
69
|
+
border: 1px solid var(--border)
|
|
70
|
+
}
|
|
71
|
+
.accordion-header {
|
|
72
|
+
padding: 5px;
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
&>*{
|
|
76
|
+
padding: 5px 0px 5px 0px;
|
|
77
|
+
}
|
|
78
|
+
I {
|
|
79
|
+
margin: 0px 10px 0px 10px;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
.accordion-body {
|
|
83
|
+
padding: 10px;
|
|
84
|
+
}
|
|
85
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Accordion } from './Accordion.vue';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import { mount } from '@vue/test-utils';
|
|
3
2
|
import { LabeledInput } from './index';
|
|
4
3
|
|
|
@@ -6,7 +5,7 @@ describe('component: LabeledInput', () => {
|
|
|
6
5
|
it('should emit input only once', () => {
|
|
7
6
|
const value = '2';
|
|
8
7
|
const delay = 1;
|
|
9
|
-
const wrapper = mount(LabeledInput, {
|
|
8
|
+
const wrapper = mount(LabeledInput as any, {
|
|
10
9
|
propsData: { delay },
|
|
11
10
|
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
|
|
12
11
|
});
|
|
@@ -20,4 +19,22 @@ describe('component: LabeledInput', () => {
|
|
|
20
19
|
expect(wrapper.emitted('input')).toHaveLength(1);
|
|
21
20
|
expect(wrapper.emitted('input')![0][0]).toBe(value);
|
|
22
21
|
});
|
|
22
|
+
|
|
23
|
+
it('using mode "multiline" should emit input value correctly', () => {
|
|
24
|
+
const value = 'any-string';
|
|
25
|
+
const delay = 1;
|
|
26
|
+
const wrapper = mount(LabeledInput as any, {
|
|
27
|
+
propsData: { delay, multiline: true },
|
|
28
|
+
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
jest.useFakeTimers();
|
|
32
|
+
wrapper.find('input').setValue('1');
|
|
33
|
+
wrapper.find('input').setValue(value);
|
|
34
|
+
jest.advanceTimersByTime(delay);
|
|
35
|
+
jest.useRealTimers();
|
|
36
|
+
|
|
37
|
+
expect(wrapper.emitted('input')).toHaveLength(1);
|
|
38
|
+
expect(wrapper.emitted('input')![0][0]).toBe(value);
|
|
39
|
+
});
|
|
23
40
|
});
|
|
@@ -90,7 +90,7 @@ export default (
|
|
|
90
90
|
delay: {
|
|
91
91
|
type: Number,
|
|
92
92
|
default: 0
|
|
93
|
-
}
|
|
93
|
+
},
|
|
94
94
|
},
|
|
95
95
|
|
|
96
96
|
data() {
|
|
@@ -206,9 +206,19 @@ export default (
|
|
|
206
206
|
}
|
|
207
207
|
},
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Emit on input change
|
|
211
|
+
*/
|
|
212
|
+
onChange(event: Event): void {
|
|
213
|
+
this.$emit('change', event);
|
|
214
|
+
},
|
|
215
|
+
|
|
209
216
|
/**
|
|
210
217
|
* Emit on input with delay. Note: Arrow function is avoided due context
|
|
211
218
|
* binding.
|
|
219
|
+
*
|
|
220
|
+
* NOTE: In multiline, TextAreaAutoGrow emits a string with the value
|
|
221
|
+
* https://github.com/rancher/dashboard/issues/10249
|
|
212
222
|
*/
|
|
213
223
|
delayInput(value: string): void {
|
|
214
224
|
this.$emit('input', value);
|
|
@@ -299,6 +309,7 @@ export default (
|
|
|
299
309
|
@input="onInput($event.target.value)"
|
|
300
310
|
@focus="onFocus"
|
|
301
311
|
@blur="onBlur"
|
|
312
|
+
@change="onChange"
|
|
302
313
|
>
|
|
303
314
|
</slot>
|
|
304
315
|
|
|
@@ -4,7 +4,7 @@ import { cleanHtmlDirective } from '@shell/plugins/clean-html-directive';
|
|
|
4
4
|
|
|
5
5
|
describe('radioButton.vue', () => {
|
|
6
6
|
it('renders label slot contents', () => {
|
|
7
|
-
const wrapper = shallowMount(RadioButton, { slots: { label: 'Test Label' } });
|
|
7
|
+
const wrapper = shallowMount(RadioButton, { slots: { label: 'Test Label' }, propsData: { val: {}, value: {} } });
|
|
8
8
|
|
|
9
9
|
expect(wrapper.find('.radio-label').text()).toBe('Test Label');
|
|
10
10
|
});
|
|
@@ -14,7 +14,9 @@ describe('radioButton.vue', () => {
|
|
|
14
14
|
RadioButton,
|
|
15
15
|
{
|
|
16
16
|
directives: { cleanHtmlDirective },
|
|
17
|
-
propsData: {
|
|
17
|
+
propsData: {
|
|
18
|
+
label: 'Test Label', val: {}, value: {}
|
|
19
|
+
}
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
expect(wrapper.find('.radio-label').text()).toBe('Test Label');
|
|
@@ -23,7 +25,9 @@ describe('radioButton.vue', () => {
|
|
|
23
25
|
it('renders slot contents when both slot and label prop are provided', () => {
|
|
24
26
|
const wrapper = shallowMount(RadioButton, {
|
|
25
27
|
slots: { label: 'Test Label - Slot' },
|
|
26
|
-
propsData: {
|
|
28
|
+
propsData: {
|
|
29
|
+
label: 'Test Label - Props', val: {}, value: {}
|
|
30
|
+
},
|
|
27
31
|
});
|
|
28
32
|
|
|
29
33
|
expect(wrapper.find('.radio-label').text()).toBe('Test Label - Slot');
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { RadioGroup } from './index';
|
|
3
|
+
|
|
4
|
+
describe('component: RadioGroup', () => {
|
|
5
|
+
describe('when disabled', () => {
|
|
6
|
+
it.each([true, false])('should expose disabled slot prop for indexed slots for %p', (disabled) => {
|
|
7
|
+
const wrapper = mount(RadioGroup, {
|
|
8
|
+
propsData: {
|
|
9
|
+
name: 'whatever',
|
|
10
|
+
options: [{ label: 'whatever', value: 'whatever' }],
|
|
11
|
+
disabled
|
|
12
|
+
},
|
|
13
|
+
scopedSlots: {
|
|
14
|
+
0(props: {isDisabled: boolean}) {
|
|
15
|
+
return this.$createElement('input', {
|
|
16
|
+
attrs: {
|
|
17
|
+
id: 'test',
|
|
18
|
+
disabled: props.isDisabled
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const slot = wrapper.find('#test').element as HTMLInputElement;
|
|
26
|
+
|
|
27
|
+
expect(slot.disabled).toBe(disabled);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -169,6 +169,7 @@ export default Vue.extend({
|
|
|
169
169
|
|
|
170
170
|
<template>
|
|
171
171
|
<div>
|
|
172
|
+
<!-- Label -->
|
|
172
173
|
<div
|
|
173
174
|
v-if="label || labelKey || tooltip || tooltipKey || $slots.label"
|
|
174
175
|
class="radio-group label"
|
|
@@ -195,6 +196,8 @@ export default Vue.extend({
|
|
|
195
196
|
</h3>
|
|
196
197
|
</slot>
|
|
197
198
|
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Group -->
|
|
198
201
|
<div
|
|
199
202
|
class="radio-group"
|
|
200
203
|
:class="{'row':row}"
|
|
@@ -212,6 +215,7 @@ export default Vue.extend({
|
|
|
212
215
|
:is-disabled="isDisabled"
|
|
213
216
|
:name="i"
|
|
214
217
|
>
|
|
218
|
+
<!-- Default input -->
|
|
215
219
|
<RadioButton
|
|
216
220
|
:key="name+'-'+i"
|
|
217
221
|
:name="name"
|