@rancher/shell 3.0.5-rc.3 → 3.0.5-rc.5
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/icons/document.svg +3 -0
- package/assets/images/vendor/cognito.svg +1 -0
- package/assets/styles/app.scss +1 -0
- package/assets/styles/base/_basic.scss +10 -0
- package/assets/styles/base/_spacing.scss +29 -0
- package/assets/styles/global/_layout.scss +1 -1
- package/assets/styles/themes/_dark.scss +25 -0
- package/assets/styles/themes/_light.scss +65 -0
- package/assets/translations/en-us.yaml +322 -24
- package/assets/translations/zh-hans.yaml +8 -5
- package/components/Certificates.vue +5 -0
- package/components/FilterPanel.vue +156 -0
- package/components/{fleet/ForceDirectedTreeChart/index.vue → ForceDirectedTreeChart.vue} +47 -41
- package/components/IconOrSvg.vue +14 -35
- package/components/PromptRemove.vue +5 -1
- package/components/Resource/Detail/Card/PodsCard/Bubble.vue +13 -0
- package/components/Resource/Detail/Card/PodsCard/composable.ts +30 -0
- package/components/Resource/Detail/Card/PodsCard/index.vue +118 -0
- package/components/Resource/Detail/Card/ResourceUsageCard/composable.ts +51 -0
- package/components/Resource/Detail/Card/ResourceUsageCard/index.vue +79 -0
- package/components/Resource/Detail/Card/Scaler.vue +89 -0
- package/components/Resource/Detail/Card/StateCard/composables.ts +112 -0
- package/components/Resource/Detail/Card/StateCard/index.vue +39 -0
- package/components/Resource/Detail/Card/VerticalGap.vue +11 -0
- package/components/Resource/Detail/Card/__tests__/Card.test.ts +36 -0
- package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +84 -0
- package/components/Resource/Detail/Card/__tests__/ResourceUsageCard.test.ts +72 -0
- package/components/Resource/Detail/Card/__tests__/Scaler.test.ts +87 -0
- package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +53 -0
- package/components/Resource/Detail/Card/__tests__/VerticalGap.test.ts +14 -0
- package/components/Resource/Detail/Card/__tests__/index.test.ts +36 -0
- package/components/Resource/Detail/Card/index.vue +56 -0
- package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +19 -0
- package/components/Resource/Detail/Metadata/Annotations/composable.ts +12 -0
- package/components/Resource/Detail/Metadata/Annotations/index.vue +26 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +103 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +281 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +111 -0
- package/components/Resource/Detail/Metadata/KeyValue.vue +130 -0
- package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +18 -0
- package/components/Resource/Detail/Metadata/Labels/composable.ts +12 -0
- package/components/Resource/Detail/Metadata/Labels/index.vue +27 -0
- package/components/Resource/Detail/Metadata/Rectangle.vue +32 -0
- package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +107 -0
- package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +24 -0
- package/components/Resource/Detail/Metadata/__tests__/index.test.ts +91 -0
- package/components/Resource/Detail/Metadata/composables.ts +29 -0
- package/components/Resource/Detail/Metadata/index.vue +66 -0
- package/components/Resource/Detail/Page.vue +22 -0
- package/components/Resource/Detail/PercentageBar.vue +40 -0
- package/components/Resource/Detail/ResourceRow.vue +119 -0
- package/components/Resource/Detail/SpacedRow.vue +14 -0
- package/components/Resource/Detail/StatusBar.vue +59 -0
- package/components/Resource/Detail/StatusRow.vue +61 -0
- package/components/Resource/Detail/TitleBar/Title.vue +13 -0
- package/components/Resource/Detail/TitleBar/Top.vue +14 -0
- package/components/Resource/Detail/TitleBar/__tests__/Title.test.ts +17 -0
- package/components/Resource/Detail/TitleBar/__tests__/Top.test.ts +17 -0
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +142 -0
- package/components/Resource/Detail/TitleBar/composable.ts +31 -0
- package/components/Resource/Detail/TitleBar/index.vue +124 -0
- package/components/Resource/Detail/Top/index.vue +34 -0
- package/components/Resource/Detail/__tests__/Page.test.ts +32 -0
- package/components/ResourceDetail/__tests__/index.test.ts +114 -0
- package/components/ResourceDetail/index.vue +64 -562
- package/components/ResourceDetail/legacy.vue +545 -0
- package/components/ResourceTable.vue +41 -7
- package/components/SlideInPanelManager.vue +76 -8
- package/components/SortableTable/index.vue +13 -2
- package/components/SortableTable/selection.js +21 -8
- package/components/StatusBadge.vue +6 -4
- package/components/SubtleLink.vue +25 -0
- package/components/Wizard.vue +12 -1
- package/components/YamlEditor.vue +1 -1
- package/components/__tests__/FilterPanel.test.ts +81 -0
- package/components/auth/AuthBanner.vue +2 -3
- package/components/auth/RoleDetailEdit.vue +45 -3
- package/components/auth/login/oidc.vue +6 -1
- package/components/fleet/FleetApplications.vue +181 -0
- package/components/fleet/FleetHelmOps.vue +115 -0
- package/components/fleet/FleetIntro.vue +58 -28
- package/components/fleet/FleetNoWorkspaces.vue +5 -1
- package/components/fleet/FleetOCIStorageSecret.vue +171 -0
- package/components/fleet/FleetRepos.vue +38 -76
- package/components/fleet/FleetResources.vue +50 -22
- package/components/fleet/FleetSummary.vue +26 -51
- package/components/fleet/__tests__/FleetOCIStorageSecret.test.ts +213 -0
- package/components/fleet/__tests__/FleetSummary.test.ts +39 -39
- package/components/fleet/dashboard/Empty.vue +73 -0
- package/components/fleet/dashboard/ResourceCard.vue +183 -0
- package/components/fleet/dashboard/ResourceCardSummary.vue +199 -0
- package/components/fleet/dashboard/ResourceDetails.vue +196 -0
- package/components/fleet/dashboard/ResourcePanel.vue +376 -0
- package/components/form/ArrayList.vue +6 -0
- package/components/form/SimpleSecretSelector.vue +8 -2
- package/components/form/ValueFromResource.vue +31 -19
- package/components/formatter/FleetApplicationClustersReady.vue +77 -0
- package/components/formatter/FleetApplicationSource.vue +71 -0
- package/components/formatter/FleetSummaryGraph.vue +7 -0
- package/components/nav/Header.vue +8 -7
- package/components/nav/TopLevelMenu.helper.ts +55 -34
- package/components/nav/TopLevelMenu.vue +11 -0
- package/components/nav/Type.vue +4 -1
- package/composables/useI18n.ts +12 -11
- package/config/labels-annotations.js +14 -11
- package/config/product/auth.js +1 -0
- package/config/product/fleet.js +70 -17
- package/config/query-params.js +3 -1
- package/config/roles.ts +1 -0
- package/config/router/routes.js +20 -2
- package/config/secret.ts +15 -0
- package/config/settings.ts +3 -2
- package/config/table-headers.js +52 -22
- package/config/types.js +2 -0
- package/core/plugin-helpers.ts +3 -2
- package/detail/fleet.cattle.io.cluster.vue +28 -15
- package/detail/fleet.cattle.io.gitrepo.vue +10 -1
- package/detail/fleet.cattle.io.helmop.vue +157 -0
- package/dialog/HelmOpForceUpdateDialog.vue +132 -0
- package/dialog/RedeployWorkloadDialog.vue +164 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +56 -67
- package/edit/auth/oidc.vue +159 -93
- package/edit/fleet.cattle.io.gitrepo.vue +26 -33
- package/edit/fleet.cattle.io.helmop.vue +997 -0
- package/edit/management.cattle.io.fleetworkspace.vue +43 -10
- package/list/fleet.cattle.io.gitrepo.vue +1 -1
- package/list/fleet.cattle.io.helmop.vue +108 -0
- package/list/namespace.vue +5 -2
- package/mixins/auth-config.js +8 -1
- package/mixins/preset.js +100 -0
- package/mixins/resource-fetch-api-pagination.js +2 -0
- package/mixins/resource-fetch.js +1 -1
- package/mixins/resource-table-watch.js +45 -0
- package/models/__tests__/chart.test.ts +273 -0
- package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
- package/models/chart.js +144 -2
- package/models/fleet-application.js +385 -0
- package/models/fleet.cattle.io.bundle.js +9 -8
- package/models/fleet.cattle.io.gitrepo.js +41 -365
- package/models/fleet.cattle.io.helmop.js +228 -0
- package/models/management.cattle.io.authconfig.js +1 -0
- package/models/management.cattle.io.fleetworkspace.js +12 -0
- package/models/workload.js +14 -18
- package/package.json +2 -1
- package/pages/auth/verify.vue +13 -1
- package/pages/c/_cluster/apps/charts/AddRepoLink.vue +37 -0
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +80 -0
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +54 -0
- package/pages/c/_cluster/apps/charts/StatusLabel.vue +33 -0
- package/pages/c/_cluster/apps/charts/index.vue +302 -484
- package/pages/c/_cluster/explorer/EventsTable.vue +1 -1
- package/pages/c/_cluster/fleet/__tests__/index.test.ts +426 -0
- package/pages/c/_cluster/fleet/application/_resource/_id.vue +14 -0
- package/pages/c/_cluster/fleet/application/_resource/create.vue +14 -0
- package/pages/c/_cluster/fleet/application/create.vue +340 -0
- package/pages/c/_cluster/fleet/application/index.vue +139 -0
- package/pages/c/_cluster/fleet/graph/config.js +277 -0
- package/pages/c/_cluster/fleet/index.vue +772 -330
- package/pages/explorer/resource/detail/configmap.vue +19 -0
- package/plugins/dashboard-store/actions.js +31 -9
- package/plugins/dashboard-store/getters.js +34 -21
- package/plugins/dashboard-store/mutations.js +51 -7
- package/plugins/dashboard-store/resource-class.js +14 -2
- package/plugins/steve/__tests__/subscribe.spec.ts +66 -1
- package/plugins/steve/actions.js +3 -0
- package/plugins/steve/steve-pagination-utils.ts +14 -13
- package/plugins/steve/subscribe.js +229 -42
- package/rancher-components/BadgeState/BadgeState.vue +3 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +2 -2
- package/rancher-components/RcItemCard/RcItemCard.test.ts +189 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +425 -0
- package/rancher-components/RcItemCard/RcItemCardAction.vue +24 -0
- package/rancher-components/RcItemCard/index.ts +2 -0
- package/store/auth.js +1 -0
- package/store/catalog.js +62 -24
- package/store/index.js +33 -14
- package/store/slideInPanel.ts +6 -0
- package/store/type-map.js +1 -0
- package/types/fleet.d.ts +35 -0
- package/types/resources/settings.d.ts +19 -1
- package/types/shell/index.d.ts +339 -272
- package/types/store/dashboard-store.types.ts +17 -3
- package/types/store/pagination.types.ts +6 -1
- package/types/store/subscribe.types.ts +50 -0
- package/utils/auth.js +32 -3
- package/utils/fleet-types.ts +0 -0
- package/utils/fleet.ts +200 -1
- package/utils/pagination-utils.ts +26 -1
- package/utils/pagination-wrapper.ts +132 -50
- package/utils/settings.ts +4 -1
- package/utils/style.ts +39 -0
- package/utils/validators/formRules/__tests__/index.test.ts +36 -3
- package/utils/validators/formRules/index.ts +10 -3
- package/utils/window.js +11 -7
- package/components/__tests__/ApplicationCard.test.ts +0 -27
- package/components/cards/ApplicationCard.vue +0 -145
- package/components/fleet/ForceDirectedTreeChart/chartIcons.js +0 -17
- package/config/secret.js +0 -14
- package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +0 -249
- /package/{components/form/SSHKnownHosts → dialog}/__tests__/KnownHostsEditDialog.test.ts +0 -0
|
@@ -34,6 +34,8 @@ import { waitFor } from '@shell/utils/async';
|
|
|
34
34
|
import { WORKER_MODES } from './worker';
|
|
35
35
|
import acceptOrRejectSocketMessage from './accept-or-reject-socket-message';
|
|
36
36
|
import { BLANK_CLUSTER, STORE } from '@shell/store/store-types.js';
|
|
37
|
+
import { _MERGE } from '@shell/plugins/dashboard-store/actions';
|
|
38
|
+
import { STEVE_WATCH_EVENT, STEVE_WATCH_MODE } from '@shell/types/store/subscribe.types';
|
|
37
39
|
import paginationUtils from '@shell/utils/pagination-utils';
|
|
38
40
|
|
|
39
41
|
// minimum length of time a disconnect notification is shown
|
|
@@ -185,10 +187,14 @@ export async function createWorker(store, ctx) {
|
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
export function equivalentWatch(a, b) {
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
+
const aResourceType = a.resourceType || a.type;
|
|
191
|
+
const bResourceType = b.resourceType || b.type;
|
|
190
192
|
|
|
191
|
-
if (
|
|
193
|
+
if ( aResourceType !== bResourceType ) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (a.mode !== b.mode && (a.mode || b.mode)) {
|
|
192
198
|
return false;
|
|
193
199
|
}
|
|
194
200
|
|
|
@@ -256,6 +262,13 @@ function growlsDisabled(rootGetters) {
|
|
|
256
262
|
return getPerformanceSetting(rootGetters)?.disableWebsocketNotification;
|
|
257
263
|
}
|
|
258
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Supported events are listed
|
|
267
|
+
*
|
|
268
|
+
* of type { [key: STEVE_WATCH_EVENT]: STEVE_WATCH_EVENT_LISTENER[]}
|
|
269
|
+
*/
|
|
270
|
+
const listeners = { [STEVE_WATCH_EVENT.CHANGES]: [] };
|
|
271
|
+
|
|
259
272
|
/**
|
|
260
273
|
* Actions that cover all cases (see file description)
|
|
261
274
|
*/
|
|
@@ -354,14 +367,80 @@ const sharedActions = {
|
|
|
354
367
|
return Promise.all(cleanupTasks);
|
|
355
368
|
},
|
|
356
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Create a trigger for a specific type of watch event
|
|
372
|
+
*
|
|
373
|
+
* For example if a watch on mgmt clusters exists and a page wants to know when any changes occur
|
|
374
|
+
* @param {} ctx
|
|
375
|
+
* @param {STEVE_WATCH_EVENT_PARAMS} event
|
|
376
|
+
*/
|
|
377
|
+
watchEvent(ctx, {
|
|
378
|
+
event = STEVE_WATCH_EVENT.CHANGES,
|
|
379
|
+
id,
|
|
380
|
+
callback,
|
|
381
|
+
/**
|
|
382
|
+
* of type @STEVE_WATCH_PARAMS
|
|
383
|
+
*/
|
|
384
|
+
params
|
|
385
|
+
}) {
|
|
386
|
+
if (!listeners[event]) {
|
|
387
|
+
console.error(`Unknown event type "${ event }", only ${ Object.keys(listeners).join(',') } are supported`); // eslint-disable-line no-console
|
|
388
|
+
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// STEVE_WATCH_EVENT_LISTENER | undefined
|
|
393
|
+
let listener = listeners[event].find((l) => equivalentWatch(l.params, params));
|
|
394
|
+
|
|
395
|
+
if (!listener) {
|
|
396
|
+
listener = {
|
|
397
|
+
params,
|
|
398
|
+
callbacks: { }
|
|
399
|
+
};
|
|
400
|
+
listeners[event].push(listener);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (!listener.callbacks[id]) {
|
|
404
|
+
listener.callbacks[id] = callback;
|
|
405
|
+
ctx.dispatch('watch', params);
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* @param {} ctx
|
|
411
|
+
* @param {STEVE_UNWATCH_EVENT_PARAMS} event
|
|
412
|
+
*/
|
|
413
|
+
unwatchEvent(ctx, {
|
|
414
|
+
event = STEVE_WATCH_EVENT.CHANGES,
|
|
415
|
+
id,
|
|
416
|
+
/**
|
|
417
|
+
* of type @STEVE_WATCH_PARAMS
|
|
418
|
+
*/
|
|
419
|
+
params
|
|
420
|
+
}) {
|
|
421
|
+
if (!listeners[event]) {
|
|
422
|
+
console.info(`Attempted to unwatch for an event "${ event }" but it had no watchers`); // eslint-disable-line no-console
|
|
423
|
+
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const existing = listeners[event].find((l) => equivalentWatch(l.params, params));
|
|
428
|
+
|
|
429
|
+
if (existing) {
|
|
430
|
+
delete existing.callbacks[id];
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* @param {STEVE_WATCH_PARAMS} params
|
|
436
|
+
*/
|
|
357
437
|
watch({
|
|
358
438
|
state, dispatch, getters, rootGetters
|
|
359
439
|
}, params) {
|
|
360
440
|
state.debugSocket && console.info(`Watch Request [${ getters.storeName }]`, JSON.stringify(params)); // eslint-disable-line no-console
|
|
361
|
-
|
|
362
441
|
let {
|
|
363
442
|
// eslint-disable-next-line prefer-const
|
|
364
|
-
type, selector, id, revision, namespace, stop, force
|
|
443
|
+
type, selector, id, revision, namespace, stop, force, mode
|
|
365
444
|
} = params;
|
|
366
445
|
|
|
367
446
|
namespace = acceptOrRejectSocketMessage.subscribeNamespace(namespace);
|
|
@@ -393,29 +472,43 @@ const sharedActions = {
|
|
|
393
472
|
return;
|
|
394
473
|
}
|
|
395
474
|
|
|
396
|
-
|
|
397
|
-
type, id, selector, namespace
|
|
398
|
-
}
|
|
475
|
+
const messageMeta = {
|
|
476
|
+
type, id, selector, namespace, mode
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
if (!stop && getters.watchStarted(messageMeta)) {
|
|
399
480
|
// eslint-disable-next-line no-console
|
|
400
481
|
state.debugSocket && console.debug(`Already Watching [${ getters.storeName }]`, {
|
|
401
|
-
type, id, selector, namespace
|
|
482
|
+
type, id, selector, namespace, mode
|
|
402
483
|
});
|
|
403
484
|
|
|
404
485
|
return;
|
|
405
486
|
}
|
|
406
487
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
488
|
+
if (!stop) {
|
|
489
|
+
dispatch('unwatchIncompatible', messageMeta);
|
|
490
|
+
}
|
|
491
|
+
|
|
411
492
|
// Watch errors mean we make a http request to get latest revision (which is still missing) and try to re-watch with it...
|
|
412
493
|
// etc
|
|
413
|
-
if (typeof revision === 'undefined'
|
|
494
|
+
if (typeof revision === 'undefined') {
|
|
414
495
|
revision = getters.nextResourceVersion(type, id);
|
|
415
496
|
}
|
|
416
497
|
|
|
417
498
|
const msg = { resourceType: type };
|
|
418
499
|
|
|
500
|
+
if (mode) {
|
|
501
|
+
msg.mode = mode;
|
|
502
|
+
|
|
503
|
+
if (mode === STEVE_WATCH_MODE.RESOURCE_CHANGES) {
|
|
504
|
+
const debounceMs = paginationUtils.resourceChangesDebounceMs({ rootGetters });
|
|
505
|
+
|
|
506
|
+
if (debounceMs) {
|
|
507
|
+
msg.debounceMs = debounceMs;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
419
512
|
if ( revision ) {
|
|
420
513
|
msg.resourceVersion = `${ revision }`;
|
|
421
514
|
}
|
|
@@ -452,7 +545,7 @@ const sharedActions = {
|
|
|
452
545
|
},
|
|
453
546
|
|
|
454
547
|
unwatch(ctx, {
|
|
455
|
-
type, id, namespace, selector, all
|
|
548
|
+
type, id, namespace, selector, all, mode
|
|
456
549
|
}) {
|
|
457
550
|
const { commit, getters, dispatch } = ctx;
|
|
458
551
|
|
|
@@ -464,6 +557,7 @@ const sharedActions = {
|
|
|
464
557
|
id,
|
|
465
558
|
namespace,
|
|
466
559
|
selector,
|
|
560
|
+
mode,
|
|
467
561
|
stop: true, // Stops the watch on a type
|
|
468
562
|
};
|
|
469
563
|
|
|
@@ -491,6 +585,24 @@ const sharedActions = {
|
|
|
491
585
|
}
|
|
492
586
|
},
|
|
493
587
|
|
|
588
|
+
/**
|
|
589
|
+
* Unwatch watches that are incompatible with the new type
|
|
590
|
+
*/
|
|
591
|
+
unwatchIncompatible({ state, dispatch, getters }, messageMeta) {
|
|
592
|
+
const watchesOfType = getters.watchesOfType(messageMeta.type);
|
|
593
|
+
let unwatch = [];
|
|
594
|
+
|
|
595
|
+
if (messageMeta.mode === STEVE_WATCH_EVENT.CHANGES) {
|
|
596
|
+
// resource.changes should not be running when other types are, so unwatch
|
|
597
|
+
unwatch = watchesOfType.filter((entry) => entry.mode !== STEVE_WATCH_EVENT.CHANGES);
|
|
598
|
+
} else {
|
|
599
|
+
// all other modes of watches should not be running when resource.changes is, so unwatch
|
|
600
|
+
unwatch = watchesOfType.filter((entry) => entry.mode === STEVE_WATCH_EVENT.CHANGES);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
unwatch.forEach((entry) => dispatch('unwatch', entry));
|
|
604
|
+
},
|
|
605
|
+
|
|
494
606
|
'ws.ping'({ getters, dispatch }, msg) {
|
|
495
607
|
if ( getters.storeName === 'management' ) {
|
|
496
608
|
const version = msg?.data?.version || null;
|
|
@@ -587,16 +699,30 @@ const defaultActions = {
|
|
|
587
699
|
return Promise.all(promises);
|
|
588
700
|
},
|
|
589
701
|
|
|
590
|
-
|
|
702
|
+
/**
|
|
703
|
+
* Socket has been closed, restart afresh (make http request, ensure we re-watch)
|
|
704
|
+
*/
|
|
705
|
+
async resyncWatch({ getters, dispatch }, params) {
|
|
706
|
+
console.info(`Resync [${ getters.storeName }]`, params); // eslint-disable-line no-console
|
|
707
|
+
|
|
708
|
+
await dispatch('fetchResources', {
|
|
709
|
+
...params,
|
|
710
|
+
opt: { force: true, forceWatch: true }
|
|
711
|
+
});
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
async fetchResources({
|
|
591
715
|
state, getters, dispatch, commit
|
|
592
|
-
}, params) {
|
|
716
|
+
}, { opt, ...params }) {
|
|
593
717
|
const {
|
|
594
|
-
resourceType, namespace, id, selector
|
|
718
|
+
resourceType, namespace, id, selector, mode
|
|
595
719
|
} = params;
|
|
596
720
|
|
|
597
|
-
|
|
721
|
+
if (!resourceType) {
|
|
722
|
+
console.error(`A socket message has prompted a request to fetch a resource but no resource type was supplied`); // eslint-disable-line no-console
|
|
598
723
|
|
|
599
|
-
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
600
726
|
|
|
601
727
|
if ( id ) {
|
|
602
728
|
await dispatch('find', {
|
|
@@ -613,7 +739,7 @@ const defaultActions = {
|
|
|
613
739
|
|
|
614
740
|
return;
|
|
615
741
|
}
|
|
616
|
-
let have
|
|
742
|
+
let have = []; let want = [];
|
|
617
743
|
|
|
618
744
|
if ( selector ) {
|
|
619
745
|
have = getters['matching'](resourceType, selector).slice();
|
|
@@ -623,17 +749,48 @@ const defaultActions = {
|
|
|
623
749
|
opt,
|
|
624
750
|
});
|
|
625
751
|
} else {
|
|
626
|
-
|
|
752
|
+
if (mode === STEVE_WATCH_MODE.RESOURCE_CHANGES) {
|
|
753
|
+
// Other findX use options (id/ns/selector) from the messages received over socket.
|
|
754
|
+
// However paginated requests have more complex params so grab them from store from the store.
|
|
755
|
+
const storePagination = getters['havePage'](resourceType);
|
|
756
|
+
|
|
757
|
+
if (!!storePagination) {
|
|
758
|
+
have = []; // findPage removes stale entries, so we don't need to rely on below process to remove them
|
|
759
|
+
|
|
760
|
+
// This could have been kicked off given a resource.changes message
|
|
761
|
+
// If the messages come in quicker than findPage completes (resource.changes debounce time >= http request time),
|
|
762
|
+
// and the request is the same, only the first request will be processed. all others until it finishes will be ignored
|
|
763
|
+
// (see deferred process - `waiting.push(later);` - in request action).
|
|
764
|
+
// If this becomes an issue we need to debounce and work around the deferred issue within request
|
|
765
|
+
want = await dispatch('findPage', {
|
|
766
|
+
type: resourceType,
|
|
767
|
+
opt: {
|
|
768
|
+
...opt,
|
|
769
|
+
// This brings in page, page size, filter, etc
|
|
770
|
+
...storePagination.request
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
627
774
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
775
|
+
// Should any listeners be notified of this request for them to kick off their own event handling?
|
|
776
|
+
const listener = listeners[STEVE_WATCH_MODE.RESOURCE_CHANGES].find((sl) => equivalentWatch(sl.params, params));
|
|
631
777
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
778
|
+
if (listener) {
|
|
779
|
+
Object.values(listener.callbacks).forEach((cb) => cb());
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
have = getters['all'](resourceType).slice();
|
|
783
|
+
|
|
784
|
+
if ( namespace ) {
|
|
785
|
+
have = have.filter((x) => x.metadata?.namespace === namespace);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
want = await dispatch('findAll', {
|
|
789
|
+
type: resourceType,
|
|
790
|
+
watchNamespace: namespace,
|
|
791
|
+
opt
|
|
792
|
+
});
|
|
793
|
+
}
|
|
637
794
|
}
|
|
638
795
|
|
|
639
796
|
const wantMap = {};
|
|
@@ -792,7 +949,8 @@ const defaultActions = {
|
|
|
792
949
|
type: msg.resourceType,
|
|
793
950
|
namespace: msg.namespace,
|
|
794
951
|
id: msg.id,
|
|
795
|
-
selector: msg.selector
|
|
952
|
+
selector: msg.selector,
|
|
953
|
+
mode: msg.mode,
|
|
796
954
|
};
|
|
797
955
|
|
|
798
956
|
state.started.filter((entry) => {
|
|
@@ -846,7 +1004,8 @@ const defaultActions = {
|
|
|
846
1004
|
type,
|
|
847
1005
|
id: msg.id,
|
|
848
1006
|
namespace: msg.namespace,
|
|
849
|
-
selector: msg.selector
|
|
1007
|
+
selector: msg.selector,
|
|
1008
|
+
mode: msg.mode
|
|
850
1009
|
};
|
|
851
1010
|
|
|
852
1011
|
state.debugSocket && console.info(`Resource Stop [${ getters.storeName }]`, type, msg); // eslint-disable-line no-console
|
|
@@ -922,6 +1081,13 @@ const defaultActions = {
|
|
|
922
1081
|
}
|
|
923
1082
|
},
|
|
924
1083
|
|
|
1084
|
+
'ws.resource.changes'({ dispatch }, msg) {
|
|
1085
|
+
dispatch('fetchResources', {
|
|
1086
|
+
...msg,
|
|
1087
|
+
opt: { force: true, load: _MERGE }
|
|
1088
|
+
} );
|
|
1089
|
+
},
|
|
1090
|
+
|
|
925
1091
|
'ws.resource.remove'(ctx, msg) {
|
|
926
1092
|
const data = msg.data;
|
|
927
1093
|
const type = data.type;
|
|
@@ -1040,9 +1206,24 @@ const defaultGetters = {
|
|
|
1040
1206
|
},
|
|
1041
1207
|
|
|
1042
1208
|
watchStarted: (state) => (obj) => {
|
|
1043
|
-
|
|
1209
|
+
const existing = state.started.find((entry) => equivalentWatch(obj, entry));
|
|
1210
|
+
|
|
1211
|
+
return !!existing;
|
|
1044
1212
|
},
|
|
1045
1213
|
|
|
1214
|
+
/**
|
|
1215
|
+
* Try to determine the latest revision to use in a watch request.
|
|
1216
|
+
*
|
|
1217
|
+
* It does some dodgy revision comparisons (revisions are not guaranteed to be numerical or equate higher to newer)
|
|
1218
|
+
*
|
|
1219
|
+
* If we have an id - and that resource has a revision - use it
|
|
1220
|
+
* If we have a list - and the store has a revision - and it's a string - use it straight away
|
|
1221
|
+
* If we have a list - and the store has a revision - and it's a number - compare it to the revisions in the list and use overall highest
|
|
1222
|
+
*
|
|
1223
|
+
* Note - This used to use parseInt which does stuff like `abc-123` --> NaN, `123-abc` --> 123
|
|
1224
|
+
*
|
|
1225
|
+
* Returns string, non-zero number or null
|
|
1226
|
+
*/
|
|
1046
1227
|
nextResourceVersion: (state, getters) => (type, id) => {
|
|
1047
1228
|
type = normalizeType(type);
|
|
1048
1229
|
let revision = 0;
|
|
@@ -1050,32 +1231,38 @@ const defaultGetters = {
|
|
|
1050
1231
|
if ( id ) {
|
|
1051
1232
|
const existing = getters['byId'](type, id);
|
|
1052
1233
|
|
|
1053
|
-
revision =
|
|
1234
|
+
revision = existing?.metadata?.resourceVersion;
|
|
1054
1235
|
}
|
|
1055
1236
|
|
|
1056
1237
|
if ( !revision ) {
|
|
1057
1238
|
const cache = state.types[type];
|
|
1058
1239
|
|
|
1240
|
+
// No Cache, nothing to compare to, return early
|
|
1059
1241
|
if ( !cache ) {
|
|
1060
1242
|
return null;
|
|
1061
1243
|
}
|
|
1062
1244
|
|
|
1063
|
-
revision = cache.revision;
|
|
1245
|
+
revision = Number(cache.revision);
|
|
1246
|
+
|
|
1247
|
+
// Cached LIST revision isn't a number, cannot compare to, return early
|
|
1248
|
+
if (Number.isNaN(revision)) {
|
|
1249
|
+
return cache.revision || null;
|
|
1250
|
+
}
|
|
1064
1251
|
|
|
1065
|
-
for ( const obj of cache.list ) {
|
|
1252
|
+
for ( const obj of cache.list || [] ) {
|
|
1066
1253
|
if ( obj && obj.metadata ) {
|
|
1067
|
-
const neu =
|
|
1254
|
+
const neu = Number(obj.metadata.resourceVersion);
|
|
1255
|
+
|
|
1256
|
+
if (Number.isNaN(neu)) {
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1068
1259
|
|
|
1069
1260
|
revision = Math.max(revision, neu);
|
|
1070
1261
|
}
|
|
1071
1262
|
}
|
|
1072
1263
|
}
|
|
1073
1264
|
|
|
1074
|
-
|
|
1075
|
-
return revision;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
return null;
|
|
1265
|
+
return revision || null;
|
|
1079
1266
|
},
|
|
1080
1267
|
};
|
|
1081
1268
|
|
|
@@ -13,7 +13,7 @@ export default defineComponent({
|
|
|
13
13
|
* The checkbox value.
|
|
14
14
|
*/
|
|
15
15
|
value: {
|
|
16
|
-
type: [Boolean, Array, String] as PropType<boolean | boolean[] | string>,
|
|
16
|
+
type: [Boolean, Array, String] as PropType<boolean | boolean[] | string | string[]>,
|
|
17
17
|
default: false
|
|
18
18
|
},
|
|
19
19
|
|
|
@@ -245,7 +245,7 @@ export default defineComponent({
|
|
|
245
245
|
/**
|
|
246
246
|
* Determines if there are multiple values for the checkbox.
|
|
247
247
|
*/
|
|
248
|
-
isMulti(value: boolean | boolean[] | string): value is boolean[] {
|
|
248
|
+
isMulti(value: boolean | boolean[] | string | string[]): value is boolean[] {
|
|
249
249
|
return Array.isArray(value);
|
|
250
250
|
},
|
|
251
251
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import RcItemCard from './RcItemCard.vue';
|
|
3
|
+
import RcItemCardAction from './RcItemCardAction.vue';
|
|
4
|
+
|
|
5
|
+
class ResizeObserverMock {
|
|
6
|
+
observe = jest.fn();
|
|
7
|
+
unobserve = jest.fn();
|
|
8
|
+
disconnect = jest.fn();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
global.ResizeObserver = ResizeObserverMock;
|
|
12
|
+
|
|
13
|
+
const id = 'test';
|
|
14
|
+
|
|
15
|
+
const baseProps = {
|
|
16
|
+
id,
|
|
17
|
+
value: { someProperty: 'some-value' },
|
|
18
|
+
image: { src: 'logo.png', alt: { text: 'Logo' } },
|
|
19
|
+
header: {
|
|
20
|
+
title: { text: 'Card Title' },
|
|
21
|
+
statuses: [
|
|
22
|
+
{ icon: 'icon-one', tooltip: { text: 'Status One' } },
|
|
23
|
+
{ icon: 'icon-two' }
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
content: { text: 'Card description here' }
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe('rcItemCard', () => {
|
|
30
|
+
it('renders title, image, and content', () => {
|
|
31
|
+
const wrapper = mount(RcItemCard, { props: baseProps });
|
|
32
|
+
|
|
33
|
+
expect(wrapper.get('[data-testid="item-card-header-title"]').text()).toBe('Card Title');
|
|
34
|
+
expect(wrapper.get('[data-testid="item-card-content"]').text()).toContain('Card description here');
|
|
35
|
+
expect(wrapper.get('[data-testid="item-card-image"]')).toBeTruthy();
|
|
36
|
+
expect(wrapper.findAll(`[data-testid="item-card-header-statuses-status"]`)).toHaveLength(2);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders pill only in medium variant', () => {
|
|
40
|
+
const wrapper = mount(RcItemCard, {
|
|
41
|
+
props: {
|
|
42
|
+
...baseProps,
|
|
43
|
+
variant: 'medium',
|
|
44
|
+
pill: { label: { text: 'Installed' } }
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(wrapper.get('[data-testid="item-card-pill"]').text()).toBe('Installed');
|
|
49
|
+
|
|
50
|
+
// now test that it's not rendered when variant is small
|
|
51
|
+
const wrapperSmall = mount(RcItemCard, {
|
|
52
|
+
props: {
|
|
53
|
+
...baseProps,
|
|
54
|
+
variant: 'small',
|
|
55
|
+
pill: { label: { text: 'Installed' } }
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(wrapperSmall.find('[data-testid="item-card-pill"]').exists()).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('renders action-menu if slot content is provided for it', () => {
|
|
63
|
+
const wrapper = mount(RcItemCard, {
|
|
64
|
+
props: { ...baseProps },
|
|
65
|
+
slots: { 'item-card-actions': '<div class="test-slot-for-actions">test</div>' }
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(wrapper.find('.test-slot-for-actions').exists()).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('renders action-menu when actions are passed as a prop', () => {
|
|
72
|
+
const wrapper = mount(RcItemCard, {
|
|
73
|
+
props: {
|
|
74
|
+
...baseProps,
|
|
75
|
+
actions: [{ action: 'test', label: 'test' }]
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(wrapper.findComponent('[data-testid="item-card-header-action-menu"]').exists()).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('does not render action-menu if no slot and no actions', () => {
|
|
83
|
+
const wrapper = mount(RcItemCard, { props: { ...baseProps } });
|
|
84
|
+
|
|
85
|
+
expect(wrapper.findComponent('[data-testid="item-card-header-action-menu"]').exists()).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('emits card-click when clicked and clickable', async() => {
|
|
89
|
+
const wrapper = mount(RcItemCard, {
|
|
90
|
+
props: {
|
|
91
|
+
...baseProps,
|
|
92
|
+
clickable: true
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await wrapper.trigger('click');
|
|
97
|
+
|
|
98
|
+
const emitted = wrapper.emitted('card-click');
|
|
99
|
+
|
|
100
|
+
expect(emitted).toBeTruthy();
|
|
101
|
+
expect(emitted?.[0]).toStrictEqual([{ someProperty: 'some-value' }]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('does not emit card-click when clicking on rc-item-card-action content', async() => {
|
|
105
|
+
const wrapper = mount(RcItemCard, {
|
|
106
|
+
props: {
|
|
107
|
+
...baseProps,
|
|
108
|
+
clickable: true
|
|
109
|
+
},
|
|
110
|
+
global: { components: { RcItemCardAction } },
|
|
111
|
+
slots: { 'item-card-actions': '<rc-item-card-action>Click me</rc-item-card-action>' }
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await wrapper.get('[data-testid="rc-item-card-action"]').trigger('click');
|
|
115
|
+
|
|
116
|
+
expect(wrapper.emitted('card-click')).toBeFalsy();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('sets role and tabindex when clickable', () => {
|
|
120
|
+
const wrapper = mount(RcItemCard, {
|
|
121
|
+
props: {
|
|
122
|
+
...baseProps,
|
|
123
|
+
clickable: true
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const root = wrapper.get(`[data-testid="item-card-${ id }"]`);
|
|
128
|
+
|
|
129
|
+
expect(root.attributes('role')).toBe('button');
|
|
130
|
+
expect(root.attributes('tabindex')).toBe('0');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('does not set role or tabindex when not clickable', () => {
|
|
134
|
+
const wrapper = mount(RcItemCard, {
|
|
135
|
+
props: {
|
|
136
|
+
...baseProps,
|
|
137
|
+
clickable: false
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const root = wrapper.get(`[data-testid="item-card-${ id }"]`);
|
|
142
|
+
|
|
143
|
+
expect(root.attributes('role')).toBeUndefined();
|
|
144
|
+
expect(root.attributes('tabindex')).toBeUndefined();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('supports keyboard enter to trigger click', async() => {
|
|
148
|
+
const wrapper = mount(RcItemCard, {
|
|
149
|
+
props: {
|
|
150
|
+
...baseProps,
|
|
151
|
+
clickable: true
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await wrapper.trigger('keydown.enter');
|
|
156
|
+
expect(wrapper.emitted('card-click')).toBeTruthy();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('supports slot for footer and sub-header', () => {
|
|
160
|
+
const wrapper = mount(RcItemCard, {
|
|
161
|
+
props: baseProps,
|
|
162
|
+
slots: {
|
|
163
|
+
'item-card-footer': '<div>FooterContent</div>',
|
|
164
|
+
'item-card-sub-header': '<div>SubHeaderContent</div>'
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(wrapper.text()).toContain('FooterContent');
|
|
169
|
+
expect(wrapper.text()).toContain('SubHeaderContent');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('renders icon with custom color', () => {
|
|
173
|
+
const wrapper = mount(RcItemCard, {
|
|
174
|
+
props: {
|
|
175
|
+
...baseProps,
|
|
176
|
+
header: {
|
|
177
|
+
...baseProps.header,
|
|
178
|
+
statuses: [
|
|
179
|
+
{ icon: 'icon-custom', customColor: 'red' }
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const icon = wrapper.get('[data-testid="item-card-header-status-0"]');
|
|
186
|
+
|
|
187
|
+
expect(icon.attributes('style')).toContain('color: red');
|
|
188
|
+
});
|
|
189
|
+
});
|