maintain (admin): admin page upgrade

This commit is contained in:
Mickael Kerjean 2021-08-16 02:09:24 +10:00
parent 6e27086ac4
commit 44fc901b4b
13 changed files with 346 additions and 349 deletions

View file

@ -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>
)
} }

View file

@ -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";

View file

@ -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);
} }

View file

@ -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){

View file

@ -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>
); );
}; };

View file

@ -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>
); );
} }

View file

@ -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{

View file

@ -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" /> );
}
} }

View file

@ -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>
);
} }

View file

@ -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>
); )
}
} }

View file

@ -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>
); );
}
} }

View file

@ -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>
); );
} }

View file

@ -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>
); );
} }