mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d17ef9ef1e | ||
|
|
337ee922a6 |
@@ -1 +1 @@
|
|||||||
{"interval":"60","port":9998,"scrapingAnt":{"apiKey":"","proxy":"datacenter"},"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":null}
|
{"interval":"60","port":9998,"scrapingAnt":{"apiKey":"d","proxy":"datacenter"},"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":null}
|
||||||
10
index.js
10
index.js
@@ -7,6 +7,8 @@ import FredyRuntime from './lib/FredyRuntime.js';
|
|||||||
import { duringWorkingHoursOrNotSet } from './lib/utils.js';
|
import { duringWorkingHoursOrNotSet } from './lib/utils.js';
|
||||||
import './lib/api/api.js';
|
import './lib/api/api.js';
|
||||||
import {track} from './lib/services/tracking/Tracker.js';
|
import {track} from './lib/services/tracking/Tracker.js';
|
||||||
|
import {handleDemoUser} from './lib/services/storage/userStorage.js';
|
||||||
|
import {cleanupDemoAtMidnight} from './lib/services/demoCleanup.js';
|
||||||
//if db folder does not exist, ensure to create it before loading anything else
|
//if db folder does not exist, ensure to create it before loading anything else
|
||||||
if (!fs.existsSync('./db')) {
|
if (!fs.existsSync('./db')) {
|
||||||
fs.mkdirSync('./db');
|
fs.mkdirSync('./db');
|
||||||
@@ -17,14 +19,21 @@ const provider = fs.readdirSync(path).filter((file) => file.endsWith('.js'));
|
|||||||
const INTERVAL = config.interval * 60 * 1000;
|
const INTERVAL = config.interval * 60 * 1000;
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
console.log(`Started Fredy successfully. Ui can be accessed via http://localhost:${config.port}`);
|
console.log(`Started Fredy successfully. Ui can be accessed via http://localhost:${config.port}`);
|
||||||
|
if(config.demoMode){
|
||||||
|
console.info('Running in demo mode');
|
||||||
|
cleanupDemoAtMidnight();
|
||||||
|
}
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
const fetchedProvider = await Promise.all(
|
const fetchedProvider = await Promise.all(
|
||||||
provider.filter((provider) => provider.endsWith('.js')).map(async (pro) => import(`${path}/${pro}`))
|
provider.filter((provider) => provider.endsWith('.js')).map(async (pro) => import(`${path}/${pro}`))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
handleDemoUser();
|
||||||
|
|
||||||
setInterval(
|
setInterval(
|
||||||
(function exec() {
|
(function exec() {
|
||||||
const isDuringWorkingHoursOrNotSet = duringWorkingHoursOrNotSet(config, Date.now());
|
const isDuringWorkingHoursOrNotSet = duringWorkingHoursOrNotSet(config, Date.now());
|
||||||
|
if(!config.demoMode) {
|
||||||
if (isDuringWorkingHoursOrNotSet) {
|
if (isDuringWorkingHoursOrNotSet) {
|
||||||
track();
|
track();
|
||||||
config.lastRun = Date.now();
|
config.lastRun = Date.now();
|
||||||
@@ -46,6 +55,7 @@ setInterval(
|
|||||||
console.debug('Working hours set. Skipping as outside of working hours.');
|
console.debug('Working hours set. Skipping as outside of working hours.');
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return exec;
|
return exec;
|
||||||
})(),
|
})(),
|
||||||
INTERVAL
|
INTERVAL
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import restana from 'restana';
|
|||||||
import files from 'serve-static';
|
import files from 'serve-static';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { getDirName } from '../utils.js';
|
import { getDirName } from '../utils.js';
|
||||||
|
import {demoRouter} from './routes/demoRouter.js';
|
||||||
const service = restana();
|
const service = restana();
|
||||||
const staticService = files(path.join(getDirName(), '../ui/public'));
|
const staticService = files(path.join(getDirName(), '../ui/public'));
|
||||||
const PORT = config.port || 9998;
|
const PORT = config.port || 9998;
|
||||||
@@ -30,6 +31,9 @@ service.use('/api/jobs/insights', analyticsRouter);
|
|||||||
service.use('/api/admin/users', userRouter);
|
service.use('/api/admin/users', userRouter);
|
||||||
service.use('/api/jobs', jobRouter);
|
service.use('/api/jobs', jobRouter);
|
||||||
service.use('/api/login', loginRouter);
|
service.use('/api/login', loginRouter);
|
||||||
|
//this route is unsecured intentionally as it is being queried from the login page
|
||||||
|
service.use('/api/demo', demoRouter);
|
||||||
|
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
service.start(PORT).then(() => {
|
service.start(PORT).then(() => {
|
||||||
console.info(`Started API service on port ${PORT}`);
|
console.info(`Started API service on port ${PORT}`);
|
||||||
|
|||||||
11
lib/api/routes/demoRouter.js
Normal file
11
lib/api/routes/demoRouter.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import restana from 'restana';
|
||||||
|
import {config} from '../../utils.js';
|
||||||
|
const service = restana();
|
||||||
|
const demoRouter = service.newRouter();
|
||||||
|
|
||||||
|
demoRouter.get('/', async (req, res) => {
|
||||||
|
res.body = Object.assign({}, {demoMode: config.demoMode});
|
||||||
|
res.send();
|
||||||
|
});
|
||||||
|
|
||||||
|
export { demoRouter };
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import restana from 'restana';
|
import restana from 'restana';
|
||||||
import {config, getDirName, readConfigFromStorage, refreshConfig} from '../../utils.js';
|
import {config, getDirName, readConfigFromStorage, refreshConfig} from '../../utils.js';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import {handleDemoUser} from '../../services/storage/userStorage.js';
|
||||||
const service = restana();
|
const service = restana();
|
||||||
const generalSettingsRouter = service.newRouter();
|
const generalSettingsRouter = service.newRouter();
|
||||||
generalSettingsRouter.get('/', async (req, res) => {
|
generalSettingsRouter.get('/', async (req, res) => {
|
||||||
@@ -10,9 +11,14 @@ generalSettingsRouter.get('/', async (req, res) => {
|
|||||||
generalSettingsRouter.post('/', async (req, res) => {
|
generalSettingsRouter.post('/', async (req, res) => {
|
||||||
const settings = req.body;
|
const settings = req.body;
|
||||||
try {
|
try {
|
||||||
|
if(config.demoMode){
|
||||||
|
res.send(new Error('In demo mode, it is not allowed to change these settings.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const currentConfig = await readConfigFromStorage();
|
const currentConfig = await readConfigFromStorage();
|
||||||
fs.writeFileSync(`${getDirName()}/../conf/config.json`, JSON.stringify({...currentConfig, ...settings}));
|
fs.writeFileSync(`${getDirName()}/../conf/config.json`, JSON.stringify({...currentConfig, ...settings}));
|
||||||
await refreshConfig();
|
await refreshConfig();
|
||||||
|
handleDemoUser();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
res.send(new Error('Error while trying to write settings.'));
|
res.send(new Error('Error while trying to write settings.'));
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import * as immoscoutProvider from '../../provider/immoscout.js';
|
|||||||
import { config } from '../../utils.js';
|
import { config } from '../../utils.js';
|
||||||
import { isAdmin } from '../security.js';
|
import { isAdmin } from '../security.js';
|
||||||
import {isScrapingAntApiKeySet} from '../../services/scrapingAnt.js';
|
import {isScrapingAntApiKeySet} from '../../services/scrapingAnt.js';
|
||||||
|
import {trackDemoJobCreated} from '../../services/tracking/Tracker.js';
|
||||||
const service = restana();
|
const service = restana();
|
||||||
const jobRouter = service.newRouter();
|
const jobRouter = service.newRouter();
|
||||||
function doesJobBelongsToUser(job, req) {
|
function doesJobBelongsToUser(job, req) {
|
||||||
@@ -68,6 +69,11 @@ jobRouter.post('/', async (req, res) => {
|
|||||||
res.send(new Error(error));
|
res.send(new Error(error));
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
trackDemoJobCreated({
|
||||||
|
name,
|
||||||
|
provider,
|
||||||
|
adapter: notificationAdapter
|
||||||
|
});
|
||||||
res.send();
|
res.send();
|
||||||
});
|
});
|
||||||
jobRouter.delete('', async (req, res) => {
|
jobRouter.delete('', async (req, res) => {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import restana from 'restana';
|
import restana from 'restana';
|
||||||
import * as userStorage from '../../services/storage/userStorage.js';
|
import * as userStorage from '../../services/storage/userStorage.js';
|
||||||
import * as hasher from '../../services/security/hash.js';
|
import * as hasher from '../../services/security/hash.js';
|
||||||
|
import {config} from '../../utils.js';
|
||||||
|
import {trackDemoAccessed} from '../../services/tracking/Tracker.js';
|
||||||
const service = restana();
|
const service = restana();
|
||||||
const loginRouter = service.newRouter();
|
const loginRouter = service.newRouter();
|
||||||
loginRouter.get('/user', async (req, res) => {
|
loginRouter.get('/user', async (req, res) => {
|
||||||
@@ -24,6 +26,11 @@ loginRouter.post('/', async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (user.password === hasher.hash(password)) {
|
if (user.password === hasher.hash(password)) {
|
||||||
|
|
||||||
|
if(config.demoMode){
|
||||||
|
trackDemoAccessed();
|
||||||
|
}
|
||||||
|
|
||||||
req.session.currentUser = user.id;
|
req.session.currentUser = user.id;
|
||||||
userStorage.setLastLoginToNow({ userId: user.id });
|
userStorage.setLastLoginToNow({ userId: user.id });
|
||||||
res.send(200);
|
res.send(200);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import restana from 'restana';
|
import restana from 'restana';
|
||||||
import * as userStorage from '../../services/storage/userStorage.js';
|
import * as userStorage from '../../services/storage/userStorage.js';
|
||||||
import * as jobStorage from '../../services/storage/jobStorage.js';
|
import * as jobStorage from '../../services/storage/jobStorage.js';
|
||||||
|
import {config} from '../../utils.js';
|
||||||
const service = restana();
|
const service = restana();
|
||||||
const userRouter = service.newRouter();
|
const userRouter = service.newRouter();
|
||||||
function checkIfAnyAdminAfterRemovingUser(userIdToBeRemoved, allUser) {
|
function checkIfAnyAdminAfterRemovingUser(userIdToBeRemoved, allUser) {
|
||||||
@@ -20,6 +21,11 @@ userRouter.get('/:userId', async (req, res) => {
|
|||||||
res.send();
|
res.send();
|
||||||
});
|
});
|
||||||
userRouter.delete('/', async (req, res) => {
|
userRouter.delete('/', async (req, res) => {
|
||||||
|
if(config.demoMode){
|
||||||
|
res.send(new Error('In demo mode, it is not allowed to remove user.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { userId } = req.body;
|
const { userId } = req.body;
|
||||||
const allUser = userStorage.getUsers(false);
|
const allUser = userStorage.getUsers(false);
|
||||||
if (!checkIfAnyAdminAfterRemovingUser(userId, allUser)) {
|
if (!checkIfAnyAdminAfterRemovingUser(userId, allUser)) {
|
||||||
@@ -36,6 +42,12 @@ userRouter.delete('/', async (req, res) => {
|
|||||||
res.send();
|
res.send();
|
||||||
});
|
});
|
||||||
userRouter.post('/', async (req, res) => {
|
userRouter.post('/', async (req, res) => {
|
||||||
|
|
||||||
|
if(config.demoMode){
|
||||||
|
res.send(new Error('In demo mode, it is not allowed to change or add user.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { username, password, password2, isAdmin, userId } = req.body;
|
const { username, password, password2, isAdmin, userId } = req.body;
|
||||||
if (password !== password2) {
|
if (password !== password2) {
|
||||||
res.send(new Error('Passwords does not match'));
|
res.send(new Error('Passwords does not match'));
|
||||||
|
|||||||
29
lib/services/demoCleanup.js
Normal file
29
lib/services/demoCleanup.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { setInterval } from 'node:timers';
|
||||||
|
import {removeJobsByUserName} from './storage/jobStorage.js';
|
||||||
|
import {config} from '../utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if we are running in demo environment, we have to cleanup the db files (specifically the jobs table)
|
||||||
|
*/
|
||||||
|
export function cleanupDemoAtMidnight() {
|
||||||
|
const now = new Date();
|
||||||
|
const millisUntilMidnightUTC = (24 - now.getUTCHours()) * 60 * 60 * 1000
|
||||||
|
- now.getUTCMinutes() * 60 * 1000
|
||||||
|
- now.getUTCSeconds() * 1000
|
||||||
|
- now.getUTCMilliseconds();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
cleanup();
|
||||||
|
}, 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
}, millisUntilMidnightUTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup(){
|
||||||
|
if(config.demoMode){
|
||||||
|
removeJobsByUserName('demo');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,6 +77,17 @@ export const removeJobsByUserId = (userId) => {
|
|||||||
.value();
|
.value();
|
||||||
db.write();
|
db.write();
|
||||||
};
|
};
|
||||||
|
export const removeJobsByUserName = (userName) => {
|
||||||
|
db.chain
|
||||||
|
.get('jobs')
|
||||||
|
.filter((job) => job.username === userName)
|
||||||
|
.forEach((job) => listingStorage.removeListings(job.id));
|
||||||
|
db.chain
|
||||||
|
.get('jobs')
|
||||||
|
.remove((job) => job.username === userName)
|
||||||
|
.value();
|
||||||
|
db.write();
|
||||||
|
};
|
||||||
export const getJobs = () => {
|
export const getJobs = () => {
|
||||||
return db.chain
|
return db.chain
|
||||||
.get('jobs')
|
.get('jobs')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { JSONFileSync } from 'lowdb/node';
|
import { JSONFileSync } from 'lowdb/node';
|
||||||
import { getDirName } from '../../utils.js';
|
import {config, getDirName} from '../../utils.js';
|
||||||
import * as hasher from '../security/hash.js';
|
import * as hasher from '../security/hash.js';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import * as jobStorage from './jobStorage.js';
|
import * as jobStorage from './jobStorage.js';
|
||||||
@@ -16,6 +16,13 @@ const defaultData = {
|
|||||||
password: hasher.hash('admin'),
|
password: hasher.hash('admin'),
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: nanoid(),
|
||||||
|
lastLogin: Date.now(),
|
||||||
|
username: 'demo',
|
||||||
|
password: hasher.hash('demo'),
|
||||||
|
isAdmin: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,3 +91,29 @@ export const removeUser = (userId) => {
|
|||||||
.value();
|
.value();
|
||||||
db.write();
|
db.write();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const handleDemoUser = () => {
|
||||||
|
if(!config.demoMode){
|
||||||
|
const user = db.chain.get('user').value();
|
||||||
|
db.chain.get('user').value();
|
||||||
|
db.chain.set('user', user.filter((u) => u.username !== 'demo')).value();
|
||||||
|
db.write();
|
||||||
|
}else {
|
||||||
|
const demoUser = db.chain
|
||||||
|
.get('user')
|
||||||
|
.filter((u) => u.username === 'demo')
|
||||||
|
.value();
|
||||||
|
if (demoUser == null || demoUser.length === 0) {
|
||||||
|
db.chain.get('user')
|
||||||
|
.value()
|
||||||
|
.push({
|
||||||
|
id: nanoid(),
|
||||||
|
username: 'demo',
|
||||||
|
password: hasher.hash('demo'),
|
||||||
|
isAdmin: true,
|
||||||
|
});
|
||||||
|
db.write();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
import Mixpanel from 'mixpanel';
|
import Mixpanel from 'mixpanel';
|
||||||
import {getJobs} from '../storage/jobStorage.js';
|
import {getJobs} from '../storage/jobStorage.js';
|
||||||
|
|
||||||
import {config} from '../../utils.js';
|
import {config, inDevMode} from '../../utils.js';
|
||||||
|
|
||||||
export const track = function () {
|
|
||||||
//only send tracking information if the user allowed to do so.
|
|
||||||
if (config.analyticsEnabled) {
|
|
||||||
|
|
||||||
const mixpanelTracker = Mixpanel.init('718670ef1c58c0208256c1e408a3d75e');
|
const mixpanelTracker = Mixpanel.init('718670ef1c58c0208256c1e408a3d75e');
|
||||||
|
|
||||||
|
export const track = function () {
|
||||||
|
//only send tracking information if the user allowed to do so.
|
||||||
|
if (config.analyticsEnabled && !inDevMode()) {
|
||||||
|
|
||||||
const activeProvider = new Set();
|
const activeProvider = new Set();
|
||||||
const activeAdapter = new Set();
|
const activeAdapter = new Set();
|
||||||
const platform = process.platform;
|
|
||||||
const arch = process.arch;
|
|
||||||
const language = process.env.LANG || 'en';
|
|
||||||
const nodeVersion = process.version || 'N/A';
|
|
||||||
|
|
||||||
const jobs = getJobs();
|
const jobs = getJobs();
|
||||||
|
|
||||||
@@ -28,15 +24,45 @@ export const track = function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
mixpanelTracker.track('fredy_tracking', {
|
mixpanelTracker.track('fredy_tracking', enrichTrackingObject({
|
||||||
adapter: Array.from(activeAdapter),
|
adapter: Array.from(activeAdapter),
|
||||||
provider: Array.from(activeProvider),
|
provider: Array.from(activeProvider),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note, this will only be used when Fredy runs in demo mode
|
||||||
|
*/
|
||||||
|
export function trackDemoJobCreated(jobData) {
|
||||||
|
if (config.analyticsEnabled && !inDevMode() && config.demoMode) {
|
||||||
|
mixpanelTracker.track('demoJobCreated', enrichTrackingObject(jobData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note, this will only be used when Fredy runs in demo mode
|
||||||
|
*/
|
||||||
|
export function trackDemoAccessed() {
|
||||||
|
if (config.analyticsEnabled && !inDevMode() && config.demoMode) {
|
||||||
|
mixpanelTracker.track('demoAccessed', enrichTrackingObject({}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function enrichTrackingObject(trackingObject) {
|
||||||
|
const platform = process.platform;
|
||||||
|
const arch = process.arch;
|
||||||
|
const language = process.env.LANG || 'en';
|
||||||
|
const nodeVersion = process.version || 'N/A';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...trackingObject,
|
||||||
isDemo: config.demoMode,
|
isDemo: config.demoMode,
|
||||||
platform,
|
platform,
|
||||||
arch,
|
arch,
|
||||||
nodeVersion,
|
nodeVersion,
|
||||||
language
|
language
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import {readFile} from 'fs/promises';
|
|||||||
import {createHash} from 'crypto';
|
import {createHash} from 'crypto';
|
||||||
import {DEFAULT_CONFIG} from './defaultConfig.js';
|
import {DEFAULT_CONFIG} from './defaultConfig.js';
|
||||||
|
|
||||||
|
function inDevMode(){
|
||||||
|
return process.env.NODE_ENV == null || process.env.NODE_ENV !== 'production';
|
||||||
|
}
|
||||||
|
|
||||||
function isOneOf(word, arr) {
|
function isOneOf(word, arr) {
|
||||||
if (arr == null || arr.length === 0) {
|
if (arr == null || arr.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -72,6 +76,7 @@ export async function refreshConfig(){
|
|||||||
await refreshConfig();
|
await refreshConfig();
|
||||||
|
|
||||||
export {isOneOf};
|
export {isOneOf};
|
||||||
|
export {inDevMode};
|
||||||
export {nullOrEmpty};
|
export {nullOrEmpty};
|
||||||
export {duringWorkingHoursOrNotSet};
|
export {duringWorkingHoursOrNotSet};
|
||||||
export {getDirName};
|
export {getDirName};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "fredy",
|
"name": "fredy",
|
||||||
"version": "10.3.0",
|
"version": "10.4.0",
|
||||||
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
|
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js",
|
"start": "node prod.js",
|
||||||
"dev": "yarn && rm -rf ./ui/public/* && vite",
|
"dev": "yarn && rm -rf ./ui/public/* && vite",
|
||||||
"ui": "rm -rf ./ui/public/* && vite",
|
"ui": "rm -rf ./ui/public/* && vite",
|
||||||
"prod": "yarn && vite build --emptyOutDir",
|
"prod": "yarn && vite build --emptyOutDir",
|
||||||
|
|||||||
2
prod.js
Normal file
2
prod.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
process.env.NODE_ENV = 'production';
|
||||||
|
import('./index.js');
|
||||||
@@ -18,6 +18,7 @@ import { Route } from 'react-router';
|
|||||||
|
|
||||||
import './App.less';
|
import './App.less';
|
||||||
import TrackingModal from './components/tracking/TrackingModal.jsx';
|
import TrackingModal from './components/tracking/TrackingModal.jsx';
|
||||||
|
import {Banner} from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
export default function FredyApp() {
|
export default function FredyApp() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -62,7 +63,18 @@ export default function FredyApp() {
|
|||||||
<Logout/>
|
<Logout/>
|
||||||
<Logo width={190} white/>
|
<Logo width={190} white/>
|
||||||
<Menu isAdmin={isAdmin()}/>
|
<Menu isAdmin={isAdmin()}/>
|
||||||
{settings.analyticsEnabled === null && <TrackingModal/>}
|
|
||||||
|
{settings.demoMode && (
|
||||||
|
<>
|
||||||
|
<Banner fullMode={true}
|
||||||
|
type="info"
|
||||||
|
bordered
|
||||||
|
closeIcon={null}
|
||||||
|
description="You're currently viewing the demo version of Fredy. Jobs won't scrape websites, and any changes you make will be reverted at midnight."
|
||||||
|
/>
|
||||||
|
<br/>
|
||||||
|
</>)}
|
||||||
|
{(settings.analyticsEnabled === null && !settings.demoMode) && <TrackingModal/>}
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route name="Insufficient Permission" path={'/403'} component={InsufficientPermission}/>
|
<Route name="Insufficient Permission" path={'/403'} component={InsufficientPermission}/>
|
||||||
<Route name="Create new Job" path={'/jobs/new'} component={JobMutation}/>
|
<Route name="Create new Job" path={'/jobs/new'} component={JobMutation}/>
|
||||||
|
|||||||
24
ui/src/services/rematch/models/demoMode.js
Normal file
24
ui/src/services/rematch/models/demoMode.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { xhrGet } from '../../xhr';
|
||||||
|
export const demoMode = {
|
||||||
|
state: {
|
||||||
|
demoMode: false,
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
setDemoMode: (state, payload) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
demoMode: payload.demoMode,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
async getDemoMode() {
|
||||||
|
try {
|
||||||
|
const response = await xhrGet('/api/demo');
|
||||||
|
this.setDemoMode(response.json);
|
||||||
|
} catch (Exception) {
|
||||||
|
console.error('Error while trying to get resource for api/demo. Error:', Exception);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import { provider } from './models/provider';
|
|||||||
import { createLogger } from 'redux-logger';
|
import { createLogger } from 'redux-logger';
|
||||||
import { jobs } from './models/jobs';
|
import { jobs } from './models/jobs';
|
||||||
import { user } from './models/user';
|
import { user } from './models/user';
|
||||||
|
import { demoMode } from './models/demoMode.js';
|
||||||
import { init } from '@rematch/core';
|
import { init } from '@rematch/core';
|
||||||
const middleware = [];
|
const middleware = [];
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
@@ -16,6 +17,7 @@ const store = init({
|
|||||||
models: {
|
models: {
|
||||||
notificationAdapter,
|
notificationAdapter,
|
||||||
generalSettings,
|
generalSettings,
|
||||||
|
demoMode,
|
||||||
provider,
|
provider,
|
||||||
jobs,
|
jobs,
|
||||||
user,
|
user,
|
||||||
|
|||||||
@@ -109,10 +109,17 @@ const GeneralSettings = function GeneralSettings() {
|
|||||||
});
|
});
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
console.error(exception);
|
console.error(exception);
|
||||||
|
if(exception?.json?.message != null){
|
||||||
|
throwMessage(exception.json.message, 'error');
|
||||||
|
}else {
|
||||||
throwMessage('Error while trying to store settings.', 'error');
|
throwMessage('Error while trying to store settings.', 'error');
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throwMessage('Settings stored successfully.', 'success');
|
throwMessage('Settings stored successfully. We will reload your browser in 3 seconds.', 'success');
|
||||||
|
setTimeout(()=>{
|
||||||
|
location.reload();
|
||||||
|
}, 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -120,14 +127,6 @@ const GeneralSettings = function GeneralSettings() {
|
|||||||
{!loading && (
|
{!loading && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Headline text="General Settings"/>
|
<Headline text="General Settings"/>
|
||||||
<Banner
|
|
||||||
fullMode={false}
|
|
||||||
type="info"
|
|
||||||
closeIcon={null}
|
|
||||||
title={<div style={{fontWeight: 600, fontSize: '14px', lineHeight: '20px'}}>Info</div>}
|
|
||||||
style={{marginBottom: '1rem'}}
|
|
||||||
description="If you change any settings, you must restart Fredy afterwards."
|
|
||||||
/>
|
|
||||||
<div>
|
<div>
|
||||||
<SegmentPart
|
<SegmentPart
|
||||||
name="Interval"
|
name="Interval"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React, {useEffect} from 'react';
|
||||||
|
|
||||||
import cityBackground from '../../assets/city_background.jpg';
|
import cityBackground from '../../assets/city_background.jpg';
|
||||||
import Logo from '../../components/logo/Logo';
|
import Logo from '../../components/logo/Logo';
|
||||||
import {xhrPost} from '../../services/xhr';
|
import {xhrPost} from '../../services/xhr';
|
||||||
import {useHistory} from 'react-router';
|
import {useHistory} from 'react-router';
|
||||||
import { useDispatch } from 'react-redux';
|
import {useDispatch, useSelector} from 'react-redux';
|
||||||
import {Input, Button, Banner} from '@douyinfe/semi-ui';
|
import {Input, Button, Banner} from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import './login.less';
|
import './login.less';
|
||||||
@@ -12,13 +12,20 @@ import { IconUser, IconLock } from '@douyinfe/semi-icons';
|
|||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [username, setUserName] = React.useState('');
|
const [username, setUserName] = React.useState('');
|
||||||
const [password, setPassword] = React.useState('');
|
const [password, setPassword] = React.useState('');
|
||||||
const [error, setError] = React.useState(null);
|
const [error, setError] = React.useState(null);
|
||||||
|
const demoMode = useSelector((state) => state.demoMode.demoMode || false);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function init() {
|
||||||
|
await dispatch.demoMode.getDemoMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const tryLogin = async () => {
|
const tryLogin = async () => {
|
||||||
if (username.length === 0 || password.length === 0) {
|
if (username.length === 0 || password.length === 0) {
|
||||||
setError('Username and password are mandatory.');
|
setError('Username and password are mandatory.');
|
||||||
@@ -52,7 +59,7 @@ export default function Login() {
|
|||||||
value={username}
|
value={username}
|
||||||
showClear
|
showClear
|
||||||
style={{marginTop: error ? '1rem' : '4rem'}}
|
style={{marginTop: error ? '1rem' : '4rem'}}
|
||||||
autofocus
|
autoFocus
|
||||||
onChange={(value) => setUserName(value)}
|
onChange={(value) => setUserName(value)}
|
||||||
onKeyPress={async (e) => {
|
onKeyPress={async (e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
@@ -79,6 +86,13 @@ export default function Login() {
|
|||||||
<Button type="primary" onClick={tryLogin} theme="solid" style={{marginTop: '3rem'}}>
|
<Button type="primary" onClick={tryLogin} theme="solid" style={{marginTop: '3rem'}}>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
|
<br/>
|
||||||
|
{demoMode && <Banner fullMode={true}
|
||||||
|
type="info"
|
||||||
|
bordered
|
||||||
|
closeIcon={null}
|
||||||
|
description="This is the demo version of Fredy. Use 'demo' as both the username and password to log in."
|
||||||
|
/>}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user