From b9adda2277ace4d1e8ed91732b10ef13d5089725 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sun, 20 Dec 2020 21:25:23 +0800 Subject: [PATCH] Add provider list and edit pages. --- controllers/provider.go | 70 +++++++++++ object/provider.go | 94 +++++++++++++++ routers/router.go | 16 ++- web/src/App.js | 13 ++ web/src/ProviderEditPage.js | 167 ++++++++++++++++++++++++++ web/src/ProviderListPage.js | 185 +++++++++++++++++++++++++++++ web/src/backend/ProviderBackend.js | 42 +++++++ 7 files changed, 582 insertions(+), 5 deletions(-) create mode 100644 controllers/provider.go create mode 100644 object/provider.go create mode 100644 web/src/ProviderEditPage.js create mode 100644 web/src/ProviderListPage.js create mode 100644 web/src/backend/ProviderBackend.js diff --git a/controllers/provider.go b/controllers/provider.go new file mode 100644 index 00000000..d7eb442e --- /dev/null +++ b/controllers/provider.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) GetProviders() { + owner := c.Input().Get("owner") + + c.Data["json"] = object.GetProviders(owner) + c.ServeJSON() +} + +func (c *ApiController) GetProvider() { + id := c.Input().Get("id") + + c.Data["json"] = object.GetProvider(id) + c.ServeJSON() +} + +func (c *ApiController) UpdateProvider() { + id := c.Input().Get("id") + + var provider object.Provider + err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider) + if err != nil { + panic(err) + } + + c.Data["json"] = object.UpdateProvider(id, &provider) + c.ServeJSON() +} + +func (c *ApiController) AddProvider() { + var provider object.Provider + err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider) + if err != nil { + panic(err) + } + + c.Data["json"] = object.AddProvider(&provider) + c.ServeJSON() +} + +func (c *ApiController) DeleteProvider() { + var provider object.Provider + err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider) + if err != nil { + panic(err) + } + + c.Data["json"] = object.DeleteProvider(&provider) + c.ServeJSON() +} diff --git a/object/provider.go b/object/provider.go new file mode 100644 index 00000000..0861376c --- /dev/null +++ b/object/provider.go @@ -0,0 +1,94 @@ +// 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 Provider 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"` + Type string `xorm:"varchar(100)" json:"type"` + ClientId string `xorm:"varchar(100)" json:"clientId"` + ClientSecret string `xorm:"varchar(100)" json:"clientSecret"` + ProviderUrl string `xorm:"varchar(100)" json:"providerUrl"` +} + +func GetProviders(owner string) []*Provider { + providers := []*Provider{} + err := adapter.engine.Desc("created_time").Find(&providers, &Provider{Owner: owner}) + if err != nil { + panic(err) + } + + return providers +} + +func getProvider(owner string, name string) *Provider { + provider := Provider{Owner: owner, Name: name} + existed, err := adapter.engine.Get(&provider) + if err != nil { + panic(err) + } + + if existed { + return &provider + } else { + return nil + } +} + +func GetProvider(id string) *Provider { + owner, name := util.GetOwnerAndNameFromId(id) + return getProvider(owner, name) +} + +func UpdateProvider(id string, provider *Provider) bool { + owner, name := util.GetOwnerAndNameFromId(id) + if getProvider(owner, name) == nil { + return false + } + + _, err := adapter.engine.Id(core.PK{owner, name}).AllCols().Update(provider) + if err != nil { + panic(err) + } + + //return affected != 0 + return true +} + +func AddProvider(provider *Provider) bool { + affected, err := adapter.engine.Insert(provider) + if err != nil { + panic(err) + } + + return affected != 0 +} + +func DeleteProvider(provider *Provider) bool { + affected, err := adapter.engine.Id(core.PK{provider.Owner, provider.Name}).Delete(&Provider{}) + if err != nil { + panic(err) + } + + return affected != 0 +} diff --git a/routers/router.go b/routers/router.go index 74ea5e50..cdd29029 100644 --- a/routers/router.go +++ b/routers/router.go @@ -33,15 +33,21 @@ func initAPI() { ) beego.AddNamespace(ns) + beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations") + beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization") + beego.Router("/api/update-organization", &controllers.ApiController{}, "POST:UpdateOrganization") + beego.Router("/api/add-organization", &controllers.ApiController{}, "POST:AddOrganization") + beego.Router("/api/delete-organization", &controllers.ApiController{}, "POST:DeleteOrganization") + beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers") beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser") beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser") beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser") beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser") - beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations") - beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization") - beego.Router("/api/update-organization", &controllers.ApiController{}, "POST:UpdateOrganization") - beego.Router("/api/add-organization", &controllers.ApiController{}, "POST:AddOrganization") - beego.Router("/api/delete-organization", &controllers.ApiController{}, "POST:DeleteOrganization") + beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders") + beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider") + 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") } diff --git a/web/src/App.js b/web/src/App.js index 853de942..d9ab0681 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -23,6 +23,8 @@ import OrganizationListPage from "./OrganizationListPage"; import OrganizationEditPage from "./OrganizationEditPage"; import UserListPage from "./UserListPage"; import UserEditPage from "./UserEditPage"; +import ProviderListPage from "./ProviderListPage"; +import ProviderEditPage from "./ProviderEditPage"; const { Header, Footer } = Layout; @@ -52,6 +54,8 @@ class App extends Component { this.setState({ selectedMenuKey: 1 }); } else if (uri.includes('users')) { this.setState({ selectedMenuKey: 2 }); + } else if (uri.includes('providers')) { + this.setState({ selectedMenuKey: 3 }); } else { this.setState({ selectedMenuKey: -1 }); } @@ -205,6 +209,13 @@ class App extends Component { ); + res.push( + + + Providers + + + ); return res; } @@ -260,6 +271,8 @@ class App extends Component { + + ) diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js new file mode 100644 index 00000000..44924bcc --- /dev/null +++ b/web/src/ProviderEditPage.js @@ -0,0 +1,167 @@ +import React from "react"; +import {Button, Card, Col, Input, Row} from 'antd'; +import {LinkOutlined} from "@ant-design/icons"; +import * as ProviderBackend from "./backend/ProviderBackend"; +import * as Setting from "./Setting"; + +class ProviderEditPage extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + providerName: props.match.params.providerName, + provider: null, + tasks: [], + resources: [], + }; + } + + componentWillMount() { + this.getProvider(); + } + + getProvider() { + ProviderBackend.getProvider("admin", this.state.providerName) + .then((provider) => { + this.setState({ + provider: provider, + }); + }); + } + + parseProviderField(key, value) { + // if ([].includes(key)) { + // value = Setting.myParseInt(value); + // } + return value; + } + + updateProviderField(key, value) { + value = this.parseProviderField(key, value); + + let provider = this.state.provider; + provider[key] = value; + this.setState({ + provider: provider, + }); + } + + renderProvider() { + return ( + + Edit Provider     + + + } style={{marginLeft: '5px'}} type="inner"> + + + Name: + + + { + this.updateProviderField('name', e.target.value); + }} /> + + + + + Display Name: + + + { + this.updateProviderField('displayName', e.target.value); + }} /> + + + + + type: + + + { + this.updateProviderField('type', e.target.value); + }} /> + + + + + Client Id: + + + { + this.updateProviderField('clientId', e.target.value); + }} /> + + + + + Client Secret: + + + { + this.updateProviderField('clientSecret', e.target.value); + }} /> + + + + + Provider Url: + + + { + this.updateProviderField('providerUrl', e.target.value); + }} /> + + + + ) + } + + submitProviderEdit() { + let provider = Setting.deepCopy(this.state.provider); + ProviderBackend.updateProvider(this.state.provider.owner, this.state.providerName, provider) + .then((res) => { + if (res) { + Setting.showMessage("success", `Successfully saved`); + this.setState({ + providerName: this.state.provider.name, + }); + this.props.history.push(`/providers/${this.state.provider.name}`); + } else { + Setting.showMessage("error", `failed to save: server side failure`); + this.updateProviderField('name', this.state.providerName); + } + }) + .catch(error => { + Setting.showMessage("error", `failed to save: ${error}`); + }); + } + + render() { + return ( +
+ + + + + { + this.state.provider !== null ? this.renderProvider() : null + } + + + + + + + + + + + +
+ ); + } +} + +export default ProviderEditPage; diff --git a/web/src/ProviderListPage.js b/web/src/ProviderListPage.js new file mode 100644 index 00000000..35a67f7b --- /dev/null +++ b/web/src/ProviderListPage.js @@ -0,0 +1,185 @@ +import React from "react"; +import {Button, Col, Popconfirm, Row, Table} from 'antd'; +import moment from "moment"; +import * as Setting from "./Setting"; +import * as ProviderBackend from "./backend/ProviderBackend"; + +class ProviderListPage extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + providers: null, + }; + } + + componentWillMount() { + this.getProviders(); + } + + getProviders() { + ProviderBackend.getProviders("admin") + .then((res) => { + this.setState({ + providers: res, + }); + }); + } + + newProvider() { + return { + owner: "admin", // this.props.account.providername, + name: `provider_${this.state.providers.length}`, + createdTime: moment().format(), + displayName: `New Provider - ${this.state.providers.length}`, + type: "github", + clientId: "", + clientSecret: "", + providerUrl: "https://github.com/organizations/xxx/settings/applications/1234567", + } + } + + addProvider() { + const newProvider = this.newProvider(); + ProviderBackend.addProvider(newProvider) + .then((res) => { + Setting.showMessage("success", `Provider added successfully`); + this.setState({ + providers: Setting.prependRow(this.state.providers, newProvider), + }); + } + ) + .catch(error => { + Setting.showMessage("error", `Provider failed to add: ${error}`); + }); + } + + deleteProvider(i) { + ProviderBackend.deleteProvider(this.state.providers[i]) + .then((res) => { + Setting.showMessage("success", `Provider deleted successfully`); + this.setState({ + providers: Setting.deleteRow(this.state.providers, i), + }); + } + ) + .catch(error => { + Setting.showMessage("error", `Provider failed to delete: ${error}`); + }); + } + + renderTable(providers) { + 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: 'Type', + dataIndex: 'type', + key: 'type', + width: '150px', + sorter: (a, b) => a.type.localeCompare(b.type), + }, + { + title: 'Client Id', + dataIndex: 'clientId', + key: 'clientId', + width: '150px', + sorter: (a, b) => a.clientId.localeCompare(b.clientId), + }, + { + title: 'Client Secret', + dataIndex: 'clientSecret', + key: 'clientSecret', + width: '150px', + sorter: (a, b) => a.clientSecret.localeCompare(b.clientSecret), + }, + { + title: 'Provider Url', + dataIndex: 'providerUrl', + key: 'providerUrl', + width: '150px', + sorter: (a, b) => a.providerUrl.localeCompare(b.providerUrl), + }, + { + title: 'Action', + dataIndex: '', + key: 'op', + width: '170px', + render: (text, record, index) => { + return ( +
+ + this.deleteProvider(index)} + > + + +
+ ) + } + }, + ]; + + return ( +
+ ( +
+ Providers     + +
+ )} + loading={providers === null} + /> + + ); + } + + render() { + return ( +
+ +
+ + + { + this.renderTable(this.state.providers) + } + + + + + + ); + } +} + +export default ProviderListPage; diff --git a/web/src/backend/ProviderBackend.js b/web/src/backend/ProviderBackend.js new file mode 100644 index 00000000..6057c56c --- /dev/null +++ b/web/src/backend/ProviderBackend.js @@ -0,0 +1,42 @@ +import * as Setting from "../Setting"; + +export function getProviders(owner) { + return fetch(`${Setting.ServerUrl}/api/get-providers?owner=${owner}`, { + method: "GET", + credentials: "include" + }).then(res => res.json()); +} + +export function getProvider(owner, name) { + return fetch(`${Setting.ServerUrl}/api/get-provider?id=${owner}/${encodeURIComponent(name)}`, { + method: "GET", + credentials: "include" + }).then(res => res.json()); +} + +export function updateProvider(owner, name, provider) { + let newProvider = Setting.deepCopy(provider); + return fetch(`${Setting.ServerUrl}/api/update-provider?id=${owner}/${encodeURIComponent(name)}`, { + method: 'POST', + credentials: 'include', + body: JSON.stringify(newProvider), + }).then(res => res.json()); +} + +export function addProvider(provider) { + let newProvider = Setting.deepCopy(provider); + return fetch(`${Setting.ServerUrl}/api/add-provider`, { + method: 'POST', + credentials: 'include', + body: JSON.stringify(newProvider), + }).then(res => res.json()); +} + +export function deleteProvider(provider) { + let newProvider = Setting.deepCopy(provider); + return fetch(`${Setting.ServerUrl}/api/delete-provider`, { + method: 'POST', + credentials: 'include', + body: JSON.stringify(newProvider), + }).then(res => res.json()); +}