diff --git a/client/admin-menu.js b/client/admin-menu.js new file mode 100644 index 0000000..f11a546 --- /dev/null +++ b/client/admin-menu.js @@ -0,0 +1,54 @@ +"use strict"; + +import m from "mithril"; + +const menu = { + view: vnode => { + const active = { [vnode.attrs.page]: "active" }; + const tabs = []; + + if (window.authorizer.hasAccess("presets", 1)) { + tabs.push( + m( + "li", + { class: active["presets"] }, + m("a", { href: "#!/admin/presets" }, "Presets") + ) + ); + } + + if (window.authorizer.hasAccess("provisions", 1)) { + tabs.push( + m( + "li", + { class: active["provisions"] }, + m("a", { href: "#!/admin/provisions" }, "Provisions") + ) + ); + } + + if (window.authorizer.hasAccess("virtualParameters", 1)) { + tabs.push( + m( + "li", + { class: active["virtualParameters"] }, + m("a", { href: "#!/admin/virtualParameters" }, "Virtual Parameters") + ) + ); + } + + if (window.authorizer.hasAccess("files", 1)) { + tabs.push( + m( + "li", + { class: active["files"] }, + m("a", { href: "#!/admin/files" }, "Files") + ) + ); + } + + return m("nav#side-menu", m("ul", tabs)); + } +}; + +export default menu; diff --git a/client/app.js b/client/app.js index f6ca4b3..84ed8c4 100644 --- a/client/app.js +++ b/client/app.js @@ -19,6 +19,8 @@ import * as notifications from "./notifications"; window.authorizer = new Authorizer(window.permissionSets); +const adminPages = ["presets", "provisions", "virtualParameters", "files"]; + let state; let lastRenderTimestamp = 0; @@ -77,14 +79,33 @@ function pagify(pageName, page) { return component; } +function redirectAdminPage() { + const component = { + onmatch: () => { + for (const page of adminPages) { + if (window.authorizer.hasAccess(page, 2)) { + m.route.set(`/admin/${page}`); + return null; + } + } + return null; + } + }; + return component; +} + m.route(document.body, "/overview", { "/login": pagify("login", loginPage), "/overview": pagify("overview", overviewPage), "/devices": pagify("devices", devicesPage), "/devices/:id": pagify("devices", devicePage), "/faults": pagify("faults", faultsPage), - "/presets": pagify("presets", presetsPage), - "/provisions": pagify("provisions", provisionsPage), - "/virtualParameters": pagify("virtualParameters", virtualParametersPage), - "/files": pagify("files", filesPage) + "/admin": redirectAdminPage(), + "/admin/presets": pagify("presets", presetsPage), + "/admin/provisions": pagify("provisions", provisionsPage), + "/admin/virtualParameters": pagify( + "virtualParameters", + virtualParametersPage + ), + "/admin/files": pagify("files", filesPage) }); diff --git a/client/css/app.css b/client/css/app.css index 982dca5..6756bad 100644 --- a/client/css/app.css +++ b/client/css/app.css @@ -253,7 +253,40 @@ i { } } +#content-wrapper { + display: flex; +} + +#side-menu { + width: 200px; + & > ul { + padding: 0; + list-style: none; + + & > li { + & > a { + display: block; + text-decoration: none; + margin: 5px 0; + padding: 8px; + border-bottom-right-radius: 6px; + border-top-right-radius: 6px; + background-color: var(--color3); + &:hover { + background-color: var(--color4); + color: var(--color3); + } + } + + &.active { + font-weight: bold; + } + } + } +} + #content { + flex-grow: 1; margin: 10px; min-height: 250px; } diff --git a/client/files-page.js b/client/files-page.js index 6173f11..b8ba4ce 100644 --- a/client/files-page.js +++ b/client/files-page.js @@ -322,13 +322,13 @@ const component = { function onFilterChanged(filter) { const ops = { filter }; if (vnode.attrs.sort) ops.sort = vnode.attrs.sort; - m.route.set("/files", ops); + m.route.set(m.route.get(), ops); } function onSortChange(sort) { const ops = { sort }; if (vnode.attrs.filter) ops.filter = vnode.attrs.filter; - m.route.set("/files", ops); + m.route.set(m.route.get(), ops); } const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {}; diff --git a/client/layout.js b/client/layout.js index b2d3ecf..feb59ed 100644 --- a/client/layout.js +++ b/client/layout.js @@ -5,18 +5,31 @@ import m from "mithril"; import menu from "./menu"; import drawerComponent from "./drawer-component"; import userMenu from "./user-menu"; +import adminMenu from "./admin-menu"; import * as overlay from "./overlay"; +const adminPages = ["presets", "provisions", "virtualParameters", "files"]; + const layout = { view: vnode => { + let sideMenu, group; + if (adminPages.includes(vnode.attrs.page)) { + group = "admin"; + sideMenu = m(adminMenu, { page: vnode.attrs.page }); + } + return [ m("#header", [ m("img.logo", { src: "logo.svg" }), m(userMenu), - m(menu, { page: vnode.attrs.page }), + m(menu, { page: group || vnode.attrs.page }), m(drawerComponent) ]), - m("#content", { class: `page-${vnode.attrs.page}` }, [vnode.children]), + m( + "#content-wrapper", + sideMenu, + m("#content", { class: `page-${vnode.attrs.page}` }, [vnode.children]) + ), overlay.render() ]; } diff --git a/client/menu.js b/client/menu.js index 605d470..2a2e196 100644 --- a/client/menu.js +++ b/client/menu.js @@ -37,44 +37,18 @@ const menu = { ); } - if (window.authorizer.hasAccess("presets", 2)) { - tabs.push( - m( - "li", - { class: active["presets"] }, - m("a", { href: "#!/presets" }, "Presets") - ) - ); - } - - if (window.authorizer.hasAccess("provisions", 2)) { - tabs.push( - m( - "li", - { class: active["provisions"] }, - m("a", { href: "#!/provisions" }, "Provisions") - ) - ); - } - - if (window.authorizer.hasAccess("virtualParameters", 2)) { - tabs.push( - m( - "li", - { class: active["virtualParameters"] }, - m("a", { href: "#!/virtualParameters" }, "Virtual Parameters") - ) - ); - } - - if (window.authorizer.hasAccess("files", 2)) { - tabs.push( - m( - "li", - { class: active["files"] }, - m("a", { href: "#!/files" }, "Files") - ) - ); + const adminPages = ["presets", "provisions", "virtualParameters", "files"]; + for (const page of adminPages) { + if (window.authorizer.hasAccess(page, 2)) { + tabs.push( + m( + "li", + { class: active["admin"] }, + m("a", { href: "#!/admin" }, "Admin") + ) + ); + break; + } } return m("nav", m("ul", tabs)); diff --git a/client/presets-page.js b/client/presets-page.js index 3c1dff0..d0031f5 100644 --- a/client/presets-page.js +++ b/client/presets-page.js @@ -360,13 +360,13 @@ const component = { function onFilterChanged(filter) { const ops = { filter }; if (vnode.attrs.sort) ops.sort = vnode.attrs.sort; - m.route.set("/presets", ops); + m.route.set(m.route.get(), ops); } function onSortChange(sort) { const ops = { sort }; if (vnode.attrs.filter) ops.filter = vnode.attrs.filter; - m.route.set("/presets", ops); + m.route.set(m.route.get(), ops); } const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {}; diff --git a/client/provisions-page.js b/client/provisions-page.js index 0b1f8a5..e18198c 100644 --- a/client/provisions-page.js +++ b/client/provisions-page.js @@ -353,13 +353,13 @@ const component = { function onFilterChanged(filter) { const ops = { filter }; if (vnode.attrs.sort) ops.sort = vnode.attrs.sort; - m.route.set("/provisions", ops); + m.route.set(m.route.get(), ops); } function onSortChange(sort) { const ops = { sort }; if (vnode.attrs.filter) ops.filter = vnode.attrs.filter; - m.route.set("/provisions", ops); + m.route.set(m.route.get(), ops); } const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {}; diff --git a/client/virtual-parameters-page.js b/client/virtual-parameters-page.js index 0188093..c048136 100644 --- a/client/virtual-parameters-page.js +++ b/client/virtual-parameters-page.js @@ -363,13 +363,13 @@ const component = { function onFilterChanged(filter) { const ops = { filter }; if (vnode.attrs.sort) ops.sort = vnode.attrs.sort; - m.route.set("/virtualParameters", ops); + m.route.set(m.route.get(), ops); } function onSortChange(sort) { const ops = { sort }; if (vnode.attrs.filter) ops.filter = vnode.attrs.filter; - m.route.set("/virtualParameters", ops); + m.route.set(m.route.get(), ops); } const sort = vnode.attrs.sort ? memoizedJsonParse(vnode.attrs.sort) : {};