@rancher/shell 0.3.15 → 0.3.16

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.
@@ -4440,7 +4440,6 @@ rbac:
4440
4440
  members:
4441
4441
  label: Members
4442
4442
  roletemplate:
4443
- label: Roles
4444
4443
  newUserDefault:
4445
4444
  no: No
4446
4445
  tooltip: This does not affect any bindings to the role that already exist.
@@ -7324,6 +7323,7 @@ manager:
7324
7323
  label: Node Templates
7325
7324
 
7326
7325
  auth:
7326
+ roleTemplate: Role Templates
7327
7327
  config:
7328
7328
  label: Auth Provider
7329
7329
  vncConsole:
@@ -117,6 +117,7 @@ locale:
117
117
  none: (None)
118
118
 
119
119
  nav:
120
+ harvesterDashboard: Harvester 仪表板
120
121
  backToRancher: Cluster Manager
121
122
  clusterTools: 集群工具
122
123
  kubeconfig:
@@ -179,6 +180,7 @@ nav:
179
180
  multiCluster: 全局应用
180
181
  legacy: 旧版应用
181
182
  configuration: 配置
183
+ hci: HCI
182
184
  search:
183
185
  placeholder: 输入关键词,搜索集群
184
186
  noResults: 没有匹配的集群
@@ -1282,9 +1284,6 @@ cluster:
1282
1284
  label: KubeconfigContent
1283
1285
  placeholder: '命名空间/名称'
1284
1286
  cluster: 导入的 Harvester 集群
1285
- affinity:
1286
- namespaces:
1287
- placeholder: 例如:default,system,base
1288
1287
  installGuestAgent: 安装访客代理
1289
1288
  description:
1290
1289
  label: 集群描述
@@ -4002,12 +4001,14 @@ plugins:
4002
4001
  builtin: 内置角色
4003
4002
  experimental: 实验功能
4004
4003
  third-party: 第三方
4004
+ image: 镜像
4005
4005
  installing: 正在安装...
4006
4006
  uninstalling: 正在卸载...
4007
4007
  descriptions:
4008
4008
  experimental: 这是实验性扩展
4009
4009
  third-party: 此扩展由第三方提供
4010
4010
  built-in: 这是内置扩展
4011
+ image: 已手动加载此扩展镜像
4011
4012
  error:
4012
4013
  title: 加载扩展时出错
4013
4014
  message: 无法加载扩展代码
@@ -4037,12 +4038,53 @@ plugins:
4037
4038
  available: 没有可用的扩展
4038
4039
  installed: 没有已安装的扩展
4039
4040
  updates: 没有可用于已安装扩展的更新
4041
+ images: 未安装扩展镜像
4040
4042
  loadError: 加载此扩展的代码时出错
4041
4043
  helmError: "通过 Helm 安装扩展时出错"
4042
4044
  manageRepos: 管理仓库
4045
+ manageCharts: 管理扩展 Chart
4046
+ manageCatalog:
4047
+ label: 管理扩展商店
4048
+ title: 扩展
4049
+ subtitle: 应用商店
4050
+ imageLoad:
4051
+ load: 导入扩展商店
4052
+ prompt: 扩展商店包含了打包在镜像中的扩展资产,导入将获取该镜像,并托管一个 Helm 仓库来作为自定义构建扩展的商店。
4053
+ fields:
4054
+ image:
4055
+ label: 商店镜像参考
4056
+ placeholder: "例如:hub.docker.io/example-org/my-image:latest"
4057
+ secrets:
4058
+ banner: "如果托管目录镜像的仓库需要拉取 Secret,则必须在以下命名空间中创建它们:<pre>cattle-ui-plugin-system</pre>"
4059
+ banner: 这将创建 Deployment、Service 和 Helm 仓库来服务扩展 Chart。
4060
+ imageVersion:
4061
+ title: 找不到镜像版本
4062
+ message: 无法通过 {image} 确定镜像版本,默认为 latest
4063
+ error:
4064
+ exists:
4065
+ deployment:
4066
+ title: 部署冲突
4067
+ message: 使用 {image} 镜像的容器已存在
4068
+ service:
4069
+ title: Service 冲突
4070
+ message: 名称为 {svc} 的 Service 已存在
4071
+ repo:
4072
+ title: Helm 仓库冲突
4073
+ message: 名称为 {repo} 的仓库已存在
4074
+ success:
4075
+ title: "导入的扩展商店来自:{name}"
4076
+ message: 已成功导入扩展商店镜像
4077
+ headers:
4078
+ image:
4079
+ name: 镜像
4080
+ label: Deployment 镜像
4081
+ cacheState:
4082
+ name: cacheState
4083
+ label: 缓存状态
4043
4084
  tabs:
4044
4085
  all: 全部
4045
4086
  available: 可用
4087
+ images: 镜像
4046
4088
  installed: 已安装
4047
4089
  updates: 更新
4048
4090
  title: 扩展
@@ -4065,6 +4107,7 @@ plugins:
4065
4107
  label: 卸载
4066
4108
  title: "卸载扩展 {name}"
4067
4109
  prompt: "确定要卸载此扩展吗?"
4110
+ custom: "确定要卸载此扩展镜像吗?此镜像提供的扩展也将被删除。"
4068
4111
  upgradeAvailable: 此扩展有可用的版本更新
4069
4112
  reload: 扩展已更改 - 需要重新加载
4070
4113
  safeMode:
@@ -4599,8 +4642,8 @@ resourceList:
4599
4642
  create: 创建
4600
4643
  createFromYaml: 使用 YAML 文件创建
4601
4644
  createResource: "创建 {resourceName}"
4602
- nsFiltering: "{resource} 太多。<br>请通过选择上面的命名空间进行过滤。"
4603
- nsFilterToolTip: "资源太多,过滤仅限于单一 {mode}。"
4645
+ nsFiltering: "请使用上面的过滤器选择一个或多个命名空间或项目。"
4646
+ nsFilterToolTip: "仅限于过滤项目和命名空间"
4604
4647
  resourceLoadingIndicator:
4605
4648
  loading: 正在加载
4606
4649
 
@@ -4811,20 +4854,13 @@ servicesPage:
4811
4854
  title: 附加配置
4812
4855
  ipam:
4813
4856
  label: IPAM
4814
- healthCheckPort:
4815
- label: 健康检查端口
4816
- healthCheckSuccessThreshold:
4817
- label: 健康检查成功阙值
4818
- description: 如果探针连续检测到某个地址的成功次数达到成功阈值,后端服务器就可以开始转发流量。
4819
- healthCheckFailureThreshold:
4820
- label: 健康检查失败阈值
4821
- description: 如果健康检查失败的数量达到失败阈值,后端服务器将停止转发流量。
4822
- healthCheckPeriod:
4823
- label: 健康检查周期
4824
- healthCheckTimeout:
4825
- label: 健康检查超时
4826
- healthCheckEnabled:
4827
- label: 健康检查
4857
+ useShareIP:
4858
+ label: 使用共享 IP
4859
+ useIpam:
4860
+ label: 使用 IPAM
4861
+ shareIP:
4862
+ label: 共享 IP LB
4863
+
4828
4864
  ips:
4829
4865
  define: 服务端口
4830
4866
  clusterIpHelpText: 集群 IP 地址必须在为 API server 配置的 CIDR 范围内。
@@ -6035,7 +6071,7 @@ workload:
6035
6071
  noExecute: 不执行
6036
6072
  noSchedule: "不调度"
6037
6073
  preferNoSchedule: 倾向于不调度
6038
- labelKey: 标签键
6074
+ labelKey:
6039
6075
  operator: 运算符
6040
6076
  operatorOptions:
6041
6077
  equal: =
@@ -6373,6 +6409,11 @@ typeLabel:
6373
6409
  one { 集群成员 }
6374
6410
  other { 集群成员 }
6375
6411
  }
6412
+ projectroletemplatebinding: |-
6413
+ {count, plural,
6414
+ one { 项目成员 }
6415
+ other { 项目成员 }
6416
+ }
6376
6417
  management.cattle.io.feature: |-
6377
6418
  {count, plural,
6378
6419
  one { 功能开关 }
@@ -7012,6 +7053,7 @@ performance:
7012
7053
  启用后,资源会更快出现,但可能需要更长的时间来加载整个资源集。此设置仅适用于来自 Kubernetes API 的资源
7013
7054
  checkboxLabel: 启用增量加载
7014
7055
  inputLabel: 资源阈值
7056
+ incompatibleDescription: "增量加载与命名空间/项目过滤不兼容,无法同时启用。"
7015
7057
  manualRefresh:
7016
7058
  label: 手动刷新
7017
7059
  setting: 你可以配置一个阈值,高于该阈值时则启用手动刷新。
@@ -7020,6 +7062,7 @@ performance:
7020
7062
  启用后,列表数据不会自动更新,用户必须手动刷新列表视图。此设置仅适用于来自 Kubernetes API 的资源
7021
7063
  checkboxLabel: 启用列表数据的手动刷新
7022
7064
  inputLabel: 资源阈值
7065
+ incompatibleDescription: "手动刷新与命名空间/项目过滤不兼容,无法同时启用。"
7023
7066
  websocketNotification:
7024
7067
  label: WebSocket 通知
7025
7068
  description: |-
@@ -7047,12 +7090,10 @@ performance:
7047
7090
  description: 资源必须超过此数量才能考虑进行垃圾回收。
7048
7091
  inputLabel: 资源数量
7049
7092
  nsFiltering:
7050
- label: 命名空间过滤
7051
- description: 当列表无法显示太多资源时,用户需要选择一个命名空间并仅提取该命名空间中的资源。
7052
- checkboxLabel: 启用命名空间过滤
7053
- count:
7054
- inputLabel: 资源阈值
7055
- description: 需要按命名空间过滤的阈值
7093
+ label: 要求命名空间/项目过滤
7094
+ description: 用户需要选择命名空间和/或项目。这将限制查看列表时获取的资源数量,有助于在具有大量资源的系统中提高 UI 的响应速度。
7095
+ checkboxLabel: 启用要求命名空间/项目过滤
7096
+ incompatibleDescription: "\"要求命名空间/项目过滤\"与手动刷新和增量加载不兼容,无法同时启用。"
7056
7097
  advancedWorker:
7057
7098
  label: Websocket Web Worker
7058
7099
  description: 推送到 UI 的资源更新来自 WebSocket,并在 UI 线程中处理。启用此选项可在单独的线程中处理 Web Worker 中的集群 WebSocket 更新。这有助于提高 UI 在资源经常变化的系统中的响应能力。
@@ -7147,6 +7188,7 @@ resourceQuota:
7147
7188
  resourceType: 资源类型
7148
7189
  helpText: 配置整个命名空间可用的资源量。
7149
7190
  helpTextDetail: 整个命名空间可以使用的资源量。
7191
+ helpTextHarvester: VM 需要预留额外的内存。
7150
7192
  configMaps: ConfigMaps
7151
7193
  limitsCpu: CPU 限制
7152
7194
  limitsMemory: 内存限制
@@ -33,7 +33,7 @@ export default {
33
33
  for ( const k of keyOptions ) {
34
34
  const sk = simplify(k);
35
35
 
36
- if ( normanSchema || likelyFields.includes(sk) || iffyFields.includes(sk) ) {
36
+ if ( normanSchema?.resourceFields || likelyFields.includes(sk) || iffyFields.includes(sk) ) {
37
37
  keys.push(k);
38
38
  }
39
39
  }
Binary file
@@ -53,6 +53,7 @@ export default {
53
53
  <div
54
54
  v-if="pref"
55
55
  class="close-button"
56
+ data-testid="graphic-banner-close"
56
57
  @click="hide()"
57
58
  >
58
59
  <i class="icon icon-close" />
@@ -288,7 +288,7 @@ export default {
288
288
  closeError(index) {
289
289
  const errors = this.errors.filter((_, i) => i !== index);
290
290
 
291
- this.$emit('error', errors);
291
+ this.$emit('error', errors, this.errors[index]);
292
292
  },
293
293
 
294
294
  emitOrRoute() {
@@ -252,6 +252,7 @@ export default {
252
252
  if (iframeEl === null) {
253
253
  iframeEl = document.createElement('iframe');
254
254
  iframeEl.setAttribute('id', EMBER_FRAME);
255
+ iframeEl.setAttribute('data-testid', EMBER_FRAME);
255
256
  iframeEl.classList.add(EMBER_FRAME_HIDE_CLASS);
256
257
 
257
258
  if (this.inline) {
@@ -259,10 +259,16 @@ export default {
259
259
  v-if="!loading && !error"
260
260
  class="external-link"
261
261
  >
262
+ <!-- https://github.com/harvester/harvester-installer/pull/512/files -->
263
+ <!-- It is necessary to include the parameter referer when accessing the Grafana page. -->
264
+ <!-- This parameter is required by the backend to identify the origin of the request from which cluster -->
265
+ <!-- The matching mechanism as follows: -->
266
+ <!-- ~.*/k8s/clusters/(c-m-.+)/.* -->
267
+ <!-- ~.*/dashboard/harvester/c/(c-m-.+)/.* -->
262
268
  <a
263
269
  :href="grafanaUrl"
264
270
  target="_blank"
265
- rel="noopener noreferrer nofollow"
271
+ rel="noopener nofollow"
266
272
  >{{ t('grafanaDashboard.grafana') }} <i class="icon icon-external-link" /></a>
267
273
  </div>
268
274
  </div>
@@ -31,6 +31,7 @@ export default {
31
31
  <div
32
32
  v-if="shown"
33
33
  class="simple-box"
34
+ data-testid="simple-box-container"
34
35
  v-on="$listeners"
35
36
  >
36
37
  <div
@@ -903,7 +903,10 @@ export default {
903
903
  </script>
904
904
 
905
905
  <template>
906
- <div ref="container">
906
+ <div
907
+ ref="container"
908
+ data-testid="cluster-list-container"
909
+ >
907
910
  <div
908
911
  :class="{'titled': $slots.title && $slots.title.length}"
909
912
  class="sortable-table-header"
@@ -1001,6 +1004,7 @@ export default {
1001
1004
  <div
1002
1005
  v-if="search || hasAdvancedFiltering || isTooManyItemsToAutoUpdate || ($slots['header-right'] && $slots['header-right'].length)"
1003
1006
  class="search row"
1007
+ data-testid="search-box-filter-row"
1004
1008
  >
1005
1009
  <ul
1006
1010
  v-if="hasAdvancedFiltering"
Binary file
@@ -357,7 +357,7 @@ export default Vue.extend<Data, any, any, any>({
357
357
  <div class="spacer">
358
358
  <LabeledInput
359
359
  v-model="selectedAccOrOrg"
360
- data-testid="epinio_app-source_git-username"
360
+ data-testid="git_picker-username-or-org"
361
361
  :tooltip="t(`gitPicker.${ type }.username.tooltip`)"
362
362
  :label="t(`gitPicker.${ type }.username.inputLabel`)"
363
363
  :required="true"
@@ -340,6 +340,11 @@ export default {
340
340
  this.queueUpdate();
341
341
  },
342
342
 
343
+ updateLabelSelector(e, props) {
344
+ this.set(props.row.value, 'labelSelector.matchExpressions', e);
345
+ this.queueUpdate();
346
+ },
347
+
343
348
  isEmpty,
344
349
  get,
345
350
  set
@@ -431,7 +436,7 @@ export default {
431
436
  :value="get(props.row.value, 'labelSelector.matchExpressions')"
432
437
  :show-remove="false"
433
438
  :data-testid="`pod-affinity-expressions-index${props.i}`"
434
- @input="e=>set(props.row.value, 'labelSelector.matchExpressions', e)"
439
+ @input="e=>updateLabelSelector(e, props)"
435
440
  />
436
441
  <div class="row mt-20">
437
442
  <div class="col span-9">
@@ -144,7 +144,7 @@ export function init(store) {
144
144
  weightType(NORMAN.SPOOFED.GROUP_PRINCIPAL, 101, true);
145
145
 
146
146
  virtualType({
147
- labelKey: 'rbac.roletemplate.label',
147
+ labelKey: 'auth.roleTemplate',
148
148
  icon: 'user',
149
149
  namespaced: false,
150
150
  name: ROLES_VIRTUAL_TYPE,
package/config/router.js CHANGED
@@ -17,11 +17,6 @@ export const routerOptions = {
17
17
  scrollBehavior,
18
18
 
19
19
  routes: [{
20
- path: '/verify-auth',
21
- redirect: (to) => {
22
- return 'auth/verify';
23
- },
24
- }, {
25
20
  path: '/about',
26
21
  component: () => interopDefault(import('../pages/about.vue' /* webpackChunkName: "pages/about" */)),
27
22
  name: 'about'
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@rancher/create-pkg",
3
+ "version": "0.1.37",
4
+ "lockfileVersion": 1,
5
+ "requires": true,
6
+ "dependencies": {
7
+ "fs-extra": {
8
+ "version": "10.1.0",
9
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
10
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
11
+ "requires": {
12
+ "graceful-fs": "^4.2.0",
13
+ "jsonfile": "^6.0.1",
14
+ "universalify": "^2.0.0"
15
+ }
16
+ },
17
+ "graceful-fs": {
18
+ "version": "4.2.10",
19
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
20
+ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
21
+ },
22
+ "jsonfile": {
23
+ "version": "6.1.0",
24
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
25
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
26
+ "requires": {
27
+ "graceful-fs": "^4.1.6",
28
+ "universalify": "^2.0.0"
29
+ }
30
+ },
31
+ "universalify": {
32
+ "version": "2.0.0",
33
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
34
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
35
+ }
36
+ }
37
+ }
@@ -19,10 +19,17 @@ export default {
19
19
  </div>
20
20
  <div
21
21
  v-if="value.isGit"
22
- class="span-6"
22
+ class="span-3"
23
23
  >
24
24
  <h3>{{ t('tableHeaders.branch') }}</h3>
25
25
  <span>{{ value.branchDisplay }}</span>
26
26
  </div>
27
+ <div
28
+ v-if="value.isGit"
29
+ class="span-3"
30
+ >
31
+ <h3>{{ t('tableHeaders.commit') }}</h3>
32
+ <span>{{ value.status.commit }}</span>
33
+ </div>
27
34
  </div>
28
35
  </template>
@@ -0,0 +1,56 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import FormValidation from '@shell/mixins/form-validation';
3
+ import Monitoring from '@shell/edit/monitoring.coreos.com.prometheusrule/index.vue';
4
+ import { _EDIT } from '@shell/config/query-params';
5
+ import { cleanHtmlDirective } from '@shell/plugins/clean-html-directive';
6
+
7
+ describe('edit: management.cattle.io.setting should', () => {
8
+ const MOCKED_ERRORS = ['error1', 'error2', 'error3', 'error4', 'error5'];
9
+ const ERROR_BANNER_SELECTOR = '[data-testid="banner-close"]';
10
+ const requiredSetup = () => ({
11
+ // Remove all these mocks after migration to Vue 2.7/3 due mixin logic
12
+ mocks: {
13
+ $store: {
14
+ getters: {
15
+ currentStore: () => 'current_store',
16
+ 'current_store/schemaFor': jest.fn(),
17
+ 'current_store/all': jest.fn(),
18
+ 'i18n/t': jest.fn(),
19
+ 'i18n/exists': jest.fn(),
20
+ namespaces: () => ({})
21
+ }
22
+ },
23
+ $route: { query: { AS: '' }, name: '' },
24
+ $router: { applyQuery: jest.fn() }
25
+ }
26
+ });
27
+
28
+ it('should close error banners', async() => {
29
+ jest.spyOn(FormValidation.computed, 'fvUnreportedValidationErrors').mockReturnValue(MOCKED_ERRORS);
30
+
31
+ const wrapper = mount(Monitoring, {
32
+ propsData: {
33
+ canYaml: false,
34
+ mode: _EDIT,
35
+ resource: {},
36
+ value: { value: 'anything' },
37
+ name: ''
38
+ },
39
+ directives: { cleanHtmlDirective },
40
+ ...requiredSetup()
41
+ });
42
+
43
+ const errorBanners = wrapper.findAll(ERROR_BANNER_SELECTOR);
44
+
45
+ // Assert that all the error banners are rendered
46
+ expect(errorBanners).toHaveLength(MOCKED_ERRORS.length);
47
+
48
+ for (let i = 0; i < MOCKED_ERRORS.length; i++) {
49
+ await errorBanners.at(0).trigger('click');
50
+ const resultErrorBanners = wrapper.findAll(ERROR_BANNER_SELECTOR);
51
+
52
+ // Assert that an error banner is closed until the last one
53
+ expect(resultErrorBanners).toHaveLength(MOCKED_ERRORS.length - 1 - i);
54
+ }
55
+ });
56
+ });
@@ -42,7 +42,10 @@ export default {
42
42
  },
43
43
 
44
44
  data() {
45
- return { fvFormRuleSets: [{ path: 'metadata.name', rules: ['dnsLabel'] }] };
45
+ return {
46
+ fvFormRuleSets: [{ path: 'metadata.name', rules: ['dnsLabel'] }],
47
+ closedErrorMessages: []
48
+ };
46
49
  },
47
50
 
48
51
  computed: {
@@ -54,7 +57,7 @@ export default {
54
57
  return [this.t('validation.prometheusRule.noEdit')];
55
58
  }
56
59
 
57
- return this.fvUnreportedValidationErrors;
60
+ return this.fvUnreportedValidationErrors.filter((e) => !this.closedErrorMessages.includes(e));
58
61
  }
59
62
  },
60
63
 
@@ -102,6 +105,8 @@ export default {
102
105
  }
103
106
  });
104
107
 
108
+ this.closedErrorMessages = [];
109
+
105
110
  return true;
106
111
  },
107
112
 
@@ -125,7 +130,7 @@ export default {
125
130
  :mode="mode"
126
131
  :resource="value"
127
132
  :validation-passed="fvFormIsValid"
128
- @error="(e) => (errors = e)"
133
+ @error="(_, closedError) => closedErrorMessages.push(closedError)"
129
134
  @finish="save"
130
135
  >
131
136
  <div class="row">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "0.3.15",
3
+ "version": "0.3.16",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -106,7 +106,7 @@
106
106
  "nyc": "15.1.0",
107
107
  "papaparse": "5.3.0",
108
108
  "portal-vue": "2.1.7",
109
- "rancher-icons": "rancher/icons#v2.0.14",
109
+ "rancher-icons": "rancher/icons#v2.0.16",
110
110
  "require-extension-hooks": "0.3.3",
111
111
  "require-extension-hooks-babel": "1.0.0",
112
112
  "require-extension-hooks-vue": "3.0.0",
@@ -542,6 +542,16 @@ export default {
542
542
  }
543
543
  }
544
544
 
545
+ .gutless {
546
+ height: 100vh;
547
+ .span-6 {
548
+ overflow-y: auto;
549
+ display: flex;
550
+ flex-direction: column;
551
+ height: 100%;
552
+ place-content: center;
553
+ }
554
+ }
545
555
  .locale-elector {
546
556
  position: absolute;
547
557
  bottom: 30px;
@@ -478,6 +478,10 @@ export default {
478
478
  .form-col {
479
479
  display: flex;
480
480
  flex-direction: column;
481
+ overflow-y: auto;
482
+ position: relative;
483
+ height: 100vh;
484
+
481
485
  & > div:first-of-type {
482
486
  flex:3;
483
487
  }
@@ -159,7 +159,7 @@ export default {
159
159
  <header>
160
160
  <div class="title">
161
161
  <h1 class="m-0">
162
- {{ t('rbac.roletemplate.label') }}
162
+ {{ t('auth.roleTemplate') }}
163
163
  </h1>
164
164
  </div>
165
165
  <div class="actions-container">
package/pages/home.vue CHANGED
@@ -306,6 +306,7 @@ export default {
306
306
  :title="t('landing.welcomeToRancher', {vendor})"
307
307
  :pref="HIDE_HOME_PAGE_CARDS"
308
308
  pref-key="welcomeBanner"
309
+ data-testid="home-banner-graphic"
309
310
  />
310
311
  <IndentedPanel class="mt-20 mb-20">
311
312
  <div
@@ -337,6 +338,7 @@ export default {
337
338
  <div class="col span-12">
338
339
  <Banner
339
340
  color="set-login-page mt-0"
341
+ data-testid="set-login-page-banner"
340
342
  :closable="true"
341
343
  @close="closeSetLoginBanner()"
342
344
  >
@@ -384,6 +386,7 @@ export default {
384
386
  v-if="canManageClusters"
385
387
  :to="manageLocation"
386
388
  class="btn btn-sm role-secondary"
389
+ data-testid="cluster-management-manage-button"
387
390
  >
388
391
  {{ t('cluster.manageAction') }}
389
392
  </n-link>
@@ -391,6 +394,7 @@ export default {
391
394
  v-if="canCreateCluster"
392
395
  :to="importLocation"
393
396
  class="btn btn-sm role-primary"
397
+ data-testid="cluster-create-import-button"
394
398
  >
395
399
  {{ t('cluster.importAction') }}
396
400
  </n-link>
@@ -398,6 +402,7 @@ export default {
398
402
  v-if="canCreateCluster"
399
403
  :to="createLocation"
400
404
  class="btn btn-sm role-primary"
405
+ data-testid="cluster-create-button"
401
406
  >
402
407
  {{ t('generic.create') }}
403
408
  </n-link>
@@ -128,6 +128,10 @@ export default {
128
128
 
129
129
  finishDeferred(key, 'resolve', out);
130
130
 
131
+ if (opt.method === 'post' || opt.method === 'put') {
132
+ handleValidationWarnings(res);
133
+ }
134
+
131
135
  return out;
132
136
  });
133
137
  }
@@ -192,6 +196,24 @@ export default {
192
196
 
193
197
  return Promise.reject(out);
194
198
  }
199
+
200
+ function handleValidationWarnings(res) {
201
+ const warnings = (res.headers?.warning || '').split(',');
202
+
203
+ if (!warnings.length || !warnings[0]) {
204
+ return;
205
+ }
206
+
207
+ const message = warnings.reduce((message, warning) => {
208
+ return `${ message }\n${ warning.trim() }`;
209
+ }, `Validation Warnings for ${ opt.url }\n`);
210
+
211
+ if (process.env.dev) {
212
+ console.warn(`${ message }\n\n`, res.data); // eslint-disable-line no-console
213
+ } else {
214
+ console.debug(message); // eslint-disable-line no-console
215
+ }
216
+ }
195
217
  },
196
218
 
197
219
  promptMove({ commit, state }, resources) {
@@ -87,7 +87,7 @@ export default Vue.extend({
87
87
  return {
88
88
  value: null as string | null,
89
89
  selected: null as string | null,
90
- isEditItem: null as string | null,
90
+ editedItem: null as string | null,
91
91
  isCreateItem: false,
92
92
  errors: { duplicate: false } as Record<Error, boolean>
93
93
  };
@@ -100,7 +100,7 @@ export default Vue.extend({
100
100
  */
101
101
  errorMessagesArray(): string[] {
102
102
  return (Object.keys(this.errors) as Error[])
103
- .filter(f => !!(this.errors)[f])
103
+ .filter(f => this.errors[f] && this.errorMessages[f])
104
104
  .map(k => this.errorMessages[k]);
105
105
  },
106
106
  },
@@ -113,23 +113,32 @@ export default Vue.extend({
113
113
  this.toggleEditMode(false);
114
114
  this.toggleCreateMode(false);
115
115
  },
116
+ errors: {
117
+ handler(val) {
118
+ this.$emit('errors', val);
119
+ },
120
+ deep: true
121
+ }
116
122
  },
117
123
 
118
124
  methods: {
119
125
  onChange(value: string) {
120
126
  this.value = value;
121
- /**
122
- * Remove duplicate error when a new value is typed
123
- */
127
+
128
+ const items = [
129
+ ...this.items,
130
+ this.value
131
+ ];
132
+
124
133
  this.toggleError(
125
134
  'duplicate',
126
- false,
127
- this.isCreateItem ? INPUT.create : INPUT.edit,
135
+ hasDuplicatedStrings(items, this.caseSensitive),
136
+ this.isCreateItem ? INPUT.create : INPUT.edit
128
137
  );
129
138
  },
130
139
 
131
140
  onSelect(item: string) {
132
- if (this.isCreateItem || this.isEditItem === item) {
141
+ if (this.readonly || this.isCreateItem || this.editedItem === item) {
133
142
  return;
134
143
  }
135
144
  this.selected = item;
@@ -160,7 +169,7 @@ export default Vue.extend({
160
169
  },
161
170
 
162
171
  onClickEmptyBody() {
163
- if (!this.isCreateItem && !this.isEditItem) {
172
+ if (!this.isCreateItem && !this.editedItem) {
164
173
  this.toggleCreateMode(true);
165
174
  }
166
175
  },
@@ -176,7 +185,7 @@ export default Vue.extend({
176
185
 
177
186
  return;
178
187
  }
179
- if (this.isEditItem) {
188
+ if (this.editedItem) {
180
189
  this.toggleEditMode(false);
181
190
 
182
191
  return;
@@ -229,6 +238,7 @@ export default Vue.extend({
229
238
  */
230
239
  toggleError(type: Error, val: boolean, refId?: string) {
231
240
  this.errors[type] = val;
241
+
232
242
  if (refId) {
233
243
  this.toggleErrorClass(refId, val);
234
244
  }
@@ -250,7 +260,11 @@ export default Vue.extend({
250
260
  * Show/Hide the input line to create new item
251
261
  */
252
262
  toggleCreateMode(show: boolean) {
263
+ if (this.readonly) {
264
+ return;
265
+ }
253
266
  if (show) {
267
+ this.toggleEditMode(false);
254
268
  this.value = '';
255
269
 
256
270
  this.isCreateItem = true;
@@ -268,18 +282,21 @@ export default Vue.extend({
268
282
  * Show/Hide the in-line editing to edit an existing item
269
283
  */
270
284
  toggleEditMode(show: boolean, item?: string) {
285
+ if (this.readonly) {
286
+ return;
287
+ }
271
288
  if (show) {
272
289
  this.toggleCreateMode(false);
273
- this.value = this.isEditItem;
290
+ this.value = this.editedItem;
274
291
 
275
- this.isEditItem = item || '';
292
+ this.editedItem = item || '';
276
293
  this.setFocus(INPUT.edit);
277
294
  } else {
278
295
  this.value = null;
279
296
  this.toggleError('duplicate', false);
280
297
  this.onSelectLeave();
281
298
 
282
- this.isEditItem = null;
299
+ this.editedItem = null;
283
300
  }
284
301
  },
285
302
 
@@ -292,7 +309,7 @@ export default Vue.extend({
292
309
  /**
293
310
  * Create a new item and insert in the items list
294
311
  */
295
- saveItem() {
312
+ saveItem(closeInput = true) {
296
313
  const value = this.value?.trim();
297
314
 
298
315
  if (value) {
@@ -301,21 +318,20 @@ export default Vue.extend({
301
318
  value,
302
319
  ];
303
320
 
304
- if (hasDuplicatedStrings(items, this.caseSensitive)) {
305
- this.toggleError('duplicate', true, INPUT.create);
306
-
307
- return;
321
+ if (!hasDuplicatedStrings(items, this.caseSensitive)) {
322
+ this.updateItems(items);
308
323
  }
324
+ }
309
325
 
310
- this.updateItems(items);
326
+ if (closeInput) {
327
+ this.toggleCreateMode(false);
311
328
  }
312
- this.toggleCreateMode(false);
313
329
  },
314
330
 
315
331
  /**
316
332
  * Update an existing item in the items list
317
333
  */
318
- updateItem(item: string) {
334
+ updateItem(item: string, closeInput = true) {
319
335
  const value = this.value?.trim();
320
336
 
321
337
  if (value) {
@@ -326,15 +342,14 @@ export default Vue.extend({
326
342
  items[index] = value;
327
343
  }
328
344
 
329
- if (hasDuplicatedStrings(items, this.caseSensitive)) {
330
- this.toggleError('duplicate', true, INPUT.edit);
331
-
332
- return;
345
+ if (!hasDuplicatedStrings(items, this.caseSensitive)) {
346
+ this.updateItems(items);
333
347
  }
348
+ }
334
349
 
335
- this.updateItems(items);
350
+ if (closeInput) {
351
+ this.toggleEditMode(false);
336
352
  }
337
- this.toggleEditMode(false);
338
353
  },
339
354
 
340
355
  /**
@@ -387,19 +402,19 @@ export default Vue.extend({
387
402
  @blur="onSelectLeave(item)"
388
403
  >
389
404
  <span
390
- v-if="!isEditItem || isEditItem !== item"
405
+ v-if="!editedItem || editedItem !== item"
391
406
  class="label static"
392
407
  >
393
408
  {{ item }}
394
409
  </span>
395
410
  <LabeledInput
396
- v-if="isEditItem && isEditItem === item"
411
+ v-if="editedItem && editedItem === item"
397
412
  ref="item-edit"
398
413
  class="edit-input static"
399
414
  :value="value != null ? value : item"
400
415
  @input="onChange($event)"
401
- @blur.prevent="toggleEditMode(false)"
402
- @keydown.native.enter="updateItem(item)"
416
+ @blur.prevent="updateItem(item)"
417
+ @keydown.native.enter="updateItem(item, !errors.duplicate)"
403
418
  />
404
419
  </div>
405
420
  <div
@@ -413,7 +428,8 @@ export default Vue.extend({
413
428
  :value="value"
414
429
  :placeholder="placeholder"
415
430
  @input="onChange($event)"
416
- @keydown.native.enter="saveItem"
431
+ @blur.prevent="saveItem"
432
+ @keydown.native.enter="saveItem(!errors.duplicate)"
417
433
  />
418
434
  </div>
419
435
  </div>
@@ -428,14 +444,14 @@ export default Vue.extend({
428
444
  >
429
445
  <button
430
446
  class="btn btn-sm role-tertiary remove-button"
431
- :disabled="!selected && !isCreateItem && !isEditItem"
447
+ :disabled="!selected && !isCreateItem && !editedItem"
432
448
  @mousedown.prevent="onClickMinusButton"
433
449
  >
434
450
  <span class="icon icon-minus icon-sm" />
435
451
  </button>
436
452
  <button
437
453
  class="btn btn-sm role-tertiary add-button"
438
- :disabled="isCreateItem"
454
+ :disabled="isCreateItem || editedItem"
439
455
  @click.prevent="onClickPlusButton"
440
456
  >
441
457
  <span class="icon icon-plus icon-sm" />
@@ -1923,7 +1923,7 @@ export default _default;
1923
1923
  // @shell/mixins/create-edit-view
1924
1924
 
1925
1925
  declare module '@shell/mixins/create-edit-view' {
1926
- declare var _default: import("vue/types/vue").ExtendedVue<Vue<Record<string, any>, Record<string, any>, never, never, (event: string, ...args: any[]) => Vue<Record<string, any>, Record<string, any>, never, never, any>>, {
1926
+ declare var _default: import("vue/types/vue").ExtendedVue<Vue, {
1927
1927
  errors: any[];
1928
1928
  }, {
1929
1929
  done(): any;
@@ -1949,7 +1949,7 @@ declare var _default: import("vue/types/vue").ExtendedVue<Vue<Record<string, any
1949
1949
  initialValue: any;
1950
1950
  liveValue: any;
1951
1951
  doneEvent: boolean;
1952
- }, {}, any, import("vue/types/v3-component-options").ComponentOptionsMixin>;
1952
+ }>;
1953
1953
  export default _default;
1954
1954
  import Vue from "vue";
1955
1955
  }
@@ -2846,35 +2846,35 @@ export namespace KEY {
2846
2846
  }
2847
2847
  }
2848
2848
 
2849
- // @shell/utils/poller
2849
+ // @shell/utils/poller-sequential
2850
2850
 
2851
- declare module '@shell/utils/poller' {
2852
- export default class Poller {
2851
+ declare module '@shell/utils/poller-sequential' {
2852
+ export default class PollerSequential {
2853
2853
  constructor(fn: any, pollRateMs: any, maxRetries?: number);
2854
2854
  fn: any;
2855
2855
  pollRateMs: any;
2856
2856
  maxRetries: number;
2857
- intervalId: any;
2857
+ timeoutId: any;
2858
2858
  tryCount: number;
2859
2859
  start(): void;
2860
2860
  stop(): void;
2861
+ _poll(): void;
2861
2862
  _intervalMethod(): Promise<void>;
2862
2863
  }
2863
2864
  }
2864
2865
 
2865
- // @shell/utils/poller-sequential
2866
+ // @shell/utils/poller
2866
2867
 
2867
- declare module '@shell/utils/poller-sequential' {
2868
- export default class PollerSequential {
2868
+ declare module '@shell/utils/poller' {
2869
+ export default class Poller {
2869
2870
  constructor(fn: any, pollRateMs: any, maxRetries?: number);
2870
2871
  fn: any;
2871
2872
  pollRateMs: any;
2872
2873
  maxRetries: number;
2873
- timeoutId: any;
2874
+ intervalId: any;
2874
2875
  tryCount: number;
2875
2876
  start(): void;
2876
2877
  stop(): void;
2877
- _poll(): void;
2878
2878
  _intervalMethod(): Promise<void>;
2879
2879
  }
2880
2880
  }
package/yarn-error.log ADDED
@@ -0,0 +1,196 @@
1
+ Arguments:
2
+ /Users/aalves/.nvm/versions/node/v16.16.0/bin/node /Users/aalves/.nvm/versions/node/v16.16.0/bin/yarn publish . --new-version 0.3.3 --no-git-tag-version --access public
3
+
4
+ PATH:
5
+ /Users/aalves/.nvm/versions/node/v16.16.0/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
6
+
7
+ Yarn version:
8
+ 1.22.19
9
+
10
+ Node version:
11
+ 16.16.0
12
+
13
+ Platform:
14
+ darwin arm64
15
+
16
+ Trace:
17
+ Error: https://registry.yarnpkg.com/-/user/org.couchdb.user:aalves08: failed to authenticate: Could not authenticate aalves08: bad password
18
+ at Request.params.callback [as _callback] (/Users/aalves/.nvm/versions/node/v16.16.0/lib/node_modules/yarn/lib/cli.js:66145:18)
19
+ at Request.self.callback (/Users/aalves/.nvm/versions/node/v16.16.0/lib/node_modules/yarn/lib/cli.js:140890:22)
20
+ at Request.emit (node:events:527:28)
21
+ at Request.<anonymous> (/Users/aalves/.nvm/versions/node/v16.16.0/lib/node_modules/yarn/lib/cli.js:141862:10)
22
+ at Request.emit (node:events:527:28)
23
+ at IncomingMessage.<anonymous> (/Users/aalves/.nvm/versions/node/v16.16.0/lib/node_modules/yarn/lib/cli.js:141784:12)
24
+ at Object.onceWrapper (node:events:641:28)
25
+ at IncomingMessage.emit (node:events:539:35)
26
+ at endReadableNT (node:internal/streams/readable:1345:12)
27
+ at processTicksAndRejections (node:internal/process/task_queues:83:21)
28
+
29
+ npm manifest:
30
+ {
31
+ "name": "@rancher/shell",
32
+ "version": "0.3.3",
33
+ "description": "Rancher Dashboard Shell",
34
+ "repository": "https://github.com/rancherlabs/dashboard",
35
+ "license": "Apache-2.0",
36
+ "author": "SUSE",
37
+ "private": false,
38
+ "engines": {
39
+ "node": ">=12"
40
+ },
41
+ "files": [
42
+ "**/*"
43
+ ],
44
+ "scripts": {
45
+ "clean": "./scripts/clean",
46
+ "lint": "./node_modules/.bin/eslint --max-warnings 0 --ext .ts,.js,.vue .",
47
+ "test": "./node_modules/.bin/nyc ava --serial --verbose",
48
+ "nuxt": "./node_modules/.bin/nuxt",
49
+ "dev": "./node_modules/.bin/nuxt dev",
50
+ "mem-dev": "node --max-old-space-size=8192 ./node_modules/.bin/nuxt dev",
51
+ "docker-dev": "docker run --rm --name dashboard-dev -p 8005:8005 -e API=$API -v $(pwd):/src -v dashboard_node:/src/node_modules rancher/dashboard:dev",
52
+ "build": "./node_modules/.bin/nuxt build --devtools",
53
+ "analyze": "./node_modules/.bin/nuxt build --analyze",
54
+ "start": "./node_modules/.bin/nuxt start",
55
+ "generate": "./node_modules/.bin/nuxt generate",
56
+ "dev-debug": "node --inspect ./node_modules/.bin/nuxt",
57
+ "cy:run": "cypress run",
58
+ "cy:open": "cypress open",
59
+ "e2e:pre": "NODE_ENV=dev yarn build",
60
+ "e2e:run": "NODE_ENV=dev START_SERVER_AND_TEST_INSECURE=1 start-server-and-test start https://localhost:8005/ cy:run",
61
+ "e2e:dev": "start-server-and-test dev https://localhost:8005 cy:open"
62
+ },
63
+ "dependencies": {
64
+ "@aws-sdk/client-ec2": "3.1.0",
65
+ "@aws-sdk/client-eks": "3.1.0",
66
+ "@aws-sdk/client-kms": "3.8.1",
67
+ "@babel/plugin-proposal-optional-chaining": "7.14.5",
68
+ "@babel/plugin-proposal-private-property-in-object": "7.14.5",
69
+ "@babel/preset-typescript": "7.16.7",
70
+ "@innologica/vue-dropdown-menu": "0.1.3",
71
+ "@novnc/novnc": "1.2.0",
72
+ "@nuxt/types": "2.14.6",
73
+ "@nuxt/typescript-build": "2.1.0",
74
+ "@nuxtjs/axios": "5.12.0",
75
+ "@nuxtjs/eslint-config-typescript": "6.0.1",
76
+ "@nuxtjs/eslint-module": "1.2.0",
77
+ "@nuxtjs/proxy": "1.3.3",
78
+ "@nuxtjs/style-resources": "1.2.1",
79
+ "@nuxtjs/webpack-profile": "0.1.0",
80
+ "@popperjs/core": "2.4.4",
81
+ "@types/node": "16.4.3",
82
+ "@typescript-eslint/eslint-plugin": "4.33.0",
83
+ "@typescript-eslint/parser": "4.33.0",
84
+ "@vue/cli-plugin-babel": "4.5.15",
85
+ "@vue/cli-plugin-typescript": "4.5.15",
86
+ "@vue/cli-service": "4.5.15",
87
+ "@vue/test-utils": "1.2.1",
88
+ "@vue/vue2-jest": "27.0.0",
89
+ "add": "2.0.6",
90
+ "ansi_up": "5.0.0",
91
+ "babel-eslint": "10.1.0",
92
+ "babel-plugin-module-resolver": "4.0.0",
93
+ "babel-preset-vue": "2.0.2",
94
+ "browser-env": "3.3.0",
95
+ "cookie": "0.5.0",
96
+ "cookie-universal-nuxt": "2.1.4",
97
+ "core-js": "3.21.1",
98
+ "cron-validator": "1.2.0",
99
+ "cronstrue": "1.95.0",
100
+ "cross-env": "6.0.3",
101
+ "css-loader": "4.3.0",
102
+ "csv-loader": "3.0.3",
103
+ "cypress": "10.3.1",
104
+ "d3": "7.3.0",
105
+ "d3-selection": "1.4.1",
106
+ "dagre-d3": "0.6.4",
107
+ "dayjs": "1.8.29",
108
+ "diff2html": "2.11.2",
109
+ "dompurify": "2.0.12",
110
+ "eslint": "7.32.0",
111
+ "eslint-config-standard": "16.0.3",
112
+ "eslint-import-resolver-node": "0.3.4",
113
+ "eslint-module-utils": "2.6.1",
114
+ "eslint-plugin-cypress": "2.12.1",
115
+ "eslint-plugin-import": "2.23.4",
116
+ "eslint-plugin-jest": "24.4.0",
117
+ "eslint-plugin-n": "15.2.0",
118
+ "eslint-plugin-vue": "7.14.0",
119
+ "event-target-shim": "5.0.1",
120
+ "express": "4.17.1",
121
+ "file-saver": "2.0.2",
122
+ "frontmatter-markdown-loader": "3.7.0",
123
+ "identicon.js": "2.3.3",
124
+ "intl-messageformat": "7.8.4",
125
+ "is-url": "1.2.4",
126
+ "jest": "27.5.1",
127
+ "jest-serializer-vue": "2.0.2",
128
+ "jexl": "2.2.2",
129
+ "jquery": "3.5.1",
130
+ "js-cookie": "2.2.1",
131
+ "js-yaml": "4.1.0",
132
+ "js-yaml-loader": "1.2.2",
133
+ "jsdiff": "1.1.1",
134
+ "jsdom-global": "3.0.2",
135
+ "jsonpath-plus": "6.0.1",
136
+ "jsrsasign": "10.2.0",
137
+ "jszip": "3.7.0",
138
+ "lodash": "4.17.21",
139
+ "marked": "4.0.17",
140
+ "nodemon": "2.0.4",
141
+ "nuxt": "2.15.8",
142
+ "nyc": "15.1.0",
143
+ "papaparse": "5.3.0",
144
+ "portal-vue": "2.1.7",
145
+ "rancher-icons": "rancher/icons#v2.0.13",
146
+ "require-extension-hooks": "0.3.3",
147
+ "require-extension-hooks-babel": "1.0.0",
148
+ "require-extension-hooks-vue": "3.0.0",
149
+ "sass": "1.51.0",
150
+ "sass-loader": "10.2.1",
151
+ "serve-static": "1.14.1",
152
+ "set-cookie-parser": "2.4.6",
153
+ "shell-quote": "1.7.3",
154
+ "sinon": "8.1.1",
155
+ "start-server-and-test": "1.13.1",
156
+ "style-loader": "1.2.1",
157
+ "ts-node": "8.10.2",
158
+ "typescript": "4.1.6",
159
+ "url-parse": "1.5.10",
160
+ "v-tooltip": "2.0.3",
161
+ "vue": "2.6.14",
162
+ "vue-clipboard2": "0.3.1",
163
+ "vue-codemirror": "4.0.6",
164
+ "vue-js-modal": "1.3.35",
165
+ "vue-resize": "0.4.5",
166
+ "vue-select": "3.18.3",
167
+ "vue-server-renderer": "2.6.14",
168
+ "vue-shortkey": "3.1.7",
169
+ "vue-template-compiler": "2.6.14",
170
+ "vue-virtual-scroll-list": "^2.3.4",
171
+ "vue2-transitions": "0.3.0",
172
+ "vuedraggable": "2.24.3",
173
+ "vuex": "3.6.2",
174
+ "webpack-bundle-analyzer": "4.5.0",
175
+ "webpack-virtual-modules": "0.4.3",
176
+ "xterm": "5.0.0",
177
+ "xterm-addon-fit": "0.6.0",
178
+ "xterm-addon-search": "0.10.0",
179
+ "xterm-addon-web-links": "0.7.0",
180
+ "xterm-addon-webgl": "0.13.0",
181
+ "worker-loader": "3.0.8",
182
+ "yarn": "1.22.18"
183
+ },
184
+ "nyc": {
185
+ "extension": [
186
+ ".js",
187
+ ".vue"
188
+ ]
189
+ }
190
+ }
191
+
192
+ yarn manifest:
193
+ No manifest
194
+
195
+ Lockfile:
196
+ No lockfile