Read/write css file from app settings. Changed order of operations at app startup. Added nano to Dockerfile

This commit is contained in:
unknown 2021-06-22 13:07:32 +02:00
parent 4c3255107c
commit 5ae4d6e7c4
15 changed files with 161 additions and 35 deletions

View file

@ -1,5 +1,7 @@
FROM node:14-alpine
RUN apk update && apk add --no-cache nano
WORKDIR /app
COPY package*.json ./

View file

@ -1,5 +1,7 @@
FROM node:14-alpine
RUN apk update && apk add --no-cache nano
WORKDIR /app
COPY package*.json ./

View file

@ -1 +1 @@
REACT_APP_VERSION=1.4.0
REACT_APP_VERSION=1.4.1

0
client/public/flame.css Normal file
View file

View file

@ -4,15 +4,10 @@
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<meta name="description" content="Flame - self-hosted startpage for your server" />
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700,900" rel="stylesheet">
<link rel="stylesheet" href="%PUBLIC_URL%/flame.css">
<title>Flame</title>
</head>
<body>
@ -21,4 +16,4 @@
<div id="root"></div>
</body>
</html>
</html>

View file

@ -1,3 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
Disallow: /

View file

@ -9,6 +9,7 @@ import Themer from '../Themer/Themer';
import WeatherSettings from './WeatherSettings/WeatherSettings';
import OtherSettings from './OtherSettings/OtherSettings';
import AppDetails from './AppDetails/AppDetails';
import StyleSettings from './StyleSettings/StyleSettings';
const Settings = (): JSX.Element => {
return (
@ -40,6 +41,13 @@ const Settings = (): JSX.Element => {
to='/settings/other'>
Other
</NavLink>
<NavLink
className={classes.SettingsNavLink}
activeClassName={classes.SettingsNavLinkActive}
exact
to='/settings/css'>
CSS
</NavLink>
<NavLink
className={classes.SettingsNavLink}
activeClassName={classes.SettingsNavLinkActive}
@ -53,6 +61,7 @@ const Settings = (): JSX.Element => {
<Route exact path='/settings' component={Themer} />
<Route path='/settings/weather' component={WeatherSettings} />
<Route path='/settings/other' component={OtherSettings} />
<Route path='/settings/css' component={StyleSettings} />
<Route path='/settings/app' component={AppDetails} />
</Switch>
</section>

View file

@ -0,0 +1,44 @@
import { useState, useEffect, ChangeEvent, FormEvent } from 'react';
import axios from 'axios';
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
import Button from '../../UI/Buttons/Button/Button';
import { ApiResponse } from '../../../interfaces';
const StyleSettings = (): JSX.Element => {
const [customStyles, setCustomStyles] = useState<string>('');
useEffect(() => {
axios.get<ApiResponse<string>>('/api/config/0/css')
.then(data => setCustomStyles(data.data.data))
.catch(err => console.log(err));
}, [])
const inputChangeHandler = (e: ChangeEvent<HTMLTextAreaElement>) => {
e.preventDefault();
setCustomStyles(e.target.value);
}
const formSubmitHandler = (e: FormEvent) => {
e.preventDefault();
axios.put<ApiResponse<{}>>('/api/config/0/css', { styles: customStyles });
}
return (
<form onSubmit={(e) => formSubmitHandler(e)}>
<InputGroup>
<label htmlFor='customStyles'>Custom CSS</label>
<textarea
id='customStyles'
name='customStyles'
value={customStyles}
onChange={(e) => inputChangeHandler(e)}
></textarea>
</InputGroup>
<Button>Save CSS</Button>
</form>
)
}
export default StyleSettings;

View file

@ -4,12 +4,14 @@
.InputGroup label,
.InputGroup span,
.InputGroup input {
.InputGroup input,
.InputGroup textarea {
display: block;
}
.InputGroup input,
.InputGroup select {
.InputGroup select,
.InputGroup textarea {
margin: 8px 0;
width: 100%;
border: none;
@ -30,4 +32,9 @@
.InputGroup label {
color: var(--color-primary);
}
.InputGroup textarea {
resize: none;
height: 50vh;
}

View file

@ -2,6 +2,8 @@ const asyncWrapper = require('../middleware/asyncWrapper');
const ErrorResponse = require('../utils/ErrorResponse');
const Config = require('../models/Config');
const { Op } = require('sequelize');
const File = require('../utils/File');
const { join } = require('path');
// @desc Insert new key:value pair
// @route POST /api/config
@ -122,6 +124,33 @@ exports.deletePair = asyncWrapper(async (req, res, next) => {
await pair.destroy();
res.status(200).json({
success: true,
data: {}
})
})
// @desc Get custom CSS file
// @route GET /api/config/0/css
// @access Public
exports.getCss = asyncWrapper(async (req, res, next) => {
const file = new File(join(__dirname, '../public/flame.css'));
const content = file.read();
res.status(200).json({
success: true,
data: content
})
})
// @desc Update custom CSS file
// @route PUT /api/config/0/css
// @access Public
exports.updateCss = asyncWrapper(async (req, res, next) => {
const file = new File(join(__dirname, '../public/flame.css'));
file.write(req.body.styles);
res.status(200).json({
success: true,
data: {}

15
db.js
View file

@ -4,21 +4,26 @@ const sequelize = new Sequelize({
dialect: 'sqlite',
storage: './data/db.sqlite',
logging: false
});
})
const connectDB = async () => {
try {
await sequelize.authenticate();
console.log('Connected to database');
await sequelize.sync({ alter: true });
console.log('All models were synced');
const syncModels = true;
if (syncModels) {
console.log('Starting model synchronization');
await sequelize.sync({ alter: true });
console.log('All models were synchronized');
}
} catch (error) {
console.error('Unable to connect to the database:', error);
throw new Error(`Unable to connect to the database: ${error.message}`);
}
}
module.exports = {
connectDB,
sequelize
};
}

View file

@ -2,12 +2,14 @@ const Category = require('./Category');
const Bookmark = require('./Bookmark');
const associateModels = () => {
// Category <> Bookmark
Category.hasMany(Bookmark, {
as: 'bookmarks',
foreignKey: 'categoryId',
as: 'bookmarks'
});
Bookmark.belongsTo(Category, {
foreignKey: 'categoryId'
});
Bookmark.belongsTo(Category, { foreignKey: 'categoryId' });
}
module.exports = associateModels;

View file

@ -8,6 +8,8 @@ const {
updateValue,
updateValues,
deletePair,
updateCss,
getCss,
} = require('../controllers/config');
router
@ -22,4 +24,9 @@ router
.put(updateValue)
.delete(deletePair);
router
.route('/0/css')
.get(getCss)
.put(updateCss);
module.exports = router;

View file

@ -10,20 +10,20 @@ const initConfig = require('./utils/initConfig');
const PORT = process.env.PORT || 5005;
connectDB()
.then(() => {
associateModels();
initConfig();
});
(async () => {
await connectDB();
await associateModels();
await initConfig();
// Create server for Express API and WebSockets
const server = http.createServer();
server.on('request', api);
// Create server for Express API and WebSockets
const server = http.createServer();
server.on('request', api);
// Register weatherSocket
const weatherSocket = new Socket(server);
Sockets.registerSocket('weather', weatherSocket);
// Register weatherSocket
const weatherSocket = new Socket(server);
Sockets.registerSocket('weather', weatherSocket);
server.listen(PORT, () => {
console.log(`Server is running on port ${PORT} in ${process.env.NODE_ENV} mode`);
})
server.listen(PORT, () => {
console.log(`Server is running on port ${PORT} in ${process.env.NODE_ENV} mode`);
})
})();

25
utils/File.js Normal file
View file

@ -0,0 +1,25 @@
const fs = require('fs');
class File {
constructor(path) {
this.path = path;
this.content = '';
}
read() {
try {
const content = fs.readFileSync(this.path, { encoding: 'utf-8' });
this.content = content;
return this.content;
} catch (err) {
return err.message;
}
}
write(data) {
this.content = data;
fs.writeFileSync(this.path, this.content);
}
}
module.exports = File;