diff --git a/controllers/application.go b/controllers/application.go new file mode 100644 index 00000000..2c573819 --- /dev/null +++ b/controllers/application.go @@ -0,0 +1,70 @@ +// Copyright 2020 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controllers + +import ( + "encoding/json" + + "github.com/casdoor/casdoor/object" +) + +func (c *ApiController) GetApplications() { + owner := c.Input().Get("owner") + + c.Data["json"] = object.GetApplications(owner) + c.ServeJSON() +} + +func (c *ApiController) GetApplication() { + id := c.Input().Get("id") + + c.Data["json"] = object.GetApplication(id) + c.ServeJSON() +} + +func (c *ApiController) UpdateApplication() { + id := c.Input().Get("id") + + var application object.Application + err := json.Unmarshal(c.Ctx.Input.RequestBody, &application) + if err != nil { + panic(err) + } + + c.Data["json"] = object.UpdateApplication(id, &application) + c.ServeJSON() +} + +func (c *ApiController) AddApplication() { + var application object.Application + err := json.Unmarshal(c.Ctx.Input.RequestBody, &application) + if err != nil { + panic(err) + } + + c.Data["json"] = object.AddApplication(&application) + c.ServeJSON() +} + +func (c *ApiController) DeleteApplication() { + var application object.Application + err := json.Unmarshal(c.Ctx.Input.RequestBody, &application) + if err != nil { + panic(err) + } + + c.Data["json"] = object.DeleteApplication(&application) + c.ServeJSON() +} diff --git a/object/adapter.go b/object/adapter.go index 541e1b6f..d244e801 100644 --- a/object/adapter.go +++ b/object/adapter.go @@ -89,12 +89,22 @@ func (a *Adapter) close() { } func (a *Adapter) createTable() { - err := a.engine.Sync2(new(User)) + err := a.engine.Sync2(new(Organization)) if err != nil { panic(err) } - err = a.engine.Sync2(new(Organization)) + err = a.engine.Sync2(new(User)) + if err != nil { + panic(err) + } + + err = a.engine.Sync2(new(Provider)) + if err != nil { + panic(err) + } + + err = a.engine.Sync2(new(Application)) if err != nil { panic(err) } diff --git a/object/application.go b/object/application.go new file mode 100644 index 00000000..21080e6e --- /dev/null +++ b/object/application.go @@ -0,0 +1,91 @@ +// Copyright 2020 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package object + +import ( + "github.com/casdoor/casdoor/util" + "xorm.io/core" +) + +type Application struct { + Owner string `xorm:"varchar(100) notnull pk" json:"owner"` + Name string `xorm:"varchar(100) notnull pk" json:"name"` + CreatedTime string `xorm:"varchar(100)" json:"createdTime"` + + DisplayName string `xorm:"varchar(100)" json:"displayName"` + Providers []string `xorm:"varchar(100)" json:"providers"` +} + +func GetApplications(owner string) []*Application { + applications := []*Application{} + err := adapter.engine.Desc("created_time").Find(&applications, &Application{Owner: owner}) + if err != nil { + panic(err) + } + + return applications +} + +func getApplication(owner string, name string) *Application { + application := Application{Owner: owner, Name: name} + existed, err := adapter.engine.Get(&application) + if err != nil { + panic(err) + } + + if existed { + return &application + } else { + return nil + } +} + +func GetApplication(id string) *Application { + owner, name := util.GetOwnerAndNameFromId(id) + return getApplication(owner, name) +} + +func UpdateApplication(id string, application *Application) bool { + owner, name := util.GetOwnerAndNameFromId(id) + if getApplication(owner, name) == nil { + return false + } + + _, err := adapter.engine.Id(core.PK{owner, name}).AllCols().Update(application) + if err != nil { + panic(err) + } + + //return affected != 0 + return true +} + +func AddApplication(application *Application) bool { + affected, err := adapter.engine.Insert(application) + if err != nil { + panic(err) + } + + return affected != 0 +} + +func DeleteApplication(application *Application) bool { + affected, err := adapter.engine.Id(core.PK{application.Owner, application.Name}).Delete(&Application{}) + if err != nil { + panic(err) + } + + return affected != 0 +} diff --git a/routers/router.go b/routers/router.go index cdd29029..13ceb7cb 100644 --- a/routers/router.go +++ b/routers/router.go @@ -50,4 +50,10 @@ func initAPI() { beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider") beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider") beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider") + + beego.Router("/api/get-applications", &controllers.ApiController{}, "GET:GetApplications") + beego.Router("/api/get-application", &controllers.ApiController{}, "GET:GetApplication") + beego.Router("/api/update-application", &controllers.ApiController{}, "POST:UpdateApplication") + beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication") + beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication") } diff --git a/web/src/App.js b/web/src/App.js index d9ab0681..90a15dfb 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -25,6 +25,8 @@ import UserListPage from "./UserListPage"; import UserEditPage from "./UserEditPage"; import ProviderListPage from "./ProviderListPage"; import ProviderEditPage from "./ProviderEditPage"; +import ApplicationListPage from "./ApplicationListPage"; +import ApplicationEditPage from "./ApplicationEditPage"; const { Header, Footer } = Layout; @@ -56,6 +58,8 @@ class App extends Component { this.setState({ selectedMenuKey: 2 }); } else if (uri.includes('providers')) { this.setState({ selectedMenuKey: 3 }); + } else if (uri.includes('applications')) { + this.setState({ selectedMenuKey: 4 }); } else { this.setState({ selectedMenuKey: -1 }); } @@ -216,6 +220,13 @@ class App extends Component { ); + res.push( + + + Applications + + + ); return res; } @@ -273,6 +284,8 @@ class App extends Component { + + ) diff --git a/web/src/ApplicationEditPage.js b/web/src/ApplicationEditPage.js new file mode 100644 index 00000000..fd92677f --- /dev/null +++ b/web/src/ApplicationEditPage.js @@ -0,0 +1,137 @@ +import React from "react"; +import {Button, Card, Col, Input, Row} from 'antd'; +import {LinkOutlined} from "@ant-design/icons"; +import * as ApplicationBackend from "./backend/ApplicationBackend"; +import * as Setting from "./Setting"; + +class ApplicationEditPage extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + applicationName: props.match.params.applicationName, + application: null, + tasks: [], + resources: [], + }; + } + + componentWillMount() { + this.getApplication(); + } + + getApplication() { + ApplicationBackend.getApplication("admin", this.state.applicationName) + .then((application) => { + this.setState({ + application: application, + }); + }); + } + + parseApplicationField(key, value) { + // if ([].includes(key)) { + // value = Setting.myParseInt(value); + // } + return value; + } + + updateApplicationField(key, value) { + value = this.parseApplicationField(key, value); + + let application = this.state.application; + application[key] = value; + this.setState({ + application: application, + }); + } + + renderApplication() { + return ( + + Edit Application     + + + } style={{marginLeft: '5px'}} type="inner"> + + + Name: + + + { + this.updateApplicationField('name', e.target.value); + }} /> + + + + + Display Name: + + + { + this.updateApplicationField('displayName', e.target.value); + }} /> + + + + + Providers: + + + { + this.updateApplicationField('providers', e.target.value); + }} /> + + + + ) + } + + submitApplicationEdit() { + let application = Setting.deepCopy(this.state.application); + ApplicationBackend.updateApplication(this.state.application.owner, this.state.applicationName, application) + .then((res) => { + if (res) { + Setting.showMessage("success", `Successfully saved`); + this.setState({ + applicationName: this.state.application.name, + }); + this.props.history.push(`/applications/${this.state.application.name}`); + } else { + Setting.showMessage("error", `failed to save: server side failure`); + this.updateApplicationField('name', this.state.applicationName); + } + }) + .catch(error => { + Setting.showMessage("error", `failed to save: ${error}`); + }); + } + + render() { + return ( +
+ + + + + { + this.state.application !== null ? this.renderApplication() : null + } + + + + + + + + + + + +
+ ); + } +} + +export default ApplicationEditPage; diff --git a/web/src/ApplicationListPage.js b/web/src/ApplicationListPage.js new file mode 100644 index 00000000..4bf3151a --- /dev/null +++ b/web/src/ApplicationListPage.js @@ -0,0 +1,161 @@ +import React from "react"; +import {Button, Col, Popconfirm, Row, Table} from 'antd'; +import moment from "moment"; +import * as Setting from "./Setting"; +import * as ApplicationBackend from "./backend/ApplicationBackend"; + +class ApplicationListPage extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + applications: null, + }; + } + + componentWillMount() { + this.getApplications(); + } + + getApplications() { + ApplicationBackend.getApplications("admin") + .then((res) => { + this.setState({ + applications: res, + }); + }); + } + + newApplication() { + return { + owner: "admin", // this.props.account.applicationname, + name: `application_${this.state.applications.length}`, + createdTime: moment().format(), + displayName: `New Application - ${this.state.applications.length}`, + providers: [], + } + } + + addApplication() { + const newApplication = this.newApplication(); + ApplicationBackend.addApplication(newApplication) + .then((res) => { + Setting.showMessage("success", `Application added successfully`); + this.setState({ + applications: Setting.prependRow(this.state.applications, newApplication), + }); + } + ) + .catch(error => { + Setting.showMessage("error", `Application failed to add: ${error}`); + }); + } + + deleteApplication(i) { + ApplicationBackend.deleteApplication(this.state.applications[i]) + .then((res) => { + Setting.showMessage("success", `Application deleted successfully`); + this.setState({ + applications: Setting.deleteRow(this.state.applications, i), + }); + } + ) + .catch(error => { + Setting.showMessage("error", `Application failed to delete: ${error}`); + }); + } + + renderTable(applications) { + const columns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + width: '120px', + sorter: (a, b) => a.name.localeCompare(b.name), + render: (text, record, index) => { + return ( + {text} + ) + } + }, + { + title: 'Created Time', + dataIndex: 'createdTime', + key: 'createdTime', + width: '160px', + sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), + render: (text, record, index) => { + return Setting.getFormattedDate(text); + } + }, + { + title: 'Display Name', + dataIndex: 'displayName', + key: 'displayName', + // width: '100px', + sorter: (a, b) => a.displayName.localeCompare(b.displayName), + }, + { + title: 'Providers', + dataIndex: 'providers', + key: 'providers', + width: '150px', + sorter: (a, b) => a.providers.localeCompare(b.providers), + }, + { + title: 'Action', + dataIndex: '', + key: 'op', + width: '170px', + render: (text, record, index) => { + return ( +
+ + this.deleteApplication(index)} + > + + +
+ ) + } + }, + ]; + + return ( +
+ ( +
+ Applications     + +
+ )} + loading={applications === null} + /> + + ); + } + + render() { + return ( +
+ +
+ + + { + this.renderTable(this.state.applications) + } + + + + + + ); + } +} + +export default ApplicationListPage; diff --git a/web/src/backend/ApplicationBackend.js b/web/src/backend/ApplicationBackend.js new file mode 100644 index 00000000..c2df90d1 --- /dev/null +++ b/web/src/backend/ApplicationBackend.js @@ -0,0 +1,42 @@ +import * as Setting from "../Setting"; + +export function getApplications(owner) { + return fetch(`${Setting.ServerUrl}/api/get-applications?owner=${owner}`, { + method: "GET", + credentials: "include" + }).then(res => res.json()); +} + +export function getApplication(owner, name) { + return fetch(`${Setting.ServerUrl}/api/get-application?id=${owner}/${encodeURIComponent(name)}`, { + method: "GET", + credentials: "include" + }).then(res => res.json()); +} + +export function updateApplication(owner, name, application) { + let newApplication = Setting.deepCopy(application); + return fetch(`${Setting.ServerUrl}/api/update-application?id=${owner}/${encodeURIComponent(name)}`, { + method: 'POST', + credentials: 'include', + body: JSON.stringify(newApplication), + }).then(res => res.json()); +} + +export function addApplication(application) { + let newApplication = Setting.deepCopy(application); + return fetch(`${Setting.ServerUrl}/api/add-application`, { + method: 'POST', + credentials: 'include', + body: JSON.stringify(newApplication), + }).then(res => res.json()); +} + +export function deleteApplication(application) { + let newApplication = Setting.deepCopy(application); + return fetch(`${Setting.ServerUrl}/api/delete-application`, { + method: 'POST', + credentials: 'include', + body: JSON.stringify(newApplication), + }).then(res => res.json()); +}