mirror of
https://github.com/balzack/databag.git
synced 2025-02-12 03:29:16 +00:00
support admin disable of accounts
This commit is contained in:
parent
172d28f0be
commit
8ff747f90f
35
doc/api.oa3
35
doc/api.oa3
@ -212,10 +212,8 @@ paths:
|
|||||||
delete:
|
delete:
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
description: Remove account from node. Access granted to admin username and password.
|
description: Remove account from node. Access granted to admin token
|
||||||
operationId: remove-node-account
|
operationId: remove-node-account
|
||||||
security:
|
|
||||||
- basicAuth: []
|
|
||||||
parameters:
|
parameters:
|
||||||
- name: accountId
|
- name: accountId
|
||||||
in: path
|
in: path
|
||||||
@ -238,6 +236,37 @@ paths:
|
|||||||
description: account not found
|
description: account not found
|
||||||
'500':
|
'500':
|
||||||
description: internal server error
|
description: internal server error
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
description: Disable account. Access granted to admin token
|
||||||
|
operationId: set-node-account
|
||||||
|
parameters:
|
||||||
|
- name: accountId
|
||||||
|
in: path
|
||||||
|
description: id of account to delete
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: token
|
||||||
|
in: query
|
||||||
|
description: token for admin access
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: successful operation
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
'401':
|
||||||
|
description: invalid authentication
|
||||||
|
'404':
|
||||||
|
description: account not found
|
||||||
|
'500':
|
||||||
|
description: internal server error
|
||||||
|
|
||||||
/admin/accounts/import:
|
/admin/accounts/import:
|
||||||
post:
|
post:
|
||||||
|
38
net/server/internal/api_setNodeAccountStatus.go
Normal file
38
net/server/internal/api_setNodeAccountStatus.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package databag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"net/http"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"databag/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetNodeAccountStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
// get referenced account id
|
||||||
|
params := mux.Vars(r)
|
||||||
|
accountId, res := strconv.ParseUint(params["accountId"], 10, 32)
|
||||||
|
if res != nil {
|
||||||
|
ErrResponse(w, http.StatusBadRequest, res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, err := ParamAdminToken(r); err != nil {
|
||||||
|
ErrResponse(w, code, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var flag bool
|
||||||
|
if err := ParseRequest(r, w, &flag); err != nil {
|
||||||
|
ErrResponse(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := store.DB.Model(store.Account{}).Where("id = ?", accountId).Update("disabled", flag).Error; err != nil {
|
||||||
|
ErrResponse(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteResponse(w, nil)
|
||||||
|
}
|
||||||
|
|
@ -193,6 +193,13 @@ var routes = Routes{
|
|||||||
GetNodeAccountImage,
|
GetNodeAccountImage,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Route{
|
||||||
|
"SetNodeAccountStatus",
|
||||||
|
strings.ToUpper("Put"),
|
||||||
|
"/admin/accounts/{accountId}/status",
|
||||||
|
SetNodeAccountStatus,
|
||||||
|
},
|
||||||
|
|
||||||
Route{
|
Route{
|
||||||
"GetNodeAccounts",
|
"GetNodeAccounts",
|
||||||
strings.ToUpper("Get"),
|
strings.ToUpper("Get"),
|
||||||
|
@ -2,6 +2,7 @@ import { Avatar } from 'avatar/Avatar';
|
|||||||
import { AccountItemWrapper, DeleteButton, EnableButton, DisableButton, ResetButton } from './AccountItem.styled';
|
import { AccountItemWrapper, DeleteButton, EnableButton, DisableButton, ResetButton } from './AccountItem.styled';
|
||||||
import { useAccountItem } from './useAccountItem.hook';
|
import { useAccountItem } from './useAccountItem.hook';
|
||||||
import { UserDeleteOutlined, UnlockOutlined, CloseCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
import { UserDeleteOutlined, UnlockOutlined, CloseCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { Tooltip } from 'antd';
|
||||||
|
|
||||||
export function AccountItem({ token, item }) {
|
export function AccountItem({ token, item }) {
|
||||||
|
|
||||||
@ -9,9 +10,19 @@ export function AccountItem({ token, item }) {
|
|||||||
|
|
||||||
const Enable = () => {
|
const Enable = () => {
|
||||||
if (state.disabled) {
|
if (state.disabled) {
|
||||||
return <EnableButton type="text" size="large" icon={<CloseCircleOutlined />}></EnableButton>
|
return (
|
||||||
|
<Tooltip placement="topLeft" title="Enable Account">
|
||||||
|
<EnableButton type="text" size="large" icon={<CheckCircleOutlined />}
|
||||||
|
onClick={() => actions.setStatus(false)}></EnableButton>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return <DisableButton type="text" size="large" icon={<CheckCircleOutlined />}></DisableButton>
|
return (
|
||||||
|
<Tooltip placement="topLeft" title="Disable Account">
|
||||||
|
<DisableButton type="text" size="large" icon={<CloseCircleOutlined />}
|
||||||
|
onClick={() => actions.setStatus(true)}></DisableButton>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -19,14 +30,18 @@ export function AccountItem({ token, item }) {
|
|||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<Avatar imageUrl={state.imageUrl} />
|
<Avatar imageUrl={state.imageUrl} />
|
||||||
</div>
|
</div>
|
||||||
<div class="id">
|
<div class={state.activeClass}>
|
||||||
<div class="handle">{ state.handle }</div>
|
<div class="handle">{ state.handle }</div>
|
||||||
<div class="guid">{ state.guid }</div>
|
<div class="guid">{ state.guid }</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
<Tooltip placement="topLeft" title="Reset Password">
|
||||||
<ResetButton type="text" size="large" icon={<UnlockOutlined />}></ResetButton>
|
<ResetButton type="text" size="large" icon={<UnlockOutlined />}></ResetButton>
|
||||||
|
</Tooltip>
|
||||||
<Enable />
|
<Enable />
|
||||||
|
<Tooltip placement="topLeft" title="Delete Account">
|
||||||
<DeleteButton type="text" size="large" icon={<UserDeleteOutlined />}></DeleteButton>
|
<DeleteButton type="text" size="large" icon={<UserDeleteOutlined />}></DeleteButton>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</AccountItemWrapper>
|
</AccountItemWrapper>
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ export const AccountItemWrapper = styled.div`
|
|||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
border-bottom: 1px solid #eeeeee;
|
border-bottom: 1px solid #eeeeee;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
@ -23,12 +24,22 @@ export const AccountItemWrapper = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.id {
|
.inactive {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.handle {
|
.handle {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
@ -46,7 +57,6 @@ export const AccountItemWrapper = styled.div`
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const EnableButton = styled(Button)`
|
export const EnableButton = styled(Button)`
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { useContext, useState, useEffect } from 'react';
|
import { useContext, useState, useEffect } from 'react';
|
||||||
import { getAccountImageUrl } from 'api/getAccountImageUrl';
|
import { getAccountImageUrl } from 'api/getAccountImageUrl';
|
||||||
|
import { setAccountStatus } from 'api/setAccountStatus';
|
||||||
|
|
||||||
export function useAccountItem(token, item) {
|
export function useAccountItem(token, item) {
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
|
statusBusy: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateState = (value) => {
|
const updateState = (value) => {
|
||||||
@ -12,16 +14,31 @@ export function useAccountItem(token, item) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateState({
|
updateState({
|
||||||
disabled: false,
|
disabled: item?.disabled,
|
||||||
|
activeClass: item?.disabled ? 'inactive' : 'active',
|
||||||
accountId: item?.accountId,
|
accountId: item?.accountId,
|
||||||
name: item?.name,
|
name: item?.name,
|
||||||
guid: item?.guid,
|
guid: item?.guid,
|
||||||
handle: item?.handle,
|
handle: item?.handle,
|
||||||
imageUrl: item?.imageSet ? getAccountImageUrl(token, item?.accountId) : null,
|
imageUrl: item?.imageSet ? getAccountImageUrl(token, item?.accountId) : null,
|
||||||
});
|
});
|
||||||
}, []);
|
}, [token, item]);
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
setStatus: async (disabled) => {
|
||||||
|
if (!state.statusBusy) {
|
||||||
|
updateState({ statusBusy: true });
|
||||||
|
try {
|
||||||
|
await setAccountStatus(token, item.accountId, disabled);
|
||||||
|
updateState({ disabled, activeClass: disabled ? 'inactive' : 'active' });
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
window.alert(err);
|
||||||
|
}
|
||||||
|
updateState({ statusBusy: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return { state, actions };
|
return { state, actions };
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DashboardWrapper, SettingsButton, AddButton, SettingsLayout } from './Dashboard.styled';
|
import { DashboardWrapper, SettingsButton, AddButton, SettingsLayout } from './Dashboard.styled';
|
||||||
import { Button, Modal, Input, InputNumber, Space, List } from 'antd';
|
import { Tooltip, Button, Modal, Input, InputNumber, Space, List } from 'antd';
|
||||||
import { SettingOutlined, UserAddOutlined, LogoutOutlined, ReloadOutlined } from '@ant-design/icons';
|
import { SettingOutlined, UserAddOutlined, LogoutOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||||
import { useDashboard } from './useDashboard.hook';
|
import { useDashboard } from './useDashboard.hook';
|
||||||
import { AccountItem } from './AccountItem/AccountItem';
|
import { AccountItem } from './AccountItem/AccountItem';
|
||||||
@ -15,19 +15,27 @@ export function Dashboard({ token, config, logout }) {
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="label">Accounts</div>
|
<div class="label">Accounts</div>
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
|
<Tooltip placement="topRight" title="Reload Accounts">
|
||||||
<SettingsButton type="text" size="small" icon={<ReloadOutlined />}
|
<SettingsButton type="text" size="small" icon={<ReloadOutlined />}
|
||||||
onClick={() => actions.getAccounts()}></SettingsButton>
|
onClick={() => actions.getAccounts()}></SettingsButton>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
|
<Tooltip placement="topRight" title="Configure Server">
|
||||||
<SettingsButton type="text" size="small" icon={<SettingOutlined />}
|
<SettingsButton type="text" size="small" icon={<SettingOutlined />}
|
||||||
onClick={() => actions.setShowSettings(true)}></SettingsButton>
|
onClick={() => actions.setShowSettings(true)}></SettingsButton>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
|
<Tooltip placement="topRight" title="Logout">
|
||||||
<SettingsButton type="text" size="small" icon={<LogoutOutlined />}
|
<SettingsButton type="text" size="small" icon={<LogoutOutlined />}
|
||||||
onClick={() => logout()}></SettingsButton>
|
onClick={() => logout()}></SettingsButton>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="add">
|
<div class="add">
|
||||||
|
<Tooltip placement="topRight" title="Add Account">
|
||||||
<AddButton type="text" size="large" icon={<UserAddOutlined />}></AddButton>
|
<AddButton type="text" size="large" icon={<UserAddOutlined />}></AddButton>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
7
net/web/src/api/setAccountStatus.js
Normal file
7
net/web/src/api/setAccountStatus.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||||
|
|
||||||
|
export async function setAccountStatus(token, accountId, disabled) {
|
||||||
|
let res = await fetchWithTimeout(`/admin/accounts/${accountId}/status?token=${token}`, { method: 'PUT', body: JSON.stringify(disabled) })
|
||||||
|
checkResponse(res);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user