Add fancier server selector
This commit is contained in:
parent
924f627c58
commit
e5e614fc4b
6 changed files with 73 additions and 18 deletions
|
@ -47,7 +47,7 @@ def generate_mac(secret: str, nonce: str, user: str, password: str, admin: bool
|
||||||
|
|
||||||
@routes.get("/client/auth/servers")
|
@routes.get("/client/auth/servers")
|
||||||
async def get_registerable_servers(_: web.Request) -> web.Response:
|
async def get_registerable_servers(_: web.Request) -> web.Response:
|
||||||
return web.json_response(list(registration_secrets().keys()))
|
return web.json_response({key: value["url"] for key, value in registration_secrets().items()})
|
||||||
|
|
||||||
|
|
||||||
AuthRequestInfo = NamedTuple("AuthRequestInfo", api=HTTPAPI, secret=str, username=str, password=str)
|
AuthRequestInfo = NamedTuple("AuthRequestInfo", api=HTTPAPI, secret=str, username=str, password=str)
|
||||||
|
|
|
@ -410,13 +410,14 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: object
|
||||||
items:
|
description: Key-value map from server name to homeserver URL
|
||||||
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
|
description: The homeserver URL
|
||||||
example:
|
example:
|
||||||
- maunium.net
|
maunium.net: https://maunium.net
|
||||||
- example.com
|
example.com: https://matrix.example.org
|
||||||
- matrix.org
|
|
||||||
401:
|
401:
|
||||||
$ref: '#/components/responses/Unauthorized'
|
$ref: '#/components/responses/Unauthorized'
|
||||||
'/client/auth/{server}/register':
|
'/client/auth/{server}/register':
|
||||||
|
|
|
@ -36,8 +36,8 @@ async function defaultDelete(type, id) {
|
||||||
return await resp.json()
|
return await resp.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function defaultPut(type, entry, id = undefined) {
|
async function defaultPut(type, entry, id = undefined, suffix = undefined) {
|
||||||
const resp = await fetch(`${BASE_PATH}/${type}/${id || entry.id}`, {
|
const resp = await fetch(`${BASE_PATH}/${type}/${id || entry.id}${suffix}`, {
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
body: JSON.stringify(entry),
|
body: JSON.stringify(entry),
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
|
@ -221,6 +221,17 @@ export function getAvatarURL({ id, avatar_url }) {
|
||||||
export const putClient = client => defaultPut("client", client)
|
export const putClient = client => defaultPut("client", client)
|
||||||
export const deleteClient = id => defaultDelete("client", id)
|
export const deleteClient = id => defaultDelete("client", id)
|
||||||
|
|
||||||
|
export const getClientAuthServers = () => defaultGet("/client/auth/servers")
|
||||||
|
|
||||||
|
export async function doClientAuth(server, type, username, password) {
|
||||||
|
const resp = await fetch(`${BASE_PATH}/client/auth/${server}/${type}`, {
|
||||||
|
headers: getHeaders(),
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
return await resp.json()
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
BASE_PATH,
|
BASE_PATH,
|
||||||
login, ping, getFeatures, remoteGetFeatures,
|
login, ping, getFeatures, remoteGetFeatures,
|
||||||
|
@ -230,4 +241,5 @@ export default {
|
||||||
getInstanceDatabase, queryInstanceDatabase,
|
getInstanceDatabase, queryInstanceDatabase,
|
||||||
getPlugins, getPlugin, uploadPlugin, deletePlugin,
|
getPlugins, getPlugin, uploadPlugin, deletePlugin,
|
||||||
getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient,
|
getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient,
|
||||||
|
getClientAuthServers, doClientAuth
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import Select from "react-select"
|
import Select from "react-select"
|
||||||
|
import CreatableSelect from "react-select/creatable"
|
||||||
import Switch from "./Switch"
|
import Switch from "./Switch"
|
||||||
|
|
||||||
export const PrefTable = ({ children, wrapperClass }) => {
|
export const PrefTable = ({ children, wrapperClass }) => {
|
||||||
|
@ -56,10 +57,12 @@ export const PrefSwitch = ({ rowName, active, origActive, fullWidth = false, ...
|
||||||
</PrefRow>
|
</PrefRow>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const PrefSelect = ({ rowName, value, origValue, fullWidth = false, ...args }) => (
|
export const PrefSelect = ({ rowName, value, origValue, fullWidth = false, creatable = false, ...args }) => (
|
||||||
<PrefRow name={rowName} fullWidth={fullWidth} labelFor={rowName}
|
<PrefRow name={rowName} fullWidth={fullWidth} labelFor={rowName}
|
||||||
changed={origValue !== undefined && value.id !== origValue}>
|
changed={origValue !== undefined && value.id !== origValue}>
|
||||||
<Select className="select" {...args} id={rowName} value={value}/>
|
{creatable
|
||||||
|
? <CreatableSelect className="select" {...args} id={rowName} value={value}/>
|
||||||
|
: <Select className="select" {...args} id={rowName} value={value}/>}
|
||||||
</PrefRow>
|
</PrefRow>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import React from "react"
|
||||||
import { NavLink, withRouter } from "react-router-dom"
|
import { NavLink, withRouter } from "react-router-dom"
|
||||||
import { ReactComponent as ChevronRight } from "../../res/chevron-right.svg"
|
import { ReactComponent as ChevronRight } from "../../res/chevron-right.svg"
|
||||||
import { ReactComponent as UploadButton } from "../../res/upload.svg"
|
import { ReactComponent as UploadButton } from "../../res/upload.svg"
|
||||||
import { PrefTable, PrefSwitch, PrefInput } from "../../components/PreferenceTable"
|
import { PrefTable, PrefSwitch, PrefInput, PrefSelect } from "../../components/PreferenceTable"
|
||||||
import Spinner from "../../components/Spinner"
|
import Spinner from "../../components/Spinner"
|
||||||
import api from "../../api"
|
import api from "../../api"
|
||||||
import BaseMainView from "./BaseMainView"
|
import BaseMainView from "./BaseMainView"
|
||||||
|
@ -48,7 +48,7 @@ class Client extends BaseMainView {
|
||||||
|
|
||||||
get entryKeys() {
|
get entryKeys() {
|
||||||
return ["id", "displayname", "homeserver", "avatar_url", "access_token", "sync",
|
return ["id", "displayname", "homeserver", "avatar_url", "access_token", "sync",
|
||||||
"autojoin", "enabled", "started"]
|
"autojoin", "enabled", "started"]
|
||||||
}
|
}
|
||||||
|
|
||||||
get initialState() {
|
get initialState() {
|
||||||
|
@ -84,6 +84,36 @@ class Client extends BaseMainView {
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get selectedHomeserver() {
|
||||||
|
return this.state.homeserver
|
||||||
|
? this.homeserverEntry([this.props.ctx.homeserversByURL[this.state.homeserver],
|
||||||
|
this.state.homeserver])
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
homeserverEntry = ([serverName, serverURL]) => serverURL && {
|
||||||
|
id: serverURL,
|
||||||
|
value: serverURL,
|
||||||
|
label: serverName || serverURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
super.componentWillReceiveProps(nextProps)
|
||||||
|
this.updateHomeserverOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHomeserverOptions() {
|
||||||
|
this.homeserverOptions = Object.entries(this.props.ctx.homeserversByName).map(this.homeserverEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidHomeserver(value) {
|
||||||
|
try {
|
||||||
|
return Boolean(new URL(value))
|
||||||
|
} catch (err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
avatarUpload = async event => {
|
avatarUpload = async event => {
|
||||||
const file = event.target.files[0]
|
const file = event.target.files[0]
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -165,9 +195,10 @@ class Client extends BaseMainView {
|
||||||
name={this.isNew ? "id" : ""} className="id"
|
name={this.isNew ? "id" : ""} className="id"
|
||||||
value={this.state.id} origValue={this.props.entry.id}
|
value={this.state.id} origValue={this.props.entry.id}
|
||||||
placeholder="@fancybot:example.com" onChange={this.inputChange}/>
|
placeholder="@fancybot:example.com" onChange={this.inputChange}/>
|
||||||
<PrefInput rowName="Homeserver" type="text" name="homeserver"
|
<PrefSelect rowName="Homeserver" options={this.homeserverOptions} isSearchable={true}
|
||||||
value={this.state.homeserver} origValue={this.props.entry.homeserver}
|
value={this.selectedHomeserver} origValue={this.props.entry.homeserver}
|
||||||
placeholder="https://example.com" onChange={this.inputChange}/>
|
onChange={({ id }) => this.setState({ homeserver: id })}
|
||||||
|
creatable={true} isValidNewOption={this.isValidHomeserver}/>
|
||||||
<PrefInput rowName="Access token" type="text" name="access_token"
|
<PrefInput rowName="Access token" type="text" name="access_token"
|
||||||
value={this.state.access_token} origValue={this.props.entry.access_token}
|
value={this.state.access_token} origValue={this.props.entry.access_token}
|
||||||
placeholder="MDAxYWxvY2F0aW9uIG1hdHJpeC5sb2NhbAowMDEzaWRlbnRpZmllc"
|
placeholder="MDAxYWxvY2F0aW9uIG1hdHJpeC5sb2NhbAowMDEzaWRlbnRpZmllc"
|
||||||
|
|
|
@ -31,6 +31,8 @@ class Dashboard extends Component {
|
||||||
instances: {},
|
instances: {},
|
||||||
clients: {},
|
clients: {},
|
||||||
plugins: {},
|
plugins: {},
|
||||||
|
homeserversByName: {},
|
||||||
|
homeserversByURL: {},
|
||||||
sidebarOpen: false,
|
sidebarOpen: false,
|
||||||
modalOpen: false,
|
modalOpen: false,
|
||||||
logFocus: null,
|
logFocus: null,
|
||||||
|
@ -50,8 +52,8 @@ class Dashboard extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentWillMount() {
|
async componentWillMount() {
|
||||||
const [instanceList, clientList, pluginList] = await Promise.all([
|
const [instanceList, clientList, pluginList, homeservers] = await Promise.all([
|
||||||
api.getInstances(), api.getClients(), api.getPlugins(),
|
api.getInstances(), api.getClients(), api.getPlugins(), api.getClientAuthServers(),
|
||||||
api.updateDebugOpenFileEnabled()])
|
api.updateDebugOpenFileEnabled()])
|
||||||
const instances = {}
|
const instances = {}
|
||||||
if (api.getFeatures().instance) {
|
if (api.getFeatures().instance) {
|
||||||
|
@ -71,7 +73,13 @@ class Dashboard extends Component {
|
||||||
plugins[plugin.id] = plugin
|
plugins[plugin.id] = plugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.setState({ instances, clients, plugins })
|
const homeserversByName = homeservers
|
||||||
|
const homeserversByURL = {}
|
||||||
|
for (const [key, value] of Object.entries(homeservers)) {
|
||||||
|
homeserversByURL[value] = key
|
||||||
|
}
|
||||||
|
console.log(homeserversByName, homeserversByURL)
|
||||||
|
this.setState({ instances, clients, plugins, homeserversByName, homeserversByURL })
|
||||||
|
|
||||||
await this.enableLogs()
|
await this.enableLogs()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue