mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bce75c1ca9 | ||
|
|
0c09992b38 | ||
|
|
86b12f30d2 | ||
|
|
d3e34d8448 | ||
|
|
76083168f6 | ||
|
|
a54b3687ae | ||
|
|
25f154dc83 | ||
|
|
f3c7d877f8 | ||
|
|
7cea7e7f54 | ||
|
|
aaa351635f | ||
|
|
379ff5486e | ||
|
|
8bcd9918e2 | ||
|
|
044ccd0ce6 | ||
|
|
ab09ca3697 | ||
|
|
1644ed5dce | ||
|
|
cea459792f | ||
|
|
a402680816 |
No files matched your search
@@ -53,13 +53,10 @@ export NETBIRD_MGMT_API_ENDPOINT=$(echo $NETBIRD_MGMT_API_ENDPOINT | sed -E 's/(
|
||||
export NETBIRD_MGMT_GRPC_API_ENDPOINT=${NETBIRD_MGMT_GRPC_API_ENDPOINT}
|
||||
export NETBIRD_HOTJAR_TRACK_ID=${NETBIRD_HOTJAR_TRACK_ID}
|
||||
|
||||
REPO="https://github.com/netbirdio/netbird/"
|
||||
# this command will fetch the latest release e.g. v0.6.3
|
||||
export NETBIRD_LATEST_VERSION=$(basename $(curl -fs -o/dev/null -w %{redirect_url} ${REPO}releases/latest))
|
||||
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_LATEST_VERSION \$\$NETBIRD_HOTJAR_TRACK_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI"
|
||||
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"
|
||||
|
||||
MAIN_JS=$(find /usr/share/nginx/html/static/js/main.*js)
|
||||
OIDC_TRUSTED_DOMAINS="/usr/share/nginx/html/OidcTrustedDomains.js"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="robots" content="noindex">
|
||||
<meta
|
||||
name="description"
|
||||
content="NetBird Management Dashboard"
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
Disallow: /
|
||||
@@ -21,6 +21,7 @@ import {User} from "./store/user/types";
|
||||
import {SecureLoading} from "./components/Loading";
|
||||
import DNS from "./views/DNS";
|
||||
import Activity from "./views/Activity";
|
||||
import Settings from "./views/Settings";
|
||||
|
||||
|
||||
|
||||
@@ -107,6 +108,7 @@ function App() {
|
||||
<Route path="/users" component={withOidcSecure(Users)}/>
|
||||
<Route path="/dns" component={withOidcSecure(DNS)}/>
|
||||
<Route path="/activity" component={withOidcSecure(Activity)}/>
|
||||
<Route path="/settings" component={withOidcSecure(Settings)}/>
|
||||
</Switch>
|
||||
</Content>
|
||||
<FooterComponent/>
|
||||
|
||||
@@ -16,7 +16,7 @@ const AccessControlModalGroups:React.FC<Props> = ({data, title, visible, onCance
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal title={title} visible={isModalVisible} onCancel={() => onCancel()} footer={null}>
|
||||
<Modal title={title} open={isModalVisible} onCancel={() => onCancel()} footer={null}>
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={data as Group[] | undefined}
|
||||
|
||||
@@ -36,7 +36,7 @@ const Banner = () => {
|
||||
|
||||
if((!stored_banner_closed || stored_banner_closed !== 'true') ||
|
||||
(!store_banner_md5 || store_banner_md5 !== announcement_md5)) {
|
||||
setShow(true);
|
||||
//setShow(true);
|
||||
localStorage.setItem(banner_md5_key,announcement_md5);
|
||||
localStorage.setItem(banner_closed_key,'false');
|
||||
}
|
||||
|
||||
@@ -41,13 +41,14 @@ const Navbar = () => {
|
||||
{label: (<Link to="/routes">Network Routes</Link>), key: '/routes'},
|
||||
{ label: (<Link to="/dns">DNS</Link>), key: '/dns' },
|
||||
{label: (<Link to="/users">Users</Link>), key: '/users'},
|
||||
{label: (<Link to="/activity">Activity</Link>), key: '/activity'}
|
||||
{label: (<Link to="/activity">Activity</Link>), key: '/activity'},
|
||||
{label: (<Link to="/settings">Settings</Link>), key: '/settings'}
|
||||
] as ItemType[]
|
||||
|
||||
const userEmailKey = 'user-email'
|
||||
const userLogoutKey = 'user-logout'
|
||||
const userDividerKey = 'user-divider'
|
||||
const adminOnlyTabs = ["/setup-keys", "/acls", "/routes", "/dns", "/activity"]
|
||||
const adminOnlyTabs = ["/setup-keys", "/acls", "/routes", "/dns", "/activity", "/settings"]
|
||||
const [menuItems, setMenuItems] = useState(items)
|
||||
const logoutWithRedirect = () =>
|
||||
logout("/", {client_id: config.clientId});
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, {useEffect, useRef, useState} from 'react';
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "typesafe-actions";
|
||||
import {actions as peerActions} from '../store/peer';
|
||||
import {Button, Col, Collapse, Divider, Drawer, Form, Input, Row, Select, Space, Tag, Typography} from "antd";
|
||||
import {Button, Col, Collapse, Divider, Drawer, Form, Input, Radio, Row, Select, Space, Tag, Typography} from "antd";
|
||||
import {Header} from "antd/es/layout/layout";
|
||||
import type {CustomTagProps} from 'rc-select/lib/BaseSelect'
|
||||
import {FormPeer, Peer, PeerGroupsToSave} from "../store/peer/types";
|
||||
@@ -18,6 +18,7 @@ const {Panel} = Collapse;
|
||||
const punycode = require('punycode/')
|
||||
|
||||
const PeerUpdate = () => {
|
||||
|
||||
const {getAccessTokenSilently} = useGetAccessTokenSilently()
|
||||
const dispatch = useDispatch()
|
||||
const groups = useSelector((state: RootState) => state.group.data)
|
||||
@@ -200,6 +201,10 @@ const PeerUpdate = () => {
|
||||
return !formPeer.name || formPeer.name === peer.name
|
||||
}
|
||||
|
||||
const noUpdateToLoginExpiration = (): Boolean => {
|
||||
return formPeer.login_expiration_enabled === peer.login_expiration_enabled
|
||||
}
|
||||
|
||||
const onChange = (data: any) => {
|
||||
setFormPeer({...formPeer, ...data})
|
||||
}
|
||||
@@ -232,7 +237,8 @@ const PeerUpdate = () => {
|
||||
return {
|
||||
id: formPeer.id,
|
||||
ssh_enabled: formPeer.ssh_enabled,
|
||||
name: formPeer.name
|
||||
name: formPeer.name,
|
||||
login_expiration_enabled: formPeer.login_expiration_enabled
|
||||
} as Peer
|
||||
}
|
||||
|
||||
@@ -240,7 +246,7 @@ const PeerUpdate = () => {
|
||||
form.validateFields()
|
||||
.then((values) => {
|
||||
setSubmitRunning(true)
|
||||
if (!noUpdateToName()) {
|
||||
if (!noUpdateToName() || !noUpdateToLoginExpiration()) {
|
||||
const peerUpdate = createPeerToSave()
|
||||
setCallingPeerAPI(true)
|
||||
dispatch(peerActions.updatePeer.request({
|
||||
@@ -248,7 +254,7 @@ const PeerUpdate = () => {
|
||||
payload: peerUpdate
|
||||
}))
|
||||
}
|
||||
if (peerGroupsToSave.groupsToRemove.length || peerGroupsToSave.groupsToAdd.length || peerGroupsToSave.groupsNoId.length) {
|
||||
if (!noUpdateToGroups()) {
|
||||
setCallingGroupAPI(true)
|
||||
dispatch(peerActions.saveGroups.request({
|
||||
getAccessTokenSilently: getAccessTokenSilently,
|
||||
@@ -303,7 +309,7 @@ const PeerUpdate = () => {
|
||||
<Space style={{display: 'flex', justifyContent: 'end'}}>
|
||||
<Button onClick={onCancel} disabled={savedGroups.loading}>Cancel</Button>
|
||||
<Button type="primary"
|
||||
disabled={(savedGroups.loading || updatedPeers.loading) || (noUpdateToGroups() && noUpdateToName())}
|
||||
disabled={(savedGroups.loading || updatedPeers.loading) || (noUpdateToGroups() && noUpdateToName() && noUpdateToLoginExpiration())}
|
||||
onClick={handleFormSubmit}>Save</Button>
|
||||
</Space>
|
||||
}
|
||||
@@ -435,6 +441,20 @@ const PeerUpdate = () => {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)}
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="login_expiration_enabled"
|
||||
label="Login expiration"
|
||||
tooltip="When login expires, the user has to re-authenticate this peer. This only applies to peers added with the SSO login."
|
||||
>
|
||||
<Radio.Group
|
||||
options={[{label: 'Enabled', value: true}, {label: 'Disabled', value: false}]}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
disabled={!formPeer.user_id}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="groupsNames"
|
||||
@@ -458,23 +478,6 @@ const PeerUpdate = () => {
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
{/*<Col span={24}>
|
||||
<Row wrap={false} gutter={12}>
|
||||
<Col flex="none">
|
||||
<FlagFilled/>
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
<Paragraph>
|
||||
Every peer is part of the group All, thus you can't remove it.
|
||||
</Paragraph>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>*/}
|
||||
{/*<Col span={24}>
|
||||
<Divider orientation="left" plain style={{color: "#5a5c5a"}}>
|
||||
System Info
|
||||
</Divider>
|
||||
</Col>*/}
|
||||
<Col span={24}>
|
||||
<Collapse onChange={onChange} bordered={false} ghost={true}
|
||||
style={{color: "#5a5c5a"}}>
|
||||
|
||||
@@ -23,18 +23,18 @@ import {RootState} from "typesafe-actions";
|
||||
import {CloseOutlined, EditOutlined, QuestionCircleFilled} from "@ant-design/icons";
|
||||
import {FormSetupKey, SetupKey, SetupKeyToSave} from "../store/setup-key/types";
|
||||
import {Header} from "antd/es/layout/layout";
|
||||
import {formatDate, timeAgo} from "../utils/common";
|
||||
import {checkExpiresIn, formatDate, timeAgo} from "../utils/common";
|
||||
import {RuleObject} from "antd/lib/form";
|
||||
import {CustomTagProps} from "rc-select/lib/BaseSelect";
|
||||
import {Group} from "../store/group/types";
|
||||
import {useGetAccessTokenSilently} from "../utils/token";
|
||||
import ExpiresInInput, {ExpiresInValue} from "../views/ExpiresInInput";
|
||||
import ExpiresInInput, {expiresInToSeconds, ExpiresInValue} from "../views/ExpiresInInput";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
const {Text} = Typography;
|
||||
|
||||
const ExpiresInDefault: ExpiresInValue = {number: 30, interval: "Days"}
|
||||
const ExpiresInDefault: ExpiresInValue = {number: 30, interval: "day"}
|
||||
|
||||
const customExpiresFormat: DatePickerProps['format'] = value => {
|
||||
return formatDate(value)
|
||||
@@ -117,31 +117,6 @@ const SetupKeyNew = () => {
|
||||
usage_limit: formSetupKey.usage_limit
|
||||
} as SetupKeyToSave
|
||||
}
|
||||
const expiresInToSeconds = (expiresIn: ExpiresInValue): number => {
|
||||
if (!expiresIn.number || !expiresIn.interval) {
|
||||
return 0
|
||||
}
|
||||
let multiplier = 0
|
||||
switch (expiresIn.interval.toLowerCase()) {
|
||||
case "day":
|
||||
multiplier = 24 * 3600
|
||||
break
|
||||
case "week":
|
||||
multiplier = 7 * 24 * 3600
|
||||
break
|
||||
case "month":
|
||||
multiplier = 30 * 24 * 3600
|
||||
break
|
||||
case "year":
|
||||
multiplier = 365 * 24 * 3600
|
||||
break
|
||||
default:
|
||||
multiplier = 0
|
||||
}
|
||||
|
||||
return expiresIn.number * multiplier
|
||||
|
||||
}
|
||||
|
||||
const handleFormSubmit = () => {
|
||||
form.validateFields()
|
||||
@@ -290,13 +265,6 @@ const SetupKeyNew = () => {
|
||||
return setupKey.auto_groups?.filter(g => !formGroupIds.includes(g)).length > 0
|
||||
}
|
||||
|
||||
const checkExpiresIn = (_: any, value: { number: number }) => {
|
||||
if (value.number > 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error("Expiration must be greater than zero"));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{setupKey &&
|
||||
@@ -447,7 +415,12 @@ const SetupKeyNew = () => {
|
||||
<Col span={24}>
|
||||
<Form.Item name="expiresInFormatted" label="Expires In"
|
||||
rules={[{validator: checkExpiresIn}]}>
|
||||
<ExpiresInInput/>
|
||||
<ExpiresInInput options={
|
||||
Array.of(
|
||||
{key: "day", title: "Days"},
|
||||
{key: "month", title: "Months"},
|
||||
{key: "year", title: "Years"})
|
||||
}/>
|
||||
</Form.Item>
|
||||
</Col>}
|
||||
<Col span={12}>
|
||||
@@ -455,7 +428,8 @@ const SetupKeyNew = () => {
|
||||
label="Usage Limit"
|
||||
tooltip="Limit the number of times this key can be used. Use 0 for unlimited use."
|
||||
>
|
||||
<InputNumber min={0} defaultValue={0} disabled={setupKey.id || formSetupKey.type !== "reusable"}
|
||||
<InputNumber min={0} defaultValue={0}
|
||||
disabled={setupKey.id || formSetupKey.type !== "reusable"}
|
||||
style={{width: "100%"}}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {actions as userActions} from "../store/user";
|
||||
import {useGetAccessTokenSilently} from "../utils/token";
|
||||
import {useOidcUser} from "@axa-fr/react-oidc";
|
||||
|
||||
const {Paragraph, Text} = Typography;
|
||||
const {Paragraph} = Typography;
|
||||
|
||||
const {confirm} = Modal;
|
||||
|
||||
@@ -358,7 +358,7 @@ const UserUpdate = () => {
|
||||
>
|
||||
<Select
|
||||
style={{width: '100%'}}
|
||||
disabled={currentUser.role != null && currentUser.role !== "admin"}>
|
||||
disabled={(currentUser.role != null && currentUser.role !== "admin") || formUser.is_current}>
|
||||
<Option value="admin">admin</Option>
|
||||
<Option value="user">user</Option>
|
||||
</Select>
|
||||
|
||||
@@ -30,13 +30,23 @@ export const LinuxTab = () => {
|
||||
} as StepCommand,
|
||||
{
|
||||
key: 3,
|
||||
title: 'Start NetBird daemon:',
|
||||
commands: [
|
||||
`sudo netbird service install`,
|
||||
`sudo netbird service start`
|
||||
].join('\n'),
|
||||
copied: false,
|
||||
showCopyButton: true
|
||||
} as StepCommand,
|
||||
{
|
||||
key: 4,
|
||||
title: 'Run NetBird and log in the browser:',
|
||||
commands: formatNetBirdUP(),
|
||||
copied: false,
|
||||
showCopyButton: true
|
||||
} as StepCommand,
|
||||
{
|
||||
key: 4,
|
||||
key: 5,
|
||||
title: 'Get your IP address:',
|
||||
commands: [
|
||||
`sudo ifconfig utun100`
|
||||
|
||||
@@ -3,18 +3,15 @@ import React, {useState} from 'react';
|
||||
import { Button } from "antd";
|
||||
import TabSteps from "./TabSteps";
|
||||
import { StepCommand } from "./types"
|
||||
import {getConfig} from "../../config";
|
||||
const {latestVersion} = getConfig();
|
||||
|
||||
export const WindowsTab = () => {
|
||||
|
||||
const releaseVersion = latestVersion ? latestVersion.replace("v", "") : "0.6.3"
|
||||
const [steps, setSteps] = useState([
|
||||
{
|
||||
key: 1,
|
||||
title: 'Download and run Windows installer:',
|
||||
commands: (
|
||||
<Button type="primary" href={`https://github.com/netbirdio/netbird/releases/download/v${releaseVersion}/netbird_installer_${releaseVersion}_windows_amd64.exe`} target="_blank">Download NetBird</Button>
|
||||
<Button type="primary" href="https://pkgs.netbird.io/windows/x64" target="_blank">Download NetBird</Button>
|
||||
),
|
||||
copied: false
|
||||
} as StepCommand,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
"apiOrigin": "$NETBIRD_MGMT_API_ENDPOINT",
|
||||
"grpcApiOrigin": "$NETBIRD_MGMT_GRPC_API_ENDPOINT",
|
||||
"latestVersion": "$NETBIRD_LATEST_VERSION",
|
||||
"hotjarTrackID": "$NETBIRD_HOTJAR_TRACK_ID",
|
||||
"redirectURI": "$AUTH_REDIRECT_URI",
|
||||
"silentRedirectURI": "$AUTH_SILENT_REDIRECT_URI"
|
||||
|
||||
@@ -28,7 +28,6 @@ export function getConfig() {
|
||||
scopesSupported: configJson.authScopesSupported,
|
||||
apiOrigin: configJson.apiOrigin,
|
||||
grpcApiOrigin: configJson.grpcApiOrigin,
|
||||
latestVersion: configJson.latestVersion,
|
||||
audience: configJson.authAudience,
|
||||
hotjarTrackID: configJson.hotjarTrackID,
|
||||
redirectURI: redirectURI,
|
||||
|
||||
@@ -3,12 +3,6 @@ import axios from 'axios';
|
||||
import {ApiError, ApiRequestParams, ApiResponse} from './types';
|
||||
import {headersFactory, RequestHeader} from './header-factory';
|
||||
|
||||
/*axios.interceptors.response.use(undefined, err => {
|
||||
let res = err.response;
|
||||
if (res.status === 401) {
|
||||
}
|
||||
})*/
|
||||
|
||||
async function apiRequest<T>(params: ApiRequestParams): Promise<ApiResponse<T>> {
|
||||
const data = params.data ? (params.data as any).payload : undefined;
|
||||
const url = `${params.urlBase}${params.url}`;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import {ActionType, createAction, createAsyncAction} from 'typesafe-actions';
|
||||
import {Account} from './types';
|
||||
import {ApiError, ChangeResponse, RequestPayload} from '../../services/api-client/types';
|
||||
|
||||
const actions = {
|
||||
getAccounts: createAsyncAction(
|
||||
'GET_ACCOUNTS_REQUEST',
|
||||
'GET_ACCOUNTS_SUCCESS',
|
||||
'GET_ACCOUNTS_FAILURE',
|
||||
)<RequestPayload<null>, Account[], ApiError>(),
|
||||
|
||||
updateAccount: createAsyncAction(
|
||||
'UPDATE_ACCOUNT',
|
||||
'UPDATE_ACCOUNT_SUCCESS',
|
||||
'UPDATE_ACCOUNT_FAILURE',
|
||||
)<RequestPayload<Account>, ChangeResponse<Account | null>, ChangeResponse<Account | null>>(),
|
||||
setUpdateAccount: createAction('SET_UPDATED_ACCOUNT')<ChangeResponse<Account | null>>(),
|
||||
resetUpdateAccount: createAction('RESET_UPDATED_ACCOUNT')<null>(),
|
||||
};
|
||||
|
||||
|
||||
export type ActionTypes = ActionType<typeof actions>;
|
||||
export default actions;
|
||||
@@ -0,0 +1,7 @@
|
||||
import actions, { ActionTypes as _actionTypes } from './actions';
|
||||
import reducer from './reducer';
|
||||
import sagas from './sagas';
|
||||
|
||||
export type ActionTypes = _actionTypes;
|
||||
|
||||
export { actions, reducer, sagas };
|
||||
@@ -0,0 +1,54 @@
|
||||
import { createReducer } from 'typesafe-actions';
|
||||
import { combineReducers } from 'redux';
|
||||
import { Account } from './types';
|
||||
import actions, { ActionTypes } from './actions';
|
||||
import {ApiError, ChangeResponse} from "../../services/api-client/types";
|
||||
|
||||
type StateType = Readonly<{
|
||||
data: Account[] | null;
|
||||
loading: boolean;
|
||||
failed: ApiError | null;
|
||||
savedAccount: ChangeResponse<Account | null>;
|
||||
}>;
|
||||
|
||||
const initialState: StateType = {
|
||||
data: [],
|
||||
loading: false,
|
||||
failed: null,
|
||||
savedAccount: <ChangeResponse<Account | null>>{
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data : null
|
||||
},
|
||||
};
|
||||
|
||||
const data = createReducer<Account[], ActionTypes>(initialState.data as Account[])
|
||||
.handleAction(actions.getAccounts.success,(_, action) => action.payload)
|
||||
.handleAction(actions.getAccounts.failure, () => []);
|
||||
|
||||
const loading = createReducer<boolean, ActionTypes>(initialState.loading)
|
||||
.handleAction(actions.getAccounts.request, () => true)
|
||||
.handleAction(actions.getAccounts.success, () => false)
|
||||
.handleAction(actions.getAccounts.failure, () => false);
|
||||
|
||||
const failed = createReducer<ApiError | null, ActionTypes>(initialState.failed)
|
||||
.handleAction(actions.getAccounts.request, () => null)
|
||||
.handleAction(actions.getAccounts.success, () => null)
|
||||
.handleAction(actions.getAccounts.failure, (store, action) => action.payload);
|
||||
|
||||
const updatedAccount = createReducer<ChangeResponse<Account | null>, ActionTypes>(initialState.savedAccount)
|
||||
.handleAction(actions.updateAccount.request, () => initialState.savedAccount)
|
||||
.handleAction(actions.updateAccount.success, (store, action) => action.payload)
|
||||
.handleAction(actions.updateAccount.failure, (store, action) => action.payload)
|
||||
.handleAction(actions.setUpdateAccount, (store, action) => action.payload)
|
||||
.handleAction(actions.resetUpdateAccount, () => initialState.savedAccount)
|
||||
|
||||
|
||||
export default combineReducers({
|
||||
data,
|
||||
loading,
|
||||
failed,
|
||||
updatedAccount
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import {all, call, put, takeLatest} from 'redux-saga/effects';
|
||||
import {ApiError, ApiResponse, ChangeResponse} from '../../services/api-client/types';
|
||||
import {Account} from './types'
|
||||
import service from './service';
|
||||
import actions from './actions';
|
||||
|
||||
export function* getAccounts(action: ReturnType<typeof actions.getAccounts.request>): Generator {
|
||||
try {
|
||||
const effect = yield call(service.getAccounts, action.payload);
|
||||
const response = effect as ApiResponse<Account[]>;
|
||||
|
||||
yield put(actions.getAccounts.success(response.body));
|
||||
} catch (err) {
|
||||
yield put(actions.getAccounts.failure(err as ApiError));
|
||||
}
|
||||
}
|
||||
|
||||
export function* updateAccount(action: ReturnType<typeof actions.updateAccount.request>): Generator {
|
||||
try {
|
||||
yield put(actions.setUpdateAccount({
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null
|
||||
}))
|
||||
|
||||
const account = action.payload.payload
|
||||
|
||||
const payloadToSave = {
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: account
|
||||
}
|
||||
|
||||
const effect = yield call(service.updateAccount, payloadToSave)
|
||||
const response = effect as ApiResponse<Account>;
|
||||
|
||||
yield put(actions.updateAccount.success({
|
||||
loading: false,
|
||||
success: true,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: response.body
|
||||
} as ChangeResponse<Account | null>));
|
||||
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
yield put(actions.updateAccount.failure({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: true,
|
||||
error: err as ApiError,
|
||||
data: null
|
||||
} as ChangeResponse<Account | null>));
|
||||
}
|
||||
}
|
||||
|
||||
export default function* sagas(): Generator {
|
||||
yield all([
|
||||
takeLatest(actions.getAccounts.request, getAccounts),
|
||||
takeLatest(actions.updateAccount.request, updateAccount),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import {ApiResponse, RequestPayload} from '../../services/api-client/types';
|
||||
import { apiClient } from '../../services/api-client';
|
||||
import {Account} from './types';
|
||||
|
||||
export default {
|
||||
async getAccounts(payload:RequestPayload<null>): Promise<ApiResponse<Account[]>> {
|
||||
return apiClient.get<Account[]>(
|
||||
`/api/accounts`,
|
||||
payload
|
||||
);
|
||||
},
|
||||
async updateAccount(payload:RequestPayload<Account>): Promise<ApiResponse<Account>> {
|
||||
const id = payload.payload.id
|
||||
return apiClient.put<Account>(
|
||||
`/api/accounts/${id}`,
|
||||
payload
|
||||
);
|
||||
}
|
||||
};
|
||||
Loaded 20 of 37 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user