mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d87aeb5f9 | ||
|
|
9774989eeb |
@@ -1,3 +0,0 @@
|
||||
/ui/public
|
||||
/db/
|
||||
/conf/
|
||||
@@ -1,7 +1 @@
|
||||
{
|
||||
"interval": "60",
|
||||
"port": 9998,
|
||||
"workingHours": { "from": "", "to": "" },
|
||||
"demoMode": false,
|
||||
"analyticsEnabled": null
|
||||
}
|
||||
{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":false}
|
||||
@@ -6,6 +6,10 @@ import react from 'eslint-plugin-react';
|
||||
import babelParser from '@babel/eslint-parser';
|
||||
|
||||
export default [
|
||||
{
|
||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/public/**', 'db/**', 'conf/**'],
|
||||
},
|
||||
js.configs.recommended,
|
||||
prettier,
|
||||
{
|
||||
@@ -23,70 +27,34 @@ export default [
|
||||
after: 'readonly',
|
||||
it: 'readonly',
|
||||
},
|
||||
parserOptions: {
|
||||
requireConfigFile: false,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
react,
|
||||
parserOptions: { requireConfigFile: false },
|
||||
},
|
||||
plugins: { react },
|
||||
rules: {
|
||||
eqeqeq: [2, 'allow-null'],
|
||||
|
||||
// Semantics / Performance impacting
|
||||
strict: 0,
|
||||
'no-redeclare': [2, { builtinGlobals: false }],
|
||||
'class-methods-use-this': 'off',
|
||||
|
||||
// Style
|
||||
indent: ['off', 2],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }],
|
||||
semi: ['error', 'always'],
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
|
||||
// React
|
||||
'jsx-quotes': ['error', 'prefer-double'],
|
||||
'react/display-name': 'off',
|
||||
'react/forbid-prop-types': 'off',
|
||||
'react/jsx-closing-bracket-location': 'off',
|
||||
'react/jsx-curly-spacing': 'off',
|
||||
'react/jsx-handler-names': [
|
||||
'off',
|
||||
{
|
||||
eventHandlerPrefix: 'handle',
|
||||
eventHandlerPropPrefix: 'on',
|
||||
},
|
||||
],
|
||||
'react/jsx-handler-names': ['off', { eventHandlerPrefix: 'handle', eventHandlerPropPrefix: 'on' }],
|
||||
'react/jsx-indent-props': 'off',
|
||||
'react/jsx-key': 'off',
|
||||
'react/jsx-max-props-per-line': 'off',
|
||||
'react/jsx-no-bind': [
|
||||
'error',
|
||||
{
|
||||
ignoreRefs: true,
|
||||
allowArrowFunctions: true,
|
||||
allowBind: false,
|
||||
},
|
||||
],
|
||||
'react/jsx-no-bind': ['error', { ignoreRefs: true, allowArrowFunctions: true, allowBind: false }],
|
||||
'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }],
|
||||
'react/jsx-no-literals': 'off',
|
||||
'react/jsx-no-undef': 'error',
|
||||
'react/jsx-pascal-case': [
|
||||
'error',
|
||||
{
|
||||
allowAllCaps: true,
|
||||
ignore: [],
|
||||
},
|
||||
],
|
||||
'react/sort-prop-types': [
|
||||
'off',
|
||||
{
|
||||
ignoreCase: true,
|
||||
callbacksLast: false,
|
||||
requiredFirst: false,
|
||||
},
|
||||
],
|
||||
'react/jsx-pascal-case': ['error', { allowAllCaps: true, ignore: [] }],
|
||||
'react/sort-prop-types': ['off', { ignoreCase: true, callbacksLast: false, requiredFirst: false }],
|
||||
'react/jsx-sort-prop-types': 'off',
|
||||
'react/jsx-sort-props': 'off',
|
||||
'react/jsx-uses-react': 'error',
|
||||
@@ -106,14 +74,7 @@ export default [
|
||||
'react/require-render-return': 'error',
|
||||
'react/self-closing-comp': 'warn',
|
||||
'react/sort-comp': 'off',
|
||||
'react/jsx-wrap-multilines': [
|
||||
'warn',
|
||||
{
|
||||
declaration: true,
|
||||
assignment: true,
|
||||
return: true,
|
||||
},
|
||||
],
|
||||
'react/jsx-wrap-multilines': ['warn', { declaration: true, assignment: true, return: true }],
|
||||
'react/wrap-multilines': 'off',
|
||||
'react/jsx-first-prop-new-line': 'off',
|
||||
'react/jsx-equals-spacing': ['warn', 'never'],
|
||||
@@ -126,20 +87,10 @@ export default [
|
||||
'react/no-find-dom-node': 'warn',
|
||||
'react/forbid-component-props': ['off', { forbid: [] }],
|
||||
'react/no-danger-with-children': 'error',
|
||||
'react/no-unused-prop-types': [
|
||||
'warn',
|
||||
{
|
||||
customValidators: [],
|
||||
skipShapeProps: true,
|
||||
},
|
||||
],
|
||||
'react/no-unused-prop-types': ['warn', { customValidators: [], skipShapeProps: true }],
|
||||
'react/style-prop-object': 'error',
|
||||
'react/no-children-prop': 'warn',
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
settings: { react: { version: 'detect' } },
|
||||
},
|
||||
];
|
||||
|
||||
22
package.json
22
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fredy",
|
||||
"version": "11.4.4",
|
||||
"version": "11.5.0",
|
||||
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
|
||||
"scripts": {
|
||||
"prepare": "husky",
|
||||
@@ -54,18 +54,18 @@
|
||||
"Firefox ESR"
|
||||
],
|
||||
"dependencies": {
|
||||
"@douyinfe/semi-ui": "2.85.0",
|
||||
"@douyinfe/semi-ui": "2.86.0",
|
||||
"@rematch/core": "2.2.0",
|
||||
"@rematch/loading": "2.1.2",
|
||||
"@sendgrid/mail": "8.1.5",
|
||||
"@visactor/react-vchart": "^2.0.4",
|
||||
"@visactor/vchart-semi-theme": "^1.12.2",
|
||||
"@vitejs/plugin-react": "5.0.2",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"body-parser": "2.2.0",
|
||||
"cheerio": "^1.1.2",
|
||||
"cookie-session": "2.1.1",
|
||||
"handlebars": "4.7.8",
|
||||
"highcharts": "12.3.0",
|
||||
"highcharts-react-official": "3.2.2",
|
||||
"lodash": "4.17.21",
|
||||
"lowdb": "7.0.1",
|
||||
"markdown": "^0.5.0",
|
||||
@@ -75,22 +75,22 @@
|
||||
"node-mailjet": "6.0.9",
|
||||
"p-throttle": "^8.0.0",
|
||||
"package-up": "^5.0.0",
|
||||
"puppeteer": "^24.17.1",
|
||||
"puppeteer": "^24.18.0",
|
||||
"puppeteer-extra": "^3.3.6",
|
||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||
"query-string": "9.2.2",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-redux": "9.2.0",
|
||||
"react-router": "5.2.1",
|
||||
"react-router-dom": "5.3.0",
|
||||
"react-router": "7.8.2",
|
||||
"react-router-dom": "7.8.2",
|
||||
"redux": "5.0.1",
|
||||
"redux-thunk": "3.1.0",
|
||||
"restana": "5.1.0",
|
||||
"serve-static": "2.2.0",
|
||||
"slack": "11.0.2",
|
||||
"string-similarity": "^4.0.4",
|
||||
"vite": "7.1.3",
|
||||
"vite": "7.1.4",
|
||||
"x-var": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -102,12 +102,12 @@
|
||||
"eslint": "9.34.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"esmock": "2.7.1",
|
||||
"esmock": "2.7.2",
|
||||
"history": "5.3.0",
|
||||
"husky": "9.1.7",
|
||||
"less": "4.4.1",
|
||||
"lint-staged": "16.1.5",
|
||||
"mocha": "11.7.1",
|
||||
"lint-staged": "16.1.6",
|
||||
"mocha": "11.7.2",
|
||||
"nodemon": "^3.1.10",
|
||||
"prettier": "3.6.2",
|
||||
"redux-logger": "3.0.6"
|
||||
|
||||
@@ -7,14 +7,13 @@ import JobMutation from './views/jobs/mutation/JobMutation';
|
||||
import UserMutator from './views/user/mutation/UserMutator';
|
||||
import JobInsight from './views/jobs/insights/JobInsight.jsx';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Switch, Redirect } from 'react-router-dom';
|
||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
import Logout from './components/logout/Logout';
|
||||
import Logo from './components/logo/Logo';
|
||||
import Menu from './components/menu/Menu';
|
||||
import Login from './views/login/Login';
|
||||
import Users from './views/user/Users';
|
||||
import Jobs from './views/jobs/Jobs';
|
||||
import { Route } from 'react-router';
|
||||
|
||||
import './App.less';
|
||||
import TrackingModal from './components/tracking/TrackingModal.jsx';
|
||||
@@ -49,10 +48,10 @@ export default function FredyApp() {
|
||||
const isAdmin = () => currentUser != null && currentUser.isAdmin;
|
||||
|
||||
const login = () => (
|
||||
<Switch>
|
||||
<Route name="Login" path={'/login'} component={Login} />
|
||||
<Redirect from="*" to={'/login'} />
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="*" element={<Navigate to="/login" replace />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
return loading ? null : needsLogin() ? (
|
||||
@@ -77,34 +76,49 @@ export default function FredyApp() {
|
||||
</>
|
||||
)}
|
||||
{settings.analyticsEnabled === null && !settings.demoMode && <TrackingModal />}
|
||||
<Switch>
|
||||
<Route name="Insufficient Permission" path={'/403'} component={InsufficientPermission} />
|
||||
<Route name="Create new Job" path={'/jobs/new'} component={JobMutation} />
|
||||
<Route name="Edit a Job" path={'/jobs/edit/:jobId'} component={JobMutation} />
|
||||
<Route name="Insights into a Job" path={'/jobs/insights/:jobId'} component={JobInsight} />
|
||||
<Route name="Job overview" path={'/jobs'} component={Jobs} />
|
||||
<PermissionAwareRoute
|
||||
name="Create new User"
|
||||
<Routes>
|
||||
<Route path="/403" element={<InsufficientPermission />} />
|
||||
<Route path="/jobs/new" element={<JobMutation />} />
|
||||
<Route path="/jobs/edit/:jobId" element={<JobMutation />} />
|
||||
<Route path="/jobs/insights/:jobId" element={<JobInsight />} />
|
||||
<Route path="/jobs" element={<Jobs />} />
|
||||
|
||||
{/* Permission-aware routes */}
|
||||
<Route
|
||||
path="/users/new"
|
||||
component={<UserMutator />}
|
||||
currentUser={currentUser}
|
||||
element={
|
||||
<PermissionAwareRoute currentUser={currentUser}>
|
||||
<UserMutator />
|
||||
</PermissionAwareRoute>
|
||||
}
|
||||
/>
|
||||
<PermissionAwareRoute
|
||||
name="Edit a user"
|
||||
<Route
|
||||
path="/users/edit/:userId"
|
||||
component={<UserMutator />}
|
||||
currentUser={currentUser}
|
||||
element={
|
||||
<PermissionAwareRoute currentUser={currentUser}>
|
||||
<UserMutator />
|
||||
</PermissionAwareRoute>
|
||||
}
|
||||
/>
|
||||
<PermissionAwareRoute name="Users" path="/users" component={<Users />} currentUser={currentUser} />
|
||||
<PermissionAwareRoute
|
||||
name="General Settings"
|
||||
<Route
|
||||
path="/users"
|
||||
element={
|
||||
<PermissionAwareRoute currentUser={currentUser}>
|
||||
<Users />
|
||||
</PermissionAwareRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/generalSettings"
|
||||
component={<GeneralSettings />}
|
||||
currentUser={currentUser}
|
||||
element={
|
||||
<PermissionAwareRoute currentUser={currentUser}>
|
||||
<GeneralSettings />
|
||||
</PermissionAwareRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Redirect from="/" to={'/jobs'} />
|
||||
</Switch>
|
||||
<Route path="/" element={<Navigate to="/jobs" replace />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,23 +2,24 @@ import React from 'react';
|
||||
|
||||
import { reduxStore } from './services/rematch/store';
|
||||
import { HashRouter } from 'react-router-dom';
|
||||
import { createHashHistory } from 'history';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
|
||||
import { LocaleProvider } from '@douyinfe/semi-ui';
|
||||
import { initVChartSemiTheme } from '@visactor/vchart-semi-theme';
|
||||
import App from './App';
|
||||
import './Index.less';
|
||||
|
||||
const container = document.getElementById('fredy');
|
||||
const root = createRoot(container);
|
||||
const history = createHashHistory();
|
||||
|
||||
import App from './App';
|
||||
|
||||
import './Index.less';
|
||||
initVChartSemiTheme({
|
||||
defaultMode: 'dark',
|
||||
});
|
||||
|
||||
root.render(
|
||||
<Provider store={reduxStore}>
|
||||
<HashRouter history={history}>
|
||||
<HashRouter>
|
||||
<LocaleProvider locale={en_US}>
|
||||
<App />
|
||||
</LocaleProvider>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Tabs, TabPane } from '@douyinfe/semi-ui';
|
||||
|
||||
import { useLocation } from 'react-router';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { IconUser, IconTerminal, IconSetting } from '@douyinfe/semi-icons';
|
||||
import './Menu.less';
|
||||
|
||||
@@ -12,15 +12,10 @@ function parsePathName(name) {
|
||||
}
|
||||
|
||||
const TopMenu = function TopMenu({ isAdmin }) {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
return (
|
||||
<Tabs
|
||||
className="menu"
|
||||
type="line"
|
||||
activeKey={parsePathName(location.pathname)}
|
||||
onTabClick={(key) => history.push(key)}
|
||||
>
|
||||
<Tabs className="menu" type="line" activeKey={parsePathName(location.pathname)} onTabClick={(key) => navigate(key)}>
|
||||
<TabPane
|
||||
itemKey="/jobs"
|
||||
tab={
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { Route } from 'react-router';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
export default function PermissionAwareRoute({ currentUser, name, path, component }) {
|
||||
/**
|
||||
* Checks if given component should be rendered if current user has given permission enabled. If that's not the case,
|
||||
* user is redirected to '/403'.
|
||||
*
|
||||
* @param permission
|
||||
* @param component
|
||||
* @param path
|
||||
* @returns {*}
|
||||
*/
|
||||
const checkIfAdmin = (component, path) => {
|
||||
return currentUser != null && currentUser.isAdmin ? component : <Redirect from={path} to="/403" />;
|
||||
};
|
||||
|
||||
return <Route name={name} path={path} render={() => checkIfAdmin(component, path)} />;
|
||||
export default function PermissionAwareRoute({ currentUser, children }) {
|
||||
const isAdmin = currentUser != null && currentUser.isAdmin;
|
||||
return isAdmin ? children : <Navigate to="/403" replace />;
|
||||
}
|
||||
|
||||
@@ -8,4 +8,4 @@ export function format(ts) {
|
||||
second: 'numeric',
|
||||
}).format(ts);
|
||||
}
|
||||
export const roundToNext5Minute = (ts) => Math.ceil(ts / (1000 * 60 * 5)) * (1000 * 60 * 5);
|
||||
export const roundToHour = (ts) => Math.ceil(ts / (1000 * 60 * 60)) * (1000 * 60 * 60);
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import JobTable from '../../components/table/JobTable';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { xhrDelete, xhrPut } from '../../services/xhr';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import ProcessingTimes from './ProcessingTimes';
|
||||
import { Button, Toast } from '@douyinfe/semi-ui';
|
||||
import { IconPlusCircle } from '@douyinfe/semi-icons';
|
||||
@@ -12,7 +12,7 @@ import './Jobs.less';
|
||||
export default function Jobs() {
|
||||
const jobs = useSelector((state) => state.jobs.jobs);
|
||||
const processingTimes = useSelector((state) => state.jobs.processingTimes);
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onJobRemoval = async (jobId) => {
|
||||
@@ -43,7 +43,7 @@ export default function Jobs() {
|
||||
type="primary"
|
||||
icon={<IconPlusCircle />}
|
||||
className="jobs__newButton"
|
||||
onClick={() => history.push('/jobs/new')}
|
||||
onClick={() => navigate('/jobs/new')}
|
||||
>
|
||||
New Job
|
||||
</Button>
|
||||
@@ -53,8 +53,8 @@ export default function Jobs() {
|
||||
jobs={jobs || []}
|
||||
onJobRemoval={onJobRemoval}
|
||||
onJobStatusChanged={onJobStatusChanged}
|
||||
onJobInsight={(jobId) => history.push(`/jobs/insights/${jobId}`)}
|
||||
onJobEdit={(jobId) => history.push(`/jobs/edit/${jobId}`)}
|
||||
onJobInsight={(jobId) => navigate(`/jobs/insights/${jobId}`)}
|
||||
onJobEdit={(jobId) => navigate(`/jobs/edit/${jobId}`)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import { roundToNext5Minute } from '../../../services/time/timeService';
|
||||
import { roundToHour } from '../../../services/time/timeService';
|
||||
import Headline from '../../../components/headline/Headline';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Linechart from './Linechart';
|
||||
|
||||
const JobInsight = function JobInsight() {
|
||||
@@ -20,27 +20,47 @@ const JobInsight = function JobInsight() {
|
||||
|
||||
const getData = () => {
|
||||
const data = insights[params.jobId] || {};
|
||||
const providers = Object.keys(data);
|
||||
|
||||
const result = [];
|
||||
Object.keys(data).forEach((key) => {
|
||||
const series = {
|
||||
name: key[0].toUpperCase() + key.substring(1),
|
||||
data: [],
|
||||
};
|
||||
const countsByProvider = {};
|
||||
const allTimes = new Set();
|
||||
|
||||
const cap = (s) => (s ? s[0].toUpperCase() + s.slice(1) : 'Unknown');
|
||||
|
||||
providers.forEach((key) => {
|
||||
const providerName = cap(key);
|
||||
const tmpTimeObj = {};
|
||||
|
||||
Object.values(data[key] || {}).forEach((listingTs) => {
|
||||
const time = roundToNext5Minute(listingTs);
|
||||
const time = roundToHour(listingTs);
|
||||
tmpTimeObj[time] = tmpTimeObj[time] == null ? 1 : tmpTimeObj[time] + 1;
|
||||
allTimes.add(time);
|
||||
});
|
||||
|
||||
Object.keys(tmpTimeObj)
|
||||
.sort()
|
||||
.forEach((timeKey) => {
|
||||
series.data.push([parseInt(timeKey), tmpTimeObj[timeKey]]);
|
||||
countsByProvider[providerName] = tmpTimeObj;
|
||||
});
|
||||
|
||||
const sortedTimes = Array.from(allTimes).sort((a, b) => a - b);
|
||||
|
||||
const result = [];
|
||||
providers.forEach((key) => {
|
||||
const providerName = cap(key);
|
||||
const bucket = countsByProvider[providerName] || {};
|
||||
|
||||
sortedTimes.forEach((t) => {
|
||||
result.push({
|
||||
listings: new Intl.DateTimeFormat('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
}).format(new Date(parseInt(t))),
|
||||
listingsNumber: bucket[t] || 0, // y value
|
||||
provider: providerName, // series key
|
||||
});
|
||||
result.push(series);
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,337 +1,49 @@
|
||||
import React from 'react';
|
||||
|
||||
import Placeholder from '../../../components/placeholder/Placeholder';
|
||||
import HighchartsReact from 'highcharts-react-official';
|
||||
import Highcharts from 'highcharts/highcharts.src.js';
|
||||
import { VChart } from '@visactor/react-vchart';
|
||||
|
||||
import './Linechart.less';
|
||||
|
||||
Highcharts.theme = {
|
||||
colors: [
|
||||
'#2b908f',
|
||||
'#90ee7e',
|
||||
'#f45b5b',
|
||||
'#7798BF',
|
||||
'#aaeeee',
|
||||
'#ff0066',
|
||||
'#eeaaee',
|
||||
'#55BF3B',
|
||||
'#DF5353',
|
||||
'#7798BF',
|
||||
'#aaeeee',
|
||||
const commonSpec = {
|
||||
type: 'line',
|
||||
xField: 'listings',
|
||||
yField: 'listingsNumber',
|
||||
seriesField: 'provider',
|
||||
legends: { visible: true },
|
||||
line: {
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
},
|
||||
},
|
||||
point: {
|
||||
visible: false,
|
||||
},
|
||||
axes: [
|
||||
{
|
||||
orient: 'bottom',
|
||||
field: 'listings',
|
||||
zero: false,
|
||||
},
|
||||
],
|
||||
chart: {
|
||||
backgroundColor: {
|
||||
linearGradient: {
|
||||
x1: 0,
|
||||
y1: 0,
|
||||
x2: 1,
|
||||
y2: 1,
|
||||
},
|
||||
stops: [
|
||||
[0, '#2a2a2b'],
|
||||
[1, '#3e3e40'],
|
||||
],
|
||||
},
|
||||
style: {
|
||||
fontFamily: "'Unica One', sans-serif",
|
||||
},
|
||||
plotBorderColor: '#606063',
|
||||
},
|
||||
title: {
|
||||
style: {
|
||||
color: '#E0E0E3',
|
||||
textTransform: 'uppercase',
|
||||
fontSize: '20px',
|
||||
},
|
||||
},
|
||||
subtitle: {
|
||||
style: {
|
||||
color: '#E0E0E3',
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
gridLineColor: '#707073',
|
||||
labels: {
|
||||
style: {
|
||||
color: '#E0E0E3',
|
||||
},
|
||||
},
|
||||
lineColor: '#707073',
|
||||
minorGridLineColor: '#505053',
|
||||
tickColor: '#707073',
|
||||
title: {
|
||||
style: {
|
||||
color: '#A0A0A3',
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
gridLineColor: '#707073',
|
||||
labels: {
|
||||
style: {
|
||||
color: '#E0E0E3',
|
||||
},
|
||||
},
|
||||
lineColor: '#707073',
|
||||
minorGridLineColor: '#505053',
|
||||
tickColor: '#707073',
|
||||
tickWidth: 1,
|
||||
title: {
|
||||
style: {
|
||||
color: '#A0A0A3',
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
||||
style: {
|
||||
color: '#F0F0F0',
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
series: {
|
||||
dataLabels: {
|
||||
color: '#F0F0F3',
|
||||
style: {
|
||||
fontSize: '13px',
|
||||
},
|
||||
},
|
||||
marker: {
|
||||
lineColor: '#333',
|
||||
},
|
||||
},
|
||||
boxplot: {
|
||||
fillColor: '#505053',
|
||||
},
|
||||
candlestick: {
|
||||
lineColor: 'white',
|
||||
},
|
||||
errorbar: {
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
itemStyle: {
|
||||
color: '#E0E0E3',
|
||||
},
|
||||
itemHoverStyle: {
|
||||
color: '#FFF',
|
||||
},
|
||||
itemHiddenStyle: {
|
||||
color: '#606063',
|
||||
},
|
||||
title: {
|
||||
style: {
|
||||
color: '#C0C0C0',
|
||||
},
|
||||
},
|
||||
},
|
||||
credits: {
|
||||
style: {
|
||||
color: '#666',
|
||||
},
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
color: '#707073',
|
||||
},
|
||||
},
|
||||
|
||||
drilldown: {
|
||||
activeAxisLabelStyle: {
|
||||
color: '#F0F0F3',
|
||||
},
|
||||
activeDataLabelStyle: {
|
||||
color: '#F0F0F3',
|
||||
},
|
||||
},
|
||||
|
||||
navigation: {
|
||||
buttonOptions: {
|
||||
symbolStroke: '#DDDDDD',
|
||||
theme: {
|
||||
fill: '#505053',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// scroll charts
|
||||
rangeSelector: {
|
||||
buttonTheme: {
|
||||
fill: '#505053',
|
||||
stroke: '#000000',
|
||||
style: {
|
||||
color: '#CCC',
|
||||
},
|
||||
states: {
|
||||
hover: {
|
||||
fill: '#707073',
|
||||
stroke: '#000000',
|
||||
style: {
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
select: {
|
||||
fill: '#000003',
|
||||
stroke: '#000000',
|
||||
style: {
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputBoxBorderColor: '#505053',
|
||||
inputStyle: {
|
||||
backgroundColor: '#333',
|
||||
color: 'silver',
|
||||
},
|
||||
labelStyle: {
|
||||
color: 'silver',
|
||||
},
|
||||
},
|
||||
|
||||
navigator: {
|
||||
handles: {
|
||||
backgroundColor: '#666',
|
||||
borderColor: '#AAA',
|
||||
},
|
||||
outlineColor: '#CCC',
|
||||
maskFill: 'rgba(255,255,255,0.1)',
|
||||
series: {
|
||||
color: '#7798BF',
|
||||
lineColor: '#A6C7ED',
|
||||
},
|
||||
xAxis: {
|
||||
gridLineColor: '#505053',
|
||||
},
|
||||
},
|
||||
|
||||
scrollbar: {
|
||||
barBackgroundColor: '#808083',
|
||||
barBorderColor: '#808083',
|
||||
buttonArrowColor: '#CCC',
|
||||
buttonBackgroundColor: '#606063',
|
||||
buttonBorderColor: '#606063',
|
||||
rifleColor: '#FFF',
|
||||
trackBackgroundColor: '#404043',
|
||||
trackBorderColor: '#404043',
|
||||
},
|
||||
};
|
||||
|
||||
// Apply the theme
|
||||
Highcharts.setOptions(Highcharts.theme);
|
||||
|
||||
const defaultOptions = {
|
||||
title: {
|
||||
text: null,
|
||||
},
|
||||
legend: {
|
||||
enabled: true,
|
||||
},
|
||||
xAxis: {
|
||||
//most of the time (if not everytime), the x axis is time
|
||||
type: 'datetime',
|
||||
crosshair: {
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: null,
|
||||
},
|
||||
//do not show float numbers
|
||||
allowDecimals: false,
|
||||
},
|
||||
chart: {
|
||||
type: 'line',
|
||||
zoomType: 'x',
|
||||
plotBackgroundColor: null,
|
||||
plotBorderWidth: null,
|
||||
},
|
||||
exporting: {
|
||||
enabled: false,
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
formatter: null,
|
||||
},
|
||||
plotOptions: {
|
||||
line: {
|
||||
animation: false,
|
||||
marker: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
series: {
|
||||
lineWidth: 1.5,
|
||||
connectNulls: true,
|
||||
marker: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Usage of this chart:
|
||||
* title: optional (show a title, if null, no title is shown)
|
||||
* zoom: optional (if true, zooming in x axis is possible)
|
||||
* legend: optional (show / hide the legend)
|
||||
* series: mandatory (an array of data to be shown)
|
||||
*
|
||||
* <Linechart
|
||||
* title="something"
|
||||
* legend={true}
|
||||
* timeframe={week/month/all} --> If this is not set, we assume the timeframe is 'all'
|
||||
* //everything that is "subscribed" to this topic will receive this update
|
||||
* highlightTopic="someTopic"
|
||||
* height={"500px"}
|
||||
* zoom={true}
|
||||
* series={[
|
||||
* {
|
||||
* name: 'something',
|
||||
* data: [x,y],
|
||||
* dashStyle: (OPTIONAL) | solid / 'shortdot'
|
||||
* }
|
||||
* ]}
|
||||
* />
|
||||
*/
|
||||
const Linechart = function Linechart({ title, series, height, isLoading = false }) {
|
||||
const options = () => {
|
||||
return {
|
||||
...defaultOptions,
|
||||
title: {
|
||||
text: title,
|
||||
},
|
||||
time: {
|
||||
useUTC: false,
|
||||
},
|
||||
legend: {
|
||||
enabled: true,
|
||||
},
|
||||
series: series.map((series) => {
|
||||
return {
|
||||
...series,
|
||||
};
|
||||
}),
|
||||
chart: {
|
||||
type: 'line',
|
||||
zoomType: 'x',
|
||||
height: height || '400px',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const Linechart = function Linechart({ title, series, isLoading = false }) {
|
||||
return (
|
||||
<Placeholder ready={!isLoading} rows={6}>
|
||||
{series == null || series.length === 0 ? (
|
||||
<div className="linechart__no__data">No Data for selected timeframe :-/</div>
|
||||
) : (
|
||||
<HighchartsReact highcharts={Highcharts} options={options()} />
|
||||
<VChart
|
||||
spec={{
|
||||
...commonSpec,
|
||||
title: {
|
||||
visible: true,
|
||||
text: title,
|
||||
},
|
||||
data: { values: series },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Placeholder>
|
||||
);
|
||||
|
||||
@@ -7,8 +7,7 @@ import ProviderMutator from './components/provider/ProviderMutator';
|
||||
import Headline from '../../../components/headline/Headline';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { xhrPost } from '../../../services/xhr';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useParams } from 'react-router';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Divider, Input, Switch, Button, TagInput, Toast } from '@douyinfe/semi-ui';
|
||||
import './JobMutation.less';
|
||||
import { SegmentPart } from '../../../components/segment/SegmentPart';
|
||||
@@ -34,7 +33,7 @@ export default function JobMutator() {
|
||||
const [blacklist, setBlacklist] = useState(defaultBlacklist);
|
||||
const [notificationAdapterData, setNotificationAdapterData] = useState(defaultNotificationAdapter);
|
||||
const [enabled, setEnabled] = useState(defaultEnabled);
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isSavingEnabled = () => {
|
||||
@@ -53,7 +52,7 @@ export default function JobMutator() {
|
||||
});
|
||||
await dispatch.jobs.getJobs();
|
||||
Toast.success('Job successfully saved...');
|
||||
history.push('/jobs');
|
||||
navigate('/jobs');
|
||||
} catch (Exception) {
|
||||
console.error(Exception.json.message);
|
||||
Toast.error(Exception.json != null ? Exception.json.message : Exception);
|
||||
@@ -177,7 +176,7 @@ export default function JobMutator() {
|
||||
<Switch className="jobMutation__spaceTop" onChange={(checked) => setEnabled(checked)} checked={enabled} />
|
||||
</SegmentPart>
|
||||
<Divider margin="1rem" />
|
||||
<Button type="danger" style={{ marginRight: '1rem' }} onClick={() => history.push('/jobs')}>
|
||||
<Button type="danger" style={{ marginRight: '1rem' }} onClick={() => navigate('/jobs')}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" icon={<IconPlusCircle />} disabled={!isSavingEnabled()} onClick={mutateJob}>
|
||||
|
||||
@@ -30,7 +30,8 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false,
|
||||
if (selectedProvider.baseUrl.indexOf(url.origin) === -1) {
|
||||
return 'The url you have copied is not valid.';
|
||||
}
|
||||
} catch (Exception) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
} catch (ignored) {
|
||||
return 'The url you have copied is not valid.';
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useEffect } from 'react';
|
||||
import cityBackground from '../../assets/city_background.jpg';
|
||||
import Logo from '../../components/logo/Logo';
|
||||
import { xhrPost } from '../../services/xhr';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Input, Button, Banner, Toast } from '@douyinfe/semi-ui';
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function Login() {
|
||||
const [password, setPassword] = React.useState('');
|
||||
const [error, setError] = React.useState(null);
|
||||
const demoMode = useSelector((state) => state.demoMode.demoMode || false);
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
@@ -38,7 +38,8 @@ export default function Login() {
|
||||
username: username.trim(),
|
||||
password,
|
||||
});
|
||||
} catch (Exception) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
} catch (ignored) {
|
||||
Toast.error('Login unsuccessful…');
|
||||
return;
|
||||
}
|
||||
@@ -46,7 +47,7 @@ export default function Login() {
|
||||
Toast.success('Login successful!');
|
||||
|
||||
await dispatch.user.getCurrentUser();
|
||||
history.push('/jobs');
|
||||
navigate('/jobs');
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IconPlus } from '@douyinfe/semi-icons';
|
||||
import { Button } from '@douyinfe/semi-ui';
|
||||
import UserRemovalModal from './UserRemovalModal';
|
||||
import { xhrDelete } from '../../services/xhr';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import './Users.less';
|
||||
|
||||
@@ -16,7 +16,7 @@ const Users = function Users() {
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const users = useSelector((state) => state.user.users);
|
||||
const [userIdToBeRemoved, setUserIdToBeRemoved] = React.useState(null);
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
React.useEffect(() => {
|
||||
async function init() {
|
||||
@@ -50,7 +50,7 @@ const Users = function Users() {
|
||||
type="primary"
|
||||
className="users__newButton"
|
||||
icon={<IconPlus />}
|
||||
onClick={() => history.push('/users/new')}
|
||||
onClick={() => navigate('/users/new')}
|
||||
>
|
||||
Create new User
|
||||
</Button>
|
||||
@@ -58,7 +58,7 @@ const Users = function Users() {
|
||||
<UserTable
|
||||
user={users}
|
||||
onUserEdit={(userId) => {
|
||||
history.push(`/users/edit/${userId}`);
|
||||
navigate(`/users/edit/${userId}`);
|
||||
}}
|
||||
onUserRemoval={(userId) => {
|
||||
setUserIdToBeRemoved(userId);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { xhrGet, xhrPost } from '../../../services/xhr';
|
||||
import { useHistory, useParams } from 'react-router';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Divider, Input, Switch, Button, Toast } from '@douyinfe/semi-ui';
|
||||
import './UserMutator.less';
|
||||
@@ -15,7 +15,7 @@ const UserMutator = function UserMutator() {
|
||||
const [password2, setPassword2] = React.useState('');
|
||||
const [isAdmin, setIsAdmin] = React.useState(false);
|
||||
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -50,7 +50,7 @@ const UserMutator = function UserMutator() {
|
||||
});
|
||||
await dispatch.user.getUsers();
|
||||
Toast.success('User successfully saved...');
|
||||
history.push('/users');
|
||||
navigate('/users');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.error(error.json.message);
|
||||
@@ -98,7 +98,7 @@ const UserMutator = function UserMutator() {
|
||||
<Switch checked={isAdmin} onChange={(checked) => setIsAdmin(checked)} />
|
||||
</SegmentPart>
|
||||
<Divider margin="1rem" />
|
||||
<Button type="danger" style={{ marginRight: '1rem' }} onClick={() => history.push('/users')}>
|
||||
<Button type="danger" style={{ marginRight: '1rem' }} onClick={() => navigate('/users')}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" icon={<IconPlusCircle />} onClick={saveUser}>
|
||||
|
||||
Reference in New Issue
Block a user