Feature/dns settings (#126)

Add DNS settings view to the DNS tab.

Split the page into sub-tabs for Nameservers and DNS settings

Added API calls to the new DNS settings API
This commit is contained in:
Maycon Santos
2023-01-18 18:12:29 +01:00
committed by GitHub
parent 29ab28847d
commit 5d901470c2
14 changed files with 971 additions and 488 deletions

View File

@@ -0,0 +1,26 @@
import { ActionType, createAction, createAsyncAction } from 'typesafe-actions';
import {DNSSettings, DNSSettingsToSave} from './types';
import {ApiError, CreateResponse, RequestPayload} from '../../services/api-client/types';
const actions = {
getDNSSettings: createAsyncAction(
'GET_DNSSettings_REQUEST',
'GET_DNSSettings_SUCCESS',
'GET_DNSSettings_FAILURE',
)<RequestPayload<null>, DNSSettings, ApiError>(),
saveDNSSettings: createAsyncAction(
'SAVE_DNSSettings_REQUEST',
'SAVE_DNSSettings_SUCCESS',
'SAVE_DNSSettings_FAILURE',
)<RequestPayload<DNSSettingsToSave>, CreateResponse<DNSSettings | null>, CreateResponse<DNSSettings | null>>(),
setSavedDNSSettings: createAction('SET_CREATE_DNSSettings')<CreateResponse<DNSSettings | null>>(),
resetSavedDNSSettings: createAction('RESET_CREATE_DNSSettings')<null>(),
setDNSSettings: createAction('SET_DNSSettings')<DNSSettings>(),
setSetupNewDNSSettingsVisible: createAction('SET_SETUP_NEW_DNSSettings_VISIBLE')<boolean>(),
setSetupNewDNSSettingsHA: createAction('SET_SETUP_NEW_DNSSettings_HA')<boolean>()
};
export type ActionTypes = ActionType<typeof actions>;
export default actions;

View File

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

View File

@@ -0,0 +1,79 @@
import { createReducer } from 'typesafe-actions';
import { combineReducers } from 'redux';
import { DNSSettings } from './types';
import actions, { ActionTypes } from './actions';
import {ApiError, CreateResponse} from "../../services/api-client/types";
type StateType = Readonly<{
data: DNSSettings | null;
dnsSettings: DNSSettings | null;
loading: boolean;
failed: ApiError | null;
saving: boolean;
savedDNSSettings: CreateResponse<DNSSettings | null>;
setupNewDNSSettingsVisible: boolean;
setupNewDNSSettingsHA: boolean
}>;
const initialState: StateType = {
data: null,
dnsSettings: null,
loading: false,
failed: null,
saving: false,
savedDNSSettings: <CreateResponse<DNSSettings | null>>{
loading: false,
success: false,
failure: false,
error: null,
data : null
},
setupNewDNSSettingsVisible: false,
setupNewDNSSettingsHA: false
};
const data = createReducer<DNSSettings, ActionTypes>(initialState.data as DNSSettings)
.handleAction(actions.getDNSSettings.success,(settings, action) => action.payload)
.handleAction(actions.getDNSSettings.failure,(settings, _) => settings);
const dnsSettings = createReducer<DNSSettings, ActionTypes>(initialState.dnsSettings as DNSSettings)
.handleAction(actions.setDNSSettings, (store, action) => action.payload);
const loading = createReducer<boolean, ActionTypes>(initialState.loading)
.handleAction(actions.getDNSSettings.request, () => true)
.handleAction(actions.getDNSSettings.success, () => false)
.handleAction(actions.getDNSSettings.failure, () => false);
const failed = createReducer<ApiError | null, ActionTypes>(initialState.failed)
.handleAction(actions.getDNSSettings.request, () => null)
.handleAction(actions.getDNSSettings.success, () => null)
.handleAction(actions.getDNSSettings.failure, (store, action) => action.payload);
const saving = createReducer<boolean, ActionTypes>(initialState.saving)
.handleAction(actions.getDNSSettings.request, () => true)
.handleAction(actions.getDNSSettings.success, () => false)
.handleAction(actions.getDNSSettings.failure, () => false);
const savedDNSSettings = createReducer<CreateResponse<DNSSettings | null>, ActionTypes>(initialState.savedDNSSettings)
.handleAction(actions.saveDNSSettings.request, () => initialState.savedDNSSettings)
.handleAction(actions.saveDNSSettings.success, (store, action) => action.payload)
.handleAction(actions.saveDNSSettings.failure, (store, action) => action.payload)
.handleAction(actions.setSavedDNSSettings, (store, action) => action.payload)
.handleAction(actions.resetSavedDNSSettings, () => initialState.savedDNSSettings)
const setupNewDNSSettingsVisible = createReducer<boolean, ActionTypes>(initialState.setupNewDNSSettingsVisible)
.handleAction(actions.setSetupNewDNSSettingsVisible, (store, action) => action.payload)
const setupNewDNSSettingsHA = createReducer<boolean, ActionTypes>(initialState.setupNewDNSSettingsHA)
.handleAction(actions.setSetupNewDNSSettingsHA, (store, action) => action.payload)
export default combineReducers({
data,
dnsSettings,
loading,
failed,
saving,
savedDNSSettings,
setupNewDNSSettingsVisible,
setupNewDNSSettingsHA
});

View File

@@ -0,0 +1,95 @@
import {all, call, put, takeLatest} from 'redux-saga/effects';
import {ApiError, ApiResponse, CreateResponse} from '../../services/api-client/types';
import {DNSSettings} from './types'
import service from './service';
import actions from './actions';
import serviceGroup from "../group/service";
import {Group} from "../group/types";
import {actions as groupActions} from "../group";
export function* getDNSSettings(action: ReturnType<typeof actions.getDNSSettings.request>): Generator {
try {
const effect = yield call(service.getDNSSettings, action.payload);
const response = effect as ApiResponse<DNSSettings>;
yield put(actions.getDNSSettings.success(response.body));
} catch (err) {
yield put(actions.getDNSSettings.failure(err as ApiError));
}
}
export function* saveDNSSettings(action: ReturnType<typeof actions.saveDNSSettings.request>): Generator {
try {
yield put(actions.setSavedDNSSettings({
loading: true,
success: false,
failure: false,
error: null,
data: null
} as CreateResponse<DNSSettings | null>))
const settingsToSave = action.payload.payload
let groupsToCreate = settingsToSave.groupsToCreate
if (!groupsToCreate) {
groupsToCreate = []
}
// first, create groups that were newly added by user
const responsesGroup = yield all(groupsToCreate.map(g => call(serviceGroup.createGroup, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: {name: g}
})
))
const resGroups = (responsesGroup as ApiResponse<Group>[]).filter(r => r.statusCode === 200).map(g => (g.body as Group)).map(g => g.id)
const newGroups = [...settingsToSave.disabled_management_groups, ...resGroups]
const payloadToSave = {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: {
disabled_management_groups: newGroups,
} as DNSSettings
}
let effect = yield call(service.editDNSSettings, payloadToSave);
const response = effect as ApiResponse<DNSSettings>;
yield put(actions.saveDNSSettings.success({
loading: false,
success: true,
failure: false,
error: null,
data: response.body
} as CreateResponse<DNSSettings | null>));
yield put(groupActions.getGroups.request({
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: null
}));
yield put(actions.getDNSSettings.request({ getAccessTokenSilently: action.payload.getAccessTokenSilently, payload: null }));
} catch (err) {
yield put(groupActions.getGroups.request({
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: null
}));
yield put(actions.saveDNSSettings.failure({
loading: false,
success: false,
failure: true,
error: err as ApiError,
data: null
} as CreateResponse<DNSSettings | null>));
}
}
export default function* sagas(): Generator {
yield all([
takeLatest(actions.getDNSSettings.request, getDNSSettings),
takeLatest(actions.saveDNSSettings.request, saveDNSSettings),
]);
}

View File

@@ -0,0 +1,18 @@
import {ApiResponse, RequestPayload} from '../../services/api-client/types';
import { apiClient } from '../../services/api-client';
import { DNSSettings } from './types';
export default {
async getDNSSettings(payload:RequestPayload<null>): Promise<ApiResponse<DNSSettings>> {
return apiClient.get<DNSSettings>(
`/api/dns/settings`,
payload
);
},
async editDNSSettings(payload:RequestPayload<DNSSettings>): Promise<ApiResponse<DNSSettings>> {
return apiClient.put<DNSSettings>(
`/api/dns/settings`,
payload
);
},
};

View File

@@ -0,0 +1,8 @@
export interface DNSSettings {
disabled_management_groups: string[]
}
export interface DNSSettingsToSave extends DNSSettings
{
groupsToCreate: string[]
}

View File

@@ -10,6 +10,7 @@ import { sagas as groupSagas } from './group';
import { sagas as routeSagas } from './route';
import { sagas as nameserverGroupSagas } from './nameservers';
import { sagas as eventSagas } from './event';
import { sagas as dnsSettingsSagas } from './dns-settings';
import rootReducer from './root-reducer';
import { apiClient } from '../services/api-client';
@@ -29,5 +30,6 @@ sagaMiddleware.run(groupSagas);
sagaMiddleware.run(routeSagas);
sagaMiddleware.run(nameserverGroupSagas);
sagaMiddleware.run(eventSagas);
sagaMiddleware.run(dnsSettingsSagas);
export { apiClient, rootReducer, store };

View File

@@ -6,6 +6,7 @@ import {actions as RuleActions} from './rule';
import {actions as RouteActions} from './route';
import {actions as NameServerGroupActions} from './nameservers';
import {actions as EventActions} from './event';
import {actions as DNSSettingsActions} from './dns-settings';
export default {
peer: PeerActions,
@@ -15,5 +16,6 @@ export default {
rule: RuleActions,
route: RouteActions,
nameserverGroup: NameServerGroupActions,
event: EventActions
event: EventActions,
dnsSettings: DNSSettingsActions
};

View File

@@ -8,6 +8,7 @@ import { reducer as rule } from './rule';
import { reducer as route } from './route';
import { reducer as nameserverGroup } from './nameservers';
import { reducer as event } from './event';
import { reducer as dnsSettings } from './dns-settings';
export default combineReducers({
peer,
@@ -17,5 +18,6 @@ export default combineReducers({
rule,
route,
nameserverGroup,
event
event,
dnsSettings
});

View File

@@ -94,12 +94,16 @@ export const useGetGroupTagHelpers = () => {
return groups?.filter(g => groupIDList.includes(g.id!)).map(g => g.name || '') || []
}
const selectValidator = (_: RuleObject, value: string[]) => {
let hasSpaceNamed = []
const selectValidator = (obj: RuleObject, value: string[]) => {
if (!value.length) {
return Promise.reject(new Error("Please enter at least one group"))
}
return selectValidatorEmptyStrings(obj,value)
}
const selectValidatorEmptyStrings = (_: RuleObject, value: string[]) => {
let hasSpaceNamed = []
value.forEach(function (v: string) {
if (!v.trim().length) {
hasSpaceNamed.push(v)
@@ -131,6 +135,7 @@ export const useGetGroupTagHelpers = () => {
setGroupTagFilterAll,
getExistingAndToCreateGroupsLists,
getGroupNamesFromIDs,
selectValidator
selectValidator,
selectValidatorEmptyStrings
}
}

View File

@@ -77,25 +77,29 @@ export const Activity = () => {
setPageSize(parseInt(value.toString()))
}
const getActivityRow = (group:string,text:string) => {
return <Row> <Text>Group <Text type="secondary">{group}</Text> {text}</Text> </Row>
}
const renderActivity = (event: EventDataTable) => {
let body = <Text>{event.activity}</Text>
switch (event.activity_code) {
case "peer.group.add":
return <Row> <Text>Group <Text type="secondary">{event.meta.group}</Text> added to peer</Text> </Row>
return getActivityRow(event.meta.group,"added to peer")
case "peer.group.delete":
return <Row> <Text>Group <Text type="secondary">{event.meta.group}</Text> removed from peer</Text>
</Row>
return getActivityRow(event.meta.group,"removed from peer")
case "user.group.add":
return <Row> <Text>Group <Text type="secondary">{event.meta.group}</Text> added to user</Text> </Row>
return getActivityRow(event.meta.group,"added to user")
case "user.group.delete":
return <Row> <Text>Group <Text type="secondary">{event.meta.group}</Text> removed from user</Text>
</Row>
return getActivityRow(event.meta.group,"removed from user")
case "setupkey.group.add":
return <Row> <Text>Group <Text type="secondary">{event.meta.group}</Text> added to setup key</Text>
</Row>
return getActivityRow(event.meta.group,"added to setup key")
case "setupkey.group.delete":
return <Row> <Text>Group <Text type="secondary">{event.meta.group}</Text> removed setup key</Text>
</Row>
return getActivityRow(event.meta.group,"removed setup key")
case "dns.setting.disabled.management.group.add":
return getActivityRow(event.meta.group,"added to disabled management DNS setting")
case "dns.setting.disabled.management.group.delete":
return getActivityRow(event.meta.group,"removed from disabled management DNS setting")
}
return body
}
@@ -126,6 +130,13 @@ export const Activity = () => {
return body
}
const renderMultiRowSpan = (primaryRowText:string,secondaryRowText:string) => {
return <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
<Row> <Text>{primaryRowText}</Text> </Row>
<Row> <Text type="secondary">{secondaryRowText}</Text> </Row>
</span>
}
const renderTarget = (event: EventDataTable) => {
if (event.activity_code === "account.create" || event.activity_code === "user.join") {
return "-"
@@ -138,62 +149,41 @@ export const Activity = () => {
case "rule.add":
case "rule.delete":
case "rule.update":
return <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
<Row> <Text>{event.meta.name}</Text> </Row>
<Row> <Text type="secondary">Rule</Text> </Row>
</span>
return renderMultiRowSpan(event.meta.name,"Rule")
case "setupkey.add":
case "setupkey.revoke":
case "setupkey.update":
case "setupkey.overuse":
return <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
<Row> <Text>{event.meta.name}</Text> </Row>
<Row> <Text
type="secondary">{capitalize(event.meta.type)} setup key ({event.meta.key})</Text> </Row>
</span>
let cType:string
cType = capitalize(event.meta.type)
return renderMultiRowSpan(event.meta.name,cType+" setup key "+event.meta.key)
case "group.add":
case "group.update":
return <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
<Row> <Text>{event.meta.name}</Text> </Row>
<Row> <Text type="secondary">Group</Text> </Row>
</span>
return renderMultiRowSpan(event.meta.name,"Group")
case "setupkey.peer.add":
case "user.peer.add":
case "user.peer.delete":
return <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
<Row> <Text>{event.meta.fqdn}</Text> </Row>
<Row> <Text type="secondary">{event.meta.ip}</Text> </Row>
</span>
return renderMultiRowSpan(event.meta.fqdn,event.meta.ip)
case "user.group.add":
case "user.group.delete":
if (user) {
return <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
<Row> <Text>{user.name ? user.name : user.id}</Text> </Row>
<Row> <Text type="secondary">{user.email ? user.email : "User"}</Text> </Row>
</span>
return renderMultiRowSpan(user.name ? user.name : user.id,user.email ? user.email : "User")
}
return "n/a"
case "setupkey.group.add":
case "setupkey.group.delete":
return <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
<Row> <Text>{event.meta.setupkey}</Text> </Row>
<Row> <Text type="secondary">Setup Key</Text> </Row>
</span>
return renderMultiRowSpan(event.meta.setupkey,"Setup Key")
case "peer.group.add":
case "peer.group.delete":
return <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
<Row> <Text>{event.meta.peer_fqdn}</Text> </Row>
<Row> <Text type="secondary">{event.meta.peer_ip}</Text> </Row>
</span>
return renderMultiRowSpan(event.meta.peer_fqdn,event.meta.peer_ip)
case "dns.setting.disabled.management.group.add":
return "-"
case "dns.setting.disabled.management.group.delete":
return "-"
case "user.invite":
if (user) {
return <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
<Row> <Text>{user.name ? user.name : user.id}</Text> </Row>
<Row> <Text type="secondary">{user.email ? user.email : "User"}</Text> </Row>
</span>
return renderMultiRowSpan(user.name ? user.name : user.id,user.email ? user.email : "User")
}
}
return event.target_id

View File

@@ -1,350 +1,58 @@
import React, {useEffect, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "typesafe-actions";
import {actions as nsGroupActions} from '../store/nameservers';
import {Container} from "../components/Container";
import {
Alert,
Button,
Card,
Col,
Dropdown,
Input,
Menu,
message,
Modal,
Popover,
Radio,
RadioChangeEvent,
Row,
Select,
Space,
Table,
Tag,
Tabs,
Typography,
} from "antd";
import {filter} from "lodash";
import tableSpin from "../components/Spin";
import {useGetAccessTokenSilently} from "../utils/token";
import {actions as groupActions} from "../store/group";
import {Group} from "../store/group/types";
import {TooltipPlacement} from "antd/es/tooltip";
import {NameServer, NameServerGroup} from "../store/nameservers/types";
import type { TabsProps } from 'antd';
import NameServerGroupUpdate from "../components/NameServerGroupUpdate";
import {ExclamationCircleOutlined} from "@ant-design/icons";
import Nameservers from "./Nameservers";
import {actions as groupActions} from "../store/group";
import {useGetAccessTokenSilently} from "../utils/token";
import {useDispatch, useSelector} from "react-redux";
import DNSSettingsForm from "./DNSSettings";
import {RootState} from "typesafe-actions";
import {actions as dnsSettingsActions} from '../store/dns-settings';
import {useGetGroupTagHelpers} from "../utils/groups";
const {Title, Paragraph} = Typography;
const {Column} = Table;
const {confirm} = Modal;
interface NameserverGroupDataTable extends NameServerGroup {
key: string
}
const styleNotification = {marginTop: 85}
export const DNS = () => {
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
const {
getGroupNamesFromIDs,
} = useGetGroupTagHelpers()
const groups = useSelector((state: RootState) => state.group.data)
const nsGroup = useSelector((state: RootState) => state.nameserverGroup.data);
const failed = useSelector((state: RootState) => state.nameserverGroup.failed);
const loading = useSelector((state: RootState) => state.nameserverGroup.loading);
const updateNameServerGroupVisible = useSelector((state: RootState) => state.nameserverGroup.setupNewNameServerGroupVisible)
const savedNSGroup = useSelector((state: RootState) => state.nameserverGroup.savedNameServerGroup)
const [groupPopupVisible, setGroupPopupVisible] = useState(false as boolean | undefined)
const [nsGroupToAction, setNsGroupToAction] = useState(null as NameserverGroupDataTable | null);
const [textToSearch, setTextToSearch] = useState('');
const [optionAllEnable, setOptionAllEnable] = useState('enabled');
const [pageSize, setPageSize] = useState(10);
const [dataTable, setDataTable] = useState([] as NameserverGroupDataTable[]);
const [showTutorial, setShowTutorial] = useState(false)
const pageSizeOptions = [
{label: "5", value: "5"},
{label: "10", value: "10"},
{label: "15", value: "15"}
]
const optionsAllEnabled = [{label: 'Enabled', value: 'enabled'}, {label: 'All', value: 'all'}]
// setUserAndView makes the UserUpdate drawer visible (right side) and sets the user object
const setUserAndView = (nsGroup: NameServerGroup) => {
dispatch(nsGroupActions.setSetupNewNameServerGroupVisible(true));
dispatch(nsGroupActions.setNameServerGroup({
id: nsGroup.id,
name: nsGroup.name,
primary: nsGroup.primary,
domains: nsGroup.domains,
description: nsGroup.description,
nameservers: nsGroup.nameservers,
groups: nsGroup.groups,
enabled: nsGroup.enabled,
} as NameServerGroup));
}
const transformDataTable = (d: NameServerGroup[]): NameserverGroupDataTable[] => {
return d.map(p => ({key: p.id, ...p} as NameserverGroupDataTable))
}
const dnsSettingsData = useSelector((state: RootState) => state.dnsSettings.data)
useEffect(() => {
dispatch(nsGroupActions.getNameServerGroups.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: null
}));
dispatch(groupActions.getGroups.request({getAccessTokenSilently: getAccessTokenSilently, payload: null}));
}, [])
useEffect(() => {
if (nsGroup.length > 0) {
setShowTutorial(false)
} else {
setShowTutorial(true)
}
setDataTable(transformDataTable(filterDataTable()))
}, [nsGroup])
useEffect(() => {
setDataTable(transformDataTable(filterDataTable()))
}, [textToSearch, optionAllEnable])
const filterDataTable = (): NameServerGroup[] => {
const t = textToSearch.toLowerCase().trim()
let f = filter(nsGroup, (f: NameServerGroup) =>
((f.name).toLowerCase().includes(t) ||
f.name.includes(t) || t === "" ||
getGroupNamesFromIDs(f.groups).find(u => u.toLowerCase().trim().includes(t)) ||
f.domains.find(d => d.toLowerCase().trim().includes(t)) ||
f.nameservers.find(n => n.ip.includes(t)))
) as NameServerGroup[]
if (optionAllEnable !== "all") {
f = filter(f, (f) => f.enabled)
}
return f
}
const onChangeAllEnabled = ({target: {value}}: RadioChangeEvent) => {
setOptionAllEnable(value)
}
const onChangeTextToSearch = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setTextToSearch(e.target.value)
};
const searchDataTable = () => {
setDataTable(transformDataTable(filterDataTable()))
}
const onChangePageSize = (value: string) => {
setPageSize(parseInt(value.toString()))
}
const onClickEdit = () => {
dispatch(nsGroupActions.setSetupNewNameServerGroupVisible(true));
dispatch(nsGroupActions.setNameServerGroup({
id: nsGroupToAction?.id,
name: nsGroupToAction?.name,
primary: nsGroupToAction?.primary,
domains: nsGroupToAction?.domains,
description: nsGroupToAction?.description,
groups: nsGroupToAction?.groups,
enabled: nsGroupToAction?.enabled,
nameservers: nsGroupToAction?.nameservers,
} as NameServerGroup));
}
const showConfirmDelete = () => {
confirm({
icon: <ExclamationCircleOutlined/>,
width: 600,
content: <Space direction="vertical" size="small">
{nsGroupToAction &&
<>
<Title level={5}>Delete Nameserver group "{nsGroupToAction ? nsGroupToAction.name : ''}"</Title>
<Paragraph>Are you sure you want to delete this nameserver group from your account?</Paragraph>
</>
}
</Space>,
okType: 'danger',
onOk() {
dispatch(nsGroupActions.deleteNameServerGroup.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: nsGroupToAction?.id || ''
}));
},
onCancel() {
setNsGroupToAction(null);
},
});
}
const renderPopoverGroups = (label: string, rowGroups: string[] | null, userToAction: NameserverGroupDataTable) => {
let groupsMap = new Map<string, Group>();
groups.forEach(g => {
groupsMap.set(g.id!, g)
})
let displayGroups: Group[] = []
if (rowGroups) {
displayGroups = rowGroups.filter(g => groupsMap.get(g)).map(g => groupsMap.get(g)!)
}
let btn = <Button type="link" onClick={() => setUserAndView(userToAction)}>{displayGroups.length}</Button>
if (!displayGroups || displayGroups!.length < 1) {
return btn
}
const content = displayGroups?.map((g, i) => {
const _g = g as Group
const peersCount = ` - ${_g.peers_count || 0} ${(!_g.peers_count || parseInt(_g.peers_count) !== 1) ? 'peers' : 'peer'} `
return (
<div key={i}>
<Tag
color="blue"
style={{marginRight: 3}}
>
<strong>{_g.name}</strong>
</Tag>
<span style={{fontSize: ".85em"}}>{peersCount}</span>
</div>
)
})
const mainContent = (<Space direction="vertical">{content}</Space>)
let popoverPlacement = "top"
if (content && content.length > 5) {
popoverPlacement = "rightTop"
}
return (
<Popover placement={popoverPlacement as TooltipPlacement}
key={userToAction.id}
onOpenChange={onPopoverVisibleChange}
open={groupPopupVisible}
content={mainContent}
title={null}>
{btn}
</Popover>
)
}
const renderPopoverDomains = (_: string, inputDomains: string[] | null, userToAction: NameserverGroupDataTable) => {
var domains = [] as string[]
if (inputDomains?.length) {
domains = inputDomains
}
let btn = <Button type="link"
onClick={() => setUserAndView(userToAction)}>{domains.length ? domains.length : 0}</Button>
if (!domains || domains!.length < 1) {
return btn
}
const content = domains?.map((d, i) => {
return (
<div key={i}>
<Tag
color="blue"
style={{marginRight: 3}}
>
<strong>{d}</strong>
</Tag>
</div>
)
})
const mainContent = (<Space direction="vertical">{content}</Space>)
let popoverPlacement = "top"
if (content && content.length > 5) {
popoverPlacement = "rightTop"
}
return (
<Popover placement={popoverPlacement as TooltipPlacement}
key={userToAction.id}
onOpenChange={onPopoverVisibleChange}
open={groupPopupVisible}
content={mainContent}
title={null}>
{btn}
</Popover>
)
}
useEffect(() => {
if (updateNameServerGroupVisible) {
setGroupPopupVisible(false)
}
}, [updateNameServerGroupVisible])
const createKey = 'saving';
useEffect(() => {
if (savedNSGroup.loading) {
message.loading({content: 'Saving...', key: createKey, duration: 0, style: styleNotification});
} else if (savedNSGroup.success) {
message.success({
content: 'User has been successfully saved.',
key: createKey,
duration: 2,
style: styleNotification
});
dispatch(nsGroupActions.setSetupNewNameServerGroupVisible(false));
dispatch(nsGroupActions.setSavedNameServerGroup({...savedNSGroup, success: false}));
dispatch(nsGroupActions.resetSavedNameServerGroup(null))
} else if (savedNSGroup.error) {
let errorMsg = "Failed to update nameserver group"
switch (savedNSGroup.error.statusCode) {
case 403:
errorMsg = "Failed to update nameserver group. You might not have enough permissions."
break
default:
errorMsg = savedNSGroup.error.data.message ? savedNSGroup.error.data.message : errorMsg
break
}
message.error({
content: errorMsg,
key: createKey,
duration: 5,
style: styleNotification
});
dispatch(nsGroupActions.setSavedNameServerGroup({...savedNSGroup, error: null}));
dispatch(nsGroupActions.resetSavedNameServerGroup(null))
}
}, [savedNSGroup])
const onPopoverVisibleChange = () => {
if (updateNameServerGroupVisible) {
setGroupPopupVisible(false)
} else {
setGroupPopupVisible(undefined)
}
}
const itemsMenuAction = [
const nsTabKey = '1'
const items: TabsProps['items'] = [
{
key: "edit",
label: (<Button type="text" onClick={() => onClickEdit()}>View</Button>)
key: nsTabKey,
label: 'Nameservers',
children: <Nameservers/>,
},
{
key: "delete",
label: (<Button type="text" onClick={() => showConfirmDelete()}>Delete</Button>)
key: '2',
label: 'Settings',
children: <DNSSettingsForm/>,
},
]
const actionsMenu = (<Menu items={itemsMenuAction}></Menu>)
const onClickAddNewNSGroup = () => {
dispatch(nsGroupActions.setSetupNewNameServerGroupVisible(true));
dispatch(nsGroupActions.setNameServerGroup({
enabled: true,
primary: true,
} as NameServerGroup))
const onTabClick = (key:string) => {
if (key == nsTabKey) {
if (!dnsSettingsData) return
dispatch(dnsSettingsActions.setDNSSettings({
disabled_management_groups: getGroupNamesFromIDs(dnsSettingsData.disabled_management_groups),
}))
}
}
return (
@@ -352,127 +60,13 @@ export const DNS = () => {
<Container style={{paddingTop: "40px"}}>
<Row>
<Col span={24}>
<Title level={4}>Nameservers</Title>
<Paragraph>Add nameservers for domain name resolution in your NetBird network</Paragraph>
<Space direction="vertical" size="large" style={{display: 'flex'}}>
<Row gutter={[16, 24]}>
<Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8} span={8}>
<Input allowClear value={textToSearch} onPressEnter={searchDataTable}
placeholder="Search..." onChange={onChangeTextToSearch}/>
</Col>
<Col xs={24} sm={24} md={11} lg={11} xl={11} xxl={11} span={11}>
<Space size="middle">
<Radio.Group
options={optionsAllEnabled}
onChange={onChangeAllEnabled}
value={optionAllEnable}
optionType="button"
buttonStyle="solid"
/>
<Select value={pageSize.toString()} options={pageSizeOptions}
onChange={onChangePageSize} className="select-rows-per-page-en"/>
</Space>
</Col>
<Col xs={24}
sm={24}
md={5}
lg={5}
xl={5}
xxl={5} span={5}>
<Row justify="end">
<Col>
{!showTutorial &&
<Button type="primary" onClick={onClickAddNewNSGroup}>Add
Nameserver</Button>}
</Col>
</Row>
</Col>
</Row>
{failed &&
<Alert message={failed.code} description={failed.message} type="error" showIcon
closable/>
}
<Card bodyStyle={{padding: 0}}>
<Table
pagination={{
pageSize,
showSizeChanger: false,
showTotal: ((total, range) => `Showing ${range[0]} to ${range[1]} of ${total} users`)
}}
// className="card-table"
className={`access-control-table ${showTutorial ? "card-table card-table-no-placeholder" : "card-table"}`}
showSorterTooltip={false}
scroll={{x: true}}
loading={tableSpin(loading)}
dataSource={dataTable}>
<Column title="Name" dataIndex="name" align="center"
onFilter={(value: string | number | boolean, record) => (record as any).name.includes(value)}
sorter={(a, b) => ((a as any).name.localeCompare((b as any).name))}
defaultSortOrder='ascend'
render={(text, record) => {
return <Button type="text"
onClick={() => setUserAndView(record as NameserverGroupDataTable)}
className="tooltip-label">{(text && text.trim() !== "") ? text : (record as NameServerGroup).id}</Button>
}}
/>
<Column title="Status" dataIndex="enabled" align="center"
render={(text: Boolean) => {
return text ? <Tag color="green">enabled</Tag> :
<Tag color="red">disabled</Tag>
}}
/>
<Column title="Nameservers" dataIndex="nameservers" align="center"
render={(nameservers: NameServer[]) => (
<>
{nameservers.map(nameserver => (
<Tag key={nameserver.ip}>
{nameserver.ip}
</Tag>
))}
</>
)}
/>
<Column title="All domains" dataIndex="primary" align="center"
render={(text: Boolean) => {
return text ? <Tag color="blue">yes</Tag> :
<Tag>no</Tag>
}}
/>
<Column title="Match domains" dataIndex="domains" align="center"
render={(text, record: NameserverGroupDataTable) => {
return renderPopoverDomains(text, record.domains, record)
}}
/>
<Column title="Groups" dataIndex="groupsCount" align="center"
render={(text, record: NameserverGroupDataTable) => {
return renderPopoverGroups(text, record.groups, record)
}}
/>
<Column title="" align="center" width="30px"
render={(text, record) => {
return (
<Dropdown.Button type="text" overlay={actionsMenu}
trigger={["click"]}
onOpenChange={visible => {
if (visible) setNsGroupToAction(record as NameserverGroupDataTable)
}}></Dropdown.Button>)
}}
/>
</Table>
{showTutorial &&
<Space direction="vertical" size="small" align="center"
style={{display: 'flex', padding: '45px 15px', justifyContent: 'center'}}>
<Paragraph type="secondary"
style={{textAlign: "center", whiteSpace: "pre-line"}}>
It looks like you don't have any nameservers. {"\n"}
Get started by adding one to your network!
</Paragraph>
<Button type="primary" onClick={onClickAddNewNSGroup}>Add
Nameserver</Button>
</Space>
}
</Card>
</Space>
<Tabs
defaultActiveKey={nsTabKey}
items={items}
onTabClick={onTabClick}
animated={{ inkBar: true, tabPane: false }}
tabPosition="top"
/>
</Col>
</Row>
</Container>

179
src/views/DNSSettings.tsx Normal file
View File

@@ -0,0 +1,179 @@
import React, {useEffect, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "typesafe-actions";
import {
Button,
Card,
Col,
Form,
message,
Select,
Space,
Typography,
} from "antd";
import {useGetAccessTokenSilently} from "../utils/token";
import {useGetGroupTagHelpers} from "../utils/groups";
import {actions as dnsSettingsActions} from '../store/dns-settings';
import {DNSSettings, DNSSettingsToSave} from "../store/dns-settings/types";
import {actions as nsGroupActions} from "../store/nameservers";
const {Paragraph} = Typography;
const styleNotification = {marginTop: 85}
export const DNSSettingsForm = () => {
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
const {
tagRender,
handleChangeTags,
dropDownRender,
optionRender,
tagGroups,
getExistingAndToCreateGroupsLists,
getGroupNamesFromIDs,
selectValidatorEmptyStrings
} = useGetGroupTagHelpers()
const dnsSettings = useSelector((state: RootState) => state.dnsSettings.dnsSettings)
const dnsSettingsData = useSelector((state: RootState) => state.dnsSettings.data)
const savedDNSSettings = useSelector((state: RootState) => state.dnsSettings.savedDNSSettings)
const loading = useSelector((state: RootState) => state.dnsSettings.loading);
const [form] = Form.useForm()
useEffect(() => {
dispatch(dnsSettingsActions.getDNSSettings.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: null
}));
}, []);
useEffect(() => {
if (!dnsSettingsData) return
dispatch(dnsSettingsActions.setDNSSettings({
disabled_management_groups: getGroupNamesFromIDs(dnsSettingsData.disabled_management_groups),
}))
}, [dnsSettingsData])
useEffect(() => {
form.setFieldsValue(dnsSettings)
}, [dnsSettings])
const createKey = 'saving';
useEffect(() => {
if (savedDNSSettings.loading) {
message.loading({content: 'Saving...', key: createKey, duration: 0, style: styleNotification});
} else if (savedDNSSettings.success) {
message.success({
content: 'DNS settings has been successfully saved.',
key: createKey,
duration: 2,
style: styleNotification
});
dispatch(dnsSettingsActions.setSavedDNSSettings({...savedDNSSettings, success: false}));
dispatch(dnsSettingsActions.resetSavedDNSSettings(null))
} else if (savedDNSSettings.error) {
let errorMsg = "Failed to update DNS settings"
switch (savedDNSSettings.error.statusCode) {
case 403:
errorMsg = "Failed to update DNS settings. You might not have enough permissions."
break
default:
errorMsg = savedDNSSettings.error.data.message ? savedDNSSettings.error.data.message : errorMsg
break
}
message.error({
content: errorMsg,
key: createKey,
duration: 5,
style: styleNotification
});
dispatch(dnsSettingsActions.setSavedDNSSettings({...savedDNSSettings, error: null}));
dispatch(nsGroupActions.resetSavedNameServerGroup(null))
}
}, [savedDNSSettings])
const handleFormSubmit = () => {
form.validateFields()
.then((values) => {
let dnsSettingsToSave = createDNSSettingsToSave(values)
dispatch(dnsSettingsActions.saveDNSSettings.request({
getAccessTokenSilently:getAccessTokenSilently,
payload: dnsSettingsToSave
}))
})
.then(() => {
console.log("issued the request")
})
.catch((errorInfo) => {
let msg = "please check the fields and try again"
if (errorInfo.errorFields) {
msg = errorInfo.errorFields[0].errors[0]
}
message.error({
content: msg,
duration: 1,
});
});
}
const createDNSSettingsToSave = (values: DNSSettings): DNSSettingsToSave => {
let [existingGroups, newGroups] = getExistingAndToCreateGroupsLists(values.disabled_management_groups)
return {
disabled_management_groups: existingGroups,
groupsToCreate: newGroups
} as DNSSettingsToSave
}
return (
<>
<Paragraph>Manage your account's DNS settings</Paragraph>
<Col>
<Form
name="basic"
autoComplete="off"
form={form}
onFinish={handleFormSubmit}
>
<Space direction={"vertical"}
style={{ display: 'flex' }}>
<Card
title="DNS Management"
loading={loading}
>
<Form.Item
label="Disable DNS management for these groups"
name="disabled_management_groups"
tooltip="Peers in these groups will have their DNS management disabled and require manual configuration for domain name resolution"
rules={[{validator: selectValidatorEmptyStrings}]}
>
<Select mode="tags"
style={{width: '100%'}}
tagRender={tagRender}
onChange={handleChangeTags}
dropdownRender={dropDownRender}
>
{
tagGroups.map(m =>
<Select.Option key={m}>{optionRender(m)}</Select.Option>
)
}
</Select>
</Form.Item>
</Card>
<Form.Item style={{ textAlign:'center' }} >
<Button type="primary" htmlType="submit">
Save
</Button>
</Form.Item>
</Space>
</Form>
</Col>
</>
)
}
export default DNSSettingsForm;

476
src/views/Nameservers.tsx Normal file
View File

@@ -0,0 +1,476 @@
import React, {useEffect, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "typesafe-actions";
import {actions as nsGroupActions} from '../store/nameservers';
import {Container} from "../components/Container";
import {
Alert,
Button,
Card,
Col,
Dropdown,
Input,
Menu,
message,
Modal,
Popover,
Radio,
RadioChangeEvent,
Row,
Select,
Space,
Table,
Tag,
Typography,
} from "antd";
import {filter} from "lodash";
import tableSpin from "../components/Spin";
import {useGetAccessTokenSilently} from "../utils/token";
import {actions as groupActions} from "../store/group";
import {Group} from "../store/group/types";
import {TooltipPlacement} from "antd/es/tooltip";
import {NameServer, NameServerGroup} from "../store/nameservers/types";
import NameServerGroupUpdate from "../components/NameServerGroupUpdate";
import {ExclamationCircleOutlined} from "@ant-design/icons";
import {useGetGroupTagHelpers} from "../utils/groups";
const {Title, Paragraph} = Typography;
const {Column} = Table;
const {confirm} = Modal;
interface NameserverGroupDataTable extends NameServerGroup {
key: string
}
const styleNotification = {marginTop: 85}
export const Nameservers = () => {
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
const {
getGroupNamesFromIDs,
} = useGetGroupTagHelpers()
const groups = useSelector((state: RootState) => state.group.data)
const nsGroup = useSelector((state: RootState) => state.nameserverGroup.data);
const failed = useSelector((state: RootState) => state.nameserverGroup.failed);
const loading = useSelector((state: RootState) => state.nameserverGroup.loading);
const updateNameServerGroupVisible = useSelector((state: RootState) => state.nameserverGroup.setupNewNameServerGroupVisible)
const savedNSGroup = useSelector((state: RootState) => state.nameserverGroup.savedNameServerGroup)
const [groupPopupVisible, setGroupPopupVisible] = useState(false as boolean | undefined)
const [nsGroupToAction, setNsGroupToAction] = useState(null as NameserverGroupDataTable | null);
const [textToSearch, setTextToSearch] = useState('');
const [optionAllEnable, setOptionAllEnable] = useState('enabled');
const [pageSize, setPageSize] = useState(10);
const [dataTable, setDataTable] = useState([] as NameserverGroupDataTable[]);
const [showTutorial, setShowTutorial] = useState(false)
const pageSizeOptions = [
{label: "5", value: "5"},
{label: "10", value: "10"},
{label: "15", value: "15"}
]
const optionsAllEnabled = [{label: 'Enabled', value: 'enabled'}, {label: 'All', value: 'all'}]
// setUserAndView makes the UserUpdate drawer visible (right side) and sets the user object
const setUserAndView = (nsGroup: NameServerGroup) => {
dispatch(nsGroupActions.setSetupNewNameServerGroupVisible(true));
dispatch(nsGroupActions.setNameServerGroup({
id: nsGroup.id,
name: nsGroup.name,
primary: nsGroup.primary,
domains: nsGroup.domains,
description: nsGroup.description,
nameservers: nsGroup.nameservers,
groups: nsGroup.groups,
enabled: nsGroup.enabled,
} as NameServerGroup));
}
const transformDataTable = (d: NameServerGroup[]): NameserverGroupDataTable[] => {
return d.map(p => ({key: p.id, ...p} as NameserverGroupDataTable))
}
useEffect(() => {
dispatch(nsGroupActions.getNameServerGroups.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: null
}));
dispatch(groupActions.getGroups.request({getAccessTokenSilently: getAccessTokenSilently, payload: null}));
}, [])
useEffect(() => {
if (nsGroup.length > 0) {
setShowTutorial(false)
} else {
setShowTutorial(true)
}
setDataTable(transformDataTable(filterDataTable()))
}, [nsGroup])
useEffect(() => {
setDataTable(transformDataTable(filterDataTable()))
}, [textToSearch, optionAllEnable])
const filterDataTable = (): NameServerGroup[] => {
const t = textToSearch.toLowerCase().trim()
let f = filter(nsGroup, (f: NameServerGroup) =>
((f.name).toLowerCase().includes(t) ||
f.name.includes(t) || t === "" ||
getGroupNamesFromIDs(f.groups).find(u => u.toLowerCase().trim().includes(t)) ||
f.domains.find(d => d.toLowerCase().trim().includes(t)) ||
f.nameservers.find(n => n.ip.includes(t)))
) as NameServerGroup[]
if (optionAllEnable !== "all") {
f = filter(f, (f) => f.enabled)
}
return f
}
const onChangeAllEnabled = ({target: {value}}: RadioChangeEvent) => {
setOptionAllEnable(value)
}
const onChangeTextToSearch = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setTextToSearch(e.target.value)
};
const searchDataTable = () => {
setDataTable(transformDataTable(filterDataTable()))
}
const onChangePageSize = (value: string) => {
setPageSize(parseInt(value.toString()))
}
const onClickEdit = () => {
dispatch(nsGroupActions.setSetupNewNameServerGroupVisible(true));
dispatch(nsGroupActions.setNameServerGroup({
id: nsGroupToAction?.id,
name: nsGroupToAction?.name,
primary: nsGroupToAction?.primary,
domains: nsGroupToAction?.domains,
description: nsGroupToAction?.description,
groups: nsGroupToAction?.groups,
enabled: nsGroupToAction?.enabled,
nameservers: nsGroupToAction?.nameservers,
} as NameServerGroup));
}
const showConfirmDelete = () => {
confirm({
icon: <ExclamationCircleOutlined/>,
width: 600,
content: <Space direction="vertical" size="small">
{nsGroupToAction &&
<>
<Title level={5}>Delete Nameserver group "{nsGroupToAction ? nsGroupToAction.name : ''}"</Title>
<Paragraph>Are you sure you want to delete this nameserver group from your account?</Paragraph>
</>
}
</Space>,
okType: 'danger',
onOk() {
dispatch(nsGroupActions.deleteNameServerGroup.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: nsGroupToAction?.id || ''
}));
},
onCancel() {
setNsGroupToAction(null);
},
});
}
const renderPopoverGroups = (label: string, rowGroups: string[] | null, userToAction: NameserverGroupDataTable) => {
let groupsMap = new Map<string, Group>();
groups.forEach(g => {
groupsMap.set(g.id!, g)
})
let displayGroups: Group[] = []
if (rowGroups) {
displayGroups = rowGroups.filter(g => groupsMap.get(g)).map(g => groupsMap.get(g)!)
}
let btn = <Button type="link" onClick={() => setUserAndView(userToAction)}>{displayGroups.length}</Button>
if (!displayGroups || displayGroups!.length < 1) {
return btn
}
const content = displayGroups?.map((g, i) => {
const _g = g as Group
const peersCount = ` - ${_g.peers_count || 0} ${(!_g.peers_count || parseInt(_g.peers_count) !== 1) ? 'peers' : 'peer'} `
return (
<div key={i}>
<Tag
color="blue"
style={{marginRight: 3}}
>
<strong>{_g.name}</strong>
</Tag>
<span style={{fontSize: ".85em"}}>{peersCount}</span>
</div>
)
})
const mainContent = (<Space direction="vertical">{content}</Space>)
let popoverPlacement = "top"
if (content && content.length > 5) {
popoverPlacement = "rightTop"
}
return (
<Popover placement={popoverPlacement as TooltipPlacement}
key={userToAction.id}
onOpenChange={onPopoverVisibleChange}
open={groupPopupVisible}
content={mainContent}
title={null}>
{btn}
</Popover>
)
}
const renderPopoverDomains = (_: string, inputDomains: string[] | null, userToAction: NameserverGroupDataTable) => {
var domains = [] as string[]
if (inputDomains?.length) {
domains = inputDomains
}
let btn = <Button type="link"
onClick={() => setUserAndView(userToAction)}>{domains.length ? domains.length : 0}</Button>
if (!domains || domains!.length < 1) {
return btn
}
const content = domains?.map((d, i) => {
return (
<div key={i}>
<Tag
color="blue"
style={{marginRight: 3}}
>
<strong>{d}</strong>
</Tag>
</div>
)
})
const mainContent = (<Space direction="vertical">{content}</Space>)
let popoverPlacement = "top"
if (content && content.length > 5) {
popoverPlacement = "rightTop"
}
return (
<Popover placement={popoverPlacement as TooltipPlacement}
key={userToAction.id}
onOpenChange={onPopoverVisibleChange}
open={groupPopupVisible}
content={mainContent}
title={null}>
{btn}
</Popover>
)
}
useEffect(() => {
if (updateNameServerGroupVisible) {
setGroupPopupVisible(false)
}
}, [updateNameServerGroupVisible])
const createKey = 'saving';
useEffect(() => {
if (savedNSGroup.loading) {
message.loading({content: 'Saving...', key: createKey, duration: 0, style: styleNotification});
} else if (savedNSGroup.success) {
message.success({
content: 'Nameserver has been successfully saved.',
key: createKey,
duration: 2,
style: styleNotification
});
dispatch(nsGroupActions.setSetupNewNameServerGroupVisible(false));
dispatch(nsGroupActions.setSavedNameServerGroup({...savedNSGroup, success: false}));
dispatch(nsGroupActions.resetSavedNameServerGroup(null))
} else if (savedNSGroup.error) {
let errorMsg = "Failed to update nameserver group"
switch (savedNSGroup.error.statusCode) {
case 403:
errorMsg = "Failed to update nameserver group. You might not have enough permissions."
break
default:
errorMsg = savedNSGroup.error.data.message ? savedNSGroup.error.data.message : errorMsg
break
}
message.error({
content: errorMsg,
key: createKey,
duration: 5,
style: styleNotification
});
dispatch(nsGroupActions.setSavedNameServerGroup({...savedNSGroup, error: null}));
dispatch(nsGroupActions.resetSavedNameServerGroup(null))
}
}, [savedNSGroup])
const onPopoverVisibleChange = () => {
if (updateNameServerGroupVisible) {
setGroupPopupVisible(false)
} else {
setGroupPopupVisible(undefined)
}
}
const itemsMenuAction = [
{
key: "edit",
label: (<Button type="text" onClick={() => onClickEdit()}>View</Button>)
},
{
key: "delete",
label: (<Button type="text" onClick={() => showConfirmDelete()}>Delete</Button>)
},
]
const actionsMenu = (<Menu items={itemsMenuAction}></Menu>)
const onClickAddNewNSGroup = () => {
dispatch(nsGroupActions.setSetupNewNameServerGroupVisible(true));
dispatch(nsGroupActions.setNameServerGroup({
enabled: true,
primary: true,
} as NameServerGroup))
}
return (
<>
<Paragraph>Add nameservers for domain name resolution in your NetBird network</Paragraph>
<Space direction="vertical" size="large" style={{display: 'flex'}}>
<Row gutter={[16, 24]}>
<Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8} span={8}>
<Input allowClear value={textToSearch} onPressEnter={searchDataTable}
placeholder="Search..." onChange={onChangeTextToSearch}/>
</Col>
<Col xs={24} sm={24} md={11} lg={11} xl={11} xxl={11} span={11}>
<Space size="middle">
<Radio.Group
options={optionsAllEnabled}
onChange={onChangeAllEnabled}
value={optionAllEnable}
optionType="button"
buttonStyle="solid"
/>
<Select value={pageSize.toString()} options={pageSizeOptions}
onChange={onChangePageSize} className="select-rows-per-page-en"/>
</Space>
</Col>
<Col xs={24}
sm={24}
md={5}
lg={5}
xl={5}
xxl={5} span={5}>
<Row justify="end">
<Col>
{!showTutorial &&
<Button type="primary" onClick={onClickAddNewNSGroup}>Add
Nameserver</Button>}
</Col>
</Row>
</Col>
</Row>
{failed &&
<Alert message={failed.code} description={failed.message} type="error" showIcon
closable/>
}
<Card bodyStyle={{padding: 0}}>
<Table
pagination={{
pageSize,
showSizeChanger: false,
showTotal: ((total, range) => `Showing ${range[0]} to ${range[1]} of ${total} users`)
}}
// className="card-table"
className={`access-control-table ${showTutorial ? "card-table card-table-no-placeholder" : "card-table"}`}
showSorterTooltip={false}
scroll={{x: true}}
loading={tableSpin(loading)}
dataSource={dataTable}>
<Column title="Name" dataIndex="name" align="center"
onFilter={(value: string | number | boolean, record) => (record as any).name.includes(value)}
sorter={(a, b) => ((a as any).name.localeCompare((b as any).name))}
defaultSortOrder='ascend'
render={(text, record) => {
return <Button type="text"
onClick={() => setUserAndView(record as NameserverGroupDataTable)}
className="tooltip-label">{(text && text.trim() !== "") ? text : (record as NameServerGroup).id}</Button>
}}
/>
<Column title="Status" dataIndex="enabled" align="center"
render={(text: Boolean) => {
return text ? <Tag color="green">enabled</Tag> :
<Tag color="red">disabled</Tag>
}}
/>
<Column title="Nameservers" dataIndex="nameservers" align="center"
render={(nameservers: NameServer[]) => (
<>
{nameservers.map(nameserver => (
<Tag key={nameserver.ip}>
{nameserver.ip}
</Tag>
))}
</>
)}
/>
<Column title="All domains" dataIndex="primary" align="center"
render={(text: Boolean) => {
return text ? <Tag color="blue">yes</Tag> :
<Tag>no</Tag>
}}
/>
<Column title="Match domains" dataIndex="domains" align="center"
render={(text, record: NameserverGroupDataTable) => {
return renderPopoverDomains(text, record.domains, record)
}}
/>
<Column title="Groups" dataIndex="groupsCount" align="center"
render={(text, record: NameserverGroupDataTable) => {
return renderPopoverGroups(text, record.groups, record)
}}
/>
<Column title="" align="center" width="30px"
render={(text, record) => {
return (
<Dropdown.Button type="text" overlay={actionsMenu}
trigger={["click"]}
onOpenChange={visible => {
if (visible) setNsGroupToAction(record as NameserverGroupDataTable)
}}></Dropdown.Button>)
}}
/>
</Table>
{showTutorial &&
<Space direction="vertical" size="small" align="center"
style={{display: 'flex', padding: '45px 15px', justifyContent: 'center'}}>
<Paragraph type="secondary"
style={{textAlign: "center", whiteSpace: "pre-line"}}>
It looks like you don't have any nameservers. {"\n"}
Get started by adding one to your network!
</Paragraph>
<Button type="primary" onClick={onClickAddNewNSGroup}>Add
Nameserver</Button>
</Space>
}
</Card>
</Space>
</>
)
}
export default Nameservers;