forked from casdoor/casdoor
Compare commits
6 Commits
custom
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caf9e26acd | ||
|
|
d42a9b95ce | ||
|
|
4caa3008b0 | ||
|
|
82136edabd | ||
|
|
ff7b335acb | ||
|
|
5c62be5aac |
206
controllers/key.go
Normal file
206
controllers/key.go
Normal file
@@ -0,0 +1,206 @@
|
||||
// Copyright 2026 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.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetKeys
|
||||
// @Title GetKeys
|
||||
// @Tag Key API
|
||||
// @Description get keys
|
||||
// @Param owner query string true "The owner of keys"
|
||||
// @Success 200 {array} object.Key The Response object
|
||||
// @router /get-keys [get]
|
||||
func (c *ApiController) GetKeys() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
keys, err := object.GetKeys(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
maskedKeys, err := object.GetMaskedKeys(keys, true, nil)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedKeys)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetKeyCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
keys, err := object.GetPaginationKeys(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
maskedKeys, err := object.GetMaskedKeys(keys, true, nil)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedKeys, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobalKeys
|
||||
// @Title GetGlobalKeys
|
||||
// @Tag Key API
|
||||
// @Description get global keys
|
||||
// @Success 200 {array} object.Key The Response object
|
||||
// @router /get-global-keys [get]
|
||||
func (c *ApiController) GetGlobalKeys() {
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
keys, err := object.GetGlobalKeys()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
maskedKeys, err := object.GetMaskedKeys(keys, true, nil)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedKeys)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetGlobalKeyCount(field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
keys, err := object.GetPaginationGlobalKeys(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
maskedKeys, err := object.GetMaskedKeys(keys, true, nil)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedKeys, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetKey
|
||||
// @Title GetKey
|
||||
// @Tag Key API
|
||||
// @Description get key
|
||||
// @Param id query string true "The id ( owner/name ) of the key"
|
||||
// @Success 200 {object} object.Key The Response object
|
||||
// @router /get-key [get]
|
||||
func (c *ApiController) GetKey() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
key, err := object.GetKey(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(key)
|
||||
}
|
||||
|
||||
// UpdateKey
|
||||
// @Title UpdateKey
|
||||
// @Tag Key API
|
||||
// @Description update key
|
||||
// @Param id query string true "The id ( owner/name ) of the key"
|
||||
// @Param body body object.Key true "The details of the key"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-key [post]
|
||||
func (c *ApiController) UpdateKey() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var key object.Key
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &key)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateKey(id, &key))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddKey
|
||||
// @Title AddKey
|
||||
// @Tag Key API
|
||||
// @Description add key
|
||||
// @Param body body object.Key true "The details of the key"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-key [post]
|
||||
func (c *ApiController) AddKey() {
|
||||
var key object.Key
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &key)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddKey(&key))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteKey
|
||||
// @Title DeleteKey
|
||||
// @Tag Key API
|
||||
// @Description delete key
|
||||
// @Param body body object.Key true "The details of the key"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-key [post]
|
||||
func (c *ApiController) DeleteKey() {
|
||||
var key object.Key
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &key)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteKey(&key))
|
||||
c.ServeJSON()
|
||||
}
|
||||
208
object/key.go
Normal file
208
object/key.go
Normal file
@@ -0,0 +1,208 @@
|
||||
// Copyright 2026 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.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
type Key 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"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
// Type indicates the scope this key belongs to: "Organization", "Application", or "User"
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Application string `xorm:"varchar(100)" json:"application"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
|
||||
AccessKey string `xorm:"varchar(100) index" json:"accessKey"`
|
||||
AccessSecret string `xorm:"varchar(100)" json:"accessSecret"`
|
||||
|
||||
ExpireTime string `xorm:"varchar(100)" json:"expireTime"`
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
}
|
||||
|
||||
func GetKeyCount(owner, field, value string) (int64, error) {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
return session.Count(&Key{})
|
||||
}
|
||||
|
||||
func GetGlobalKeyCount(field, value string) (int64, error) {
|
||||
session := GetSession("", -1, -1, field, value, "", "")
|
||||
return session.Count(&Key{})
|
||||
}
|
||||
|
||||
func GetKeys(owner string) ([]*Key, error) {
|
||||
keys := []*Key{}
|
||||
err := ormer.Engine.Desc("created_time").Find(&keys, &Key{Owner: owner})
|
||||
if err != nil {
|
||||
return keys, err
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func GetPaginationKeys(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Key, error) {
|
||||
keys := []*Key{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&keys)
|
||||
if err != nil {
|
||||
return keys, err
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func GetGlobalKeys() ([]*Key, error) {
|
||||
keys := []*Key{}
|
||||
err := ormer.Engine.Desc("created_time").Find(&keys)
|
||||
if err != nil {
|
||||
return keys, err
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func GetPaginationGlobalKeys(offset, limit int, field, value, sortField, sortOrder string) ([]*Key, error) {
|
||||
keys := []*Key{}
|
||||
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&keys)
|
||||
if err != nil {
|
||||
return keys, err
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func getKey(owner, name string) (*Key, error) {
|
||||
if owner == "" || name == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
key := Key{Owner: owner, Name: name}
|
||||
existed, err := ormer.Engine.Get(&key)
|
||||
if err != nil {
|
||||
return &key, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &key, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func GetKey(id string) (*Key, error) {
|
||||
owner, name, err := util.GetOwnerAndNameFromIdWithError(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getKey(owner, name)
|
||||
}
|
||||
|
||||
func GetMaskedKey(key *Key, isMaskEnabled bool) *Key {
|
||||
if !isMaskEnabled {
|
||||
return key
|
||||
}
|
||||
|
||||
if key == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if key.AccessSecret != "" {
|
||||
key.AccessSecret = "***"
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func GetMaskedKeys(keys []*Key, isMaskEnabled bool, err error) ([]*Key, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
GetMaskedKey(key, isMaskEnabled)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func UpdateKey(id string, key *Key) (bool, error) {
|
||||
owner, name, err := util.GetOwnerAndNameFromIdWithError(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if k, err := getKey(owner, name); err != nil {
|
||||
return false, err
|
||||
} else if k == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
key.UpdatedTime = util.GetCurrentTime()
|
||||
|
||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func AddKey(key *Key) (bool, error) {
|
||||
if key.AccessKey == "" {
|
||||
key.AccessKey = util.GenerateId()
|
||||
}
|
||||
if key.AccessSecret == "" {
|
||||
key.AccessSecret = util.GenerateId()
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.Insert(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func DeleteKey(key *Key) (bool, error) {
|
||||
affected, err := ormer.Engine.ID(core.PK{key.Owner, key.Name}).Delete(&Key{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (key *Key) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", key.Owner, key.Name)
|
||||
}
|
||||
|
||||
func GetKeyByAccessKey(accessKey string) (*Key, error) {
|
||||
if accessKey == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
key := Key{AccessKey: accessKey}
|
||||
existed, err := ormer.Engine.Get(&key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &key, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -343,6 +343,11 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Key))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Role))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -155,6 +155,13 @@ func InitAPI() {
|
||||
web.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
||||
web.Router("/api/update-cert-domain-expire", &controllers.ApiController{}, "POST:UpdateCertDomainExpire")
|
||||
|
||||
web.Router("/api/get-keys", &controllers.ApiController{}, "GET:GetKeys")
|
||||
web.Router("/api/get-global-keys", &controllers.ApiController{}, "GET:GetGlobalKeys")
|
||||
web.Router("/api/get-key", &controllers.ApiController{}, "GET:GetKey")
|
||||
web.Router("/api/update-key", &controllers.ApiController{}, "POST:UpdateKey")
|
||||
web.Router("/api/add-key", &controllers.ApiController{}, "POST:AddKey")
|
||||
web.Router("/api/delete-key", &controllers.ApiController{}, "POST:DeleteKey")
|
||||
|
||||
web.Router("/api/get-roles", &controllers.ApiController{}, "GET:GetRoles")
|
||||
web.Router("/api/get-role", &controllers.ApiController{}, "GET:GetRole")
|
||||
web.Router("/api/update-role", &controllers.ApiController{}, "POST:UpdateRole")
|
||||
|
||||
@@ -174,7 +174,7 @@ class App extends Component {
|
||||
const validMenuItems = [
|
||||
"/", "/shortcuts", "/apps", // Home group
|
||||
"/organizations", "/groups", "/users", "/invitations", // User Management
|
||||
"/applications", "/providers", "/resources", "/certs", // Identity
|
||||
"/applications", "/providers", "/resources", "/certs", "/keys", // Identity
|
||||
"/roles", "/permissions", "/models", "/adapters", "/enforcers", // Authorization
|
||||
"/sessions", "/records", "/tokens", "/verifications", // Logging & Auditing
|
||||
"/products", "/orders", "/payments", "/plans", "/pricings", "/subscriptions", "/transactions", // Business
|
||||
@@ -215,6 +215,8 @@ class App extends Component {
|
||||
} else if (uri.includes("/certs")) {
|
||||
return "/certs";
|
||||
}
|
||||
} else if (uri.includes("/keys")) {
|
||||
return "/keys";
|
||||
} else if (uri.includes("/roles") || uri.includes("/permissions") || uri.includes("/models") || uri.includes("/adapters") || uri.includes("/enforcers")) {
|
||||
if (uri.includes("/roles")) {
|
||||
return "/roles";
|
||||
@@ -293,7 +295,7 @@ class App extends Component {
|
||||
this.setState({selectedMenuKey: "/home"});
|
||||
} else if (uri.includes("/organizations") || uri.includes("/trees") || uri.includes("/groups") || uri.includes("/users") || uri.includes("/invitations")) {
|
||||
this.setState({selectedMenuKey: "/orgs"});
|
||||
} else if (uri.includes("/applications") || uri.includes("/providers") || uri.includes("/resources") || uri.includes("/certs")) {
|
||||
} else if (uri.includes("/applications") || uri.includes("/providers") || uri.includes("/resources") || uri.includes("/certs") || uri.includes("/keys")) {
|
||||
this.setState({selectedMenuKey: "/identity"});
|
||||
} else if (uri.includes("/roles") || uri.includes("/permissions") || uri.includes("/models") || uri.includes("/adapters") || uri.includes("/enforcers")) {
|
||||
this.setState({selectedMenuKey: "/auth"});
|
||||
|
||||
312
web/src/KeyEditPage.js
Normal file
312
web/src/KeyEditPage.js
Normal file
@@ -0,0 +1,312 @@
|
||||
// Copyright 2026 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 React from "react";
|
||||
import {Button, Card, Col, DatePicker, Input, Row, Select} from "antd";
|
||||
import * as KeyBackend from "./backend/KeyBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import moment from "moment";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
class KeyEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
organizationName: props.match.params.organizationName,
|
||||
keyName: props.match.params.keyName,
|
||||
key: null,
|
||||
organizations: [],
|
||||
applications: [],
|
||||
users: [],
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getKey();
|
||||
this.getOrganizations();
|
||||
}
|
||||
|
||||
getKey() {
|
||||
KeyBackend.getKey(this.state.organizationName, this.state.keyName)
|
||||
.then((res) => {
|
||||
if (res.data === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
key: res.data,
|
||||
});
|
||||
|
||||
this.getApplicationsByOrganization(res.data.organization || this.state.organizationName);
|
||||
this.getUsersByOrganization(res.data.organization || this.state.organizationName);
|
||||
});
|
||||
}
|
||||
|
||||
getOrganizations() {
|
||||
OrganizationBackend.getOrganizations("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
organizations: res.data || [],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getApplicationsByOrganization(organizationName) {
|
||||
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
applications: res.data || [],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getUsersByOrganization(organizationName) {
|
||||
UserBackend.getUsers(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
users: res.data || [],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
parseKeyField(key, value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
updateKeyField(key, value) {
|
||||
value = this.parseKeyField(key, value);
|
||||
|
||||
const keyObj = this.state.key;
|
||||
keyObj[key] = value;
|
||||
this.setState({
|
||||
key: keyObj,
|
||||
});
|
||||
}
|
||||
|
||||
renderKey() {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
{this.state.mode === "add" ? i18next.t("key:New Key") : i18next.t("key:Edit Key")}
|
||||
<Button onClick={() => this.submitKeyEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitKeyEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteKey()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.key.owner} onChange={(value => {
|
||||
this.updateKeyField("owner", value);
|
||||
this.updateKeyField("organization", value);
|
||||
this.getApplicationsByOrganization(value);
|
||||
this.getUsersByOrganization(value);
|
||||
})}>
|
||||
{
|
||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.key.name} onChange={e => {
|
||||
this.updateKeyField("name", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.key.displayName} onChange={e => {
|
||||
this.updateKeyField("displayName", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.key.type} onChange={(value => {
|
||||
this.updateKeyField("type", value);
|
||||
})}>
|
||||
<Option value="Organization">{i18next.t("general:Organization")}</Option>
|
||||
<Option value="Application">{i18next.t("general:Application")}</Option>
|
||||
<Option value="User">{i18next.t("general:User")}</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.key.type === "Application" ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Application"), i18next.t("general:Application - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.key.application} onChange={(value => {
|
||||
this.updateKeyField("application", value);
|
||||
})}>
|
||||
{
|
||||
this.state.applications.map((application, index) => <Option key={index} value={application.name}>{application.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
this.state.key.type === "User" ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.key.user} onChange={(value => {
|
||||
this.updateKeyField("user", value);
|
||||
})}>
|
||||
{
|
||||
this.state.users.map((user, index) => <Option key={index} value={user.name}>{user.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("key:Access key"), i18next.t("key:Access key - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.key.accessKey} readOnly={true} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("key:Access secret"), i18next.t("key:Access secret - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input.Password value={this.state.key.accessSecret} readOnly={true} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Expire time"), i18next.t("general:Expire time - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<DatePicker
|
||||
showTime
|
||||
value={this.state.key.expireTime ? moment(this.state.key.expireTime) : null}
|
||||
onChange={(value, dateString) => {
|
||||
this.updateKeyField("expireTime", dateString);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.key.state} onChange={(value => {
|
||||
this.updateKeyField("state", value);
|
||||
})}>
|
||||
<Option value="Active">{i18next.t("subscription:Active")}</Option>
|
||||
<Option value="Inactive">{i18next.t("key:Inactive")}</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
submitKeyEdit(exitAfterSave) {
|
||||
const key = Setting.deepCopy(this.state.key);
|
||||
KeyBackend.updateKey(this.state.organizationName, this.state.keyName, key)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||
this.setState({
|
||||
organizationName: this.state.key.owner,
|
||||
keyName: this.state.key.name,
|
||||
});
|
||||
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push("/keys");
|
||||
} else {
|
||||
this.props.history.push(`/keys/${this.state.key.owner}/${this.state.key.name}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||
this.updateKeyField("owner", this.state.organizationName);
|
||||
this.updateKeyField("name", this.state.keyName);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteKey() {
|
||||
KeyBackend.deleteKey(this.state.key)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.props.history.push("/keys");
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.state.key !== null ? this.renderKey() : null
|
||||
}
|
||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||
<Button size="large" onClick={() => this.submitKeyEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitKeyEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default KeyEditPage;
|
||||
253
web/src/KeyListPage.js
Normal file
253
web/src/KeyListPage.js
Normal file
@@ -0,0 +1,253 @@
|
||||
// Copyright 2026 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 React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Table} from "antd";
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as KeyBackend from "./backend/KeyBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class KeyListPage extends BaseListPage {
|
||||
newKey() {
|
||||
const randomName = Setting.getRandomName();
|
||||
const owner = Setting.getRequestOrganization(this.props.account);
|
||||
return {
|
||||
owner: owner,
|
||||
name: `key_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
updatedTime: moment().format(),
|
||||
displayName: `New Key - ${randomName}`,
|
||||
type: "Organization",
|
||||
organization: owner,
|
||||
application: "",
|
||||
user: "",
|
||||
accessKey: "",
|
||||
accessSecret: "",
|
||||
expireTime: "",
|
||||
state: "Active",
|
||||
};
|
||||
}
|
||||
|
||||
addKey() {
|
||||
const newKey = this.newKey();
|
||||
KeyBackend.addKey(newKey)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.props.history.push({pathname: `/keys/${newKey.owner}/${newKey.name}`, mode: "add"});
|
||||
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteKey(i) {
|
||||
KeyBackend.deleteKey(this.state.data[i])
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||
this.fetch({
|
||||
pagination: {
|
||||
...this.state.pagination,
|
||||
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
renderTable(keys) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "140px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("name"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/keys/${record.owner}/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
key: "createdTime",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Display name"),
|
||||
dataIndex: "displayName",
|
||||
key: "displayName",
|
||||
width: "170px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("displayName"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Type"),
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
width: "140px",
|
||||
sorter: true,
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
{text: i18next.t("general:Organization"), value: "Organization"},
|
||||
{text: i18next.t("general:Application"), value: "Application"},
|
||||
{text: i18next.t("general:User"), value: "User"},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: i18next.t("key:Access key"),
|
||||
dataIndex: "accessKey",
|
||||
key: "accessKey",
|
||||
width: "300px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("accessKey"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Expire time"),
|
||||
dataIndex: "expireTime",
|
||||
key: "expireTime",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:State"),
|
||||
dataIndex: "state",
|
||||
key: "state",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("state"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "",
|
||||
key: "op",
|
||||
width: "180px",
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/keys/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<PopconfirmModal
|
||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteKey(index)}
|
||||
>
|
||||
</PopconfirmModal>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const paginationProps = {
|
||||
total: this.state.pagination.total,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={keys} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Keys")}
|
||||
<Button type="primary" size="small" onClick={this.addKey.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
fetch = (params = {}) => {
|
||||
let field = params.searchedColumn, value = params.searchText;
|
||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
if (params.type !== undefined && params.type !== null) {
|
||||
field = "type";
|
||||
value = params.type;
|
||||
}
|
||||
this.setState({loading: true});
|
||||
(Setting.isDefaultOrganizationSelected(this.props.account) ? KeyBackend.getGlobalKeys(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
: KeyBackend.getKeys(Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
data: res.data,
|
||||
pagination: {
|
||||
...params.pagination,
|
||||
total: res.data2,
|
||||
},
|
||||
searchText: params.searchText,
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
isAuthorized: false,
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default KeyListPage;
|
||||
@@ -47,6 +47,8 @@ import RecordListPage from "./RecordListPage";
|
||||
import ResourceListPage from "./ResourceListPage";
|
||||
import CertListPage from "./CertListPage";
|
||||
import CertEditPage from "./CertEditPage";
|
||||
import KeyListPage from "./KeyListPage";
|
||||
import KeyEditPage from "./KeyEditPage";
|
||||
import RoleListPage from "./RoleListPage";
|
||||
import RoleEditPage from "./RoleEditPage";
|
||||
import PermissionListPage from "./PermissionListPage";
|
||||
@@ -329,6 +331,9 @@ function ManagementPage(props) {
|
||||
Setting.getItem(<Link to="/providers">{i18next.t("application:Providers")}</Link>, "/providers"),
|
||||
Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>, "/resources"),
|
||||
Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>, "/certs"),
|
||||
Setting.getItem(<Link to="/keys">{i18next.t("general:Keys")}</Link>, "/keys"),
|
||||
Setting.getItem(<Link to="/sites">{i18next.t("general:Sites")}</Link>, "/sites"),
|
||||
Setting.getItem(<Link to="/rules">{i18next.t("general:Rules")}</Link>, "/rules"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateTwoTone twoToneColor={twoToneColor} />, [
|
||||
@@ -485,6 +490,8 @@ function ManagementPage(props) {
|
||||
<Route exact path="/resources" render={(props) => renderLoginIfNotLoggedIn(<ResourceListPage account={account} {...props} />)} />
|
||||
<Route exact path="/certs" render={(props) => renderLoginIfNotLoggedIn(<CertListPage account={account} {...props} />)} />
|
||||
<Route exact path="/certs/:organizationName/:certName" render={(props) => renderLoginIfNotLoggedIn(<CertEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/keys" render={(props) => renderLoginIfNotLoggedIn(<KeyListPage account={account} {...props} />)} />
|
||||
<Route exact path="/keys/:organizationName/:keyName" render={(props) => renderLoginIfNotLoggedIn(<KeyEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/servers" render={(props) => renderLoginIfNotLoggedIn(<ServerListPage account={account} {...props} />)} />
|
||||
<Route exact path="/servers/:organizationName/:serverName" render={(props) => renderLoginIfNotLoggedIn(<ServerEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/sites" render={(props) => renderLoginIfNotLoggedIn(<SiteListPage account={account} {...props} />)} />
|
||||
|
||||
81
web/src/backend/KeyBackend.js
Normal file
81
web/src/backend/KeyBackend.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2026 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 * as Setting from "../Setting";
|
||||
|
||||
export function getKeys(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-keys?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getGlobalKeys(page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-global-keys?p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getKey(owner, name) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-key?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function updateKey(owner, name, key) {
|
||||
const newKey = Setting.deepCopy(key);
|
||||
return fetch(`${Setting.ServerUrl}/api/update-key?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newKey),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function addKey(key) {
|
||||
const newKey = Setting.deepCopy(key);
|
||||
return fetch(`${Setting.ServerUrl}/api/add-key`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newKey),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function deleteKey(key) {
|
||||
const newKey = Setting.deepCopy(key);
|
||||
return fetch(`${Setting.ServerUrl}/api/delete-key`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newKey),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
Reference in New Issue
Block a user