mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-11 18:32:24 +02:00
some ui refactorings,
use_user_db option for storing api user inside db file
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
--attention-color: red;
|
||||
--warn-color: orange;
|
||||
--ok-color: #21e821;
|
||||
|
||||
--card-background-color: #202733; //#282828;
|
||||
--card-color: #ffffff;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user