v0.0.3: Dark Mode and User List

This commit is contained in:
Yann Stepienik 2023-03-13 00:49:27 +00:00
parent cfadca8c06
commit b6f872343d
21 changed files with 157 additions and 34 deletions

BIN
client/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

View file

@ -2,9 +2,16 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cosmos</title> <title>Cosmos</title>
<link rel="icon" type="image/x-icon" href="/Logo.png">
<style>
@media (prefers-color-scheme: dark) {
html {
background-color: #141414;
}
}
</style>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View file

@ -1,4 +1,6 @@
import * as auth from './authentication.jsx'; import * as auth from './authentication.jsx';
import * as users from './users.jsx';
export { export {
auth auth,
users
}; };

14
client/src/api/users.jsx Normal file
View file

@ -0,0 +1,14 @@
function list() {
return fetch('/cosmos/api/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
})
.then((res) => res.json())
}
export {
list,
};

View file

@ -9,7 +9,7 @@ import logo from '../../../../../Logo.png';
const AuthBackground = () => { const AuthBackground = () => {
const theme = useTheme(); const theme = useTheme();
return ( return (
<Box sx={{ position: 'fixed', float: 'left', height: 'calc(100vh - 50px)', overflow: 'hidden', filter: 'blur(25px)', zIndex: -1, top: 100, left: -500 }}> <Box sx={{ position: 'fixed', float: 'left', height: 'calc(100vh - 50px)', overflow: 'hidden', filter: 'blur(25px)', zIndex: 0, top: 100, left: -500 }}>
<img src={logo} style={{ display:'inline'}} alt="Cosmos" width="1100" /> <img src={logo} style={{ display:'inline'}} alt="Cosmos" width="1100" />
</Box> </Box>
); );

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><defs><style>.cls-1{fill:#fff;}</style></defs><g id="图层_2" data-name="图层 2"><g id="Discord_Logos" data-name="Discord Logos"><g id="Discord_Logo_-_Large_-_White" data-name="Discord Logo - Large - White"><path class="cls-1" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 985 B

View file

@ -28,7 +28,7 @@ const MainCard = forwardRef(
divider = true, divider = true,
elevation, elevation,
secondary, secondary,
shadow, // shadow,
sx = {}, sx = {},
title, title,
codeHighlight, codeHighlight,
@ -37,7 +37,7 @@ const MainCard = forwardRef(
ref ref
) => { ) => {
const theme = useTheme(); const theme = useTheme();
boxShadow = theme.palette.mode === 'dark' ? boxShadow || true : boxShadow; boxShadow = false; // theme.palette.mode === 'dark' ? boxShadow || true : boxShadow;
return ( return (
<Card <Card

View file

@ -6,7 +6,7 @@ const config = {
i18n: 'en', i18n: 'en',
miniDrawer: false, miniDrawer: false,
container: true, container: true,
mode: 'light', mode: 'dark',
presetColor: 'default', presetColor: 'default',
themeDirection: 'ltr' themeDirection: 'ltr'
}; };

View file

@ -62,8 +62,8 @@ const Notification = () => {
setOpen(false); setOpen(false);
}; };
const iconBackColorOpen = 'grey.300'; const iconBackColor = theme.palette.mode === 'dark' ? 'grey.700' : 'grey.100';
const iconBackColor = 'grey.100'; const iconBackColorOpen = theme.palette.mode === 'dark' ? 'grey.800' : 'grey.200';
return ( return (
<Box sx={{ flexShrink: 0, ml: 0.75 }}> <Box sx={{ flexShrink: 0, ml: 0.75 }}>

View file

@ -17,8 +17,8 @@ const Header = ({ open, handleDrawerToggle }) => {
const theme = useTheme(); const theme = useTheme();
const matchDownMD = useMediaQuery(theme.breakpoints.down('lg')); const matchDownMD = useMediaQuery(theme.breakpoints.down('lg'));
const iconBackColor = 'grey.100'; const iconBackColor = theme.palette.mode === 'dark' ? 'grey.700' : 'grey.100';
const iconBackColorOpen = 'grey.200'; const iconBackColorOpen = theme.palette.mode === 'dark' ? 'grey.800' : 'grey.200';
// common header // common header
const mainHeader = ( const mainHeader = (
@ -28,7 +28,7 @@ const Header = ({ open, handleDrawerToggle }) => {
aria-label="open drawer" aria-label="open drawer"
onClick={handleDrawerToggle} onClick={handleDrawerToggle}
edge="start" edge="start"
color="secondary" color="red"
sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor, ml: { xs: 0, lg: -2 } }} sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor, ml: { xs: 0, lg: -2 } }}
> >
{!open ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} {!open ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}

View file

@ -50,7 +50,7 @@ const MainLayout = () => {
<Drawer open={open} handleDrawerToggle={handleDrawerToggle} /> <Drawer open={open} handleDrawerToggle={handleDrawerToggle} />
<Box component="main" sx={{ width: '100%', flexGrow: 1, p: { xs: 2, sm: 3 } }}> <Box component="main" sx={{ width: '100%', flexGrow: 1, p: { xs: 2, sm: 3 } }}>
<Toolbar /> <Toolbar />
<Breadcrumbs navigation={navigation} title titleBottom card={false} divider={false} /> <Breadcrumbs navigation={navigation} title divider={false} />
<Outlet /> <Outlet />
</Box> </Box>
</Box> </Box>

View file

@ -1,12 +1,16 @@
// assets // assets
import { GithubOutlined, QuestionOutlined } from '@ant-design/icons'; import { GithubOutlined, QuestionOutlined } from '@ant-design/icons';
import DiscordOutlined from '../assets/images/icons/discord.svg' import DiscordOutlined from '../assets/images/icons/discord.svg'
import DiscordOutlinedWhite from '../assets/images/icons/discord_white.svg'
import { useTheme } from '@mui/material/styles';
// ==============================|| MENU ITEMS - SAMPLE PAGE & DOCUMENTATION ||============================== // // ==============================|| MENU ITEMS - SAMPLE PAGE & DOCUMENTATION ||============================== //
const DiscordOutlinedIcon = (props) => { const DiscordOutlinedIcon = (props) => {
const theme = useTheme();
return ( return (
<img src={DiscordOutlined} width="16px" alt="Discord" {...props} /> <img src={
theme.palette.mode === 'dark' ? DiscordOutlinedWhite : DiscordOutlined} width="16px" alt="Discord" {...props} />
); );
}; };

View file

@ -8,16 +8,16 @@ import MainCard from '../../components/MainCard';
// ==============================|| AUTHENTICATION - CARD WRAPPER ||============================== // // ==============================|| AUTHENTICATION - CARD WRAPPER ||============================== //
const AuthCard = ({ children, ...other }) => ( const AuthCard = ({ children, ...other }) => (<MainCard
<MainCard
sx={{ sx={{
maxWidth: { xs: 400, lg: 475 }, maxWidth: { xs: 400, lg: 475 },
margin: { xs: 2.5, md: 3 }, margin: { xs: 2.5, md: 3 },
'& > *': { '& > *': {
flexGrow: 1, flexGrow: 1,
flexBasis: '50%' flexBasis: '50%'
} },
}} }}
style={{ zIndex: 1, position: 'relative' }}
content={false} content={false}
{...other} {...other}
border={false} border={false}

View file

@ -0,0 +1,69 @@
// material-ui
import { Button, Typography } from '@mui/material';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import * as API from '../../../api';
// project import
import MainCard from '../../../components/MainCard';
import isLoggedIn from '../../../isLoggedIn';
import { useEffect, useState } from 'react';
// ==============================|| SAMPLE PAGE ||============================== //
const UserManagement = () => {
const [rows, setRows] = useState([
{ id: 0, nickname: '12313', reset:'123132' },
{ id: 1, nickname: '354345', reset:'345345' },
]);
isLoggedIn();
useEffect(() => {
API.users.list()
.then(data => {
console.log(data);
setRows(data.data);
})
}, [])
return <MainCard title="Users">
<TableContainer component={Paper}>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Nickname</TableCell>
<TableCell>Role</TableCell>
<TableCell>Password</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow
key={row.nickname}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.nickname}
</TableCell>
<TableCell>User</TableCell>
<TableCell><Button variant="contained" color="primary">Send Password Link</Button></TableCell>
<TableCell><Button variant="contained" color="error">Delete</Button></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</MainCard>
};
export default UserManagement;

View file

@ -3,6 +3,7 @@ import { lazy } from 'react';
// project import // project import
import Loadable from '../components/Loadable'; import Loadable from '../components/Loadable';
import MainLayout from '../layout/MainLayout'; import MainLayout from '../layout/MainLayout';
import UserManagement from '../pages/config/users/usermanagement';
// render - dashboard // render - dashboard
const DashboardDefault = Loadable(lazy(() => import('../pages/dashboard'))); const DashboardDefault = Loadable(lazy(() => import('../pages/dashboard')));
@ -40,8 +41,8 @@ const MainRoutes = {
] ]
}, },
{ {
path: 'sample-page', path: 'config/users',
element: <SamplePage /> element: <UserManagement />
}, },
{ {
path: 'shadow', path: 'shadow',

View file

@ -14,7 +14,9 @@ import componentsOverride from './overrides';
// ==============================|| DEFAULT THEME - MAIN ||============================== // // ==============================|| DEFAULT THEME - MAIN ||============================== //
export default function ThemeCustomization({ children }) { export default function ThemeCustomization({ children }) {
const theme = Palette('light', 'default'); const theme = Palette(
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ?
'dark' : 'light');
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const themeTypography = Typography(`'Public Sans', sans-serif`); const themeTypography = Typography(`'Public Sans', sans-serif`);

View file

@ -32,28 +32,48 @@ const Palette = (mode) => {
const paletteColor = ThemeOption(colors); const paletteColor = ThemeOption(colors);
return createTheme({ return createTheme(mode === 'dark' ? {
palette: { palette: {
mode, mode,
common: { common: {
black: '#000', black: '#fff',
white: '#fff' white: '#000'
}, },
...paletteColor, ...paletteColor,
text: { text: {
primary: paletteColor.grey[700], primary: paletteColor.grey[0],
secondary: paletteColor.grey[500], secondary: paletteColor.grey[200],
disabled: paletteColor.grey[400] disabled: paletteColor.grey[300]
}, },
action: { action: {
disabled: paletteColor.grey[300] disabled: paletteColor.grey[300]
}, },
divider: paletteColor.grey[200], divider: paletteColor.grey[600],
background: { background: {
paper: paletteColor.grey[0], paper: paletteColor.grey[700],
default: paletteColor.grey.A50 default: paletteColor.grey[800]
} }
} }
} : {
mode,
common: {
black: '#000',
white: '#fff'
},
...paletteColor,
text: {
primary: paletteColor.grey[700],
secondary: paletteColor.grey[500],
disabled: paletteColor.grey[400]
},
action: {
disabled: paletteColor.grey[300]
},
divider: paletteColor.grey[200],
background: {
paper: paletteColor.grey[0],
default: paletteColor.grey.A50
}
}); });
}; };

View file

@ -33,6 +33,7 @@
"npm://@esbuild/linux-x64": "0.16.17", "npm://@esbuild/linux-x64": "0.16.17",
"npm://@mui/lab": "^5.0.0-alpha.100", "npm://@mui/lab": "^5.0.0-alpha.100",
"npm://@mui/material": "^5.10.6", "npm://@mui/material": "^5.10.6",
"npm://@mui/x-data-grid": "6.0.1",
"npm://@reduxjs/toolkit": "^1.8.5", "npm://@reduxjs/toolkit": "^1.8.5",
"npm://@testing-library/jest-dom": "^5.16.5", "npm://@testing-library/jest-dom": "^5.16.5",
"npm://@testing-library/react": "^13.4.0", "npm://@testing-library/react": "^13.4.0",
@ -85,6 +86,6 @@
}, },
"description": "Cosmos Server", "description": "Cosmos Server",
"name": "cosmos-server", "name": "cosmos-server",
"version": "0.0.2", "version": "0.0.3",
"wrapInstallFolder": "src" "wrapInstallFolder": "src"
} }

View file

@ -5,14 +5,16 @@
# Cosmos Server # Cosmos Server
``` ```
Disclaimer: Cosmos is still in early Alpha stage, please be careful when you use it. It is not (yet, at least ;p) a replacement for proper control and mindfulness of your own security. **Disclaimer**: Cosmos is still in early Alpha stage, please be careful when you use it. It is not (yet, at least ;p) a replacement for proper control and mindfulness of your own security.
``` ```
Looking for a **secure** and **robust** way to run your **self-hosted applications**? With **Cosmos**, you can take control of your data and privacy without sacrificing security and stability. Looking for the right way to run your **self-hosted applications**? With **Cosmos**, you can take control of your data and privacy without sacrificing security and stability. **Safe** and **secure** platform by design, and most importantly, **easy to setup** without ambiguity. It is a combination of a **reverse proxy**, an **authentication provider** and an **application manager**.
![screenshot1](./screenshot1.png)
Whether you have a **server**, a **NAS**, or a **Raspberry Pi** with applications such as **Plex**, **HomeAssistant** or even a blog, Cosmos is the perfect solution to secure it all. Simply install Cosmos on your server and connect to your applications through it to enjoy built-in security and robustness for all your services, right out of the box. Whether you have a **server**, a **NAS**, or a **Raspberry Pi** with applications such as **Plex**, **HomeAssistant** or even a blog, Cosmos is the perfect solution to secure it all. Simply install Cosmos on your server and connect to your applications through it to enjoy built-in security and robustness for all your services, right out of the box.
* **Authentication** Connect to all your application with the same account, including strong security and multi-factor authentication * **Authentication** Connect to all your application with the same account, including strong security and **multi-factor authentication**
* **Automatic HTTPS** certificates provision * **Automatic HTTPS** certificates provision
* **Anti-bot** protections such as Captcha and IP rate limiting * **Anti-bot** protections such as Captcha and IP rate limiting
* **Anti-DDOS** protections such as variable timeouts/throttling, IP rate limiting and IP blacklisting * **Anti-DDOS** protections such as variable timeouts/throttling, IP rate limiting and IP blacklisting

BIN
screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

View file

@ -172,8 +172,8 @@ func StartServer() {
// srapi.Use(utils.AcceptHeader("*/*")) // srapi.Use(utils.AcceptHeader("*/*"))
srapi.Use(utils.CORSHeader(utils.GetMainConfig().HTTPConfig.Hostname)) srapi.Use(utils.CORSHeader(utils.GetMainConfig().HTTPConfig.Hostname))
srapi.Use(utils.MiddlewareTimeout(5 * time.Second)) srapi.Use(utils.MiddlewareTimeout(20 * time.Second))
srapi.Use(httprate.Limit(20, 1*time.Minute, srapi.Use(httprate.Limit(60, 1*time.Minute,
httprate.WithKeyFuncs(httprate.KeyByIP), httprate.WithKeyFuncs(httprate.KeyByIP),
httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) { httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
utils.Error("Too many requests. Throttling", nil) utils.Error("Too many requests. Throttling", nil)