employees
parent
c3268bd71b
commit
23af00a619
15
README.md
15
README.md
|
@ -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
|
|
|
@ -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)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue