mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-10-31 01:58:11 +08:00
maintain (admin): admin page upgrade
This commit is contained in:
@ -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
|
||||
*/
|
||||
export function CSSTransition({ transitionName = "animate", children = null, transitionAppearTimeout = 300 }) {
|
||||
const [child, setChildren] = useState(React.cloneElement(children, {
|
||||
className: `${children.props.className} ${transitionName}-appear`
|
||||
}));
|
||||
const [className, setClassName] = useState(`${transitionName} ${transitionName}-appear`);
|
||||
|
||||
useEffect(() => {
|
||||
setChildren(React.cloneElement(child, {
|
||||
className: `${children.props.className} ${transitionName}-appear ${transitionName}-appear-active`
|
||||
}))
|
||||
const timeout = setTimeout(() => {
|
||||
setChildren(React.cloneElement(child, {
|
||||
className: `${children.props.className}`
|
||||
}))
|
||||
}, transitionAppearTimeout);
|
||||
setClassName(`${transitionName} ${transitionName}-appear ${transitionName}-appear-active`)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
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 { FormBuilder } from "./formbuilder";
|
||||
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 "./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){
|
||||
super(props);
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||
const $loader = document.querySelector("#n-lder");
|
||||
|
||||
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();
|
||||
};
|
||||
function waitFor(n){
|
||||
|
||||
@ -1,118 +1,96 @@
|
||||
import React from 'react';
|
||||
import Path from 'path';
|
||||
import { Route, Switch, Link, NavLink } 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 React, { useState, useEffect } from "react";
|
||||
import Path from "path";
|
||||
import { Route, Switch, Link, NavLink, useRouteMatch } from "react-router-dom";
|
||||
|
||||
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){
|
||||
return class extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
isAdmin: null
|
||||
};
|
||||
this.admin = () => {
|
||||
Admin.isAdmin().then((t) => {
|
||||
this.setState({isAdmin: t});
|
||||
}).catch((err) => {
|
||||
notify.send("Error: " + (err && err.message) , "error");
|
||||
});
|
||||
};
|
||||
this.timeout = window.setInterval(this.admin.bind(this), 30 * 1000);
|
||||
let initIsAdmin = null;
|
||||
return function(props) {
|
||||
const [isAdmin, setIsAdmin] = useState(initIsAdmin);
|
||||
|
||||
const refresh = () => {
|
||||
Admin.isAdmin().then((t) => {
|
||||
initIsAdmin = t
|
||||
setIsAdmin(t)
|
||||
}).catch((err) => {
|
||||
notify.send("Error: " + (err && err.message) , "error");
|
||||
});
|
||||
}
|
||||
useEffect(() => {
|
||||
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(){
|
||||
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 />);
|
||||
}
|
||||
};
|
||||
return ( <LoadingPage /> );
|
||||
}
|
||||
}
|
||||
|
||||
@AdminOnly
|
||||
export class AdminPage extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
isAdmin: null,
|
||||
isSaving: false
|
||||
};
|
||||
}
|
||||
|
||||
isSaving(yesOrNo){
|
||||
this.setState({isSaving: yesOrNo});
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<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>
|
||||
export default AdminOnly((props) => {
|
||||
const match = useRouteMatch();
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
return (
|
||||
<div className="component_page_admin">
|
||||
<SideMenu url={match.url} isLoading={isSaving}/>
|
||||
<div className="page_container scroll-y">
|
||||
<CSSTransition key={location.pathname} transitionName="adminpage" transitionAppearTimeout={30000}>
|
||||
<Switch>
|
||||
<Route path={match.url + "/backend"} render={()=><BackendPage isSaving={setIsSaving}/>} />
|
||||
<Route path={match.url + "/settings"} render={()=><SettingsPage isSaving={setIsSaving}/>} />
|
||||
<Route path={match.url + "/logs"} render={() =><LogPage isSaving={setIsSaving}/>} />
|
||||
<Route path={match.url + "/setup"} component={SetupPage} />
|
||||
<Route path={match.url} component={HomePage} />
|
||||
</Switch>
|
||||
</CSSTransition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const SideMenu = (props) => {
|
||||
function SideMenu(props) {
|
||||
return (
|
||||
<div className="component_menu_sidebar no-select">
|
||||
{ props.isLoading ?
|
||||
<div className="header">
|
||||
<Icon name="arrow_left" style={{"opacity": 0}}/>
|
||||
<Icon name="loading" />
|
||||
</div> :
|
||||
<NavLink to="/" className="header">
|
||||
<Icon name="arrow_left" />
|
||||
<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") }
|
||||
{ props.isLoading ?
|
||||
<div className="header">
|
||||
<Icon name="arrow_left" style={{"opacity": 0}}/>
|
||||
<Icon name="loading" />
|
||||
</div> :
|
||||
<NavLink to="/" className="header">
|
||||
<Icon name="arrow_left" />
|
||||
<img src="/assets/logo/android-chrome-512x512.png" />
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink activeClassName="active" to={props.url + "/settings"}>
|
||||
{ t("Settings") }
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink activeClassName="active" to={props.url + "/logs"}>
|
||||
{ t("Logs") }
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
<h2>{ t("Admin console") }</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<NavLink activeClassName="active" to={props.url + "/backend"}>
|
||||
{ t("Backend") }
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink activeClassName="active" to={props.url + "/settings"}>
|
||||
{ t("Settings") }
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink activeClassName="active" to={props.url + "/logs"}>
|
||||
{ t("Logs") }
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
import { FormBuilder, Icon, Input, Alert } from "../../components/";
|
||||
import { Backend, Config } from "../../model/";
|
||||
import { FormObjToJSON, notify, format, createFormBackend } from "../../helpers/";
|
||||
import { t } from '../../locales/';
|
||||
import { t } from "../../locales/";
|
||||
|
||||
import "./backend.scss";
|
||||
|
||||
@ -26,7 +26,7 @@ export class BackendPage extends React.Component {
|
||||
let [backend, config] = data;
|
||||
this.setState({
|
||||
backend_available: backend,
|
||||
backend_enabled: window.CONFIG["connections"].map((conn) => {
|
||||
backend_enabled: window.CONFIG["connections"].filter((b) => b).map((conn) => {
|
||||
return createFormBackend(backend, conn);
|
||||
}),
|
||||
config: config
|
||||
@ -56,12 +56,12 @@ export class BackendPage extends React.Component {
|
||||
return Config.save(json, true, () => {
|
||||
this.props.isSaving(false);
|
||||
}, (err) => {
|
||||
notify.send(err && err.message || t('Oops'), 'error');
|
||||
notify.send(err && err.message || t("Oops"), "error");
|
||||
this.props.isSaving(false);
|
||||
});
|
||||
}
|
||||
|
||||
addBackend(backend_id){
|
||||
addBackend(backend_id){
|
||||
this.setState({
|
||||
backend_enabled: this.state.backend_enabled.concat(
|
||||
createFormBackend(this.state.backend_available, {
|
||||
@ -105,7 +105,7 @@ export class BackendPage extends React.Component {
|
||||
const isActiveBackend = (backend_key) => {
|
||||
return this.state.backend_enabled
|
||||
.map((b) => Object.keys(b)[0])
|
||||
.indexOf(backend_key) !== -1;
|
||||
.indexOf(backend_key) !== -1;
|
||||
};
|
||||
|
||||
const isActiveAuth = (auth_key) => {
|
||||
@ -134,7 +134,7 @@ export class BackendPage extends React.Component {
|
||||
</div>
|
||||
|
||||
<h2>Authentication Middleware</h2>
|
||||
|
||||
|
||||
<Alert>
|
||||
Integrate Filestash with your identity management system
|
||||
</Alert>
|
||||
@ -163,8 +163,6 @@ export class BackendPage extends React.Component {
|
||||
<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>
|
||||
</Alert>
|
||||
|
||||
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
@ -181,14 +179,14 @@ export class BackendPage extends React.Component {
|
||||
<div className="icons no-select" onClick={this.removeBackend.bind(this, index)}>
|
||||
<Icon name="delete" />
|
||||
</div>
|
||||
<FormBuilder onChange={this.onChange.bind(this)}
|
||||
idx={index}
|
||||
<FormBuilder onChange={this.onChange.bind(this)}
|
||||
idx={index}
|
||||
key={index}
|
||||
form={{"": backend_enable}}
|
||||
autoComplete="new-password"
|
||||
render={ ($input, props, struct, onChange) => {
|
||||
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))}/>
|
||||
);
|
||||
if(struct.label === "label"){
|
||||
@ -203,13 +201,13 @@ export class BackendPage extends React.Component {
|
||||
{ $checkbox }
|
||||
{ format(struct.label) }:
|
||||
</span>
|
||||
<div style={{width: '100%'}}>
|
||||
<div style={{width: "100%"}}>
|
||||
{ $input }
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="nothing"></span>
|
||||
<div style={{width: '100%'}}>
|
||||
<div style={{width: "100%"}}>
|
||||
{
|
||||
struct.description ? (<div className="description">{struct.description}</div>) : null
|
||||
}
|
||||
@ -224,7 +222,7 @@ export class BackendPage extends React.Component {
|
||||
}
|
||||
</form>
|
||||
</div> : <Alert>You need to enable a backend first.</Alert>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
.component_dashboard{
|
||||
|
||||
.alert { margin-top: -15px; }
|
||||
.box-container {
|
||||
display: flex;
|
||||
@ -41,6 +40,7 @@
|
||||
text-shadow: none;
|
||||
background: var(--emphasis-primary);
|
||||
padding: 18px 0;
|
||||
@media (max-width: 750px){ padding: 8px 0; }
|
||||
margin: 6px;
|
||||
opacity: 0.95;
|
||||
.icon{
|
||||
|
||||
@ -1,15 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import React from "react";
|
||||
import { Redirect } from "react-router-dom";
|
||||
|
||||
export class HomePage extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
stage: "loading"
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
return ( <Redirect to="/admin/backend" /> );
|
||||
}
|
||||
export function HomePage() {
|
||||
return ( <Redirect to="/admin/backend" /> );
|
||||
}
|
||||
|
||||
@ -1,91 +1,86 @@
|
||||
import React from 'react';
|
||||
import { FormBuilder, Loader, Button, Icon } from '../../components/';
|
||||
import { Config, Log } from '../../model/';
|
||||
import { FormObjToJSON, notify, format } from '../../helpers/';
|
||||
import { t } from '../../locales/';
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { FormBuilder, Loader, Button, Icon } from "../../components/";
|
||||
import { Config, Log } from "../../model/";
|
||||
import { FormObjToJSON, notify, format, nop } from "../../helpers/";
|
||||
import { t } from "../../locales/";
|
||||
|
||||
import "./logger.scss";
|
||||
|
||||
export class LogPage extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
form: {},
|
||||
log: "",
|
||||
config: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
Config.all().then((config) => {
|
||||
this.setState({
|
||||
form: {"":{"params":config["log"]}},
|
||||
config: FormObjToJSON(config)
|
||||
});
|
||||
});
|
||||
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);
|
||||
export function LogPage({ isSaving = nop }) {
|
||||
const [log, setLog] = useState("");
|
||||
const [form, setForm] = useState({});
|
||||
const [config, setConfig] = useState({});
|
||||
const $log = useRef();
|
||||
const filename = () => {
|
||||
const t = new Date().toISOString().substring(0,10).replace(/-/g, "");
|
||||
return `access_${t}.log`;
|
||||
};
|
||||
const onChange = (r) => {
|
||||
const c = Object.assign({}, config)
|
||||
c["log"] = r[""]["params"];
|
||||
c["connections"] = window.CONFIG.connections;
|
||||
delete c["constant"]
|
||||
isSaving(true);
|
||||
Config.save(c, true, () => {
|
||||
isSaving(false);
|
||||
}, () => {
|
||||
notify.send(err && err.message || t('Oops'), 'error');
|
||||
this.props.isSaving(false);
|
||||
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(){
|
||||
const filename = () => {
|
||||
let tmp = "access_";
|
||||
tmp += new Date().toISOString().substring(0,10).replace(/-/g, "");
|
||||
tmp += ".log";
|
||||
};
|
||||
return (
|
||||
<div className="component_logpage">
|
||||
<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>
|
||||
useEffect(() => {
|
||||
Config.all().then((config) => {
|
||||
setForm({"":{"params":config["log"]}});
|
||||
setConfig(FormObjToJSON(config));
|
||||
});
|
||||
fetchLogs();
|
||||
const id = setInterval(fetchLogs, 5000);
|
||||
return () => clearInterval(id)
|
||||
}, []);
|
||||
|
||||
<pre style={{height: '350px'}} ref="$log">
|
||||
{
|
||||
this.state.log === "" ? <Loader/> : this.state.log + "\n\n\n\n\n"
|
||||
}
|
||||
</pre>
|
||||
<div>
|
||||
<a href={Log.url()} download={filename()}><Button className="primary">{ t("Download") }</Button></a>
|
||||
</div>
|
||||
return (
|
||||
<div className="component_logpage">
|
||||
<h2>Logging</h2>
|
||||
<div style={{minHeight: "150px"}}>
|
||||
<FormBuilder
|
||||
form={form}
|
||||
onChange={onChange}
|
||||
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>
|
||||
);
|
||||
}
|
||||
<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 { Redirect } from 'react-router';
|
||||
import React, { useState, useRef } from "react";
|
||||
import { Redirect } from "react-router";
|
||||
|
||||
import { Input, Button, Container, Icon, Loader } from '../../components/';
|
||||
import { Config, Admin } from '../../model/';
|
||||
import { t } from '../../locales/';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
import { Input, Button, Container, Icon } from "../../components/";
|
||||
import { Admin } from "../../model/";
|
||||
import { nop } from "../../helpers/";
|
||||
import { t } from "../../locales/";
|
||||
|
||||
export class LoginPage extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: false,
|
||||
error: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.refs.$input.ref.focus();
|
||||
}
|
||||
|
||||
authenticate(e){
|
||||
export function LoginPage({ reload = nop }) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const $input = useRef();
|
||||
const marginTop = () => ({ marginTop: `${parseInt(window.innerHeight / 3)}px` })
|
||||
const authenticate = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({loading: true});
|
||||
Admin.login(this.refs.$input.ref.value)
|
||||
.then(() => this.props.reload())
|
||||
.catch(() => {
|
||||
this.refs.$input.ref.value = "";
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: true
|
||||
}, () => {
|
||||
window.setTimeout(() => {
|
||||
this.setState({error: false});
|
||||
}, 500);
|
||||
});
|
||||
setIsLoading(true);
|
||||
Admin.login($input.current.ref.value)
|
||||
.then(() => reload())
|
||||
.catch(() => {
|
||||
$input.current.ref.value = "";
|
||||
setIsLoading(false)
|
||||
setHasError(true);
|
||||
setTimeout(() => {
|
||||
setHasError(false);
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
const marginTop = () => { return {marginTop: parseInt(window.innerHeight / 3)+'px'};};
|
||||
useRef(() => {
|
||||
$input.current.ref.focus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container maxWidth="300px" className="sharepage_component">
|
||||
<form className={this.state.error ? "error" : ""} onSubmit={this.authenticate.bind(this)} style={marginTop()}>
|
||||
<Input ref="$input" type="password" placeholder={ t("Password") } />
|
||||
|
||||
return (
|
||||
<Container maxWidth="300px" className="sharepage_component">
|
||||
<form className={hasError ? "error" : ""} onSubmit={authenticate} style={marginTop()}>
|
||||
<Input ref={$input} type="password" placeholder={ t("Password") } />
|
||||
<Button theme="transparent">
|
||||
<Icon name={this.state.loading ? "loading" : "arrow_right"}/>
|
||||
<Icon name={isLoading ? "loading" : "arrow_right"}/>
|
||||
</Button>
|
||||
</form>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
</form>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,26 +1,12 @@
|
||||
import React from 'react';
|
||||
import { FormBuilder } from '../../components/';
|
||||
import { Config } from '../../model/';
|
||||
import { format, notify } from '../../helpers';
|
||||
import { t } from '../../locales/';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { FormBuilder } from "../../components/";
|
||||
import { Config } from "../../model/";
|
||||
import { format, notify, nop } from "../../helpers";
|
||||
import { t } from "../../locales/";
|
||||
|
||||
export class SettingsPage extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
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){
|
||||
export function SettingsPage({ isSaving = nop }) {
|
||||
const [form, setForm] = useState({});
|
||||
const format = (name) => {
|
||||
if(typeof name !== "string"){
|
||||
return "N/A";
|
||||
}
|
||||
@ -34,45 +20,52 @@ export class SettingsPage extends React.Component {
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
onChange(form){
|
||||
form.connections = window.CONFIG.connections;
|
||||
this.props.isSaving(true);
|
||||
Config.save(form, true, () => {
|
||||
this.props.isSaving(false);
|
||||
const onChange = (_form) => {
|
||||
_form.connections = window.CONFIG.connections;
|
||||
delete _form.constant;
|
||||
refresh(Math.random())
|
||||
isSaving(true)
|
||||
Config.save(_form, true, () => {
|
||||
isSaving(false)
|
||||
}, (err) => {
|
||||
notify.send(err && err.message || t('Oops'), 'error');
|
||||
this.props.isSaving(false);
|
||||
isSaving(false)
|
||||
notify.send(err && err.message || t("Oops"), "error");
|
||||
});
|
||||
}
|
||||
const [_, refresh] = useState(null);
|
||||
|
||||
render(){
|
||||
return (
|
||||
<form className="sticky">
|
||||
<FormBuilder form={this.state.form}
|
||||
onChange={this.onChange.bind(this)}
|
||||
autoComplete="new-password"
|
||||
render={ ($input, props, struct, onChange) => {
|
||||
return (
|
||||
<label className={"no-select input_type_" + props.params["type"]}>
|
||||
<div>
|
||||
useEffect(() => {
|
||||
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
|
||||
setForm(c)
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<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>
|
||||
{ format(struct.label) }:
|
||||
{ format(struct.label) }:
|
||||
</span>
|
||||
<div style={{width: '100%'}}>
|
||||
{ $input }
|
||||
<div style={{width: "100%"}}>
|
||||
{ $input }
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="nothing"></span>
|
||||
<div style={{width: '100%'}}>
|
||||
{ struct.description ? (<div className="description">{struct.description}</div>) : null }
|
||||
<div style={{width: "100%"}}>
|
||||
{ struct.description ? (<div className="description">{struct.description}</div>) : null }
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
}}/>
|
||||
</div>
|
||||
</label>
|
||||
)} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { createRef } from "react";
|
||||
|
||||
import { Input, Button, Container, Icon, NgIf, Loader } from '../../components/';
|
||||
import { Config, Admin } from '../../model/';
|
||||
import { notify, FormObjToJSON, alert, prompt } from '../../helpers';
|
||||
import { bcrypt_password } from '../../helpers/bcrypt';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
import { Input, Button, Container, Icon, NgIf, Loader, CSSTransition } from "../../components/";
|
||||
import { Config, Admin } from "../../model/";
|
||||
import { notify, FormObjToJSON, alert, prompt } from "../../helpers";
|
||||
import { bcrypt_password } from "../../helpers/bcrypt";
|
||||
//import ReactCSSTransitionGroup from "react-addons-css-transition-group";
|
||||
|
||||
import "./setup.scss";
|
||||
|
||||
@ -28,12 +28,12 @@ export class SetupPage extends React.Component {
|
||||
this.unlisten();
|
||||
alert.now((
|
||||
<div>
|
||||
<p style={{textAlign: 'justify'}}>
|
||||
<p style={{textAlign: "justify"}}>
|
||||
Help making this software better by sending crash reports and anonymous usage statistics
|
||||
</p>
|
||||
<form onSubmit={start.bind(this)} style={{fontSize: '0.9em', marginTop: '10px'}}>
|
||||
<form onSubmit={start.bind(this)} style={{fontSize: "0.9em", marginTop: "10px"}}>
|
||||
<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
|
||||
</label>
|
||||
</form>
|
||||
@ -134,6 +134,7 @@ class MultiStepForm extends React.Component {
|
||||
has_answered_password: false,
|
||||
deps: []
|
||||
};
|
||||
this.$input = createRef()
|
||||
}
|
||||
|
||||
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)}>
|
||||
Admin Password
|
||||
</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}>
|
||||
<p>Create your instance admin password: </p>
|
||||
<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">
|
||||
<Icon name={this.props.loading ? "loading" : "arrow_right"}/>
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</ReactCSSTransitionGroup>
|
||||
</CSSTransition>
|
||||
{hideMenu}
|
||||
</div>
|
||||
);
|
||||
@ -186,7 +187,7 @@ class MultiStepForm extends React.Component {
|
||||
<FormStage navleft={true} navright={false} current={this.state.current} onStepChange={this.onStepChange.bind(this)}>
|
||||
Summary
|
||||
</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}>
|
||||
<NgIf cond={!!this.props.loading}>
|
||||
<Loader/>
|
||||
@ -204,7 +205,7 @@ class MultiStepForm extends React.Component {
|
||||
}
|
||||
</NgIf>
|
||||
</div>
|
||||
</ReactCSSTransitionGroup>
|
||||
</CSSTransition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import React from "react";
|
||||
import React, { Suspense } from "react";
|
||||
import { BrowserRouter, Route, Switch } from "react-router-dom";
|
||||
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 { 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">
|
||||
{(Comp) => <Comp {...props}/>}
|
||||
</Bundle>
|
||||
|
||||
const LazyAdminPage = React.lazy(() => import(/* webpackChunkName: "admin" */"./pages/adminpage"));
|
||||
const AdminPage = () => (
|
||||
<Suspense fallback={<LoadingPage/>}>
|
||||
<LazyAdminPage/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
export default class AppRouter extends React.Component {
|
||||
@ -26,8 +28,12 @@ export default class AppRouter extends React.Component {
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
{
|
||||
/*
|
||||
<ModalPrompt /> <ModalAlert /> <ModalConfirm />
|
||||
<Notification /> <UploadQueue/>
|
||||
*/
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user