Finish basic database viewing

This commit is contained in:
Tulir Asokan 2018-12-28 19:19:58 +02:00
parent dc22f35d08
commit 4cc605df76
7 changed files with 1337 additions and 1117 deletions

View file

@ -19,7 +19,7 @@ from asyncio import AbstractEventLoop
from ...config import Config from ...config import Config
from .base import routes, set_config, set_loop from .base import routes, set_config, set_loop
from .middleware import auth, error from .middleware import auth, error
from . import auth, plugin, instance, database, client, client_proxy, client_auth, dev_open from . import auth, plugin, instance, instance_database, client, client_proxy, client_auth, dev_open
from .log import stop_all as stop_log_sockets, init as init_log_listener from .log import stop_all as stop_log_sockets, init as init_log_listener

View file

@ -16,7 +16,7 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from aiohttp import web from aiohttp import web
from sqlalchemy import Table, Column from sqlalchemy import Table, Column, asc, desc
from ...instance import PluginInstance from ...instance import PluginInstance
from .base import routes from .base import routes
@ -59,3 +59,19 @@ async def get_table(request: web.Request) -> web.Response:
elif not instance.inst_db: elif not instance.inst_db:
return resp.plugin_has_no_database return resp.plugin_has_no_database
tables = instance.get_db_tables() tables = instance.get_db_tables()
try:
table = tables[request.match_info.get("table", "")]
except KeyError:
return resp.table_not_found
db = instance.inst_db
try:
order = [tuple(order.split(":")) for order in request.query.getall("order")]
order = [(asc if sort == "asc" else desc)(table.columns[column])
if sort else table.columns[column]
for column, sort in order]
except KeyError:
order = []
limit = int(request.query.get("limit", 100))
query = table.select().order_by(*order).limit(limit)
data = [[*row] for row in db.execute(query)]
return web.json_response(data)

View file

@ -159,6 +159,13 @@ class _Response:
"errcode": "plugin_has_no_database", "errcode": "plugin_has_no_database",
}) })
@property
def table_not_found(self) -> web.Response:
return web.json_response({
"error": "Given table not found in plugin database",
"errcode": "table_not_found",
})
@property @property
def method_not_allowed(self) -> web.Response: def method_not_allowed(self) -> web.Response:
return web.json_response({ return web.json_response({

View file

@ -154,6 +154,8 @@ export const putInstance = (instance, id) => defaultPut("instance", instance, id
export const deleteInstance = id => defaultDelete("instance", id) export const deleteInstance = id => defaultDelete("instance", id)
export const getInstanceDatabase = id => defaultGet(`/instance/${id}/database`) export const getInstanceDatabase = id => defaultGet(`/instance/${id}/database`)
export const getInstanceDatabaseTable = (id, table, query = []) =>
defaultGet(`/instance/${id}/database/${table}?${query.join("&")}`)
export const getPlugins = () => defaultGet("/plugins") export const getPlugins = () => defaultGet("/plugins")
export const getPlugin = id => defaultGet(`/plugin/${id}`) export const getPlugin = id => defaultGet(`/plugin/${id}`)
@ -205,7 +207,7 @@ export default {
BASE_PATH, BASE_PATH,
login, ping, openLogSocket, debugOpenFile, debugOpenFileEnabled, updateDebugOpenFileEnabled, login, ping, openLogSocket, debugOpenFile, debugOpenFileEnabled, updateDebugOpenFileEnabled,
getInstances, getInstance, putInstance, deleteInstance, getInstances, getInstance, putInstance, deleteInstance,
getInstanceDatabase, getInstanceDatabase, getInstanceDatabaseTable,
getPlugins, getPlugin, uploadPlugin, deletePlugin, getPlugins, getPlugin, uploadPlugin, deletePlugin,
getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient, getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient,
} }

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// 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, { Component } from "react" import React, { Component } from "react"
import { NavLink, Link, Route } from "react-router-dom" import { NavLink, Link, Route, withRouter } from "react-router-dom"
import { ReactComponent as ChevronLeft } from "../../res/chevron-left.svg" import { ReactComponent as ChevronLeft } from "../../res/chevron-left.svg"
import { ReactComponent as OrderDesc } from "../../res/sort-down.svg" import { ReactComponent as OrderDesc } from "../../res/sort-down.svg"
import { ReactComponent as OrderAsc } from "../../res/sort-up.svg" import { ReactComponent as OrderAsc } from "../../res/sort-up.svg"
@ -26,53 +26,130 @@ class InstanceDatabase extends Component {
super(props) super(props)
this.state = { this.state = {
tables: null, tables: null,
sortBy: null, tableContent: null,
} }
this.sortBy = []
} }
async componentWillMount() { async componentWillMount() {
const tables = new Map(Object.entries(await api.getInstanceDatabase(this.props.instanceID))) const tables = new Map(Object.entries(await api.getInstanceDatabase(this.props.instanceID)))
for (const table of tables.values()) { for (const [name, table] of tables) {
table.name = name
table.columns = new Map(Object.entries(table.columns)) table.columns = new Map(Object.entries(table.columns))
for (const column of table.columns.values()) { for (const [columnName, column] of table.columns) {
column.sort = "desc" column.name = columnName
column.sort = null
} }
} }
this.setState({ tables }) this.setState({ tables })
this.checkLocationTable()
} }
toggleSort(column) { componentDidUpdate(prevProps) {
column.sort = column.sort === "desc" ? "asc" : "desc" if (this.props.location !== prevProps.location) {
this.forceUpdate() this.sortBy = []
this.setState({ tableContent: null })
this.checkLocationTable()
}
} }
checkLocationTable() {
const prefix = `/instance/${this.props.instanceID}/database/`
if (this.props.location.pathname.startsWith(prefix)) {
const table = this.props.location.pathname.substr(prefix.length)
this.reloadContent(table)
}
}
getSortQuery(table) {
const sort = []
for (const column of this.sortBy) {
sort.push(`order=${column.name}:${column.sort}`)
}
return sort
}
async reloadContent(name) {
const table = this.state.tables.get(name)
const query = this.getSortQuery(table)
query.push("limit=100")
this.setState({
tableContent: await api.getInstanceDatabaseTable(
this.props.instanceID, table.name, query),
})
}
toggleSort(tableName, column) {
const index = this.sortBy.indexOf(column)
if (index >= 0) {
this.sortBy.splice(index, 1)
}
switch (column.sort) {
default:
column.sort = "desc"
this.sortBy.unshift(column)
break
case "desc":
column.sort = "asc"
this.sortBy.unshift(column)
break
case "asc":
column.sort = null
break
}
this.forceUpdate()
this.reloadContent(tableName)
}
renderTableHead = table => <thead>
<tr>
{Array.from(table.columns.entries()).map(([name, column]) => (
<td key={name}>
<span onClick={() => this.toggleSort(table.name, column)}>
{name}
{column.sort === "desc" ?
<OrderDesc/> :
column.sort === "asc"
? <OrderAsc/>
: null}
</span>
</td>
))}
</tr>
</thead>
renderTable = ({ match }) => { renderTable = ({ match }) => {
const table = this.state.tables.get(match.params.table) const table = this.state.tables.get(match.params.table)
console.log(table)
return <div className="table"> return <div className="table">
<table> {this.state.tableContent ? (
<thead> <table>
<tr> {this.renderTableHead(table)}
{Array.from(table.columns.entries()).map(([name, column]) => ( <tbody>
<td key={name}> {this.state.tableContent.map(row => (
<span onClick={() => this.toggleSort(column)}> <tr key={row}>
{name} {row.map((column, index) => (
{column.sort === "desc" ? <OrderDesc/> : <OrderAsc/>} <td key={index}>
</span> {column}
</td> </td>
))}
</tr>
))} ))}
</tr> </tbody>
</thead> </table>
<tbody> ) : <>
</tbody> <table>
</table> {this.renderTableHead(table)}
</table>
<Spinner/>
</>}
</div> </div>
} }
renderContent() { renderContent() {
return <> return <>
<div className="tables"> <div className="tables">
{Object.keys(this.state.tables).map(key => ( {Array.from(this.state.tables.keys()).map(key => (
<NavLink key={key} to={`/instance/${this.props.instanceID}/database/${key}`}> <NavLink key={key} to={`/instance/${this.props.instanceID}/database/${key}`}>
{key} {key}
</NavLink> </NavLink>
@ -98,4 +175,4 @@ class InstanceDatabase extends Component {
} }
} }
export default InstanceDatabase export default withRouter(InstanceDatabase)

View file

@ -62,6 +62,9 @@
background-color: $primary background-color: $primary
> div.table > div.table
overflow-x: auto
overflow-y: hidden
table table
font-family: "Fira Code", monospace font-family: "Fira Code", monospace
width: 100% width: 100%

File diff suppressed because it is too large Load diff