mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-07 17:02:29 +01:00
maintain (admin): admin page upgrade
This commit is contained in:
parent
6e27086ac4
commit
44fc901b4b
13 changed files with 346 additions and 349 deletions
|
|
@ -7,24 +7,20 @@ import React, { useState, useEffect } from "react";
|
||||||
* to realise it would be easier to write this simpler wrapper than migrate things over
|
* to realise it would be easier to write this simpler wrapper than migrate things over
|
||||||
*/
|
*/
|
||||||
export function CSSTransition({ transitionName = "animate", children = null, transitionAppearTimeout = 300 }) {
|
export function CSSTransition({ transitionName = "animate", children = null, transitionAppearTimeout = 300 }) {
|
||||||
const [child, setChildren] = useState(React.cloneElement(children, {
|
const [className, setClassName] = useState(`${transitionName} ${transitionName}-appear`);
|
||||||
className: `${children.props.className} ${transitionName}-appear`
|
|
||||||
}));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setChildren(React.cloneElement(child, {
|
setClassName(`${transitionName} ${transitionName}-appear ${transitionName}-appear-active`)
|
||||||
className: `${children.props.className} ${transitionName}-appear ${transitionName}-appear-active`
|
|
||||||
}))
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
setChildren(React.cloneElement(child, {
|
|
||||||
className: `${children.props.className}`
|
|
||||||
}))
|
|
||||||
}, transitionAppearTimeout);
|
|
||||||
|
|
||||||
return () => {
|
const timeout = setTimeout(() => {
|
||||||
clearTimeout(timeout);
|
setClassName(`${transitionName}`)
|
||||||
};
|
}, transitionAppearTimeout);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return child;
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,4 @@ export { MapShot } from "./mapshot";
|
||||||
export { LoggedInOnly, ErrorPage, LoadingPage } from "./decorator";
|
export { LoggedInOnly, ErrorPage, LoadingPage } from "./decorator";
|
||||||
export { FormBuilder } from "./formbuilder";
|
export { FormBuilder } from "./formbuilder";
|
||||||
export { UploadQueue } from "./upload_queue";
|
export { UploadQueue } from "./upload_queue";
|
||||||
|
export { CSSTransition } from "./animation";
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,57 @@
|
||||||
import React from "react";
|
import React, { useRef, useState, useLayoutEffect } from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import "./textarea.scss";
|
import "./textarea.scss";
|
||||||
|
|
||||||
export class Textarea extends React.Component {
|
export function Textarea({ ...props }) {
|
||||||
|
const $el = useRef();
|
||||||
|
const [className, setClassName] = useState(
|
||||||
|
"component_textarea"
|
||||||
|
+ (/Firefox/.test(navigator.userAgent) ? " firefox" : "")
|
||||||
|
+ (props.value && props.value.length > 0 ? " hasText" : "")
|
||||||
|
);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if($el.current && $el.current.value.length > 0 && className.indexOf("hasText") === -1){
|
||||||
|
setClassName(`${className} hasText`)
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const disabledEnter = (e) => {
|
||||||
|
if(e.key === "Enter" && e.shiftKey === false){
|
||||||
|
e.preventDefault();
|
||||||
|
const $form = getForm($el.current.ref);
|
||||||
|
if($form){
|
||||||
|
$form.dispatchEvent(new Event("submit", { cancelable: true }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getForm($el){
|
||||||
|
if(!$el.parentElement) return $el;
|
||||||
|
if($el.parentElement.nodeName == "FORM"){
|
||||||
|
return $el.parentElement;
|
||||||
|
}
|
||||||
|
return getForm($el.parentElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const inputProps = (p) => {
|
||||||
|
return Object.keys(p).reduce((acc, key) => {
|
||||||
|
if(key === "disabledEnter") return acc;
|
||||||
|
acc[key] = p[key];
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
onKeyPress={disabledEnter}
|
||||||
|
{...inputProps(props)}
|
||||||
|
className={className}
|
||||||
|
ref={$el}>
|
||||||
|
</textarea>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export class Textarea2 extends React.Component {
|
||||||
constructor(props){
|
constructor(props){
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
const $loader = document.querySelector("#n-lder");
|
const $loader = document.querySelector("#n-lder");
|
||||||
|
|
||||||
function render(){
|
function render(){
|
||||||
ReactDOM.render(<Router/>, document.querySelector("div[role='main']"));
|
ReactDOM.render(<React.StrictMode><Router/></React.StrictMode>, document.querySelector("div[role='main']"));
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
function waitFor(n){
|
function waitFor(n){
|
||||||
|
|
|
||||||
|
|
@ -1,118 +1,96 @@
|
||||||
import React from 'react';
|
import React, { useState, useEffect } from "react";
|
||||||
import Path from 'path';
|
import Path from "path";
|
||||||
import { Route, Switch, Link, NavLink } from 'react-router-dom';
|
import { Route, Switch, Link, NavLink, useRouteMatch } from "react-router-dom";
|
||||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
|
||||||
|
|
||||||
import './error.scss';
|
|
||||||
import './adminpage.scss';
|
|
||||||
import { Icon, LoadingPage } from '../components/';
|
|
||||||
import { Config, Admin } from '../model';
|
|
||||||
import { notify } from '../helpers/';
|
|
||||||
import { HomePage, BackendPage, SettingsPage, LogPage, SetupPage, LoginPage } from './adminpage/';
|
|
||||||
import { t } from '../locales/';
|
|
||||||
|
|
||||||
|
import "./error.scss";
|
||||||
|
import "./adminpage.scss";
|
||||||
|
import { Icon, LoadingPage, CSSTransition } from "../components/";
|
||||||
|
import { Config, Admin } from "../model";
|
||||||
|
import { notify } from "../helpers/";
|
||||||
|
import { HomePage, BackendPage, SettingsPage, LogPage, SetupPage, LoginPage } from "./adminpage/";
|
||||||
|
import { t } from "../locales/";
|
||||||
|
|
||||||
function AdminOnly(WrappedComponent){
|
function AdminOnly(WrappedComponent){
|
||||||
return class extends React.Component {
|
let initIsAdmin = null;
|
||||||
constructor(props){
|
return function(props) {
|
||||||
super(props);
|
const [isAdmin, setIsAdmin] = useState(initIsAdmin);
|
||||||
this.state = {
|
|
||||||
isAdmin: null
|
const refresh = () => {
|
||||||
};
|
Admin.isAdmin().then((t) => {
|
||||||
this.admin = () => {
|
initIsAdmin = t
|
||||||
Admin.isAdmin().then((t) => {
|
setIsAdmin(t)
|
||||||
this.setState({isAdmin: t});
|
}).catch((err) => {
|
||||||
}).catch((err) => {
|
notify.send("Error: " + (err && err.message) , "error");
|
||||||
notify.send("Error: " + (err && err.message) , "error");
|
});
|
||||||
});
|
}
|
||||||
};
|
useEffect(() => {
|
||||||
this.timeout = window.setInterval(this.admin.bind(this), 30 * 1000);
|
refresh()
|
||||||
|
const timeout = window.setInterval(refresh, 5 * 1000);
|
||||||
|
return () => clearInterval(timeout);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if(isAdmin === true) {
|
||||||
|
return ( <WrappedComponent {...props} /> );
|
||||||
|
} else if(isAdmin === false) {
|
||||||
|
return ( <LoginPage reload={refresh} /> );
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(){
|
return ( <LoadingPage /> );
|
||||||
this.admin.call(this);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount(){
|
|
||||||
window.clearInterval(this.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
render(){
|
|
||||||
if(this.state.isAdmin === true){
|
|
||||||
return ( <WrappedComponent {...this.props} /> );
|
|
||||||
} else if(this.state.isAdmin === false) {
|
|
||||||
return ( <LoginPage reload={() => this.admin()} /> );
|
|
||||||
}
|
|
||||||
return ( <LoadingPage />);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AdminOnly
|
export default AdminOnly((props) => {
|
||||||
export class AdminPage extends React.Component {
|
const match = useRouteMatch();
|
||||||
constructor(props){
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
super(props);
|
return (
|
||||||
this.state = {
|
<div className="component_page_admin">
|
||||||
isAdmin: null,
|
<SideMenu url={match.url} isLoading={isSaving}/>
|
||||||
isSaving: false
|
<div className="page_container scroll-y">
|
||||||
};
|
<CSSTransition key={location.pathname} transitionName="adminpage" transitionAppearTimeout={30000}>
|
||||||
}
|
<Switch>
|
||||||
|
<Route path={match.url + "/backend"} render={()=><BackendPage isSaving={setIsSaving}/>} />
|
||||||
isSaving(yesOrNo){
|
<Route path={match.url + "/settings"} render={()=><SettingsPage isSaving={setIsSaving}/>} />
|
||||||
this.setState({isSaving: yesOrNo});
|
<Route path={match.url + "/logs"} render={() =><LogPage isSaving={setIsSaving}/>} />
|
||||||
}
|
<Route path={match.url + "/setup"} component={SetupPage} />
|
||||||
|
<Route path={match.url} component={HomePage} />
|
||||||
render(){
|
</Switch>
|
||||||
return (
|
</CSSTransition>
|
||||||
<div className="component_page_admin">
|
|
||||||
<SideMenu url={this.props.match.url} isLoading={this.state.isSaving}/>
|
|
||||||
<div className="page_container scroll-y">
|
|
||||||
<ReactCSSTransitionGroup key={window.location.pathname} transitionName="adminpage" transitionLeave={true} transitionEnter={true} transitionLeaveTimeout={15000} transitionEnterTimeout={20000} transitionAppear={true} transitionAppearTimeout={20000}>
|
|
||||||
<Switch>
|
|
||||||
<Route path={this.props.match.url + "/backend"} render={()=><BackendPage isSaving={this.isSaving.bind(this)}/>} />
|
|
||||||
<Route path={this.props.match.url + "/settings"} render={()=><SettingsPage isSaving={this.isSaving.bind(this)}/>} />
|
|
||||||
<Route path={this.props.match.url + "/logs"} render={() =><LogPage isSaving={this.isSaving.bind(this)}/>} />
|
|
||||||
<Route path={this.props.match.url + "/setup"} component={SetupPage} />
|
|
||||||
<Route path={this.props.match.url} component={HomePage} />
|
|
||||||
</Switch>
|
|
||||||
</ReactCSSTransitionGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
const SideMenu = (props) => {
|
function SideMenu(props) {
|
||||||
return (
|
return (
|
||||||
<div className="component_menu_sidebar no-select">
|
<div className="component_menu_sidebar no-select">
|
||||||
{ props.isLoading ?
|
{ props.isLoading ?
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<Icon name="arrow_left" style={{"opacity": 0}}/>
|
<Icon name="arrow_left" style={{"opacity": 0}}/>
|
||||||
<Icon name="loading" />
|
<Icon name="loading" />
|
||||||
</div> :
|
</div> :
|
||||||
<NavLink to="/" className="header">
|
<NavLink to="/" className="header">
|
||||||
<Icon name="arrow_left" />
|
<Icon name="arrow_left" />
|
||||||
<img src="/assets/logo/android-chrome-512x512.png" />
|
<img src="/assets/logo/android-chrome-512x512.png" />
|
||||||
</NavLink>
|
|
||||||
}
|
|
||||||
<h2>{ t("Admin console") }</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<NavLink activeClassName="active" to={props.url + "/backend"}>
|
|
||||||
{ t("Backend") }
|
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
}
|
||||||
<li>
|
<h2>{ t("Admin console") }</h2>
|
||||||
<NavLink activeClassName="active" to={props.url + "/settings"}>
|
<ul>
|
||||||
{ t("Settings") }
|
<li>
|
||||||
</NavLink>
|
<NavLink activeClassName="active" to={props.url + "/backend"}>
|
||||||
</li>
|
{ t("Backend") }
|
||||||
<li>
|
</NavLink>
|
||||||
<NavLink activeClassName="active" to={props.url + "/logs"}>
|
</li>
|
||||||
{ t("Logs") }
|
<li>
|
||||||
</NavLink>
|
<NavLink activeClassName="active" to={props.url + "/settings"}>
|
||||||
</li>
|
{ t("Settings") }
|
||||||
</ul>
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NavLink activeClassName="active" to={props.url + "/logs"}>
|
||||||
|
{ t("Logs") }
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { FormBuilder, Icon, Input, Alert } from "../../components/";
|
import { FormBuilder, Icon, Input, Alert } from "../../components/";
|
||||||
import { Backend, Config } from "../../model/";
|
import { Backend, Config } from "../../model/";
|
||||||
import { FormObjToJSON, notify, format, createFormBackend } from "../../helpers/";
|
import { FormObjToJSON, notify, format, createFormBackend } from "../../helpers/";
|
||||||
import { t } from '../../locales/';
|
import { t } from "../../locales/";
|
||||||
|
|
||||||
import "./backend.scss";
|
import "./backend.scss";
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ export class BackendPage extends React.Component {
|
||||||
let [backend, config] = data;
|
let [backend, config] = data;
|
||||||
this.setState({
|
this.setState({
|
||||||
backend_available: backend,
|
backend_available: backend,
|
||||||
backend_enabled: window.CONFIG["connections"].map((conn) => {
|
backend_enabled: window.CONFIG["connections"].filter((b) => b).map((conn) => {
|
||||||
return createFormBackend(backend, conn);
|
return createFormBackend(backend, conn);
|
||||||
}),
|
}),
|
||||||
config: config
|
config: config
|
||||||
|
|
@ -56,12 +56,12 @@ export class BackendPage extends React.Component {
|
||||||
return Config.save(json, true, () => {
|
return Config.save(json, true, () => {
|
||||||
this.props.isSaving(false);
|
this.props.isSaving(false);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
notify.send(err && err.message || t('Oops'), 'error');
|
notify.send(err && err.message || t("Oops"), "error");
|
||||||
this.props.isSaving(false);
|
this.props.isSaving(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addBackend(backend_id){
|
addBackend(backend_id){
|
||||||
this.setState({
|
this.setState({
|
||||||
backend_enabled: this.state.backend_enabled.concat(
|
backend_enabled: this.state.backend_enabled.concat(
|
||||||
createFormBackend(this.state.backend_available, {
|
createFormBackend(this.state.backend_available, {
|
||||||
|
|
@ -105,7 +105,7 @@ export class BackendPage extends React.Component {
|
||||||
const isActiveBackend = (backend_key) => {
|
const isActiveBackend = (backend_key) => {
|
||||||
return this.state.backend_enabled
|
return this.state.backend_enabled
|
||||||
.map((b) => Object.keys(b)[0])
|
.map((b) => Object.keys(b)[0])
|
||||||
.indexOf(backend_key) !== -1;
|
.indexOf(backend_key) !== -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isActiveAuth = (auth_key) => {
|
const isActiveAuth = (auth_key) => {
|
||||||
|
|
@ -134,7 +134,7 @@ export class BackendPage extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Authentication Middleware</h2>
|
<h2>Authentication Middleware</h2>
|
||||||
|
|
||||||
<Alert>
|
<Alert>
|
||||||
Integrate Filestash with your identity management system
|
Integrate Filestash with your identity management system
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
@ -163,8 +163,6 @@ export class BackendPage extends React.Component {
|
||||||
<Alert className="success">
|
<Alert className="success">
|
||||||
<i><strong>Register your interest: <a href={`mailto:mickael@kerjean.me?Subject=Filestash - Authentication Middleware - ${this.state.auth_enabled}`}>mickael@kerjean.me</a></strong></i>
|
<i><strong>Register your interest: <a href={`mailto:mickael@kerjean.me?Subject=Filestash - Authentication Middleware - ${this.state.auth_enabled}`}>mickael@kerjean.me</a></strong></i>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -181,14 +179,14 @@ export class BackendPage extends React.Component {
|
||||||
<div className="icons no-select" onClick={this.removeBackend.bind(this, index)}>
|
<div className="icons no-select" onClick={this.removeBackend.bind(this, index)}>
|
||||||
<Icon name="delete" />
|
<Icon name="delete" />
|
||||||
</div>
|
</div>
|
||||||
<FormBuilder onChange={this.onChange.bind(this)}
|
<FormBuilder onChange={this.onChange.bind(this)}
|
||||||
idx={index}
|
idx={index}
|
||||||
key={index}
|
key={index}
|
||||||
form={{"": backend_enable}}
|
form={{"": backend_enable}}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
render={ ($input, props, struct, onChange) => {
|
render={ ($input, props, struct, onChange) => {
|
||||||
let $checkbox = (
|
let $checkbox = (
|
||||||
<Input type="checkbox" style={{width: "inherit", marginRight: '6px', top: '6px'}}
|
<Input type="checkbox" style={{width: "inherit", marginRight: "6px", top: "6px"}}
|
||||||
checked={enable(struct)} onChange={(e) => onChange(update.bind(this, e.target.checked))}/>
|
checked={enable(struct)} onChange={(e) => onChange(update.bind(this, e.target.checked))}/>
|
||||||
);
|
);
|
||||||
if(struct.label === "label"){
|
if(struct.label === "label"){
|
||||||
|
|
@ -203,13 +201,13 @@ export class BackendPage extends React.Component {
|
||||||
{ $checkbox }
|
{ $checkbox }
|
||||||
{ format(struct.label) }:
|
{ format(struct.label) }:
|
||||||
</span>
|
</span>
|
||||||
<div style={{width: '100%'}}>
|
<div style={{width: "100%"}}>
|
||||||
{ $input }
|
{ $input }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="nothing"></span>
|
<span className="nothing"></span>
|
||||||
<div style={{width: '100%'}}>
|
<div style={{width: "100%"}}>
|
||||||
{
|
{
|
||||||
struct.description ? (<div className="description">{struct.description}</div>) : null
|
struct.description ? (<div className="description">{struct.description}</div>) : null
|
||||||
}
|
}
|
||||||
|
|
@ -224,7 +222,7 @@ export class BackendPage extends React.Component {
|
||||||
}
|
}
|
||||||
</form>
|
</form>
|
||||||
</div> : <Alert>You need to enable a backend first.</Alert>
|
</div> : <Alert>You need to enable a backend first.</Alert>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
.component_dashboard{
|
.component_dashboard{
|
||||||
|
|
||||||
.alert { margin-top: -15px; }
|
.alert { margin-top: -15px; }
|
||||||
.box-container {
|
.box-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -41,6 +40,7 @@
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
background: var(--emphasis-primary);
|
background: var(--emphasis-primary);
|
||||||
padding: 18px 0;
|
padding: 18px 0;
|
||||||
|
@media (max-width: 750px){ padding: 8px 0; }
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
.icon{
|
.icon{
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from "react-router-dom";
|
||||||
|
|
||||||
export class HomePage extends React.Component {
|
export function HomePage() {
|
||||||
constructor(props){
|
return ( <Redirect to="/admin/backend" /> );
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
stage: "loading"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render(){
|
|
||||||
return ( <Redirect to="/admin/backend" /> );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,91 +1,86 @@
|
||||||
import React from 'react';
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { FormBuilder, Loader, Button, Icon } from '../../components/';
|
import { FormBuilder, Loader, Button, Icon } from "../../components/";
|
||||||
import { Config, Log } from '../../model/';
|
import { Config, Log } from "../../model/";
|
||||||
import { FormObjToJSON, notify, format } from '../../helpers/';
|
import { FormObjToJSON, notify, format, nop } from "../../helpers/";
|
||||||
import { t } from '../../locales/';
|
import { t } from "../../locales/";
|
||||||
|
|
||||||
import "./logger.scss";
|
import "./logger.scss";
|
||||||
|
|
||||||
export class LogPage extends React.Component {
|
export function LogPage({ isSaving = nop }) {
|
||||||
constructor(props){
|
const [log, setLog] = useState("");
|
||||||
super(props);
|
const [form, setForm] = useState({});
|
||||||
this.state = {
|
const [config, setConfig] = useState({});
|
||||||
form: {},
|
const $log = useRef();
|
||||||
log: "",
|
const filename = () => {
|
||||||
config: {}
|
const t = new Date().toISOString().substring(0,10).replace(/-/g, "");
|
||||||
};
|
return `access_${t}.log`;
|
||||||
}
|
};
|
||||||
|
const onChange = (r) => {
|
||||||
componentDidMount(){
|
const c = Object.assign({}, config)
|
||||||
Config.all().then((config) => {
|
c["log"] = r[""]["params"];
|
||||||
this.setState({
|
c["connections"] = window.CONFIG.connections;
|
||||||
form: {"":{"params":config["log"]}},
|
delete c["constant"]
|
||||||
config: FormObjToJSON(config)
|
isSaving(true);
|
||||||
});
|
Config.save(c, true, () => {
|
||||||
});
|
isSaving(false);
|
||||||
Log.get(1024*100).then((log) => { // get only the last 100kb of log
|
|
||||||
this.setState({log: log}, () => {
|
|
||||||
this.refs.$log.scrollTop = this.refs.$log.scrollHeight;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange(r){
|
|
||||||
this.state.config["log"] = r[""].params;
|
|
||||||
this.state.config["connections"] = window.CONFIG.connections;
|
|
||||||
this.props.isSaving(true);
|
|
||||||
Config.save(this.state.config, true, () => {
|
|
||||||
this.props.isSaving(false);
|
|
||||||
}, () => {
|
}, () => {
|
||||||
notify.send(err && err.message || t('Oops'), 'error');
|
isSaving(false);
|
||||||
this.props.isSaving(false);
|
notify.send(err && err.message || t("Oops"), "error");
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
const fetchLogs = () => {
|
||||||
|
Log.get(1024*100).then((log) => { // get only the last 100kb of log
|
||||||
|
setLog(log + "\n\n\n\n\n");
|
||||||
|
if($log.current.scrollTop === 0) {
|
||||||
|
$log.current.scrollTop = $log.current.scrollHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render(){
|
useEffect(() => {
|
||||||
const filename = () => {
|
Config.all().then((config) => {
|
||||||
let tmp = "access_";
|
setForm({"":{"params":config["log"]}});
|
||||||
tmp += new Date().toISOString().substring(0,10).replace(/-/g, "");
|
setConfig(FormObjToJSON(config));
|
||||||
tmp += ".log";
|
});
|
||||||
};
|
fetchLogs();
|
||||||
return (
|
const id = setInterval(fetchLogs, 5000);
|
||||||
<div className="component_logpage">
|
return () => clearInterval(id)
|
||||||
<h2>Logging</h2>
|
}, []);
|
||||||
<div style={{minHeight: '150px'}}>
|
|
||||||
<FormBuilder form={this.state.form} onChange={this.onChange.bind(this)}
|
|
||||||
render={ ($input, props, struct, onChange) => {
|
|
||||||
return (
|
|
||||||
<label className={"no-select input_type_" + props.params["type"]}>
|
|
||||||
<div>
|
|
||||||
<span>
|
|
||||||
{ format(struct.label) }:
|
|
||||||
</span>
|
|
||||||
<div style={{width: '100%'}}>
|
|
||||||
{ $input }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="nothing"></span>
|
|
||||||
<div style={{width: '100%'}}>
|
|
||||||
{
|
|
||||||
struct.description ? (<div className="description">{struct.description}</div>) : null
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
}} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<pre style={{height: '350px'}} ref="$log">
|
return (
|
||||||
{
|
<div className="component_logpage">
|
||||||
this.state.log === "" ? <Loader/> : this.state.log + "\n\n\n\n\n"
|
<h2>Logging</h2>
|
||||||
}
|
<div style={{minHeight: "150px"}}>
|
||||||
</pre>
|
<FormBuilder
|
||||||
<div>
|
form={form}
|
||||||
<a href={Log.url()} download={filename()}><Button className="primary">{ t("Download") }</Button></a>
|
onChange={onChange}
|
||||||
</div>
|
render={ ($input, props, struct, onChange) => (
|
||||||
|
<label className={"no-select input_type_" + props.params["type"]}>
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
{ format(struct.label) }:
|
||||||
|
</span>
|
||||||
|
<div style={{width: "100%"}}>
|
||||||
|
{ $input }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="nothing"></span>
|
||||||
|
<div style={{width: "100%"}}>
|
||||||
|
{
|
||||||
|
struct.description ? (<div className="description">{struct.description}</div>) : null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
)} />
|
||||||
</div>
|
</div>
|
||||||
);
|
<pre style={{height: "350px"}} ref={$log}>
|
||||||
}
|
{ log === "" ? <Loader/> : log }
|
||||||
|
</pre>
|
||||||
|
<div>
|
||||||
|
<a href={Log.url()} download={filename()}><Button className="primary">{ t("Download") }</Button></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,44 @@
|
||||||
import React from 'react';
|
import React, { useState, useRef } from "react";
|
||||||
import { Redirect } from 'react-router';
|
import { Redirect } from "react-router";
|
||||||
|
|
||||||
import { Input, Button, Container, Icon, Loader } from '../../components/';
|
import { Input, Button, Container, Icon } from "../../components/";
|
||||||
import { Config, Admin } from '../../model/';
|
import { Admin } from "../../model/";
|
||||||
import { t } from '../../locales/';
|
import { nop } from "../../helpers/";
|
||||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
import { t } from "../../locales/";
|
||||||
|
|
||||||
export class LoginPage extends React.Component {
|
export function LoginPage({ reload = nop }) {
|
||||||
constructor(props){
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
super(props);
|
const [hasError, setHasError] = useState(false);
|
||||||
this.state = {
|
const $input = useRef();
|
||||||
loading: false,
|
const marginTop = () => ({ marginTop: `${parseInt(window.innerHeight / 3)}px` })
|
||||||
error: null
|
const authenticate = (e) => {
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(){
|
|
||||||
this.refs.$input.ref.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticate(e){
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setState({loading: true});
|
setIsLoading(true);
|
||||||
Admin.login(this.refs.$input.ref.value)
|
Admin.login($input.current.ref.value)
|
||||||
.then(() => this.props.reload())
|
.then(() => reload())
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.refs.$input.ref.value = "";
|
$input.current.ref.value = "";
|
||||||
this.setState({
|
setIsLoading(false)
|
||||||
loading: false,
|
setHasError(true);
|
||||||
error: true
|
setTimeout(() => {
|
||||||
}, () => {
|
setHasError(false);
|
||||||
window.setTimeout(() => {
|
}, 500);
|
||||||
this.setState({error: false});
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(){
|
useRef(() => {
|
||||||
const marginTop = () => { return {marginTop: parseInt(window.innerHeight / 3)+'px'};};
|
$input.current.ref.focus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
|
||||||
<Container maxWidth="300px" className="sharepage_component">
|
return (
|
||||||
<form className={this.state.error ? "error" : ""} onSubmit={this.authenticate.bind(this)} style={marginTop()}>
|
<Container maxWidth="300px" className="sharepage_component">
|
||||||
<Input ref="$input" type="password" placeholder={ t("Password") } />
|
<form className={hasError ? "error" : ""} onSubmit={authenticate} style={marginTop()}>
|
||||||
|
<Input ref={$input} type="password" placeholder={ t("Password") } />
|
||||||
<Button theme="transparent">
|
<Button theme="transparent">
|
||||||
<Icon name={this.state.loading ? "loading" : "arrow_right"}/>
|
<Icon name={isLoading ? "loading" : "arrow_right"}/>
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,12 @@
|
||||||
import React from 'react';
|
import React, { useState, useEffect } from "react";
|
||||||
import { FormBuilder } from '../../components/';
|
import { FormBuilder } from "../../components/";
|
||||||
import { Config } from '../../model/';
|
import { Config } from "../../model/";
|
||||||
import { format, notify } from '../../helpers';
|
import { format, notify, nop } from "../../helpers";
|
||||||
import { t } from '../../locales/';
|
import { t } from "../../locales/";
|
||||||
|
|
||||||
export class SettingsPage extends React.Component {
|
export function SettingsPage({ isSaving = nop }) {
|
||||||
constructor(props){
|
const [form, setForm] = useState({});
|
||||||
super(props);
|
const format = (name) => {
|
||||||
this.state = {
|
|
||||||
form: {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(){
|
|
||||||
Config.all().then((c) => {
|
|
||||||
delete c.constant; // The constant key contains read only global variable that are
|
|
||||||
// application wide truth => not editable from the admin area
|
|
||||||
this.setState({form: c});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
format(name){
|
|
||||||
if(typeof name !== "string"){
|
if(typeof name !== "string"){
|
||||||
return "N/A";
|
return "N/A";
|
||||||
}
|
}
|
||||||
|
|
@ -34,45 +20,52 @@ export class SettingsPage extends React.Component {
|
||||||
})
|
})
|
||||||
.join(" ");
|
.join(" ");
|
||||||
}
|
}
|
||||||
|
const onChange = (_form) => {
|
||||||
onChange(form){
|
_form.connections = window.CONFIG.connections;
|
||||||
form.connections = window.CONFIG.connections;
|
delete _form.constant;
|
||||||
this.props.isSaving(true);
|
refresh(Math.random())
|
||||||
Config.save(form, true, () => {
|
isSaving(true)
|
||||||
this.props.isSaving(false);
|
Config.save(_form, true, () => {
|
||||||
|
isSaving(false)
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
notify.send(err && err.message || t('Oops'), 'error');
|
isSaving(false)
|
||||||
this.props.isSaving(false);
|
notify.send(err && err.message || t("Oops"), "error");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const [_, refresh] = useState(null);
|
||||||
|
|
||||||
render(){
|
useEffect(() => {
|
||||||
return (
|
Config.all().then((c) => {
|
||||||
<form className="sticky">
|
delete c.constant; // The constant key contains read only global variable that are
|
||||||
<FormBuilder form={this.state.form}
|
// application wide truth => not editable from the admin area
|
||||||
onChange={this.onChange.bind(this)}
|
setForm(c)
|
||||||
autoComplete="new-password"
|
});
|
||||||
render={ ($input, props, struct, onChange) => {
|
}, []);
|
||||||
return (
|
|
||||||
<label className={"no-select input_type_" + props.params["type"]}>
|
return (
|
||||||
<div>
|
<form className="sticky">
|
||||||
|
<FormBuilder
|
||||||
|
form={form}
|
||||||
|
onChange={onChange}
|
||||||
|
autoComplete="new-password"
|
||||||
|
render={ ($input, props, struct, onChange) => (
|
||||||
|
<label className={"no-select input_type_" + props.params["type"]}>
|
||||||
|
<div>
|
||||||
<span>
|
<span>
|
||||||
{ format(struct.label) }:
|
{ format(struct.label) }:
|
||||||
</span>
|
</span>
|
||||||
<div style={{width: '100%'}}>
|
<div style={{width: "100%"}}>
|
||||||
{ $input }
|
{ $input }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="nothing"></span>
|
<span className="nothing"></span>
|
||||||
<div style={{width: '100%'}}>
|
<div style={{width: "100%"}}>
|
||||||
{ struct.description ? (<div className="description">{struct.description}</div>) : null }
|
{ struct.description ? (<div className="description">{struct.description}</div>) : null }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
);
|
)} />
|
||||||
}}/>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React, { createRef } from "react";
|
||||||
|
|
||||||
import { Input, Button, Container, Icon, NgIf, Loader } from '../../components/';
|
import { Input, Button, Container, Icon, NgIf, Loader, CSSTransition } from "../../components/";
|
||||||
import { Config, Admin } from '../../model/';
|
import { Config, Admin } from "../../model/";
|
||||||
import { notify, FormObjToJSON, alert, prompt } from '../../helpers';
|
import { notify, FormObjToJSON, alert, prompt } from "../../helpers";
|
||||||
import { bcrypt_password } from '../../helpers/bcrypt';
|
import { bcrypt_password } from "../../helpers/bcrypt";
|
||||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
//import ReactCSSTransitionGroup from "react-addons-css-transition-group";
|
||||||
|
|
||||||
import "./setup.scss";
|
import "./setup.scss";
|
||||||
|
|
||||||
|
|
@ -28,12 +28,12 @@ export class SetupPage extends React.Component {
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
alert.now((
|
alert.now((
|
||||||
<div>
|
<div>
|
||||||
<p style={{textAlign: 'justify'}}>
|
<p style={{textAlign: "justify"}}>
|
||||||
Help making this software better by sending crash reports and anonymous usage statistics
|
Help making this software better by sending crash reports and anonymous usage statistics
|
||||||
</p>
|
</p>
|
||||||
<form onSubmit={start.bind(this)} style={{fontSize: '0.9em', marginTop: '10px'}}>
|
<form onSubmit={start.bind(this)} style={{fontSize: "0.9em", marginTop: "10px"}}>
|
||||||
<label>
|
<label>
|
||||||
<Input type="checkbox" style={{width: 'inherit', marginRight: '10px'}} onChange={(e) => this.enableLog(e.target.checked)} defaultChecked={config.log.telemetry.value} />
|
<Input type="checkbox" style={{width: "inherit", marginRight: "10px"}} onChange={(e) => this.enableLog(e.target.checked)} defaultChecked={config.log.telemetry.value} />
|
||||||
I accept but the data is not to be share with any third party
|
I accept but the data is not to be share with any third party
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -134,6 +134,7 @@ class MultiStepForm extends React.Component {
|
||||||
has_answered_password: false,
|
has_answered_password: false,
|
||||||
deps: []
|
deps: []
|
||||||
};
|
};
|
||||||
|
this.$input = createRef()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(){
|
componentDidMount(){
|
||||||
|
|
@ -166,17 +167,17 @@ class MultiStepForm extends React.Component {
|
||||||
<FormStage navleft={false} navright={this.state.has_answered_password === true} current={this.state.current} onStepChange={this.onStepChange.bind(this)}>
|
<FormStage navleft={false} navright={this.state.has_answered_password === true} current={this.state.current} onStepChange={this.onStepChange.bind(this)}>
|
||||||
Admin Password
|
Admin Password
|
||||||
</FormStage>
|
</FormStage>
|
||||||
<ReactCSSTransitionGroup transitionName="stepper-form" transitionEnterTimeout={600} transitionAppearTimeout={600} transitionAppear={true} transitionEnter={true} transitionLeave={false}>
|
<CSSTransition transitionName="stepper-form" transitionEnterTimeout={600} transitionAppearTimeout={600} transitionAppear={true} transitionEnter={true} transitionLeave={false}>
|
||||||
<div key={this.state.current}>
|
<div key={this.state.current}>
|
||||||
<p>Create your instance admin password: </p>
|
<p>Create your instance admin password: </p>
|
||||||
<form onSubmit={this.onAdminPassword.bind(this)}>
|
<form onSubmit={this.onAdminPassword.bind(this)}>
|
||||||
<Input ref="$input" type="password" placeholder="Password" value={this.state.answer_password} onChange={(e) => this.setState({answer_password: e.target.value})}/>
|
<Input ref={this.$input} type="password" placeholder="Password" value={this.state.answer_password} onChange={(e) => this.setState({answer_password: e.target.value})}/>
|
||||||
<Button theme="transparent">
|
<Button theme="transparent">
|
||||||
<Icon name={this.props.loading ? "loading" : "arrow_right"}/>
|
<Icon name={this.props.loading ? "loading" : "arrow_right"}/>
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</ReactCSSTransitionGroup>
|
</CSSTransition>
|
||||||
{hideMenu}
|
{hideMenu}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -186,7 +187,7 @@ class MultiStepForm extends React.Component {
|
||||||
<FormStage navleft={true} navright={false} current={this.state.current} onStepChange={this.onStepChange.bind(this)}>
|
<FormStage navleft={true} navright={false} current={this.state.current} onStepChange={this.onStepChange.bind(this)}>
|
||||||
Summary
|
Summary
|
||||||
</FormStage>
|
</FormStage>
|
||||||
<ReactCSSTransitionGroup transitionName="stepper-form" transitionEnterTimeout={600} transitionAppearTimeout={600} transitionAppear={true} transitionEnter={true} transitionLeave={false}>
|
<CSSTransition transitionName="stepper-form" transitionEnterTimeout={600} transitionAppearTimeout={600} transitionAppear={true} transitionEnter={true} transitionLeave={false}>
|
||||||
<div key={this.state.current}>
|
<div key={this.state.current}>
|
||||||
<NgIf cond={!!this.props.loading}>
|
<NgIf cond={!!this.props.loading}>
|
||||||
<Loader/>
|
<Loader/>
|
||||||
|
|
@ -204,7 +205,7 @@ class MultiStepForm extends React.Component {
|
||||||
}
|
}
|
||||||
</NgIf>
|
</NgIf>
|
||||||
</div>
|
</div>
|
||||||
</ReactCSSTransitionGroup>
|
</CSSTransition>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import React from "react";
|
import React, { Suspense } from "react";
|
||||||
import { BrowserRouter, Route, Switch } from "react-router-dom";
|
import { BrowserRouter, Route, Switch } from "react-router-dom";
|
||||||
import { NotFoundPage, ConnectPage, HomePage, SharePage, LogoutPage, FilesPage, ViewerPage } from "./pages/";
|
import { NotFoundPage, ConnectPage, HomePage, SharePage, LogoutPage, FilesPage, ViewerPage } from "./pages/";
|
||||||
import { URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT, URL_ADMIN, URL_SHARE } from "./helpers/";
|
import { URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT, URL_ADMIN, URL_SHARE } from "./helpers/";
|
||||||
import { Bundle, ModalPrompt, ModalAlert, ModalConfirm, Notification, UploadQueue } from "./components/";
|
import { ModalPrompt, ModalAlert, ModalConfirm, Notification, UploadQueue, LoadingPage } from "./components/";
|
||||||
|
|
||||||
const AdminPage = (props) => (
|
|
||||||
<Bundle loader={import(/* webpackChunkName: "admin" */"./pages/adminpage")} symbol="AdminPage">
|
const LazyAdminPage = React.lazy(() => import(/* webpackChunkName: "admin" */"./pages/adminpage"));
|
||||||
{(Comp) => <Comp {...props}/>}
|
const AdminPage = () => (
|
||||||
</Bundle>
|
<Suspense fallback={<LoadingPage/>}>
|
||||||
|
<LazyAdminPage/>
|
||||||
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default class AppRouter extends React.Component {
|
export default class AppRouter extends React.Component {
|
||||||
|
|
@ -26,8 +28,12 @@ export default class AppRouter extends React.Component {
|
||||||
<Route component={NotFoundPage} />
|
<Route component={NotFoundPage} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
{
|
||||||
|
/*
|
||||||
<ModalPrompt /> <ModalAlert /> <ModalConfirm />
|
<ModalPrompt /> <ModalAlert /> <ModalConfirm />
|
||||||
<Notification /> <UploadQueue/>
|
<Notification /> <UploadQueue/>
|
||||||
|
*/
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue