@rancher/shell 3.0.9-rc.1 → 3.0.9-rc.2
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/styles/base/_color.scss +1 -0
- package/assets/styles/base/_typography.scss +14 -5
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/styles/themes/_modern.scss +1 -1
- package/assets/translations/en-us.yaml +94 -33
- package/assets/translations/zh-hans.yaml +0 -2
- package/components/ActionMenuShell.vue +4 -4
- package/components/CodeMirror.vue +4 -3
- package/components/DetailText.vue +54 -7
- package/components/Drawer/Chrome.vue +11 -4
- package/components/Drawer/DrawerCard.vue +19 -0
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
- package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
- package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
- package/components/Drawer/types.ts +1 -0
- package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
- package/components/LocaleSelector.vue +1 -1
- package/components/Markdown.vue +1 -1
- package/components/PopoverCard.vue +3 -3
- package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
- package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
- package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
- package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
- package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +11 -10
- package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
- package/components/Resource/Detail/Cards.vue +27 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
- package/components/Resource/Detail/Masthead/index.vue +5 -0
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
- package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
- package/components/Resource/Detail/ResourceRow.types.ts +14 -0
- package/components/Resource/Detail/ResourceRow.vue +23 -35
- package/components/Resource/Detail/StatusRow.vue +5 -2
- package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
- package/components/Resource/Detail/TitleBar/composables.ts +2 -1
- package/components/Resource/Detail/TitleBar/index.vue +41 -6
- package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
- package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
- package/components/ResourceDetail/Masthead/index.vue +1 -0
- package/components/ResourceDetail/Masthead/latest.vue +8 -1
- package/components/ResourceDetail/Masthead/legacy.vue +1 -1
- package/components/Setting.vue +1 -1
- package/components/SortableTable/index.vue +25 -0
- package/components/SortableTable/selection.js +25 -12
- package/components/SortableTable/sorting.js +1 -1
- package/components/Tabbed/Tab.vue +1 -0
- package/components/Tabbed/index.vue +29 -6
- package/components/Window/ContainerShell.vue +10 -13
- package/components/fleet/FleetClusterTargets/TargetsList.vue +47 -29
- package/components/fleet/FleetClusterTargets/index.vue +82 -29
- package/components/fleet/FleetClusters.vue +26 -12
- package/components/fleet/FleetGitRepoPaths.vue +2 -2
- package/components/fleet/FleetResources.vue +14 -0
- package/components/fleet/FleetValuesFrom.vue +2 -2
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +531 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +576 -0
- package/components/fleet/dashboard/ResourceDetails.vue +96 -123
- package/components/form/Conditions.vue +1 -15
- package/components/form/HookOption.vue +5 -0
- package/components/form/LabeledSelect.vue +1 -1
- package/components/form/LifecycleHooks.vue +2 -6
- package/components/form/ResourceLabeledSelect.vue +12 -1
- package/components/form/SeccompProfile.vue +113 -0
- package/components/form/Security.vue +244 -133
- package/components/form/__tests__/LabeledSelect.test.ts +1 -1
- package/components/form/__tests__/SeccompProfile.test.js +124 -0
- package/components/form/__tests__/Security.test.ts +125 -37
- package/components/formatter/Autoscaler.vue +2 -2
- package/components/formatter/FleetSummaryGraph.vue +4 -1
- package/components/nav/Group.vue +5 -0
- package/components/nav/Header.vue +3 -3
- package/components/nav/HeaderPageActionMenu.vue +1 -1
- package/components/nav/NamespaceFilter.vue +6 -6
- package/components/nav/NotificationCenter/index.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +41 -16
- package/components/nav/TopLevelMenu.vue +45 -25
- package/components/nav/WorkspaceSwitcher.vue +1 -1
- package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
- package/components/templates/default.vue +0 -3
- package/components/templates/home.vue +0 -3
- package/components/templates/plain.vue +0 -3
- package/composables/useClickOutside.ts +1 -1
- package/config/product/explorer.js +1 -2
- package/config/types.js +41 -8
- package/detail/__tests__/workload.test.ts +8 -16
- package/detail/catalog.cattle.io.app.vue +5 -0
- package/detail/fleet.cattle.io.cluster.vue +6 -0
- package/detail/workload/index.vue +7 -109
- package/edit/__tests__/projectsecret.test.ts +42 -0
- package/edit/auth/__tests__/oidc.test.ts +50 -0
- package/edit/auth/oidc.vue +68 -44
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
- package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
- package/edit/projectsecret.vue +29 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
- package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
- package/edit/workload/__tests__/index.test.ts +3 -4
- package/edit/workload/index.vue +47 -28
- package/edit/workload/mixins/workload.js +66 -31
- package/list/catalog.cattle.io.clusterrepo.vue +1 -1
- package/list/projectsecret.vue +2 -2
- package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
- package/machine-config/amazonec2.vue +2 -2
- package/machine-config/vmwarevsphere.vue +58 -4
- package/mixins/__tests__/chart.test.ts +63 -0
- package/mixins/chart.js +56 -51
- package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
- package/models/__tests__/workload.test.ts +333 -0
- package/models/catalog.cattle.io.app.js +8 -0
- package/models/pod.js +14 -0
- package/models/secret.js +1 -1
- package/models/workload.js +93 -27
- package/package.json +4 -4
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
- package/pages/c/_cluster/apps/charts/install.vue +4 -4
- package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
- package/pages/c/_cluster/fleet/index.vue +14 -8
- package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
- package/pages/c/_cluster/uiplugins/index.vue +1 -1
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
- package/plugins/dashboard-store/actions.js +9 -8
- package/plugins/dashboard-store/resource-class.js +97 -1
- package/plugins/steve/__tests__/revision.test.ts +84 -0
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
- package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
- package/plugins/steve/revision.ts +26 -0
- package/plugins/steve/steve-pagination-utils.ts +6 -5
- package/plugins/steve/subscribe.js +188 -49
- package/plugins/subscribe-events.ts +2 -2
- package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -1
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
- package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
- package/rancher-components/Pill/index.ts +4 -0
- package/rancher-components/RcButton/RcButton.test.ts +53 -9
- package/rancher-components/RcButton/RcButton.vue +217 -25
- package/rancher-components/RcButton/types.ts +27 -1
- package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
- package/rancher-components/RcDropdown/types.ts +3 -3
- package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
- package/rancher-components/RcIcon/RcIcon.vue +9 -6
- package/rancher-components/RcIcon/types.ts +13 -9
- package/rancher-components/utils/status.test.ts +10 -15
- package/rancher-components/utils/status.ts +5 -6
- package/store/aws.js +18 -12
- package/store/index.js +4 -8
- package/store/type-map.utils.ts +1 -1
- package/types/kube/kube-api.ts +29 -3
- package/types/rancher/steve.api.ts +40 -0
- package/types/shell/index.d.ts +99 -0
- package/types/store/pagination.types.ts +1 -0
- package/types/store/subscribe-events.types.ts +1 -0
- package/utils/__tests__/azure.test.ts +56 -0
- package/utils/__tests__/back-off.test.ts +364 -245
- package/utils/__tests__/error.test.ts +44 -0
- package/utils/__tests__/fleet.test.ts +8 -1
- package/utils/__tests__/pagination-wrapper.test.ts +167 -0
- package/utils/__tests__/version.test.ts +55 -1
- package/utils/azure.js +12 -0
- package/utils/back-off.ts +302 -69
- package/utils/dynamic-content/__tests__/index.test.ts +1 -1
- package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
- package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
- package/utils/dynamic-content/index.ts +1 -6
- package/utils/dynamic-content/new-release.ts +5 -3
- package/utils/dynamic-content/types.d.ts +0 -1
- package/utils/error.js +9 -0
- package/utils/fleet.ts +2 -2
- package/utils/inactivity.ts +2 -3
- package/utils/pagination-wrapper.ts +99 -15
- package/utils/validators/formRules/index.ts +3 -0
- package/utils/version.js +38 -0
- package/components/auth/AzureWarning.vue +0 -77
- /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
- /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper class to handle Steve API revisions comparisons
|
|
3
|
+
*/
|
|
4
|
+
export class SteveRevision {
|
|
5
|
+
public asNumber: number;
|
|
6
|
+
public isNumber: boolean;
|
|
7
|
+
|
|
8
|
+
constructor(public revision: any) {
|
|
9
|
+
this.asNumber = Number(revision);
|
|
10
|
+
this.isNumber = !Number.isNaN(this.asNumber);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Is this provided revision newer than this revision?
|
|
15
|
+
*
|
|
16
|
+
* @param revision
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
isNewerThan(revision: SteveRevision): boolean {
|
|
20
|
+
return SteveRevision.areAllNumbers([this, revision]) && this.asNumber > revision.asNumber;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private static areAllNumbers(revisions: SteveRevision[]): boolean {
|
|
24
|
+
return revisions.every((r) => r.isNumber);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -213,9 +213,9 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
213
213
|
* Match
|
|
214
214
|
* - a-z (case insensitive)
|
|
215
215
|
* - 0-9
|
|
216
|
-
* -
|
|
216
|
+
* - `_`, `.`
|
|
217
217
|
*/
|
|
218
|
-
static VALID_FIELD_VALUE_REGEX = /^[\w
|
|
218
|
+
static VALID_FIELD_VALUE_REGEX = /^[\w.]+$/;
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
221
|
* Filtering with the vai cache supports specific fields
|
|
@@ -596,10 +596,11 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
596
596
|
if ([PaginationFilterEquality.IN, PaginationFilterEquality.NOT_IN].includes(equality)) {
|
|
597
597
|
safeValue = `(${ field.value })`;
|
|
598
598
|
} else {
|
|
599
|
-
const
|
|
599
|
+
const booleanSafeValue = typeof field.value === 'undefined' || field.value === null ? '' : field.value;
|
|
600
|
+
const encodedValue = encodeURIComponent(booleanSafeValue);
|
|
600
601
|
|
|
601
|
-
if (StevePaginationUtils.VALID_FIELD_VALUE_REGEX.test(
|
|
602
|
-
//
|
|
602
|
+
if (StevePaginationUtils.VALID_FIELD_VALUE_REGEX.test(booleanSafeValue)) {
|
|
603
|
+
// All characters safe, send as is
|
|
603
604
|
safeValue = encodedValue;
|
|
604
605
|
} else {
|
|
605
606
|
// Contains protected characters, wrap in quotes to ensure backend doesn't fail
|
|
@@ -20,40 +20,67 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Below are some VERY brief steps for common flows. Some will link together
|
|
22
22
|
*
|
|
23
|
-
* Successfully flow
|
|
23
|
+
* # Successfully flow
|
|
24
|
+
* ## watch - standard mode
|
|
24
25
|
* 1. UI --> Rancher: _watch_ request
|
|
25
26
|
* 2. Rancher --> UI: `resource.start`. UI sets watch as started
|
|
26
27
|
* ...
|
|
27
28
|
* 3. Rancher --> UI: `resource.change` (contains data). UI caches data
|
|
28
29
|
*
|
|
29
|
-
*
|
|
30
|
+
* ## watch - new resource.changes mode
|
|
30
31
|
* 1. UI --> Rancher: _watch_ request
|
|
31
32
|
* 2. Rancher --> UI: `resource.start`. UI sets watch as started
|
|
32
33
|
* ...
|
|
33
34
|
* 3. Rancher --> UI: `resource.changes` (contains no data). UI makes a HTTP request to fetch data
|
|
34
35
|
*
|
|
35
|
-
*
|
|
36
|
+
* ## watch - unwatch
|
|
36
37
|
* 1. UI --> Rancher: _unwatch_ request
|
|
37
38
|
* 2. Rancher --> UI: `resource.stop`. UI sets watch as stopped
|
|
38
39
|
*
|
|
39
|
-
*
|
|
40
|
+
* ## watch - resource.stop received
|
|
40
41
|
* 1. Rancher --> UI: `resource.stop`. UI sets watch as stopped
|
|
41
42
|
* 2. UI --> Rancher: _watch_ request
|
|
42
43
|
*
|
|
43
|
-
*
|
|
44
|
+
* ## watch - socket disconnected
|
|
44
45
|
* 1. Socket closes|disconnects (not sure which)
|
|
45
46
|
* 2. UI: reopens socket
|
|
46
47
|
* 3. UI --> Rancher: _watch_ request (for every started watch)
|
|
47
48
|
*
|
|
48
|
-
* Error Flow
|
|
49
|
+
* # Error Flow
|
|
50
|
+
* ## resource.error
|
|
49
51
|
* 1. UI --> Rancher: _watch_ request
|
|
50
52
|
* 2. Rancher --> UI: `resource.start`. UI sets watch as started
|
|
51
53
|
* 3. Rancher --> UI: `resource.error`. UI sets watch as errored.
|
|
52
54
|
* a) UI: in the event of 'too old' the UI will make a http request to fetch a new revision and re-watch with it. This process is delayed on each call
|
|
53
55
|
* 4. Rancher --> UI: `resource.stop`. UI sets watch as stop (note the resource.stop flow above is avoided given error state)
|
|
54
56
|
*
|
|
57
|
+
* # HA Support for Stale Replicates - https://github.com/rancher/dashboard/issues/14974
|
|
58
|
+
*
|
|
59
|
+
* ## Scenario 1 - handle case where watch request is handled by a stale replica
|
|
60
|
+
* 1. UI --> Rancher: _watch_ request (contains latest revision)
|
|
61
|
+
* 2. Rancher --> UI: `resource.error` (stale replica does not know new revision)
|
|
62
|
+
* 3. Rancher --> UI: `resource.stop` (stale replica cannot provide updates for unknown revision)
|
|
63
|
+
* 4. UI --> Rancher : UI makes a HTTP request to fetch data
|
|
64
|
+
* 5. Loop back to step 1 (if stale again, backoff retry)
|
|
65
|
+
*
|
|
66
|
+
* ## Scenario 2 - handle case where http request is handled by a stale replica (don't fetch stale data)
|
|
67
|
+
* 1. UI --> Rancher: _watch_ request
|
|
68
|
+
* 2. Rancher --> UI: `resource.start`. UI sets watch as started
|
|
69
|
+
* ...
|
|
70
|
+
* 3. Rancher --> UI: `resource.changes` (sent by good replica containing good revision)
|
|
71
|
+
* 4. UI --> Rancher : UI makes a HTTP request to fetch data. Stale Replica handles request, does not know revision, returns error
|
|
72
|
+
* 5. Loop back to step 4 (if errors with stale again, backoff retry)
|
|
73
|
+
*
|
|
74
|
+
* ## Scenario 3 - handle case where update request was sent by stale replica (don't overwrite good data with stale)
|
|
75
|
+
* 1. UI --> Rancher: _watch_ request
|
|
76
|
+
* 2. Rancher --> UI: `resource.start`. UI sets watch as started
|
|
77
|
+
* ...
|
|
78
|
+
* 3. Rancher --> UI: `resource.changes` (sent by stale replica containing stale revision)
|
|
79
|
+
* 4. UI compares stale revision with newer store revision
|
|
80
|
+
* 5. UI does not make new http request, which could be handled by stale replica --> overwrites newer local values
|
|
81
|
+
*
|
|
55
82
|
* Additionally
|
|
56
|
-
* - if we receive resource.stop, unless the watch is in error, we immediately send back a watch
|
|
83
|
+
* - if we receive resource.stop, unless the watch is in error, we immediately send back a watch request to re-start the watch
|
|
57
84
|
* - if the web socket is disconnected (for steve based sockets it happens every 30 mins, or when there are permission changes)
|
|
58
85
|
* the ui will re-connect it and re-watch all previous watches using a best effort revision
|
|
59
86
|
*/
|
|
@@ -68,7 +95,6 @@ import Socket, {
|
|
|
68
95
|
EVENT_CONNECTED,
|
|
69
96
|
EVENT_DISCONNECTED,
|
|
70
97
|
EVENT_MESSAGE,
|
|
71
|
-
// EVENT_FRAME_TIMEOUT,
|
|
72
98
|
EVENT_CONNECT_ERROR,
|
|
73
99
|
EVENT_DISCONNECT_ERROR,
|
|
74
100
|
NO_WATCH,
|
|
@@ -90,6 +116,8 @@ import { STEVE_WATCH_EVENT_TYPES, STEVE_WATCH_MODE } from '@shell/types/store/su
|
|
|
90
116
|
import paginationUtils from '@shell/utils/pagination-utils';
|
|
91
117
|
import backOff from '@shell/utils/back-off';
|
|
92
118
|
import { SteveWatchEventListenerManager } from '@shell/plugins/subscribe-events';
|
|
119
|
+
import { SteveRevision } from '@shell/plugins/steve/revision';
|
|
120
|
+
import { STEVE_RESPONSE_CODE } from '@shell/types/rancher/steve.api';
|
|
93
121
|
|
|
94
122
|
// minimum length of time a disconnect notification is shown
|
|
95
123
|
const MINIMUM_TIME_NOTIFIED = 3000;
|
|
@@ -683,7 +711,7 @@ const sharedActions = {
|
|
|
683
711
|
/**
|
|
684
712
|
* Ensure there's no back-off process waiting to run for
|
|
685
713
|
* - resource.changes fetchResources
|
|
686
|
-
* - resource.error
|
|
714
|
+
* - resource.error resyncWatch
|
|
687
715
|
*/
|
|
688
716
|
resetWatchBackOff({ state, getters, commit }, {
|
|
689
717
|
type, compareWatches, resetInError = true, resetStarted = true
|
|
@@ -820,17 +848,117 @@ const defaultActions = {
|
|
|
820
848
|
async resyncWatch({ getters, dispatch }, params) {
|
|
821
849
|
console.info(`Resync [${ getters.storeName }]`, params); // eslint-disable-line no-console
|
|
822
850
|
|
|
851
|
+
const { backOffId, ...others } = params;
|
|
852
|
+
|
|
823
853
|
await dispatch('fetchResources', {
|
|
824
|
-
|
|
825
|
-
|
|
854
|
+
params: others,
|
|
855
|
+
backOffId,
|
|
856
|
+
opt: { force: true, forceWatch: true }
|
|
826
857
|
});
|
|
827
858
|
},
|
|
828
859
|
|
|
860
|
+
/**
|
|
861
|
+
* Helper function used by fetchResources
|
|
862
|
+
*
|
|
863
|
+
* Integrates the concept of 'back-off' to reduce spam, overwrite stale old requests, etc
|
|
864
|
+
*/
|
|
865
|
+
async fetchPageResources({ getters, dispatch }, {
|
|
866
|
+
opt, storePagination, params, backOffId
|
|
867
|
+
}) {
|
|
868
|
+
const { resourceType, namespace, revision } = params;
|
|
869
|
+
const type = resourceType || params.type;
|
|
870
|
+
|
|
871
|
+
const safeBackOffId = backOffId || getters.backOffId(params, `fetchPageResources`);
|
|
872
|
+
|
|
873
|
+
const activeRevisionSt = backOff.getBackOff(safeBackOffId)?.metadata?.revision;
|
|
874
|
+
const cachedRevisionSt = getters['typeEntry'](resourceType || type)?.revision;
|
|
875
|
+
|
|
876
|
+
const targetRevision = new SteveRevision(revision);
|
|
877
|
+
const activeRevision = new SteveRevision(activeRevisionSt);
|
|
878
|
+
const cachedRevision = new SteveRevision(cachedRevisionSt);
|
|
879
|
+
const currentRevision = new SteveRevision(activeRevisionSt || cachedRevisionSt);
|
|
880
|
+
|
|
881
|
+
// Three cases to support HA scenarios 2 + 3
|
|
882
|
+
// 1. current version is newer than target revision - abort/ignore (don't overwrite new with old)
|
|
883
|
+
// 2. current version is older than target revision - reset previous (drop older requests with older revision, use new revision)
|
|
884
|
+
// 3. current version is same as target revision - we're retrying
|
|
885
|
+
|
|
886
|
+
// There are two places we do this to cover the two cases we make http request following socket changes
|
|
887
|
+
// shell/utils/pagination-wrapper.ts - request
|
|
888
|
+
// shell/plugins/steve/subscribe.js - fetchPageResources
|
|
889
|
+
|
|
890
|
+
if (currentRevision.isNewerThan(targetRevision)) {
|
|
891
|
+
// Case 1 - abort/ignore (don't overwrite new with old)
|
|
892
|
+
|
|
893
|
+
// eslint-disable-next-line no-console
|
|
894
|
+
console.warn(`Ignoring subscribe request to update '${ type }' with revision '${ targetRevision.revision }' (active revision '${ currentRevision.revision } & cached revision '${ cachedRevision.revision }''). ` +
|
|
895
|
+
`This probably means the replica that provided the web socket message has not yet correctly synced it's cache with other fresher replicas.`);
|
|
896
|
+
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (targetRevision.isNewerThan(activeRevision)) {
|
|
901
|
+
// Case 2 - reset previous (drop older requests with older revision, use new revision)
|
|
902
|
+
|
|
903
|
+
console.info(`Dropping previous subscribe request to update '${ type }' with revision '${ currentRevision.revision }' (new target revision '${ targetRevision.revision }'). `); // eslint-disable-line no-console
|
|
904
|
+
|
|
905
|
+
backOff.reset(safeBackOffId);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
try {
|
|
909
|
+
// Keep making requests until we make one that succeeds, fails with unknown revision or we run out of retries
|
|
910
|
+
await backOff.recurse({
|
|
911
|
+
id: safeBackOffId,
|
|
912
|
+
metadata: { revision },
|
|
913
|
+
description: `Fetching resources for ${ type }. Triggered by web socket`,
|
|
914
|
+
canFn: () => {
|
|
915
|
+
if (!getters.canBackoff(this.$socket)) {
|
|
916
|
+
console.info(`Aborting subscribe request to update '${ type }' with revision '${ currentRevision.revision }' (socket closed). `); // eslint-disable-line no-console
|
|
917
|
+
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (!getters['watchStarted'](params)) {
|
|
922
|
+
// No watch has started... but are we in initial state where the watch failed due to a bad revision?
|
|
923
|
+
const inError = getters.inError(params);
|
|
924
|
+
|
|
925
|
+
if (inError !== REVISION_TOO_OLD) {
|
|
926
|
+
console.info(`Aborting subscribe request to update '${ type }' with revision '${ currentRevision.revision }' (resource not watched). `); // eslint-disable-line no-console
|
|
927
|
+
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
return true;
|
|
933
|
+
},
|
|
934
|
+
continueOnError: async(err) => {
|
|
935
|
+
// Have we made a request to a stale replica that does not know about the required revision? If so continue to try until we hit a ripe replica
|
|
936
|
+
return err?.status === 400 && err?.code === STEVE_RESPONSE_CODE.UNKNOWN_REVISION;
|
|
937
|
+
},
|
|
938
|
+
delayedFn: async() => {
|
|
939
|
+
return await dispatch('findPage', {
|
|
940
|
+
type,
|
|
941
|
+
opt: {
|
|
942
|
+
...opt,
|
|
943
|
+
namespaced: namespace,
|
|
944
|
+
revision,
|
|
945
|
+
// This brings in page, page size, filter, etc
|
|
946
|
+
...storePagination.request,
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
},
|
|
950
|
+
});
|
|
951
|
+
} catch (err) {
|
|
952
|
+
// Nothing depends on the error higher in the call stack, so prevent dev full screen errors by catching it
|
|
953
|
+
console.info(`Failed subscribe request to update '${ type }' with revision '${ currentRevision.revision }' (error). `, err); // eslint-disable-line no-console
|
|
954
|
+
}
|
|
955
|
+
},
|
|
956
|
+
|
|
829
957
|
async fetchResources({
|
|
830
958
|
state, getters, dispatch, commit
|
|
831
|
-
}, { opt,
|
|
959
|
+
}, { opt, params, backOffId }) {
|
|
832
960
|
const {
|
|
833
|
-
resourceType, namespace, id, selector, mode
|
|
961
|
+
resourceType, namespace, id, selector, mode, revision
|
|
834
962
|
} = params;
|
|
835
963
|
|
|
836
964
|
if (!resourceType) {
|
|
@@ -840,6 +968,7 @@ const defaultActions = {
|
|
|
840
968
|
}
|
|
841
969
|
|
|
842
970
|
if ( id ) {
|
|
971
|
+
// Fetch an individual resource
|
|
843
972
|
await dispatch('find', {
|
|
844
973
|
type: resourceType,
|
|
845
974
|
id,
|
|
@@ -857,6 +986,7 @@ const defaultActions = {
|
|
|
857
986
|
let have = []; let want = [];
|
|
858
987
|
|
|
859
988
|
if ( selector ) {
|
|
989
|
+
// Fetch a selection of resources
|
|
860
990
|
have = getters['matching'](resourceType, selector).slice();
|
|
861
991
|
want = await dispatch('findMatching', {
|
|
862
992
|
type: resourceType,
|
|
@@ -864,39 +994,40 @@ const defaultActions = {
|
|
|
864
994
|
opt,
|
|
865
995
|
});
|
|
866
996
|
} else {
|
|
997
|
+
// Fetch all or a page of resources
|
|
867
998
|
if (mode === STEVE_WATCH_MODE.RESOURCE_CHANGES) {
|
|
999
|
+
// Fetch a page of resources
|
|
1000
|
+
|
|
868
1001
|
// Other findX use options (id/ns/selector) from the messages received over socket.
|
|
869
|
-
// However paginated requests have more complex params so grab them from
|
|
1002
|
+
// However paginated requests have more complex params so grab them from the store.
|
|
1003
|
+
|
|
870
1004
|
// of type @StorePagination
|
|
871
1005
|
const storePagination = getters['havePage'](resourceType);
|
|
872
1006
|
|
|
873
1007
|
if (!!storePagination) {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
// (see deferred process - `waiting.push(later);` - in request action).
|
|
880
|
-
// If this becomes an issue we need to debounce and work around the deferred issue within request
|
|
881
|
-
want = await dispatch('findPage', {
|
|
882
|
-
type: resourceType,
|
|
883
|
-
opt: {
|
|
884
|
-
...opt,
|
|
885
|
-
namespaced: namespace,
|
|
886
|
-
// This brings in page, page size, filter, etc
|
|
887
|
-
...storePagination.request
|
|
888
|
-
}
|
|
1008
|
+
await dispatch('fetchPageResources', {
|
|
1009
|
+
params,
|
|
1010
|
+
storePagination,
|
|
1011
|
+
opt,
|
|
1012
|
+
backOffId
|
|
889
1013
|
});
|
|
1014
|
+
|
|
1015
|
+
// findPage removes stale entries, so we don't need to rely on below process to remove them
|
|
1016
|
+
have = [];
|
|
1017
|
+
want = [];
|
|
890
1018
|
}
|
|
1019
|
+
|
|
891
1020
|
// Should any listeners be notified of this request for them to kick off their own event handling?
|
|
892
1021
|
getters.listenerManager.triggerEventListener({
|
|
893
1022
|
event: STEVE_WATCH_MODE.RESOURCE_CHANGES,
|
|
894
1023
|
params: {
|
|
895
1024
|
...params,
|
|
896
|
-
|
|
1025
|
+
revision,
|
|
1026
|
+
forceWatch: opt.forceWatch,
|
|
897
1027
|
}
|
|
898
1028
|
});
|
|
899
1029
|
} else {
|
|
1030
|
+
// Fetch all of a resource
|
|
900
1031
|
have = getters['all'](resourceType).slice();
|
|
901
1032
|
|
|
902
1033
|
if ( namespace ) {
|
|
@@ -1110,7 +1241,7 @@ const defaultActions = {
|
|
|
1110
1241
|
// 2) will be cleared when resyncWatch --> watch (with force) --> resource.start completes
|
|
1111
1242
|
commit('setInError', { msg, reason: REVISION_TOO_OLD });
|
|
1112
1243
|
|
|
1113
|
-
//
|
|
1244
|
+
// HA scenario 1 - handle case where stale replica processes watch request
|
|
1114
1245
|
// The watch that results from resyncWatch will fail and end up here if the revision isn't (yet) known
|
|
1115
1246
|
// So re-retry resyncWatch until it does OR
|
|
1116
1247
|
// - we're already re-retrying
|
|
@@ -1120,11 +1251,17 @@ const defaultActions = {
|
|
|
1120
1251
|
// - we need to stop (socket is disconnected or closed, type is 'forgotten', watch is unwatched)
|
|
1121
1252
|
// - `reset` called asynchronously
|
|
1122
1253
|
// - Note - we won't need to clear the id outside of the above scenarios because `too old` only occurs on fresh watches (covered by above scenarios)
|
|
1254
|
+
|
|
1255
|
+
const backOffId = getters.backOffId(msg, REVISION_TOO_OLD);
|
|
1256
|
+
|
|
1123
1257
|
backOff.execute({
|
|
1124
|
-
id:
|
|
1258
|
+
id: backOffId,
|
|
1125
1259
|
description: `Invalid watch revision, re-syncing`,
|
|
1126
1260
|
canFn: () => getters.canBackoff(this.$socket),
|
|
1127
|
-
delayedFn: () => dispatch('resyncWatch',
|
|
1261
|
+
delayedFn: () => dispatch('resyncWatch', {
|
|
1262
|
+
...msg,
|
|
1263
|
+
backOffId: undefined,
|
|
1264
|
+
}),
|
|
1128
1265
|
});
|
|
1129
1266
|
} else if ( err.includes('the server does not allow this method on the requested resource')) {
|
|
1130
1267
|
commit('setInError', { msg, reason: NO_PERMS });
|
|
@@ -1268,10 +1405,10 @@ const defaultActions = {
|
|
|
1268
1405
|
}
|
|
1269
1406
|
},
|
|
1270
1407
|
|
|
1271
|
-
'ws.resource.changes'({ dispatch }, msg) {
|
|
1272
|
-
dispatch('fetchResources', {
|
|
1273
|
-
|
|
1274
|
-
opt:
|
|
1408
|
+
async 'ws.resource.changes'({ dispatch }, msg) {
|
|
1409
|
+
await dispatch('fetchResources', {
|
|
1410
|
+
params: msg,
|
|
1411
|
+
opt: { force: true, load: _MERGE }
|
|
1275
1412
|
} );
|
|
1276
1413
|
},
|
|
1277
1414
|
|
|
@@ -1413,7 +1550,7 @@ const defaultGetters = {
|
|
|
1413
1550
|
* @param postFix - something else to uniquely id this back-off
|
|
1414
1551
|
*/
|
|
1415
1552
|
backOffId: () => (obj, postFix) => {
|
|
1416
|
-
return `${ keyForSubscribe(obj) }${ postFix ?
|
|
1553
|
+
return `${ keyForSubscribe(obj) }${ postFix ? `:detail=${ postFix }` : '' }`;
|
|
1417
1554
|
},
|
|
1418
1555
|
|
|
1419
1556
|
/**
|
|
@@ -1454,15 +1591,15 @@ const defaultGetters = {
|
|
|
1454
1591
|
*/
|
|
1455
1592
|
nextResourceVersion: (state, getters) => (type, id) => {
|
|
1456
1593
|
type = normalizeType(type);
|
|
1457
|
-
let
|
|
1594
|
+
let nextRevision = 0;
|
|
1458
1595
|
|
|
1459
1596
|
if ( id ) {
|
|
1460
1597
|
const existing = getters['byId'](type, id);
|
|
1461
1598
|
|
|
1462
|
-
|
|
1599
|
+
nextRevision = existing?.metadata?.resourceVersion;
|
|
1463
1600
|
}
|
|
1464
1601
|
|
|
1465
|
-
if ( !
|
|
1602
|
+
if ( !nextRevision ) {
|
|
1466
1603
|
const cache = state.types[type];
|
|
1467
1604
|
|
|
1468
1605
|
// No Cache, nothing to compare to, return early
|
|
@@ -1470,27 +1607,29 @@ const defaultGetters = {
|
|
|
1470
1607
|
return null;
|
|
1471
1608
|
}
|
|
1472
1609
|
|
|
1473
|
-
|
|
1610
|
+
const cacheRevision = new SteveRevision(cache.revision);
|
|
1474
1611
|
|
|
1475
1612
|
// Cached LIST revision isn't a number, cannot compare to, return early
|
|
1476
|
-
if (
|
|
1613
|
+
if (!cacheRevision.isNumber) {
|
|
1477
1614
|
return cache.revision || null;
|
|
1478
1615
|
}
|
|
1479
1616
|
|
|
1617
|
+
nextRevision = cacheRevision;
|
|
1618
|
+
|
|
1480
1619
|
for ( const obj of cache.list || [] ) {
|
|
1481
1620
|
if ( obj && obj.metadata ) {
|
|
1482
|
-
const
|
|
1621
|
+
const candidateRevision = new SteveRevision(obj.metadata.resourceVersion);
|
|
1483
1622
|
|
|
1484
|
-
if (
|
|
1485
|
-
|
|
1623
|
+
if (candidateRevision.isNewerThan(nextRevision)) {
|
|
1624
|
+
nextRevision = candidateRevision;
|
|
1486
1625
|
}
|
|
1487
|
-
|
|
1488
|
-
revision = Math.max(revision, neu);
|
|
1489
1626
|
}
|
|
1490
1627
|
}
|
|
1628
|
+
|
|
1629
|
+
nextRevision = nextRevision.asNumber;
|
|
1491
1630
|
}
|
|
1492
1631
|
|
|
1493
|
-
return
|
|
1632
|
+
return nextRevision || null;
|
|
1494
1633
|
},
|
|
1495
1634
|
|
|
1496
1635
|
/**
|
|
@@ -167,7 +167,7 @@ export class SteveWatchEventListenerManager {
|
|
|
167
167
|
|
|
168
168
|
if (eventWatcher) {
|
|
169
169
|
Object.values(eventWatcher.callbacks).forEach((cb) => {
|
|
170
|
-
cb({ forceWatch: params.forceWatch }); // eslint-disable-line node/no-callback-literal
|
|
170
|
+
cb({ forceWatch: params.forceWatch, revision: params.revision }); // eslint-disable-line node/no-callback-literal
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
173
|
}
|
|
@@ -177,7 +177,7 @@ export class SteveWatchEventListenerManager {
|
|
|
177
177
|
|
|
178
178
|
watch.listeners.forEach((l) => {
|
|
179
179
|
Object.values(l.callbacks || {}).forEach((cb) => {
|
|
180
|
-
cb({ forceWatch: params.forceWatch });// eslint-disable-line node/no-callback-literal
|
|
180
|
+
cb({ forceWatch: params.forceWatch, revision: params.revision });// eslint-disable-line node/no-callback-literal
|
|
181
181
|
});
|
|
182
182
|
});
|
|
183
183
|
}
|
|
@@ -259,6 +259,12 @@ export default defineComponent({
|
|
|
259
259
|
*/
|
|
260
260
|
findTrueValues(value: boolean[]): boolean {
|
|
261
261
|
return value.find((v) => v === this.valueWhenTrue) || false;
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
focus() {
|
|
265
|
+
if (!this.isDisabled) {
|
|
266
|
+
(this.$refs.checkbox as HTMLElement)?.focus();
|
|
267
|
+
}
|
|
262
268
|
}
|
|
263
269
|
}
|
|
264
270
|
});
|
|
@@ -285,10 +291,12 @@ export default defineComponent({
|
|
|
285
291
|
:value="valueWhenTrue"
|
|
286
292
|
type="checkbox"
|
|
287
293
|
tabindex="-1"
|
|
294
|
+
aria-hidden="true"
|
|
288
295
|
@click.stop.prevent
|
|
289
296
|
@keyup.enter.stop.prevent
|
|
290
297
|
>
|
|
291
298
|
<span
|
|
299
|
+
ref="checkbox"
|
|
292
300
|
class="checkbox-custom"
|
|
293
301
|
:class="{indeterminate: indeterminate}"
|
|
294
302
|
:tabindex="isDisabled ? -1 : 0"
|
|
@@ -424,6 +432,11 @@ $fontColor: var(--input-label);
|
|
|
424
432
|
outline-offset: 2px;
|
|
425
433
|
border-radius: 0;
|
|
426
434
|
}
|
|
435
|
+
&:focus {
|
|
436
|
+
@include focus-outline;
|
|
437
|
+
outline-offset: 2px;
|
|
438
|
+
border-radius: 0;
|
|
439
|
+
}
|
|
427
440
|
}
|
|
428
441
|
|
|
429
442
|
input {
|
|
@@ -68,7 +68,7 @@ export default defineComponent({
|
|
|
68
68
|
<template v-if="hover">
|
|
69
69
|
<i
|
|
70
70
|
v-clean-tooltip="tooltipContent"
|
|
71
|
-
v-stripped-aria-label="isObject(value) ? value.content : value"
|
|
71
|
+
v-stripped-aria-label="`${t('generic.tooltip')} - ${(isObject(value) ? value.content : value)}`"
|
|
72
72
|
:class="{'hover':!value, [iconClass]: true}"
|
|
73
73
|
class="icon status-icon"
|
|
74
74
|
tabindex="0"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { RcCounterBadgeProps } from '
|
|
2
|
+
import { RcCounterBadgeProps } from './types';
|
|
3
3
|
import { computed } from 'vue';
|
|
4
4
|
const props = withDefaults(defineProps<RcCounterBadgeProps>(), { disabled: false });
|
|
5
5
|
const displayCount = computed(() => props.count < 1000 ? props.count : '999+');
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { toRef } from 'vue';
|
|
2
3
|
import { RcStatusBadgeProps } from './types';
|
|
3
4
|
import { useStatusColors } from '@components/utils/status';
|
|
4
5
|
|
|
5
6
|
const props = defineProps<RcStatusBadgeProps>();
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const status = toRef(props, 'status');
|
|
9
|
+
const { backgroundColor, borderColor, textColor } = useStatusColors(status, 'outlined');
|
|
8
10
|
</script>
|
|
9
11
|
|
|
10
12
|
<template>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { toRef } from 'vue';
|
|
2
3
|
import { RcStatusIndicatorProps } from './types';
|
|
3
4
|
import { useStatusColors } from '@components/utils/status';
|
|
4
5
|
|
|
5
6
|
const props = defineProps<RcStatusIndicatorProps>();
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const status = toRef(props, 'status');
|
|
9
|
+
const { backgroundColor, borderColor } = useStatusColors(status, 'solid');
|
|
8
10
|
</script>
|
|
9
11
|
|
|
10
12
|
<template>
|