import m from "mithril" import { Treetable, iTreetable, iTreenode, Treenode, TopbarButton, iTopbarButton, changeTreenodes } from "../widgets/Treetable_V2" import {ModalType, ModalView, iModalView} from "../widgets/Modal" import { iApplication} from "./models/applications" import { iRole } from "./models/roles" import {gridMountpoints} from "../mainlayout" import {WrongEditSelectionMessage, iWrongEditSelectionMessage, showServerError} from "./authmessages" const MOVERIGHT = "Die markierten und nicht markierten Rollen werden den markierten Anwendungen zugeordnet" const localDataModel = { applist: [] as iApplication[], rolelist: [] as iRole[], appnodes: [] as iTreenode[], rolenodes: [] as iTreenode[], loadData: () => { Promise.all([ m.request({ method: "GET", url: "/auth/getapplications" }).then((result) => localDataModel.applist = result as iApplication[]), m.request({ method: "GET", url: "/auth/getroles" }).then((result) => {localDataModel.rolelist = result as iRole[]}) ]) .then (() => { localDataModel.appnodes = [] localDataModel.rolenodes = [] localDataModel.applist.map((app: iApplication) => { let appnode: iTreenode = new Treenode(app, false, []) app.roles.forEach((role: iRole) => { let newChildnode = new Treenode(role) newChildnode.isChecked = true appnode.children.push(newChildnode) }) appnode.children.sort(sortRoleNodes) localDataModel.appnodes.push(appnode) }) localDataModel.rolelist.map((role: iRole) => { let rolenode: iTreenode = new Treenode(role, false, []) localDataModel.rolenodes.push(rolenode) }) localDataModel.rolenodes.sort(sortRoleNodes) changeTreenodes(localDataModel.appnodes, true) }) }, updateRole: (role: iRole) => { m.request ({ method: "POST", // Hier muss POST verwendet werden, weil GET keinen Body hat und Parameter nur im URL transportieren kann url: "/auth/updaterole", body: role }) .catch((e) => {showServerError(e)}) .finally(() => localDataModel.loadData()) }, deleteRoles: (roles: iRole[]) => { let roleids: string[] = [] roles.forEach((role: iRole) => { roleids.push(role.roleid) }) m.request({ method: "POST", url: "/auth/deleteroles", body: roleids }) .catch((e) => {showServerError(e)}) .finally(() => localDataModel.loadData()) }, updateApplication: (app: iApplication) => { m.request ({ method: "POST", // Hier muss POST verwendet werden, weil GET keinen Body hat und Parameter nur im URL transportieren kann url: "/auth/updateapplication", body: app }) .catch((e) => {showServerError(e)}) .finally(() => localDataModel.loadData()) }, deleteApplications: (apps: iApplication[]) => { let appids: string[] = [] apps.forEach((app: iApplication) => { appids.push(app.appid) }) m.request({ method: "POST", url: "/auth/deleteapplications", body: appids }) .catch((e) => {showServerError(e)}) .finally(() => localDataModel.loadData()) }, updateApplicationRoles: () => { interface iApplicationRole { appid: string roles: {roleid: string, isChecked: boolean}[] } let applicationroles: iApplicationRole[] = [] localDataModel.appnodes.forEach((appnode: iTreenode) => { let app: iApplication = appnode.nodevalue let approle: iApplicationRole = { appid: app.appid, roles: [] } appnode.children.forEach((rolenode: iTreenode) => { let role: iRole = rolenode.nodevalue approle.roles.push({roleid: role.roleid, isChecked: rolenode.isChecked}) }) applicationroles.push(approle) }) m.request({ method: "POST", url: "/auth/updateapplicationroles", body: applicationroles }) .catch((e) => {showServerError(e)}) .finally(() => localDataModel.loadData()) } } export const applicationpage: m.ClosureComponent = (vnode) => { return { oninit: (vnode) => { localDataModel.loadData() }, view: (vnode) => { return m("div.w3-container.w3-content", { style: {'max-width': '80%', display: 'flex', 'flex-direction': 'column', height: 'inherit', 'overflow-y': 'inherit'} }, m("div", {style: {flex: '1', 'overflow-y': 'inherit'}}, m("div.w3-cell-row", m("div.w3-cell.w3-padding.w3-mobile", m("h1", "Rollen"), m(Treetable, { treenodes: localDataModel.rolenodes, startexpanded: false, onRowMarked: (node: iTreenode) => { //alert(node.nodevalue['roleid']) }, columnmapper: (node: iTreenode) => { let role = node.nodevalue as iRole return [ m("span.w3-button", { style: {padding: '0px'}, onclick: () => { node.isChecked = !node.isChecked } }, m("i.material-icons.w3-theme-light", node.isChecked?"check_box":"check_box_outline_blank") ), m("div", role.level), m("div", role.roleid), m("div", role.name) ] } } as iTreetable, m(TopbarButton, { onclick: applyRoles, tooltip: MOVERIGHT } as iTopbarButton, m("i.material-icons", "keyboard_double_arrow_right") ), m(TopbarButton, { onclick: () => {m.mount(gridMountpoints.modalhook, { view: (vnode) => { return m(RoleForm, {role: {id: -1, level: 0, name: "", roleid: ""} as iRole}) } })} } as iTopbarButton, m("i.material-icons", "add") ), m(TopbarButton, { onclick: () => {m.mount(gridMountpoints.modalhook, DeleteRolesMessage)} } as iTopbarButton, m("i.material-icons", "delete_forever") ), m(TopbarButton, { onclick: () => { let roles: iRole[] = [] localDataModel.rolenodes.forEach((rolenode: Treenode) => { if (rolenode.isChecked) { roles.push(rolenode.nodevalue) } }) if (roles.length != 1) { m.mount(gridMountpoints.modalhook, WrongRoleEditSelectionMessage) } else { m.mount(gridMountpoints.modalhook, { view: (vnode) => { return m(RoleForm, { role: roles[0] as iRole }) } }) } } } as iTopbarButton, m("i.material-icons", "edit") ) ) ), m("div.w3-cell.w3-padding.w3-mobile", m("h1", "Anwendungen"), m(Treetable, { treenodes: localDataModel.appnodes, startexpanded: true, columnmapper: (node: iTreenode) => { let cols: any[] = [] // Hier wird "Duck-Typing" genutzt, um den Typ von node.nodevalue zu ermitteln // Je nach Typ werden die Spalten der Tabelle dann anders befüllt if (node.nodevalue['appid'] != undefined) { let app = node.nodevalue as iApplication cols[0] = "enabled" cols[1] = app.appid cols[2] = app.name } else if (node.nodevalue['roleid'] != undefined) { let role = node.nodevalue as iRole cols[0] = "disabled" cols[1] = role.roleid cols[2] = role.name } return [ m(cols[0] == "enabled"?"span.w3-button":"span", { style: {padding: '0px'}, onclick: () => { if (cols[0] == "enabled") { node.isChecked = !node.isChecked } } }, m("i.material-icons.w3-theme-light", node.isChecked?"check_box":"check_box_outline_blank") ), m("div", cols[1]), m("div", cols[2]) ] } } as iTreetable, m(TopbarButton, { onclick: applyRoles, tooltip: MOVERIGHT } as iTopbarButton, m("i.material-icons", "keyboard_double_arrow_right") ), m(TopbarButton, { onclick: () => {m.mount(gridMountpoints.modalhook, { view: (vnode) => { return m(ApplicationForm, {application: {id: -1} as iApplication} as iApplicationForm) } } )} } as iTopbarButton, m("i.material-icons", "add") ), m(TopbarButton, { onclick: () => {m.mount(gridMountpoints.modalhook, DeleteApplicatonsMessage)} } as iTopbarButton, m("i.material-icons", "delete_forever") ), m(TopbarButton, { onclick: () => { let apps: iApplication[] = [] localDataModel.appnodes.forEach((appnode: Treenode) => { if (appnode.isChecked) { apps.push(appnode.nodevalue) } }) if (apps.length != 1) { m.mount(gridMountpoints.modalhook, WrongApplicationEditSelectionMessage) } else { m.mount(gridMountpoints.modalhook, { view: (vnode) => { return m(ApplicationForm, { application: apps[0] as iApplication } as iApplicationForm) } }) } } } as iTopbarButton, m("i.material-icons", "edit") ) ) ) ) ) ) } } } function sortRoleNodes(node1: iTreenode, node2: iTreenode) { // es soll zuerst nach Level und dann nach rowid sortiert werden let v1 = (node1.nodevalue['level'].toString().padStart(6,'0') + node1.nodevalue['roleid']).toLocaleLowerCase('de-de') let v2 = (node2.nodevalue['level'].toString().padStart(6,'0') + node2.nodevalue['roleid']).toLocaleLowerCase('de-de') if (v1 < v2) { return -1 } if (v1 > v2) { return 1 } return 0 } function applyRoles() { let checkedApps = localDataModel.appnodes.filter((appnode: iTreenode) => { return appnode.isChecked }) if (checkedApps.length == 0) { m.mount(gridMountpoints.modalhook, ErrorMessage) return } m.mount(gridMountpoints.modalhook, {view: () => {return m(ApplyRolesWarning, {applyroles: doit})}}) function doit() { checkedApps.forEach((appnode: iTreenode) => { localDataModel.rolenodes.forEach((rolenode: iTreenode) => { let approle: iTreenode = appnode.children.find((child: iTreenode) => { return child.nodevalue['roleid'] === rolenode.nodevalue['roleid'] }) if (approle != undefined) { approle.isChecked = rolenode.isChecked } else if (rolenode.isChecked) { let newapprole = new Treenode(rolenode.nodevalue, false, []) newapprole.isChecked = rolenode.isChecked appnode.children.push(newapprole) } }) }) localDataModel.updateApplicationRoles() } } const ErrorMessage: m.Component = { view: (vnode) => { return ( m(ModalView, { title: "Fehler", type: ModalType.ERROR, width: '33%' } as iModalView, m("p", "Es wurden noch keine Ziel-Anwendungen markiert!"), m("div.w3-bar.w3-padding-16", [ m("button.w3-button.w3-theme.w3-right", {onclick: () => {m.mount(gridMountpoints.modalhook, null)}}, "OK"), ] ) ) ) } } export interface iApplyRolesWaring { applyroles: Function } const ApplyRolesWarning: m.Component = { view: (vnode) => { return ( m(ModalView, { title: "Hinweis", type: ModalType.MESSAGE, width: '33%' } as iModalView, m("p", "Wenn Rollen nicht ausgewählt sind bedeutet dies, dass die Rollen in den ausgewählten Anwendungen gelöscht werden!"), m("div.w3-bar.w3-padding-16", [ m("button.w3-button.w3-theme.w3-right", {onclick: () => {m.mount(gridMountpoints.modalhook, null)}}, "Abbruch"), m("button.w3-button.w3-theme.w3-right", {onclick: () => {m.mount(gridMountpoints.modalhook, null); vnode.attrs.applyroles()}}, "OK"), ] ) ) ) } } const WrongRoleEditSelectionMessage: m.Component = { view: () => { return m(WrongEditSelectionMessage, {elementname: "Rolle"} as iWrongEditSelectionMessage) } } const WrongApplicationEditSelectionMessage: m.Component = { view: () => { return m(WrongEditSelectionMessage, {elementname: "Anwendung"} as iWrongEditSelectionMessage) } } const DeleteRolesMessage: m.ClosureComponent = (vnode) => { let roles: iRole[] return { oninit: (vnode) => { roles = [] localDataModel.rolenodes.forEach((rolenode: iTreenode) => { if (rolenode.isChecked) { roles.push(rolenode.nodevalue) } }) }, view: (vnode) => { return ( m(ModalView, { title: "Rollen löschen", type: ModalType.MESSAGE, width: '33%' } as iModalView, m("p", "Sie wollen die folgenden Rollen löschen:"), m("ul", roles.map((role: iRole) => { return m("li", role.roleid) }) ), m("div.w3-bar.w3-padding-16", [ m("button.w3-button.w3-theme.w3-right", { style: {'margin-left': '4px'}, onclick: () => { localDataModel.deleteRoles(roles); m.mount(gridMountpoints.modalhook, null) }}, "Löschen"), m("button.w3-button.w3-theme.w3-right", {onclick: () => {m.mount(gridMountpoints.modalhook, null)}}, "Abbrechen"), ] ) ) ) } } } interface iRoleForm { role: iRole } const RoleForm: m.ClosureComponent = () => { let form: HTMLFormElement = null; let currentrole: iRole function reset (e) { form.reset() } function unmount () { m.mount(gridMountpoints.modalhook, null) } function submit (e) { e.preventDefault() let formdata: FormData = new FormData(e.target as HTMLFormElement) for (let element of formdata) { currentrole[element[0]] = element[1] } localDataModel.updateRole(currentrole) unmount() } return { view: (vnode) => { currentrole = (vnode.attrs.role === undefined) ? {} as iRole : vnode.attrs.role return m(ModalView, { title: "Rolle", type: ModalType.MESSAGE, width: 'max-content' } as iModalView, m("form.w3-padding-16", {oncreate: (vnode) => {form = vnode.dom as HTMLFormElement}, onsubmit: submit}, m("label", "ID (intern)"), m("div.w3-padding-small", currentrole.id), m("label", "Rollen-ID"), m("input.w3-input.w3-border", {name: 'roleid', type: 'text', value: currentrole.roleid}), m("label", "Name"), m("input.w3-input.w3-border", {name: 'name', type: 'text', value: currentrole.name}), m("label", "Level"), m("input.w3-input.w3-border", {name: 'level', type: 'number', value: currentrole.level}), m("label", "Beschreibung"), m("input.w3-input.w3-border", {name: 'beschreibung', type: 'text', value: currentrole.beschreibung}), m("div.w3-bar.w3-padding-16", [ m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, type: "submit"}, "Speichern"), m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, onclick: unmount, type: "button"}, "Abbrechen"), m("button.w3-button.w3-theme.w3-right", {onclick: reset, type: "button"}, "Zurücksetzen") ] ) ) ) } } } interface iApplicationForm { application: iApplication } const ApplicationForm: m.ClosureComponent = () => { let form: HTMLFormElement = null; let currentapp: iApplication let isDirty = false function reset (e) { form.reset() } function unmount () { if (isDirty) { alert("is Dirty") } m.mount(gridMountpoints.modalhook, null) } function submit (e) { e.preventDefault() let formdata: FormData = new FormData(e.target as HTMLFormElement) for (let element of formdata) { currentapp[element[0]] = element[1] } localDataModel.updateApplication(currentapp) unmount() } return { view: (vnode) => { currentapp = (vnode.attrs.application === undefined) ? {} as iApplication : vnode.attrs.application return m(ModalView, { title: "Anwendung", type: ModalType.MESSAGE, width: 'max-content' } as iModalView, m("form.w3-padding-16", {oncreate: (vnode) => {form = vnode.dom as HTMLFormElement}, onsubmit: submit}, m("label", "ID (intern)"), m("div.w3-padding-small", currentapp.id), m("label", "Application-ID"), m("input.w3-input.w3-border", {name: 'appid', type: 'text', value: currentapp.appid}), m("label", "Name"), m("input.w3-input.w3-border", {name: 'name', type: 'text', value: currentapp.name}), m("label", "Minimum-Version"), m("input.w3-input.w3-border", {name: 'minversion', type: 'text', value: currentapp.minversion}), m("label", "Beschreibung"), m("input.w3-input.w3-border", {name: 'beschreibung', type: 'text', value: currentapp.beschreibung}), m("div.w3-bar.w3-padding-16", [ m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, type: "submit"}, "Speichern"), m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, onclick: unmount, type: "button"}, "Abbrechen"), m("button.w3-button.w3-theme.w3-right", {onclick: reset, type: "button"}, "Zurücksetzen") ] ) ) ) } } } const DeleteApplicatonsMessage: m.ClosureComponent = (vnode) => { let apps: iApplication[] return { oninit: (vnode) => { apps = [] localDataModel.appnodes.forEach((appnode: iTreenode) => { if (appnode.isChecked) { apps.push(appnode.nodevalue) } }) }, view: (vnode) => { return ( m(ModalView, { title: "Anwendungen löschen", type: ModalType.MESSAGE, width: '33%' } as iModalView, m("p.w3-panel.w3-red", [ "Vor dem Löschen muss sichergestellt sein, dass es noch mindestens eine App gibt, in der die Rolle 'SysAdmin' ", "vergeben ist und in der ein Benutzer diese Rolle besitzt!" ] ), m("p", "Sie wollen die folgenden Anwendungen löschen:"), m("ul", apps.map((app: iApplication) => { return m("li", app.appid) }) ), m("div.w3-bar.w3-padding-16", [ m("button.w3-button.w3-theme.w3-right", { style: {'margin-left': '4px'}, onclick: () => { localDataModel.deleteApplications(apps); m.mount(gridMountpoints.modalhook, null) }}, "Löschen"), m("button.w3-button.w3-theme.w3-right", {onclick: () => {m.mount(gridMountpoints.modalhook, null)}}, "Abbrechen"), ] ) ) ) } } }