Finish basic database viewing
This commit is contained in:
parent
dc22f35d08
commit
4cc605df76
7 changed files with 1337 additions and 1117 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
@ -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({
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue