mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2443c6332d | ||
|
|
3b5193ae4e | ||
|
|
cf42dd52fc | ||
|
|
bc6842e5b5 | ||
|
|
a8ed755dda | ||
|
|
a87c06ef52 | ||
|
|
c0130d265c |
No files matched your search
@@ -64,7 +64,7 @@ const AccessControlEdit = () => {
|
||||
{ label: "Drop", value: "drop" },
|
||||
];
|
||||
const protocols: SelectProps["options"] = [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "ALL", value: "all" },
|
||||
{ label: "TCP", value: "tcp" },
|
||||
{ label: "UDP", value: "udp" },
|
||||
{ label: "ICMP", value: "icmp" },
|
||||
@@ -461,6 +461,7 @@ const AccessControlEdit = () => {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Card bordered={true} style={{ marginBottom: "7px" }}>
|
||||
<div style={{ maxWidth: "550px" }}>
|
||||
<Form
|
||||
@@ -606,6 +607,7 @@ const AccessControlEdit = () => {
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
<Row gutter={15}>
|
||||
<Col span={10}>
|
||||
|
||||
@@ -67,7 +67,7 @@ const AccessControlNew = () => {
|
||||
{ label: "Drop", value: "drop" },
|
||||
];
|
||||
const protocols: SelectProps["options"] = [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "ALL", value: "all" },
|
||||
{ label: "TCP", value: "tcp" },
|
||||
{ label: "UDP", value: "udp" },
|
||||
{ label: "ICMP", value: "icmp" },
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from "../store/nameservers/types";
|
||||
import { useGetGroupTagHelpers } from "../utils/groups";
|
||||
import { useGetTokenSilently } from "../utils/token";
|
||||
import { domainValidator } from "../utils/common";
|
||||
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
@@ -272,21 +273,6 @@ const NameServerGroupAdd = () => {
|
||||
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
|
||||
|
||||
@@ -30,12 +30,14 @@ import {
|
||||
import { useGetGroupTagHelpers } from "../utils/groups";
|
||||
import { useGetTokenSilently } from "../utils/token";
|
||||
import { Container } from "./Container";
|
||||
|
||||
import { domainValidator } from "../utils/common";
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
interface formNSGroup extends NameServerGroup {}
|
||||
|
||||
const NameServerGroupUpdate = () => {
|
||||
const NameServerGroupUpdate = (props: any) => {
|
||||
const { isGroupUpdateView, setShowGroupModal } = props;
|
||||
|
||||
const {
|
||||
blueTagRender,
|
||||
handleChangeTags,
|
||||
@@ -121,6 +123,9 @@ const NameServerGroupUpdate = () => {
|
||||
);
|
||||
setEditName(false);
|
||||
setIsPrimary(false);
|
||||
if (setShowGroupModal) {
|
||||
setShowGroupModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (changedValues: any) => {
|
||||
@@ -253,21 +258,6 @@ const NameServerGroupUpdate = () => {
|
||||
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
|
||||
@@ -316,38 +306,38 @@ const NameServerGroupUpdate = () => {
|
||||
<Col span={24} style={{ marginBottom: "15px" }}>
|
||||
<Form.Item name="enabled" label="">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
}}
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
onChange={handleChangeDisabled}
|
||||
defaultChecked={formNSGroup.enabled}
|
||||
size="small"
|
||||
checked={formNSGroup.enabled}
|
||||
onChange={handleChangeDisabled}
|
||||
defaultChecked={formNSGroup.enabled}
|
||||
size="small"
|
||||
checked={formNSGroup.enabled}
|
||||
/>
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
Enabled
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "400",
|
||||
marginBottom: "0",
|
||||
}}
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "400",
|
||||
marginBottom: "0",
|
||||
}}
|
||||
>
|
||||
{formNSGroup.enabled
|
||||
? "Disable this server if you don't want the configuration to apply immediately"
|
||||
: " Enable this server if you want the configuration to apply immediately"}
|
||||
? "Disable this server if you don't want the configuration to apply immediately"
|
||||
: " Enable this server if you want the configuration to apply immediately"}
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
@@ -568,19 +558,21 @@ const NameServerGroupUpdate = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container style={{ paddingTop: "40px" }}>
|
||||
<Breadcrumb
|
||||
style={{ marginBottom: "25px" }}
|
||||
items={[
|
||||
{
|
||||
title: <a onClick={onBreadcrumbUsersClick}>DNS</a>,
|
||||
},
|
||||
{
|
||||
title: formNSGroup.name,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Card>
|
||||
<Container style={{ paddingTop: `${!isGroupUpdateView ? "40px" : "0"}` }}>
|
||||
{!isGroupUpdateView && (
|
||||
<Breadcrumb
|
||||
style={{ marginBottom: "25px" }}
|
||||
items={[
|
||||
{
|
||||
title: <a onClick={onBreadcrumbUsersClick}>DNS</a>,
|
||||
},
|
||||
{
|
||||
title: formNSGroup.name,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<Card className={isGroupUpdateView ? "noborderPadding" : ""}>
|
||||
<Form
|
||||
layout="vertical"
|
||||
requiredMark={false}
|
||||
@@ -588,139 +580,148 @@ const NameServerGroupUpdate = () => {
|
||||
onValuesChange={onChange}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={24}>
|
||||
<Header
|
||||
style={{
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
<Row align="top">
|
||||
<Col flex="auto">
|
||||
{!editName && formNSGroup.id ? (
|
||||
<div
|
||||
className={
|
||||
"access-control input-text ant-drawer-title"
|
||||
}
|
||||
onClick={() => toggleEditName(true)}
|
||||
style={{
|
||||
fontSize: "22px",
|
||||
margin: " 0px 0px 10px",
|
||||
cursor: "pointer",
|
||||
fontWeight: "500",
|
||||
lineHeight: "24px",
|
||||
}}
|
||||
>
|
||||
{formNSGroup.id
|
||||
? formNSGroup.name
|
||||
: "New nameserver group"}
|
||||
</div>
|
||||
) : (
|
||||
<Row>
|
||||
<Col span={8}>
|
||||
<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>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{!editDescription ? (
|
||||
<div
|
||||
className={
|
||||
"access-control input-text ant-drawer-subtitle"
|
||||
}
|
||||
style={{ margin: "0 0 39px 0px" }}
|
||||
onClick={() => toggleEditDescription(true)}
|
||||
>
|
||||
{formNSGroup.description &&
|
||||
formNSGroup.description.trim() !== ""
|
||||
? formNSGroup.description
|
||||
: "Add description"}
|
||||
</div>
|
||||
) : (
|
||||
<Row>
|
||||
<Col span={8} style={{ marginBottom: "15px" }}>
|
||||
|
||||
<span className={isGroupUpdateView ? "d-none" : ""}>
|
||||
<Col span={24}>
|
||||
<Header
|
||||
style={{
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
<Row align="top">
|
||||
<Col flex="auto">
|
||||
{!editName && formNSGroup.id ? (
|
||||
<div
|
||||
style={{ lineHeight: "15px", marginTop: "24px" }}
|
||||
className={
|
||||
"access-control input-text ant-drawer-title"
|
||||
}
|
||||
onClick={() => toggleEditName(true)}
|
||||
style={{
|
||||
fontSize: "22px",
|
||||
margin: " 0px 0px 10px",
|
||||
cursor: "pointer",
|
||||
fontWeight: "500",
|
||||
lineHeight: "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>
|
||||
{formNSGroup.id
|
||||
? formNSGroup.name
|
||||
: "New nameserver group"}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Header>
|
||||
</Col>
|
||||
) : (
|
||||
<Row>
|
||||
<Col span={8}>
|
||||
<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>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{!editDescription ? (
|
||||
<div
|
||||
className={
|
||||
"access-control input-text ant-drawer-subtitle"
|
||||
}
|
||||
style={{ margin: "0 0 39px 0px" }}
|
||||
onClick={() => toggleEditDescription(true)}
|
||||
>
|
||||
{formNSGroup.description &&
|
||||
formNSGroup.description.trim() !== ""
|
||||
? formNSGroup.description
|
||||
: "Add description"}
|
||||
</div>
|
||||
) : (
|
||||
<Row>
|
||||
<Col span={8} style={{ marginBottom: "15px" }}>
|
||||
<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>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Header>
|
||||
</Col>
|
||||
|
||||
<Col span={24} style={{ marginBottom: "15px" }}>
|
||||
<Form.List
|
||||
name="nameservers"
|
||||
rules={[{ validator: formListValidator }]}
|
||||
>
|
||||
{renderNSList}
|
||||
</Form.List>
|
||||
</Col>
|
||||
<Col span={24} style={{ marginBottom: "15px" }}>
|
||||
<Form.List
|
||||
name="nameservers"
|
||||
rules={[{ validator: formListValidator }]}
|
||||
>
|
||||
{renderNSList}
|
||||
</Form.List>
|
||||
</Col>
|
||||
|
||||
<Col span={24} style={{ marginBottom: "15px" }}>
|
||||
<Form.List name="domains">{renderDomains}</Form.List>
|
||||
</Col>
|
||||
<Col span={24} style={{ marginBottom: "15px" }}>
|
||||
<Form.List name="domains">{renderDomains}</Form.List>
|
||||
</Col>
|
||||
</span>
|
||||
|
||||
<Col span={24} style={{ marginBottom: "15px" }}>
|
||||
<label
|
||||
style={{
|
||||
@@ -764,7 +765,7 @@ const NameServerGroupUpdate = () => {
|
||||
<Space
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "start",
|
||||
justifyContent: `${!isGroupUpdateView ? "start" : "end"}`,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
|
||||
+456
-425
File diff suppressed because it is too large.
Load diff
+125
-105
@@ -38,7 +38,8 @@ const customExpiresFormat = (value: Date): string | null => {
|
||||
return formatDate(value);
|
||||
};
|
||||
|
||||
const SetupKeyNew = () => {
|
||||
const SetupKeyNew = (props: any) => {
|
||||
const { isGroupUpdateView, setShowGroupModal } = props;
|
||||
const {
|
||||
optionRender,
|
||||
blueTagRender,
|
||||
@@ -53,12 +54,12 @@ const SetupKeyNew = () => {
|
||||
const savedSetupKey = useSelector(
|
||||
(state: RootState) => state.setupKey.savedSetupKey
|
||||
);
|
||||
|
||||
|
||||
const groups = useSelector((state: RootState) => state.group.data);
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const [editName, setEditName] = useState(false);
|
||||
const [formSetupKey, setFormSetupKey] = useState({} as FormSetupKey);
|
||||
const [formSetupKey, setFormSetupKey] = useState({} as FormSetupKey);
|
||||
const inputNameRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -160,6 +161,9 @@ const SetupKeyNew = () => {
|
||||
);
|
||||
setFormSetupKey({} as FormSetupKey);
|
||||
setVisibleNewSetupKey(false);
|
||||
if (setShowGroupModal) {
|
||||
setShowGroupModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (data: any) => {
|
||||
@@ -256,28 +260,36 @@ const SetupKeyNew = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Breadcrumb
|
||||
style={{ marginBottom: "25px" }}
|
||||
items={[
|
||||
{
|
||||
title: <a onClick={onBreadcrumbUsersClick}>Setup Keys</a>,
|
||||
},
|
||||
{
|
||||
title: setupKey.name,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Card bordered={true} style={{ marginBottom: "7px", border: "none" }}>
|
||||
{!isGroupUpdateView && (
|
||||
<Breadcrumb
|
||||
style={{ marginBottom: "25px" }}
|
||||
items={[
|
||||
{
|
||||
title: <a onClick={onBreadcrumbUsersClick}>Setup Keys</a>,
|
||||
},
|
||||
{
|
||||
title: setupKey.name,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<Card
|
||||
bordered={true}
|
||||
className={isGroupUpdateView ? " noborderPadding" : ""}
|
||||
style={{ marginBottom: "7px", border: "none" }}
|
||||
>
|
||||
<div style={{ maxWidth: "800px" }}>
|
||||
<h3
|
||||
style={{
|
||||
fontSize: "22px",
|
||||
fontWeight: "500",
|
||||
marginBottom: "30px",
|
||||
}}
|
||||
>
|
||||
{setupKey.name}
|
||||
</h3>
|
||||
{!isGroupUpdateView && (
|
||||
<h3
|
||||
style={{
|
||||
fontSize: "22px",
|
||||
fontWeight: "500",
|
||||
marginBottom: "30px",
|
||||
}}
|
||||
>
|
||||
{setupKey.name}
|
||||
</h3>
|
||||
)}
|
||||
<Form
|
||||
layout="vertical"
|
||||
requiredMark={false}
|
||||
@@ -287,90 +299,95 @@ const SetupKeyNew = () => {
|
||||
usage_limit: 1,
|
||||
}}
|
||||
>
|
||||
<Row style={{ marginTop: "10px" }}>
|
||||
<Col
|
||||
xs={24}
|
||||
sm={24}
|
||||
md={11}
|
||||
lg={11}
|
||||
xl={11}
|
||||
xxl={11}
|
||||
span={11}
|
||||
style={{ paddingRight: "70px" }}
|
||||
>
|
||||
<Paragraph
|
||||
{!isGroupUpdateView && (
|
||||
<Row style={{ marginTop: "10px" }}>
|
||||
<Col
|
||||
xs={24}
|
||||
sm={24}
|
||||
md={11}
|
||||
lg={11}
|
||||
xl={11}
|
||||
xxl={11}
|
||||
span={11}
|
||||
style={{
|
||||
whiteSpace: "pre-line",
|
||||
fontWeight: "500",
|
||||
margin: 0,
|
||||
paddingRight: "70px",
|
||||
}}
|
||||
>
|
||||
Key
|
||||
<Tag
|
||||
color={`${
|
||||
formSetupKey.state === "valid" ? "green" : "red"
|
||||
}`}
|
||||
<Paragraph
|
||||
style={{
|
||||
marginLeft: "10px",
|
||||
borderRadius: "2px",
|
||||
whiteSpace: "pre-line",
|
||||
fontWeight: "500",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{formSetupKey.state}
|
||||
</Tag>
|
||||
</Paragraph>
|
||||
<Input
|
||||
style={{ marginTop: "8px" }}
|
||||
disabled
|
||||
value={getFormKey(formSetupKey.key)}
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
/>
|
||||
</Col>
|
||||
Key
|
||||
<Tag
|
||||
color={`${
|
||||
formSetupKey.state === "valid" ? "green" : "red"
|
||||
}`}
|
||||
style={{
|
||||
marginLeft: "10px",
|
||||
borderRadius: "2px",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
{formSetupKey.state}
|
||||
</Tag>
|
||||
</Paragraph>
|
||||
<Input
|
||||
style={{ marginTop: "8px" }}
|
||||
disabled
|
||||
value={getFormKey(formSetupKey.key)}
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
|
||||
<Paragraph
|
||||
style={{
|
||||
whiteSpace: "pre-line",
|
||||
margin: 0,
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
|
||||
<Paragraph
|
||||
style={{
|
||||
whiteSpace: "pre-line",
|
||||
margin: 0,
|
||||
fontWeight: "500",
|
||||
}}
|
||||
></Paragraph>
|
||||
{formSetupKey.type === "one-off" ? "One-off" : "Reusable"},
|
||||
available uses
|
||||
</Paragraph>
|
||||
<Col>
|
||||
<Input
|
||||
disabled
|
||||
value={
|
||||
formSetupKey.type === "reusable" &&
|
||||
formSetupKey.usage_limit === 0
|
||||
? "unlimited"
|
||||
: formSetupKey.usage_limit - formSetupKey.used_times
|
||||
}
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
style={{ marginTop: "8px" }}
|
||||
/>
|
||||
>
|
||||
<Paragraph
|
||||
style={{
|
||||
whiteSpace: "pre-line",
|
||||
margin: 0,
|
||||
fontWeight: "500",
|
||||
}}
|
||||
></Paragraph>
|
||||
{formSetupKey.type === "one-off" ? "One-off" : "Reusable"},
|
||||
available uses
|
||||
</Paragraph>
|
||||
<Col>
|
||||
<Input
|
||||
disabled
|
||||
value={
|
||||
formSetupKey.type === "reusable" &&
|
||||
formSetupKey.usage_limit === 0
|
||||
? "unlimited"
|
||||
: formSetupKey.usage_limit - formSetupKey.used_times
|
||||
}
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
style={{ marginTop: "8px" }}
|
||||
/>
|
||||
</Col>
|
||||
</Col>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row style={{ marginTop: "39px" }}>
|
||||
</Row>
|
||||
)}
|
||||
<Row style={{ marginTop: `${isGroupUpdateView ? "0" : "39px"}` }}>
|
||||
<Col
|
||||
xs={24}
|
||||
sm={24}
|
||||
md={11}
|
||||
lg={11}
|
||||
xl={11}
|
||||
xxl={11}
|
||||
span={11}
|
||||
style={{ paddingRight: "70px" }}
|
||||
md={!isGroupUpdateView ? 11 : 24}
|
||||
lg={!isGroupUpdateView ? 11 : 24}
|
||||
xl={!isGroupUpdateView ? 11 : 24}
|
||||
xxl={!isGroupUpdateView ? 11 : 24}
|
||||
span={!isGroupUpdateView ? 11 : 24}
|
||||
style={{
|
||||
paddingRight: `${!isGroupUpdateView ? "70px" : "0"}`,
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{
|
||||
@@ -407,20 +424,23 @@ const SetupKeyNew = () => {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
|
||||
<Paragraph style={{ margin: 0, fontWeight: "500" }}>
|
||||
Expires
|
||||
</Paragraph>
|
||||
<Row>
|
||||
<Input
|
||||
style={{ marginTop: "8px" }}
|
||||
disabled
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
value={customExpiresFormat(new Date(formSetupKey.expires))!}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
{!isGroupUpdateView && (
|
||||
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
|
||||
<Paragraph style={{ margin: 0, fontWeight: "500" }}>
|
||||
Expires
|
||||
</Paragraph>
|
||||
<Row>
|
||||
<Input
|
||||
style={{ marginTop: "8px" }}
|
||||
disabled
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
value={
|
||||
customExpiresFormat(new Date(formSetupKey.expires))!
|
||||
}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</Form>
|
||||
</div>
|
||||
@@ -428,7 +448,7 @@ const SetupKeyNew = () => {
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "start",
|
||||
justifyContent: `${!isGroupUpdateView ? "start" : "end"}`,
|
||||
padding: 0,
|
||||
gap: "10px",
|
||||
marginTop: "24px",
|
||||
|
||||
@@ -154,7 +154,7 @@ const SetupKeyNew = () => {
|
||||
auto_groups: [] as string[],
|
||||
usage_limit: 0,
|
||||
used_times: 0,
|
||||
expires_in: 0,
|
||||
expires_in: 7,
|
||||
} as SetupKey)
|
||||
);
|
||||
setFormSetupKey({} as FormSetupKey);
|
||||
@@ -220,8 +220,7 @@ const SetupKeyNew = () => {
|
||||
);
|
||||
|
||||
const changesDetected = (): boolean => {
|
||||
console.log("formSetupKey", formSetupKey);
|
||||
return (
|
||||
return (
|
||||
formSetupKey.name == null ||
|
||||
formSetupKey.name !== setupKey.name ||
|
||||
formSetupKey?.usage_limit !== setupKey.usage_limit
|
||||
@@ -229,8 +228,7 @@ const SetupKeyNew = () => {
|
||||
};
|
||||
|
||||
const groupsChanged = (): boolean => {
|
||||
console.log("groupsChanged", formSetupKey);
|
||||
if (
|
||||
if (
|
||||
setupKey &&
|
||||
setupKey?.auto_groups &&
|
||||
formSetupKey?.autoGroupNames &&
|
||||
@@ -346,7 +344,7 @@ const SetupKeyNew = () => {
|
||||
onValuesChange={onChange}
|
||||
initialValues={{
|
||||
usage_limit: 1,
|
||||
expires_in: "7",
|
||||
expires_in: 7,
|
||||
}}
|
||||
>
|
||||
<Row>
|
||||
@@ -462,6 +460,13 @@ const SetupKeyNew = () => {
|
||||
<Form.Item
|
||||
name="expires_in"
|
||||
rules={[
|
||||
{
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 365,
|
||||
message:
|
||||
"The expiration should be set between 1 and 365 days",
|
||||
},
|
||||
{ required: true, message: "Please enter expiration date" },
|
||||
]}
|
||||
>
|
||||
@@ -469,6 +474,7 @@ const SetupKeyNew = () => {
|
||||
placeholder={`2`}
|
||||
type="number"
|
||||
addonAfter=" Days"
|
||||
value={setupKey.expires_in}
|
||||
style={{ width: "160px", marginTop: "5px" }}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -476,7 +482,7 @@ const SetupKeyNew = () => {
|
||||
type={"secondary"}
|
||||
style={{ marginTop: "-18px", marginBottom: 0 }}
|
||||
>
|
||||
Should be between 1 and 180 days
|
||||
Should be between 1 and 365 days
|
||||
</Paragraph>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal } from "antd";
|
||||
import SetupKeyNew from "./SetupKeyEdit";
|
||||
export const UpdateKeyGroupModal = (props:any) => {
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
closable={false}
|
||||
open={true}
|
||||
footer={[]}
|
||||
onCancel={() => props.setShowGroupModal(false)}
|
||||
width={450}
|
||||
>
|
||||
<SetupKeyNew
|
||||
isGroupUpdateView={true}
|
||||
setShowGroupModal={props.setShowGroupModal}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal } from "antd";
|
||||
import NameServerGroupUpdate from "./NameServerGroupUpdate";
|
||||
export const UpdateNameServerGroupModal = (props:any) => {
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
closable={false}
|
||||
open={true}
|
||||
footer={[]}
|
||||
onCancel={() => props.setShowGroupModal(false)}
|
||||
width={450}
|
||||
>
|
||||
<NameServerGroupUpdate
|
||||
isGroupUpdateView={true}
|
||||
setShowGroupModal={props.setShowGroupModal}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal } from "antd";
|
||||
import PeerUpdate from "./PeerUpdate";
|
||||
export const UpdatePeerGroupModal = (props:any) => {
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
closable={false}
|
||||
open={true}
|
||||
footer={[]}
|
||||
onCancel={() => props.setShowGroupModal(false)}
|
||||
width={450}
|
||||
>
|
||||
<PeerUpdate
|
||||
isGroupUpdateView={true}
|
||||
setShowGroupModal={props.setShowGroupModal}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal } from "antd";
|
||||
import UserEdit from "./UserEdit";
|
||||
|
||||
export const UpdateUsersGroupModal = (props: any) => {
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
closable={false}
|
||||
open={true}
|
||||
footer={[]}
|
||||
onCancel={() => props.setShowGroupModal(false)}
|
||||
width={450}
|
||||
>
|
||||
{props.showGroupModal && (
|
||||
<UserEdit
|
||||
isGroupUpdateView={true}
|
||||
setShowGroupModal={props.setShowGroupModal}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+305
-257
File diff suppressed because it is too large.
Load diff
+28
-1
@@ -478,14 +478,41 @@ ul.ant-list-items {
|
||||
margin-bottom: 39px;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
cursor: auto !important;
|
||||
}
|
||||
|
||||
.groupsSelect {
|
||||
max-width: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1220px) {
|
||||
.groupsSelect{
|
||||
.groupsSelect {
|
||||
margin-left: 0;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.setting-nav {
|
||||
flex-flow: column !important;
|
||||
}
|
||||
|
||||
.setting-nav>div {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.noborderPadding .ant-card-body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.noborderPadding {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.d-none{
|
||||
display: none!important;
|
||||
}
|
||||
@@ -74,13 +74,13 @@ export function* setDeleteGroup(action: ReturnType<typeof actions.setDeleteGrou
|
||||
|
||||
export function* deleteGroup(action: ReturnType<typeof actions.deleteGroup.request>): Generator {
|
||||
try {
|
||||
yield call(actions.setDeleteGroup,{
|
||||
yield put(actions.setDeleteGroup({
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null
|
||||
} as DeleteResponse<string | null>)
|
||||
} as DeleteResponse<string | null>))
|
||||
|
||||
const effect = yield call(service.deleteGroup, action.payload);
|
||||
const response = effect as ApiResponse<any>;
|
||||
@@ -93,8 +93,8 @@ export function* deleteGroup(action: ReturnType<typeof actions.deleteGroup.reque
|
||||
data: response.body
|
||||
} as DeleteResponse<string | null>));
|
||||
|
||||
const rules = (yield select(state => state.rule.data)) as Group[]
|
||||
yield put(actions.getGroups.success(rules.filter((p:Group) => p.id !== action.payload.payload)))
|
||||
const groups = (yield select(state => state.group.data)) as Group[]
|
||||
yield put(actions.getGroups.success(groups.filter((p:Group) => p.id !== action.payload.payload)))
|
||||
} catch (err) {
|
||||
yield put(actions.deleteGroup.failure({
|
||||
loading: false,
|
||||
|
||||
+22
-7
@@ -59,22 +59,22 @@ function getFormattedDate(date, preformattedDate = false, hideYear = false) {
|
||||
|
||||
if (minutes < 10) {
|
||||
// Adding leading zero to minutes
|
||||
minutes = `0${ minutes }`;
|
||||
minutes = `0${minutes}`;
|
||||
}
|
||||
|
||||
if (preformattedDate) {
|
||||
// Today
|
||||
// Yesterday
|
||||
return `${ preformattedDate }`;
|
||||
return `${preformattedDate}`;
|
||||
}
|
||||
|
||||
if (hideYear) {
|
||||
// 10. January
|
||||
return `${ day }. ${ month }`;
|
||||
return `${day}. ${month}`;
|
||||
}
|
||||
|
||||
// 10. January 2017.
|
||||
return `${ day }. ${ month } ${ year }`;
|
||||
return `${day}. ${month} ${year}`;
|
||||
}
|
||||
|
||||
export const fullDate = (dateParam) => {
|
||||
@@ -107,11 +107,11 @@ export const timeAgo = (dateParam) => {
|
||||
} else if (seconds < 5) {
|
||||
return 'just now';
|
||||
} else if (seconds < 60) {
|
||||
return `${ seconds } seconds ago`;
|
||||
return `${seconds} seconds ago`;
|
||||
} else if (seconds < 90) {
|
||||
return 'about a minute ago';
|
||||
} else if (minutes < 60) {
|
||||
return `${ minutes } minutes ago`;
|
||||
return `${minutes} minutes ago`;
|
||||
} else if (isToday) {
|
||||
return getFormattedDate(date, 'today'); // Today at 10:20
|
||||
} else if (isYesterday) {
|
||||
@@ -133,4 +133,19 @@ export const isNetBirdHosted = () => {
|
||||
|
||||
export const isLocalDev = () => {
|
||||
return window.location.hostname.includes("localhost")
|
||||
}
|
||||
}
|
||||
|
||||
const domainRegex =
|
||||
/^(?!.*\s)[a-zA-Z0-9](?!.*\s$)(?!.*\.$)(?:(?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.){1,126}(?!-)[a-zA-Z0-9-]{1,63}(?<!-)$/;
|
||||
|
||||
export const domainValidator = (_, domain) => {
|
||||
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"
|
||||
)
|
||||
);
|
||||
};
|
||||
+20
-20
@@ -1,25 +1,25 @@
|
||||
import { useState } from "react";
|
||||
import { storeFilterState } from "./filterState";
|
||||
import {useState} from "react";
|
||||
import {storeFilterState} from "./filterState";
|
||||
|
||||
export const usePageSizeHelpers = () => {
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
export const usePageSizeHelpers = (defaultSize = 10) => {
|
||||
const [pageSize, setPageSize] = useState(defaultSize);
|
||||
|
||||
const onChangePageSize = (value: string, pageName: string) => {
|
||||
setPageSize(parseInt(value.toString()));
|
||||
storeFilterState(pageName, "pageSize", parseInt(value.toString()));
|
||||
};
|
||||
const onChangePageSize = (value: string, pageName: string) => {
|
||||
setPageSize(parseInt(value.toString()));
|
||||
storeFilterState(pageName, "pageSize", parseInt(value.toString()));
|
||||
};
|
||||
|
||||
const pageSizeOptions = [
|
||||
{ label: "10", value: "10" },
|
||||
{ label: "25", value: "25" },
|
||||
{ label: "50", value: "50" },
|
||||
{ label: "100", value: "100" },
|
||||
{ label: "1000", value: "1000" },
|
||||
];
|
||||
const pageSizeOptions = [
|
||||
{label: "10", value: "10"},
|
||||
{label: "25", value: "25"},
|
||||
{label: "50", value: "50"},
|
||||
{label: "100", value: "100"},
|
||||
{label: "1000", value: "1000"},
|
||||
];
|
||||
|
||||
return {
|
||||
onChangePageSize,
|
||||
pageSize,
|
||||
pageSizeOptions,
|
||||
};
|
||||
return {
|
||||
onChangePageSize,
|
||||
pageSize,
|
||||
pageSizeOptions,
|
||||
};
|
||||
};
|
||||
@@ -72,7 +72,7 @@ export const AccessControl = () => {
|
||||
const { onChangePageSize, pageSizeOptions, pageSize } = usePageSizeHelpers();
|
||||
const { getTokenSilently } = useGetTokenSilently();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
const policies = useSelector((state: RootState) => state.policy.data);
|
||||
const failed = useSelector((state: RootState) => state.policy.failed);
|
||||
const loading = useSelector((state: RootState) => state.policy.loading);
|
||||
@@ -420,6 +420,7 @@ export const AccessControl = () => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const toggleModalGroups = (
|
||||
title: string,
|
||||
groups: Group[] | string[] | null,
|
||||
|
||||
+251
-230
File diff suppressed because it is too large.
Load diff
+18
-3
@@ -50,6 +50,7 @@ import AddPeerPopup from "../components/popups/addpeer/addpeer/AddPeerPopup";
|
||||
import { getLocalItem, setLocalItem, StorageKey } from "../services/local";
|
||||
import { useOidcUser } from "@axa-fr/react-oidc";
|
||||
import { useGetGroupTagHelpers } from "../utils/groups";
|
||||
import { UpdatePeerGroupModal } from "../components/UpdatePeerGroupModal";
|
||||
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
const { Column } = Table;
|
||||
@@ -81,6 +82,7 @@ export const Peers = () => {
|
||||
const [addPeerModalOpen, setAddPeerModalOpen] = useState(false);
|
||||
const { oidcUser } = useOidcUser();
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [showGroupModal, setShowGroupModal] = useState(false);
|
||||
|
||||
const [textToSearch, setTextToSearch] = useState("");
|
||||
const [groupsFilterArray, setGroupsFilterArray] = useState([]);
|
||||
@@ -441,6 +443,16 @@ export const Peers = () => {
|
||||
dispatch(peerActions.setUpdateGroupsVisible(false));
|
||||
};
|
||||
|
||||
const setGroupVisible = (peerToAction: Peer, status: boolean) => {
|
||||
if (status) {
|
||||
dispatch(peerActions.setPeer({ ...peerToAction }));
|
||||
setShowGroupModal(true);
|
||||
return;
|
||||
}
|
||||
dispatch(peerActions.setPeer(null));
|
||||
setShowGroupModal(true);
|
||||
};
|
||||
|
||||
const renderPopoverGroups = (
|
||||
label: string,
|
||||
groups: Group[] | string[] | null,
|
||||
@@ -492,7 +504,7 @@ export const Peers = () => {
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => setUpdateGroupsVisible(peerToAction, true)}
|
||||
onClick={() => setGroupVisible(peerToAction, true)}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
@@ -616,7 +628,7 @@ export const Peers = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!peer && (
|
||||
{(!peer || (peer && showGroupModal)) && (
|
||||
<>
|
||||
<Container style={{ paddingTop: "40px" }}>
|
||||
<Row>
|
||||
@@ -935,7 +947,10 @@ export const Peers = () => {
|
||||
{confirmModalContextHolder}
|
||||
</>
|
||||
)}
|
||||
{peer && <PeerUpdate />}
|
||||
{peer && !showGroupModal && <PeerUpdate />}
|
||||
{showGroupModal && (
|
||||
<UpdatePeerGroupModal setShowGroupModal={setShowGroupModal} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ import { ExclamationCircleOutlined, MinusOutlined } from "@ant-design/icons";
|
||||
import { actions as peerActions } from "../store/peer";
|
||||
import { useOidcUser } from "@axa-fr/react-oidc";
|
||||
import { storeFilterState, getFilterState } from "../utils/filterState";
|
||||
|
||||
import { UpdateUsersGroupModal } from "../components/UpdateUsersGroupModal";
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
const { Column } = Table;
|
||||
|
||||
@@ -56,6 +56,8 @@ export const RegularUsers = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [showGroupModal, setShowGroupModal] = useState(false);
|
||||
|
||||
|
||||
const groups = useSelector((state: RootState) => state.group.data);
|
||||
const users = useSelector((state: RootState) => state.user.regularUsers);
|
||||
@@ -89,6 +91,20 @@ export const RegularUsers = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const setUserAndViewGroups = (user: User) => {
|
||||
dispatch(
|
||||
userActions.setUser({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
auto_groups: user.auto_groups ? user.auto_groups : [],
|
||||
name: user.name,
|
||||
is_current: user.is_current,
|
||||
} as User)
|
||||
);
|
||||
setShowGroupModal(true)
|
||||
};
|
||||
|
||||
const transformDataTable = (d: User[]): UserDataTable[] => {
|
||||
return d.map((p) => ({ key: p.id, ...p } as UserDataTable));
|
||||
};
|
||||
@@ -109,7 +125,6 @@ export const RegularUsers = () => {
|
||||
}, [savedUser]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("here");
|
||||
if (!loading && groups.length && users) {
|
||||
const searchText = getFilterState("userFilter", "search");
|
||||
if (searchText) setTextToSearch(searchText);
|
||||
@@ -202,7 +217,7 @@ export const RegularUsers = () => {
|
||||
}
|
||||
|
||||
let btn = (
|
||||
<Button type="link" onClick={() => setUserAndView(userToAction)}>
|
||||
<Button type="link" onClick={() => setUserAndViewGroups(userToAction)}>
|
||||
{displayGroups.length}
|
||||
</Button>
|
||||
);
|
||||
@@ -271,6 +286,7 @@ export const RegularUsers = () => {
|
||||
dispatch(userActions.setUpdateUserDrawerVisible(false));
|
||||
dispatch(userActions.setSavedUser({ ...savedUser, success: false }));
|
||||
dispatch(userActions.resetSavedUser(null));
|
||||
setShowGroupModal(false)
|
||||
} else if (savedUser.error) {
|
||||
let errorMsg = "Failed to update user";
|
||||
switch (savedUser.error.statusCode) {
|
||||
@@ -601,6 +617,12 @@ export const RegularUsers = () => {
|
||||
</Row>
|
||||
</Container>
|
||||
<InviteUserPopup />
|
||||
{showGroupModal && (
|
||||
<UpdateUsersGroupModal
|
||||
showGroupModal={showGroupModal}
|
||||
setShowGroupModal={setShowGroupModal}
|
||||
/>
|
||||
)}
|
||||
{confirmModalContextHolder}
|
||||
</>
|
||||
);
|
||||
|
||||
Loaded 20 of 23 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user