mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
899f56acdc | ||
|
|
dc2760d5ff | ||
|
|
5ae4fd31f1 | ||
|
|
54d9d7c768 | ||
|
|
3a73781fca | ||
|
|
f3c5d4df6a | ||
|
|
b4c9135c58 | ||
|
|
a280d9c67c |
No files matched your search
@@ -43,6 +43,7 @@ fi
|
||||
|
||||
export AUTH_AUTHORITY=${AUTH_AUTHORITY:-https://$AUTH0_DOMAIN}
|
||||
export AUTH_CLIENT_ID=${AUTH_CLIENT_ID:-$AUTH0_CLIENT_ID}
|
||||
export AUTH_CLIENT_SECRET=${AUTH_CLIENT_SECRET}
|
||||
export AUTH_AUDIENCE=${AUTH_AUDIENCE:-$AUTH0_AUDIENCE}
|
||||
export AUTH_REDIRECT_URI=${AUTH_REDIRECT_URI}
|
||||
export AUTH_SILENT_REDIRECT_URI=${AUTH_SILENT_REDIRECT_URI}
|
||||
@@ -57,7 +58,7 @@ export NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
|
||||
echo "NetBird latest version: ${NETBIRD_LATEST_VERSION}"
|
||||
|
||||
# replace ENVs in the config
|
||||
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE"
|
||||
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_CLIENT_SECRET \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE"
|
||||
|
||||
MAIN_JS=$(find /usr/share/nginx/html/static/js/main.*js)
|
||||
OIDC_TRUSTED_DOMAINS="/usr/share/nginx/html/OidcTrustedDomains.js"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="29.435" height="7.636" viewBox="0 0 29.435 7.636">
|
||||
<path id="direct_bi" d="M3.64,8.158-.178,4.34,3.64.522,4.3,1.17l-2.7,2.7h25.89l-2.7-2.7.656-.648L29.257,4.34,27.213,6.384,25.439,8.158,24.783,7.5l2.7-2.693H1.595L4.3,7.5Z" transform="translate(0.178 -0.522)" fill="#1e429f"/>
|
||||
<svg width="39" height="6" viewBox="0 0 39 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.451731 2.66267C0.315048 2.79935 0.315048 3.02096 0.451731 3.15764L2.67912 5.38503C2.8158 5.52171 3.03741 5.52171 3.17409 5.38503C3.31078 5.24835 3.31078 5.02674 3.17409 4.89006L1.19419 2.91016L3.17409 0.930257C3.31078 0.793574 3.31078 0.571966 3.17409 0.435282C3.03741 0.298599 2.8158 0.298599 2.67912 0.435282L0.451731 2.66267ZM38.3807 3.15764C38.5174 3.02096 38.5174 2.79935 38.3807 2.66267L36.1533 0.435282C36.0166 0.298599 35.795 0.298599 35.6583 0.435282C35.5216 0.571966 35.5216 0.793574 35.6583 0.930257L37.6382 2.91016L35.6583 4.89006C35.5216 5.02674 35.5216 5.24835 35.6583 5.38503C35.795 5.52171 36.0166 5.52171 36.1533 5.38503L38.3807 3.15764ZM0.699219 3.26016H38.1332V2.56016H0.699219V3.26016Z" fill="#03543F"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 332 B After Width: | Height: | Size: 837 B |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="29.497" height="7.636" viewBox="0 0 29.497 7.636">
|
||||
<path id="direct_out" d="M26.589,8l-.656-.656,2.7-2.693H.91V3.713H28.635l-2.7-2.7.656-.648,3.818,3.818Z" transform="translate(-0.91 -0.364)" fill="#03543f"/>
|
||||
<svg width="39" height="6" viewBox="0 0 39 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M38.3807 3.15764C38.5174 3.02096 38.5174 2.79935 38.3807 2.66267L36.1533 0.435282C36.0166 0.298599 35.795 0.298599 35.6583 0.435282C35.5216 0.571966 35.5216 0.793574 35.6583 0.930257L37.6382 2.91016L35.6583 4.89006C35.5216 5.02674 35.5216 5.24835 35.6583 5.38503C35.795 5.52171 36.0166 5.52171 36.1533 5.38503L38.3807 3.15764ZM0.699219 3.26016H38.1332V2.56016H0.699219V3.26016Z" fill="#1E429F"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 506 B |
+658
-547
File diff suppressed because it is too large.
Load diff
+893
-525
File diff suppressed because it is too large.
Load diff
@@ -357,7 +357,7 @@ const UserEdit = () => {
|
||||
<Input
|
||||
disabled={user.id}
|
||||
value={formUser.email}
|
||||
style={{ color: "#5a5c5a" }}
|
||||
style={{ color: "#8c8c8c"}}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -371,10 +371,11 @@ const UserEdit = () => {
|
||||
>
|
||||
<Select
|
||||
style={{ width: "100%" }}
|
||||
|
||||
disabled={user.is_current}
|
||||
>
|
||||
<Option value="admin">admin</Option>
|
||||
<Option value="user">user</Option>
|
||||
<Option value="admin"><Text type={"secondary"}>admin</Text></Option>
|
||||
<Option value="user"><Text type={"secondary"}>user</Text></Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"auth0Auth": "$USE_AUTH0",
|
||||
"authAuthority": "$AUTH_AUTHORITY",
|
||||
"authClientId": "$AUTH_CLIENT_ID",
|
||||
"authClientSecret": "$AUTH_CLIENT_SECRET",
|
||||
"authScopesSupported": "$AUTH_SUPPORTED_SCOPES",
|
||||
"authAudience": "$AUTH_AUDIENCE",
|
||||
|
||||
|
||||
+2
-1
@@ -28,8 +28,9 @@ export function getConfig() {
|
||||
|
||||
return {
|
||||
auth0Auth: configJson.auth0Auth == "true", //due to substitution we can't use boolean in the config
|
||||
authority: configJson.authAuthority,
|
||||
authority: configJson.authAuthority.replace(/\/+$/, ''),
|
||||
clientId: configJson.authClientId,
|
||||
clientSecret: configJson.authClientSecret,
|
||||
scopesSupported: configJson.authScopesSupported,
|
||||
apiOrigin: configJson.apiOrigin,
|
||||
grpcApiOrigin: configJson.grpcApiOrigin,
|
||||
|
||||
+18
-1
@@ -5,7 +5,7 @@ body {
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ant-layout-header {
|
||||
background: #fff;
|
||||
padding: 0;
|
||||
@@ -163,4 +163,21 @@ td.non-highlighted-table-column {
|
||||
.CustomPopupCollapse .ant-collapse-header {
|
||||
padding-left: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.system-info-panel .ant-collapse-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.system-info-panel .ant-collapse-content-box {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.peers-form .ant-layout-header {
|
||||
line-height: 25px;
|
||||
height: auto;
|
||||
min-height: auto;
|
||||
padding-bottom: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
+2
-1
@@ -34,7 +34,8 @@ const providerConfig = {
|
||||
// service_worker_relative_url:'/OidcServiceWorker.js',
|
||||
service_worker_only: false,
|
||||
authority_configuration: config.auth0Auth ? auth0AuthorityConfig : undefined,
|
||||
...(config.audience ? {extras: {audience: config.audience}} : null)
|
||||
...(config.audience ? {extras: {audience: config.audience}} : null),
|
||||
...(config.clientSecret ? {token_request_extras: {client_secret: config.clientSecret}} : null)
|
||||
};
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
|
||||
+194
-138
@@ -1,167 +1,223 @@
|
||||
import { all, call, put, select, takeLatest } from 'redux-saga/effects';
|
||||
import { ApiError, ApiResponse, CreateResponse, DeleteResponse } from '../../services/api-client/types';
|
||||
import { Policy, PolicyRule } from './types'
|
||||
import service from './service';
|
||||
import serviceGroup from '../group/service';
|
||||
import actions from './actions';
|
||||
import { actions as groupActions } from '../group';
|
||||
import { all, call, put, select, takeLatest } from "redux-saga/effects";
|
||||
import {
|
||||
ApiError,
|
||||
ApiResponse,
|
||||
CreateResponse,
|
||||
DeleteResponse,
|
||||
} from "../../services/api-client/types";
|
||||
import { Policy, PolicyRule } from "./types";
|
||||
import service from "./service";
|
||||
import serviceGroup from "../group/service";
|
||||
import actions from "./actions";
|
||||
import { actions as groupActions } from "../group";
|
||||
import { Group } from "../group/types";
|
||||
|
||||
export function* getPolicies(action: ReturnType<typeof actions.getPolicies.request>): Generator {
|
||||
try {
|
||||
export function* getPolicies(
|
||||
action: ReturnType<typeof actions.getPolicies.request>
|
||||
): Generator {
|
||||
try {
|
||||
yield put(
|
||||
actions.setDeletedPolicy({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null,
|
||||
} as DeleteResponse<string | null>)
|
||||
);
|
||||
|
||||
yield put(actions.setDeletedPolicy({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null
|
||||
} as DeleteResponse<string | null>))
|
||||
const effect = yield call(service.getPolicies, action.payload);
|
||||
const response = effect as ApiResponse<Policy[]>;
|
||||
|
||||
const effect = yield call(service.getPolicies, action.payload);
|
||||
const response = effect as ApiResponse<Policy[]>;
|
||||
|
||||
yield put(actions.getPolicies.success(response.body));
|
||||
} catch (err) {
|
||||
yield put(actions.getPolicies.failure(err as ApiError));
|
||||
}
|
||||
yield put(actions.getPolicies.success(response.body));
|
||||
} catch (err) {
|
||||
yield put(actions.getPolicies.failure(err as ApiError));
|
||||
}
|
||||
}
|
||||
|
||||
export function* setCreatedPolicy(action: ReturnType<typeof actions.setSavedPolicy>): Generator {
|
||||
yield put(actions.setSavedPolicy(action.payload))
|
||||
export function* setCreatedPolicy(
|
||||
action: ReturnType<typeof actions.setSavedPolicy>
|
||||
): Generator {
|
||||
yield put(actions.setSavedPolicy(action.payload));
|
||||
}
|
||||
|
||||
function getNewGroupIds(dataString: string[], responses: Group[]): string[] {
|
||||
return responses.filter(r => dataString.includes(r.name)).map(r => r.id || '')
|
||||
return responses
|
||||
.filter((r) => dataString.includes(r.name))
|
||||
.map((r) => r.id || "");
|
||||
}
|
||||
|
||||
export function* savePolicy(action: ReturnType<typeof actions.savePolicy.request>): Generator {
|
||||
try {
|
||||
yield put(actions.setSavedPolicy({
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null
|
||||
} as CreateResponse<Policy | null>))
|
||||
export function* savePolicy(
|
||||
action: ReturnType<typeof actions.savePolicy.request>
|
||||
): Generator {
|
||||
try {
|
||||
yield put(
|
||||
actions.setSavedPolicy({
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null,
|
||||
} as CreateResponse<Policy | null>)
|
||||
);
|
||||
|
||||
const policyToSave = action.payload.payload
|
||||
|
||||
const responsesGroup = yield all(policyToSave.groupsToSave.map(g => call(serviceGroup.createGroup, {
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: { name: g }
|
||||
const policyToSave = action.payload.payload;
|
||||
const groupsToSave = policyToSave.groupsToSave
|
||||
? policyToSave.groupsToSave
|
||||
: [];
|
||||
const responsesGroup = yield all(
|
||||
groupsToSave.map((g) =>
|
||||
call(serviceGroup.createGroup, {
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: { name: g },
|
||||
})
|
||||
))
|
||||
)
|
||||
);
|
||||
|
||||
const resGroups = (responsesGroup as ApiResponse<Policy>[]).filter(r => r.statusCode === 200).map(r => (r.body as Group))
|
||||
const resGroups = (responsesGroup as ApiResponse<Policy>[])
|
||||
.filter((r) => r.statusCode === 200)
|
||||
.map((r) => r.body as Group);
|
||||
|
||||
const currentGroups = [...(yield select(state => state.group.data)) as Policy[]]
|
||||
const newGroups = [...currentGroups, ...resGroups]
|
||||
yield put(groupActions.getGroups.success(newGroups));
|
||||
const currentGroups = [
|
||||
...((yield select((state) => state.group.data)) as Policy[]),
|
||||
];
|
||||
const newGroups = [...currentGroups, ...resGroups];
|
||||
yield put(groupActions.getGroups.success(newGroups));
|
||||
|
||||
const newSources = getNewGroupIds(policyToSave.sourcesNoId, resGroups)
|
||||
const newDestinations = getNewGroupIds(policyToSave.destinationsNoId, resGroups)
|
||||
const newSources = getNewGroupIds(
|
||||
policyToSave.sourcesNoId ? policyToSave.sourcesNoId : [],
|
||||
resGroups
|
||||
);
|
||||
const newDestinations = getNewGroupIds(
|
||||
policyToSave.destinationsNoId ? policyToSave.destinationsNoId : [],
|
||||
resGroups
|
||||
);
|
||||
|
||||
const payloadToSave = {
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: {
|
||||
name: policyToSave.name,
|
||||
description: policyToSave.description,
|
||||
enabled: policyToSave.enabled,
|
||||
query: policyToSave.query
|
||||
} as Policy
|
||||
}
|
||||
if (policyToSave.rules.length > 0) {
|
||||
payloadToSave.payload.rules = []
|
||||
}
|
||||
policyToSave.rules.forEach((r) => {
|
||||
payloadToSave.payload.rules.push({
|
||||
name: r.name,
|
||||
description: r.description,
|
||||
enabled: r.enabled,
|
||||
sources: [...r.sources as string[], ...newSources],
|
||||
destinations: [...r.destinations as string[], ...newDestinations],
|
||||
bidirectional: r.bidirectional,
|
||||
protocol: r.protocol,
|
||||
ports: r.ports,
|
||||
action: r.action
|
||||
} as PolicyRule)
|
||||
})
|
||||
|
||||
let effect
|
||||
if (!policyToSave.id) {
|
||||
effect = yield call(service.createPolicy, payloadToSave);
|
||||
} else {
|
||||
payloadToSave.payload.id = policyToSave.id
|
||||
effect = yield call(service.editPolicy, payloadToSave);
|
||||
}
|
||||
|
||||
const response = effect as ApiResponse<Policy>;
|
||||
|
||||
yield put(actions.savePolicy.success({
|
||||
loading: false,
|
||||
success: true,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: response.body
|
||||
} as CreateResponse<Policy | null>));
|
||||
|
||||
yield put(groupActions.getGroups.request({ getAccessTokenSilently: action.payload.getAccessTokenSilently, payload: null }));
|
||||
yield put(actions.getPolicies.request({ getAccessTokenSilently: action.payload.getAccessTokenSilently, payload: null }));
|
||||
} catch (err) {
|
||||
yield put(actions.savePolicy.failure({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: true,
|
||||
error: err as ApiError,
|
||||
data: null
|
||||
} as CreateResponse<Policy | null>));
|
||||
const payloadToSave = {
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: {
|
||||
name: policyToSave.name,
|
||||
description: policyToSave.description,
|
||||
enabled: policyToSave.enabled,
|
||||
query: policyToSave.query,
|
||||
} as Policy,
|
||||
};
|
||||
if (policyToSave.rules.length > 0) {
|
||||
payloadToSave.payload.rules = [];
|
||||
}
|
||||
policyToSave.rules.forEach((r) => {
|
||||
payloadToSave.payload.rules.push({
|
||||
name: r.name,
|
||||
description: r.description,
|
||||
enabled: r.enabled,
|
||||
sources: [...(r.sources as string[]), ...newSources],
|
||||
destinations: [...(r.destinations as string[]), ...newDestinations],
|
||||
bidirectional: r.bidirectional,
|
||||
protocol: r.protocol,
|
||||
ports: r.ports,
|
||||
action: r.action,
|
||||
} as PolicyRule);
|
||||
});
|
||||
|
||||
let effect;
|
||||
if (!policyToSave.id) {
|
||||
effect = yield call(service.createPolicy, payloadToSave);
|
||||
} else {
|
||||
payloadToSave.payload.id = policyToSave.id;
|
||||
effect = yield call(service.editPolicy, payloadToSave);
|
||||
}
|
||||
|
||||
const response = effect as ApiResponse<Policy>;
|
||||
|
||||
yield put(
|
||||
actions.savePolicy.success({
|
||||
loading: false,
|
||||
success: true,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: response.body,
|
||||
} as CreateResponse<Policy | null>)
|
||||
);
|
||||
|
||||
yield put(
|
||||
groupActions.getGroups.request({
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: null,
|
||||
})
|
||||
);
|
||||
yield put(
|
||||
actions.getPolicies.request({
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: null,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
yield put(
|
||||
actions.savePolicy.failure({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: true,
|
||||
error: err as ApiError,
|
||||
data: null,
|
||||
} as CreateResponse<Policy | null>)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function* setDeletePolicy(action: ReturnType<typeof actions.setDeletedPolicy>): Generator {
|
||||
yield put(actions.setDeletedPolicy(action.payload))
|
||||
export function* setDeletePolicy(
|
||||
action: ReturnType<typeof actions.setDeletedPolicy>
|
||||
): Generator {
|
||||
yield put(actions.setDeletedPolicy(action.payload));
|
||||
}
|
||||
|
||||
export function* deletePolicy(action: ReturnType<typeof actions.deletePolicy.request>): Generator {
|
||||
try {
|
||||
yield call(actions.setDeletedPolicy, {
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null
|
||||
} as DeleteResponse<string | null>)
|
||||
export function* deletePolicy(
|
||||
action: ReturnType<typeof actions.deletePolicy.request>
|
||||
): Generator {
|
||||
try {
|
||||
yield call(actions.setDeletedPolicy, {
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null,
|
||||
} as DeleteResponse<string | null>);
|
||||
|
||||
const effect = yield call(service.deletedPolicy, action.payload);
|
||||
const response = effect as ApiResponse<any>;
|
||||
const effect = yield call(service.deletedPolicy, action.payload);
|
||||
const response = effect as ApiResponse<any>;
|
||||
|
||||
yield put(actions.deletePolicy.success({
|
||||
loading: false,
|
||||
success: true,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: response.body
|
||||
} as DeleteResponse<string | null>));
|
||||
yield put(
|
||||
actions.deletePolicy.success({
|
||||
loading: false,
|
||||
success: true,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: response.body,
|
||||
} as DeleteResponse<string | null>)
|
||||
);
|
||||
|
||||
const policies = (yield select(state => state.policy.data)) as Policy[]
|
||||
yield put(actions.getPolicies.success(policies.filter((p: Policy) => p.id !== action.payload.payload)))
|
||||
} catch (err) {
|
||||
yield put(actions.deletePolicy.failure({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: err as ApiError,
|
||||
data: null
|
||||
} as DeleteResponse<string | null>));
|
||||
}
|
||||
const policies = (yield select((state) => state.policy.data)) as Policy[];
|
||||
yield put(
|
||||
actions.getPolicies.success(
|
||||
policies.filter((p: Policy) => p.id !== action.payload.payload)
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
yield put(
|
||||
actions.deletePolicy.failure({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: err as ApiError,
|
||||
data: null,
|
||||
} as DeleteResponse<string | null>)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default function* sagas(): Generator {
|
||||
yield all([
|
||||
takeLatest(actions.getPolicies.request, getPolicies),
|
||||
takeLatest(actions.savePolicy.request, savePolicy),
|
||||
takeLatest(actions.deletePolicy.request, deletePolicy)
|
||||
]);
|
||||
yield all([
|
||||
takeLatest(actions.getPolicies.request, getPolicies),
|
||||
takeLatest(actions.savePolicy.request, savePolicy),
|
||||
takeLatest(actions.deletePolicy.request, deletePolicy),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface Policy {
|
||||
};
|
||||
|
||||
export interface PolicyToSave extends Policy {
|
||||
sourcesNoId: string[],
|
||||
destinationsNoId: string[],
|
||||
groupsToSave: string[]
|
||||
sourcesNoId?: string[],
|
||||
destinationsNoId?: string[],
|
||||
groupsToSave?: string[]
|
||||
};
|
||||
+720
-484
File diff suppressed because it is too large.
Load diff
+909
-633
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user