mirror of https://github.com/rancher/dashboard.git
Merge pull request #13372 from torchiaf/7824-expose-polling
Fleet: expose Polling Interval for GitRepos
This commit is contained in:
commit
8a0e784f72
|
|
@ -64,7 +64,7 @@
|
|||
"@aws-sdk/client-kms": "3.8.1",
|
||||
"@novnc/novnc": "1.2.0",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@rancher/icons": "2.0.29",
|
||||
"@rancher/icons": "2.0.32",
|
||||
"ansi_up": "5.0.0",
|
||||
"axios": "0.21.4",
|
||||
"axios-retry": "3.1.9",
|
||||
|
|
|
|||
|
|
@ -2479,6 +2479,14 @@ fleet:
|
|||
keepResourcesTooltip: When enabled, resources will be kept when deleting a GitRepo or Bundle - only Helm release secrets will be deleted.
|
||||
correctDrift: Enable Self-Healing
|
||||
correctDriftTooltip: When enabled, Fleet will ensure that the cluster resources are kept in sync with the git repository. All resource changes made on the cluster will be lost.
|
||||
polling:
|
||||
label: Polling
|
||||
enable: Enable Polling
|
||||
pollingInterval:
|
||||
label: Polling Interval
|
||||
tooltip: Polling Interval is the time between a push to the Repository and Fleet's reaction to it.
|
||||
webhookWarning: Warning, a webhook is configured in Rancher to reconcile Git updates and GitRepo resources. The Polling Interval will automatically be adjusted to 1 hour.
|
||||
minimumValuewarning: Warning, the recommended minimum Polling Interval is 15 seconds. To avoid performance issues with GitRepos that contain a large amount of resources please consider increasing this value.
|
||||
add:
|
||||
steps:
|
||||
repoInfo:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
gitRepoHasClusters() {
|
||||
return this.value.status.desiredReadyClusters;
|
||||
return this.value.status?.desiredReadyClusters;
|
||||
},
|
||||
clusterSchema() {
|
||||
return this.$store.getters['management/schemaFor'](FLEET.CLUSTER);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,20 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { _CREATE, _EDIT } from '@shell/config/query-params';
|
||||
import GitRepo from '@shell/edit/fleet.cattle.io.gitrepo.vue';
|
||||
|
||||
describe('view: fleet.cattle.io.gitrepo should', () => {
|
||||
const values = {
|
||||
metadata: { namespace: 'test' },
|
||||
spec: {
|
||||
template: {},
|
||||
correctDrift: { enabled: false },
|
||||
},
|
||||
targetInfo: { mode: 'all' },
|
||||
};
|
||||
|
||||
describe.each([
|
||||
_CREATE,
|
||||
_EDIT
|
||||
])('view: fleet.cattle.io.gitrepo, mode: %p - should', (mode) => {
|
||||
const mockStore = {
|
||||
dispatch: jest.fn(),
|
||||
getters: {
|
||||
|
|
@ -25,35 +38,32 @@ describe('view: fleet.cattle.io.gitrepo should', () => {
|
|||
}
|
||||
},
|
||||
};
|
||||
const values = {
|
||||
metadata: { namespace: 'test' }, spec: { template: {}, correctDrift: { enabled: false } }, targetInfo: { mode: 'all' },
|
||||
};
|
||||
const wrapper = mount(GitRepo, {
|
||||
props: { value: values },
|
||||
props: { value: values, mode },
|
||||
global: { mocks }
|
||||
});
|
||||
|
||||
it('should have self-healing checkbox and tooltip', () => {
|
||||
const correctDriftCheckbox = wrapper.find('[data-testid="GitRepo-correctDrift-checkbox"]');
|
||||
const tooltip = wrapper.find('[data-testid="GitRepo-correctDrift-checkbox"]');
|
||||
it('have self-healing checkbox and tooltip', () => {
|
||||
const correctDriftCheckbox = wrapper.find('[data-testid="gitRepo-correctDrift-checkbox"]');
|
||||
const tooltip = wrapper.find('[data-testid="gitRepo-correctDrift-checkbox"]');
|
||||
|
||||
expect(tooltip.element.classList).toContain('v-popper--has-tooltip');
|
||||
expect(correctDriftCheckbox.exists()).toBeTruthy();
|
||||
expect(correctDriftCheckbox.attributes().value).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should have keep-resources checkbox and tooltip', () => {
|
||||
const correctDriftCheckbox = wrapper.find('[data-testid="GitRepo-keepResources-checkbox"]');
|
||||
const tooltip = wrapper.find('[data-testid="GitRepo-keepResources-checkbox"]');
|
||||
it('have keep-resources checkbox and tooltip', () => {
|
||||
const correctDriftCheckbox = wrapper.find('[data-testid="gitRepo-keepResources-checkbox"]');
|
||||
const tooltip = wrapper.find('[data-testid="gitRepo-keepResources-checkbox"]');
|
||||
|
||||
expect(tooltip.element.classList).toContain('v-popper--has-tooltip');
|
||||
expect(correctDriftCheckbox.exists()).toBeTruthy();
|
||||
expect(correctDriftCheckbox.attributes().value).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should enable drift if self-healing is checked', async() => {
|
||||
const correctDriftCheckbox = wrapper.findComponent('[data-testid="GitRepo-correctDrift-checkbox"]');
|
||||
const correctDriftContainer = wrapper.find('[data-testid="GitRepo-correctDrift-checkbox"] .checkbox-container');
|
||||
it('enable drift if self-healing is checked', async() => {
|
||||
const correctDriftCheckbox = wrapper.findComponent('[data-testid="gitRepo-correctDrift-checkbox"]');
|
||||
const correctDriftContainer = wrapper.find('[data-testid="gitRepo-correctDrift-checkbox"] .checkbox-container');
|
||||
|
||||
expect(correctDriftContainer.exists()).toBeTruthy();
|
||||
|
||||
|
|
@ -63,4 +73,129 @@ describe('view: fleet.cattle.io.gitrepo should', () => {
|
|||
expect(correctDriftCheckbox.emitted('update:value')![0][0]).toBe(true);
|
||||
expect(correctDriftCheckbox.props().value).toBeTruthy();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['show Polling Interval and warnings', 'enabled', undefined, true],
|
||||
['show Polling Interval and warnings', 'enabled', false, true],
|
||||
['hide Polling Interval and warnings', 'disabled', true, false],
|
||||
])('show Enable Polling checkbox and %p if %p, with spec.disablePolling: %p', (
|
||||
descr1,
|
||||
descr2,
|
||||
disablePolling,
|
||||
enabled
|
||||
) => {
|
||||
const wrapper = mount(GitRepo, {
|
||||
props: {
|
||||
value: {
|
||||
...values,
|
||||
spec: {
|
||||
disablePolling,
|
||||
pollingInterval: 10
|
||||
},
|
||||
status: { webhookCommit: 'sha' },
|
||||
},
|
||||
realMode: mode
|
||||
},
|
||||
global: { mocks },
|
||||
});
|
||||
|
||||
const pollingCheckbox = wrapper.findComponent('[data-testid="gitRepo-enablePolling-checkbox"]') as any;
|
||||
const pollingIntervalInput = wrapper.find('[data-testid="gitRepo-pollingInterval-input"]');
|
||||
const pollingIntervalMinimumValueWarning = wrapper.find('[data-testid="gitRepo-pollingInterval-minimumValueWarning"]');
|
||||
const pollingIntervalWebhookWarning = wrapper.find('[data-testid="gitRepo-pollingInterval-webhookWarning"]');
|
||||
|
||||
expect(pollingIntervalMinimumValueWarning.exists()).toBe(enabled);
|
||||
expect(pollingIntervalWebhookWarning.exists()).toBe(enabled);
|
||||
expect(pollingCheckbox.exists()).toBeTruthy();
|
||||
expect(pollingCheckbox.vm.value).toBe(enabled);
|
||||
expect(pollingIntervalInput.exists()).toBe(enabled);
|
||||
});
|
||||
|
||||
const defaultPollingInterval = mode === _CREATE ? '60' : '15';
|
||||
|
||||
it.each([
|
||||
['null', `default ${ defaultPollingInterval } seconds`, null, defaultPollingInterval],
|
||||
['0', `default ${ defaultPollingInterval } seconds`, 0, defaultPollingInterval],
|
||||
['1', 'custom 1 second', 1, '1'],
|
||||
['60', 'custom 60 seconds', 60, '60'],
|
||||
['15', 'custom 15 seconds', 15, '15'],
|
||||
])('show Polling Interval input with source: %p, value: %p', async(
|
||||
descr1,
|
||||
descr2,
|
||||
pollingInterval,
|
||||
unitValue,
|
||||
) => {
|
||||
const wrapper = mount(GitRepo, {
|
||||
props: {
|
||||
value: {
|
||||
...values,
|
||||
spec: { pollingInterval }
|
||||
},
|
||||
realMode: mode
|
||||
},
|
||||
global: { mocks },
|
||||
});
|
||||
|
||||
const pollingIntervalInput = wrapper.find('[data-testid="gitRepo-pollingInterval-input"]').element as any;
|
||||
|
||||
expect(pollingIntervalInput).toBeDefined();
|
||||
expect(pollingIntervalInput.value).toBe(unitValue);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['hide', 'source: null, value: equal to 60', null, false],
|
||||
['hide', 'source: 0, value: equal to 60', 0, false],
|
||||
['hide', 'source: 15, value: equal to 15', 15, false],
|
||||
['hide', 'source: 60, value: equal to 60', 60, false],
|
||||
['hide', 'source: 16, value: higher than 15', 16, false],
|
||||
['show', 'source: 1, value: lower than 15', 1, true],
|
||||
])('%p Polling Interval warning if %p', async(
|
||||
descr1,
|
||||
descr2,
|
||||
pollingInterval,
|
||||
visible,
|
||||
) => {
|
||||
const wrapper = mount(GitRepo, {
|
||||
props: {
|
||||
value: {
|
||||
...values,
|
||||
spec: { pollingInterval }
|
||||
},
|
||||
realMode: mode
|
||||
},
|
||||
global: { mocks },
|
||||
});
|
||||
|
||||
const pollingIntervalMinimumValueWarning = wrapper.find('[data-testid="gitRepo-pollingInterval-minimumValueWarning"]');
|
||||
|
||||
expect(pollingIntervalMinimumValueWarning.exists()).toBe(visible);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['hide', 'disabled', null, false],
|
||||
['hide', 'disabled', false, false],
|
||||
['hide', 'disabled', '', false],
|
||||
['show', 'enabled', 'sha', true],
|
||||
])('%p Webhook configured warning if webhook is %p', (
|
||||
descr1,
|
||||
descr2,
|
||||
webhookCommit,
|
||||
visible
|
||||
) => {
|
||||
const wrapper = mount(GitRepo, {
|
||||
props: {
|
||||
value: {
|
||||
...values,
|
||||
spec: { pollingInterval: 60 },
|
||||
status: { webhookCommit },
|
||||
},
|
||||
realMode: mode
|
||||
},
|
||||
global: { mocks },
|
||||
});
|
||||
|
||||
const pollingIntervalWebhookWarning = wrapper.find('[data-testid="gitRepo-pollingInterval-webhookWarning"]');
|
||||
|
||||
expect(pollingIntervalWebhookWarning.exists()).toBe(visible);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,13 +19,17 @@ import NameNsDescription from '@shell/components/form/NameNsDescription';
|
|||
import YamlEditor from '@shell/components/YamlEditor';
|
||||
import { base64Decode, base64Encode } from '@shell/utils/crypto';
|
||||
import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthSecret';
|
||||
import { _CREATE } from '@shell/config/query-params';
|
||||
import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params';
|
||||
import { isHarvesterCluster } from '@shell/utils/cluster';
|
||||
import { CAPI, CATALOG, FLEET as FLEET_LABELS } from '@shell/config/labels-annotations';
|
||||
import { SECRET_TYPES } from '@shell/config/secret';
|
||||
import { checkSchemasForFindAllHash } from '@shell/utils/auth';
|
||||
import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
|
||||
import FormValidation from '@shell/mixins/form-validation';
|
||||
import UnitInput from '@shell/components/form/UnitInput';
|
||||
|
||||
const MINIMUM_POLLING_INTERVAL = 15;
|
||||
const DEFAULT_POLLING_INTERVAL = 60;
|
||||
|
||||
const _VERIFY = 'verify';
|
||||
const _SKIP = 'skip';
|
||||
|
|
@ -51,6 +55,7 @@ export default {
|
|||
NameNsDescription,
|
||||
YamlEditor,
|
||||
SelectOrCreateAuthSecret,
|
||||
UnitInput,
|
||||
},
|
||||
|
||||
mixins: [CreateEditView, FormValidation],
|
||||
|
|
@ -92,6 +97,16 @@ export default {
|
|||
},
|
||||
|
||||
data() {
|
||||
let pollingInterval = this.value.spec.pollingInterval;
|
||||
|
||||
if (!pollingInterval) {
|
||||
if (this.realMode === _CREATE) {
|
||||
pollingInterval = DEFAULT_POLLING_INTERVAL;
|
||||
} else if (this.realMode === _EDIT || this.realMode === _VIEW) {
|
||||
pollingInterval = MINIMUM_POLLING_INTERVAL;
|
||||
}
|
||||
}
|
||||
|
||||
const targetInfo = this.value.targetInfo;
|
||||
const targetCluster = targetInfo.cluster;
|
||||
const targetClusterGroup = targetInfo.clusterGroup;
|
||||
|
|
@ -144,6 +159,7 @@ export default {
|
|||
correctDriftEnabled: false,
|
||||
targetAdvancedErrors: null,
|
||||
matchingClusters: null,
|
||||
pollingInterval,
|
||||
ref,
|
||||
refValue,
|
||||
targetMode,
|
||||
|
|
@ -185,6 +201,14 @@ export default {
|
|||
return !(this.value?.spec?.repo || '').startsWith('http://');
|
||||
},
|
||||
|
||||
isPollingEnabled() {
|
||||
return !this.value.spec.disablePolling;
|
||||
},
|
||||
|
||||
isWebhookConfigured() {
|
||||
return !!this.value.status?.webhookCommit;
|
||||
},
|
||||
|
||||
targetOptions() {
|
||||
const out = [
|
||||
{
|
||||
|
|
@ -265,6 +289,10 @@ export default {
|
|||
];
|
||||
},
|
||||
|
||||
showPollingIntervalWarning() {
|
||||
return !this.isView && this.isPollingEnabled && this.pollingInterval < MINIMUM_POLLING_INTERVAL;
|
||||
},
|
||||
|
||||
stepOneRequires() {
|
||||
return !!this.value.metadata.name && !!this.refValue && !!this.fvFormIsValid;
|
||||
},
|
||||
|
|
@ -512,6 +540,35 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
enablePolling(value) {
|
||||
if (value) {
|
||||
delete this.value.spec.disablePolling;
|
||||
} else {
|
||||
this.value.spec.disablePolling = true;
|
||||
}
|
||||
},
|
||||
|
||||
updatePollingInterval(value) {
|
||||
if (!value) {
|
||||
this.pollingInterval = DEFAULT_POLLING_INTERVAL;
|
||||
this.value.spec.pollingInterval = DEFAULT_POLLING_INTERVAL.toString();
|
||||
} else if (value === MINIMUM_POLLING_INTERVAL) {
|
||||
delete this.value.spec.pollingInterval;
|
||||
} else {
|
||||
this.value.spec.pollingInterval = value.toString();
|
||||
}
|
||||
},
|
||||
|
||||
scrollToBottom() {
|
||||
this.$nextTick(() => {
|
||||
const scrollable = document.getElementsByTagName('main')[0];
|
||||
|
||||
if (scrollable) {
|
||||
scrollable.scrollTop = scrollable.scrollHeight;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onSave() {
|
||||
this.value.spec['correctDrift'] = { enabled: this.correctDriftEnabled };
|
||||
|
||||
|
|
@ -669,7 +726,7 @@ export default {
|
|||
<Checkbox
|
||||
v-model:value="correctDriftEnabled"
|
||||
:tooltip="t('fleet.gitRepo.resources.correctDriftTooltip')"
|
||||
data-testid="GitRepo-correctDrift-checkbox"
|
||||
data-testid="gitRepo-correctDrift-checkbox"
|
||||
class="check"
|
||||
type="checkbox"
|
||||
label-key="fleet.gitRepo.resources.correctDrift"
|
||||
|
|
@ -678,7 +735,7 @@ export default {
|
|||
<Checkbox
|
||||
v-model:value="value.spec.keepResources"
|
||||
:tooltip="t('fleet.gitRepo.resources.keepResourcesTooltip')"
|
||||
data-testid="GitRepo-keepResources-checkbox"
|
||||
data-testid="gitRepo-keepResources-checkbox"
|
||||
class="check"
|
||||
type="checkbox"
|
||||
label-key="fleet.gitRepo.resources.keepResources"
|
||||
|
|
@ -696,6 +753,50 @@ export default {
|
|||
:add-label="t('fleet.gitRepo.paths.addLabel')"
|
||||
:protip="t('fleet.gitRepo.paths.empty')"
|
||||
/>
|
||||
|
||||
<div class="spacer" />
|
||||
<h2 v-t="'fleet.gitRepo.polling.label'" />
|
||||
<div class="row polling">
|
||||
<div class="col span-6">
|
||||
<Checkbox
|
||||
v-model:value="isPollingEnabled"
|
||||
data-testid="gitRepo-enablePolling-checkbox"
|
||||
class="check"
|
||||
type="checkbox"
|
||||
label-key="fleet.gitRepo.polling.enable"
|
||||
:mode="mode"
|
||||
@update:value="enablePolling"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="isPollingEnabled">
|
||||
<div class="col">
|
||||
<Banner
|
||||
v-if="showPollingIntervalWarning"
|
||||
color="warning"
|
||||
label-key="fleet.gitRepo.polling.pollingInterval.minimumValuewarning"
|
||||
data-testid="gitRepo-pollingInterval-minimumValueWarning"
|
||||
/>
|
||||
<Banner
|
||||
v-if="isWebhookConfigured"
|
||||
color="warning"
|
||||
label-key="fleet.gitRepo.polling.pollingInterval.webhookWarning"
|
||||
data-testid="gitRepo-pollingInterval-webhookWarning"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<UnitInput
|
||||
v-model:value="pollingInterval"
|
||||
data-testid="gitRepo-pollingInterval-input"
|
||||
min="1"
|
||||
:suffix="t('suffix.seconds', { count: pollingInterval })"
|
||||
:label="t('fleet.gitRepo.polling.pollingInterval.label')"
|
||||
:mode="mode"
|
||||
tooltip-key="fleet.gitRepo.polling.pollingInterval.tooltip"
|
||||
@update:value="updatePollingInterval"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template #stepTargetInfo>
|
||||
<h2 v-t="isLocal ? 'fleet.gitRepo.target.labelLocal' : 'fleet.gitRepo.target.label'" />
|
||||
|
|
@ -781,6 +882,11 @@ export default {
|
|||
.resource-handling {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px
|
||||
gap: 5px;
|
||||
}
|
||||
.polling {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ export default {
|
|||
>
|
||||
<UnitInput
|
||||
v-model:value="terminationGracePeriodSeconds"
|
||||
:suffix="terminationGracePeriodSeconds == 1 ? 'Second' : 'Seconds'"
|
||||
:suffix="t('suffix.seconds', { count: terminationGracePeriodSeconds })"
|
||||
:label="t('workload.upgrading.activeDeadlineSeconds.label')"
|
||||
:mode="mode"
|
||||
@input="update"
|
||||
|
|
@ -316,7 +316,7 @@ export default {
|
|||
>
|
||||
<UnitInput
|
||||
v-model:value="terminationGracePeriodSeconds"
|
||||
:suffix="terminationGracePeriodSeconds == 1 ? 'Second' : 'Seconds'"
|
||||
:suffix="t('suffix.seconds', { count: terminationGracePeriodSeconds })"
|
||||
:label="t('workload.upgrading.activeDeadlineSeconds.label')"
|
||||
:mode="mode"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -83,6 +83,22 @@ export default class GitRepo extends SteveModel {
|
|||
});
|
||||
|
||||
insertAt(out, 2, {
|
||||
action: 'enablePolling',
|
||||
label: 'Enable Polling',
|
||||
icon: 'icon icon-endpoints_connected',
|
||||
bulkable: true,
|
||||
enabled: !!this.links.update && !!this.spec?.disablePolling
|
||||
});
|
||||
|
||||
insertAt(out, 3, {
|
||||
action: 'disablePolling',
|
||||
label: 'Disable Polling',
|
||||
icon: 'icon icon-endpoints_disconnected',
|
||||
bulkable: true,
|
||||
enabled: !!this.links.update && !this.spec?.disablePolling
|
||||
});
|
||||
|
||||
insertAt(out, 4, {
|
||||
action: 'forceUpdate',
|
||||
label: 'Force Update',
|
||||
icon: 'icon icon-refresh',
|
||||
|
|
@ -90,7 +106,7 @@ export default class GitRepo extends SteveModel {
|
|||
enabled: !!this.links.update
|
||||
});
|
||||
|
||||
insertAt(out, 3, { divider: true });
|
||||
insertAt(out, 5, { divider: true });
|
||||
|
||||
return out;
|
||||
}
|
||||
|
|
@ -105,6 +121,16 @@ export default class GitRepo extends SteveModel {
|
|||
this.save();
|
||||
}
|
||||
|
||||
enablePolling() {
|
||||
this.spec.disablePolling = false;
|
||||
this.save();
|
||||
}
|
||||
|
||||
disablePolling() {
|
||||
this.spec.disablePolling = true;
|
||||
this.save();
|
||||
}
|
||||
|
||||
forceUpdate() {
|
||||
const now = this.spec.forceSyncGeneration || 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -3320,10 +3320,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||
|
||||
"@rancher/icons@2.0.29":
|
||||
version "2.0.29"
|
||||
resolved "https://registry.npmjs.org/@rancher/icons/-/icons-2.0.29.tgz#6546d69768c706bebd66bfa73b36aef3d105987a"
|
||||
integrity sha512-qBBqfazS9y5VjV7fJDPNXmxd9AP/2uiE05mKFWP41kpbO+tEb62RnUBXCm14XLeScDZQcOuiAKVHMmvCFzF0BA==
|
||||
"@rancher/icons@2.0.32":
|
||||
version "2.0.32"
|
||||
resolved "https://registry.yarnpkg.com/@rancher/icons/-/icons-2.0.32.tgz#c6b18c81b87bc9439b1e449b2f6bd859d76c0bbe"
|
||||
integrity sha512-afZlIHPboVgQIwnMuaQT7LTPoWuCIgyqD2Ch277lEAoFIzuU+NwHzpEAvX5xcOpVXYAFlvDE/VlJ6FmYaQBBig==
|
||||
|
||||
"@rtsao/scc@^1.1.0":
|
||||
version "1.1.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue