Remove code duplication and add better 404 handler
This commit is contained in:
parent
f97b39f4e3
commit
bc97df7de8
6 changed files with 130 additions and 177 deletions
|
@ -0,0 +1,68 @@
|
||||||
|
import React, { Component } from "react"
|
||||||
|
import { Link } from "react-router-dom"
|
||||||
|
|
||||||
|
class BaseMainView extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = Object.assign(this.initialState, props.entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.setState(Object.assign(this.initialState, nextProps.entry))
|
||||||
|
}
|
||||||
|
|
||||||
|
delete = async () => {
|
||||||
|
if (!window.confirm(`Are you sure you want to delete ${this.state.id}?`)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.setState({ deleting: true })
|
||||||
|
const resp = await this.deleteFunc(this.state.id)
|
||||||
|
if (resp.success) {
|
||||||
|
this.props.history.push("/")
|
||||||
|
this.props.onDelete()
|
||||||
|
} else {
|
||||||
|
this.setState({ deleting: false, error: resp.error })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get initialState() {
|
||||||
|
throw Error("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasInstances() {
|
||||||
|
return this.state.instances && this.state.instances.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get isNew() {
|
||||||
|
return !this.props.entry
|
||||||
|
}
|
||||||
|
|
||||||
|
inputChange = event => {
|
||||||
|
if (!event.target.name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.setState({ [event.target.name]: event.target.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFile(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.readAsArrayBuffer(file)
|
||||||
|
reader.onload = evt => resolve(evt.target.result)
|
||||||
|
reader.onerror = err => reject(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInstances = () => !this.isNew && (
|
||||||
|
<div className="instances">
|
||||||
|
<h3>{this.hasInstances ? "Instances" : "No instances :("}</h3>
|
||||||
|
{this.state.instances.map(instance => (
|
||||||
|
<Link className="instance" key={instance.id} to={`/instance/${instance.id}`}>
|
||||||
|
{instance.id}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BaseMainView
|
|
@ -13,36 +13,37 @@
|
||||||
//
|
//
|
||||||
// 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 from "react"
|
||||||
import { Link, 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 } 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"
|
||||||
|
|
||||||
const ClientListEntry = ({ client }) => {
|
const ClientListEntry = ({ entry }) => {
|
||||||
const classes = ["client", "entry"]
|
const classes = ["client", "entry"]
|
||||||
if (!client.enabled) {
|
if (!entry.enabled) {
|
||||||
classes.push("disabled")
|
classes.push("disabled")
|
||||||
} else if (!client.started) {
|
} else if (!entry.started) {
|
||||||
classes.push("stopped")
|
classes.push("stopped")
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<NavLink className={classes.join(" ")} to={`/client/${client.id}`}>
|
<NavLink className={classes.join(" ")} to={`/client/${entry.id}`}>
|
||||||
<img className="avatar" src={api.getAvatarURL(client.id)} alt=""/>
|
<img className="avatar" src={api.getAvatarURL(entry.id)} alt=""/>
|
||||||
<span className="displayname">{client.displayname || client.id}</span>
|
<span className="displayname">{entry.displayname || entry.id}</span>
|
||||||
<ChevronRight/>
|
<ChevronRight/>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Client extends Component {
|
class Client extends BaseMainView {
|
||||||
static ListEntry = ClientListEntry
|
static ListEntry = ClientListEntry
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = Object.assign(this.initialState, props.client)
|
this.deleteFunc = api.deleteClient
|
||||||
}
|
}
|
||||||
|
|
||||||
get initialState() {
|
get initialState() {
|
||||||
|
@ -78,26 +79,6 @@ class Client extends Component {
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
this.setState(Object.assign(this.initialState, nextProps.client))
|
|
||||||
}
|
|
||||||
|
|
||||||
inputChange = event => {
|
|
||||||
if (!event.target.name) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.setState({ [event.target.name]: event.target.value })
|
|
||||||
}
|
|
||||||
|
|
||||||
async readFile(file) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.readAsArrayBuffer(file)
|
|
||||||
reader.onload = evt => resolve(evt.target.result)
|
|
||||||
reader.onerror = err => reject(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
avatarUpload = async event => {
|
avatarUpload = async event => {
|
||||||
const file = event.target.files[0]
|
const file = event.target.files[0]
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -126,25 +107,11 @@ class Client extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete = async () => {
|
|
||||||
if (!window.confirm(`Are you sure you want to delete ${this.state.id}?`)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.setState({ deleting: true })
|
|
||||||
const resp = await api.deleteClient(this.state.id)
|
|
||||||
if (resp.success) {
|
|
||||||
this.props.history.push("/")
|
|
||||||
this.props.onDelete()
|
|
||||||
} else {
|
|
||||||
this.setState({ deleting: false, error: resp.error })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startOrStop = async () => {
|
startOrStop = async () => {
|
||||||
this.setState({ startingOrStopping: true })
|
this.setState({ startingOrStopping: true })
|
||||||
const resp = await api.putClient({
|
const resp = await api.putClient({
|
||||||
id: this.props.client.id,
|
id: this.props.entry.id,
|
||||||
started: !this.props.client.started,
|
started: !this.props.entry.started,
|
||||||
})
|
})
|
||||||
if (resp.id) {
|
if (resp.id) {
|
||||||
this.props.onChange(resp)
|
this.props.onChange(resp)
|
||||||
|
@ -158,10 +125,6 @@ class Client extends Component {
|
||||||
return this.state.saving || this.state.startingOrStopping || this.state.deleting
|
return this.state.saving || this.state.startingOrStopping || this.state.deleting
|
||||||
}
|
}
|
||||||
|
|
||||||
get isNew() {
|
|
||||||
return !Boolean(this.props.client)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSidebar = () => !this.isNew && (
|
renderSidebar = () => !this.isNew && (
|
||||||
<div className="sidebar">
|
<div className="sidebar">
|
||||||
<div className={`avatar-container ${this.state.avatar_url ? "" : "no-avatar"}
|
<div className={`avatar-container ${this.state.avatar_url ? "" : "no-avatar"}
|
||||||
|
@ -175,17 +138,17 @@ class Client extends Component {
|
||||||
{this.state.uploadingAvatar && <Spinner/>}
|
{this.state.uploadingAvatar && <Spinner/>}
|
||||||
</div>
|
</div>
|
||||||
<div className="started-container">
|
<div className="started-container">
|
||||||
<span className={`started ${this.props.client.started}
|
<span className={`started ${this.props.entry.started}
|
||||||
${this.props.client.enabled ? "" : "disabled"}`}/>
|
${this.props.entry.enabled ? "" : "disabled"}`}/>
|
||||||
<span className="text">
|
<span className="text">
|
||||||
{this.props.client.started ? "Started" :
|
{this.props.entry.started ? "Started" :
|
||||||
(this.props.client.enabled ? "Stopped" : "Disabled")}
|
(this.props.entry.enabled ? "Stopped" : "Disabled")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{(this.props.client.started || this.props.client.enabled) && (
|
{(this.props.entry.started || this.props.entry.enabled) && (
|
||||||
<button className="save" onClick={this.startOrStop} disabled={this.loading}>
|
<button className="save" onClick={this.startOrStop} disabled={this.loading}>
|
||||||
{this.state.startingOrStopping ? <Spinner/>
|
{this.state.startingOrStopping ? <Spinner/>
|
||||||
: (this.props.client.started ? "Stop" : "Start")}
|
: (this.props.entry.started ? "Stop" : "Start")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -236,21 +199,6 @@ class Client extends Component {
|
||||||
<div className="error">{this.state.error}</div>
|
<div className="error">{this.state.error}</div>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
get hasInstances() {
|
|
||||||
return this.state.instances.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
renderInstances = () => !this.isNew && (
|
|
||||||
<div className="instances">
|
|
||||||
<h3>{this.hasInstances ? "Instances" : "No instances :("}</h3>
|
|
||||||
{this.state.instances.map(instance => (
|
|
||||||
<Link className="instance" key={instance.id} to={`/instance/${instance.id}`}>
|
|
||||||
{instance.id}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className="client">
|
return <div className="client">
|
||||||
{this.renderSidebar()}
|
{this.renderSidebar()}
|
||||||
|
|
|
@ -13,7 +13,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 from "react"
|
||||||
import { NavLink, withRouter } from "react-router-dom"
|
import { NavLink, withRouter } from "react-router-dom"
|
||||||
import AceEditor from "react-ace"
|
import AceEditor from "react-ace"
|
||||||
import "brace/mode/yaml"
|
import "brace/mode/yaml"
|
||||||
|
@ -22,20 +22,21 @@ import { ReactComponent as ChevronRight } from "../../res/chevron-right.svg"
|
||||||
import PrefTable, { PrefInput, PrefSelect, PrefSwitch } from "../../components/PreferenceTable"
|
import PrefTable, { PrefInput, PrefSelect, PrefSwitch } from "../../components/PreferenceTable"
|
||||||
import api from "../../api"
|
import api from "../../api"
|
||||||
import Spinner from "../../components/Spinner"
|
import Spinner from "../../components/Spinner"
|
||||||
|
import BaseMainView from "./BaseMainView"
|
||||||
|
|
||||||
const InstanceListEntry = ({ instance }) => (
|
const InstanceListEntry = ({ entry }) => (
|
||||||
<NavLink className="instance entry" to={`/instance/${instance.id}`}>
|
<NavLink className="instance entry" to={`/instance/${entry.id}`}>
|
||||||
<span className="id">{instance.id}</span>
|
<span className="id">{entry.id}</span>
|
||||||
<ChevronRight/>
|
<ChevronRight/>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)
|
)
|
||||||
|
|
||||||
class Instance extends Component {
|
class Instance extends BaseMainView {
|
||||||
static ListEntry = InstanceListEntry
|
static ListEntry = InstanceListEntry
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = Object.assign(this.initialState, props.instance)
|
this.deleteFunc = api.deleteInstance
|
||||||
this.updateClientOptions()
|
this.updateClientOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ class Instance extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.setState(Object.assign(this.initialState, nextProps.instance))
|
super.componentWillReceiveProps(nextProps)
|
||||||
this.updateClientOptions()
|
this.updateClientOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,22 +83,15 @@ class Instance extends Component {
|
||||||
this.clientOptions = Object.values(this.props.ctx.clients).map(this.clientSelectEntry)
|
this.clientOptions = Object.values(this.props.ctx.clients).map(this.clientSelectEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
inputChange = event => {
|
|
||||||
if (!event.target.name) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.setState({ [event.target.name]: event.target.value })
|
|
||||||
}
|
|
||||||
|
|
||||||
save = async () => {
|
save = async () => {
|
||||||
this.setState({ saving: true })
|
this.setState({ saving: true })
|
||||||
const resp = await api.putInstance(this.instanceInState, this.props.instance
|
const resp = await api.putInstance(this.instanceInState, this.props.entry
|
||||||
? this.props.instance.id : undefined)
|
? this.props.entry.id : undefined)
|
||||||
if (resp.id) {
|
if (resp.id) {
|
||||||
if (this.isNew) {
|
if (this.isNew) {
|
||||||
this.props.history.push(`/instance/${resp.id}`)
|
this.props.history.push(`/instance/${resp.id}`)
|
||||||
} else {
|
} else {
|
||||||
if (resp.id !== this.props.instance.id) {
|
if (resp.id !== this.props.entry.id) {
|
||||||
this.props.history.replace(`/instance/${resp.id}`)
|
this.props.history.replace(`/instance/${resp.id}`)
|
||||||
}
|
}
|
||||||
this.setState({ saving: false, error: "" })
|
this.setState({ saving: false, error: "" })
|
||||||
|
@ -108,24 +102,6 @@ class Instance extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete = async () => {
|
|
||||||
if (!window.confirm(`Are you sure you want to delete ${this.state.id}?`)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.setState({ deleting: true })
|
|
||||||
const resp = await api.deleteInstance(this.state.id)
|
|
||||||
if (resp.success) {
|
|
||||||
this.props.history.push("/")
|
|
||||||
this.props.onDelete()
|
|
||||||
} else {
|
|
||||||
this.setState({ deleting: false, error: resp.error })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get isNew() {
|
|
||||||
return !Boolean(this.props.instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectedClientEntry() {
|
get selectedClientEntry() {
|
||||||
return this.state.primary_user
|
return this.state.primary_user
|
||||||
? this.clientSelectEntry(this.props.ctx.clients[this.state.primary_user])
|
? this.clientSelectEntry(this.props.ctx.clients[this.state.primary_user])
|
||||||
|
|
|
@ -13,30 +13,26 @@
|
||||||
//
|
//
|
||||||
// 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 from "react"
|
||||||
import { NavLink, Link } from "react-router-dom"
|
import { NavLink } 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, { PrefInput } from "../../components/PreferenceTable"
|
import PrefTable, { PrefInput } 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"
|
||||||
|
|
||||||
const PluginListEntry = ({ plugin }) => (
|
const PluginListEntry = ({ entry }) => (
|
||||||
<NavLink className="plugin entry" to={`/plugin/${plugin.id}`}>
|
<NavLink className="plugin entry" to={`/plugin/${entry.id}`}>
|
||||||
<span className="id">{plugin.id}</span>
|
<span className="id">{entry.id}</span>
|
||||||
<ChevronRight/>
|
<ChevronRight/>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Plugin extends Component {
|
class Plugin extends BaseMainView {
|
||||||
static ListEntry = PluginListEntry
|
static ListEntry = PluginListEntry
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
this.state = Object.assign(this.initialState, props.plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
get initialState() {
|
get initialState() {
|
||||||
return {
|
return {
|
||||||
id: "",
|
id: "",
|
||||||
|
@ -50,18 +46,6 @@ class Plugin extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
this.setState(Object.assign(this.initialState, nextProps.plugin))
|
|
||||||
}
|
|
||||||
|
|
||||||
async readFile(file) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.readAsArrayBuffer(file)
|
|
||||||
reader.onload = evt => resolve(evt.target.result)
|
|
||||||
reader.onerror = err => reject(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
upload = async event => {
|
upload = async event => {
|
||||||
const file = event.target.files[0]
|
const file = event.target.files[0]
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -81,39 +65,6 @@ class Plugin extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete = async () => {
|
|
||||||
if (!window.confirm(`Are you sure you want to delete ${this.state.id}?`)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.setState({ deleting: true })
|
|
||||||
const resp = await api.deletePlugin(this.state.id)
|
|
||||||
if (resp.success) {
|
|
||||||
this.props.history.push("/")
|
|
||||||
this.props.onDelete()
|
|
||||||
} else {
|
|
||||||
this.setState({ deleting: false, error: resp.error })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get isNew() {
|
|
||||||
return !Boolean(this.props.plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasInstances() {
|
|
||||||
return this.state.instances.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
renderInstances = () => !this.isNew && (
|
|
||||||
<div className="instances">
|
|
||||||
<h3>{this.hasInstances ? "Instances" : "No instances :("}</h3>
|
|
||||||
{this.state.instances.map(instance => (
|
|
||||||
<Link className="instance" key={instance.id} to={`/instance/${instance.id}`}>
|
|
||||||
{instance.id}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className="plugin">
|
return <div className="plugin">
|
||||||
<div className={`upload-box ${this.state.uploading ? "uploading" : ""}`}>
|
<div className={`upload-box ${this.state.uploading ? "uploading" : ""}`}>
|
||||||
|
|
|
@ -29,7 +29,7 @@ class Dashboard extends Component {
|
||||||
clients: {},
|
clients: {},
|
||||||
plugins: {},
|
plugins: {},
|
||||||
}
|
}
|
||||||
global.maubot = this
|
window.maubot = this
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentWillMount() {
|
async componentWillMount() {
|
||||||
|
@ -51,8 +51,8 @@ class Dashboard extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList(field, type) {
|
renderList(field, type) {
|
||||||
return Object.values(this.state[field + "s"]).map(entry =>
|
return this.state[field] && Object.values(this.state[field]).map(entry =>
|
||||||
React.createElement(type, { key: entry.id, [field]: entry }))
|
React.createElement(type, { key: entry.id, entry }))
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(stateField, id) {
|
delete(stateField, id) {
|
||||||
|
@ -71,19 +71,24 @@ class Dashboard extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderView(field, type, id) {
|
renderView(field, type, id) {
|
||||||
const stateField = field + "s"
|
const entry = this.state[field][id]
|
||||||
const entry = this.state[stateField][id]
|
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
return "Not found :("
|
return this.renderNotFound(field.slice(0, -1))
|
||||||
}
|
}
|
||||||
return React.createElement(type, {
|
return React.createElement(type, {
|
||||||
[field]: entry,
|
entry,
|
||||||
onDelete: () => this.delete(stateField, id),
|
onDelete: () => this.delete(field, id),
|
||||||
onChange: newEntry => this.add(stateField, newEntry, id),
|
onChange: newEntry => this.add(field, newEntry, id),
|
||||||
ctx: this.state,
|
ctx: this.state,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderNotFound = (thing = "path") => (
|
||||||
|
<div className="not-found">
|
||||||
|
Oops! I'm afraid that {thing} couldn't be found.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className="dashboard">
|
return <div className="dashboard">
|
||||||
<Link to="/" className="title">
|
<Link to="/" className="title">
|
||||||
|
@ -100,21 +105,21 @@ class Dashboard extends Component {
|
||||||
<h2>Instances</h2>
|
<h2>Instances</h2>
|
||||||
<Link to="/new/instance"><Plus/></Link>
|
<Link to="/new/instance"><Plus/></Link>
|
||||||
</div>
|
</div>
|
||||||
{this.renderList("instance", Instance.ListEntry)}
|
{this.renderList("instances", Instance.ListEntry)}
|
||||||
</div>
|
</div>
|
||||||
<div className="clients list">
|
<div className="clients list">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
<h2>Clients</h2>
|
<h2>Clients</h2>
|
||||||
<Link to="/new/client"><Plus/></Link>
|
<Link to="/new/client"><Plus/></Link>
|
||||||
</div>
|
</div>
|
||||||
{this.renderList("client", Client.ListEntry)}
|
{this.renderList("clients", Client.ListEntry)}
|
||||||
</div>
|
</div>
|
||||||
<div className="plugins list">
|
<div className="plugins list">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
<h2>Plugins</h2>
|
<h2>Plugins</h2>
|
||||||
<Link to="/new/plugin"><Plus/></Link>
|
<Link to="/new/plugin"><Plus/></Link>
|
||||||
</div>
|
</div>
|
||||||
{this.renderList("plugin", Plugin.ListEntry)}
|
{this.renderList("plugins", Plugin.ListEntry)}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<main className="view">
|
<main className="view">
|
||||||
|
@ -128,12 +133,12 @@ class Dashboard extends Component {
|
||||||
<Route path="/new/plugin" render={() => <Plugin
|
<Route path="/new/plugin" render={() => <Plugin
|
||||||
onChange={newEntry => this.add("plugins", newEntry)}/>}/>
|
onChange={newEntry => this.add("plugins", newEntry)}/>}/>
|
||||||
<Route path="/instance/:id" render={({ match }) =>
|
<Route path="/instance/:id" render={({ match }) =>
|
||||||
this.renderView("instance", Instance, match.params.id)}/>
|
this.renderView("instances", Instance, match.params.id)}/>
|
||||||
<Route path="/client/:id" render={({ match }) =>
|
<Route path="/client/:id" render={({ match }) =>
|
||||||
this.renderView("client", Client, match.params.id)}/>
|
this.renderView("clients", Client, match.params.id)}/>
|
||||||
<Route path="/plugin/:id" render={({ match }) =>
|
<Route path="/plugin/:id" render={({ match }) =>
|
||||||
this.renderView("plugin", Plugin, match.params.id)}/>
|
this.renderView("plugin", Plugin, match.params.id)}/>
|
||||||
<Route render={() => "Not found :("}/>
|
<Route render={() => this.renderNotFound()}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -69,6 +69,11 @@
|
||||||
@import instance
|
@import instance
|
||||||
@import plugin
|
@import plugin
|
||||||
|
|
||||||
|
> .not-found
|
||||||
|
text-align: center
|
||||||
|
margin-top: 5rem
|
||||||
|
font-size: 1.5rem
|
||||||
|
|
||||||
div.buttons
|
div.buttons
|
||||||
+button-group
|
+button-group
|
||||||
display: flex
|
display: flex
|
||||||
|
|
Loading…
Reference in a new issue