employees

master
alex 2024-01-11 01:14:55 +01:00
parent c3268bd71b
commit 23af00a619
7 changed files with 287 additions and 67 deletions

View File

@ -1,15 +0,0 @@
# Jannex Admin Dashboard
With this system you can easily run your Python scripts and visualize them in the user interface to manage them more easily. You can see who started the task and who worked on it later in the task view. When a group task is started, the backend server starts the Python script and sends the result to the web UI. The web UI can then be used to start the other tasks or to repeat or undo the current task.
## Features
- Dynamic Group Task System
- Equipment Documentation System
- Advanced log system with log viewer and log filter
- Robot control system
- Role System
- Scanner integration to use mobile or pc devices as qrcode scanners
- User login system
- User deactivation system
- User API key system

View File

@ -8,7 +8,12 @@
"confirm": "Bestätigen", "confirm": "Bestätigen",
"create": "Erstellen" "create": "Erstellen"
}, },
"contactAdmin": "Bitte kontaktieren Sie einen Administrator" "action": "Aktion",
"contactAdmin": "Bitte kontaktieren Sie einen Administrator",
"username": "Anzeigename",
"accountName": "Benutzername",
"password": "Passwort",
"noDataFound": "Keine Daten gefunden"
}, },
"pageNotFound": { "pageNotFound": {
"title": "Seite nicht gefunden", "title": "Seite nicht gefunden",
@ -28,5 +33,12 @@
"calendar": "Kalender", "calendar": "Kalender",
"support": "Unterstützung", "support": "Unterstützung",
"feedback": "Feedback" "feedback": "Feedback"
},
"employees": {
"pageTitle": "Mitarbeiter",
"addEmployee": "Mitarbeiter anlegen",
"modalAddEmployee": {
"checkboxPasswordChange": "Mitarbeiter auffordern, das Passwort zu ändern (empfohlen)"
}
} }
} }

View File

@ -8,7 +8,12 @@
"confirm": "Confirm", "confirm": "Confirm",
"create": "Create" "create": "Create"
}, },
"contactAdmin": "Please contact an administrator" "action": "Action",
"contactAdmin": "Please contact an administrator",
"username": "Username",
"accountName": "Account name",
"password": "Password",
"noDataFound": "No data found"
}, },
"pageNotFound": { "pageNotFound": {
"title": "Page Not Found", "title": "Page Not Found",
@ -28,5 +33,12 @@
"calendar": "Calendar", "calendar": "Calendar",
"support": "Support", "support": "Support",
"feedback": "Feedback" "feedback": "Feedback"
},
"employees": {
"pageTitle": "Employees",
"addEmployee": "Add employee",
"modalAddEmployee": {
"checkboxPasswordChange": "Require employee to change password (recommended)"
}
} }
} }

View File

@ -0,0 +1,27 @@
import { Table } from "antd";
import { useTranslation } from "react-i18next";
export default function MyTable({ props }) {
const { t } = useTranslation();
return (
<Table
{...props}
scroll={{ x: "max-content" }}
locale={{
emptyText: (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: 110,
}}
>
{t("common.noDataFound")}
</div>
),
}}
/>
);
}

View File

@ -1,50 +1,80 @@
import { PlusOutlined } from "@ant-design/icons"; import { LockOutlined, PlusOutlined, UserOutlined } from "@ant-design/icons";
import { Button, Table } from "antd"; import { Button, Checkbox, Col, Form, Grid, Input, Row, Table } from "antd";
import MyModal, {
MyModalCloseCreateButtonFooter,
} from "../../Components/MyModal";
import { useEffect, useState } from "react";
import { Constants, myFetch, EncodeStringToBase64 } from "../../utils";
import { useTranslation } from "react-i18next";
import MyTable from "../../Components/MyTable";
const { useBreakpoint } = Grid;
export default function Employees() { export default function Employees() {
const { t } = useTranslation();
const screenBreakpoint = useBreakpoint();
const [employees, setEmployees] = useState([]);
const [isAddEmployeeModalOpen, setIsAddEmployeeModalOpen] = useState(false);
const [username, setUsername] = useState("");
const [accountName, setAccountName] = useState("");
const [password, setPassword] = useState("");
const [isRequesting, setIsRequesting] = useState(false);
const getTableColumns = () => { const getTableColumns = () => {
return [ return [
{ {
title: "Name", title: t("common.accountName"),
dataIndex: "name", dataIndex: "account_name",
key: "name", key: "account_name",
}, },
{ {
title: "Age", title: t("common.username"),
dataIndex: "age", dataIndex: "username",
key: "age", key: "username",
}, },
{ {
title: "Action", title: t("common.action"),
dataIndex: "action", dataIndex: "action",
key: "actions", key: "actions",
render: () => <a>Delete</a>, render: () => <a>{t("common.button.delete")}</a>,
}, },
]; ];
}; };
const getTableItems = () => { const getTableItems = () => {
return [ return employees.map((employee) => {
{ return {
key: "1", key: employee.account_name,
name: "John Brown", account_name: employee.account_name,
age: 32, username: employee.username,
address: "New York No. 1 Lake Park",
},
{
key: "2",
name: "Jim Green",
age: 42,
address: "London No. 1 Lake Park",
},
{
key: "3",
name: "Joe Black",
age: 32,
address: "Sidney No. 1 Lake Park",
},
];
}; };
});
};
const handleAddEmployeeModalClose = () => {
setIsAddEmployeeModalOpen(false);
setUsername("");
setAccountName("");
setPassword("");
};
const fetchEmployees = () => {
myFetch("/users", "GET")
.then((data) => {
setIsRequesting(false);
setEmployees(data.employees);
})
.catch((errStatus) => {
console.log(errStatus);
});
};
useEffect(() => {
setIsRequesting(true);
fetchEmployees();
}, []);
return ( return (
<> <>
@ -52,16 +82,136 @@ export default function Employees() {
style={{ style={{
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", flexDirection: screenBreakpoint.xs ? "column" : "row",
}} }}
> >
<h1>Employees</h1> <h1>
<Button type="primary" icon={<PlusOutlined />}> {t("employees.pageTitle")}
Add employee {employees.length > 0 && ` (${employees.length})`}
</h1>
<Button
type="primary"
block={screenBreakpoint.xs}
icon={<PlusOutlined />}
onClick={() => setIsAddEmployeeModalOpen(true)}
>
{t("employees.addEmployee")}
</Button> </Button>
</div> </div>
<Table columns={getTableColumns()} dataSource={getTableItems()} /> <MyTable
props={{
loading: isRequesting,
columns: getTableColumns(),
dataSource: getTableItems(),
}}
/>
<MyModal
title={t("employees.addEmployee")}
isOpen={isAddEmployeeModalOpen}
onCancel={handleAddEmployeeModalClose}
footer={
<MyModalCloseCreateButtonFooter
onCancel={handleAddEmployeeModalClose}
isCreateButtonLoading={isRequesting}
onCreate={() => {
setIsRequesting(true);
myFetch("/users", "POST", {
username: username,
accountName: accountName,
password: EncodeStringToBase64(password),
})
.then(() => {
setIsRequesting(false);
handleAddEmployeeModalClose();
fetchEmployees();
})
.catch((errStatus) => {
console.log(errStatus);
setIsRequesting(false);
});
}}
/>
}
>
<Form>
<Form.Item
name="username"
required
rules={[
{ required: true, message: "Please enter your username!" },
{
min: Constants.GLOBALS.MIN_USERNAME_LENGTH,
message: `Please enter a username length of at least ${Constants.GLOBALS.MIN_USERNAME_LENGTH}!`,
},
]}
>
<Input
prefix={<UserOutlined />}
placeholder={t("common.username")}
value={username}
onChange={(e) => setUsername(e.target.value)}
minLength={Constants.GLOBALS.MIN_USERNAME_LENGTH}
maxLength={Constants.GLOBALS.MAX_USERNAME_LENGTH}
/>
</Form.Item>
<Form.Item
name="accountName"
required
rules={[
{ required: true, message: "Please enter an account name!" },
{
min: Constants.GLOBALS.MIN_USERNAME_LENGTH,
message: `Please enter an account name length of at least ${Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH}!`,
},
]}
>
<Input
prefix={<UserOutlined />}
placeholder={t("common.accountName")}
value={accountName}
onChange={(e) => setAccountName(e.target.value)}
minLength={Constants.GLOBALS.MIN_USERNAME_LENGTH}
maxLength={Constants.GLOBALS.MAX_USERNAME_LENGTH}
/>
</Form.Item>
<Form.Item
name="password"
required
rules={[
{
required: true,
message: "Please enter your Password!",
},
{
min: Constants.GLOBALS.MIN_PASSWORD_LENGTH,
message: `Please enter a password length of at least ${Constants.GLOBALS.MIN_PASSWORD_LENGTH}!`,
},
]}
>
<Input.Password
prefix={<LockOutlined />}
placeholder={t("common.password")}
value={password}
onChange={(e) => setPassword(e.target.value)}
minLength={Constants.GLOBALS.MIN_PASSWORD_LENGTH}
maxLength={Constants.GLOBALS.MAX_PASSWORD_LENGTH}
/>
</Form.Item>
<Form.Item>
<Checkbox defaultChecked>
{t("employees.modalAddEmployee.checkboxPasswordChange")}
</Checkbox>
</Form.Item>
</Form>
</MyModal>
</> </>
); );
} }

View File

@ -11,6 +11,7 @@ import { useState } from "react";
export default function Login() { export default function Login() {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [accountName, setAccountName] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [api, contextHolder] = notification.useNotification(); const [api, contextHolder] = notification.useNotification();
@ -27,14 +28,14 @@ export default function Login() {
api["error"]({ api["error"]({
message: "Login failed", message: "Login failed",
description: "Please check your username and password!", description: "Please check your accountName and password!",
}); });
}; };
const handleSubmit = () => { const handleSubmit = () => {
if ( if (
username.length > Constants.GLOBALS.MAX_USERNAME_LENGTH || accountName.length > Constants.GLOBALS.MAX_ACCOUNT_NAME_LENGTH ||
username.length < Constants.GLOBALS.MIN_USERNAME_LENGTH || accountName.length < Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH ||
password.length > Constants.GLOBALS.MAX_PASSWORD_LENGTH || password.length > Constants.GLOBALS.MAX_PASSWORD_LENGTH ||
password.length < Constants.GLOBALS.MIN_PASSWORD_LENGTH password.length < Constants.GLOBALS.MIN_PASSWORD_LENGTH
) { ) {
@ -42,13 +43,19 @@ export default function Login() {
return; return;
} }
let body = {
accountName: accountName.toLocaleLowerCase(),
password: EncodeStringToBase64(password),
};
if (selectedMethod === "2") {
body.username = username;
}
myFetch( myFetch(
`/user/auth/${selectedMethod === "1" ? "login" : "signup"}`, `/user/auth/${selectedMethod === "1" ? "login" : "signup"}`,
"POST", "POST",
{ body,
username: username,
password: EncodeStringToBase64(password),
},
{}, {},
myFetchContentType.JSON, myFetchContentType.JSON,
"", "",
@ -102,6 +109,7 @@ export default function Login() {
/> />
<Form> <Form>
{selectedMethod === "2" && (
<Form.Item <Form.Item
name="username" name="username"
required required
@ -115,12 +123,36 @@ export default function Login() {
> >
<Input <Input
prefix={<UserOutlined />} prefix={<UserOutlined />}
placeholder="Benutzername" placeholder="Anzeigename"
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
minLength={Constants.GLOBALS.MIN_USERNAME_LENGTH} minLength={Constants.GLOBALS.MIN_USERNAME_LENGTH}
maxLength={Constants.GLOBALS.MAX_USERNAME_LENGTH} maxLength={Constants.GLOBALS.MAX_USERNAME_LENGTH}
/> />
</Form.Item> </Form.Item>
)}
<Form.Item
hasFeedback
name="accountName"
validateStatus="validating"
required
rules={[
{ required: true, message: "Please enter your accountName!" },
{
min: Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH,
message: `Please enter a accountName length of at least ${Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH}!`,
},
]}
>
<Input
prefix={<UserOutlined />}
placeholder="Benutzername"
onChange={(e) => setAccountName(e.target.value)}
minLength={Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH}
maxLength={Constants.GLOBALS.MAX_ACCOUNT_NAME_LENGTH}
/>
</Form.Item>
<Form.Item <Form.Item
name="password" name="password"
required required

View File

@ -55,7 +55,9 @@ export const Constants = {
GLOBALS: { GLOBALS: {
MIN_USERNAME_LENGTH: 2, MIN_USERNAME_LENGTH: 2,
MAX_USERNAME_LENGTH: 20, MAX_USERNAME_LENGTH: 20,
MIN_PASSWORD_LENGTH: 6, MIN_ACCOUNT_NAME_LENGTH: 2,
MAX_ACCOUNT_NAME_LENGTH: 20,
MIN_PASSWORD_LENGTH: 8,
MAX_PASSWORD_LENGTH: 64, MAX_PASSWORD_LENGTH: 64,
}, },
MAX_AVATAR_SIZE: 5 * 1024 * 1024, MAX_AVATAR_SIZE: 5 * 1024 * 1024,