Read/write css file from app settings. Changed order of operations at app startup. Added nano to Dockerfile
This commit is contained in:
parent
4c3255107c
commit
5ae4d6e7c4
15 changed files with 161 additions and 35 deletions
|
@ -1,5 +1,7 @@
|
|||
FROM node:14-alpine
|
||||
|
||||
RUN apk update && apk add --no-cache nano
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
FROM node:14-alpine
|
||||
|
||||
RUN apk update && apk add --no-cache nano
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
|
|
@ -1 +1 @@
|
|||
REACT_APP_VERSION=1.4.0
|
||||
REACT_APP_VERSION=1.4.1
|
0
client/public/flame.css
Normal file
0
client/public/flame.css
Normal 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>
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
Disallow: /
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
|
@ -31,3 +33,8 @@
|
|||
.InputGroup label {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.InputGroup textarea {
|
||||
resize: none;
|
||||
height: 50vh;
|
||||
}
|
|
@ -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
|
||||
|
@ -127,3 +129,30 @@ exports.deletePair = asyncWrapper(async (req, res, next) => {
|
|||
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: {}
|
||||
})
|
||||
})
|
13
db.js
13
db.js
|
@ -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');
|
||||
|
||||
const syncModels = true;
|
||||
|
||||
if (syncModels) {
|
||||
console.log('Starting model synchronization');
|
||||
await sequelize.sync({ alter: true });
|
||||
console.log('All models were synced');
|
||||
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
|
||||
};
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
10
server.js
10
server.js
|
@ -10,11 +10,10 @@ 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();
|
||||
|
@ -27,3 +26,4 @@ Sockets.registerSocket('weather', weatherSocket);
|
|||
server.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT} in ${process.env.NODE_ENV} mode`);
|
||||
})
|
||||
})();
|
25
utils/File.js
Normal file
25
utils/File.js
Normal 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;
|
Loading…
Reference in a new issue