dashboard-shell-shell 1.0.1000000116 → 1.0.1000000117

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.
Files changed (124) hide show
  1. package/assets/images/action.svg +6 -0
  2. package/assets/images/pl/logo.png +0 -0
  3. package/assets/styles/base/_functions.scss +0 -0
  4. package/assets/styles/base/_mixins.scss +1 -1
  5. package/assets/styles/global/_button.scss +17 -10
  6. package/assets/styles/global/_form.scss +2 -2
  7. package/assets/styles/global/_labeled-input.scss +6 -2
  8. package/assets/styles/global/_select.scss +6 -7
  9. package/assets/styles/global/_table.scss +3 -2
  10. package/assets/styles/global/_tooltip.scss +8 -1
  11. package/assets/styles/themes/_dark.scss +2 -0
  12. package/assets/styles/themes/_light.scss +5 -2
  13. package/assets/styles/vendor/vue-select.scss +2 -1
  14. package/assets/translations/en-us.yaml +1 -3
  15. package/assets/translations/zh-hans.yaml +51 -28
  16. package/components/ActionDropdown.vue +1 -0
  17. package/components/ActionMenuShell.vue +6 -3
  18. package/components/BrandImage.vue +22 -0
  19. package/components/ClusterIconMenu.vue +1 -1
  20. package/components/CodeMirror.vue +1 -0
  21. package/components/CruResource.vue +1 -1
  22. package/components/CruResourceFooter.vue +1 -1
  23. package/components/ExplorerProjectsNamespaces.vue +4 -24
  24. package/components/GlobalRoleBindings.vue +112 -48
  25. package/components/IndentedPanel.vue +4 -10
  26. package/components/PromptRemove.vue +3 -3
  27. package/components/ResourceDetail/Masthead.vue +190 -242
  28. package/components/ResourceDetail/index.vue +20 -5
  29. package/components/ResourceList/Masthead.vue +146 -84
  30. package/components/ResourceList/ResourceLoadingIndicator.vue +5 -2
  31. package/components/ResourceTable.vue +76 -1
  32. package/components/SideNav.vue +66 -29
  33. package/components/SortableTable/THead.vue +6 -0
  34. package/components/SortableTable/index.vue +481 -388
  35. package/components/Tabbed/index.vue +4 -5
  36. package/components/auth/Principal.vue +3 -2
  37. package/components/auth/RoleDetailEdit.vue +58 -5
  38. package/components/auth/SelectPrincipal.vue +1 -0
  39. package/components/form/BannerSettings.vue +18 -16
  40. package/components/form/ChangePassword.vue +4 -4
  41. package/components/form/ColorInput.vue +32 -8
  42. package/components/form/Footer.vue +1 -1
  43. package/components/form/InputWithSelect.vue +2 -0
  44. package/components/form/KeyValue.vue +31 -7
  45. package/components/form/LabeledSelect.vue +178 -178
  46. package/components/form/Members/ClusterPermissionsEditor.vue +1 -2
  47. package/components/form/Members/MembershipEditor.vue +1 -1
  48. package/components/form/NameNsDescription.vue +24 -11
  49. package/components/form/Password.vue +6 -2
  50. package/components/form/ResourceQuota/Namespace.vue +1 -1
  51. package/components/form/ResourceQuota/NamespaceRow.vue +13 -10
  52. package/components/form/ResourceQuota/ProjectRow.vue +0 -1
  53. package/components/form/Select.vue +2 -2
  54. package/components/nav/Favorite.vue +5 -1
  55. package/components/nav/Group.vue +69 -23
  56. package/components/nav/Header.vue +82 -17
  57. package/components/nav/HeaderPageActionMenu.vue +1 -0
  58. package/components/nav/NamespaceFilter.vue +0 -3
  59. package/components/nav/TopLevelMenu.vue +182 -119
  60. package/components/nav/Type.vue +48 -11
  61. package/composables/useClickOutside.ts +1 -1
  62. package/config/product/auth.js +16 -7
  63. package/config/product/explorer.js +1 -1
  64. package/config/product/settings.js +17 -8
  65. package/config/settings.ts +28 -0
  66. package/edit/management.cattle.io.user.vue +17 -4
  67. package/edit/networking.k8s.io.ingress/RulePath.vue +1 -1
  68. package/edit/token.vue +1 -1
  69. package/list/harvesterhci.io.management.cluster.vue +17 -0
  70. package/list/management.cattle.io.setting.vue +22 -13
  71. package/list/management.cattle.io.user.vue +25 -14
  72. package/list/provisioning.cattle.io.cluster.vue +6 -7
  73. package/mixins/brand.js +17 -0
  74. package/package.json +1 -1
  75. package/pages/auth/login.vue +84 -29
  76. package/pages/c/_cluster/auth/roles/index.vue +61 -14
  77. package/pages/c/_cluster/settings/banners.vue +174 -101
  78. package/pages/c/_cluster/settings/brand.vue +348 -301
  79. package/pages/c/_cluster/settings/performance.vue +61 -38
  80. package/pages/home.vue +70 -21
  81. package/pages/prefs.vue +25 -23
  82. package/pkg/tsconfig.json +9 -9
  83. package/pkg/vue.config.js +1 -1
  84. package/promptRemove/mixin/roleDeletionCheck.js +2 -2
  85. package/scripts/clean +0 -0
  86. package/scripts/extension/bundle +0 -0
  87. package/scripts/extension/helm/scripts/package +0 -0
  88. package/scripts/extension/helm/scripts/patch +0 -0
  89. package/scripts/extension/helm/scripts/version +0 -0
  90. package/scripts/extension/helmpatch +0 -0
  91. package/scripts/extension/parse-tag-name +0 -0
  92. package/scripts/extension/publish +0 -0
  93. package/scripts/publish-shell.sh +86 -60
  94. package/scripts/serve-pkgs +0 -0
  95. package/scripts/sync-shell-deps +0 -0
  96. package/scripts/typegen.sh +44 -28
  97. package/store/i18n.js +5 -5
  98. package/store/prefs.js +17 -5
  99. package/store/type-map.js +2 -1
  100. package/types/shell/index.d.ts +1 -1
  101. package/utils/error.js +4 -0
  102. package/utils/router.js +3 -3
  103. package/vue.config.js +1 -6
  104. package/components/rancherResourceDetail/Masthead.vue +0 -769
  105. package/components/rancherResourceDetail/__tests__/Masthead.test.ts +0 -65
  106. package/components/rancherResourceDetail/index.vue +0 -591
  107. package/components/rancherResourceList/Masthead.vue +0 -375
  108. package/components/rancherResourceList/ResourceLoadingIndicator.vue +0 -140
  109. package/components/rancherResourceList/index.vue +0 -307
  110. package/components/rancherResourceList/resource-list.config.js +0 -7
  111. package/components/rancherResourceTable.vue +0 -783
  112. package/components/rancherSortableTable/THead.vue +0 -561
  113. package/components/rancherSortableTable/actions.js +0 -153
  114. package/components/rancherSortableTable/advanced-filtering.js +0 -272
  115. package/components/rancherSortableTable/debug.js +0 -117
  116. package/components/rancherSortableTable/filtering.js +0 -290
  117. package/components/rancherSortableTable/grouping.js +0 -48
  118. package/components/rancherSortableTable/index.vue +0 -2712
  119. package/components/rancherSortableTable/paging.js +0 -155
  120. package/components/rancherSortableTable/selection.js +0 -629
  121. package/components/rancherSortableTable/sortable-config.ts +0 -4
  122. package/components/rancherSortableTable/sorting.js +0 -129
  123. package/types/cloud-shell/index.d.ts +0 -11014
  124. /package/components/{rancherResourceList → ResourceList}/Masthead-btn.vue +0 -0
@@ -1,65 +0,0 @@
1
- import { mount, RouterLinkStub } from '@vue/test-utils';
2
- import { _VIEW } from '@shell/config/query-params';
3
- import Masthead from '@shell/components/ResourceDetail/Masthead.vue';
4
- import { createStore } from 'vuex';
5
-
6
- const mockedStore = () => {
7
- return {
8
- getters: {
9
- currentStore: () => 'current_store',
10
- currentProduct: { inStore: 'cluster' },
11
- isExplorer: false,
12
- currentCluster: {},
13
- 'type-map/labelFor': jest.fn(),
14
- 'type-map/optionsFor': jest.fn(),
15
- 'current_store/schemaFor': jest.fn(),
16
- },
17
- };
18
- };
19
-
20
- const requiredSetup = () => {
21
- const store = createStore({ getters: { 'management/byId': () => jest.fn() } });
22
-
23
- return {
24
- stubs: {
25
- 'router-link': RouterLinkStub,
26
- LiveDate: true
27
- },
28
- provide: { store },
29
- mocks: { $store: mockedStore() }
30
- };
31
- };
32
-
33
- describe('component: Masthead', () => {
34
- it.each([
35
- ['hidden', '', false, { displayName: 'admin', location: { id: 'resource-id' } }, false, false],
36
- ['plain-text', 'admin', true, { displayName: 'admin', location: null }, false, true],
37
- ['link', 'foo', true, { displayName: 'foo', location: { id: 'resource-id' } }, true, false],
38
- ])('"Created By" should be %p, with text: %p', (
39
- _,
40
- text,
41
- showCreatedBy,
42
- createdBy,
43
- showLink,
44
- showPlainText,
45
- ) => {
46
- const wrapper = mount(Masthead, {
47
- props: {
48
- mode: _VIEW,
49
- value: {
50
- showCreatedBy,
51
- createdBy,
52
- },
53
- },
54
- global: { ...requiredSetup() }
55
- });
56
-
57
- const container = wrapper.find('[data-testid="masthead-subheader-createdBy"]');
58
- const link = wrapper.find('[data-testid="masthead-subheader-createdBy-link"]');
59
- const plainText = wrapper.find('[data-testid="masthead-subheader-createdBy_plain-text"]');
60
-
61
- expect(link.exists()).toBe(showLink);
62
- expect(plainText.exists()).toBe(showPlainText);
63
- expect(showLink || showPlainText ? container.element.textContent : '').toContain(text);
64
- });
65
- });
@@ -1,591 +0,0 @@
1
- <script>
2
- import CreateEditView from '@shell/mixins/create-edit-view/impl';
3
- import Loading from '@shell/components/Loading';
4
- import ResourceYaml from '@shell/components/ResourceYaml';
5
- import {
6
- _VIEW, _EDIT, _CLONE, _IMPORT, _STAGE, _CREATE,
7
- AS, _YAML, _DETAIL, _CONFIG, _GRAPH, PREVIEW, MODE,
8
- } from '@shell/config/query-params';
9
- import { FLEET, SCHEMA } from '@shell/config/types';
10
- import { createYaml } from '@shell/utils/create-yaml';
11
- import Masthead from '@shell/components/ResourceDetail/Masthead';
12
- import DetailTop from '@shell/components/DetailTop';
13
- import { clone, diff } from '@shell/utils/object';
14
- import IconMessage from '@shell/components/IconMessage';
15
- import ForceDirectedTreeChart from '@shell/components/fleet/ForceDirectedTreeChart';
16
- import { checkSchemasForFindAllHash } from '@shell/utils/auth';
17
- import { stringify } from '@shell/utils/error';
18
- import { Banner } from '@components/Banner';
19
- import { harvesterhci2cloud, cloud2harvesterhci } from '@shell/utils/router'
20
-
21
- function modeFor(route) {
22
- if ( route.query?.mode === _IMPORT ) {
23
- return _IMPORT;
24
- }
25
-
26
- if ( route.params?.id ) {
27
- return route.query.mode || _VIEW;
28
- } else {
29
- return _CREATE;
30
- }
31
- }
32
-
33
- async function getYaml(store, model) {
34
- let yaml;
35
- const opt = { headers: { accept: 'application/yaml' } };
36
-
37
- if ( model.hasLink('view') ) {
38
- yaml = (await model.followLink('view', opt)).data;
39
- }
40
-
41
- return model.cleanForDownload(yaml);
42
- }
43
-
44
- export default {
45
- emits: ['input'],
46
-
47
- components: {
48
- Loading,
49
- DetailTop,
50
- ForceDirectedTreeChart,
51
- ResourceYaml,
52
- Masthead,
53
- IconMessage,
54
- Banner
55
- },
56
-
57
- mixins: [CreateEditView],
58
-
59
- props: {
60
- storeOverride: {
61
- type: String,
62
- default: null,
63
- },
64
-
65
- resourceOverride: {
66
- type: String,
67
- default: null,
68
- },
69
-
70
- parentRouteOverride: {
71
- type: String,
72
- default: null,
73
- },
74
-
75
- flexContent: {
76
- type: Boolean,
77
- default: false,
78
- },
79
-
80
- /**
81
- * Inherited global identifier prefix for tests
82
- * Define a term based on the parent component to avoid conflicts on multiple components
83
- */
84
- componentTestid: {
85
- type: String,
86
- default: 'resource-details'
87
- },
88
- errorsMap: {
89
- type: Object,
90
- default: null
91
- },
92
- },
93
-
94
- async fetch() {
95
- const store = this.$store;
96
- const route = this.$route;
97
- const params = route.params;
98
- const cloneParams = clone(params)
99
- let resourceType = this.resourceOverride || cloneParams.resource;
100
-
101
- const inStore = this.storeOverride || store.getters['currentStore'](resourceType);
102
- const realMode = this.realMode;
103
-
104
- // eslint-disable-next-line prefer-const
105
- let { namespace, id } = params;
106
-
107
- // There are 6 "real" modes that can be put into the query string
108
- // These are mapped down to the 3 regular page "mode"s that create-edit-view components
109
- // know about: view, edit, create (stage, import and clone become "create")
110
- const mode = ([_CLONE, _IMPORT, _STAGE].includes(realMode) ? _CREATE : realMode);
111
-
112
- const getGraphConfig = store.getters['type-map/hasGraph'](resourceType);
113
- const hasGraph = !!getGraphConfig;
114
- const hasCustomDetail = store.getters['type-map/hasCustomDetail'](resourceType, id);
115
- const hasCustomEdit = store.getters['type-map/hasCustomEdit'](resourceType, id);
116
-
117
- const schemas = store.getters[`${ inStore }/all`](SCHEMA);
118
-
119
- // As determines what component will be rendered
120
- const requested = route.query[AS];
121
- let as;
122
- let notFound = false;
123
-
124
- if ( mode === _VIEW && hasCustomDetail && (!requested || requested === _DETAIL) ) {
125
- as = _DETAIL;
126
- } else if ( mode === _VIEW && hasGraph && requested === _GRAPH) {
127
- as = _GRAPH;
128
- } else if ( hasCustomEdit && (!requested || requested === _CONFIG) ) {
129
- as = _CONFIG;
130
- } else {
131
- as = _YAML;
132
- }
133
-
134
- this.as = as;
135
-
136
- const options = store.getters[`type-map/optionsFor`](resourceType);
137
-
138
- this.showMasthead = [_CREATE, _EDIT].includes(mode) ? options.resourceEditMasthead : true;
139
- const canViewYaml = options.canYaml;
140
-
141
- if ( options.resource ) {
142
- resourceType = options.resource;
143
- }
144
-
145
- const schema = store.getters[`${ inStore }/schemaFor`](resourceType);
146
- let model, initialModel, liveModel, yaml;
147
-
148
- if ( realMode === _CREATE || realMode === _IMPORT ) {
149
- if ( !namespace ) {
150
- namespace = store.getters['defaultNamespace'];
151
- }
152
-
153
- const data = { type: resourceType };
154
-
155
- if ( schema?.attributes?.namespaced ) {
156
- data.metadata = { namespace };
157
- }
158
-
159
- liveModel = await store.dispatch(`${ inStore }/create`, data);
160
- initialModel = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
161
- model = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
162
-
163
- if (model.forceYaml === true) {
164
- as = _YAML;
165
- this.as = as;
166
- }
167
-
168
- if ( as === _YAML ) {
169
- if (schema?.fetchResourceFields) {
170
- // fetch resourceFields for createYaml
171
- await schema.fetchResourceFields();
172
- }
173
-
174
- yaml = createYaml(schemas, resourceType, data);
175
- }
176
- } else {
177
- if ( as === _GRAPH ) {
178
- const graphSchema = await checkSchemasForFindAllHash({
179
- cluster: {
180
- inStoreType: 'management',
181
- type: FLEET.CLUSTER
182
- },
183
- bundle: {
184
- inStoreType: 'management',
185
- type: FLEET.BUNDLE,
186
- opt: { excludeFields: ['metadata.managedFields', 'spec.resources'] },
187
- },
188
-
189
- bundleDeployment: {
190
- inStoreType: 'management',
191
- type: FLEET.BUNDLE_DEPLOYMENT
192
- }
193
-
194
- }, this.$store);
195
-
196
- this.canViewChart = graphSchema.cluster && graphSchema.bundle && graphSchema.bundleDeployment;
197
- }
198
-
199
- let fqid = id;
200
-
201
- if ( schema.attributes?.namespaced && namespace ) {
202
- fqid = `${ namespace }/${ fqid }`;
203
- }
204
-
205
- try {
206
- liveModel = await store.dispatch(`${ inStore }/find`, {
207
- type: resourceType,
208
- id: fqid,
209
- opt: { watch: true }
210
- });
211
- } catch (e) {
212
- if (e.status === 404 || e.status === 403) {
213
- store.dispatch('loadingError', new Error(this.t('nav.failWhale.resourceIdNotFound', { resource: resourceType, fqid }, true)));
214
- }
215
- liveModel = {};
216
- notFound = fqid;
217
- }
218
-
219
- try {
220
- if (realMode === _VIEW) {
221
- model = liveModel;
222
- } else {
223
- model = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
224
- }
225
- initialModel = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
226
-
227
- if ( as === _YAML ) {
228
- yaml = await getYaml(this.$store, liveModel);
229
- }
230
- } catch (e) {
231
- this.errors.push(e);
232
- }
233
- if ( as === _YAML ) {
234
- try {
235
- yaml = await getYaml(this.$store, liveModel);
236
- } catch (e) {
237
- this.errors.push(e);
238
- }
239
- }
240
-
241
- if ( as === _GRAPH ) {
242
- this.chartData = liveModel;
243
- }
244
-
245
- if ( [_CLONE, _IMPORT, _STAGE].includes(realMode) ) {
246
- model.cleanForNew();
247
- yaml = model.cleanYaml(yaml, realMode);
248
- }
249
- }
250
-
251
- // Ensure common properties exists
252
- try {
253
- model = await store.dispatch(`${ inStore }/cleanForDetail`, model);
254
- } catch (e) {
255
- this.errors.push(e);
256
- }
257
-
258
- const out = {
259
- hasGraph,
260
- getGraphConfig,
261
- hasCustomDetail,
262
- hasCustomEdit,
263
- canViewYaml,
264
- resourceType,
265
- as,
266
- yaml,
267
- initialModel,
268
- liveModel,
269
- mode,
270
- value: model,
271
- notFound,
272
- };
273
-
274
- for ( const key in out ) {
275
- this[key] = out[key];
276
- }
277
-
278
- if ( this.mode === _CREATE ) {
279
- this.value.applyDefaults(this, realMode);
280
- }
281
- },
282
- data() {
283
- return {
284
- chartData: null,
285
- resourceSubtype: null,
286
-
287
- // Set by fetch
288
- hasGraph: null,
289
- hasCustomDetail: null,
290
- hasCustomEdit: null,
291
- resourceType: null,
292
- asYaml: null,
293
- yaml: null,
294
- liveModel: null,
295
- initialModel: null,
296
- mode: null,
297
- as: null,
298
- value: null,
299
- model: null,
300
- notFound: null,
301
- canViewChart: true,
302
- canViewYaml: null,
303
- errors: []
304
- };
305
- },
306
-
307
- computed: {
308
- realMode() {
309
- // There are 5 "real" modes that you can start in: view, edit, create, stage, clone
310
- const realMode = modeFor(this.$route);
311
-
312
- return realMode;
313
- },
314
-
315
- isView() {
316
- return this.mode === _VIEW;
317
- },
318
-
319
- isYaml() {
320
- return this.as === _YAML;
321
- },
322
-
323
- isDetail() {
324
- return this.as === _DETAIL;
325
- },
326
-
327
- isGraph() {
328
- return this.as === _GRAPH;
329
- },
330
-
331
- offerPreview() {
332
- return this.as === _YAML && [_EDIT, _CLONE, _IMPORT, _STAGE].includes(this.mode);
333
- },
334
-
335
- showComponent() {
336
- switch ( this.as ) {
337
- case _DETAIL: return this.detailComponent;
338
- case _CONFIG: return this.editComponent;
339
- }
340
-
341
- return null;
342
- },
343
- hasErrors() {
344
- return this.errors?.length && Array.isArray(this.errors);
345
- },
346
- mappedErrors() {
347
- return !this.errors ? {} : this.errorsMap || this.errors.reduce((acc, error) => ({
348
- ...acc,
349
- [error]: {
350
- message: error?.data?.message || error,
351
- icon: null
352
- }
353
- }), {});
354
- },
355
- },
356
-
357
- watch: {
358
- '$route'(current, prev) {
359
- if (current.name !== prev.name) {
360
- return;
361
- }
362
- const neu = clone(current.query);
363
- const old = clone(prev.query);
364
-
365
- delete neu[PREVIEW];
366
- delete old[PREVIEW];
367
-
368
- if ( !this.isView ) {
369
- delete neu[AS];
370
- delete old[AS];
371
- }
372
-
373
- const queryDiff = Object.keys(diff(neu, old));
374
-
375
- if (queryDiff.includes(MODE) || queryDiff.includes(AS)) {
376
- this.$fetch();
377
- }
378
- },
379
-
380
- // Auto refresh YAML when the model changes
381
- async 'value.metadata.resourceVersion'(a, b) {
382
- if ( this.mode === _VIEW && this.as === _YAML && a && b && a !== b) {
383
- this.yaml = await getYaml(this.$store, this.liveModel);
384
- }
385
- }
386
- },
387
-
388
- created() {
389
- this.configureResource();
390
- },
391
-
392
- methods: {
393
- stringify,
394
- setSubtype(subtype) {
395
- this.resourceSubtype = subtype;
396
- },
397
-
398
- keyAction(act) {
399
- const m = this.liveModel;
400
-
401
- if ( m?.[act] ) {
402
- m[act]();
403
- }
404
- },
405
- closeError(index) {
406
- this.errors = this.errors.filter((_, i) => i !== index);
407
- },
408
- /**
409
- * Initializes the resource components based on the provided user and
410
- * resource override.
411
- *
412
- * Configures the detail and edit components for a resource based on the
413
- * user's ID and the specified resource.
414
- *
415
- * @param {Object} user - The user object containing user-specific
416
- * information.
417
- * @param {string|null} resourceOverride - An optional resource override
418
- * string. If not provided, the method will use the default resource from
419
- * the route parameters or the instance's resourceOverride property.
420
- */
421
- configureResource(userId = '', resourceOverride = null) {
422
- const id = userId || this.$route.params.id;
423
- // const paramsResource = this.$route.params.resource
424
- const routerResource = clone(this.$route.params.resource)
425
- const resource = resourceOverride || this.resourceOverride || routerResource;
426
- const options = this.$store.getters[`type-map/optionsFor`](resource);
427
-
428
- const detailResource = options.resourceDetail || options.resource || resource;
429
- const editResource = options.resourceEdit || options.resource || resource;
430
-
431
- // FIXME: These aren't right... signature is (rawType, subType).. not (rawType, resourceId)
432
- // Remove id? How does subtype get in (cluster/node)
433
- this.detailComponent = this.$store.getters['type-map/importDetail'](detailResource, id);
434
- this.editComponent = this.$store.getters['type-map/importEdit'](editResource, id);
435
- },
436
- /**
437
- * Sets the mode and initializes the resource components.
438
- *
439
- * This method sets the mode of the component and configures the resource
440
- * components based on the provided user and resource.
441
- *
442
- * @param {Object} payload - An object containing the mode, user, and
443
- * resource properties.
444
- * @param {string} payload.mode - The mode to set.
445
- * @param {Object} payload.user - The user object containing user-specific
446
- * information.
447
- * @param {string} payload.resource - The resource string to use for
448
- * initialization.
449
- */
450
- setMode({ mode, userId, resource }) {
451
- this.mode = mode;
452
- this.value.id = userId;
453
- this.configureResource(userId, resource);
454
- }
455
- }
456
- };
457
- </script>
458
-
459
- <template>
460
-
461
- <!-- 如果数据还在加载中,或者资源未找到,则显示 Loading 组件 -->
462
- <Loading v-if="$fetchState.pending || notFound" />
463
-
464
- <!-- 数据加载完成且资源存在时 -->
465
- <div style="padding: 20px 0 20px 20px;height: 100%;" v-else>
466
-
467
- <!-- 顶部 Masthead 区域 -->
468
- <Masthead
469
- v-if="showMasthead"
470
- :resource="resourceType"
471
- :value="liveModel"
472
- :mode="mode"
473
- :real-mode="realMode"
474
- :as="as"
475
- :has-graph="hasGraph"
476
- :has-detail="hasCustomDetail"
477
- :has-edit="hasCustomEdit"
478
- :can-view-yaml="canViewYaml"
479
- :resource-subtype="resourceSubtype"
480
- :parent-route-override="parentRouteOverride"
481
- :store-override="storeOverride"
482
- >
483
-
484
- <!-- 详情顶部信息,仅在查看模式且是详情页面时显示 -->
485
- <!-- <DetailTop
486
- v-if="isView && isDetail"
487
- :value="liveModel"
488
- /> -->
489
- </Masthead>
490
-
491
-
492
- <!-- 错误信息显示区域 -->
493
- <div
494
- v-if="hasErrors"
495
- id="cru-errors"
496
- class="cru__errors"
497
- >
498
-
499
- <!-- 循环渲染错误 Banner -->
500
- <Banner
501
- v-for="(err, i) in errors"
502
- :key="i"
503
- color="error"
504
- :data-testid="`error-banner${i}`"
505
- :label="stringify(mappedErrors[err].message)"
506
- :icon="mappedErrors[err].icon"
507
- :closable="true"
508
- @close="closeError(i)"
509
- />
510
- </div>
511
-
512
- <!-- 力导向图模式 -->
513
- <ForceDirectedTreeChart
514
- v-if="isGraph && canViewChart"
515
- :data="chartData"
516
- :fdc-config="getGraphConfig"
517
- />
518
-
519
- <!-- YAML 查看/编辑模式 -->
520
- <ResourceYaml
521
- v-else-if="isYaml"
522
- ref="resourceyaml"
523
- :value="value"
524
- :mode="mode"
525
- :yaml="yaml"
526
- :offer-preview="offerPreview"
527
- :done-route="doneRoute"
528
- :done-override="value ? value.doneOverride : null"
529
- @update:value="$emit('input', $event)"
530
- @error="e=>errors.push(e)"
531
- />
532
-
533
- <!-- 动态组件渲染,根据 showComponent 动态选择组件 -->
534
- <component
535
- :is="showComponent"
536
- v-else
537
- ref="comp"
538
- v-model:value="value"
539
- v-bind="$data"
540
- :done-params="doneParams"
541
- :done-route="doneRoute"
542
- :mode="mode"
543
- :initial-value="initialModel"
544
- :live-value="liveModel"
545
- :real-mode="realMode"
546
- :class="{'flex-content': flexContent}"
547
- @update:value="$emit('input', $event)"
548
- @update:mode="setMode"
549
- @set-subtype="setSubtype"
550
- />
551
-
552
-
553
- <!-- 快捷键按钮(隐藏) -->
554
- <button
555
- v-if="isView"
556
- v-shortkey.once="['shift','d']"
557
- :data-testid="componentTestid + '-detail'"
558
- class="hide"
559
- @shortkey="keyAction('goToDetail')"
560
- />
561
- <button
562
- v-if="isView"
563
- v-shortkey.once="['shift','c']"
564
- :data-testid="componentTestid + '-config'"
565
- class="hide"
566
- @shortkey="keyAction('goToViewConfig')"
567
- />
568
- <button
569
- v-if="isView"
570
- v-shortkey.once="['shift','y']"
571
- :data-testid="componentTestid + '-yaml'"
572
- class="hide"
573
- @shortkey="keyAction('goToViewYaml')"
574
- />
575
- <button
576
- v-if="isView"
577
- v-shortkey.once="['shift','e']"
578
- :data-testid="componentTestid + '-edit'"
579
- class="hide"
580
- @shortkey="keyAction('goToEdit')"
581
- />
582
- </div>
583
- </template>
584
-
585
- <style lang='scss' scoped>
586
- .flex-content {
587
- display: flex;
588
- flex-direction: column;
589
- flex-grow: 1;
590
- }
591
- </style>