forked from casdoor/casdoor
feat: add Agent list and edit pages (#5338)
This commit is contained in:
149
controllers/agent.go
Normal file
149
controllers/agent.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// 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/server/web/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetAgents
|
||||
// @Title GetAgents
|
||||
// @Tag Agent API
|
||||
// @Description get agents
|
||||
// @Param owner query string true "The owner of agents"
|
||||
// @Success 200 {array} object.Agent The Response object
|
||||
// @router /get-agents [get]
|
||||
func (c *ApiController) GetAgents() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
if owner == "admin" {
|
||||
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 == "" {
|
||||
agents, err := object.GetAgents(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(agents)
|
||||
return
|
||||
}
|
||||
|
||||
limitInt := util.ParseInt(limit)
|
||||
count, err := object.GetAgentCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limitInt, count)
|
||||
agents, err := object.GetPaginationAgents(owner, paginator.Offset(), limitInt, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(agents, paginator.Nums())
|
||||
}
|
||||
|
||||
// GetAgent
|
||||
// @Title GetAgent
|
||||
// @Tag Agent API
|
||||
// @Description get agent
|
||||
// @Param id query string true "The id ( owner/name ) of the agent"
|
||||
// @Success 200 {object} object.Agent The Response object
|
||||
// @router /get-agent [get]
|
||||
func (c *ApiController) GetAgent() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
agent, err := object.GetAgent(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(agent)
|
||||
}
|
||||
|
||||
// UpdateAgent
|
||||
// @Title UpdateAgent
|
||||
// @Tag Agent API
|
||||
// @Description update agent
|
||||
// @Param id query string true "The id ( owner/name ) of the agent"
|
||||
// @Param body body object.Agent true "The details of the agent"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-agent [post]
|
||||
func (c *ApiController) UpdateAgent() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var agent object.Agent
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &agent)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateAgent(id, &agent))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddAgent
|
||||
// @Title AddAgent
|
||||
// @Tag Agent API
|
||||
// @Description add agent
|
||||
// @Param body body object.Agent true "The details of the agent"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-agent [post]
|
||||
func (c *ApiController) AddAgent() {
|
||||
var agent object.Agent
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &agent)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddAgent(&agent))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteAgent
|
||||
// @Title DeleteAgent
|
||||
// @Tag Agent API
|
||||
// @Description delete agent
|
||||
// @Param body body object.Agent true "The details of the agent"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-agent [post]
|
||||
func (c *ApiController) DeleteAgent() {
|
||||
var agent object.Agent
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &agent)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteAgent(&agent))
|
||||
c.ServeJSON()
|
||||
}
|
||||
118
object/agent.go
Normal file
118
object/agent.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// 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 Agent 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"`
|
||||
|
||||
Url string `xorm:"varchar(500)" json:"url"`
|
||||
Token string `xorm:"varchar(500)" json:"token"`
|
||||
Application string `xorm:"varchar(100)" json:"application"`
|
||||
}
|
||||
|
||||
func GetAgents(owner string) ([]*Agent, error) {
|
||||
agents := []*Agent{}
|
||||
err := ormer.Engine.Desc("created_time").Find(&agents, &Agent{Owner: owner})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return agents, nil
|
||||
}
|
||||
|
||||
func getAgent(owner string, name string) (*Agent, error) {
|
||||
agent := Agent{Owner: owner, Name: name}
|
||||
existed, err := ormer.Engine.Get(&agent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &agent, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func GetAgent(id string) (*Agent, error) {
|
||||
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
return getAgent(owner, name)
|
||||
}
|
||||
|
||||
func UpdateAgent(id string, agent *Agent) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
if a, err := getAgent(owner, name); err != nil {
|
||||
return false, err
|
||||
} else if a == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
agent.UpdatedTime = util.GetCurrentTime()
|
||||
|
||||
_, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(agent)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func AddAgent(agent *Agent) (bool, error) {
|
||||
affected, err := ormer.Engine.Insert(agent)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func DeleteAgent(agent *Agent) (bool, error) {
|
||||
affected, err := ormer.Engine.ID(core.PK{agent.Owner, agent.Name}).Delete(&Agent{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (agent *Agent) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", agent.Owner, agent.Name)
|
||||
}
|
||||
|
||||
func GetAgentCount(owner, field, value string) (int64, error) {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
return session.Count(&Agent{})
|
||||
}
|
||||
|
||||
func GetPaginationAgents(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Agent, error) {
|
||||
agents := []*Agent{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&agents)
|
||||
if err != nil {
|
||||
return agents, err
|
||||
}
|
||||
|
||||
return agents, nil
|
||||
}
|
||||
@@ -477,4 +477,9 @@ func (a *Ormer) createTable() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Agent))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,12 @@ func InitAPI() {
|
||||
web.Router("/api/delete-server", &controllers.ApiController{}, "POST:DeleteServer")
|
||||
web.Router("/api/server/:owner/:name", &controllers.ApiController{}, "POST:ProxyServer")
|
||||
|
||||
web.Router("/api/get-agents", &controllers.ApiController{}, "GET:GetAgents")
|
||||
web.Router("/api/get-agent", &controllers.ApiController{}, "GET:GetAgent")
|
||||
web.Router("/api/update-agent", &controllers.ApiController{}, "POST:UpdateAgent")
|
||||
web.Router("/api/add-agent", &controllers.ApiController{}, "POST:AddAgent")
|
||||
web.Router("/api/delete-agent", &controllers.ApiController{}, "POST:DeleteAgent")
|
||||
|
||||
web.Router("/api/get-rules", &controllers.ApiController{}, "GET:GetRules")
|
||||
web.Router("/api/get-rule", &controllers.ApiController{}, "GET:GetRule")
|
||||
web.Router("/api/add-rule", &controllers.ApiController{}, "POST:AddRule")
|
||||
|
||||
228
web/src/AgentEditPage.js
Normal file
228
web/src/AgentEditPage.js
Normal file
@@ -0,0 +1,228 @@
|
||||
// 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, Input, Row, Select} from "antd";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as AgentBackend from "./backend/AgentBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
class AgentEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
agentName: props.match.params.agentName,
|
||||
owner: props.match.params.organizationName,
|
||||
agent: null,
|
||||
organizations: [],
|
||||
applications: [],
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getAgent();
|
||||
this.getOrganizations();
|
||||
this.getApplications(this.state.owner);
|
||||
}
|
||||
|
||||
getAgent() {
|
||||
AgentBackend.getAgent(this.state.agent?.owner || this.state.owner, this.state.agentName)
|
||||
.then((res) => {
|
||||
if (res.data === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
agent: res.data,
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to get")}: ${res.msg}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getOrganizations() {
|
||||
if (Setting.isAdminUser(this.props.account)) {
|
||||
OrganizationBackend.getOrganizations("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
organizations: res.data || [],
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getApplications(owner) {
|
||||
ApplicationBackend.getApplicationsByOrganization("admin", owner)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
applications: res.data || [],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateAgentField(key, value) {
|
||||
const agent = this.state.agent;
|
||||
if (key === "owner" && agent.owner !== value) {
|
||||
agent.application = "";
|
||||
this.getApplications(value);
|
||||
}
|
||||
|
||||
agent[key] = value;
|
||||
this.setState({
|
||||
agent: agent,
|
||||
});
|
||||
}
|
||||
|
||||
submitAgentEdit(willExit) {
|
||||
const agent = Setting.deepCopy(this.state.agent);
|
||||
AgentBackend.updateAgent(this.state.owner, this.state.agentName, agent)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully modified"));
|
||||
if (willExit) {
|
||||
this.props.history.push("/agents");
|
||||
} else {
|
||||
this.setState({
|
||||
mode: "edit",
|
||||
owner: agent.owner,
|
||||
agentName: agent.name,
|
||||
}, () => {this.getAgent();});
|
||||
this.props.history.push(`/agents/${agent.owner}/${agent.name}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to update")}: ${res.msg}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteAgent() {
|
||||
AgentBackend.deleteAgent(this.state.agent)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||
this.props.history.push("/agents");
|
||||
} 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}`);
|
||||
});
|
||||
}
|
||||
|
||||
renderAgent() {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
{this.state.mode === "add" ? i18next.t("agent:New Agent") : i18next.t("agent:Edit Agent")}
|
||||
<Button onClick={() => this.submitAgentEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitAgentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteAgent()}>{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.agent.owner} onChange={(value => {this.updateAgentField("owner", 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}>
|
||||
{i18next.t("general:Name")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.agent.name} onChange={e => {
|
||||
this.updateAgentField("name", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{i18next.t("general:Display name")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.agent.displayName} onChange={e => {
|
||||
this.updateAgentField("displayName", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Listening URL"), i18next.t("general:Listening URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={this.state.agent.url} onChange={e => {
|
||||
this.updateAgentField("url", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("token:Access token"), i18next.t("token:Access token - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input.Password placeholder={"***"} value={this.state.agent.token} onChange={e => {
|
||||
this.updateAgentField("token", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<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.agent.application} onChange={(value => {this.updateAgentField("application", value);})}>
|
||||
{
|
||||
this.state.applications.map((application, index) => <Option key={index} value={application.name}>{application.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.agent === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.renderAgent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AgentEditPage;
|
||||
219
web/src/AgentListPage.js
Normal file
219
web/src/AgentListPage.js
Normal file
@@ -0,0 +1,219 @@
|
||||
// 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 AgentBackend from "./backend/AgentBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class AgentListPage extends BaseListPage {
|
||||
newAgent() {
|
||||
const randomName = Setting.getRandomName();
|
||||
const owner = Setting.getRequestOrganization(this.props.account);
|
||||
return {
|
||||
owner: owner,
|
||||
name: `agent_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
displayName: `New Agent - ${randomName}`,
|
||||
url: "",
|
||||
token: "",
|
||||
application: "",
|
||||
};
|
||||
}
|
||||
|
||||
addAgent() {
|
||||
const newAgent = this.newAgent();
|
||||
AgentBackend.addAgent(newAgent)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.props.history.push({pathname: `/agents/${newAgent.owner}/${newAgent.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}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteAgent(i) {
|
||||
AgentBackend.deleteAgent(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}`);
|
||||
});
|
||||
}
|
||||
|
||||
fetch = (params = {}) => {
|
||||
const field = params.searchedColumn, value = params.searchText;
|
||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
if (!params.pagination) {
|
||||
params.pagination = {current: 1, pageSize: 10};
|
||||
}
|
||||
|
||||
this.setState({loading: true});
|
||||
AgentBackend.getAgents(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 {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to get")}: ${res.msg}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
renderTable(agents) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("name"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/agents/${record.owner}/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "130px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("owner"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
key: "createdTime",
|
||||
width: "180px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Display name"),
|
||||
dataIndex: "displayName",
|
||||
key: "displayName",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("displayName"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Listening URL"),
|
||||
dataIndex: "url",
|
||||
key: "url",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("url"),
|
||||
render: (text) => {
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<a target="_blank" rel="noreferrer" href={text}>
|
||||
{Setting.getShortText(text, 40)}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Application"),
|
||||
dataIndex: "application",
|
||||
key: "application",
|
||||
width: "140px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("application"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "op",
|
||||
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(`/agents/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<PopconfirmModal title={i18next.t("general:Sure to delete") + `: ${record.name} ?`} onConfirm={() => this.deleteAgent(index)}>
|
||||
</PopconfirmModal>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const filteredColumns = Setting.filterTableColumns(columns, this.props.formItems ?? this.state.formItems);
|
||||
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 (
|
||||
<Table
|
||||
scroll={{x: "max-content"}}
|
||||
dataSource={agents}
|
||||
columns={filteredColumns}
|
||||
rowKey={record => `${record.owner}/${record.name}`}
|
||||
pagination={{...this.state.pagination, ...paginationProps}}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
size="middle"
|
||||
bordered
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Agents")}
|
||||
<Button type="primary" size="small" onClick={() => this.addAgent()}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AgentListPage;
|
||||
@@ -176,6 +176,7 @@ class App extends Component {
|
||||
"/organizations", "/groups", "/users", "/invitations", // User Management
|
||||
"/applications", "/providers", "/resources", "/certs", "/keys", // Identity
|
||||
"/roles", "/permissions", "/models", "/adapters", "/enforcers", // Authorization
|
||||
"/servers", "/agents", "/sites", "/rules", // Gateway
|
||||
"/sessions", "/records", "/tokens", "/verifications", // Logging & Auditing
|
||||
"/products", "/orders", "/payments", "/plans", "/pricings", "/subscriptions", "/transactions", // Business
|
||||
"/sysinfo", "/forms", "/syncers", "/webhooks", "/tickets", "/swagger", // Admin
|
||||
@@ -217,6 +218,16 @@ class App extends Component {
|
||||
}
|
||||
} else if (uri.includes("/keys")) {
|
||||
return "/keys";
|
||||
} else if (uri.includes("/servers") || uri.includes("/agents") || uri.includes("/sites") || uri.includes("/rules")) {
|
||||
if (uri.includes("/servers")) {
|
||||
return "/servers";
|
||||
} else if (uri.includes("/agents")) {
|
||||
return "/agents";
|
||||
} else if (uri.includes("/sites")) {
|
||||
return "/sites";
|
||||
} else if (uri.includes("/rules")) {
|
||||
return "/rules";
|
||||
}
|
||||
} else if (uri.includes("/roles") || uri.includes("/permissions") || uri.includes("/models") || uri.includes("/adapters") || uri.includes("/enforcers")) {
|
||||
if (uri.includes("/roles")) {
|
||||
return "/roles";
|
||||
@@ -297,6 +308,8 @@ class App extends Component {
|
||||
this.setState({selectedMenuKey: "/orgs"});
|
||||
} 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("/servers") || uri.includes("/agents") || uri.includes("/sites") || uri.includes("/rules")) {
|
||||
this.setState({selectedMenuKey: "/gateway"});
|
||||
} else if (uri.includes("/roles") || uri.includes("/permissions") || uri.includes("/models") || uri.includes("/adapters") || uri.includes("/enforcers")) {
|
||||
this.setState({selectedMenuKey: "/auth"});
|
||||
} else if (uri.includes("/records") || uri.includes("/tokens") || uri.includes("/sessions") || uri.includes("/verifications")) {
|
||||
|
||||
@@ -110,6 +110,8 @@ import SiteListPage from "./SiteListPage";
|
||||
import SiteEditPage from "./SiteEditPage";
|
||||
import ServerListPage from "./ServerListPage";
|
||||
import ServerEditPage from "./ServerEditPage";
|
||||
import AgentListPage from "./AgentListPage";
|
||||
import AgentEditPage from "./AgentEditPage";
|
||||
import RuleEditPage from "./RuleEditPage";
|
||||
import RuleListPage from "./RuleListPage";
|
||||
|
||||
@@ -352,6 +354,7 @@ function ManagementPage(props) {
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sites">{i18next.t("general:Gateway")}</Link>, "/gateway", <CheckCircleTwoTone twoToneColor={twoToneColor} />, [
|
||||
Setting.getItem(<Link to="/servers">{i18next.t("general:MCP Servers")}</Link>, "/servers"),
|
||||
Setting.getItem(<Link to="/agents">{i18next.t("general:Agents")}</Link>, "/agents"),
|
||||
Setting.getItem(<Link to="/sites">{i18next.t("general:Sites")}</Link>, "/sites"),
|
||||
Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>, "/certs"),
|
||||
Setting.getItem(<Link to="/rules">{i18next.t("general:Rules")}</Link>, "/rules"),
|
||||
@@ -498,6 +501,8 @@ function ManagementPage(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="/agents" render={(props) => renderLoginIfNotLoggedIn(<AgentListPage account={account} {...props} />)} />
|
||||
<Route exact path="/agents/:organizationName/:agentName" render={(props) => renderLoginIfNotLoggedIn(<AgentEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/sites" render={(props) => renderLoginIfNotLoggedIn(<SiteListPage account={account} {...props} />)} />
|
||||
<Route exact path="/sites/:organizationName/:siteName" render={(props) => renderLoginIfNotLoggedIn(<SiteEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/rules" render={(props) => renderLoginIfNotLoggedIn(<RuleListPage account={account} {...props} />)} />
|
||||
|
||||
56
web/src/backend/AgentBackend.js
Normal file
56
web/src/backend/AgentBackend.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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 getAgents(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-agents?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getAgent(owner, name) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-agent?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function updateAgent(owner, name, agent) {
|
||||
const newAgent = Setting.deepCopy(agent);
|
||||
return fetch(`${Setting.ServerUrl}/api/update-agent?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newAgent),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function addAgent(agent) {
|
||||
const newAgent = Setting.deepCopy(agent);
|
||||
return fetch(`${Setting.ServerUrl}/api/add-agent`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newAgent),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function deleteAgent(agent) {
|
||||
const newAgent = Setting.deepCopy(agent);
|
||||
return fetch(`${Setting.ServerUrl}/api/delete-agent`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newAgent),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
@@ -45,6 +45,7 @@ export const NavItemTree = ({disabled, checkedKeys, defaultExpandedKeys, onCheck
|
||||
{title: i18next.t("general:Rules"), key: "/rules"},
|
||||
{title: i18next.t("general:Sites"), key: "/sites"},
|
||||
{title: i18next.t("general:MCP Servers"), key: "/servers"},
|
||||
{title: i18next.t("general:Agents"), key: "/agents"},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user