Compare commits

..

6 Commits

Author SHA1 Message Date
aiden
2e3a323528 feat: Dingtalk provider supports fetching organization email (#1636)
* feat(dingtalk): try to get email from corp app

* chore: format codes

* chore: format codes (#1)

* Delete .fleet directory

* fix: fix syntax errors

* Update dingtalk.go

* style: fmt codes with gofumpt

---------

Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-03-10 21:47:54 +08:00
Gucheng Wang
09e8408a3d Fix Popconfirm text 2023-03-10 19:17:53 +08:00
陈温鹏
2998bbf4b9 fix: Put Popconfirm into a React component. (#1638)
* add "Sure to delete" to i18n(#1569)

* fix: add sure to delete to i18n

* fix: Put Popconfirm into a React component.
2023-03-10 19:16:08 +08:00
imp2002
404382f2e0 feat: fix incompatibility css inset when Safari version <=14.1 (#1635) 2023-03-09 22:01:39 +08:00
Gucheng Wang
71db1f62a9 Fix DingTalk oauth link 2023-03-09 21:11:16 +08:00
Gucheng Wang
07dc6bf7cd Refactor sysinfo page 2023-03-09 17:17:12 +08:00
28 changed files with 278 additions and 228 deletions

View File

@@ -15,22 +15,9 @@
package controllers
import (
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
type SystemInfo struct {
MemoryUsed uint64 `json:"memory_used"`
MemoryTotal uint64 `json:"memory_total"`
CpuUsage []float64 `json:"cpu_usage"`
}
type GitRepoInfo struct {
AheadCnt int `json:"ahead_cnt"`
Commit string `json:"commit"`
Version string `json:"version"`
}
// GetSystemInfo
// @Title GetSystemInfo
// @Tag System API
@@ -39,54 +26,32 @@ type GitRepoInfo struct {
// @Success 200 {object} object.SystemInfo The Response object
// @router /get-system-info [get]
func (c *ApiController) GetSystemInfo() {
id := c.GetString("id")
if id == "" {
id = c.GetSessionUsername()
}
user := object.GetUser(id)
if user == nil || !user.IsGlobalAdmin {
c.ResponseError(c.T("auth:Unauthorized operation"))
_, ok := c.RequireAdmin()
if !ok {
return
}
cpuUsage, err := util.GetCpuUsage()
systemInfo, err := util.GetSystemInfo()
if err != nil {
c.ResponseError(err.Error())
return
}
memoryUsed, memoryTotal, err := util.GetMemoryUsage()
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = SystemInfo{
CpuUsage: cpuUsage,
MemoryUsed: memoryUsed,
MemoryTotal: memoryTotal,
}
c.ServeJSON()
c.ResponseOk(systemInfo)
}
// GitRepoVersion
// @Title GitRepoVersion
// GetVersionInfo
// @Title GetVersionInfo
// @Tag System API
// @Description get local github repo's latest release version info
// @Success 200 {string} local latest version hash of casdoor
// @router /get-release [get]
func (c *ApiController) GitRepoVersion() {
aheadCnt, commit, version, err := util.GetGitRepoVersion()
// @Description get local git repo's latest release version info
// @Success 200 {string} local latest version hash of Casdoor
// @router /get-version-info [get]
func (c *ApiController) GetVersionInfo() {
versionInfo, err := util.GetVersionInfo()
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = GitRepoInfo{
AheadCnt: aheadCnt,
Commit: commit,
Version: version,
}
c.ServeJSON()
c.ResponseOk(versionInfo)
}

View File

@@ -15,11 +15,9 @@
package idp
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
@@ -170,10 +168,18 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
Email: dtUserInfo.Email,
AvatarUrl: dtUserInfo.AvatarUrl,
}
isUserInOrg, err := idp.isUserInOrg(userInfo.UnionId)
if !isUserInOrg {
corpAccessToken := idp.getInnerAppAccessToken()
userId, err := idp.getUserId(userInfo.UnionId, corpAccessToken)
if err != nil {
return nil, err
}
corpEmail, err := idp.getUserCorpEmail(userId, corpAccessToken)
if err == nil && corpEmail != "" {
userInfo.Email = corpEmail
}
return &userInfo, nil
}
@@ -202,23 +208,14 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
}
func (idp *DingTalkIdProvider) getInnerAppAccessToken() string {
appKey := idp.Config.ClientID
appSecret := idp.Config.ClientSecret
body := make(map[string]string)
body["appKey"] = appKey
body["appSecret"] = appSecret
bodyData, err := json.Marshal(body)
if err != nil {
log.Println(err.Error())
}
reader := bytes.NewReader(bodyData)
request, err := http.NewRequest("POST", "https://api.dingtalk.com/v1.0/oauth2/accessToken", reader)
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
resp, err := idp.Client.Do(request)
respBytes, err := ioutil.ReadAll(resp.Body)
body["appKey"] = idp.Config.ClientID
body["appSecret"] = idp.Config.ClientSecret
respBytes, err := idp.postWithBody(body, "https://api.dingtalk.com/v1.0/oauth2/accessToken")
if err != nil {
log.Println(err.Error())
}
var data struct {
ExpireIn int `json:"expireIn"`
AccessToken string `json:"accessToken"`
@@ -230,34 +227,53 @@ func (idp *DingTalkIdProvider) getInnerAppAccessToken() string {
return data.AccessToken
}
func (idp *DingTalkIdProvider) isUserInOrg(unionId string) (bool, error) {
func (idp *DingTalkIdProvider) getUserId(unionId string, accessToken string) (string, error) {
body := make(map[string]string)
body["unionid"] = unionId
bodyData, err := json.Marshal(body)
respBytes, err := idp.postWithBody(body, "https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token="+accessToken)
if err != nil {
log.Println(err.Error())
}
reader := bytes.NewReader(bodyData)
accessToken := idp.getInnerAppAccessToken()
request, _ := http.NewRequest("POST", "https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token="+accessToken, reader)
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
resp, err := idp.Client.Do(request)
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err.Error())
return "", err
}
var data struct {
ErrCode int `json:"errcode"`
ErrMessage string `json:"errmsg"`
Result struct {
UserId string `json:"userid"`
} `json:"result"`
}
err = json.Unmarshal(respBytes, &data)
if err != nil {
log.Println(err.Error())
return "", err
}
if data.ErrCode == 60121 {
return false, fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
return "", fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
} else if data.ErrCode != 0 {
return false, fmt.Errorf(data.ErrMessage)
return "", fmt.Errorf(data.ErrMessage)
}
return true, nil
return data.Result.UserId, nil
}
func (idp *DingTalkIdProvider) getUserCorpEmail(userId string, accessToken string) (string, error) {
body := make(map[string]string)
body["userid"] = userId
respBytes, err := idp.postWithBody(body, "https://oapi.dingtalk.com/topapi/v2/user/get?access_token="+accessToken)
if err != nil {
return "", err
}
var data struct {
ErrMessage string `json:"errmsg"`
Result struct {
Email string `json:"email"`
} `json:"result"`
}
err = json.Unmarshal(respBytes, &data)
if err != nil {
return "", err
}
if data.ErrMessage != "ok" {
return "", fmt.Errorf(data.ErrMessage)
}
return data.Result.Email, nil
}

View File

@@ -225,5 +225,5 @@ func initAPI() {
beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "Post:WebAuthnSigninFinish")
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
beego.Router("/api/get-release", &controllers.ApiController{}, "GET:GitRepoVersion")
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
}

View File

@@ -26,16 +26,26 @@ import (
"github.com/shirou/gopsutil/mem"
)
// get cpu usage
func GetCpuUsage() ([]float64, error) {
type SystemInfo struct {
CpuUsage []float64 `json:"cpuUsage"`
MemoryUsed uint64 `json:"memoryUsed"`
MemoryTotal uint64 `json:"memoryTotal"`
}
type VersionInfo struct {
Version string `json:"version"`
CommitId string `json:"commitId"`
CommitOffset int `json:"commitOffset"`
}
// getCpuUsage get cpu usage
func getCpuUsage() ([]float64, error) {
usage, err := cpu.Percent(time.Second, true)
return usage, err
}
var fileDate, version string
// get memory usage
func GetMemoryUsage() (uint64, uint64, error) {
// getMemoryUsage get memory usage
func getMemoryUsage() (uint64, uint64, error) {
virtualMem, err := mem.VirtualMemory()
if err != nil {
return 0, 0, err
@@ -47,21 +57,46 @@ func GetMemoryUsage() (uint64, uint64, error) {
return m.TotalAlloc, virtualMem.Total, nil
}
// get github current commit and repo release version
func GetGitRepoVersion() (int, string, string, error) {
func GetSystemInfo() (*SystemInfo, error) {
cpuUsage, err := getCpuUsage()
if err != nil {
return nil, err
}
memoryUsed, memoryTotal, err := getMemoryUsage()
if err != nil {
return nil, err
}
res := &SystemInfo{
CpuUsage: cpuUsage,
MemoryUsed: memoryUsed,
MemoryTotal: memoryTotal,
}
return res, nil
}
// GetVersionInfo get git current commit and repo release version
func GetVersionInfo() (*VersionInfo, error) {
res := &VersionInfo{
Version: "",
CommitId: "",
CommitOffset: -1,
}
_, filename, _, _ := runtime.Caller(0)
rootPath := path.Dir(path.Dir(filename))
r, err := git.PlainOpen(rootPath)
if err != nil {
return -1, "", "", err
return res, err
}
ref, err := r.Head()
if err != nil {
return -1, "", "", err
return res, err
}
tags, err := r.Tags()
if err != nil {
return -1, "", "", err
return res, err
}
tagMap := make(map[plumbing.Hash]string)
err = tags.ForEach(func(t *plumbing.Reference) error {
@@ -74,29 +109,34 @@ func GetGitRepoVersion() (int, string, string, error) {
return nil
})
if err != nil {
return -1, "", "", err
return res, err
}
cIter, err := r.Log(&git.LogOptions{From: ref.Hash()})
aheadCnt := 0
releaseVersion := ""
commitOffset := 0
version := ""
// iterates over the commits
err = cIter.ForEach(func(c *object.Commit) error {
tag, ok := tagMap[c.Hash]
if ok {
if releaseVersion == "" {
releaseVersion = tag
if version == "" {
version = tag
}
}
if releaseVersion == "" {
aheadCnt++
if version == "" {
commitOffset++
}
return nil
})
if err != nil {
return -1, "", "", err
return res, err
}
return aheadCnt, ref.Hash().String(), releaseVersion, nil
res = &VersionInfo{
Version: version,
CommitId: ref.Hash().String(),
CommitOffset: commitOffset,
}
return res, nil
}

View File

@@ -29,21 +29,21 @@ import (
)
func TestGetCpuUsage(t *testing.T) {
usage, err := GetCpuUsage()
usage, err := getCpuUsage()
assert.Nil(t, err)
t.Log(usage)
}
func TestGetMemoryUsage(t *testing.T) {
used, total, err := GetMemoryUsage()
used, total, err := getMemoryUsage()
assert.Nil(t, err)
t.Log(used, total)
}
func TestGetGitRepoVersion(t *testing.T) {
aheadCnt, commit, version, err := GetGitRepoVersion()
versionInfo, err := GetVersionInfo()
assert.Nil(t, err)
t.Log(aheadCnt, commit, version)
t.Log(versionInfo)
}
func TestGetVersion(t *testing.T) {

View File

@@ -14,12 +14,13 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from "antd";
import {Button, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as AdapterBackend from "./backend/AdapterBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class AdapterListPage extends BaseListPage {
newAdapter() {
@@ -204,12 +205,11 @@ class AdapterListPage extends BaseListPage {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/adapters/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteAdapter(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -16,7 +16,7 @@ import React, {Component} from "react";
import "./App.less";
import {Helmet} from "react-helmet";
import * as Setting from "./Setting";
import {StyleProvider} from "@ant-design/cssinjs";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
@@ -730,7 +730,7 @@ class App extends Component {
},
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
}}>
<StyleProvider hashPriority="high">
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
{
this.renderPage()
}

View File

@@ -14,13 +14,14 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Col, List, Popconfirm, Row, Table, Tooltip} from "antd";
import {Button, Col, List, Row, Table, Tooltip} from "antd";
import {EditOutlined} from "@ant-design/icons";
import moment from "moment";
import * as Setting from "./Setting";
import * as ApplicationBackend from "./backend/ApplicationBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class ApplicationListPage extends BaseListPage {
constructor(props) {
@@ -232,13 +233,12 @@ class ApplicationListPage extends BaseListPage {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/applications/${record.organization}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteApplication(index)}
disabled={record.name === "app-built-in"}
>
<Button style={{marginBottom: "10px"}} disabled={record.name === "app-built-in"} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,12 +14,13 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Table} from "antd";
import {Button, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as CertBackend from "./backend/CertBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class CertListPage extends BaseListPage {
newCert() {
@@ -168,12 +169,11 @@ class CertListPage extends BaseListPage {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/certs/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteCert(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,10 +14,11 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Col, Popconfirm, Row, Table} from "antd";
import {Button, Col, Row, Table} from "antd";
import * as Setting from "./Setting";
import * as LdapBackend from "./backend/LdapBackend";
import i18next from "i18next";
import PopconfirmModal from "./PopconfirmModal";
class LdapListPage extends React.Component {
constructor(props) {
@@ -139,13 +140,11 @@ class LdapListPage extends React.Component {
onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("ldap:Sync")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.serverName} ?`}
onConfirm={() => this.deleteLdap(index)}
>
<Button style={{marginBottom: "10px"}}
type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -13,11 +13,12 @@
// limitations under the License.
import React from "react";
import {Button, Col, Popconfirm, Row, Table} from "antd";
import {Button, Col, Row, Table} from "antd";
import * as Setting from "./Setting";
import i18next from "i18next";
import * as LdapBackend from "./backend/LdapBackend";
import {Link} from "react-router-dom";
import PopconfirmModal from "./PopconfirmModal";
class LdapTable extends React.Component {
constructor(props) {
@@ -157,13 +158,11 @@ class LdapTable extends React.Component {
onClick={() => Setting.goToLink(`/ldap/sync/${record.owner}/${record.id}`)}>{i18next.t("ldap:Sync")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
onClick={() => Setting.goToLink(`/ldap/${record.owner}/${record.id}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.serverName} ?`}
onConfirm={() => this.deleteRow(table, index)}
>
<Button style={{marginBottom: "10px"}}
type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,12 +14,13 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from "antd";
import {Button, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as ModelBackend from "./backend/ModelBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class ModelListPage extends BaseListPage {
newModel() {
@@ -142,12 +143,11 @@ class ModelListPage extends BaseListPage {
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary"
onClick={() => this.props.history.push(`/models/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteModel(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,12 +14,13 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from "antd";
import {Button, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class OrganizationListPage extends BaseListPage {
newOrganization() {
@@ -226,13 +227,12 @@ class OrganizationListPage extends BaseListPage {
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}/users`)}>{i18next.t("general:Users")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/organizations/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteOrganization(index)}
disabled={record.name === "built-in"}
>
<Button style={{marginBottom: "10px"}} disabled={record.name === "built-in"} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,13 +14,14 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Table} from "antd";
import {Button, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as PaymentBackend from "./backend/PaymentBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import * as Provider from "./auth/Provider";
import PopconfirmModal from "./PopconfirmModal";
class PaymentListPage extends BaseListPage {
newPayment() {
@@ -222,12 +223,11 @@ class PaymentListPage extends BaseListPage {
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/payments/${record.name}/result`)}>{i18next.t("payment:Result")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/payments/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deletePayment(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,12 +14,13 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from "antd";
import {Button, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as PermissionBackend from "./backend/PermissionBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class PermissionListPage extends BaseListPage {
newPermission() {
@@ -300,12 +301,11 @@ class PermissionListPage extends BaseListPage {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/permissions/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deletePermission(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -0,0 +1,33 @@
// Copyright 2021 The Casdoor 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.
import {Button, Popconfirm} from "antd";
import i18next from "i18next";
import React from "react";
export const PopconfirmModal = (props) => {
return (
<Popconfirm
title={props.title}
onConfirm={props.onConfirm}
disabled={props.disabled}
okText={i18next.t("user:OK")}
cancelText={i18next.t("general:Cancel")}
>
<Button style={{marginBottom: "10px"}} disabled={props.disabled} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
);
};
export default PopconfirmModal;

View File

@@ -14,13 +14,14 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Col, List, Popconfirm, Row, Table, Tooltip} from "antd";
import {Button, Col, List, Row, Table, Tooltip} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as ProductBackend from "./backend/ProductBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import {EditOutlined} from "@ant-design/icons";
import PopconfirmModal from "./PopconfirmModal";
class ProductListPage extends BaseListPage {
newProduct() {
@@ -239,12 +240,11 @@ class ProductListPage extends BaseListPage {
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/products/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/products/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteProduct(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,13 +14,14 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Table} from "antd";
import {Button, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as ProviderBackend from "./backend/ProviderBackend";
import * as Provider from "./auth/Provider";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class ProviderListPage extends BaseListPage {
constructor(props) {
@@ -206,12 +207,11 @@ class ProviderListPage extends BaseListPage {
return (
<div>
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteProvider(index)}
>
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Popconfirm, Table, Upload} from "antd";
import {Button, Table, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons";
import copy from "copy-to-clipboard";
import * as Setting from "./Setting";
@@ -21,6 +21,7 @@ import * as ResourceBackend from "./backend/ResourceBackend";
import i18next from "i18next";
import {Link} from "react-router-dom";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class ResourceListPage extends BaseListPage {
constructor(props) {
@@ -244,15 +245,13 @@ class ResourceListPage extends BaseListPage {
render: (text, record, index) => {
return (
<div>
{/* <Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/resources/${record.name}`)}>{i18next.t("general:Edit")}</Button>*/}
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteResource(index)}
okText={i18next.t("user:OK")}
cancelText={i18next.t("user:Cancel")}
>
<Button type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,12 +14,13 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from "antd";
import {Button, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as RoleBackend from "./backend/RoleBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class RoleListPage extends BaseListPage {
newRole() {
@@ -175,12 +176,11 @@ class RoleListPage extends BaseListPage {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/roles/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteRole(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -16,9 +16,10 @@ import BaseListPage from "./BaseListPage";
import * as Setting from "./Setting";
import i18next from "i18next";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Table, Tag} from "antd";
import {Table, Tag} from "antd";
import React from "react";
import * as SessionBackend from "./backend/SessionBackend";
import PopconfirmModal from "./PopconfirmModal";
class SessionListPage extends BaseListPage {
@@ -97,12 +98,11 @@ class SessionListPage extends BaseListPage {
render: (text, record, index) => {
return (
<div>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteSession(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,12 +14,13 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from "antd";
import {Button, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as SyncerBackend from "./backend/SyncerBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class SyncerListPage extends BaseListPage {
newSyncer() {
@@ -232,12 +233,11 @@ class SyncerListPage extends BaseListPage {
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.runSyncer(index)}>{i18next.t("general:Sync")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/syncers/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteSyncer(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -23,31 +23,24 @@ class SystemInfo extends React.Component {
constructor(props) {
super(props);
this.state = {
cpuUsage: [],
memUsed: 0,
memTotal: 0,
latestVersion: undefined,
systemInfo: {cpuUsage: [], memoryUsed: 0, memoryTotal: 0},
versionInfo: {},
intervalId: null,
href: "",
loading: true,
};
}
UNSAFE_componentWillMount() {
SystemBackend.getSystemInfo(this.props.account?.owner, this.props.account?.name).then(res => {
SystemBackend.getSystemInfo().then(res => {
this.setState({
cpuUsage: res.cpu_usage,
memUsed: res.memory_used,
memTotal: res.memory_total,
systemInfo: res.data,
loading: false,
});
const id = setInterval(() => {
SystemBackend.getSystemInfo(this.props.account?.owner, this.props.account?.name).then(res => {
SystemBackend.getSystemInfo().then(res => {
this.setState({
cpuUsage: res.cpu_usage,
memUsed: res.memory_used,
memTotal: res.memory_total,
systemInfo: res.data,
});
}).catch(error => {
Setting.showMessage("error", `System info failed to get: ${error}`);
@@ -58,12 +51,12 @@ class SystemInfo extends React.Component {
Setting.showMessage("error", `System info failed to get: ${error}`);
});
SystemBackend.getGitHubLatestReleaseVersion().then(res => {
const href = res.version && res.version.length > 0 ? `https://github.com/casdoor/casdoor/releases/tag/${res.version}` : "";
const versionText = res.version && res.version.length > 0 ? res.version : i18next.t("system:Unknown Version");
this.setState({latestVersion: versionText, href: href});
SystemBackend.getVersionInfo().then(res => {
this.setState({
versionInfo: res.data,
});
}).catch(err => {
Setting.showMessage("error", `get latest commit version failed: ${err}`);
Setting.showMessage("error", `Version info failed to get: ${err}`);
});
}
@@ -74,21 +67,25 @@ class SystemInfo extends React.Component {
}
render() {
const CPUInfo = this.state.cpuUsage?.length > 0 ?
this.state.cpuUsage.map((usage, i) => {
const cpuUi = this.state.systemInfo.cpuUsage?.length <= 0 ? i18next.t("system:Get CPU Usage Failed") :
this.state.systemInfo.cpuUsage.map((usage, i) => {
return (
<Progress key={i} percent={Number(usage.toFixed(1))} />
);
}) : i18next.t("system:Get CPU Usage Failed");
});
const MemInfo = (
this.state.memUsed && this.state.memTotal && this.state.memTotal !== 0 ?
<div>
{Setting.getFriendlyFileSize(this.state.memUsed)} / {Setting.getFriendlyFileSize(this.state.memTotal)}
<br /> <br />
<Progress type="circle" percent={Number((Number(this.state.memUsed) / Number(this.state.memTotal) * 100).toFixed(2))} />
</div> : i18next.t("system:Get Memory Usage Failed")
);
const memUi = this.state.systemInfo.memoryUsed && this.state.systemInfo.memoryTotal && this.state.systemInfo.memoryTotal <= 0 ? i18next.t("system:Get Memory Usage Failed") :
<div>
{Setting.getFriendlyFileSize(this.state.systemInfo.memoryUsed)} / {Setting.getFriendlyFileSize(this.state.systemInfo.memoryTotal)}
<br /> <br />
<Progress type="circle" percent={Number((Number(this.state.systemInfo.memoryUsed) / Number(this.state.systemInfo.memoryTotal) * 100).toFixed(2))} />
</div>;
const link = this.state.versionInfo?.version !== "" ? `https://github.com/casdoor/casdoor/releases/tag/${this.state.versionInfo?.version}` : "";
let versionText = this.state.versionInfo?.version !== "" ? this.state.versionInfo?.version : i18next.t("system:Unknown Version");
if (this.state.versionInfo?.commitOffset > 0) {
versionText += ` (ahead+${this.state.versionInfo?.commitOffset})`;
}
if (!Setting.isMobile()) {
return (
@@ -98,25 +95,25 @@ class SystemInfo extends React.Component {
<Row gutter={[10, 10]}>
<Col span={12}>
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center", height: "100%"}}>
{this.state.loading ? <Spin size="large" /> : CPUInfo}
{this.state.loading ? <Spin size="large" /> : cpuUi}
</Card>
</Col>
<Col span={12}>
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center", height: "100%"}}>
{this.state.loading ? <Spin size="large" /> : MemInfo}
{this.state.loading ? <Spin size="large" /> : memUi}
</Card>
</Col>
</Row>
<Divider />
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
<div>{i18next.t("system:An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS")}</div>
GitHub: <a href="https://github.com/casdoor/casdoor">casdoor</a>
GitHub: <a target="_blank" rel="noreferrer" href="https://github.com/casdoor/casdoor">Casdoor</a>
<br />
{i18next.t("system:Version")}: <a href={this.state.href}>{this.state.latestVersion}</a>
{i18next.t("system:Version")}: <a target="_blank" rel="noreferrer" href={link}>{versionText}</a>
<br />
{i18next.t("system:Official Website")}: <a href="https://casdoor.org/">casdoor.org</a>
{i18next.t("system:Official Website")}: <a target="_blank" rel="noreferrer" href="https://casdoor.org">https://casdoor.org</a>
<br />
{i18next.t("system:Community")}: <a href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">contact us</a>
{i18next.t("system:Community")}: <a target="_blank" rel="noreferrer" href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">Get in Touch!</a>
</Card>
</Col>
<Col span={6}></Col>
@@ -127,24 +124,24 @@ class SystemInfo extends React.Component {
<Row gutter={[16, 0]}>
<Col span={24}>
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center", width: "100%"}}>
{this.state.loading ? <Spin size="large" /> : CPUInfo}
{this.state.loading ? <Spin size="large" /> : cpuUi}
</Card>
</Col>
<Col span={24}>
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center", width: "100%"}}>
{this.state.loading ? <Spin size="large" /> : MemInfo}
{this.state.loading ? <Spin size="large" /> : memUi}
</Card>
</Col>
<Col span={24}>
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
<div>{i18next.t("system:An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS")}</div>
GitHub: <a href="https://github.com/casdoor/casdoor">casdoor</a>
GitHub: <a target="_blank" rel="noreferrer" href="https://github.com/casdoor/casdoor">Casdoor</a>
<br />
{i18next.t("system:Version")}: <a href={this.state.href}>{this.state.latestVersion}</a>
{i18next.t("system:Version")}: <a target="_blank" rel="noreferrer" href={link}>{versionText}</a>
<br />
{i18next.t("system:Official Website")}: <a href="https://casdoor.org/">casdoor.org</a>
{i18next.t("system:Official Website")}: <a target="_blank" rel="noreferrer" href="https://casdoor.org">https://casdoor.org</a>
<br />
{i18next.t("system:Community")}: <a href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">contact us</a>
{i18next.t("system:Community")}: <a target="_blank" rel="noreferrer" href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">Get in Touch!</a>
</Card>
</Col>
</Row>

View File

@@ -14,12 +14,13 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Table} from "antd";
import {Button, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as TokenBackend from "./backend/TokenBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class TokenListPage extends BaseListPage {
newToken() {
@@ -201,12 +202,11 @@ class TokenListPage extends BaseListPage {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/tokens/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteToken(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,7 +14,7 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table, Upload} from "antd";
import {Button, Switch, Table, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons";
import moment from "moment";
import * as OrganizationBackend from "./backend/OrganizationBackend";
@@ -22,6 +22,7 @@ import * as Setting from "./Setting";
import * as UserBackend from "./backend/UserBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class UserListPage extends BaseListPage {
constructor(props) {
@@ -341,13 +342,12 @@ class UserListPage extends BaseListPage {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteUser(index)}
disabled={disabled}
>
<Button disabled={disabled} style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -14,12 +14,13 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from "antd";
import {Button, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as WebhookBackend from "./backend/WebhookBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./PopconfirmModal";
class WebhookListPage extends BaseListPage {
newWebhook() {
@@ -197,12 +198,11 @@ class WebhookListPage extends BaseListPage {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/webhooks/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteWebhook(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</PopconfirmModal>
</div>
);
},

View File

@@ -378,7 +378,7 @@ export function getAuthUrl(application, provider, method) {
const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState);
const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier"))
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "QQ" || provider.type === "Facebook" || provider.type === "DingTalk"
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "QQ" || provider.type === "Facebook"
|| provider.type === "Weibo" || provider.type === "Gitee" || provider.type === "LinkedIn" || provider.type === "GitLab" || provider.type === "AzureAD"
|| provider.type === "Slack" || provider.type === "Line" || provider.type === "Amazon" || provider.type === "Auth0" || provider.type === "BattleNet"
|| provider.type === "Bitbucket" || provider.type === "Box" || provider.type === "CloudFoundry" || provider.type === "Dailymotion"
@@ -390,6 +390,8 @@ export function getAuthUrl(application, provider, method) {
|| provider.type === "Twitch" || provider.type === "Typetalk" || provider.type === "Uber" || provider.type === "VK" || provider.type === "Wepay"
|| provider.type === "Xero" || provider.type === "Yahoo" || provider.type === "Yammer" || provider.type === "Yandex" || provider.type === "Zoom") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
} else if (provider.type === "DingTalk") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&prompt=consent&state=${state}`;
} else if (provider.type === "WeChat") {
if (navigator.userAgent.includes("MicroMessenger")) {
return `${authInfo[provider.type].mpEndpoint}?appid=${provider.clientId2}&redirect_uri=${redirectUri}&state=${state}&scope=${authInfo[provider.type].mpScope}&response_type=code#wechat_redirect`;

View File

@@ -14,8 +14,8 @@
import * as Setting from "../Setting";
export function getSystemInfo(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-system-info?id=${owner}/${encodeURIComponent(name)}`, {
export function getSystemInfo() {
return fetch(`${Setting.ServerUrl}/api/get-system-info`, {
method: "GET",
credentials: "include",
headers: {
@@ -24,8 +24,8 @@ export function getSystemInfo(owner, name) {
}).then(res => res.json());
}
export function getGitHubLatestReleaseVersion() {
return fetch(`${Setting.ServerUrl}/api/get-release`, {
export function getVersionInfo() {
return fetch(`${Setting.ServerUrl}/api/get-version-info`, {
method: "GET",
credentials: "include",
headers: {