mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b103ba0f1 | ||
|
|
9fa1b4d568 | ||
|
|
0695ebd080 | ||
|
|
b595e0d6a8 | ||
|
|
ca822d428e | ||
|
|
c49e766b92 | ||
|
|
33621cae5d | ||
|
|
07cff534e8 | ||
|
|
71f1745b82 | ||
|
|
ac428a0044 | ||
|
|
1de8538884 | ||
|
|
cec15f6471 | ||
|
|
87425f80a4 | ||
|
|
39c56ab22e | ||
|
|
571c2a6e64 | ||
|
|
8bb421abcd |
No files matched your search
Generated
+52
@@ -57,6 +57,7 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/react-syntax-highlighter": "^15.5.3"
|
||||
}
|
||||
},
|
||||
@@ -3478,6 +3479,21 @@
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
|
||||
"integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash.castarray": "^4.4.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.18.1.tgz",
|
||||
@@ -10546,11 +10562,23 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.castarray": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
||||
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
@@ -19239,6 +19267,18 @@
|
||||
"loader-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@tailwindcss/typography": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
|
||||
"integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash.castarray": "^4.4.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
}
|
||||
},
|
||||
"@testing-library/dom": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.18.1.tgz",
|
||||
@@ -24518,11 +24558,23 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash.castarray": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
||||
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||
},
|
||||
"lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/react-syntax-highlighter": "^15.5.3"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
'postcss-focus-visible': {
|
||||
replaceWith: '[data-focus-visible-added]',
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
+1
-1
@@ -22,7 +22,7 @@ import {SecureLoading} from "./components/Loading";
|
||||
import DNS from "./views/DNS";
|
||||
import Activity from "./views/Activity";
|
||||
import Settings from "./views/Settings";
|
||||
|
||||
import './styles/tailwind.css'
|
||||
|
||||
const {Header, Content} = Layout;
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
function SunIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
|
||||
<path d="M12.5 10a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
d="M10 5.5v-1M13.182 6.818l.707-.707M14.5 10h1M13.182 13.182l.707.707M10 15.5v-1M6.11 13.889l.708-.707M4.5 10h1M6.11 6.111l.708.707"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MoonIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
|
||||
<path d="M15.224 11.724a5.5 5.5 0 0 1-6.949-6.949 5.5 5.5 0 1 0 6.949 6.949Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ModeToggle() {
|
||||
function disableTransitionsTemporarily() {
|
||||
document.documentElement.classList.add('[&_*]:!transition-none')
|
||||
window.setTimeout(() => {
|
||||
document.documentElement.classList.remove('[&_*]:!transition-none')
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function toggleMode() {
|
||||
disableTransitionsTemporarily()
|
||||
|
||||
let isDarkMode = document.documentElement.classList.toggle('dark')
|
||||
|
||||
if (document.documentElement.getAttribute("data-dark-mode") !== "false") {
|
||||
delete window.localStorage.isDarkMode
|
||||
document.documentElement.setAttribute('data-dark-mode', 'false');
|
||||
} else {
|
||||
window.localStorage.isDarkMode = isDarkMode
|
||||
document.documentElement.setAttribute('data-dark-mode', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5"
|
||||
aria-label="Toggle dark mode"
|
||||
onClick={toggleMode}
|
||||
>
|
||||
<SunIcon className="h-5 w-5 stroke-zinc-900 hidden dark:block" />
|
||||
<MoonIcon className="dark:hidden h-5 w-5 stroke-white dark:block" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -314,7 +314,7 @@ const NameServerGroupUpdate = () => {
|
||||
// @ts-ignore
|
||||
const renderNSList = (fields: FormListFieldData[], {add, remove}, {errors}) => (
|
||||
<>
|
||||
<Row>Nameservers</Row>
|
||||
<Row><label>Nameservers</label></Row>
|
||||
{!!fields.length && (
|
||||
|
||||
<Row align='middle'>
|
||||
@@ -384,7 +384,7 @@ const NameServerGroupUpdate = () => {
|
||||
<Row>
|
||||
<Space>
|
||||
<Col>
|
||||
Match domains
|
||||
<label>Match domains</label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Tooltip title="Only queries to domains specified here will be resolved by these nameservers."
|
||||
|
||||
@@ -13,6 +13,7 @@ import {RootState} from "typesafe-actions";
|
||||
import {actions as userActions} from "../store/user";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {actions as personalAccessTokenActions} from "../store/personal-access-token";
|
||||
import {ModeToggle} from "./ModeToggle";
|
||||
|
||||
const {useBreakpoint} = Grid;
|
||||
|
||||
@@ -170,6 +171,9 @@ const Navbar = () => {
|
||||
defaultSelectedKeys={[location.pathname]} items={menuItems}/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col style={{width: "3%"}}>
|
||||
<ModeToggle />
|
||||
</Col>
|
||||
{hideMenuUser &&
|
||||
<Col>
|
||||
<Dropdown overlay={menuUser} placement="bottomRight" trigger={['click']}>
|
||||
|
||||
+54
-42
@@ -10,7 +10,7 @@ import {
|
||||
Row,
|
||||
Select,
|
||||
Skeleton,
|
||||
Space, Table,
|
||||
Space, Switch, Table,
|
||||
Tag, Typography
|
||||
} from "antd";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
@@ -63,6 +63,7 @@ const UserEdit = () => {
|
||||
|
||||
const [formUser, setFormUser] = useState({} as FormUser)
|
||||
const [form] = Form.useForm()
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
const [confirmModal, confirmModalContextHolder] = Modal.useModal();
|
||||
|
||||
@@ -87,10 +88,20 @@ const UserEdit = () => {
|
||||
name: values.name,
|
||||
groupsToCreate: groupsToCreate,
|
||||
auto_groups: autoGroups,
|
||||
is_service_user: isServiceUser
|
||||
is_service_user: isServiceUser,
|
||||
is_blocked: !values.is_active
|
||||
} as UserToSave
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(users) {
|
||||
let currentUser = users.find((user) => user.is_current)
|
||||
if(currentUser) {
|
||||
setIsAdmin(currentUser.role === "admin");
|
||||
}
|
||||
}
|
||||
}, [users])
|
||||
|
||||
const handleFormSubmit = () => {
|
||||
form.validateFields()
|
||||
.then((values) => {
|
||||
@@ -274,29 +285,13 @@ const UserEdit = () => {
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
email: user.email,
|
||||
is_blocked: user.is_blocked,
|
||||
is_active: !user.is_blocked,
|
||||
autoGroupsNames: currentGroups,
|
||||
})
|
||||
}
|
||||
}, [form, user, currentGroups])
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<Text onClick={() => onBreadcrumbUsersClick("Users")}>
|
||||
Users
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: (
|
||||
<Text onClick={() => onBreadcrumbUsersClick("Service Users")}>
|
||||
Service Users
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const isUserAdmin = (userId: string): boolean => {
|
||||
return users.find(u => u.id === userId)?.role === "admin"
|
||||
@@ -308,10 +303,10 @@ const UserEdit = () => {
|
||||
<Breadcrumb style={{marginBottom: "30px"}}
|
||||
items={[
|
||||
{
|
||||
title: <a href = "" onClick={() => onBreadcrumbUsersClick("Users")}>All Users</a>,
|
||||
title: <a onClick={() => onBreadcrumbUsersClick("Users")}>All Users</a>,
|
||||
},
|
||||
{
|
||||
title: <a href= "" onClick={() => onBreadcrumbUsersClick(tab)}>{tab}</a>,
|
||||
title: <a onClick={() => onBreadcrumbUsersClick(tab)}>{tab}</a>,
|
||||
// menu: { items: menuItems },
|
||||
},
|
||||
{
|
||||
@@ -332,6 +327,8 @@ const UserEdit = () => {
|
||||
name: formUser.name,
|
||||
role: formUser.role,
|
||||
email: formUser.email,
|
||||
is_blocked: formUser.is_blocked,
|
||||
is_active: !formUser.is_blocked,
|
||||
autoGroupsNames: formUser.autoGroupsNames,
|
||||
}}
|
||||
>
|
||||
@@ -364,30 +361,45 @@ const UserEdit = () => {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{!user.is_service_user && <Row style={{paddingBottom: "15px"}}>
|
||||
<Col span={9}>
|
||||
<Form.Item
|
||||
name="autoGroupsNames"
|
||||
label={<Text style={{}}>Auto-assigned groups</Text>}
|
||||
tooltip="Every peer enrolled with this user will be automatically added to these groups"
|
||||
rules={[{validator: selectValidator}]}
|
||||
>
|
||||
<Select mode="tags"
|
||||
style={{width: '100%'}}
|
||||
placeholder="Associate groups with the user"
|
||||
tagRender={tagRender}
|
||||
dropdownRender={dropDownRender}
|
||||
disabled={oidcUser && !isUserAdmin(oidcUser.sub)}
|
||||
|
||||
<Col xs={24} sm={24} md={11} lg={11} xl={11} xxl={11} span={11}>
|
||||
<Form.Item
|
||||
name="autoGroupsNames"
|
||||
label={<Text style={{}}>Auto-assigned groups</Text>}
|
||||
tooltip="Every peer enrolled with this user will be automatically added to these groups"
|
||||
rules={[{validator: selectValidator}]}
|
||||
style={{marginRight: "70px"}}
|
||||
>
|
||||
{
|
||||
tagGroups.map(m =>
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
)
|
||||
}
|
||||
</Select>
|
||||
<Select mode="tags"
|
||||
placeholder="Associate groups with the user"
|
||||
tagRender={tagRender}
|
||||
dropdownRender={dropDownRender}
|
||||
disabled={oidcUser && !isUserAdmin(oidcUser.sub)}
|
||||
>
|
||||
{
|
||||
tagGroups.map(m =>
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
)
|
||||
}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
{!user.is_current && isAdmin && (
|
||||
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
|
||||
<Form.Item
|
||||
valuePropName="checked"
|
||||
name="is_active"
|
||||
label="Active"
|
||||
style={{marginRight: "50px"}}
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Col>)}
|
||||
</Row>}
|
||||
|
||||
<Space style={{display: 'flex', justifyContent: 'start'}}>
|
||||
<Button disabled={loading} onClick={onCancel}>Cancel</Button>
|
||||
<Button type="primary"
|
||||
|
||||
+914
-50
File diff suppressed because it is too large.
Load diff
@@ -5,8 +5,6 @@ import service from './service';
|
||||
import actions from './actions';
|
||||
import serviceGroup from "../group/service";
|
||||
import {Group} from "../group/types";
|
||||
import {actions as groupActions} from "../group";
|
||||
import {PersonalAccessToken} from "../personal-access-token/types";
|
||||
|
||||
export function* getUsers(action: ReturnType<typeof actions.getUsers.request>): Generator {
|
||||
try {
|
||||
@@ -74,7 +72,8 @@ export function* saveUser(action: ReturnType<typeof actions.saveUser.request>):
|
||||
email: userToSave.email,
|
||||
role: userToSave.role,
|
||||
auto_groups: newGroups,
|
||||
is_service_user: userToSave.is_service_user
|
||||
is_service_user: userToSave.is_service_user,
|
||||
is_blocked: userToSave.is_blocked
|
||||
} as UserToSave
|
||||
|
||||
let effect
|
||||
|
||||
@@ -7,10 +7,12 @@ export interface User {
|
||||
auto_groups: string[];
|
||||
is_current?: boolean;
|
||||
is_service_user?: boolean;
|
||||
is_blocked?: boolean;
|
||||
}
|
||||
|
||||
export interface FormUser extends User {
|
||||
autoGroupsNames: string[]
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
export interface UserToSave extends User {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
:root {
|
||||
--shiki-color-text: theme('colors.white');
|
||||
--shiki-token-constant: theme('colors.orange.300');
|
||||
--shiki-token-string: theme('colors.orange.300');
|
||||
--shiki-token-comment: theme('colors.zinc.500');
|
||||
--shiki-token-keyword: theme('colors.sky.300');
|
||||
--shiki-token-parameter: theme('colors.pink.300');
|
||||
--shiki-token-function: theme('colors.violet.300');
|
||||
--shiki-token-string-expression: theme('colors.orange.300');
|
||||
--shiki-token-punctuation: theme('colors.zinc.200');
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.videowrapper {
|
||||
float: none;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding-bottom: 56.25%;
|
||||
padding-top: 25px;
|
||||
height: 0;
|
||||
}
|
||||
.videowrapper iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -430,9 +430,9 @@ export const AccessControl = () => {
|
||||
/>
|
||||
<Column title="Direction" dataIndex="flow"
|
||||
render={(text, record: RuleDataTable, index) => {
|
||||
const s = {minWidth: 50, textAlign: "center"} as React.CSSProperties
|
||||
const s = {minWidth: 50, textAlign: "center", fontSize: "20pt", paddingBottom: "23px"} as React.CSSProperties
|
||||
if (text === "bidirect")
|
||||
return <Tag color="processing" style={s}><img src={bidirect}/></Tag>
|
||||
return <Tag color="processing" style={s}>↔</Tag>
|
||||
else if (text === "srcToDest") {
|
||||
return <Tag color="green" style={s}><img src={outbound}/></Tag>
|
||||
} else if (text === "destToSrc") {
|
||||
|
||||
+106
-41
@@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "typesafe-actions";
|
||||
import {actions as userActions} from '../store/user';
|
||||
@@ -8,29 +8,33 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Dropdown,
|
||||
Dropdown, Empty,
|
||||
Input,
|
||||
Menu,
|
||||
message, Modal,
|
||||
Popover,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Space, Switch,
|
||||
Table,
|
||||
Tag,
|
||||
Tag, Tooltip,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import {User} from "../store/user/types";
|
||||
import {User, UserToSave} from "../store/user/types";
|
||||
import {filter} from "lodash";
|
||||
import tableSpin from "../components/Spin";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {actions as groupActions} from "../store/group";
|
||||
import {Group} from "../store/group/types";
|
||||
import {TooltipPlacement} from "antd/es/tooltip";
|
||||
import {isLocalDev, isNetBirdHosted} from "../utils/common";
|
||||
import {capitalize, isLocalDev, isNetBirdHosted} from "../utils/common";
|
||||
import {usePageSizeHelpers} from "../utils/pageSize";
|
||||
import AddServiceUserPopup from "../components/popups/AddServiceUserPopup";
|
||||
import InviteUserPopup from "../components/popups/InviteUserPopup";
|
||||
import {Peer, PeerDataTable} from "../store/peer/types";
|
||||
import {ExclamationCircleOutlined, MinusOutlined} from "@ant-design/icons";
|
||||
import {actions as peerActions} from "../store/peer";
|
||||
import {useOidcUser} from "@axa-fr/react-oidc";
|
||||
|
||||
const {Title, Paragraph, Text} = Typography;
|
||||
const {Column} = Table;
|
||||
@@ -42,10 +46,12 @@ interface UserDataTable extends User {
|
||||
const styleNotification = {marginTop: 85}
|
||||
|
||||
export const RegularUsers = () => {
|
||||
const {onChangePageSize,pageSizeOptions,pageSize} = usePageSizeHelpers()
|
||||
const {onChangePageSize, pageSizeOptions, pageSize} = usePageSizeHelpers()
|
||||
const {getTokenSilently} = useGetTokenSilently()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
const groups = useSelector((state: RootState) => state.group.data)
|
||||
const users = useSelector((state: RootState) => state.user.regularUsers);
|
||||
const failed = useSelector((state: RootState) => state.user.failed);
|
||||
@@ -114,6 +120,7 @@ export const RegularUsers = () => {
|
||||
auto_groups: userToAction?.auto_groups ? userToAction?.auto_groups : [],
|
||||
name: userToAction?.name,
|
||||
role: userToAction?.role,
|
||||
is_blocked: userToAction?.is_blocked,
|
||||
} as User));
|
||||
}
|
||||
|
||||
@@ -122,6 +129,15 @@ export const RegularUsers = () => {
|
||||
dispatch(userActions.setUser(null as unknown as User));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(users) {
|
||||
let currentUser = users.find((user) => user.is_current)
|
||||
if(currentUser) {
|
||||
setIsAdmin(currentUser.role === "admin");
|
||||
}
|
||||
}
|
||||
}, [users])
|
||||
|
||||
const renderPopoverGroups = (label: string, rowGroups: string[] | string[] | null, userToAction: UserDataTable) => {
|
||||
|
||||
let groupsMap = new Map<string, Group>();
|
||||
@@ -196,10 +212,10 @@ export const RegularUsers = () => {
|
||||
let errorMsg = "Failed to update user"
|
||||
switch (savedUser.error.statusCode) {
|
||||
case 412:
|
||||
errorMsg = savedUser.error.data
|
||||
break
|
||||
case 403:
|
||||
errorMsg = "Failed to update user. You might not have enough permissions."
|
||||
if (savedUser.error.data) {
|
||||
errorMsg = capitalize(savedUser.error.data.message)
|
||||
}
|
||||
break
|
||||
}
|
||||
message.error({
|
||||
@@ -243,18 +259,61 @@ export const RegularUsers = () => {
|
||||
name: user.name,
|
||||
is_current: user.is_current,
|
||||
is_service_user: user.is_service_user,
|
||||
is_blocked: user.is_blocked
|
||||
} as User));
|
||||
dispatch(userActions.setEditUserPopupVisible(true));
|
||||
}
|
||||
|
||||
const handleBlockUser = (block: boolean, user: UserDataTable) => {
|
||||
if (block) {
|
||||
confirmModal.confirm({
|
||||
icon: <ExclamationCircleOutlined/>,
|
||||
title: "Are you sure you want to deactivate \"" + user.name + "\"?",
|
||||
width: 600,
|
||||
content: <Space direction="vertical" size="small">
|
||||
<Paragraph>Disabling this user will disconnect their devices and block dashboard access.</Paragraph>
|
||||
</Space>,
|
||||
onOk() {
|
||||
let userToSave = createUserToSave(user, block)
|
||||
dispatch(userActions.saveUser.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: userToSave
|
||||
}));
|
||||
},
|
||||
onCancel() {
|
||||
// noop
|
||||
},
|
||||
});
|
||||
} else {
|
||||
let userToSave = createUserToSave(user, block)
|
||||
dispatch(userActions.saveUser.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: userToSave
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const createUserToSave = (values: UserDataTable, block: boolean): UserToSave => {
|
||||
return {
|
||||
id: values.id,
|
||||
role: values.role,
|
||||
name: values.name,
|
||||
groupsToCreate: Array.of(),
|
||||
auto_groups: values.auto_groups,
|
||||
is_service_user: values.is_service_user,
|
||||
is_blocked: block
|
||||
} as UserToSave
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container style={{padding: "0px"}}>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Paragraph>Manage users and their permissions.{(window.location.hostname == "app.netbird.io") ? "Same-domain email users are added automatically on first sign-in." : ""}</Paragraph>
|
||||
<Paragraph>Manage users and their
|
||||
permissions.{(window.location.hostname == "app.netbird.io") ? "Same-domain email users are added automatically on first sign-in." : ""}</Paragraph>
|
||||
<Space direction="vertical" size="large" style={{display: 'flex'}}>
|
||||
<Row gutter={[16, 24]}>
|
||||
<Row gutter={[16, 24]} style={{paddingBottom: "0px"}}>
|
||||
<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}/>
|
||||
@@ -272,15 +331,16 @@ export const RegularUsers = () => {
|
||||
xl={5}
|
||||
xxl={5} span={5}>
|
||||
{(isNetBirdHosted() || isLocalDev()) &&
|
||||
<Row justify="end">
|
||||
<Col>
|
||||
<Button type="primary" onClick={onClickInviteUser}>Invite User</Button>
|
||||
</Col>
|
||||
</Row>}
|
||||
<Row justify="end">
|
||||
<Col>
|
||||
<Button type="primary" onClick={onClickInviteUser}>Invite user</Button>
|
||||
</Col>
|
||||
</Row>}
|
||||
</Col>
|
||||
</Row>
|
||||
{failed &&
|
||||
<Alert message={failed.message} description={failed.data ? failed.data.message : " "} type="error" showIcon
|
||||
<Alert message={failed.message} description={failed.data ? failed.data.message : " "}
|
||||
type="error" showIcon
|
||||
closable/>
|
||||
}
|
||||
<Card bodyStyle={{padding: 0}}>
|
||||
@@ -305,6 +365,7 @@ export const RegularUsers = () => {
|
||||
className="tooltip-label">
|
||||
<Text
|
||||
strong>{(text && text.trim() !== "") ? text : (record as User).id}</Text>
|
||||
|
||||
</Button>
|
||||
|
||||
if ((record as User).is_current) {
|
||||
@@ -313,25 +374,18 @@ export const RegularUsers = () => {
|
||||
</div>
|
||||
}
|
||||
|
||||
if ((record as User).status === "invited") {
|
||||
return <div>{btn}
|
||||
<Tag color="gold">invited</Tag>
|
||||
</div>
|
||||
}
|
||||
|
||||
return btn
|
||||
}}
|
||||
/>
|
||||
<Column title="Name" dataIndex="name"
|
||||
onFilter={(value: string | number | boolean, record) => (record as any).name.includes(value)}
|
||||
sorter={(a, b) => ((a as any).name.localeCompare((b as any).name))}/>
|
||||
<Column title="Status" dataIndex="status"
|
||||
align="center"
|
||||
onFilter={(value: string | number | boolean, record) => (record as any).status.includes(value)}
|
||||
sorter={(a, b) => ((a as any).status.localeCompare((b as any).status))}
|
||||
render={(text, record, index) => {
|
||||
if (text == "active") {
|
||||
return <Tag color="green">{text}</Tag>
|
||||
} else if (text === "invited"){
|
||||
return <Tag color="gold">{text}</Tag>
|
||||
}
|
||||
return <Tag color="red">{text}</Tag>
|
||||
}}
|
||||
/>
|
||||
<Column title="Groups" dataIndex="groupsCount" align="center"
|
||||
render={(text, record: UserDataTable, index) => {
|
||||
return renderPopoverGroups(text, record.auto_groups, record)
|
||||
@@ -340,16 +394,27 @@ export const RegularUsers = () => {
|
||||
<Column title="Role" dataIndex="role"
|
||||
onFilter={(value: string | number | boolean, record) => (record as any).role.includes(value)}
|
||||
sorter={(a, b) => ((a as any).role.localeCompare((b as any).role))}/>
|
||||
{/*<Column title="" align="center" width="30px"*/}
|
||||
{/* render={(text, record, index) => {*/}
|
||||
{/* return (*/}
|
||||
{/* <Dropdown.Button type="text" overlay={actionsMenu}*/}
|
||||
{/* trigger={["click"]}*/}
|
||||
{/* onVisibleChange={visible => {*/}
|
||||
{/* if (visible) setUserToAction(record as UserDataTable)*/}
|
||||
{/* }}></Dropdown.Button>)*/}
|
||||
{/* }}*/}
|
||||
{/*/>*/}
|
||||
{isAdmin && (
|
||||
<Column title="Active" align="center" width="150px" dataIndex="is_blocked"
|
||||
render={(e, record: UserDataTable, index) => {
|
||||
let witch = <Switch size={"small"} checked={!e}
|
||||
disabled={record.is_current}
|
||||
onClick={(active: boolean) => {
|
||||
handleBlockUser(!active, record)
|
||||
}}
|
||||
/>
|
||||
|
||||
if (record.is_current) {
|
||||
return <Tooltip
|
||||
title="You can't block or unblock yourself">
|
||||
<Empty image={""} description={""} style={{height: "1px", width: "auto"}}/>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
return witch
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Table>
|
||||
</Card>
|
||||
</Space>
|
||||
|
||||
@@ -22,7 +22,7 @@ import {filter} from "lodash";
|
||||
import tableSpin from "../components/Spin";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {actions as groupActions} from "../store/group";
|
||||
import {isLocalDev, isNetBirdHosted} from "../utils/common";
|
||||
import {capitalize, isLocalDev, isNetBirdHosted} from "../utils/common";
|
||||
import {usePageSizeHelpers} from "../utils/pageSize";
|
||||
import AddServiceUserPopup from "../components/popups/AddServiceUserPopup";
|
||||
import {ExclamationCircleOutlined} from "@ant-design/icons";
|
||||
@@ -111,10 +111,10 @@ export const ServiceUsers = () => {
|
||||
let errorMsg = "Failed to update user"
|
||||
switch (savedUser.error.statusCode) {
|
||||
case 412:
|
||||
errorMsg = savedUser.error.data
|
||||
break
|
||||
case 403:
|
||||
errorMsg = "Failed to update user. You might not have enough permissions."
|
||||
if (savedUser.error.data) {
|
||||
errorMsg = capitalize(savedUser.error.data.message)
|
||||
}
|
||||
break
|
||||
}
|
||||
message.error({
|
||||
|
||||
@@ -329,9 +329,9 @@ export const SetupKeys = () => {
|
||||
<Col span={24}>
|
||||
<Title level={4}>Setup Keys</Title>
|
||||
<Paragraph
|
||||
style={{
|
||||
color: dataTable.length ? "black" : "#818183",
|
||||
}}
|
||||
// style={{
|
||||
// color: dataTable.length ? "black" : "#818183",
|
||||
// }}
|
||||
>
|
||||
A list of all the setup keys in your account including their name, state, type and
|
||||
expiration.
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, {useEffect, useState} from 'react';
|
||||
|
||||
import {Col, Row, Tabs, TabsProps} from "antd";
|
||||
import {Container} from "../components/Container";
|
||||
import {useOidcUser} from "@axa-fr/react-oidc";
|
||||
import {actions as userActions} from "../store/user";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
|
||||
+36
-6
@@ -1,11 +1,41 @@
|
||||
module.exports = {
|
||||
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
darkMode: "class", // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {}
|
||||
fontSize: {
|
||||
'2xs': ['0.75rem', { lineHeight: '1.25rem' }],
|
||||
xs: ['0.8125rem', { lineHeight: '1.5rem' }],
|
||||
sm: ['0.875rem', { lineHeight: '1.5rem' }],
|
||||
base: ['1rem', { lineHeight: '1.75rem' }],
|
||||
lg: ['1.125rem', { lineHeight: '1.75rem' }],
|
||||
xl: ['1.25rem', { lineHeight: '1.75rem' }],
|
||||
'2xl': ['1.5rem', { lineHeight: '2rem' }],
|
||||
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
|
||||
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
|
||||
'5xl': ['3rem', { lineHeight: '1' }],
|
||||
'6xl': ['3.75rem', { lineHeight: '1' }],
|
||||
'7xl': ['4.5rem', { lineHeight: '1' }],
|
||||
'8xl': ['6rem', { lineHeight: '1' }],
|
||||
'9xl': ['8rem', { lineHeight: '1' }],
|
||||
},
|
||||
typography: require('./typography'),
|
||||
extend: {
|
||||
boxShadow: {
|
||||
glow: '0 0 4px rgb(0 0 0 / 0.1)',
|
||||
},
|
||||
maxWidth: {
|
||||
lg: '33rem',
|
||||
'2xl': '40rem',
|
||||
'3xl': '50rem',
|
||||
'5xl': '66rem',
|
||||
},
|
||||
opacity: {
|
||||
1: '0.01',
|
||||
2.5: '0.025',
|
||||
7.5: '0.075',
|
||||
15: '0.15',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: []
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
};
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
module.exports = ({ theme }) => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
'--tw-prose-body': theme('colors.zinc.700'),
|
||||
'--tw-prose-headings': theme('colors.zinc.900'),
|
||||
'--tw-prose-links': theme('colors.orange.500'),
|
||||
'--tw-prose-links-hover': theme('colors.orange.600'),
|
||||
'--tw-prose-links-underline': theme('colors.orange.500 / 0.3'),
|
||||
'--tw-prose-bold': theme('colors.zinc.900'),
|
||||
'--tw-prose-counters': theme('colors.zinc.500'),
|
||||
'--tw-prose-bullets': theme('colors.zinc.300'),
|
||||
'--tw-prose-hr': theme('colors.zinc.900 / 0.05'),
|
||||
'--tw-prose-quotes': theme('colors.zinc.900'),
|
||||
'--tw-prose-quote-borders': theme('colors.zinc.200'),
|
||||
'--tw-prose-captions': theme('colors.zinc.500'),
|
||||
'--tw-prose-code': theme('colors.zinc.900'),
|
||||
'--tw-prose-code-bg': theme('colors.zinc.100'),
|
||||
'--tw-prose-code-ring': theme('colors.zinc.300'),
|
||||
'--tw-prose-th-borders': theme('colors.zinc.300'),
|
||||
'--tw-prose-td-borders': theme('colors.zinc.200'),
|
||||
|
||||
'--tw-prose-invert-body': theme('colors.zinc.400'),
|
||||
'--tw-prose-invert-headings': theme('colors.white'),
|
||||
'--tw-prose-invert-links': theme('colors.orange.400'),
|
||||
'--tw-prose-invert-links-hover': theme('colors.orange.500'),
|
||||
'--tw-prose-invert-links-underline': theme('colors.orange.500 / 0.3'),
|
||||
'--tw-prose-invert-bold': theme('colors.white'),
|
||||
'--tw-prose-invert-counters': theme('colors.zinc.400'),
|
||||
'--tw-prose-invert-bullets': theme('colors.zinc.600'),
|
||||
'--tw-prose-invert-hr': theme('colors.white / 0.05'),
|
||||
'--tw-prose-invert-quotes': theme('colors.zinc.100'),
|
||||
'--tw-prose-invert-quote-borders': theme('colors.zinc.700'),
|
||||
'--tw-prose-invert-captions': theme('colors.zinc.400'),
|
||||
'--tw-prose-invert-code': theme('colors.white'),
|
||||
'--tw-prose-invert-code-bg': theme('colors.zinc.700 / 0.15'),
|
||||
'--tw-prose-invert-code-ring': theme('colors.white / 0.1'),
|
||||
'--tw-prose-invert-th-borders': theme('colors.zinc.600'),
|
||||
'--tw-prose-invert-td-borders': theme('colors.zinc.700'),
|
||||
|
||||
// Base
|
||||
color: 'var(--tw-prose-body)',
|
||||
fontSize: theme('fontSize.sm')[0],
|
||||
lineHeight: theme('lineHeight.7'),
|
||||
|
||||
// Layout
|
||||
'> *': {
|
||||
maxWidth: theme('maxWidth.2xl'),
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
'@screen lg': {
|
||||
maxWidth: theme('maxWidth.3xl'),
|
||||
marginLeft: `calc(50% - min(50%, ${theme('maxWidth.lg')}))`,
|
||||
marginRight: `calc(50% - min(50%, ${theme('maxWidth.lg')}))`,
|
||||
},
|
||||
},
|
||||
|
||||
// Text
|
||||
p: {
|
||||
marginTop: theme('spacing.6'),
|
||||
marginBottom: theme('spacing.6'),
|
||||
},
|
||||
'[class~="lead"]': {
|
||||
fontSize: theme('fontSize.base')[0],
|
||||
...theme('fontSize.base')[1],
|
||||
},
|
||||
|
||||
// Lists
|
||||
ol: {
|
||||
listStyleType: 'decimal',
|
||||
marginTop: theme('spacing.5'),
|
||||
marginBottom: theme('spacing.5'),
|
||||
paddingLeft: '1.625rem',
|
||||
},
|
||||
'ol[type="A"]': {
|
||||
listStyleType: 'upper-alpha',
|
||||
},
|
||||
'ol[type="a"]': {
|
||||
listStyleType: 'lower-alpha',
|
||||
},
|
||||
'ol[type="A" s]': {
|
||||
listStyleType: 'upper-alpha',
|
||||
},
|
||||
'ol[type="a" s]': {
|
||||
listStyleType: 'lower-alpha',
|
||||
},
|
||||
'ol[type="I"]': {
|
||||
listStyleType: 'upper-roman',
|
||||
},
|
||||
'ol[type="i"]': {
|
||||
listStyleType: 'lower-roman',
|
||||
},
|
||||
'ol[type="I" s]': {
|
||||
listStyleType: 'upper-roman',
|
||||
},
|
||||
'ol[type="i" s]': {
|
||||
listStyleType: 'lower-roman',
|
||||
},
|
||||
'ol[type="1"]': {
|
||||
listStyleType: 'decimal',
|
||||
},
|
||||
ul: {
|
||||
listStyleType: 'disc',
|
||||
marginTop: theme('spacing.5'),
|
||||
marginBottom: theme('spacing.5'),
|
||||
paddingLeft: '1.625rem',
|
||||
},
|
||||
li: {
|
||||
marginTop: theme('spacing.2'),
|
||||
marginBottom: theme('spacing.2'),
|
||||
},
|
||||
':is(ol, ul) > li': {
|
||||
paddingLeft: theme('spacing[1.5]'),
|
||||
},
|
||||
'ol > li::marker': {
|
||||
fontWeight: '400',
|
||||
color: 'var(--tw-prose-counters)',
|
||||
},
|
||||
'ul > li::marker': {
|
||||
color: 'var(--tw-prose-bullets)',
|
||||
},
|
||||
'> ul > li p': {
|
||||
marginTop: theme('spacing.3'),
|
||||
marginBottom: theme('spacing.3'),
|
||||
},
|
||||
'> ul > li > *:first-child': {
|
||||
marginTop: theme('spacing.5'),
|
||||
},
|
||||
'> ul > li > *:last-child': {
|
||||
marginBottom: theme('spacing.5'),
|
||||
},
|
||||
'> ol > li > *:first-child': {
|
||||
marginTop: theme('spacing.5'),
|
||||
},
|
||||
'> ol > li > *:last-child': {
|
||||
marginBottom: theme('spacing.5'),
|
||||
},
|
||||
'ul ul, ul ol, ol ul, ol ol': {
|
||||
marginTop: theme('spacing.3'),
|
||||
marginBottom: theme('spacing.3'),
|
||||
},
|
||||
|
||||
// Horizontal rules
|
||||
hr: {
|
||||
borderColor: 'var(--tw-prose-hr)',
|
||||
borderTopWidth: 1,
|
||||
marginTop: theme('spacing.16'),
|
||||
marginBottom: theme('spacing.16'),
|
||||
maxWidth: 'none',
|
||||
marginLeft: `calc(-1 * ${theme('spacing.4')})`,
|
||||
marginRight: `calc(-1 * ${theme('spacing.4')})`,
|
||||
'@screen sm': {
|
||||
marginLeft: `calc(-1 * ${theme('spacing.6')})`,
|
||||
marginRight: `calc(-1 * ${theme('spacing.6')})`,
|
||||
},
|
||||
'@screen lg': {
|
||||
marginLeft: `calc(-1 * ${theme('spacing.8')})`,
|
||||
marginRight: `calc(-1 * ${theme('spacing.8')})`,
|
||||
},
|
||||
},
|
||||
|
||||
// Quotes
|
||||
blockquote: {
|
||||
fontWeight: '500',
|
||||
fontStyle: 'italic',
|
||||
color: 'var(--tw-prose-quotes)',
|
||||
borderLeftWidth: '0.25rem',
|
||||
borderLeftColor: 'var(--tw-prose-quote-borders)',
|
||||
quotes: '"\\201C""\\201D""\\2018""\\2019"',
|
||||
marginTop: theme('spacing.8'),
|
||||
marginBottom: theme('spacing.8'),
|
||||
paddingLeft: theme('spacing.5'),
|
||||
},
|
||||
'blockquote p:first-of-type::before': {
|
||||
content: 'open-quote',
|
||||
},
|
||||
'blockquote p:last-of-type::after': {
|
||||
content: 'close-quote',
|
||||
},
|
||||
|
||||
// Headings
|
||||
h1: {
|
||||
color: 'var(--tw-prose-headings)',
|
||||
fontWeight: '700',
|
||||
fontSize: theme('fontSize.2xl')[0],
|
||||
...theme('fontSize.2xl')[1],
|
||||
marginBottom: theme('spacing.2'),
|
||||
},
|
||||
h2: {
|
||||
color: 'var(--tw-prose-headings)',
|
||||
fontWeight: '600',
|
||||
fontSize: theme('fontSize.lg')[0],
|
||||
...theme('fontSize.lg')[1],
|
||||
marginTop: theme('spacing.16'),
|
||||
marginBottom: theme('spacing.2'),
|
||||
},
|
||||
h3: {
|
||||
color: 'var(--tw-prose-headings)',
|
||||
fontSize: theme('fontSize.base')[0],
|
||||
...theme('fontSize.base')[1],
|
||||
fontWeight: '600',
|
||||
marginTop: theme('spacing.10'),
|
||||
marginBottom: theme('spacing.2'),
|
||||
},
|
||||
|
||||
// Media
|
||||
'img, video, figure': {
|
||||
marginTop: theme('spacing.8'),
|
||||
marginBottom: theme('spacing.8'),
|
||||
},
|
||||
'figure > *': {
|
||||
marginTop: '0',
|
||||
marginBottom: '0',
|
||||
},
|
||||
figcaption: {
|
||||
color: 'var(--tw-prose-captions)',
|
||||
fontSize: theme('fontSize.xs')[0],
|
||||
...theme('fontSize.xs')[1],
|
||||
marginTop: theme('spacing.2'),
|
||||
},
|
||||
|
||||
// Tables
|
||||
table: {
|
||||
width: '100%',
|
||||
tableLayout: 'auto',
|
||||
textAlign: 'left',
|
||||
marginTop: theme('spacing.8'),
|
||||
marginBottom: theme('spacing.8'),
|
||||
lineHeight: theme('lineHeight.6'),
|
||||
},
|
||||
thead: {
|
||||
borderBottomWidth: '1px',
|
||||
borderBottomColor: 'var(--tw-prose-th-borders)',
|
||||
},
|
||||
'thead th': {
|
||||
color: 'var(--tw-prose-headings)',
|
||||
fontWeight: '600',
|
||||
verticalAlign: 'bottom',
|
||||
paddingRight: theme('spacing.2'),
|
||||
paddingBottom: theme('spacing.2'),
|
||||
paddingLeft: theme('spacing.2'),
|
||||
},
|
||||
'thead th:first-child': {
|
||||
paddingLeft: '0',
|
||||
},
|
||||
'thead th:last-child': {
|
||||
paddingRight: '0',
|
||||
},
|
||||
'tbody tr': {
|
||||
borderBottomWidth: '1px',
|
||||
borderBottomColor: 'var(--tw-prose-td-borders)',
|
||||
},
|
||||
'tbody tr:last-child': {
|
||||
borderBottomWidth: '0',
|
||||
},
|
||||
'tbody td': {
|
||||
verticalAlign: 'baseline',
|
||||
},
|
||||
tfoot: {
|
||||
borderTopWidth: '1px',
|
||||
borderTopColor: 'var(--tw-prose-th-borders)',
|
||||
},
|
||||
'tfoot td': {
|
||||
verticalAlign: 'top',
|
||||
},
|
||||
':is(tbody, tfoot) td': {
|
||||
paddingTop: theme('spacing.2'),
|
||||
paddingRight: theme('spacing.2'),
|
||||
paddingBottom: theme('spacing.2'),
|
||||
paddingLeft: theme('spacing.2'),
|
||||
},
|
||||
':is(tbody, tfoot) td:first-child': {
|
||||
paddingLeft: '0',
|
||||
},
|
||||
':is(tbody, tfoot) td:last-child': {
|
||||
paddingRight: '0',
|
||||
},
|
||||
|
||||
// Inline elements
|
||||
a: {
|
||||
color: 'var(--tw-prose-links)',
|
||||
textDecoration: 'underline transparent',
|
||||
fontWeight: '500',
|
||||
transitionProperty: 'color, text-decoration-color',
|
||||
transitionDuration: theme('transitionDuration.DEFAULT'),
|
||||
transitionTimingFunction: theme('transitionTimingFunction.DEFAULT'),
|
||||
'&:hover': {
|
||||
color: 'var(--tw-prose-links-hover)',
|
||||
textDecorationColor: 'var(--tw-prose-links-underline)',
|
||||
},
|
||||
},
|
||||
':is(h1, h2, h3) a': {
|
||||
fontWeight: 'inherit',
|
||||
},
|
||||
strong: {
|
||||
color: 'var(--tw-prose-bold)',
|
||||
fontWeight: '600',
|
||||
},
|
||||
':is(a, blockquote, thead th) strong': {
|
||||
color: 'inherit',
|
||||
},
|
||||
code: {
|
||||
color: 'var(--tw-prose-code)',
|
||||
borderRadius: theme('borderRadius.lg'),
|
||||
paddingTop: theme('padding.1'),
|
||||
paddingRight: theme('padding[1.5]'),
|
||||
paddingBottom: theme('padding.1'),
|
||||
paddingLeft: theme('padding[1.5]'),
|
||||
boxShadow: 'inset 0 0 0 1px var(--tw-prose-code-ring)',
|
||||
backgroundColor: 'var(--tw-prose-code-bg)',
|
||||
fontSize: theme('fontSize.2xs'),
|
||||
},
|
||||
':is(a, h1, h2, h3, blockquote, thead th) code': {
|
||||
color: 'inherit',
|
||||
},
|
||||
'h2 code': {
|
||||
fontSize: theme('fontSize.base')[0],
|
||||
fontWeight: 'inherit',
|
||||
},
|
||||
'h3 code': {
|
||||
fontSize: theme('fontSize.sm')[0],
|
||||
fontWeight: 'inherit',
|
||||
},
|
||||
|
||||
// Overrides
|
||||
':is(h1, h2, h3) + *': {
|
||||
marginTop: '0',
|
||||
},
|
||||
'> :first-child': {
|
||||
marginTop: '0 !important',
|
||||
},
|
||||
'> :last-child': {
|
||||
marginBottom: '0 !important',
|
||||
},
|
||||
},
|
||||
},
|
||||
invert: {
|
||||
css: {
|
||||
'--tw-prose-body': 'var(--tw-prose-invert-body)',
|
||||
'--tw-prose-headings': 'var(--tw-prose-invert-headings)',
|
||||
'--tw-prose-links': 'var(--tw-prose-invert-links)',
|
||||
'--tw-prose-links-hover': 'var(--tw-prose-invert-links-hover)',
|
||||
'--tw-prose-links-underline': 'var(--tw-prose-invert-links-underline)',
|
||||
'--tw-prose-bold': 'var(--tw-prose-invert-bold)',
|
||||
'--tw-prose-counters': 'var(--tw-prose-invert-counters)',
|
||||
'--tw-prose-bullets': 'var(--tw-prose-invert-bullets)',
|
||||
'--tw-prose-hr': 'var(--tw-prose-invert-hr)',
|
||||
'--tw-prose-quotes': 'var(--tw-prose-invert-quotes)',
|
||||
'--tw-prose-quote-borders': 'var(--tw-prose-invert-quote-borders)',
|
||||
'--tw-prose-captions': 'var(--tw-prose-invert-captions)',
|
||||
'--tw-prose-code': 'var(--tw-prose-invert-code)',
|
||||
'--tw-prose-code-bg': 'var(--tw-prose-invert-code-bg)',
|
||||
'--tw-prose-code-ring': 'var(--tw-prose-invert-code-ring)',
|
||||
'--tw-prose-th-borders': 'var(--tw-prose-invert-th-borders)',
|
||||
'--tw-prose-td-borders': 'var(--tw-prose-invert-td-borders)',
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user