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());
+}