diff --git a/maubot/management/frontend/package.json b/maubot/management/frontend/package.json index c5cf653..9ecf46a 100644 --- a/maubot/management/frontend/package.json +++ b/maubot/management/frontend/package.json @@ -6,6 +6,7 @@ "node-sass": "^4.9.4", "react": "^16.6.0", "react-dom": "^16.6.0", + "react-router-dom": "^4.3.1", "react-scripts": "2.0.5" }, "scripts": { diff --git a/maubot/management/frontend/src/MaubotManager.js b/maubot/management/frontend/src/Home.js similarity index 75% rename from maubot/management/frontend/src/MaubotManager.js rename to maubot/management/frontend/src/Home.js index 314f47a..53a8466 100644 --- a/maubot/management/frontend/src/MaubotManager.js +++ b/maubot/management/frontend/src/Home.js @@ -15,19 +15,12 @@ // along with this program. If not, see . import React, { Component } from "react" -class MaubotManager extends Component { +class Home extends Component { render() { - return ( -
-
+ return
-
-
- -
-
- ) + } } -export default MaubotManager +export default Home diff --git a/maubot/management/frontend/src/Login.js b/maubot/management/frontend/src/Login.js new file mode 100644 index 0000000..6788168 --- /dev/null +++ b/maubot/management/frontend/src/Login.js @@ -0,0 +1,47 @@ +// 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, { Component } from "react" + +class Login extends Component { + constructor(props, context) { + super(props, context) + this.state = { + username: "", + password: "", + } + } + + inputChanged = event => this.setState({ [event.target.name]: event.target.value }) + + login = () => { + + } + + render() { + return
+
+

Maubot Manager

+ + + +
+
+ } +} + +export default Login diff --git a/maubot/management/frontend/src/PrivateRoute.js b/maubot/management/frontend/src/PrivateRoute.js new file mode 100644 index 0000000..2180e96 --- /dev/null +++ b/maubot/management/frontend/src/PrivateRoute.js @@ -0,0 +1,16 @@ +import React, { Component } from "react" +import { Route, Redirect } from "react-router-dom" + +const PrivateRoute = ({ component, authed, ...rest }) => ( + authed === true + ? + : } + /> +) + +export default PrivateRoute diff --git a/maubot/management/frontend/src/Router.js b/maubot/management/frontend/src/Router.js new file mode 100644 index 0000000..3eb4faf --- /dev/null +++ b/maubot/management/frontend/src/Router.js @@ -0,0 +1,41 @@ +// 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, { Component } from "react" +import { BrowserRouter as Router, Route, Redirect } from "react-router-dom" +import PrivateRoute from "./PrivateRoute" +import Home from "./Home" +import Login from "./Login" + +class MaubotRouter extends Component { + constructor(props) { + super(props) + this.state = { + authed: localStorage.accessToken !== undefined, + } + } + + render() { + return +
+ }/> + + +
+
+ } +} + +export default MaubotRouter diff --git a/maubot/management/frontend/src/Spinner.js b/maubot/management/frontend/src/Spinner.js new file mode 100644 index 0000000..4f227d4 --- /dev/null +++ b/maubot/management/frontend/src/Spinner.js @@ -0,0 +1,9 @@ +import React from "react" + +const Spinner = () => ( +
+ + + +
+) diff --git a/maubot/management/frontend/src/api.js b/maubot/management/frontend/src/api.js new file mode 100644 index 0000000..712eb0d --- /dev/null +++ b/maubot/management/frontend/src/api.js @@ -0,0 +1,86 @@ +// 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 . + +const BASE_PATH = "/_matrix/maubot/v1" + +export function login(username, password) { + return fetch(`${BASE_PATH}/auth/login`, { + method: "POST", + body: JSON.stringify({ + username, + password, + }), + }) +} + +function getHeaders(contentType = "application/json") { + return { + "Content-Type": contentType, + "Authorization": `Bearer ${localStorage.accessToken}`, + } +} + +export async function ping() { + const response = await fetch(`${BASE_PATH}/auth/ping`, { + method: "POST", + headers: getHeaders(), + }) + const json = await response.json() + if (json.username) { + return json.username + } else if (json.errcode === "auth_token_missing" || json.errcode === "auth_token_invalid") { + return null + } + throw json +} + +export async function getInstances() { + const resp = await fetch(`${BASE_PATH}/instances`, { headers: getHeaders() }) + return await resp.json() +} + +export async function getInstance(id) { + const resp = await fetch(`${BASE_PATH}/instance/${id}`, { headers: getHeaders() }) + return await resp.json() +} + +export async function getPlugins() { + const resp = await fetch(`${BASE_PATH}/plugins`, { headers: getHeaders() }) + return await resp.json() +} + +export async function getPlugin(id) { + const resp = await fetch(`${BASE_PATH}/plugin/${id}`, { headers: getHeaders() }) + return await resp.json() +} + +export async function uploadPlugin(data) { + const resp = await fetch(`${BASE_PATH}/plugins/upload`, { + headers: getHeaders("application/zip"), + body: data, + }) + return await resp.json() +} + +export async function getClients() { + const resp = await fetch(`${BASE_PATH}/clients`, { headers: getHeaders() }) + return await resp.json() +} + +export async function getClient(id) { + const resp = await fetch(`${BASE_PATH}/client/${id}`, { headers: getHeaders() }) + return await resp.json() +} diff --git a/maubot/management/frontend/src/index.js b/maubot/management/frontend/src/index.js index 8f50972..9e9203f 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 MaubotManager from "./MaubotManager" +import App from "./Router" -ReactDOM.render(, document.getElementById("root")) +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 b45c068..01d90e9 100644 --- a/maubot/management/frontend/src/style/base/body.sass +++ b/maubot/management/frontend/src/style/base/body.sass @@ -27,6 +27,13 @@ body right: 0 left: 0 +.maubot-wrapper + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + //.lindeb > header position: absolute diff --git a/maubot/management/frontend/src/style/base/elements.sass b/maubot/management/frontend/src/style/base/elements.sass new file mode 100644 index 0000000..10a183e --- /dev/null +++ b/maubot/management/frontend/src/style/base/elements.sass @@ -0,0 +1,95 @@ +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +=button() + font-family: $font-stack + padding: .375rem 1rem + background-color: $background-color + border: none + border-radius: .25rem + color: $inverted-text-color + box-sizing: border-box + font-size: 1rem + cursor: pointer + + &:hover + background-color: darken($background-color, 10%) + +=link-button() + display: inline-block + text-align: center + text-decoration: none + +=main-color-button() + background-color: $main-color + &:hover + background-color: $dark-color + +button, .button + +button + + &.main-color + +main-color-button + +=button-group() + width: 100% + display: flex + > button, > .button + flex: 1 + border-radius: 0 + + &:first-of-type + border-radius: .25rem 0 0 .25rem + + &:last-of-type + border-radius: 0 .25rem .25rem 0 + + &:first-of-type:last-of-type + border-radius: .25rem + +=vertical-button-group() + display: flex + flex-direction: column + > button, > .button + flex: 1 + border-radius: 0 + + &:first-of-type + border-radius: .25rem .25rem 0 0 + + &:last-of-type + border-radius: 0 0 .25rem .25rem + + &:first-of-type:last-of-type + border-radius: .25rem + +input, textarea + font-family: $font-stack + border: 1px solid $border-color + background-color: $background-color + color: $text-color + box-sizing: border-box + border-radius: .25rem + padding: .375rem 1rem + font-size: 1rem + resize: vertical + + &:hover, &:focus + border-color: $main-color + + &:focus + border-width: 2px + padding: calc(.375rem - 1px) 1rem diff --git a/maubot/management/frontend/src/style/index.sass b/maubot/management/frontend/src/style/index.sass index d3d2eaa..20d193d 100644 --- a/maubot/management/frontend/src/style/index.sass +++ b/maubot/management/frontend/src/style/index.sass @@ -15,3 +15,8 @@ // along with this program. If not, see . @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 new file mode 100644 index 0000000..3fb4cbc --- /dev/null +++ b/maubot/management/frontend/src/style/lib/spinner.sass @@ -0,0 +1,60 @@ +$green: #008744 +$blue: #0057e7 +$red: #d62d20 +$yellow: #ffa700 +$white: #eee + +$width: 100px + +.loader + position: relative + margin: 0 auto + width: $width + + &:before + content: "" + display: block + padding-top: 100% + + svg + animation: rotate 2s linear infinite + height: 100% + transform-origin: center center + width: 100% + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + margin: auto + + circle + stroke-dasharray: 1, 200 + stroke-dashoffset: 0 + animation: dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite + stroke-linecap: round + +@keyframes rotate + 100% + transform: rotate(360deg) + +@keyframes dash + 0% + stroke-dasharray: 1, 200 + stroke-dashoffset: 0 + 50% + stroke-dasharray: 89, 200 + stroke-dashoffset: -35px + 100% + stroke-dasharray: 89, 200 + stroke-dashoffset: -124px + +@keyframes color + 100%, 0% + stroke: $red + 40% + stroke: $blue + 66% + stroke: $green + 80%, 90% + stroke: $yellow diff --git a/maubot/management/frontend/src/style/pages/login.sass b/maubot/management/frontend/src/style/pages/login.sass new file mode 100644 index 0000000..f3d04fa --- /dev/null +++ b/maubot/management/frontend/src/style/pages/login.sass @@ -0,0 +1,44 @@ +// 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 . + +.maubot-wrapper:not(.authenticated) + background-color: $main-color + + text-align: center + +.login + width: 25rem + height: 23.5rem + display: inline-block + box-sizing: border-box + background-color: white + border-radius: .25rem + margin-top: 3rem + + .title + color: $main-color + margin: 3rem 0 + + input, button + width: calc(100% - 5rem) + margin: .5rem 2.5rem + padding: 1rem + + input:focus + padding: calc(1rem - 1px) + + button + +main-color-button diff --git a/maubot/management/frontend/yarn.lock b/maubot/management/frontend/yarn.lock index 64d5190..9b58f83 100644 --- a/maubot/management/frontend/yarn.lock +++ b/maubot/management/frontend/yarn.lock @@ -4316,6 +4316,17 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== +history@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" + integrity sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA== + dependencies: + invariant "^2.2.1" + loose-envify "^1.2.0" + resolve-pathname "^2.2.0" + value-equal "^0.4.0" + warning "^3.0.0" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -4330,6 +4341,11 @@ hoek@4.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== +hoist-non-react-statics@^2.5.0: + version "2.5.5" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" + integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== + home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -4643,7 +4659,7 @@ internal-ip@^3.0.1: default-gateway "^2.6.0" ipaddr.js "^1.5.2" -invariant@^2.2.0, invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -5851,7 +5867,7 @@ loglevel@^1.4.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -6895,6 +6911,13 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30= + dependencies: + isarray "0.0.1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -7698,7 +7721,7 @@ prompts@^0.1.9: kleur "^2.0.1" sisteransi "^0.1.1" -prop-types@^15.6.2: +prop-types@^15.6.1, prop-types@^15.6.2: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ== @@ -7916,6 +7939,31 @@ react-error-overlay@^5.0.5: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.0.5.tgz#716ff1a92fda76bb89a39adf9ce046a5d740e71a" integrity sha512-ab0HWBgxdIsngHtMGU8+8gYFdTBXpUGd4AE4lN2VZvOIlBmWx9dtaWEViihuGSIGosCKPeHCnzFoRWB42UtnLg== +react-router-dom@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" + integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA== + dependencies: + history "^4.7.2" + invariant "^2.2.4" + loose-envify "^1.3.1" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" + +react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" + integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg== + dependencies: + history "^4.7.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.6.1" + warning "^4.0.1" + react-scripts@2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-2.0.5.tgz#74b8e9fa6a7c5f0f11221dd18c10df2ae3df3d69" @@ -8303,6 +8351,11 @@ resolve-from@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha1-six699nWiBvItuZTM17rywoYh0g= +resolve-pathname@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" + integrity sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -9559,6 +9612,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +value-equal@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" + integrity sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw== + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -9599,6 +9657,20 @@ walker@~1.0.5: dependencies: makeerror "1.0.x" +warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" + integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w= + dependencies: + loose-envify "^1.0.0" + +warning@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" + integrity sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug== + dependencies: + loose-envify "^1.0.0" + watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986"