diff --git a/maubot/management/frontend/src/Home.js b/maubot/management/frontend/src/Home.js index 53a8466..464294d 100644 --- a/maubot/management/frontend/src/Home.js +++ b/maubot/management/frontend/src/Home.js @@ -18,7 +18,7 @@ import React, { Component } from "react" class Home extends Component { render() { return
- + Hello, {localStorage.username}
} } diff --git a/maubot/management/frontend/src/Login.js b/maubot/management/frontend/src/Login.js index 6788168..af95d07 100644 --- a/maubot/management/frontend/src/Login.js +++ b/maubot/management/frontend/src/Login.js @@ -14,6 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import React, { Component } from "react" +import Spinner from "./Spinner" +import api from "./api" class Login extends Component { constructor(props, context) { @@ -21,24 +23,38 @@ class Login extends Component { this.state = { username: "", password: "", + loading: false, + error: "", } } inputChanged = event => this.setState({ [event.target.name]: event.target.value }) - login = () => { - + login = async () => { + this.setState({ loading: true }) + const resp = await api.login(this.state.username, this.state.password) + if (resp.token) { + await this.props.onLogin(resp.token) + } else if (resp.error) { + this.setState({ error: resp.error, loading: false }) + } else { + this.setState({ error: "Unknown error", loading: false }) + console.log("Unknown error:", resp) + } } render() { return
-
-

Maubot Manager

+
+

Maubot Manager

- + + {this.state.error &&
{this.state.error}
}
} diff --git a/maubot/management/frontend/src/Router.js b/maubot/management/frontend/src/MaubotRouter.js similarity index 61% rename from maubot/management/frontend/src/Router.js rename to maubot/management/frontend/src/MaubotRouter.js index 3eb4faf..891f613 100644 --- a/maubot/management/frontend/src/Router.js +++ b/maubot/management/frontend/src/MaubotRouter.js @@ -18,21 +18,52 @@ import { BrowserRouter as Router, Route, Redirect } from "react-router-dom" import PrivateRoute from "./PrivateRoute" import Home from "./Home" import Login from "./Login" +import Spinner from "./Spinner" +import api from "./api" class MaubotRouter extends Component { constructor(props) { super(props) this.state = { - authed: localStorage.accessToken !== undefined, + pinged: false, + authed: false, } } + async componentWillMount() { + if (localStorage.accessToken) { + await this.ping() + } + this.setState({ pinged: true }) + } + + async ping() { + try { + const username = await api.ping() + if (username) { + localStorage.username = username + this.setState({ authed: true }) + } + } catch (err) { + console.error(err) + } + } + + login = async (token) => { + localStorage.accessToken = token + await this.ping() + } + render() { + if (!this.state.pinged) { + return + } return
}/> - + } + authed={!this.state.authed} to="/dashboard"/>
} diff --git a/maubot/management/frontend/src/PrivateRoute.js b/maubot/management/frontend/src/PrivateRoute.js index 2180e96..745078d 100644 --- a/maubot/management/frontend/src/PrivateRoute.js +++ b/maubot/management/frontend/src/PrivateRoute.js @@ -1,15 +1,27 @@ -import React, { Component } from "react" +// maubot - A plugin-based Matrix bot system. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import React from "react" import { Route, Redirect } from "react-router-dom" -const PrivateRoute = ({ component, authed, ...rest }) => ( +const PrivateRoute = ({ component, render, authed, to = "/login", ...args }) => ( authed === true - ? - : } + ? (component ? React.createElement(component, props) : render()) + : } /> ) diff --git a/maubot/management/frontend/src/Spinner.js b/maubot/management/frontend/src/Spinner.js index 4f227d4..77735f5 100644 --- a/maubot/management/frontend/src/Spinner.js +++ b/maubot/management/frontend/src/Spinner.js @@ -1,9 +1,11 @@ import React from "react" -const Spinner = () => ( -
+const Spinner = (props) => ( +
) + +export default Spinner diff --git a/maubot/management/frontend/src/api.js b/maubot/management/frontend/src/api.js index 712eb0d..7e1ef7f 100644 --- a/maubot/management/frontend/src/api.js +++ b/maubot/management/frontend/src/api.js @@ -16,14 +16,15 @@ const BASE_PATH = "/_matrix/maubot/v1" -export function login(username, password) { - return fetch(`${BASE_PATH}/auth/login`, { +export async function login(username, password) { + const resp = await fetch(`${BASE_PATH}/auth/login`, { method: "POST", body: JSON.stringify({ username, password, }), }) + return await resp.json() } function getHeaders(contentType = "application/json") { @@ -84,3 +85,10 @@ export async function getClient(id) { const resp = await fetch(`${BASE_PATH}/client/${id}`, { headers: getHeaders() }) return await resp.json() } + +export default { + login, ping, + getInstances, getInstance, + getPlugins, getPlugin, uploadPlugin, + getClients, getClient, +} diff --git a/maubot/management/frontend/src/index.js b/maubot/management/frontend/src/index.js index 9e9203f..cbe10ed 100644 --- a/maubot/management/frontend/src/index.js +++ b/maubot/management/frontend/src/index.js @@ -16,6 +16,6 @@ import React from "react" import ReactDOM from "react-dom" import "./style/index.sass" -import App from "./Router" +import App from "./MaubotRouter" ReactDOM.render(, document.getElementById("root")) diff --git a/maubot/management/frontend/src/style/base/body.sass b/maubot/management/frontend/src/style/base/body.sass index 01d90e9..462fe8c 100644 --- a/maubot/management/frontend/src/style/base/body.sass +++ b/maubot/management/frontend/src/style/base/body.sass @@ -34,6 +34,10 @@ body left: 0 right: 0 +.maubot-loading + margin-top: 10rem + width: 10rem + //.lindeb > header position: absolute diff --git a/maubot/management/frontend/src/style/base/elements.sass b/maubot/management/frontend/src/style/base/elements.sass index 10a183e..48fb045 100644 --- a/maubot/management/frontend/src/style/base/elements.sass +++ b/maubot/management/frontend/src/style/base/elements.sass @@ -14,9 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -=button() +=button($width: null, $height: null, $padding: .375rem 1rem) font-family: $font-stack - padding: .375rem 1rem + padding: $padding + width: $width + height: $height background-color: $background-color border: none border-radius: .25rem @@ -38,7 +40,7 @@ &:hover background-color: $dark-color -button, .button +.button +button &.main-color @@ -76,15 +78,17 @@ button, .button &:first-of-type:last-of-type border-radius: .25rem -input, textarea +=input($width: null, $height: null, $vertical-padding: .375rem, $horizontal-padding: 1rem, $font-size: 1rem) font-family: $font-stack border: 1px solid $border-color background-color: $background-color color: $text-color + width: $width + height: $height box-sizing: border-box border-radius: .25rem - padding: .375rem 1rem - font-size: 1rem + padding: $vertical-padding $horizontal-padding + font-size: $font-size resize: vertical &:hover, &:focus @@ -92,4 +96,13 @@ input, textarea &:focus border-width: 2px - padding: calc(.375rem - 1px) 1rem + padding: calc(#{$vertical-padding} - 1px) calc(#{$horizontal-padding} - 1px) + +.input, .textarea + +input + +=notification($color: $error-color) + padding: 1rem + border-radius: .25rem + border: 2px solid $color + background-color: lighten($color, 25%) diff --git a/maubot/management/frontend/src/style/index.sass b/maubot/management/frontend/src/style/index.sass index 20d193d..8486157 100644 --- a/maubot/management/frontend/src/style/index.sass +++ b/maubot/management/frontend/src/style/index.sass @@ -13,10 +13,10 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +@import lib/spinner + @import base/vars @import base/body @import base/elements -@import lib/spinner - @import pages/login diff --git a/maubot/management/frontend/src/style/lib/spinner.sass b/maubot/management/frontend/src/style/lib/spinner.sass index 3fb4cbc..d164586 100644 --- a/maubot/management/frontend/src/style/lib/spinner.sass +++ b/maubot/management/frontend/src/style/lib/spinner.sass @@ -2,14 +2,11 @@ $green: #008744 $blue: #0057e7 $red: #d62d20 $yellow: #ffa700 -$white: #eee -$width: 100px - -.loader +.spinner position: relative margin: 0 auto - width: $width + width: 5rem &:before content: "" @@ -34,6 +31,14 @@ $width: 100px animation: dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite stroke-linecap: round +=white-spinner() + circle + stroke: white !important + +=thick-spinner($thickness: 5) + svg > circle + stroke-width: $thickness + @keyframes rotate 100% transform: rotate(360deg) diff --git a/maubot/management/frontend/src/style/pages/login.sass b/maubot/management/frontend/src/style/pages/login.sass index f3d04fa..62b0891 100644 --- a/maubot/management/frontend/src/style/pages/login.sass +++ b/maubot/management/frontend/src/style/pages/login.sass @@ -21,24 +21,37 @@ .login width: 25rem - height: 23.5rem + height: 23rem display: inline-block box-sizing: border-box background-color: white border-radius: .25rem margin-top: 3rem - .title + h1 color: $main-color margin: 3rem 0 input, button - width: calc(100% - 5rem) margin: .5rem 2.5rem - padding: 1rem + height: 3rem + width: 20rem - input:focus - padding: calc(1rem - 1px) + input + +input button + +button($width: 20rem, $height: 3rem, $padding: 0) +main-color-button + + .spinner + +white-spinner + +thick-spinner + width: 2rem + + &.errored + height: 26.5rem + + .error + +notification($error-color) + margin: .5rem 2.5rem