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 FROM node:14-alpine
RUN apk update && apk add --no-cache nano
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./

View File

@ -1,5 +1,7 @@
FROM node:14-alpine FROM node:14-alpine
RUN apk update && apk add --no-cache nano
WORKDIR /app WORKDIR /app
COPY package*.json ./ 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" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="description" content="Flame - self-hosted startpage for your server" />
<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" />
<link rel="preconnect" href="https://fonts.gstatic.com"> <link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700,900" rel="stylesheet"> <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> <title>Flame</title>
</head> </head>
<body> <body>
@ -21,4 +16,4 @@
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>

View File

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

View File

@ -9,6 +9,7 @@ import Themer from '../Themer/Themer';
import WeatherSettings from './WeatherSettings/WeatherSettings'; import WeatherSettings from './WeatherSettings/WeatherSettings';
import OtherSettings from './OtherSettings/OtherSettings'; import OtherSettings from './OtherSettings/OtherSettings';
import AppDetails from './AppDetails/AppDetails'; import AppDetails from './AppDetails/AppDetails';
import StyleSettings from './StyleSettings/StyleSettings';
const Settings = (): JSX.Element => { const Settings = (): JSX.Element => {
return ( return (
@ -40,6 +41,13 @@ const Settings = (): JSX.Element => {
to='/settings/other'> to='/settings/other'>
Other Other
</NavLink> </NavLink>
<NavLink
className={classes.SettingsNavLink}
activeClassName={classes.SettingsNavLinkActive}
exact
to='/settings/css'>
CSS
</NavLink>
<NavLink <NavLink
className={classes.SettingsNavLink} className={classes.SettingsNavLink}
activeClassName={classes.SettingsNavLinkActive} activeClassName={classes.SettingsNavLinkActive}
@ -53,6 +61,7 @@ const Settings = (): JSX.Element => {
<Route exact path='/settings' component={Themer} /> <Route exact path='/settings' component={Themer} />
<Route path='/settings/weather' component={WeatherSettings} /> <Route path='/settings/weather' component={WeatherSettings} />
<Route path='/settings/other' component={OtherSettings} /> <Route path='/settings/other' component={OtherSettings} />
<Route path='/settings/css' component={StyleSettings} />
<Route path='/settings/app' component={AppDetails} /> <Route path='/settings/app' component={AppDetails} />
</Switch> </Switch>
</section> </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 label,
.InputGroup span, .InputGroup span,
.InputGroup input { .InputGroup input,
.InputGroup textarea {
display: block; display: block;
} }
.InputGroup input, .InputGroup input,
.InputGroup select { .InputGroup select,
.InputGroup textarea {
margin: 8px 0; margin: 8px 0;
width: 100%; width: 100%;
border: none; border: none;
@ -30,4 +32,9 @@
.InputGroup label { .InputGroup label {
color: var(--color-primary); 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 ErrorResponse = require('../utils/ErrorResponse');
const Config = require('../models/Config'); const Config = require('../models/Config');
const { Op } = require('sequelize'); const { Op } = require('sequelize');
const File = require('../utils/File');
const { join } = require('path');
// @desc Insert new key:value pair // @desc Insert new key:value pair
// @route POST /api/config // @route POST /api/config
@ -122,6 +124,33 @@ exports.deletePair = asyncWrapper(async (req, res, next) => {
await pair.destroy(); 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({ res.status(200).json({
success: true, success: true,
data: {} data: {}

15
db.js
View File

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

View File

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

View File

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

View File

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