Merge pull request #206 from pawelmalak/feature

Version 2.0.1
This commit is contained in:
pawelmalak 2021-11-19 15:02:58 +01:00 committed by GitHub
commit fd7d8e65c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 480 additions and 232 deletions

2
.env
View file

@ -1,5 +1,5 @@
PORT=5005
NODE_ENV=development
VERSION=2.0.0
VERSION=2.0.1
PASSWORD=flame_password
SECRET=e02eb43d69953658c6d07311d6313f2d4467672cb881f96b29368ba1f3f4da4b

View file

@ -1,3 +1,11 @@
### v2.0.1 (2021-11-19)
- Added option to display humidity in the weather widget ([#136](https://github.com/pawelmalak/flame/issues/136))
- Added option to set default theme for all new users ([#165](https://github.com/pawelmalak/flame/issues/165))
- Added option to hide header greetings and date separately ([#200](https://github.com/pawelmalak/flame/issues/200))
- Fixed bug with broken basic auth ([#202](https://github.com/pawelmalak/flame/issues/202))
- Fixed bug with parsing visibility value for apps and bookmarks when custom icon was used ([#203](https://github.com/pawelmalak/flame/issues/203))
- Fixed bug with custom icons not working with apps when "pin by default" was disabled
### v2.0.0 (2021-11-15)
- Added authentication system:
- Only logged in user can access settings ([#33](https://github.com/pawelmalak/flame/issues/33))

View file

@ -11,7 +11,7 @@ Flame is self-hosted startpage for your server. Its design is inspired (heavily)
- 📌 Pin your favourite items to the homescreen for quick and easy access
- 🔍 Integrated search bar with local filtering, 11 web search providers and ability to add your own
- 🔑 Authentication system to protect your settings, apps and bookmarks
- 🔨 Dozens of option to customize Flame interface to your needs, including support for custom CSS and 15 built-in color themes
- 🔨 Dozens of options to customize Flame interface to your needs, including support for custom CSS and 15 built-in color themes
- ☀️ Weather widget with current temperature, cloud coverage and animated weather status
- 🐳 Docker integration to automatically pick and add apps based on their labels

View file

@ -1 +1 @@
REACT_APP_VERSION=2.0.0
REACT_APP_VERSION=2.0.1

View file

@ -1,7 +1,13 @@
import { useEffect } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import 'external-svg-loader';
// Redux
import { useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { autoLogin, getConfig } from './store/action-creators';
import { actionCreators, store } from './store';
import 'external-svg-loader';
import { State } from './store/reducers';
// Utils
import { checkVersion, decodeToken } from './utility';
@ -12,9 +18,6 @@ import { Apps } from './components/Apps/Apps';
import { Settings } from './components/Settings/Settings';
import { Bookmarks } from './components/Bookmarks/Bookmarks';
import { NotificationCenter } from './components/NotificationCenter/NotificationCenter';
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { useEffect } from 'react';
// Get config
store.dispatch<any>(getConfig());
@ -25,6 +28,8 @@ if (localStorage.token) {
}
export const App = (): JSX.Element => {
const { config, loading } = useSelector((state: State) => state.config);
const dispath = useDispatch();
const { fetchQueries, setTheme, logout, createNotification } =
bindActionCreators(actionCreators, dispath);
@ -46,7 +51,7 @@ export const App = (): JSX.Element => {
}
}, 1000);
// set theme
// set user theme if present
if (localStorage.theme) {
setTheme(localStorage.theme);
}
@ -60,6 +65,13 @@ export const App = (): JSX.Element => {
return () => window.clearInterval(tokenIsValid);
}, []);
// If there is no user theme, set the default one
useEffect(() => {
if (!loading && !localStorage.theme) {
setTheme(config.defaultTheme, false);
}
}, [loading]);
return (
<>
<BrowserRouter>

View file

@ -61,7 +61,7 @@ export const AppForm = ({ app, modalHandler }: Props): JSX.Element => {
}
data.append('name', formData.name);
data.append('url', formData.url);
data.append('isPublic', `${formData.isPublic}`);
data.append('isPublic', `${formData.isPublic ? 1 : 0}`);
return data;
};

View file

@ -77,7 +77,7 @@ export const BookmarksForm = ({
data.append('name', formData.name);
data.append('url', formData.url);
data.append('categoryId', `${formData.categoryId}`);
data.append('isPublic', `${formData.isPublic}`);
data.append('isPublic', `${formData.isPublic ? 1 : 0}`);
return data;
};

View file

@ -1,6 +1,10 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
// Redux
import { useSelector } from 'react-redux';
import { State } from '../../../store/reducers';
// CSS
import classes from './Header.module.css';
@ -12,6 +16,10 @@ import { getDateTime } from './functions/getDateTime';
import { greeter } from './functions/greeter';
export const Header = (): JSX.Element => {
const { hideHeader, hideDate, showTime } = useSelector(
(state: State) => state.config.config
);
const [dateTime, setDateTime] = useState<string>(getDateTime());
const [greeting, setGreeting] = useState<string>(greeter());
@ -28,14 +36,18 @@ export const Header = (): JSX.Element => {
return (
<header className={classes.Header}>
<p>{dateTime}</p>
{(!hideDate || showTime) && <p>{dateTime}</p>}
<Link to="/settings" className={classes.SettingsLink}>
Go to Settings
</Link>
<span className={classes.HeaderMain}>
<h1>{greeting}</h1>
<WeatherWidget />
</span>
{!hideHeader && (
<span className={classes.HeaderMain}>
<h1>{greeting}</h1>
<WeatherWidget />
</span>
)}
</header>
);
};

View file

@ -30,22 +30,42 @@ export const getDateTime = (): string => {
const useAmericanDate = localStorage.useAmericanDate === 'true';
const showTime = localStorage.showTime === 'true';
const hideDate = localStorage.hideDate === 'true';
const p = parseTime;
// Date
let dateEl = '';
const time = `${p(now.getHours())}:${p(now.getMinutes())}:${p(
now.getSeconds()
)}`;
const timeEl = showTime ? ` - ${time}` : '';
if (!useAmericanDate) {
return `${days[now.getDay()]}, ${now.getDate()} ${
months[now.getMonth()]
} ${now.getFullYear()}${timeEl}`;
} else {
return `${days[now.getDay()]}, ${
months[now.getMonth()]
} ${now.getDate()} ${now.getFullYear()}${timeEl}`;
if (!hideDate) {
if (!useAmericanDate) {
dateEl = `${days[now.getDay()]}, ${now.getDate()} ${
months[now.getMonth()]
} ${now.getFullYear()}`;
} else {
dateEl = `${days[now.getDay()]}, ${
months[now.getMonth()]
} ${now.getDate()} ${now.getFullYear()}`;
}
}
// Time
const p = parseTime;
let timeEl = '';
if (showTime) {
const time = `${p(now.getHours())}:${p(now.getMinutes())}:${p(
now.getSeconds()
)}`;
timeEl = time;
}
// Separator
let separator = '';
if (!hideDate && showTime) {
separator = ' - ';
}
// Output
return `${dateEl}${separator}${timeEl}`;
};

View file

@ -98,7 +98,7 @@ export const Home = (): JSX.Element => {
<div></div>
)}
{!config.hideHeader ? <Header /> : <div></div>}
<Header />
{!config.hideApps ? (
<Fragment>

View file

@ -59,7 +59,7 @@ export const DockerSettings = (): JSX.Element => {
<SettingsHeadline text="Docker" />
{/* CUSTOM DOCKER SOCKET HOST */}
<InputGroup>
<label htmlFor="dockerHost">Docker Host</label>
<label htmlFor="dockerHost">Docker host</label>
<input
type="text"
id="dockerHost"

View file

@ -70,7 +70,7 @@ export const SearchSettings = (): JSX.Element => {
>
<SettingsHeadline text="General" />
<InputGroup>
<label htmlFor="defaultSearchProvider">Default Search Provider</label>
<label htmlFor="defaultSearchProvider">Default search provider</label>
<select
id="defaultSearchProvider"
name="defaultSearchProvider"

View file

@ -11,7 +11,7 @@ import { Route as SettingsRoute } from '../../interfaces';
import classes from './Settings.module.css';
// Components
import { Themer } from '../Themer/Themer';
import { Themer } from './Themer/Themer';
import { WeatherSettings } from './WeatherSettings/WeatherSettings';
import { UISettings } from './UISettings/UISettings';
import { AppDetails } from './AppDetails/AppDetails';

View file

@ -14,7 +14,6 @@
text-transform: capitalize;
margin: 8px 0;
color: var(--color-primary);
/* align-self: flex-start; */
}
.ColorsPreview {
@ -32,4 +31,4 @@
width: 40px;
height: 40px;
}
}
}

View file

@ -1,4 +1,4 @@
import { Theme } from '../../interfaces/Theme';
import { Theme } from '../../../interfaces/Theme';
import classes from './ThemePreview.module.css';
interface Props {

View file

@ -0,0 +1,101 @@
import { ChangeEvent, FormEvent, Fragment, useEffect, useState } from 'react';
// Redux
import { useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../../store';
// Typescript
import { Theme, ThemeSettingsForm } from '../../../interfaces';
// Components
import { ThemePreview } from './ThemePreview';
import { Button, InputGroup, SettingsHeadline } from '../../UI';
// Other
import classes from './Themer.module.css';
import { themes } from './themes.json';
import { State } from '../../../store/reducers';
import { inputHandler, themeSettingsTemplate } from '../../../utility';
export const Themer = (): JSX.Element => {
const {
auth: { isAuthenticated },
config: { loading, config },
} = useSelector((state: State) => state);
const dispatch = useDispatch();
const { setTheme, updateConfig } = bindActionCreators(
actionCreators,
dispatch
);
// Initial state
const [formData, setFormData] = useState<ThemeSettingsForm>(
themeSettingsTemplate
);
// Get config
useEffect(() => {
setFormData({
...config,
});
}, [loading]);
// Form handler
const formSubmitHandler = async (e: FormEvent) => {
e.preventDefault();
// Save settings
await updateConfig(formData);
};
// Input handler
const inputChangeHandler = (
e: ChangeEvent<HTMLInputElement | HTMLSelectElement>,
options?: { isNumber?: boolean; isBool?: boolean }
) => {
inputHandler<ThemeSettingsForm>({
e,
options,
setStateHandler: setFormData,
state: formData,
});
};
return (
<Fragment>
<SettingsHeadline text="Set theme" />
<div className={classes.ThemerGrid}>
{themes.map(
(theme: Theme, idx: number): JSX.Element => (
<ThemePreview key={idx} theme={theme} applyTheme={setTheme} />
)
)}
</div>
{isAuthenticated && (
<form onSubmit={formSubmitHandler}>
<SettingsHeadline text="Other settings" />
<InputGroup>
<label htmlFor="defaultTheme">Default theme (for new users)</label>
<select
id="defaultTheme"
name="defaultTheme"
value={formData.defaultTheme}
onChange={(e) => inputChangeHandler(e)}
>
{themes.map((theme: Theme, idx) => (
<option key={idx} value={theme.name}>
{theme.name}
</option>
))}
</select>
</InputGroup>
<Button>Save changes</Button>
</form>
)}
</Fragment>
);
};

View file

@ -66,7 +66,7 @@ export const UISettings = (): JSX.Element => {
return (
<form onSubmit={(e) => formSubmitHandler(e)}>
{/* OTHER OPTIONS */}
{/* === OTHER OPTIONS === */}
<SettingsHeadline text="Miscellaneous" />
{/* PAGE TITLE */}
<InputGroup>
@ -81,6 +81,50 @@ export const UISettings = (): JSX.Element => {
/>
</InputGroup>
{/* === HEADER OPTIONS === */}
<SettingsHeadline text="Header" />
{/* HIDE HEADER */}
<InputGroup>
<label htmlFor="hideHeader">Hide greetings</label>
<select
id="hideHeader"
name="hideHeader"
value={formData.hideHeader ? 1 : 0}
onChange={(e) => inputChangeHandler(e, { isBool: true })}
>
<option value={1}>True</option>
<option value={0}>False</option>
</select>
</InputGroup>
{/* HIDE DATE */}
<InputGroup>
<label htmlFor="hideDate">Hide date</label>
<select
id="hideDate"
name="hideDate"
value={formData.hideDate ? 1 : 0}
onChange={(e) => inputChangeHandler(e, { isBool: true })}
>
<option value={1}>True</option>
<option value={0}>False</option>
</select>
</InputGroup>
{/* HIDE TIME */}
<InputGroup>
<label htmlFor="showTime">Hide time</label>
<select
id="showTime"
name="showTime"
value={formData.showTime ? 1 : 0}
onChange={(e) => inputChangeHandler(e, { isBool: true })}
>
<option value={0}>True</option>
<option value={1}>False</option>
</select>
</InputGroup>
{/* DATE FORMAT */}
<InputGroup>
<label htmlFor="useAmericanDate">Date formatting</label>
@ -95,7 +139,52 @@ export const UISettings = (): JSX.Element => {
</select>
</InputGroup>
{/* BEAHVIOR OPTIONS */}
{/* CUSTOM GREETINGS */}
<InputGroup>
<label htmlFor="greetingsSchema">Custom greetings</label>
<input
type="text"
id="greetingsSchema"
name="greetingsSchema"
placeholder="Good day;Hi;Bye!"
value={formData.greetingsSchema}
onChange={(e) => inputChangeHandler(e)}
/>
<span>
Greetings must be separated with semicolon. Only 4 messages can be
used
</span>
</InputGroup>
{/* CUSTOM DAYS */}
<InputGroup>
<label htmlFor="daySchema">Custom weekday names</label>
<input
type="text"
id="daySchema"
name="daySchema"
placeholder="Sunday;Monday;Tuesday"
value={formData.daySchema}
onChange={(e) => inputChangeHandler(e)}
/>
<span>Names must be separated with semicolon</span>
</InputGroup>
{/* CUSTOM MONTHS */}
<InputGroup>
<label htmlFor="monthSchema">Custom month names</label>
<input
type="text"
id="monthSchema"
name="monthSchema"
placeholder="January;February;March"
value={formData.monthSchema}
onChange={(e) => inputChangeHandler(e)}
/>
<span>Names must be separated with semicolon</span>
</InputGroup>
{/* === BEAHVIOR OPTIONS === */}
<SettingsHeadline text="App Behavior" />
{/* PIN APPS */}
<InputGroup>
@ -172,82 +261,7 @@ export const UISettings = (): JSX.Element => {
</select>
</InputGroup>
{/* HEADER OPTIONS */}
<SettingsHeadline text="Header" />
{/* HIDE HEADER */}
<InputGroup>
<label htmlFor="hideHeader">Hide greeting and date</label>
<select
id="hideHeader"
name="hideHeader"
value={formData.hideHeader ? 1 : 0}
onChange={(e) => inputChangeHandler(e, { isBool: true })}
>
<option value={1}>True</option>
<option value={0}>False</option>
</select>
</InputGroup>
{/* CUSTOM GREETINGS */}
<InputGroup>
<label htmlFor="greetingsSchema">Custom greetings</label>
<input
type="text"
id="greetingsSchema"
name="greetingsSchema"
placeholder="Good day;Hi;Bye!"
value={formData.greetingsSchema}
onChange={(e) => inputChangeHandler(e)}
/>
<span>
Greetings must be separated with semicolon. Only 4 messages can be
used
</span>
</InputGroup>
{/* CUSTOM DAYS */}
<InputGroup>
<label htmlFor="daySchema">Custom weekday names</label>
<input
type="text"
id="daySchema"
name="daySchema"
placeholder="Sunday;Monday;Tuesday"
value={formData.daySchema}
onChange={(e) => inputChangeHandler(e)}
/>
<span>Names must be separated with semicolon</span>
</InputGroup>
{/* CUSTOM MONTHS */}
<InputGroup>
<label htmlFor="monthSchema">Custom month names</label>
<input
type="text"
id="monthSchema"
name="monthSchema"
placeholder="January;February;March"
value={formData.monthSchema}
onChange={(e) => inputChangeHandler(e)}
/>
<span>Names must be separated with semicolon</span>
</InputGroup>
{/* SHOW TIME */}
<InputGroup>
<label htmlFor="showTime">Show time</label>
<select
id="showTime"
name="showTime"
value={formData.showTime ? 1 : 0}
onChange={(e) => inputChangeHandler(e, { isBool: true })}
>
<option value={1}>True</option>
<option value={0}>False</option>
</select>
</InputGroup>
{/* MODULES OPTIONS */}
{/* === MODULES OPTIONS === */}
<SettingsHeadline text="Modules" />
{/* HIDE APPS */}
<InputGroup>

View file

@ -11,7 +11,7 @@ import { State } from '../../../store/reducers';
import { ApiResponse, Weather, WeatherForm } from '../../../interfaces';
// UI
import { InputGroup, Button } from '../../UI';
import { InputGroup, Button, SettingsHeadline } from '../../UI';
// Utils
import { inputHandler, weatherSettingsTemplate } from '../../../utility';
@ -84,6 +84,8 @@ export const WeatherSettings = (): JSX.Element => {
return (
<form onSubmit={(e) => formSubmitHandler(e)}>
<SettingsHeadline text="API" />
{/* API KEY */}
<InputGroup>
<label htmlFor="WEATHER_API_KEY">API key</label>
<input
@ -104,8 +106,10 @@ export const WeatherSettings = (): JSX.Element => {
</span>
</InputGroup>
<SettingsHeadline text="Location" />
{/* LAT */}
<InputGroup>
<label htmlFor="lat">Location latitude</label>
<label htmlFor="lat">Latitude</label>
<input
type="number"
id="lat"
@ -128,8 +132,9 @@ export const WeatherSettings = (): JSX.Element => {
</span>
</InputGroup>
{/* LONG */}
<InputGroup>
<label htmlFor="long">Location longitude</label>
<label htmlFor="long">Longitude</label>
<input
type="number"
id="long"
@ -142,6 +147,8 @@ export const WeatherSettings = (): JSX.Element => {
/>
</InputGroup>
<SettingsHeadline text="Other" />
{/* TEMPERATURE */}
<InputGroup>
<label htmlFor="isCelsius">Temperature unit</label>
<select
@ -155,6 +162,20 @@ export const WeatherSettings = (): JSX.Element => {
</select>
</InputGroup>
{/* WEATHER DATA */}
<InputGroup>
<label htmlFor="weatherData">Additional weather data</label>
<select
id="weatherData"
name="weatherData"
value={formData.weatherData}
onChange={(e) => inputChangeHandler(e)}
>
<option value="cloud">Cloud coverage</option>
<option value="humidity">Humidity</option>
</select>
</InputGroup>
<Button>Save changes</Button>
</form>
);

View file

@ -1,29 +0,0 @@
import { Fragment } from 'react';
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../store';
import classes from './Themer.module.css';
import { themes } from './themes.json';
import { Theme } from '../../interfaces/Theme';
import { ThemePreview } from './ThemePreview';
export const Themer = (): JSX.Element => {
const dispatch = useDispatch();
const { setTheme } = bindActionCreators(actionCreators, dispatch);
return (
<Fragment>
<div>
<div className={classes.ThemerGrid}>
{themes.map(
(theme: Theme, idx: number): JSX.Element => (
<ThemePreview key={idx} theme={theme} applyTheme={setTheme} />
)
)}
</div>
</div>
</Fragment>
);
};

View file

@ -13,22 +13,14 @@ import classes from './WeatherWidget.module.css';
// UI
import { WeatherIcon } from '../../UI';
import { State } from '../../../store/reducers';
import { weatherTemplate } from '../../../utility/templateObjects/weatherTemplate';
export const WeatherWidget = (): JSX.Element => {
const { loading, config } = useSelector((state: State) => state.config);
const { loading: configLoading, config } = useSelector(
(state: State) => state.config
);
const [weather, setWeather] = useState<Weather>({
externalLastUpdate: '',
tempC: 0,
tempF: 0,
isDay: 1,
cloud: 0,
conditionText: '',
conditionCode: 1000,
id: -1,
createdAt: new Date(),
updatedAt: new Date(),
});
const [weather, setWeather] = useState<Weather>(weatherTemplate);
const [isLoading, setIsLoading] = useState(true);
// Initial request to get data
@ -65,8 +57,7 @@ export const WeatherWidget = (): JSX.Element => {
return (
<div className={classes.WeatherWidget}>
{isLoading ||
loading ||
{configLoading ||
(config.WEATHER_API_KEY && weather.id > 0 && (
<Fragment>
<div className={classes.WeatherIcon}>
@ -76,12 +67,15 @@ export const WeatherWidget = (): JSX.Element => {
/>
</div>
<div className={classes.WeatherDetails}>
{/* TEMPERATURE */}
{config.isCelsius ? (
<span>{weather.tempC}°C</span>
) : (
<span>{weather.tempF}°F</span>
)}
<span>{weather.cloud}%</span>
{/* ADDITIONAL DATA */}
<span>{weather[config.weatherData]}%</span>
</div>
</Fragment>
))}

View file

@ -1,3 +1,5 @@
import { WeatherData } from '../types';
export interface Config {
WEATHER_API_KEY: string;
lat: number;
@ -25,4 +27,8 @@ export interface Config {
daySchema: string;
monthSchema: string;
showTime: boolean;
defaultTheme: string;
isKilometer: boolean;
weatherData: WeatherData;
hideDate: boolean;
}

View file

@ -1,8 +1,11 @@
import { WeatherData } from '../types';
export interface WeatherForm {
WEATHER_API_KEY: string;
lat: number;
long: number;
isCelsius: boolean;
weatherData: WeatherData;
}
export interface SearchForm {
@ -27,6 +30,7 @@ export interface OtherSettingsForm {
daySchema: string;
monthSchema: string;
showTime: boolean;
hideDate: boolean;
}
export interface DockerSettingsForm {
@ -35,3 +39,7 @@ export interface DockerSettingsForm {
kubernetesApps: boolean;
unpinStoppedApps: boolean;
}
export interface ThemeSettingsForm {
defaultTheme: string;
}

View file

@ -8,4 +8,7 @@ export interface Weather extends Model {
cloud: number;
conditionText: string;
conditionCode: number;
}
humidity: number;
windK: number;
windM: number;
}

View file

@ -8,17 +8,10 @@ import {
UpdateQueryAction,
} from '../actions/config';
import axios from 'axios';
import {
ApiResponse,
Config,
DockerSettingsForm,
OtherSettingsForm,
Query,
SearchForm,
WeatherForm,
} from '../../interfaces';
import { ApiResponse, Config, Query } from '../../interfaces';
import { ActionType } from '../action-types';
import { storeUIConfig, applyAuth } from '../../utility';
import { ConfigFormData } from '../../types';
const keys: (keyof Config)[] = [
'useAmericanDate',
@ -26,6 +19,7 @@ const keys: (keyof Config)[] = [
'daySchema',
'monthSchema',
'showTime',
'hideDate',
];
export const getConfig = () => async (dispatch: Dispatch<GetConfigAction>) => {
@ -50,9 +44,7 @@ export const getConfig = () => async (dispatch: Dispatch<GetConfigAction>) => {
};
export const updateConfig =
(
formData: WeatherForm | OtherSettingsForm | SearchForm | DockerSettingsForm
) =>
(formData: ConfigFormData) =>
async (dispatch: Dispatch<UpdateConfigAction>) => {
try {
const res = await axios.put<ApiResponse<Config>>(

View file

@ -2,14 +2,18 @@ import { Dispatch } from 'redux';
import { SetThemeAction } from '../actions/theme';
import { ActionType } from '../action-types';
import { Theme } from '../../interfaces/Theme';
import { themes } from '../../components/Themer/themes.json';
import { themes } from '../../components/Settings/Themer/themes.json';
export const setTheme =
(name: string) => (dispatch: Dispatch<SetThemeAction>) => {
(name: string, remeberTheme: boolean = true) =>
(dispatch: Dispatch<SetThemeAction>) => {
const theme = themes.find((theme) => theme.name === name);
if (theme) {
localStorage.setItem('theme', name);
if (remeberTheme) {
localStorage.setItem('theme', name);
}
loadTheme(theme);
dispatch({

View file

@ -0,0 +1,14 @@
import {
DockerSettingsForm,
OtherSettingsForm,
SearchForm,
ThemeSettingsForm,
WeatherForm,
} from '../interfaces';
export type ConfigFormData =
| WeatherForm
| SearchForm
| DockerSettingsForm
| OtherSettingsForm
| ThemeSettingsForm;

View file

@ -0,0 +1 @@
export type WeatherData = 'cloud' | 'humidity';

View file

@ -0,0 +1,2 @@
export * from './ConfigFormData';
export * from './WeatherData';

View file

@ -1,4 +1,4 @@
export const applyAuth = () => {
const token = localStorage.getItem('token') || '';
return { Authorization: `Bearer ${token}` };
return { 'Authorization-Flame': `Bearer ${token}` };
};

View file

@ -28,4 +28,8 @@ export const configTemplate: Config = {
monthSchema:
'January;February;March;April;May;June;July;August;September;October;November;December',
showTime: false,
defaultTheme: 'tron',
isKilometer: true,
weatherData: 'cloud',
hideDate: false,
};

View file

@ -2,6 +2,7 @@ import {
DockerSettingsForm,
OtherSettingsForm,
SearchForm,
ThemeSettingsForm,
WeatherForm,
} from '../../interfaces';
@ -21,6 +22,7 @@ export const otherSettingsTemplate: OtherSettingsForm = {
monthSchema:
'January;February;March;April;May;June;July;August;September;October;November;December',
showTime: false,
hideDate: false,
};
export const weatherSettingsTemplate: WeatherForm = {
@ -28,6 +30,7 @@ export const weatherSettingsTemplate: WeatherForm = {
lat: 0,
long: 0,
isCelsius: true,
weatherData: 'cloud',
};
export const searchSettingsTemplate: SearchForm = {
@ -43,3 +46,7 @@ export const dockerSettingsTemplate: DockerSettingsForm = {
kubernetesApps: true,
unpinStoppedApps: true,
};
export const themeSettingsTemplate: ThemeSettingsForm = {
defaultTheme: 'tron',
};

View file

@ -0,0 +1,17 @@
import { Weather } from '../../interfaces';
export const weatherTemplate: Weather = {
externalLastUpdate: '',
tempC: 0,
tempF: 0,
isDay: 1,
cloud: 0,
conditionText: '',
conditionCode: 1000,
id: -1,
createdAt: new Date(),
updatedAt: new Date(),
humidity: 0,
windK: 0,
windM: 0,
};

View file

@ -8,25 +8,20 @@ const loadConfig = require('../../utils/loadConfig');
const createApp = asyncWrapper(async (req, res, next) => {
const { pinAppsByDefault } = await loadConfig();
let app;
let _body = { ...req.body };
let body = { ...req.body };
if (_body.icon) {
_body.icon = _body.icon.trim();
if (body.icon) {
body.icon = body.icon.trim();
}
if (req.file) {
_body.icon = req.file.filename;
body.icon = req.file.filename;
}
if (pinAppsByDefault) {
app = await App.create({
..._body,
isPinned: true,
});
} else {
app = await App.create(req.body);
}
const app = await App.create({
...body,
isPinned: pinAppsByDefault,
});
res.status(201).json({
success: true,

View file

@ -18,17 +18,17 @@ const updateApp = asyncWrapper(async (req, res, next) => {
);
}
let _body = { ...req.body };
let body = { ...req.body };
if (_body.icon) {
_body.icon = _body.icon.trim();
if (body.icon) {
body.icon = body.icon.trim();
}
if (req.file) {
_body.icon = req.file.filename;
body.icon = req.file.filename;
}
app = await app.update(_body);
app = await app.update(body);
res.status(200).json({
success: true,

View file

@ -7,20 +7,20 @@ const Bookmark = require('../../models/Bookmark');
const createBookmark = asyncWrapper(async (req, res, next) => {
let bookmark;
let _body = {
let body = {
...req.body,
categoryId: parseInt(req.body.categoryId),
};
if (_body.icon) {
_body.icon = _body.icon.trim();
if (body.icon) {
body.icon = body.icon.trim();
}
if (req.file) {
_body.icon = req.file.filename;
body.icon = req.file.filename;
}
bookmark = await Bookmark.create(_body);
bookmark = await Bookmark.create(body);
res.status(201).json({
success: true,

View file

@ -19,20 +19,20 @@ const updateBookmark = asyncWrapper(async (req, res, next) => {
);
}
let _body = {
let body = {
...req.body,
categoryId: parseInt(req.body.categoryId),
};
if (_body.icon) {
_body.icon = _body.icon.trim();
if (body.icon) {
body.icon = body.icon.trim();
}
if (req.file) {
_body.icon = req.file.filename;
body.icon = req.file.filename;
}
bookmark = await bookmark.update(_body);
bookmark = await bookmark.update(body);
res.status(200).json({
success: true,

View file

@ -8,16 +8,10 @@ const loadConfig = require('../../utils/loadConfig');
const createCategory = asyncWrapper(async (req, res, next) => {
const { pinCategoriesByDefault: pinCategories } = await loadConfig();
let category;
if (pinCategories) {
category = await Category.create({
...req.body,
isPinned: true,
});
} else {
category = await Category.create(req.body);
}
const category = await Category.create({
...req.body,
isPinned: pinCategories,
});
res.status(201).json({
success: true,

View file

@ -0,0 +1,35 @@
const { DataTypes } = require('sequelize');
const { INTEGER, FLOAT } = DataTypes;
const loadConfig = require('../../utils/loadConfig');
const getExternalWeather = require('../../utils/getExternalWeather');
const up = async (query) => {
await query.addColumn('weather', 'humidity', {
type: INTEGER,
});
await query.addColumn('weather', 'windK', {
type: FLOAT,
});
await query.addColumn('weather', 'windM', {
type: FLOAT,
});
const { WEATHER_API_KEY: secret } = await loadConfig();
if (secret) {
await getExternalWeather();
}
};
const down = async (query) => {
await query.removeColumn('weather', 'humidity');
await query.removeColumn('weather', 'windK');
await query.removeColumn('weather', 'windM');
};
module.exports = {
up,
down,
};

View file

@ -1,7 +1,7 @@
const jwt = require('jsonwebtoken');
const auth = (req, res, next) => {
const authHeader = req.header('Authorization');
const authHeader = req.header('Authorization-Flame');
let token;
let tokenIsValid = false;

View file

@ -1,16 +1,23 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const Weather = sequelize.define('Weather', {
externalLastUpdate: DataTypes.STRING,
tempC: DataTypes.FLOAT,
tempF: DataTypes.FLOAT,
isDay: DataTypes.INTEGER,
cloud: DataTypes.INTEGER,
conditionText: DataTypes.TEXT,
conditionCode: DataTypes.INTEGER
}, {
tableName: 'weather'
});
const Weather = sequelize.define(
'Weather',
{
externalLastUpdate: DataTypes.STRING,
tempC: DataTypes.FLOAT,
tempF: DataTypes.FLOAT,
isDay: DataTypes.INTEGER,
cloud: DataTypes.INTEGER,
conditionText: DataTypes.TEXT,
conditionCode: DataTypes.INTEGER,
humidity: DataTypes.INTEGER,
windK: DataTypes.FLOAT,
windM: DataTypes.FLOAT,
},
{
tableName: 'weather',
}
);
module.exports = Weather;
module.exports = Weather;

View file

@ -21,6 +21,9 @@ const getExternalWeather = async () => {
cloud: cursor.cloud,
conditionText: cursor.condition.text,
conditionCode: cursor.condition.code,
humidity: cursor.humidity,
windK: cursor.wind_kph,
windM: cursor.wind_mph,
});
return weatherData;
} catch (err) {

View file

@ -24,5 +24,9 @@
"greetingsSchema": "Good evening!;Good afternoon!;Good morning!;Good night!",
"daySchema": "Sunday;Monday;Tuesday;Wednesday;Thursday;Friday;Saturday",
"monthSchema": "January;February;March;April;May;June;July;August;September;October;November;December",
"showTime": false
"showTime": false,
"defaultTheme": "tron",
"isKilometer": true,
"weatherData": "cloud",
"hideDate": false
}