Finish basic database viewing
This commit is contained in:
parent
dc22f35d08
commit
4cc605df76
@ -19,7 +19,7 @@ from asyncio import AbstractEventLoop
|
||||
from ...config import Config
|
||||
from .base import routes, set_config, set_loop
|
||||
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
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiohttp import web
|
||||
from sqlalchemy import Table, Column
|
||||
from sqlalchemy import Table, Column, asc, desc
|
||||
|
||||
from ...instance import PluginInstance
|
||||
from .base import routes
|
||||
@ -59,3 +59,19 @@ async def get_table(request: web.Request) -> web.Response:
|
||||
elif not instance.inst_db:
|
||||
return resp.plugin_has_no_database
|
||||
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)
|
@ -159,6 +159,13 @@ class _Response:
|
||||
"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
|
||||
def method_not_allowed(self) -> web.Response:
|
||||
return web.json_response({
|
||||
|
@ -154,6 +154,8 @@ export const putInstance = (instance, id) => defaultPut("instance", instance, id
|
||||
export const deleteInstance = id => defaultDelete("instance", id)
|
||||
|
||||
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 getPlugin = id => defaultGet(`/plugin/${id}`)
|
||||
@ -205,7 +207,7 @@ export default {
|
||||
BASE_PATH,
|
||||
login, ping, openLogSocket, debugOpenFile, debugOpenFileEnabled, updateDebugOpenFileEnabled,
|
||||
getInstances, getInstance, putInstance, deleteInstance,
|
||||
getInstanceDatabase,
|
||||
getInstanceDatabase, getInstanceDatabaseTable,
|
||||
getPlugins, getPlugin, uploadPlugin, deletePlugin,
|
||||
getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient,
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
// 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/>.
|
||||
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 OrderDesc } from "../../res/sort-down.svg"
|
||||
import { ReactComponent as OrderAsc } from "../../res/sort-up.svg"
|
||||
@ -26,53 +26,130 @@ class InstanceDatabase extends Component {
|
||||
super(props)
|
||||
this.state = {
|
||||
tables: null,
|
||||
sortBy: null,
|
||||
tableContent: null,
|
||||
}
|
||||
this.sortBy = []
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
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))
|
||||
for (const column of table.columns.values()) {
|
||||
column.sort = "desc"
|
||||
for (const [columnName, column] of table.columns) {
|
||||
column.name = columnName
|
||||
column.sort = null
|
||||
}
|
||||
}
|
||||
this.setState({ tables })
|
||||
this.checkLocationTable()
|
||||
}
|
||||
|
||||
toggleSort(column) {
|
||||
column.sort = column.sort === "desc" ? "asc" : "desc"
|
||||
this.forceUpdate()
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.location !== prevProps.location) {
|
||||
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 }) => {
|
||||
const table = this.state.tables.get(match.params.table)
|
||||
console.log(table)
|
||||
return <div className="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{Array.from(table.columns.entries()).map(([name, column]) => (
|
||||
<td key={name}>
|
||||
<span onClick={() => this.toggleSort(column)}>
|
||||
{name}
|
||||
{column.sort === "desc" ? <OrderDesc/> : <OrderAsc/>}
|
||||
</span>
|
||||
</td>
|
||||
{this.state.tableContent ? (
|
||||
<table>
|
||||
{this.renderTableHead(table)}
|
||||
<tbody>
|
||||
{this.state.tableContent.map(row => (
|
||||
<tr key={row}>
|
||||
{row.map((column, index) => (
|
||||
<td key={index}>
|
||||
{column}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
) : <>
|
||||
<table>
|
||||
{this.renderTableHead(table)}
|
||||
</table>
|
||||
<Spinner/>
|
||||
</>}
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
return <>
|
||||
<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}`}>
|
||||
{key}
|
||||
</NavLink>
|
||||
@ -98,4 +175,4 @@ class InstanceDatabase extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default InstanceDatabase
|
||||
export default withRouter(InstanceDatabase)
|
||||
|
@ -62,6 +62,9 @@
|
||||
background-color: $primary
|
||||
|
||||
> div.table
|
||||
overflow-x: auto
|
||||
overflow-y: hidden
|
||||
|
||||
table
|
||||
font-family: "Fira Code", monospace
|
||||
width: 100%
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user