Re-work DNS layout (#222)

This commit is contained in:
Sarooj bukhari
2023-07-10 12:03:27 +05:00
committed by GitHub
parent cb89eeb921
commit 09ae157be3
14 changed files with 2073 additions and 959 deletions

View File

@@ -0,0 +1,908 @@
import React, { useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "typesafe-actions";
import { actions as nsGroupActions } from "../store/nameservers";
import {
Button,
Col,
Switch,
Form,
FormListFieldData,
Input,
InputNumber,
message,
Modal,
Row,
Select,
Space,
Typography,
} from "antd";
import {
CloseOutlined,
MinusCircleOutlined,
PlusOutlined,
} from "@ant-design/icons";
import { Header } from "antd/es/layout/layout";
import { RuleObject } from "antd/lib/form";
import cidrRegex from "cidr-regex";
import {
NameServer,
NameServerGroup,
NameServerGroupToSave,
} from "../store/nameservers/types";
import { useGetGroupTagHelpers } from "../utils/groups";
import { useGetTokenSilently } from "../utils/token";
const { Paragraph, Text } = Typography;
interface formNSGroup extends NameServerGroup {}
const NameServerGroupAdd = () => {
const {
blueTagRender,
handleChangeTags,
dropDownRender,
optionRender,
tagGroups,
getExistingAndToCreateGroupsLists,
getGroupNamesFromIDs,
selectValidator,
} = useGetGroupTagHelpers();
const dispatch = useDispatch();
const { getTokenSilently } = useGetTokenSilently();
const { Option } = Select;
const nsGroup = useSelector(
(state: RootState) => state.nameserverGroup.nameserverGroup
);
const setupNewNameServerGroupVisible = useSelector(
(state: RootState) => state.nameserverGroup.setupNewNameServerGroupVisible
);
const savedNSGroup = useSelector(
(state: RootState) => state.nameserverGroup.savedNameServerGroup
);
const nsGroupData = useSelector(
(state: RootState) => state.nameserverGroup.data
);
const [formNSGroup, setFormNSGroup] = useState({} as formNSGroup);
const [form] = Form.useForm();
const [editName, setEditName] = useState(false);
const [isPrimary, setIsPrimary] = useState(false);
const [editDescription, setEditDescription] = useState(false);
const inputNameRef = useRef<any>(null);
const inputDescriptionRef = useRef<any>(null);
const [selectCustom, setSelectCustom] = useState(false);
useEffect(() => {
if (editName)
inputNameRef.current!.focus({
cursor: "end",
});
}, [editName]);
useEffect(() => {
if (editDescription)
inputDescriptionRef.current!.focus({
cursor: "end",
});
}, [editDescription]);
useEffect(() => {
if (!nsGroup) return;
let newFormGroup = {
...nsGroup,
groups: getGroupNamesFromIDs(nsGroup.groups),
} as formNSGroup;
setFormNSGroup(newFormGroup);
form.setFieldsValue(newFormGroup);
if (nsGroup.id) {
setSelectCustom(true);
}
if (nsGroup.primary !== undefined) {
setIsPrimary(nsGroup.primary);
}
}, [nsGroup]);
const onCancel = () => {
dispatch(nsGroupActions.setSetupNewNameServerGroupVisible(false));
dispatch(
nsGroupActions.setNameServerGroup({
id: "",
name: "",
description: "",
primary: false,
domains: [],
nameservers: [] as NameServer[],
groups: [],
enabled: false,
} as NameServerGroup)
);
setEditName(false);
setSelectCustom(false);
setIsPrimary(false);
};
const onChange = (changedValues: any) => {
if (changedValues.primary !== undefined) {
setIsPrimary(changedValues.primary);
}
setFormNSGroup({ ...formNSGroup, ...changedValues });
};
let googleChoice = "Google DNS";
let cloudflareChoice = "Cloudflare DNS";
let quad9Choice = "Quad9 DNS";
let customChoice = "Add custom nameserver";
let defaultDNSOptions: NameServerGroup[] = [
{
name: googleChoice,
description: "Google DNS servers",
domains: [],
primary: true,
nameservers: [
{
ip: "8.8.8.8",
ns_type: "udp",
port: 53,
},
{
ip: "8.8.4.4",
ns_type: "udp",
port: 53,
},
],
groups: [],
enabled: true,
},
{
name: cloudflareChoice,
description: "Cloudflare DNS servers",
domains: [],
primary: true,
nameservers: [
{
ip: "1.1.1.1",
ns_type: "udp",
port: 53,
},
{
ip: "1.0.0.1",
ns_type: "udp",
port: 53,
},
],
groups: [],
enabled: true,
},
{
name: quad9Choice,
description: "Quad9 DNS servers",
domains: [],
primary: true,
nameservers: [
{
ip: "9.9.9.9",
ns_type: "udp",
port: 53,
},
{
ip: "149.112.112.112",
ns_type: "udp",
port: 53,
},
],
groups: [],
enabled: true,
},
];
const handleSelectChange = (value: string) => {
let nsGroupLocal = {} as NameServerGroup;
if (value === customChoice) {
nsGroupLocal = nsGroup;
} else {
defaultDNSOptions.forEach((nsg) => {
if (value === nsg.name) {
nsGroupLocal = nsg;
}
});
}
let newFormGroup = {
...nsGroupLocal,
groups: getGroupNamesFromIDs(nsGroupLocal.groups),
} as formNSGroup;
setFormNSGroup(newFormGroup);
form.setFieldsValue(newFormGroup);
setSelectCustom(true);
};
const handleFormSubmit = () => {
form
.validateFields()
.then((values) => {
const nsGroupToSave = createNSGroupToSave(values as NameServerGroup);
dispatch(
nsGroupActions.saveNameServerGroup.request({
getAccessTokenSilently: getTokenSilently,
payload: nsGroupToSave,
})
);
})
.then(() => onCancel())
.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 createNSGroupToSave = (
values: NameServerGroup
): NameServerGroupToSave => {
let [existingGroups, newGroups] = getExistingAndToCreateGroupsLists(
values.groups
);
return {
id: formNSGroup.id || null,
name: values.name ? values.name : formNSGroup.name,
description: values.description
? values.description
: formNSGroup.description,
primary: values.domains.length ? false : true,
domains: values.primary ? [] : values.domains,
nameservers: values.nameservers,
groups: existingGroups,
groupsToCreate: newGroups,
enabled: values.enabled,
} as NameServerGroupToSave;
};
const toggleEditName = (status: boolean) => {
setEditName(status);
};
const toggleEditDescription = (status: boolean) => {
setEditDescription(status);
};
const domainRegex =
/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/;
const domainValidator = (_: RuleObject, domain: string) => {
if (domainRegex.test(domain)) {
return Promise.resolve();
}
setIsPrimary(false);
return Promise.reject(
new Error(
"Please enter a valid domain, e.g. example.com or intra.example.com"
)
);
};
const nameValidator = (_: RuleObject, value: string) => {
const found = nsGroupData.find(
(u) => u.name == value && u.id !== formNSGroup.id
);
if (found) {
return Promise.reject(
new Error(
"Please enter a unique name for your nameserver configuration"
)
);
}
return Promise.resolve();
};
const ipValidator = (_: RuleObject, value: string) => {
if (!cidrRegex().test(value + "/32")) {
return Promise.reject(
new Error("Please enter a valid IP, e.g. 192.168.1.1 or 8.8.8.8")
);
}
return Promise.resolve();
};
// @ts-ignore
const formListValidator = (_: RuleObject, names) => {
if (names.length >= 3) {
return Promise.reject(
new Error("Exceeded maximum number of Nameservers. (Max is 2)")
);
}
if (names.length < 1) {
return Promise.reject(new Error("You should add at least 1 Nameserver"));
}
return Promise.resolve();
};
// @ts-ignore
const renderNSList = (
fields: FormListFieldData[],
{ add, remove }: any,
{ errors }: any
) => (
<Col>
<div style={{ width: "100%", maxWidth: "360px" }}>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
marginBottom: "10px",
display: "block",
}}
>
Nameservers
</label>
{!!fields.length && (
<Row align="middle">
<Col span={4} style={{ textAlign: "left" }}>
<Typography.Text
style={{ color: "#818183", paddingLeft: "5px" }}
></Typography.Text>
</Col>
<Col span={10} style={{ textAlign: "left" }}>
<Typography.Text style={{ color: "#818183", paddingLeft: "5px" }}>
Nameserver IP
</Typography.Text>
</Col>
<Col span={4} style={{ textAlign: "left" }}>
<Typography.Text style={{ color: "#818183", paddingLeft: "5px" }}>
Port
</Typography.Text>
</Col>
<Col span={4} />
</Row>
)}
{fields.map((field, index) => {
return (
<Row key={index}>
<Col span={4} style={{ textAlign: "left" }}>
<Form.Item
style={{ margin: "3px" }}
name={[field.name, "ns_type"]}
rules={[
{ required: true, message: "Missing first protocol" },
]}
initialValue={"udp"}
>
<Select
disabled
style={{ width: "100%" }}
className="style-like-text"
>
<Option value="udp">UDP</Option>
</Select>
</Form.Item>
</Col>
<Col span={10} style={{ margin: "1px" }}>
<Form.Item
style={{ margin: "1px" }}
name={[field.name, "ip"]}
rules={[{ validator: ipValidator }]}
>
<Input
placeholder="e.g. X.X.X.X"
style={{ width: "100%" }}
autoComplete="off"
/>
</Form.Item>
</Col>
<Col span={4} style={{ textAlign: "center" }}>
<Form.Item
style={{ margin: "1px" }}
name={[field.name, "port"]}
rules={[{ required: true, message: "Missing port" }]}
initialValue={53}
>
<InputNumber placeholder="Port" style={{ width: "100%" }} />
</Form.Item>
</Col>
<Col
span={2}
style={{
textAlign: "center",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<MinusCircleOutlined onClick={() => remove(field.name)} />
</Col>
</Row>
);
})}
<Row>
<Col span={20}>
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
block
style={{
maxWidth: "270px",
marginTop: "5px",
}}
disabled={fields.length > 1}
icon={<PlusOutlined />}
>
Add Nameserver
</Button>
<Form.ErrorList errors={errors} />
</Form.Item>
</Col>
</Row>
</div>
</Col>
);
// @ts-ignore
const renderDomains = (
fields: FormListFieldData[],
{ add, remove }: any,
{ errors }: any
) => (
<>
<Row>
<Space>
<Col>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Match domains
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "0",
}}
>
Add domain if you want to have a specific one
</Paragraph>
</Col>
</Space>
</Row>
{fields.map((field, index) => {
return (
<Row key={index} style={{ marginBottom: "5px" }}>
<Col span={22}>
<Form.Item
style={{ margin: "0" }}
{...field}
rules={[{ validator: domainValidator }]}
>
<Input
placeholder="e.g. example.com"
style={{ width: "100%" }}
autoComplete="off"
/>
</Form.Item>
</Col>
<Col
span={2}
style={{
textAlign: "center",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => remove(field.name)}
/>
</Col>
</Row>
);
})}
<Row>
<Col span={24} style={{ margin: "1px" }}>
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
block
icon={<PlusOutlined />}
style={{ marginTop: "5px" }}
>
Add Domain
</Button>
</Form.Item>
</Col>
</Row>
<Form.ErrorList errors={errors} />
</>
);
const handleChangeDisabled = (checked: boolean) => {
setFormNSGroup({
...formNSGroup,
enabled: checked,
});
};
return (
<>
{nsGroup && (
<Modal
forceRender={true}
footer={false}
onCancel={onCancel}
open={setupNewNameServerGroupVisible}
>
<Row gutter={16}>
<Col span={24}>
<Paragraph
style={{
textAlign: "start",
whiteSpace: "pre-line",
fontSize: "18px",
margin: "0px",
fontWeight: "500",
}}
>
Add Nameserver
</Paragraph>
<Paragraph
type={"secondary"}
style={{
textAlign: "start",
whiteSpace: "pre-line",
paddingBottom: "0",
}}
>
Use this nameserver to resolve domains in your network
</Paragraph>
</Col>
</Row>
{selectCustom ? (
<Form
layout="vertical"
requiredMark={false}
form={form}
onValuesChange={onChange}
>
<Row gutter={16}>
<Col span={24}>
<Header
style={{
border: "none",
}}
>
<Row align="top">
<Col flex="none" style={{ display: "flex" }}>
{!editName && !editDescription && formNSGroup.id && (
<button
type="button"
aria-label="Close"
className="ant-drawer-close"
style={{ paddingTop: 3 }}
onClick={onCancel}
>
<span
role="img"
aria-label="close"
className="anticon anticon-close"
>
<CloseOutlined size={16} />
</span>
</button>
)}
</Col>
<Col flex="auto">
{!editName && formNSGroup.id ? (
<div
className={
"access-control input-text ant-drawer-title"
}
onClick={() => toggleEditName(true)}
>
{formNSGroup.id
? formNSGroup.name
: "New nameserver group"}
</div>
) : (
<div style={{ lineHeight: "15px" }}>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Name
</label>
<Form.Item
name="name"
rules={[
{
required: true,
message:
"Please add an identifier for this nameserver group",
whitespace: true,
},
{
validator: nameValidator,
},
]}
style={{
marginBottom: "10px",
marginTop: "10px",
}}
>
<Input
placeholder="e.g. Public DNS"
ref={inputNameRef}
onPressEnter={() => toggleEditName(false)}
onBlur={() => toggleEditName(false)}
autoComplete="off"
maxLength={40}
/>
</Form.Item>
</div>
)}
{!editDescription ? (
<div
className={
"access-control input-text ant-drawer-subtitle"
}
style={{ marginTop: "0" }}
onClick={() => toggleEditDescription(true)}
>
{formNSGroup.description &&
formNSGroup.description.trim() !== ""
? formNSGroup.description
: "Add description"}
</div>
) : (
<div
style={{ lineHeight: "15px", marginTop: "24px" }}
>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Description
</label>
<Form.Item
name="description"
style={{ marginTop: "8px" }}
>
<Input
placeholder="Add description..."
ref={inputDescriptionRef}
onPressEnter={() =>
toggleEditDescription(false)
}
onBlur={() => toggleEditDescription(false)}
autoComplete="off"
/>
</Form.Item>
</div>
)}
</Col>
</Row>
</Header>
</Col>
<Col span={24} flex="auto">
<Form.List
name="nameservers"
rules={[{ validator: formListValidator }]}
>
{renderNSList}
</Form.List>
</Col>
<Col span={24} flex="auto">
<Form.List name="domains">{renderDomains}</Form.List>
</Col>
<Col span={24}>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Distribution groups
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "0",
}}
>
Advertise this route to peers that belong to the following
groups
</Paragraph>
<Form.Item
name="groups"
rules={[{ validator: selectValidator }]}
>
<Select
mode="tags"
style={{ width: "100%" }}
placeholder="Associate groups with the NS group"
tagRender={blueTagRender}
onChange={handleChangeTags}
dropdownRender={dropDownRender}
>
{tagGroups.map((m) => (
<Option key={m}>{optionRender(m)}</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="enabled" label="">
<div
style={{
display: "flex",
gap: "15px",
}}
>
<Switch
onChange={handleChangeDisabled}
defaultChecked={formNSGroup.enabled}
size="small"
/>
<div>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Enabled
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "0",
}}
>
Disable this server if you don't want it to apply
immediately
</Paragraph>
</div>
</div>
</Form.Item>
</Col>
<Col
span={24}
style={{ marginTop: "10px", marginBottom: "20px" }}
>
<Text type={"secondary"}>
Learn more about
<a
target="_blank"
rel="noreferrer"
href="https://docs.netbird.io/how-to/manage-dns-in-your-network"
>
{" "}
DNS
</a>
</Text>
</Col>
<Col
style={{
width: "100%",
}}
>
<Space
style={{
display: "flex",
justifyContent: "end",
width: "100%",
}}
>
<Button onClick={onCancel} disabled={savedNSGroup.loading}>
Cancel
</Button>
<Button
type="primary"
onClick={handleFormSubmit}
disabled={savedNSGroup.loading}
>
Create Nameserver
</Button>
</Space>
</Col>
</Row>
</Form>
) : (
<>
<Space direction={"vertical"} style={{ width: "100%" }}>
<Row align="middle">
<Col span={24} style={{ textAlign: "left" }}>
<span className="ant-form-item font-500">
Select a predefined one
</span>
</Col>
</Row>
<Row align="middle">
<Col span={24} style={{ textAlign: "center" }}>
<Select
style={{ width: "100%" }}
onChange={handleSelectChange}
options={[
{
value: googleChoice,
label: googleChoice,
},
{
value: cloudflareChoice,
label: cloudflareChoice,
},
{
value: quad9Choice,
label: quad9Choice,
},
{
value: customChoice,
label: customChoice,
},
]}
/>
</Col>
</Row>
<Row align="middle">
<Col span={24} style={{ textAlign: "left" }}>
<Col span={24} style={{ textAlign: "left" }}>
<span className="ant-form-item blue-color">
<Typography.Link
onClick={() => handleSelectChange(customChoice)}
>
or create custom
</Typography.Link>
</span>
</Col>
</Col>
</Row>
</Space>
<Space
style={{
display: "flex",
justifyContent: "end",
marginTop: "25px",
}}
>
<Button onClick={onCancel} type="primary">
Cancel
</Button>
</Space>
</>
)}
</Modal>
)}
</>
);
};
export default NameServerGroupAdd;

File diff suppressed because it is too large Load Diff

View File

@@ -366,7 +366,6 @@ const PeerUpdate = () => {
noUpdateToName() &&
noUpdateToLoginExpiration()
) {
console.log("no group update==<");
const style = { marginTop: 85 };
if (savedGroups.loading) {
message.loading({

View File

@@ -234,7 +234,6 @@ const RoutePeerUpdate = () => {
style: styleNotification,
});
} else if (savedRoute.success) {
console.log("savedRoute", savedRoute);
message.success({
content: "Route has been successfully updated.",
key: saveKey,

View File

@@ -259,7 +259,6 @@ const RouteAddNew = () => {
style: styleNotification,
});
} else if (savedRoute.success) {
console.log("savedRoute", savedRoute);
message.success({
content: "Route has been successfully added.",
key: saveKey,

View File

@@ -211,6 +211,9 @@ td.non-highlighted-table-column {
.ant-form-item-explain-error {
font-weight: 500;
font-size: 12px;
line-height: 14px;
margin-top: 2px;
}
.menlo-font,
@@ -441,4 +444,27 @@ ul.ant-list-items {
position: absolute;
top: 10px;
right: 2px;
}
.blue-color {
color: #1890FF;
}
.style-like-text .ant-select-selector {
background: transparent !important;
border: none !important;
padding: 0 !important;
color: rgba(0, 0, 0, 1) !important;
}
.style-like-text .ant-select-selection-search::after {
display: none !important;
}
.style-like-text .ant-select-selection-item {
padding-inline-end: 0 !important;
}
.style-like-text .ant-select-arrow {
display: none !important;
}

View File

@@ -28,6 +28,7 @@ const actions = {
setNameServerGroup: createAction('SET_NameServerGroup')<NameServerGroup>(),
setSetupNewNameServerGroupVisible: createAction('SET_SETUP_NEW_NameServerGroup_VISIBLE')<boolean>(),
setSetupEditNameServerGroupVisible: createAction('SET_SETUP_EDIT_NameServerGroup_VISIBLE')<boolean>(),
setSetupNewNameServerGroupHA: createAction('SET_SETUP_NEW_NameServerGroup_HA')<boolean>()
};

View File

@@ -13,6 +13,7 @@ type StateType = Readonly<{
deleteNameServerGroup: DeleteResponse<string | null>;
savedNameServerGroup: CreateResponse<NameServerGroup | null>;
setupNewNameServerGroupVisible: boolean;
setupEditNameServerGroupVisible: boolean;
setupNewNameServerGroupHA: boolean
}>;
@@ -27,17 +28,18 @@ const initialState: StateType = {
success: false,
failure: false,
error: null,
data : null
data: null,
},
savedNameServerGroup: <CreateResponse<NameServerGroup | null>>{
loading: false,
success: false,
failure: false,
error: null,
data : null
data: null,
},
setupNewNameServerGroupVisible: false,
setupNewNameServerGroupHA: false
setupEditNameServerGroupVisible: false,
setupNewNameServerGroupHA: false,
};
const data = createReducer<NameServerGroup[], ActionTypes>(initialState.data as NameServerGroup[])
@@ -79,6 +81,9 @@ const savedNameServerGroup = createReducer<CreateResponse<NameServerGroup | null
const setupNewNameServerGroupVisible = createReducer<boolean, ActionTypes>(initialState.setupNewNameServerGroupVisible)
.handleAction(actions.setSetupNewNameServerGroupVisible, (store, action) => action.payload)
const setupEditNameServerGroupVisible = createReducer<boolean, ActionTypes>(initialState.setupEditNameServerGroupVisible)
.handleAction(actions.setSetupEditNameServerGroupVisible, (store, action) => action.payload)
const setupNewNameServerGroupHA = createReducer<boolean, ActionTypes>(initialState.setupNewNameServerGroupHA)
.handleAction(actions.setSetupNewNameServerGroupHA, (store, action) => action.payload)
@@ -91,5 +96,6 @@ export default combineReducers({
deletedNameServerGroup,
savedNameServerGroup,
setupNewNameServerGroupVisible,
setupNewNameServerGroupHA
setupEditNameServerGroupVisible,
setupNewNameServerGroupHA,
});

View File

@@ -118,13 +118,13 @@ export function* setDeleteNameServerGroup(action: ReturnType<typeof actions.set
export function* deleteNameServerGroup(action: ReturnType<typeof actions.deleteNameServerGroup.request>): Generator {
try {
yield call(actions.setDeletedNameServerGroup,{
yield put(actions.setDeletedNameServerGroup({
loading: true,
success: false,
failure: false,
error: null,
data: null
} as DeleteResponse<string | null>)
}))
const effect = yield call(service.deletedNameServerGroup, action.payload);
const response = effect as ApiResponse<any>;

View File

@@ -1,21 +1,20 @@
export interface NameServerGroup {
id?: string
name: string
description: string
primary: boolean
domains: string[]
nameservers: NameServer[]
groups: string[]
enabled: boolean
id?: string;
name: string;
description: string;
primary: boolean;
domains: string[];
nameservers: NameServer[];
groups: string[];
enabled: boolean;
}
export interface NameServer {
ip: string
ns_type: string
port: number
ip: string;
ns_type: string;
port: number;
}
export interface NameServerGroupToSave extends NameServerGroup
{
groupsToCreate: string[]
}
export interface NameServerGroupToSave extends NameServerGroup {
groupsToCreate?: string[];
}

View File

@@ -174,13 +174,15 @@ export function* deletePolicy(
action: ReturnType<typeof actions.deletePolicy.request>
): Generator {
try {
yield call(actions.setDeletedPolicy, {
loading: true,
success: false,
failure: false,
error: null,
data: null,
} as DeleteResponse<string | null>);
yield put(
actions.setDeletedPolicy({
loading: true,
success: false,
failure: false,
error: null,
data: null,
})
);
const effect = yield call(service.deletedPolicy, action.payload);
const response = effect as ApiResponse<any>;

View File

@@ -549,7 +549,6 @@ export const AccessControl = () => {
})
);
};
console.log("dataTable", dataTable);
return (
<>
{!setupEditPolicyVisible && (

View File

@@ -1,78 +1,91 @@
import React, {useEffect, useState} from 'react';
import {Container} from "../components/Container";
import {
Col,
Row,
Tabs,
Typography,
} from "antd";
import type { TabsProps } from 'antd';
import React, { useEffect, useState } from "react";
import { Container } from "../components/Container";
import { Col, Row, Tabs, Typography } from "antd";
import type { TabsProps } from "antd";
import NameServerGroupUpdate from "../components/NameServerGroupUpdate";
import NameServerGroupAdd from "../components/NameServerGroupAdd";
import Nameservers from "./Nameservers";
import {actions as groupActions} from "../store/group";
import {useGetTokenSilently} from "../utils/token";
import {useDispatch, useSelector} from "react-redux";
import { actions as groupActions } from "../store/group";
import { useGetTokenSilently } 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";
import { RootState } from "typesafe-actions";
import { actions as dnsSettingsActions } from "../store/dns-settings";
import { useGetGroupTagHelpers } from "../utils/groups";
const {Title, Paragraph} = Typography;
const { Title, Paragraph } = Typography;
export const DNS = () => {
const {getTokenSilently} = useGetTokenSilently()
const dispatch = useDispatch()
const {
getGroupNamesFromIDs,
} = useGetGroupTagHelpers()
const { getTokenSilently } = useGetTokenSilently();
const dispatch = useDispatch();
const { getGroupNamesFromIDs } = useGetGroupTagHelpers();
const dnsSettingsData = useSelector((state: RootState) => state.dnsSettings.data)
const dnsSettingsData = useSelector(
(state: RootState) => state.dnsSettings.data
);
const setupEditNameServerGroupVisible = useSelector(
(state: RootState) => state.nameserverGroup.setupEditNameServerGroupVisible
);
const setupNewNameServerGroupVisible = useSelector(
(state: RootState) => state.nameserverGroup.setupNewNameServerGroupVisible
);
useEffect(() => {
dispatch(
groupActions.getGroups.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
}, []);
useEffect(() => {
dispatch(groupActions.getGroups.request({getAccessTokenSilently: getTokenSilently, payload: null}));
}, [])
const nsTabKey = "1";
const items: TabsProps["items"] = [
{
key: nsTabKey,
label: "Nameservers",
children: <Nameservers />,
},
{
key: "2",
label: "Settings",
children: <DNSSettingsForm />,
},
];
const nsTabKey = '1'
const items: TabsProps['items'] = [
{
key: nsTabKey,
label: 'Nameservers',
children: <Nameservers/>,
},
{
key: '2',
label: 'Settings',
children: <DNSSettingsForm/>,
},
]
const onTabClick = (key:string) => {
if (key == nsTabKey) {
if (!dnsSettingsData) return
dispatch(dnsSettingsActions.setDNSSettings({
disabled_management_groups: getGroupNamesFromIDs(dnsSettingsData.disabled_management_groups),
}))
}
const onTabClick = (key: string) => {
if (key == nsTabKey) {
if (!dnsSettingsData) return;
dispatch(
dnsSettingsActions.setDNSSettings({
disabled_management_groups: getGroupNamesFromIDs(
dnsSettingsData.disabled_management_groups
),
})
);
}
};
return (
<>
<Container style={{paddingTop: "40px"}}>
<Row>
<Col span={24}>
<Tabs
defaultActiveKey={nsTabKey}
items={items}
onTabClick={onTabClick}
animated={{ inkBar: true, tabPane: false }}
tabPosition="top"
/>
</Col>
</Row>
</Container>
<NameServerGroupUpdate/>
</>
)
}
return (
<>
{!setupEditNameServerGroupVisible && (
<Container style={{ paddingTop: "40px" }}>
<Row>
<Col span={24}>
<Tabs
defaultActiveKey={nsTabKey}
items={items}
onTabClick={onTabClick}
animated={{ inkBar: true, tabPane: false }}
tabPosition="top"
/>
</Col>
</Row>
</Container>
)}
{setupEditNameServerGroupVisible && <NameServerGroupUpdate />}
{setupNewNameServerGroupVisible && <NameServerGroupAdd />}
</>
);
};
export default DNS;
export default DNS;

View File

@@ -8,9 +8,8 @@ import {
Button,
Card,
Col,
Dropdown,
Input,
Menu,
Switch,
message,
Modal,
Popover,
@@ -30,8 +29,7 @@ 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 { EllipsisOutlined, ExclamationCircleOutlined } from "@ant-design/icons";
import { ExclamationCircleOutlined } from "@ant-design/icons";
import { useGetGroupTagHelpers } from "../utils/groups";
import { usePageSizeHelpers } from "../utils/pageSize";
@@ -60,30 +58,35 @@ export const Nameservers = () => {
const loading = useSelector(
(state: RootState) => state.nameserverGroup.loading
);
const updateNameServerGroupVisible = useSelector(
const addNewNameServerGroupVisible = useSelector(
(state: RootState) => state.nameserverGroup.setupNewNameServerGroupVisible
);
const savedNSGroup = useSelector(
(state: RootState) => state.nameserverGroup.savedNameServerGroup
);
const deleteNSGroup = useSelector(
(state: RootState) => state.nameserverGroup.deletedNameServerGroup
);
const [groupPopupVisible, setGroupPopupVisible] = useState("");
const [nsGroupToAction, setNsGroupToAction] = useState(
null as NameserverGroupDataTable | null
);
const [textToSearch, setTextToSearch] = useState("");
const [optionAllEnable, setOptionAllEnable] = useState("enabled");
const [optionAllEnable, setOptionAllEnable] = useState("all");
const [dataTable, setDataTable] = useState([] as NameserverGroupDataTable[]);
const [showTutorial, setShowTutorial] = useState(false);
const optionsAllEnabled = [
{ label: "Enabled", value: "enabled" },
{ label: "All", value: "all" },
{ label: "Enabled", value: "enabled" },
];
// setUserAndView makes the UserUpdate drawer visible (right side) and sets the user object
const setUserAndView = (nsGroup: NameServerGroup) => {
dispatch(nsGroupActions.setSetupNewNameServerGroupVisible(true));
dispatch(nsGroupActions.setSetupEditNameServerGroupVisible(true));
dispatch(
nsGroupActions.setNameServerGroup({
id: nsGroup.id,
@@ -166,24 +169,9 @@ export const Nameservers = () => {
setDataTable(transformDataTable(filterDataTable()));
};
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 = () => {
let name = nsGroupToAction ? nsGroupToAction.name : "";
const showConfirmDelete = (record: NameserverGroupDataTable) => {
setNsGroupToAction(record as NameserverGroupDataTable);
let name = record ? record.name : "";
confirm({
icon: <ExclamationCircleOutlined />,
title: 'Delete Nameserver group "' + name + '"',
@@ -201,7 +189,7 @@ export const Nameservers = () => {
dispatch(
nsGroupActions.deleteNameServerGroup.request({
getAccessTokenSilently: getTokenSilently,
payload: nsGroupToAction?.id || "",
payload: record?.id || "",
})
);
},
@@ -227,10 +215,13 @@ export const Nameservers = () => {
.filter((g) => groupsMap.get(g))
.map((g) => groupsMap.get(g)!);
}
let btn = (
<Button type="link" onClick={() => setUserAndView(userToAction)}>
{displayGroups.length}
<Button
type="link"
onClick={() => setUserAndView(userToAction)}
style={{ padding: "0" }}
>
+{displayGroups && displayGroups.length - 1}
</Button>
);
if (!displayGroups || displayGroups!.length < 1) {
@@ -251,13 +242,18 @@ export const Nameservers = () => {
</div>
);
});
const mainContent = <Space direction="vertical">{content}</Space>;
const updateContent =
displayGroups && displayGroups.length > 1
? content && content?.slice(1)
: content;
const mainContent = <Space direction="vertical">{updateContent}</Space>;
let popoverPlacement = "top";
if (content && content.length > 5) {
popoverPlacement = "rightTop";
}
return (
return displayGroups && displayGroups.length === 1 ? (
<> {displayGroups && displayGroups.length && displayGroups[0].name}</>
) : (
<Popover
placement={popoverPlacement as TooltipPlacement}
key={userToAction.id}
@@ -268,7 +264,9 @@ export const Nameservers = () => {
content={mainContent}
title={null}
>
{btn}
<span className="d-flex">
{displayGroups && displayGroups.length && displayGroups[0].name} {btn}
</span>
</Popover>
);
};
@@ -283,10 +281,12 @@ export const Nameservers = () => {
domains = inputDomains;
}
let btn = (
let btn = domains.length ? (
<Button type="link" onClick={() => setUserAndView(userToAction)}>
{domains.length ? domains.length : 0}
{domains.length}
</Button>
) : (
<Tag>ALL</Tag>
);
if (!domains || domains!.length < 1) {
return btn;
@@ -325,10 +325,10 @@ export const Nameservers = () => {
};
useEffect(() => {
if (updateNameServerGroupVisible) {
if (addNewNameServerGroupVisible) {
setGroupPopupVisible("");
}
}, [updateNameServerGroupVisible]);
}, [addNewNameServerGroupVisible]);
const createKey = "saving";
useEffect(() => {
@@ -380,8 +380,54 @@ export const Nameservers = () => {
}
}, [savedNSGroup]);
const createDeleteKey = "Delete";
useEffect(() => {
if (deleteNSGroup.loading) {
message.loading({
content: "Deleting...",
key: createDeleteKey,
duration: 0,
style: styleNotification,
});
} else if (deleteNSGroup.success) {
message.success({
content: "Nameserver has been deleted successfully.",
key: createDeleteKey,
duration: 2,
style: styleNotification,
});
dispatch(nsGroupActions.resetDeletedNameServerGroup(null));
} else if (deleteNSGroup.error) {
let errorMsg = "Failed to delete nameserver group";
switch (deleteNSGroup.error.statusCode) {
case 403:
errorMsg =
"Failed to delete nameserver group. You might not have enough permissions.";
break;
default:
errorMsg = deleteNSGroup.error.data.message
? deleteNSGroup.error.data.message
: errorMsg;
break;
}
message.error({
content: errorMsg,
key: createDeleteKey,
duration: 5,
style: styleNotification,
});
dispatch(
nsGroupActions.setSavedNameServerGroup({
...deleteNSGroup,
error: null,
})
);
dispatch(nsGroupActions.resetDeletedNameServerGroup(null));
}
}, [deleteNSGroup]);
const onPopoverVisibleChange = (b: boolean, key: string) => {
if (updateNameServerGroupVisible) {
if (addNewNameServerGroupVisible) {
setGroupPopupVisible("");
} else {
if (b) {
@@ -392,27 +438,6 @@ export const Nameservers = () => {
}
};
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(
@@ -423,32 +448,41 @@ export const Nameservers = () => {
);
};
const handleChangeDisabled = (checked: boolean, record: any) => {
dispatch(
nsGroupActions.saveNameServerGroup.request({
getAccessTokenSilently: getTokenSilently,
payload: { ...record, enabled: checked },
})
);
};
return (
<>
{nsGroup.length ? (
<Paragraph style={{marginTop: "5px"}}>
Add nameservers for domain name resolution in your NetBird network.
<a
target="_blank"
rel="noreferrer"
href="https://docs.netbird.io/how-to/manage-dns-in-your-network"
>
{" "}
Learn more
</a>
</Paragraph>
<Paragraph style={{ marginTop: "5px" }}>
Add nameservers for domain name resolution in your NetBird network.
<a
target="_blank"
rel="noreferrer"
href="https://docs.netbird.io/how-to/manage-dns-in-your-network"
>
{" "}
Learn more
</a>
</Paragraph>
) : (
<Paragraph style={{marginTop: "5px"}} type={"secondary"}>
Add nameservers for domain name resolution in your NetBird network.
<a
target="_blank"
rel="noreferrer"
href="https://docs.netbird.io/how-to/manage-dns-in-your-network"
>
{" "}
Learn more
</a>
</Paragraph>
<Paragraph style={{ marginTop: "5px" }} type={"secondary"}>
Add nameservers for domain name resolution in your NetBird network.
<a
target="_blank"
rel="noreferrer"
href="https://docs.netbird.io/how-to/manage-dns-in-your-network"
>
{" "}
Learn more
</a>
</Paragraph>
)}
<Space direction="vertical" size="large" style={{ display: "flex" }}>
<Row gutter={[16, 24]}>
@@ -484,7 +518,11 @@ export const Nameservers = () => {
<Row justify="end">
<Col>
{!showTutorial && (
<Button type="primary" onClick={onClickAddNewNSGroup}>
<Button
type="primary"
onClick={onClickAddNewNSGroup}
disabled={savedNSGroup.loading}
>
Add Nameserver
</Button>
)}
@@ -502,161 +540,157 @@ export const Nameservers = () => {
/>
)}
<Card bodyStyle={{ padding: 0 }}>
{!showTutorial && ( <Table
pagination={{
pageSize,
showSizeChanger: false,
showTotal: (total, range) =>
`Showing ${range[0]} to ${range[1]} of ${total} nameservers`,
}}
// 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>
);
{!showTutorial && (
<Table
pagination={{
pageSize,
showSizeChanger: false,
showTotal: (total, range) =>
`Showing ${range[0]} to ${range[1]} of ${total} nameservers`,
}}
/>
<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
trigger={["click"]}
overlay={actionsMenu}
onOpenChange={(visible) => {
if (visible)
setNsGroupToAction(record as NameserverGroupDataTable);
}}
>
<Button type="text">
<Space>
<EllipsisOutlined />
</Space>
</Button>
</Dropdown>
);
}}
/>
</Table>)}
{showTutorial && (
<Space
direction="vertical"
size="small"
align="center"
style={{
display: "flex",
padding: "45px 15px",
justifyContent: "center",
}}
>
<Title level={4} style={{ textAlign: "center" }}>
Create Nameserver
</Title>
<Paragraph
style={{
textAlign: "center",
whiteSpace: "pre-line",
}}
>
It looks like you don't have any nameservers. {"\n"}
Get started by adding one to your network.
<a
target="_blank"
rel="noreferrer"
href="https://docs.netbird.io/how-to/manage-dns-in-your-network"
>
{" "}
Learn more
</a>
</Paragraph>
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
size={"middle"}
type="primary"
onClick={() => onClickAddNewNSGroup()}
type="text"
onClick={() =>
setUserAndView(record as NameserverGroupDataTable)
}
className="tooltip-label"
>
Add nameserver
{text && text.trim() !== ""
? text
: (record as NameServerGroup).id}
</Button>
</Space>
)}
);
}}
/>
<Column
title="Enabled"
dataIndex="enabled"
align="center"
render={(text: Boolean, record) => {
return (
<Switch
onChange={(isChecked) =>
handleChangeDisabled(isChecked, record)
}
disabled={savedNSGroup.loading}
defaultChecked={!!text}
size="small"
/>
);
}}
/>
<Column
title="Nameservers"
dataIndex="nameservers"
align="center"
render={(nameservers: NameServer[]) => (
<>
{nameservers.map((nameserver) => (
<Tag key={nameserver.ip}>{nameserver.ip}</Tag>
))}
</>
)}
/>
<Column
title="Match domains"
dataIndex="domains"
align="center"
render={(text, record: NameserverGroupDataTable) => {
return renderPopoverDomains(text, record.domains, record);
}}
/>
<Column
title="Distribution 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 (
<Button
type="text"
disabled={savedNSGroup.loading}
onClick={() =>
showConfirmDelete(record as NameserverGroupDataTable)
}
danger={true}
>
Delete
</Button>
);
}}
/>
</Table>
)}
{showTutorial && (
<Space
direction="vertical"
size="small"
align="center"
style={{
display: "flex",
padding: "45px 15px",
justifyContent: "center",
}}
>
<Title level={4} style={{ textAlign: "center" }}>
Create Nameserver
</Title>
<Paragraph
style={{
textAlign: "center",
whiteSpace: "pre-line",
}}
>
It looks like you don't have any nameservers. {"\n"}
Get started by adding one to your network.
<a
target="_blank"
rel="noreferrer"
href="https://docs.netbird.io/how-to/manage-dns-in-your-network"
>
{" "}
Learn more
</a>
</Paragraph>
<Button
size={"middle"}
type="primary"
onClick={() => onClickAddNewNSGroup()}
>
Add nameserver
</Button>
</Space>
)}
</Card>
</Space>
</>