From b0d782906b30064c8e69ea45360387a4d4b1808d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 10 Nov 2018 16:33:12 +0200 Subject: [PATCH] Implement instance view --- maubot/instance.py | 17 ++ maubot/management/api/instance.py | 1 + maubot/management/frontend/package.json | 3 +- maubot/management/frontend/src/api.js | 101 ++++----- .../src/components/PreferenceTable.js | 7 + .../frontend/src/pages/dashboard/Client.js | 8 +- .../frontend/src/pages/dashboard/Instance.js | 157 ++++++++++++- .../frontend/src/pages/dashboard/index.js | 13 +- .../src/style/lib/preferencetable.sass | 4 + .../src/style/pages/client/index.sass | 23 -- .../frontend/src/style/pages/dashboard.sass | 23 ++ .../frontend/src/style/pages/instance.sass | 14 +- maubot/management/frontend/yarn.lock | 206 +++++++++++++++++- 13 files changed, 478 insertions(+), 99 deletions(-) diff --git a/maubot/instance.py b/maubot/instance.py index 00f8be7..c797466 100644 --- a/maubot/instance.py +++ b/maubot/instance.py @@ -186,10 +186,27 @@ class PluginInstance: self.db_instance.primary_user = client.id self.client.references.remove(self) self.client = client + self.client.references.add(self) await self.start() self.log.debug(f"Primary user switched to {self.client.id}") return True + async def update_type(self, type: str) -> bool: + if not type or type == self.type: + return True + try: + loader = PluginLoader.find(type) + except KeyError: + return False + await self.stop() + self.db_instance.type = loader.id + self.loader.references.remove(self) + self.loader = loader + self.loader.references.add(self) + await self.start() + self.log.debug(f"Type switched to {self.loader.id}") + return True + async def update_started(self, started: bool) -> None: if started is not None and started != self.started: await (self.start() if started else self.stop()) diff --git a/maubot/management/api/instance.py b/maubot/management/api/instance.py index 57cf2f3..ad7f429 100644 --- a/maubot/management/api/instance.py +++ b/maubot/management/api/instance.py @@ -70,6 +70,7 @@ async def _update_instance(instance: PluginInstance, data: dict) -> web.Response instance.update_enabled(data.get("enabled", None)) instance.update_config(data.get("config", None)) await instance.update_started(data.get("started", None)) + await instance.update_type(data.get("type", None)) instance.db.commit() return resp.updated(instance.to_dict()) diff --git a/maubot/management/frontend/package.json b/maubot/management/frontend/package.json index 75765fa..7bd08ef 100644 --- a/maubot/management/frontend/package.json +++ b/maubot/management/frontend/package.json @@ -7,7 +7,8 @@ "react": "^16.6.0", "react-dom": "^16.6.0", "react-router-dom": "^4.3.1", - "react-scripts": "2.0.5" + "react-scripts": "2.0.5", + "react-select": "^2.1.1" }, "scripts": { "start": "react-scripts start", diff --git a/maubot/management/frontend/src/api.js b/maubot/management/frontend/src/api.js index 6342b9d..e266383 100644 --- a/maubot/management/frontend/src/api.js +++ b/maubot/management/frontend/src/api.js @@ -16,6 +16,40 @@ export const BASE_PATH = "/_matrix/maubot/v1" +function getHeaders(contentType = "application/json") { + return { + "Content-Type": contentType, + "Authorization": `Bearer ${localStorage.accessToken}`, + } +} + +async function defaultDelete(type, id) { + const resp = await fetch(`${BASE_PATH}/${type}/${id}`, { + headers: getHeaders(), + method: "DELETE", + }) + if (resp.status === 204) { + return { + "success": true, + } + } + return await resp.json() +} + +async function defaultPut(type, entry, id = undefined) { + const resp = await fetch(`${BASE_PATH}/${type}/${id || entry.id}`, { + headers: getHeaders(), + body: JSON.stringify(entry), + method: "PUT", + }) + return await resp.json() +} + +async function defaultGet(url) { + const resp = await fetch(`${BASE_PATH}/${url}`, { headers: getHeaders() }) + return await resp.json() +} + export async function login(username, password) { const resp = await fetch(`${BASE_PATH}/auth/login`, { method: "POST", @@ -27,13 +61,6 @@ export async function login(username, password) { return await resp.json() } -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", @@ -48,25 +75,13 @@ export async function ping() { throw json } -export async function getInstances() { - const resp = await fetch(`${BASE_PATH}/instances`, { headers: getHeaders() }) - return await resp.json() -} +export const getInstances = () => defaultGet("/instances") +export const getInstance = id => defaultGet(`/instance/${id}`) +export const putInstance = (instance, id) => defaultPut("instance", instance, id) +export const deleteInstance = id => defaultDelete("instance", id) -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 const getPlugins = () => defaultGet("/plugins") +export const getPlugin = id => defaultGet(`/plugin/${id}`) export async function uploadPlugin(data, id) { let resp @@ -86,15 +101,8 @@ export async function uploadPlugin(data, id) { 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() -} +export const getClients = () => defaultGet("/clients") +export const getClient = id => defaultGet(`/clients/${id}`) export async function uploadAvatar(id, data, mime) { const resp = await fetch(`${BASE_PATH}/client/${id}/avatar`, { @@ -109,32 +117,13 @@ export function getAvatarURL(id) { return `${BASE_PATH}/client/${id}/avatar?access_token=${localStorage.accessToken}` } -export async function putClient(client) { - const resp = await fetch(`${BASE_PATH}/client/${client.id}`, { - headers: getHeaders(), - body: JSON.stringify(client), - method: "PUT", - }) - return await resp.json() -} - -export async function deleteClient(id) { - const resp = await fetch(`${BASE_PATH}/client/${id}`, { - headers: getHeaders(), - method: "DELETE", - }) - if (resp.status === 204) { - return { - "success": true, - } - } - return await resp.json() -} +export const putClient = client => defaultPut("client", client) +export const deleteClient = id => defaultDelete("client", id) export default { BASE_PATH, login, ping, - getInstances, getInstance, + getInstances, getInstance, putInstance, deleteInstance, getPlugins, getPlugin, uploadPlugin, getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient, } diff --git a/maubot/management/frontend/src/components/PreferenceTable.js b/maubot/management/frontend/src/components/PreferenceTable.js index 28ddfab..b58c55b 100644 --- a/maubot/management/frontend/src/components/PreferenceTable.js +++ b/maubot/management/frontend/src/components/PreferenceTable.js @@ -14,6 +14,7 @@ // 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 Select from "react-select" import Switch from "./Switch" export const PrefTable = ({ children, wrapperClass }) => { @@ -52,4 +53,10 @@ export const PrefSwitch = ({ rowName, ...args }) => ( ) +export const PrefSelect = ({ rowName, ...args }) => ( + +