some ui refactorings,

use_user_db option for storing api user inside db file
This commit is contained in:
euzu
2025-02-26 13:46:23 +01:00
parent 4a5f1b488d
commit cbef4e3d4b
16 changed files with 493 additions and 123 deletions
@@ -1,11 +1,54 @@
@use '../../scss/common';
@import "react-datepicker/dist/react-datepicker.css";
.date-picker-container {
display: flex;
flex-flow: row nowrap;
align-items: center;
gap: 4px;
&__toolbar {
display: flex;
flex-flow: row nowrap;
align-items: center;
gap: 4px;
button {
@include common.iconButton();
}
svg {
fill: var(--icon-button-color);
}
}
}
.react-datepicker-wrapper {
box-sizing: border-box;
overflow: hidden;
}
.react-datepicker__header {
h2 {
display: none;
}
}
.react-datepicker__navigation--years-upcoming {
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 12px solid var(--border-color);
transform: translateY(4px);
}
.react-datepicker__navigation--years-previous {
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 12px solid var(--border-color);
transform: translateY(-4px);
}
.react-datepicker__input-container {
display: flex;
flex-flow: row-reverse nowrap;
@@ -15,6 +58,7 @@
border-radius: var(--border-radius);
user-select: none;
cursor: pointer;
box-sizing: border-box;
input {
user-select: none;
@@ -2,6 +2,7 @@ import React, {useCallback, useEffect} from "react";
import "./date-picker.scss";
import ReactDatePicker from "react-datepicker";
import {getIconByName} from "../../icons/icons";
import useTranslator from "../../hook/use-translator";
interface DatePickerProps {
@@ -12,6 +13,7 @@ interface DatePickerProps {
export default function DatePicker(props: DatePickerProps) {
const {name, value, onChange} = props;
const translate= useTranslator();
const [selected, setSelected] = React.useState<any>(value);
const datePickerRef = React.useRef<ReactDatePicker>(null);
@@ -31,19 +33,36 @@ export default function DatePicker(props: DatePickerProps) {
}
}, []);
const handleInfiniteDate = useCallback(() => {
handleDateChange(new Date(0));
}, [handleDateChange]);
const handleNowDate = useCallback(() => {
const date = new Date();
date.setFullYear(date.getFullYear() + 1);
handleDateChange(date);
}, [handleDateChange]);
return (
<div className="date-picker-container" onClick={handleClick}>
<ReactDatePicker
ref={datePickerRef}
showIcon={true}
icon={getIconByName('Calendar')}
dateFormat={'YYYY-MM-dd'}
// readOnly={true}
showMonthDropdown={true}
showYearDropdown={true}
onSelect={handleDateChange}
selected={selected}>
</ReactDatePicker>
<div className="date-picker-container">
<div className="date-picker-container-wrapper" onClick={handleClick}>
<ReactDatePicker
ref={datePickerRef}
showIcon={true}
icon={getIconByName('Calendar')}
dateFormat={'YYYY-MM-dd'}
// readOnly={true}
showMonthDropdown={true}
showYearDropdown={true}
minDate={new Date(0)}
onSelect={handleDateChange}
selected={selected}>
</ReactDatePicker>
</div>
<div className={'date-picker-container__toolbar'}>
<button title={translate('LABEL.INFINITE')} onClick={handleInfiniteDate}>{getIconByName('Unlimited')}</button>
<button title={translate('LABEL.ONE_YEAR')} onClick={handleNowDate}>{getIconByName('Today')}</button>
</div>
</div>
);
}
@@ -1,6 +1,26 @@
@use '../../scss/common';
@use '../../scss/preferences';
.proxy-type {
&-reverse {
}
&-redirect {
color: #fffa00;
}
}
.status {
&-active, .status-trial {
color: var(--ok-color);
}
&-banned {
color: var(--attention-color);
}
&-expired, &-disabled, &-pending {
color: var(--warn-color);
}
}
.tag-select {
min-width: 160px;
}
+58 -14
View File
@@ -12,15 +12,43 @@ import PlaylistFilter from "../playlist-filter/playlist-filter";
import UserEditor from "../user-editor/user-editor";
import TextGenerator from "../../utils/text-generator";
const renderExpDate = (value: any, hidden?: boolean) => {
if (!value) {
return getIconByName('Unlimited');
}
return DateUtils.formatDate(value)
};
const renderMaxCon = (value: any, hidden?: boolean) => {
if (!value) {
return getIconByName('Unlimited');
}
return value;
};
const renderStatus = (value: any, hidden?: boolean) => {
if (value) {
return <span className={'status-' + value.toLowerCase()}>{value}</span>
}
return value;
};
const renderProxyType = (value: any, hidden?: boolean) => {
if (value) {
return <span className={'proxy-type-' + value.toLowerCase()}>{value}</span>
}
return value;
};
const COLUMNS = [
{field: 'username', label: 'LABEL.USERNAME'},
{field: 'password', label: 'LABEL.PASSWORD', hidden: true},
{field: 'token', label: 'LABEL.TOKEN', hidden: true},
{field: 'server', label: 'LABEL.SERVER'},
{field: 'proxy', label: 'LABEL.PROXY'},
{field: 'max_connections', label: 'LABEL.MAX_CON'},
{field: 'status', label: 'LABEL.STATUS'},
{field: 'exp_date', label: 'LABEL.EXP_DATE', render: (value: any, hidden?: boolean) => DateUtils.formatDate(value)},
{field: 'proxy', label: 'LABEL.PROXY', render: renderProxyType},
{field: 'max_connections', label: 'LABEL.MAX_CON', render: renderMaxCon},
{field: 'status', label: 'LABEL.STATUS', render: renderStatus},
{field: 'exp_date', label: 'LABEL.EXP_DATE', render: renderExpDate},
]
COLUMNS.forEach(col => {
@@ -33,7 +61,10 @@ const prepareCredentials = (targetUser: TargetUser[]) => {
targetUser.forEach((user) => {
user.credentials.forEach((credential) => {
if (credential.exp_date) {
credential.exp_date = new Date(credential.exp_date * 1000) as any;
credential.exp_date = DateUtils.unixSecondsToDate(credential.exp_date);
}
if (credential.created_at) {
credential.created_at = DateUtils.unixSecondsToDate(credential.created_at);
}
})
});
@@ -41,13 +72,12 @@ const prepareCredentials = (targetUser: TargetUser[]) => {
const prepareTargetUserForSave = (targetUser: TargetUser[]): TargetUser[] => {
return targetUser.map((user) => {
let newUser = {...user, credentials: user.credentials.map(c => ({...c}))};
newUser.credentials.forEach((credential) => {
if (credential.exp_date) {
credential.exp_date = Math.floor((credential.exp_date as any).getTime() / 1000);
}
let storeUser = {...user, credentials: user.credentials.map(c => ({...c}))};
storeUser.credentials.forEach((credential) => {
credential.exp_date = DateUtils.toUnixSeconds(credential.exp_date);
credential.created_at = DateUtils.toUnixSeconds(credential.created_at);
});
return newUser;
return storeUser;
});
}
@@ -93,15 +123,17 @@ const createNewUser = (targets: TargetUser[]): Credentials => {
break;
}
}
const created_at = Math.floor(Date.now() / 1000);
const created_at = Date.now();
const exp_date = new Date();
exp_date.setFullYear(exp_date.getFullYear()+1);
return {
username,
password: TextGenerator.generatePassword(),
token: TextGenerator.generatePassword(),
proxy: 'reverse',
created_at,
exp_date: undefined,
max_connections: undefined,
exp_date: exp_date.getTime(),
max_connections: 1,
status: "Active",
// @ts-ignore
_ref: undefined, // an indicator for new user
@@ -203,6 +235,7 @@ export default function UserView(props: UserViewProps) {
const handleSave = useCallback(() => {
const usernames: any = {};
const tokens: any = {};
for (const target of targets) {
for (const user of target.credentials) {
const err = checkuser(user);
@@ -210,7 +243,18 @@ export default function UserView(props: UserViewProps) {
enqueueSnackbar(translate(err), {variant: 'error'});
return;
}
if (usernames[user.username]) {
enqueueSnackbar(translate("MESSAGES.USER.DUPLICATE_USERNAME") + user.username, {variant: 'error'});
return;
}
usernames[user.username] = true;
if (user.token) {
if (tokens[user.token]) {
enqueueSnackbar(translate("MESSAGES.USER.DUPLICATE_TOKEN") + user.token, {variant: 'error'});
return;
}
tokens[user.token] = true;
}
}
}
const targetUser = targets.map(t => {
+3 -1
View File
@@ -51,7 +51,9 @@ const DEFAULT_ICONS = {
DeselectAll: 'M 2,5 V 7.1538458 H 13.612371 V 5 Z M 2,9.3076922 V 11.461539 H 13.612371 V 9.3076922 Z m 14.082473,1.7899618 -1.265978,1.392431 2.327832,2.559791 -2.327832,2.557693 L 16.082473,19 18.408247,16.442307 20.734022,19 22,17.607569 19.674225,15.049876 22,12.490085 20.734022,11.097654 18.408247,13.655347 Z M 2,13.615385 v 2.153847 h 7.3896896 v -2.153847 z',
Visibility: 'M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5M12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5m0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3',
Edit: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.996.996 0 0 0-1.41 0l-1.83 1.83 3.75 3.75z',
Calendar: 'M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2m0 16H5V10h14zM9 14H7v-2h2zm4 0h-2v-2h2zm4 0h-2v-2h2zm-8 4H7v-2h2zm4 0h-2v-2h2zm4 0h-2v-2h2z'
Calendar: 'M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2m0 16H5V10h14zM9 14H7v-2h2zm4 0h-2v-2h2zm4 0h-2v-2h2zm-8 4H7v-2h2zm4 0h-2v-2h2zm4 0h-2v-2h2z',
Unlimited: 'M18.6 6.62c-1.44 0-2.8.56-3.77 1.53L12 10.66 10.48 12h.01L7.8 14.39c-.64.64-1.49.99-2.4.99-1.87 0-3.39-1.51-3.39-3.38S3.53 8.62 5.4 8.62c.91 0 1.76.35 2.44 1.03l1.13 1 1.51-1.34L9.22 8.2C8.2 7.18 6.84 6.62 5.4 6.62 2.42 6.62 0 9.04 0 12s2.42 5.38 5.4 5.38c1.44 0 2.8-.56 3.77-1.53l2.83-2.5.01.01L13.52 12h-.01l2.69-2.39c.64-.64 1.49-.99 2.4-.99 1.87 0 3.39 1.51 3.39 3.38s-1.52 3.38-3.39 3.38c-.9 0-1.76-.35-2.44-1.03l-1.14-1.01-1.51 1.34 1.27 1.12c1.02 1.01 2.37 1.57 3.82 1.57 2.98 0 5.4-2.41 5.4-5.38s-2.42-5.37-5.4-5.37',
Today: 'M16.53 11.06 15.47 10l-4.88 4.88-2.12-2.12-1.06 1.06L10.59 17zM19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m0 16H5V8h14z'
}
+1
View File
@@ -34,6 +34,7 @@
--attention-color: red;
--warn-color: orange;
--ok-color: #21e821;
--card-background-color: #202733; //#282828;
--card-color: #ffffff;
+35 -4
View File
@@ -2,11 +2,18 @@ function formatDate(date: any): string {
if (typeof date === 'string') {
return date;
}
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
if (typeof date === 'number') {
date = new Date(date);
return `${year}-${month}-${day}`;
}
if (typeof date?.getTime === 'function') {
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
return `${year}-${month}-${day}`;
}
return '';
}
const DateUtils = {
@@ -15,6 +22,30 @@ const DateUtils = {
return formatDate(date);
}
return '';
},
toUnixSeconds: (date: any) => {
if (date && typeof date.getTime == 'function') {
if (date.getTime() > 0) {
return Math.floor(date.getTime() / 1000);
}
return 0;
}
if (date && typeof date == 'number') {
if ( date > 0) {
return Math.floor(date / 1000);
}
return 0;
}
return date;
},
unixSecondsToDate: (date: any) => {
if (date && typeof date == 'number') {
return new Date(date * 1000);
}
if (date && typeof date.getTime == 'function') {
return date;
}
return date;
}
}