support admin disable of accounts

This commit is contained in:
Roland Osborne 2022-06-07 10:54:33 -07:00
parent 172d28f0be
commit 8ff747f90f
8 changed files with 164 additions and 33 deletions

View File

@ -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:

View 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)
}

View File

@ -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"),

View File

@ -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>
); );

View File

@ -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)`

View File

@ -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 };

View File

@ -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>

View 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);
}