@northern.tech/store 0.1.0
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/.eslintrc.cjs +9 -0
- package/.prettierignore +1 -0
- package/CHANGELOG.md +12 -0
- package/actions.ts +32 -0
- package/api/general-api.test.ts +51 -0
- package/api/general-api.ts +60 -0
- package/api/types/AWSCredentials.ts +16 -0
- package/api/types/Actor.ts +35 -0
- package/api/types/Addon.ts +17 -0
- package/api/types/AddonTenantadm.ts +17 -0
- package/api/types/Alert.ts +34 -0
- package/api/types/AlertDetails.ts +23 -0
- package/api/types/AlertSubject.ts +23 -0
- package/api/types/ApiBurst.ts +21 -0
- package/api/types/ApiBurstTenantadm.ts +21 -0
- package/api/types/ApiLimits.ts +16 -0
- package/api/types/ApiLimitsTenantadm.ts +16 -0
- package/api/types/ApiQuota.ts +17 -0
- package/api/types/ApiQuotaTenantadm.ts +17 -0
- package/api/types/Artifact.ts +48 -0
- package/api/types/ArtifactDeployments.ts +48 -0
- package/api/types/ArtifactInfo.ts +11 -0
- package/api/types/ArtifactInfoDeployments.ts +11 -0
- package/api/types/ArtifactLink.ts +11 -0
- package/api/types/ArtifactTypeInfo.ts +13 -0
- package/api/types/ArtifactTypeInfoDeployments.ts +13 -0
- package/api/types/ArtifactUpdate.ts +10 -0
- package/api/types/ArtifactUploadLink.ts +15 -0
- package/api/types/Attribute.ts +36 -0
- package/api/types/AttributeInventory.ts +32 -0
- package/api/types/AuditLog.ts +26 -0
- package/api/types/AuthSet.ts +33 -0
- package/api/types/AuthSetDeviceauth.ts +37 -0
- package/api/types/AutoAuthRequest.ts +23 -0
- package/api/types/AzureSharedAccessSecret.ts +11 -0
- package/api/types/BillingInfo.ts +13 -0
- package/api/types/BinaryDeltaConfiguration.ts +15 -0
- package/api/types/BinaryDeltaConfigurationDeployments.ts +15 -0
- package/api/types/BinaryDeltaLimits.ts +15 -0
- package/api/types/BinaryDeltaLimitsDeployments.ts +15 -0
- package/api/types/BoundingBox.ts +11 -0
- package/api/types/CancelRequest.ts +10 -0
- package/api/types/CardData.ts +49 -0
- package/api/types/CardSetupData.ts +17 -0
- package/api/types/CheckoutData.ts +13 -0
- package/api/types/Configuration.ts +11 -0
- package/api/types/Count.ts +13 -0
- package/api/types/Credentials.ts +23 -0
- package/api/types/DBusSubsystem.ts +27 -0
- package/api/types/DeltaConfiguration.ts +14 -0
- package/api/types/DeltaConfigurationDeployments.ts +14 -0
- package/api/types/Deployment.ts +95 -0
- package/api/types/DeploymentAggregation.ts +18 -0
- package/api/types/DeploymentAggregationItem.ts +18 -0
- package/api/types/DeploymentAggregationTerm.ts +23 -0
- package/api/types/DeploymentAggregationTerms.ts +18 -0
- package/api/types/DeploymentAttributeProjection.ts +11 -0
- package/api/types/DeploymentAuditlogs.ts +11 -0
- package/api/types/DeploymentDeployments.ts +99 -0
- package/api/types/DeploymentFilterTerm.ts +37 -0
- package/api/types/DeploymentIdentifier.ts +10 -0
- package/api/types/DeploymentPhase.ts +23 -0
- package/api/types/DeploymentReporting.ts +46 -0
- package/api/types/DeploymentSearchTerms.ts +39 -0
- package/api/types/DeploymentSortTerm.ts +25 -0
- package/api/types/DeploymentStatistics.ts +15 -0
- package/api/types/DeploymentStatusStatistics.ts +54 -0
- package/api/types/Device.ts +29 -0
- package/api/types/DeviceAggregation.ts +18 -0
- package/api/types/DeviceAggregationItem.ts +18 -0
- package/api/types/DeviceAggregationTerm.ts +27 -0
- package/api/types/DeviceAggregationTerms.ts +22 -0
- package/api/types/DeviceAttribute.ts +23 -0
- package/api/types/DeviceAttributeProjection.ts +15 -0
- package/api/types/DeviceAuditlogs.ts +17 -0
- package/api/types/DeviceAuthEvent.ts +23 -0
- package/api/types/DeviceConfiguration.ts +16 -0
- package/api/types/DeviceDeployment.ts +11 -0
- package/api/types/DeviceDeployments.ts +31 -0
- package/api/types/DeviceDeviceauth.ts +42 -0
- package/api/types/DeviceFilterAttribute.ts +22 -0
- package/api/types/DeviceFilterTerm.ts +41 -0
- package/api/types/DeviceInventory.ts +19 -0
- package/api/types/DeviceInventoryInventory.ts +19 -0
- package/api/types/DeviceReporting.ts +22 -0
- package/api/types/DeviceSearchTerms.ts +39 -0
- package/api/types/DeviceSortTerm.ts +29 -0
- package/api/types/DeviceState.ts +32 -0
- package/api/types/DeviceStateDeviceconnect.ts +33 -0
- package/api/types/DeviceStateIot_manager.ts +14 -0
- package/api/types/DeviceStatus.ts +19 -0
- package/api/types/DeviceWithImage.ts +80 -0
- package/api/types/DirectUploadMetadata.ts +18 -0
- package/api/types/EmailVerificationCompletion.ts +13 -0
- package/api/types/EmailVerificationRequest.ts +13 -0
- package/api/types/Error.ts +17 -0
- package/api/types/ErrorAuditlogs.ts +13 -0
- package/api/types/ErrorDeployments.ts +17 -0
- package/api/types/ErrorDeviceauth.ts +13 -0
- package/api/types/ErrorDeviceconfig.ts +17 -0
- package/api/types/ErrorDeviceconnect.ts +17 -0
- package/api/types/ErrorDevicemonitor.ts +17 -0
- package/api/types/ErrorExt.ts +18 -0
- package/api/types/ErrorInventory.ts +17 -0
- package/api/types/ErrorIot_manager.ts +17 -0
- package/api/types/ErrorNotFound.ts +17 -0
- package/api/types/ErrorReporting.ts +18 -0
- package/api/types/ErrorTenantadm.ts +13 -0
- package/api/types/ErrorUseradm.ts +17 -0
- package/api/types/Event.ts +48 -0
- package/api/types/ExternalIdentity.ts +17 -0
- package/api/types/Features.ts +17 -0
- package/api/types/FileUpload.ts +23 -0
- package/api/types/Filter.ts +19 -0
- package/api/types/FilterAttribute.ts +21 -0
- package/api/types/FilterDefinition.ts +18 -0
- package/api/types/FilterDeployments.ts +19 -0
- package/api/types/FilterInventory.ts +20 -0
- package/api/types/FilterPredicate.ts +78 -0
- package/api/types/FilterPredicateDeployments.ts +48 -0
- package/api/types/FilterPredicateInventory.ts +76 -0
- package/api/types/GeoBoundingBox.ts +10 -0
- package/api/types/GeoBoundingBoxFilter.ts +6 -0
- package/api/types/GeoDistance.ts +17 -0
- package/api/types/GeoDistanceFilter.ts +10 -0
- package/api/types/GeoPoint.ts +15 -0
- package/api/types/Group.ts +10 -0
- package/api/types/HTTP.ts +19 -0
- package/api/types/IdentityData.ts +25 -0
- package/api/types/InputParameter.ts +8 -0
- package/api/types/Integration.ts +24 -0
- package/api/types/JobStatus.ts +13 -0
- package/api/types/Limit.ts +21 -0
- package/api/types/LimitDeployments.ts +21 -0
- package/api/types/LimitDeviceauth.ts +10 -0
- package/api/types/LimitUseradm.ts +11 -0
- package/api/types/Limits.ts +11 -0
- package/api/types/LineDescriptor.ts +11 -0
- package/api/types/LogSubsystem.ts +23 -0
- package/api/types/LoginOptions.ts +18 -0
- package/api/types/ManagementAPIConfiguration.ts +5 -0
- package/api/types/MenderTypes.ts +183 -0
- package/api/types/MonitorConfiguration.ts +22 -0
- package/api/types/NewConfigurationDeployment.ts +15 -0
- package/api/types/NewConfigurationDeploymentResponse.ts +10 -0
- package/api/types/NewDeployment.ts +42 -0
- package/api/types/NewDeploymentForGroup.ts +33 -0
- package/api/types/NewDeploymentPhase.ts +37 -0
- package/api/types/NewDeploymentPhaseDeployments.ts +20 -0
- package/api/types/NewDeploymentV2.ts +31 -0
- package/api/types/NewTenant.ts +41 -0
- package/api/types/Object.ts +35 -0
- package/api/types/OwnUserUpdate.ts +21 -0
- package/api/types/PasswordResetCompletion.ts +17 -0
- package/api/types/PasswordResetRequest.ts +13 -0
- package/api/types/Permission.ts +21 -0
- package/api/types/PermissionObject.ts +14 -0
- package/api/types/PermissionSet.ts +22 -0
- package/api/types/PermissionSetWithScope.ts +15 -0
- package/api/types/PersonalAccessToken.ts +29 -0
- package/api/types/PersonalAccessTokenRequest.ts +18 -0
- package/api/types/Plan.ts +19 -0
- package/api/types/PlanBindingDetails.ts +13 -0
- package/api/types/PlanChangeRequest.ts +14 -0
- package/api/types/PlanLimits.ts +12 -0
- package/api/types/PreAuthSet.ts +16 -0
- package/api/types/Release.ts +32 -0
- package/api/types/ReleaseDeployments.ts +32 -0
- package/api/types/ReleaseUpdate.ts +13 -0
- package/api/types/Releases.ts +9 -0
- package/api/types/ReleasesDeleteError.ts +21 -0
- package/api/types/ReleasesDeployments.ts +9 -0
- package/api/types/Role.ts +19 -0
- package/api/types/RolePermission.ts +25 -0
- package/api/types/RolePermissionObject.ts +17 -0
- package/api/types/RoleUseradm.ts +19 -0
- package/api/types/SAMLMetadata.ts +21 -0
- package/api/types/Scope.ts +19 -0
- package/api/types/SelectAttribute.ts +17 -0
- package/api/types/ServiceSubsystem.ts +19 -0
- package/api/types/Settings.ts +23 -0
- package/api/types/SortCriteria.ts +30 -0
- package/api/types/SsoObject.ts +21 -0
- package/api/types/Status.ts +17 -0
- package/api/types/StatusDeviceauth.ts +18 -0
- package/api/types/StatusTenantadm.ts +19 -0
- package/api/types/StorageLimit.ts +17 -0
- package/api/types/SubscriptionData.ts +8 -0
- package/api/types/SupportRequest.ts +17 -0
- package/api/types/Tag.ts +27 -0
- package/api/types/Tags.ts +10 -0
- package/api/types/TaskResult.ts +13 -0
- package/api/types/TaskResultCLI.ts +10 -0
- package/api/types/TaskResultHTTPRequest.ts +8 -0
- package/api/types/TaskResultHTTPResponse.ts +10 -0
- package/api/types/Tenant.ts +38 -0
- package/api/types/TenantApiLimits.ts +12 -0
- package/api/types/TenantApiLimitsTenantadm.ts +12 -0
- package/api/types/TenantIdName.ts +14 -0
- package/api/types/TenantIdsData.ts +10 -0
- package/api/types/TenantInfo.ts +14 -0
- package/api/types/TenantShortInfo.ts +21 -0
- package/api/types/TenantTenantadm.ts +38 -0
- package/api/types/TenantsIdName.ts +6 -0
- package/api/types/Update.ts +17 -0
- package/api/types/UpdateDeployments.ts +18 -0
- package/api/types/UpdateFile.ts +13 -0
- package/api/types/UpdateFileDeployments.ts +13 -0
- package/api/types/UpdateTypes.ts +8 -0
- package/api/types/UpgradeCompleteRequest.ts +23 -0
- package/api/types/User.ts +13 -0
- package/api/types/UserAuditlogs.ts +13 -0
- package/api/types/UserNew.ts +26 -0
- package/api/types/UserUpdate.ts +25 -0
- package/api/types/UserUseradm.ts +45 -0
- package/api/types/UserWithTenantInfo.ts +8 -0
- package/api/types/XDeltaArgs.ts +9 -0
- package/api/types/XDeltaArgsDeployments.ts +9 -0
- package/api/types/XDeltaArgsLimits.ts +10 -0
- package/api/types/XDeltaArgsLimitsDeployments.ts +10 -0
- package/api/users-api.ts +34 -0
- package/appSlice/constants.ts +166 -0
- package/appSlice/index.ts +159 -0
- package/appSlice/reducer.test.ts +97 -0
- package/appSlice/selectors.ts +33 -0
- package/appSlice/thunks.test.ts +118 -0
- package/appSlice/thunks.ts +148 -0
- package/auth.test.ts +36 -0
- package/auth.ts +82 -0
- package/commonConstants.tsx +252 -0
- package/commonSelectors.ts +255 -0
- package/constants.ts +22 -0
- package/deploymentsSlice/constants.ts +175 -0
- package/deploymentsSlice/index.ts +145 -0
- package/deploymentsSlice/reducer.test.ts +108 -0
- package/deploymentsSlice/selectors.ts +58 -0
- package/deploymentsSlice/thunks.test.ts +425 -0
- package/deploymentsSlice/thunks.ts +382 -0
- package/devicesSlice/constants.ts +56 -0
- package/devicesSlice/index.ts +207 -0
- package/devicesSlice/reducer.test.ts +194 -0
- package/devicesSlice/selectors.ts +127 -0
- package/devicesSlice/thunks.test.tsx +1245 -0
- package/devicesSlice/thunks.tsx +1098 -0
- package/monitorSlice/constants.ts +17 -0
- package/monitorSlice/index.ts +65 -0
- package/monitorSlice/reducer.test.ts +79 -0
- package/monitorSlice/selectors.ts +15 -0
- package/monitorSlice/thunks.test.ts +112 -0
- package/monitorSlice/thunks.ts +101 -0
- package/onboardingSlice/constants.ts +53 -0
- package/onboardingSlice/index.ts +60 -0
- package/onboardingSlice/reducer.test.ts +45 -0
- package/onboardingSlice/selectors.ts +15 -0
- package/onboardingSlice/thunks.test.ts +235 -0
- package/onboardingSlice/thunks.ts +142 -0
- package/organizationSlice/constants.ts +82 -0
- package/organizationSlice/index.ts +111 -0
- package/organizationSlice/reducer.test.ts +78 -0
- package/organizationSlice/selectors.ts +45 -0
- package/organizationSlice/thunks.test.ts +619 -0
- package/organizationSlice/thunks.ts +383 -0
- package/package.json +38 -0
- package/releasesSlice/constants.ts +18 -0
- package/releasesSlice/index.ts +116 -0
- package/releasesSlice/reducer.test.ts +78 -0
- package/releasesSlice/selectors.ts +33 -0
- package/releasesSlice/thunks.test.ts +389 -0
- package/releasesSlice/thunks.ts +379 -0
- package/selectors.ts +22 -0
- package/store.ts +89 -0
- package/storehooks.test.tsx +386 -0
- package/storehooks.ts +250 -0
- package/thunks.ts +21 -0
- package/tsconfig.json +11 -0
- package/tsup.config.ts +11 -0
- package/usersSlice/constants.ts +374 -0
- package/usersSlice/index.ts +151 -0
- package/usersSlice/reducer.test.ts +141 -0
- package/usersSlice/selectors.ts +60 -0
- package/usersSlice/thunks.test.ts +660 -0
- package/usersSlice/thunks.ts +773 -0
- package/utils.test.ts +187 -0
- package/utils.ts +249 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
// Copyright 2024 Northern.tech AS
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
// @ts-nocheck
|
|
15
|
+
|
|
16
|
+
/*eslint import/namespace: ['error', { allowComputed: true }]*/
|
|
17
|
+
import storeActions from '@northern.tech/store/actions';
|
|
18
|
+
import GeneralApi from '@northern.tech/store/api/general-api';
|
|
19
|
+
import { DEVICE_LIST_DEFAULTS, SORTING_OPTIONS, TIMEOUTS, headerNames } from '@northern.tech/store/constants';
|
|
20
|
+
import { getDevicesById, getGlobalSettings, getUserCapabilities } from '@northern.tech/store/selectors';
|
|
21
|
+
import { commonErrorHandler } from '@northern.tech/store/store';
|
|
22
|
+
import { getDeviceAuth, getDeviceById, saveGlobalSettings } from '@northern.tech/store/thunks';
|
|
23
|
+
import { mapTermsToFilters } from '@northern.tech/store/utils';
|
|
24
|
+
import { deepCompare, isEmpty, standardizePhases, startTimeSort } from '@northern.tech/utils/helpers';
|
|
25
|
+
import { createAsyncThunk } from '@reduxjs/toolkit';
|
|
26
|
+
import isUUID from 'validator/lib/isUUID';
|
|
27
|
+
|
|
28
|
+
import { actions, sliceName } from '.';
|
|
29
|
+
import { DEPLOYMENT_ROUTES, DEPLOYMENT_STATES, DEPLOYMENT_TYPES, deploymentPrototype, deploymentsApiUrl, deploymentsApiUrlV2 } from './constants';
|
|
30
|
+
import { getDeploymentsById, getDeploymentsByStatus as getDeploymentsByStatusSelector } from './selectors';
|
|
31
|
+
|
|
32
|
+
const { receivedDevice, setSnackbar } = storeActions;
|
|
33
|
+
|
|
34
|
+
// default per page until pagination and counting integrated
|
|
35
|
+
const { page: defaultPage, perPage: defaultPerPage } = DEVICE_LIST_DEFAULTS;
|
|
36
|
+
|
|
37
|
+
export const deriveDeploymentGroup = ({ filter = {}, group, groups = [], name }) => (group || (groups.length === 1 && !isUUID(name)) ? groups[0] : filter.name);
|
|
38
|
+
|
|
39
|
+
const transformDeployments = (deployments, deploymentsById) =>
|
|
40
|
+
deployments.sort(startTimeSort).reduce(
|
|
41
|
+
(accu, item) => {
|
|
42
|
+
const filter = item.filter ?? {};
|
|
43
|
+
let deployment = {
|
|
44
|
+
...deploymentPrototype,
|
|
45
|
+
...deploymentsById[item.id],
|
|
46
|
+
...item,
|
|
47
|
+
filter: item.filter ? { ...filter, name: filter.name ?? filter.id, filters: mapTermsToFilters(filter.terms) } : undefined,
|
|
48
|
+
name: decodeURIComponent(item.name)
|
|
49
|
+
};
|
|
50
|
+
// deriving the group in a second step to potentially make use of the merged data from the existing group state + the decoded name
|
|
51
|
+
deployment = { ...deployment, group: deriveDeploymentGroup(deployment) };
|
|
52
|
+
accu.deployments[item.id] = deployment;
|
|
53
|
+
accu.deploymentIds.push(item.id);
|
|
54
|
+
return accu;
|
|
55
|
+
},
|
|
56
|
+
{ deployments: {}, deploymentIds: [] }
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
/*Deployments */
|
|
60
|
+
export const getDeploymentsByStatus = createAsyncThunk(`${sliceName}/getDeploymentsByStatus`, (options = {}, { dispatch, getState }) => {
|
|
61
|
+
const { status, page = defaultPage, per_page = defaultPerPage, startDate, endDate, group, type, shouldSelect = true, sort = SORTING_OPTIONS.desc } = options;
|
|
62
|
+
const created_after = startDate ? `&created_after=${startDate}` : '';
|
|
63
|
+
const created_before = endDate ? `&created_before=${endDate}` : '';
|
|
64
|
+
const search = group ? `&search=${group}` : '';
|
|
65
|
+
const typeFilter = type ? `&type=${type}` : '';
|
|
66
|
+
return GeneralApi.get(
|
|
67
|
+
`${deploymentsApiUrl}/deployments?status=${status}&per_page=${per_page}&page=${page}${created_after}${created_before}${search}${typeFilter}&sort=${sort}`
|
|
68
|
+
).then(res => {
|
|
69
|
+
const { deployments, deploymentIds } = transformDeployments(res.data, getState().deployments.byId);
|
|
70
|
+
const total = Number(res.headers[headerNames.total]);
|
|
71
|
+
let tasks = [
|
|
72
|
+
dispatch(actions.receivedDeployments(deployments)),
|
|
73
|
+
dispatch(
|
|
74
|
+
actions.receivedDeploymentsForStatus({
|
|
75
|
+
deploymentIds,
|
|
76
|
+
status,
|
|
77
|
+
total: !(startDate || endDate || group || type) ? total : getState().deployments.byStatus[status].total
|
|
78
|
+
})
|
|
79
|
+
)
|
|
80
|
+
];
|
|
81
|
+
tasks = deploymentIds.reduce((accu, deploymentId) => {
|
|
82
|
+
if (deployments[deploymentId].type === DEPLOYMENT_TYPES.configuration) {
|
|
83
|
+
accu.push(dispatch(getSingleDeployment(deploymentId)));
|
|
84
|
+
}
|
|
85
|
+
return accu;
|
|
86
|
+
}, tasks);
|
|
87
|
+
if (shouldSelect) {
|
|
88
|
+
tasks.push(dispatch(actions.selectDeploymentsForStatus({ deploymentIds, status, total })));
|
|
89
|
+
}
|
|
90
|
+
tasks.push({ deploymentIds, total });
|
|
91
|
+
return Promise.all(tasks);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const MAX_PREVIOUS_PHASES_COUNT = 5;
|
|
96
|
+
export const createDeployment = createAsyncThunk(`${sliceName}/createDeployment`, ({ newDeployment, hasNewRetryDefault = false }, { dispatch, getState }) => {
|
|
97
|
+
let request;
|
|
98
|
+
if (newDeployment.filter_id) {
|
|
99
|
+
request = GeneralApi.post(`${deploymentsApiUrlV2}/deployments`, newDeployment);
|
|
100
|
+
} else if (newDeployment.group) {
|
|
101
|
+
request = GeneralApi.post(`${deploymentsApiUrl}/deployments/group/${newDeployment.group}`, newDeployment);
|
|
102
|
+
} else {
|
|
103
|
+
request = GeneralApi.post(`${deploymentsApiUrl}/deployments`, newDeployment);
|
|
104
|
+
}
|
|
105
|
+
return request
|
|
106
|
+
.catch(err => commonErrorHandler(err, 'Error creating deployment.', dispatch))
|
|
107
|
+
.then(data => {
|
|
108
|
+
const lastslashindex = data.headers.location.lastIndexOf('/');
|
|
109
|
+
const deploymentId = data.headers.location.substring(lastslashindex + 1);
|
|
110
|
+
const deployment = {
|
|
111
|
+
...newDeployment,
|
|
112
|
+
id: deploymentId,
|
|
113
|
+
devices: newDeployment.devices ? newDeployment.devices.map(id => ({ id, status: 'pending' })) : [],
|
|
114
|
+
statistics: { status: {} }
|
|
115
|
+
};
|
|
116
|
+
let tasks = [
|
|
117
|
+
dispatch(actions.createdDeployment(deployment)),
|
|
118
|
+
dispatch(getSingleDeployment(deploymentId)),
|
|
119
|
+
dispatch(setSnackbar('Deployment created successfully', TIMEOUTS.fiveSeconds))
|
|
120
|
+
];
|
|
121
|
+
const { canManageUsers } = getUserCapabilities(getState());
|
|
122
|
+
if (canManageUsers) {
|
|
123
|
+
const { phases, retries } = newDeployment;
|
|
124
|
+
const { previousPhases = [], retries: previousRetries = 0 } = getGlobalSettings(getState());
|
|
125
|
+
let newSettings = { retries: hasNewRetryDefault ? retries : previousRetries, hasDeployments: true };
|
|
126
|
+
if (phases) {
|
|
127
|
+
const standardPhases = standardizePhases(phases);
|
|
128
|
+
let prevPhases = previousPhases.map(standardizePhases);
|
|
129
|
+
if (!prevPhases.find(previousPhaseList => previousPhaseList.every(oldPhase => standardPhases.find(phase => deepCompare(phase, oldPhase))))) {
|
|
130
|
+
prevPhases.push(standardPhases);
|
|
131
|
+
}
|
|
132
|
+
newSettings.previousPhases = prevPhases.slice(-1 * MAX_PREVIOUS_PHASES_COUNT);
|
|
133
|
+
}
|
|
134
|
+
tasks.push(dispatch(saveGlobalSettings(newSettings)));
|
|
135
|
+
}
|
|
136
|
+
return Promise.all(tasks);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
export const getDeploymentDevices = createAsyncThunk(`${sliceName}/getDeploymentDevices`, (options, { dispatch, getState }) => {
|
|
141
|
+
const { id, page = defaultPage, perPage = defaultPerPage } = options;
|
|
142
|
+
return GeneralApi.get(`${deploymentsApiUrl}/deployments/${id}/devices/list?&page=${page}&per_page=${perPage}`).then(response => {
|
|
143
|
+
const { devices: deploymentDevices = {} } = getState().deployments.byId[id] || {};
|
|
144
|
+
const devices = response.data.reduce((accu, item) => {
|
|
145
|
+
accu[item.id] = item;
|
|
146
|
+
const log = (deploymentDevices[item.id] || {}).log;
|
|
147
|
+
if (log) {
|
|
148
|
+
accu[item.id].log = log;
|
|
149
|
+
}
|
|
150
|
+
return accu;
|
|
151
|
+
}, {});
|
|
152
|
+
const selectedDeviceIds = Object.keys(devices);
|
|
153
|
+
let tasks = [
|
|
154
|
+
dispatch(
|
|
155
|
+
actions.receivedDeploymentDevices({
|
|
156
|
+
id,
|
|
157
|
+
devices,
|
|
158
|
+
selectedDeviceIds,
|
|
159
|
+
totalDeviceCount: Number(response.headers[headerNames.total])
|
|
160
|
+
})
|
|
161
|
+
)
|
|
162
|
+
];
|
|
163
|
+
const devicesById = getDevicesById(getState());
|
|
164
|
+
// only update those that have changed & lack data
|
|
165
|
+
const lackingData = selectedDeviceIds.reduce((accu, deviceId) => {
|
|
166
|
+
const device = devicesById[deviceId];
|
|
167
|
+
if (!device || !device.identity_data || !device.attributes || Object.keys(device.attributes).length === 0) {
|
|
168
|
+
accu.push(deviceId);
|
|
169
|
+
}
|
|
170
|
+
return accu;
|
|
171
|
+
}, []);
|
|
172
|
+
// get device artifact, inventory and identity details not listed in schedule data
|
|
173
|
+
tasks = lackingData.reduce((accu, deviceId) => [...accu, dispatch(getDeviceById(deviceId)), dispatch(getDeviceAuth(deviceId))], tasks);
|
|
174
|
+
return Promise.all(tasks);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const parseDeviceDeployment = ({
|
|
179
|
+
deployment: { id, artifact_name: release, status: deploymentStatus },
|
|
180
|
+
device: { created, deleted, id: deviceId, finished, status, log }
|
|
181
|
+
}) => ({
|
|
182
|
+
id,
|
|
183
|
+
release,
|
|
184
|
+
created,
|
|
185
|
+
deleted,
|
|
186
|
+
deviceId,
|
|
187
|
+
finished,
|
|
188
|
+
status,
|
|
189
|
+
log,
|
|
190
|
+
route: Object.values(DEPLOYMENT_ROUTES).reduce((accu, { key, states }) => {
|
|
191
|
+
if (!accu) {
|
|
192
|
+
return states.includes(deploymentStatus) ? key : accu;
|
|
193
|
+
}
|
|
194
|
+
return accu;
|
|
195
|
+
}, '')
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
export const getDeviceDeployments = createAsyncThunk(`${sliceName}/getDeviceDeployments`, (options, { dispatch }) => {
|
|
199
|
+
const { deviceId, filterSelection = [], page = defaultPage, perPage = defaultPerPage } = options;
|
|
200
|
+
const filters = filterSelection.map(item => `&status=${item}`).join('');
|
|
201
|
+
return GeneralApi.get(`${deploymentsApiUrl}/deployments/devices/${deviceId}?page=${page}&per_page=${perPage}${filters}`)
|
|
202
|
+
.then(({ data, headers }) =>
|
|
203
|
+
Promise.resolve(
|
|
204
|
+
dispatch(
|
|
205
|
+
receivedDevice({
|
|
206
|
+
id: deviceId,
|
|
207
|
+
deviceDeployments: data.map(parseDeviceDeployment),
|
|
208
|
+
deploymentsCount: Number(headers[headerNames.total])
|
|
209
|
+
})
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
.catch(err => commonErrorHandler(err, 'There was an error retrieving the device deployment history:', dispatch));
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
export const resetDeviceDeployments = createAsyncThunk(`${sliceName}/resetDeviceDeployments`, (deviceId, { dispatch }) =>
|
|
217
|
+
GeneralApi.delete(`${deploymentsApiUrl}/deployments/devices/${deviceId}/history`)
|
|
218
|
+
.then(() => Promise.resolve(dispatch(getDeviceDeployments({ deviceId }))))
|
|
219
|
+
.catch(err => commonErrorHandler(err, 'There was an error resetting the device deployment history:', dispatch))
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
export const getSingleDeployment = createAsyncThunk(`${sliceName}/getSingleDeployment`, (id, { dispatch, getState }) =>
|
|
223
|
+
GeneralApi.get(`${deploymentsApiUrl}/deployments/${id}`).then(({ data }) => {
|
|
224
|
+
const { deployments } = transformDeployments([data], getState().deployments.byId);
|
|
225
|
+
return Promise.resolve(dispatch(actions.receivedDeployment(deployments[id])));
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
export const getDeviceLog = createAsyncThunk(`${sliceName}/getDeviceLog`, ({ deploymentId, deviceId }, { dispatch }) =>
|
|
230
|
+
GeneralApi.get(`${deploymentsApiUrl}/deployments/${deploymentId}/devices/${deviceId}/log`)
|
|
231
|
+
.catch(e => {
|
|
232
|
+
console.log('no log here', e);
|
|
233
|
+
return Promise.reject();
|
|
234
|
+
})
|
|
235
|
+
.then(({ data: log }) =>
|
|
236
|
+
Promise.all([Promise.resolve(dispatch(actions.receivedDeploymentDeviceLog({ id: deploymentId, deviceId, log }))), Promise.resolve(log)])
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
export const abortDeployment = createAsyncThunk(`${sliceName}/abortDeployment`, (deploymentId, { dispatch, getState }) =>
|
|
241
|
+
GeneralApi.put(`${deploymentsApiUrl}/deployments/${deploymentId}/status`, { status: 'aborted' })
|
|
242
|
+
.then(() => {
|
|
243
|
+
const deploymentsByStatus = getDeploymentsByStatusSelector(getState());
|
|
244
|
+
let status = DEPLOYMENT_STATES.pending;
|
|
245
|
+
let index = deploymentsByStatus.pending.deploymentIds.findIndex(id => id === deploymentId);
|
|
246
|
+
if (index < 0) {
|
|
247
|
+
status = DEPLOYMENT_STATES.inprogress;
|
|
248
|
+
index = deploymentsByStatus.inprogress.deploymentIds.findIndex(id => id === deploymentId);
|
|
249
|
+
}
|
|
250
|
+
const deploymentIds = [...deploymentsByStatus[status].deploymentIds.slice(0, index), ...deploymentsByStatus[status].deploymentIds.slice(index + 1)];
|
|
251
|
+
const deploymentsById = getDeploymentsById(getState());
|
|
252
|
+
const deployments = deploymentIds.reduce((accu, id) => {
|
|
253
|
+
accu[id] = deploymentsById[id];
|
|
254
|
+
return accu;
|
|
255
|
+
}, {});
|
|
256
|
+
const total = Math.max(deploymentsByStatus[status].total - 1, 0);
|
|
257
|
+
return Promise.all([
|
|
258
|
+
dispatch(actions.receivedDeployments(deployments)),
|
|
259
|
+
dispatch(actions.receivedDeploymentsForStatus({ deploymentIds, status, total })),
|
|
260
|
+
dispatch(actions.removedDeployment(deploymentId)),
|
|
261
|
+
dispatch(setSnackbar('The deployment was successfully aborted'))
|
|
262
|
+
]);
|
|
263
|
+
})
|
|
264
|
+
.catch(err => commonErrorHandler(err, 'There was an error while aborting the deployment:', dispatch))
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
export const updateDeploymentControlMap = createAsyncThunk(`${sliceName}/updateDeploymentControlMap`, ({ deploymentId, updateControlMap }, { dispatch }) =>
|
|
268
|
+
GeneralApi.patch(`${deploymentsApiUrl}/deployments/${deploymentId}`, { update_control_map: updateControlMap })
|
|
269
|
+
.catch(err => commonErrorHandler(err, 'There was an error while updating the deployment status:', dispatch))
|
|
270
|
+
.then(() => Promise.resolve(dispatch(getSingleDeployment(deploymentId))))
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
export const setDeploymentsState = createAsyncThunk(`${sliceName}/setDeploymentsState`, (selection, { dispatch, getState }) => {
|
|
274
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
275
|
+
const { page, perPage, ...selectionState } = selection;
|
|
276
|
+
const currentState = getState().deployments.selectionState;
|
|
277
|
+
let nextState = {
|
|
278
|
+
...currentState,
|
|
279
|
+
...selectionState,
|
|
280
|
+
...Object.keys(DEPLOYMENT_STATES).reduce((accu, item) => {
|
|
281
|
+
accu[item] = {
|
|
282
|
+
...currentState[item],
|
|
283
|
+
...selectionState[item]
|
|
284
|
+
};
|
|
285
|
+
return accu;
|
|
286
|
+
}, {}),
|
|
287
|
+
general: {
|
|
288
|
+
...currentState.general,
|
|
289
|
+
...selectionState.general
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
let tasks = [dispatch(actions.setDeploymentsState(nextState))];
|
|
293
|
+
if (nextState.selectedId && currentState.selectedId !== nextState.selectedId) {
|
|
294
|
+
tasks.push(dispatch(getSingleDeployment(nextState.selectedId)));
|
|
295
|
+
}
|
|
296
|
+
return Promise.all(tasks);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const deltaAttributeMappings = [
|
|
300
|
+
{ here: 'compressionLevel', there: 'compression_level' },
|
|
301
|
+
{ here: 'disableChecksum', there: 'disable_checksum' },
|
|
302
|
+
{ here: 'disableDecompression', there: 'disable_external_decompression' },
|
|
303
|
+
{ here: 'sourceWindow', there: 'source_window_size' },
|
|
304
|
+
{ here: 'inputWindow', there: 'input_window_size' },
|
|
305
|
+
{ here: 'duplicatesWindow', there: 'compression_duplicates_window' },
|
|
306
|
+
{ here: 'instructionBuffer', there: 'instruction_buffer_size' }
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
const mapExternalDeltaConfig = (config = {}) =>
|
|
310
|
+
deltaAttributeMappings.reduce((accu, { here, there }) => {
|
|
311
|
+
if (config[there] !== undefined) {
|
|
312
|
+
accu[here] = config[there];
|
|
313
|
+
}
|
|
314
|
+
return accu;
|
|
315
|
+
}, {});
|
|
316
|
+
|
|
317
|
+
export const getDeploymentsConfig = createAsyncThunk(`${sliceName}/getDeploymentsConfig`, (_, { dispatch, getState }) =>
|
|
318
|
+
GeneralApi.get(`${deploymentsApiUrl}/config`).then(({ data }) => {
|
|
319
|
+
const oldConfig = getState().deployments.config;
|
|
320
|
+
const { delta = {} } = data;
|
|
321
|
+
const { binary_delta = {}, binary_delta_limits = {} } = delta;
|
|
322
|
+
const { xdelta_args = {}, timeout: timeoutConfig = oldConfig.binaryDelta.timeout } = binary_delta;
|
|
323
|
+
const { xdelta_args_limits = {}, timeout: timeoutLimit = oldConfig.binaryDeltaLimits.timeout } = binary_delta_limits;
|
|
324
|
+
const config = {
|
|
325
|
+
...oldConfig,
|
|
326
|
+
hasDelta: Boolean(delta.enabled),
|
|
327
|
+
binaryDelta: {
|
|
328
|
+
...oldConfig.binaryDelta,
|
|
329
|
+
timeout: timeoutConfig,
|
|
330
|
+
...mapExternalDeltaConfig(xdelta_args)
|
|
331
|
+
},
|
|
332
|
+
binaryDeltaLimits: {
|
|
333
|
+
...oldConfig.binaryDeltaLimits,
|
|
334
|
+
timeout: timeoutLimit,
|
|
335
|
+
...mapExternalDeltaConfig(xdelta_args_limits)
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
return Promise.resolve(dispatch(actions.setDeploymentsConfig(config)));
|
|
339
|
+
})
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// traverse a source object and remove undefined & empty object properties to only return an attribute if there really is content worth sending
|
|
343
|
+
const deepClean = source =>
|
|
344
|
+
Object.entries(source).reduce((accu, [key, value]) => {
|
|
345
|
+
if (value !== undefined) {
|
|
346
|
+
let cleanedValue = typeof value === 'object' ? deepClean(value) : value;
|
|
347
|
+
if (cleanedValue === undefined || (typeof cleanedValue === 'object' && isEmpty(cleanedValue))) {
|
|
348
|
+
return accu;
|
|
349
|
+
}
|
|
350
|
+
accu = { ...(accu ?? {}), [key]: cleanedValue };
|
|
351
|
+
}
|
|
352
|
+
return accu;
|
|
353
|
+
}, undefined);
|
|
354
|
+
|
|
355
|
+
export const saveDeltaDeploymentsConfig = createAsyncThunk(`${sliceName}/saveDeltaDeploymentsConfig`, (config, { dispatch, getState }) => {
|
|
356
|
+
const configChange = {
|
|
357
|
+
timeout: config.timeout,
|
|
358
|
+
xdelta_args: deltaAttributeMappings.reduce((accu, { here, there }) => {
|
|
359
|
+
if (config[here] !== undefined) {
|
|
360
|
+
accu[there] = config[here];
|
|
361
|
+
}
|
|
362
|
+
return accu;
|
|
363
|
+
}, {})
|
|
364
|
+
};
|
|
365
|
+
const result = deepClean(configChange);
|
|
366
|
+
if (!result) {
|
|
367
|
+
return Promise.resolve();
|
|
368
|
+
}
|
|
369
|
+
return GeneralApi.put(`${deploymentsApiUrl}/config/binary_delta`, result)
|
|
370
|
+
.catch(err => commonErrorHandler(err, 'There was a problem storing your delta artifact generation configuration.', dispatch))
|
|
371
|
+
.then(() => {
|
|
372
|
+
const oldConfig = getState().deployments.config;
|
|
373
|
+
const newConfig = {
|
|
374
|
+
...oldConfig,
|
|
375
|
+
binaryDelta: {
|
|
376
|
+
...oldConfig.binaryDelta,
|
|
377
|
+
...config
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
return Promise.all([dispatch(actions.setDeploymentsConfig(newConfig)), dispatch(setSnackbar('Settings saved successfully'))]);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Copyright 2019 Northern.tech AS
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import { DEVICE_FILTERING_OPTIONS, apiUrl } from '@northern.tech/store/constants';
|
|
15
|
+
|
|
16
|
+
export const emptyFilter = { key: null, value: '', operator: DEVICE_FILTERING_OPTIONS.$eq.key, scope: 'inventory' };
|
|
17
|
+
|
|
18
|
+
export const deviceAuthV2 = `${apiUrl.v2}/devauth`;
|
|
19
|
+
export const deviceConnect = `${apiUrl.v1}/deviceconnect`;
|
|
20
|
+
export const inventoryApiUrl = `${apiUrl.v1}/inventory`;
|
|
21
|
+
export const inventoryApiUrlV2 = `${apiUrl.v2}/inventory`;
|
|
22
|
+
export const deviceConfig = `${apiUrl.v1}/deviceconfig/configurations/device`;
|
|
23
|
+
export const reportingApiUrl = `${apiUrl.v1}/reporting`;
|
|
24
|
+
export const iotManagerBaseURL = `${apiUrl.v1}/iot-manager`;
|
|
25
|
+
|
|
26
|
+
// see https://github.com/mendersoftware/go-lib-micro/tree/master/ws
|
|
27
|
+
// for the description of proto_header and the consts
|
|
28
|
+
// *Note*: this needs to be aligned with mender-connect and deviceconnect.
|
|
29
|
+
export const DEVICE_MESSAGE_PROTOCOLS = {
|
|
30
|
+
Shell: 1
|
|
31
|
+
};
|
|
32
|
+
export const DEVICE_MESSAGE_TYPES = {
|
|
33
|
+
Delay: 'delay',
|
|
34
|
+
New: 'new',
|
|
35
|
+
Ping: 'ping',
|
|
36
|
+
Pong: 'pong',
|
|
37
|
+
Resize: 'resize',
|
|
38
|
+
Shell: 'shell',
|
|
39
|
+
Stop: 'stop'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// we can't include the dismiss state with the rest since this would include dismissed devices in several queries
|
|
43
|
+
export const DEVICE_DISMISSAL_STATE = 'dismiss';
|
|
44
|
+
export const DEVICE_STATES = {
|
|
45
|
+
accepted: 'accepted',
|
|
46
|
+
pending: 'pending',
|
|
47
|
+
preauth: 'preauthorized',
|
|
48
|
+
rejected: 'rejected'
|
|
49
|
+
};
|
|
50
|
+
export const DEVICE_CONNECT_STATES = {
|
|
51
|
+
connected: 'connected',
|
|
52
|
+
disconnected: 'disconnected',
|
|
53
|
+
unknown: 'unknown'
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const geoAttributes = ['geo-lat', 'geo-lon'].map(attribute => ({ attribute, scope: 'inventory' }));
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// Copyright 2023 Northern.tech AS
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
// @ts-nocheck
|
|
15
|
+
import { DEVICE_LIST_DEFAULTS, SORTING_OPTIONS } from '@northern.tech/store/commonConstants';
|
|
16
|
+
import { deepCompare, duplicateFilter } from '@northern.tech/utils/helpers';
|
|
17
|
+
import { createSlice } from '@reduxjs/toolkit';
|
|
18
|
+
|
|
19
|
+
import { DEVICE_STATES } from './constants';
|
|
20
|
+
|
|
21
|
+
export const sliceName = 'devices';
|
|
22
|
+
|
|
23
|
+
export const initialState = {
|
|
24
|
+
byId: {
|
|
25
|
+
// [deviceId]: {
|
|
26
|
+
// ...,
|
|
27
|
+
// twinsByIntegration: { [external.provider.id]: twinData }
|
|
28
|
+
// }
|
|
29
|
+
},
|
|
30
|
+
byStatus: {
|
|
31
|
+
[DEVICE_STATES.accepted]: { deviceIds: [], total: 0 },
|
|
32
|
+
active: { deviceIds: [], total: 0 },
|
|
33
|
+
inactive: { deviceIds: [], total: 0 },
|
|
34
|
+
[DEVICE_STATES.pending]: { deviceIds: [], total: 0 },
|
|
35
|
+
[DEVICE_STATES.preauth]: { deviceIds: [], total: 0 },
|
|
36
|
+
[DEVICE_STATES.rejected]: { deviceIds: [], total: 0 }
|
|
37
|
+
},
|
|
38
|
+
deviceList: {
|
|
39
|
+
deviceIds: [],
|
|
40
|
+
...DEVICE_LIST_DEFAULTS,
|
|
41
|
+
selectedAttributes: [],
|
|
42
|
+
selectedIssues: [],
|
|
43
|
+
selection: [],
|
|
44
|
+
sort: {
|
|
45
|
+
direction: SORTING_OPTIONS.desc
|
|
46
|
+
// key: null,
|
|
47
|
+
// scope: null
|
|
48
|
+
},
|
|
49
|
+
state: DEVICE_STATES.accepted,
|
|
50
|
+
total: 0
|
|
51
|
+
},
|
|
52
|
+
filters: [
|
|
53
|
+
// { key: 'device_type', value: 'raspberry', operator: '$eq', scope: 'inventory' }
|
|
54
|
+
],
|
|
55
|
+
filteringAttributes: { identityAttributes: [], inventoryAttributes: [], systemAttributes: [], tagAttributes: [] },
|
|
56
|
+
filteringAttributesLimit: 10,
|
|
57
|
+
filteringAttributesConfig: {
|
|
58
|
+
attributes: {
|
|
59
|
+
// inventory: ['some_attribute']
|
|
60
|
+
},
|
|
61
|
+
count: 0,
|
|
62
|
+
limit: 100
|
|
63
|
+
},
|
|
64
|
+
reports: [
|
|
65
|
+
// { items: [{ key: "someKey", count: 42 }], otherCount: 123, total: <otherCount + itemsCount> }
|
|
66
|
+
],
|
|
67
|
+
total: 0,
|
|
68
|
+
limit: 0,
|
|
69
|
+
groups: {
|
|
70
|
+
byId: {
|
|
71
|
+
// groupName: { deviceIds: [], total: 0, filters: [] },
|
|
72
|
+
// dynamo: { deviceIds: [], total: 3, filters: [{ a: 1 }] }
|
|
73
|
+
},
|
|
74
|
+
selectedGroup: undefined
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const devicesSlice = createSlice({
|
|
79
|
+
name: sliceName,
|
|
80
|
+
initialState,
|
|
81
|
+
reducers: {
|
|
82
|
+
receivedGroups: (state, action) => {
|
|
83
|
+
state.groups.byId = action.payload;
|
|
84
|
+
},
|
|
85
|
+
addToGroup: (state, action) => {
|
|
86
|
+
const { group, deviceIds } = action.payload;
|
|
87
|
+
const maybeExistingGroup = {
|
|
88
|
+
filters: [],
|
|
89
|
+
deviceIds: [],
|
|
90
|
+
...state.groups.byId[group]
|
|
91
|
+
};
|
|
92
|
+
state.groups.byId[group] = {
|
|
93
|
+
...maybeExistingGroup,
|
|
94
|
+
deviceIds: [...maybeExistingGroup.deviceIds, ...deviceIds].filter(duplicateFilter),
|
|
95
|
+
total: (maybeExistingGroup.total || 0) + 1
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
removeFromGroup: (state, action) => {
|
|
99
|
+
const { group, deviceIds: removedIds } = action.payload;
|
|
100
|
+
const { deviceIds = [], total = 0, ...maybeExistingGroup } = state.groups.byId[group] || {};
|
|
101
|
+
const changedGroup = {
|
|
102
|
+
...maybeExistingGroup,
|
|
103
|
+
deviceIds: deviceIds.filter(id => !removedIds.includes(id)),
|
|
104
|
+
total: Math.max(total - removedIds.length, 0)
|
|
105
|
+
};
|
|
106
|
+
if (changedGroup.total || changedGroup.deviceIds.length) {
|
|
107
|
+
state.groups.byId[group] = changedGroup;
|
|
108
|
+
return;
|
|
109
|
+
} else if (state.groups.selectedGroup === group) {
|
|
110
|
+
state.groups.selectedGroup = undefined;
|
|
111
|
+
}
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
113
|
+
const { [group]: removal, ...remainingById } = state.groups.byId;
|
|
114
|
+
state.groups.byId = remainingById;
|
|
115
|
+
},
|
|
116
|
+
addGroup: (state, action) => {
|
|
117
|
+
const { groupName, group } = action.payload;
|
|
118
|
+
state.groups.byId[groupName] = {
|
|
119
|
+
...state.groups.byId[groupName],
|
|
120
|
+
...group
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
selectGroup: (state, { payload: group }) => {
|
|
124
|
+
state.deviceList.deviceIds = state.groups.byId[group] && state.groups.byId[group].deviceIds.length > 0 ? state.groups.byId[group].deviceIds : [];
|
|
125
|
+
state.groups.selectedGroup = group;
|
|
126
|
+
},
|
|
127
|
+
removeGroup: (state, action) => {
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
129
|
+
const { [action.payload]: removal, ...remainingById } = state.groups.byId;
|
|
130
|
+
state.groups.byId = remainingById;
|
|
131
|
+
state.groups.selectedGroup = state.groups.selectedGroup === action.payload ? undefined : state.groups.selectedGroup;
|
|
132
|
+
},
|
|
133
|
+
setDeviceListState: (state, action) => {
|
|
134
|
+
state.deviceList = {
|
|
135
|
+
...state.deviceList,
|
|
136
|
+
...action.payload,
|
|
137
|
+
sort: {
|
|
138
|
+
...state.deviceList.sort,
|
|
139
|
+
...action.payload.sort
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
setFilterAttributes: (state, action) => {
|
|
144
|
+
state.filteringAttributes = action.payload;
|
|
145
|
+
},
|
|
146
|
+
setFilterablesConfig: (state, action) => {
|
|
147
|
+
state.filteringAttributesConfig = action.payload;
|
|
148
|
+
},
|
|
149
|
+
receivedDevices: (state, action) => {
|
|
150
|
+
state.byId = {
|
|
151
|
+
...state.byId,
|
|
152
|
+
...action.payload
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
setDeviceFilters: (state, action) => {
|
|
156
|
+
if (deepCompare(action.payload, state.filters)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
state.filters = action.payload.filter(filter => filter.key && filter.operator && filter.scope && typeof filter.value !== 'undefined');
|
|
160
|
+
},
|
|
161
|
+
setInactiveDevices: (state, action) => {
|
|
162
|
+
const { activeDeviceTotal, inactiveDeviceTotal } = action.payload;
|
|
163
|
+
state.byStatus.active.total = activeDeviceTotal;
|
|
164
|
+
state.byStatus.inactive.total = inactiveDeviceTotal;
|
|
165
|
+
},
|
|
166
|
+
setDeviceReports: (state, action) => {
|
|
167
|
+
state.reports = action.payload;
|
|
168
|
+
},
|
|
169
|
+
setDevicesByStatus: (state, action) => {
|
|
170
|
+
const { forceUpdate, status, total, deviceIds } = action.payload;
|
|
171
|
+
state.byStatus[status] = total || forceUpdate ? { deviceIds, total } : state.byStatus[status];
|
|
172
|
+
},
|
|
173
|
+
setDevicesCountByStatus: (state, action) => {
|
|
174
|
+
const { count, status } = action.payload;
|
|
175
|
+
state.byStatus[status].total = count;
|
|
176
|
+
},
|
|
177
|
+
setTotalDevices: (state, action) => {
|
|
178
|
+
state.total = action.payload;
|
|
179
|
+
},
|
|
180
|
+
setDeviceLimit: (state, action) => {
|
|
181
|
+
state.limit = action.payload;
|
|
182
|
+
},
|
|
183
|
+
receivedDevice: (state, action) => {
|
|
184
|
+
state.byId[action.payload.id] = {
|
|
185
|
+
...state.byId[action.payload.id],
|
|
186
|
+
...action.payload
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
maybeUpdateDevicesByStatus: (state, action) => {
|
|
190
|
+
const { deviceId, authId } = action.payload;
|
|
191
|
+
const device = state.byId[deviceId];
|
|
192
|
+
const hasMultipleAuthSets = authId ? device.auth_sets.filter(authset => authset.id !== authId).length > 0 : false;
|
|
193
|
+
if (!hasMultipleAuthSets && Object.values(DEVICE_STATES).includes(device.status)) {
|
|
194
|
+
const deviceIds = state.byStatus[device.status].deviceIds.filter(id => id !== deviceId);
|
|
195
|
+
state.byStatus[device.status] = { deviceIds, total: Math.max(0, state.byStatus[device.status].total - 1) };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// The following would likely be the way to go with a simpler store setup, but ours seems too entangled for this to provide much benefit
|
|
200
|
+
// import * as actionCreators from './thunks';
|
|
201
|
+
// extraReducers: {
|
|
202
|
+
// [actionCreators.setDeviceListState.fulfilled]: setDeviceListState
|
|
203
|
+
// }
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
export const actions = devicesSlice.actions;
|
|
207
|
+
export default devicesSlice.reducer;
|