首次提交APIPark代码,面向开源

This commit is contained in:
Liujian
2024-08-12 21:38:09 +08:00
parent 7c8d175c9c
commit 469a612e41
750 changed files with 66335 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
*.DS_Store
/.idea/
/config.yml
+37
View File
@@ -0,0 +1,37 @@
package enum
const (
HeaderOptTypeAdd = "ADD" //新增或修改
HeaderOptTypeDelete = "DELETE" //删除
MatchPositionHeader = "header"
MatchPositionQuery = "query"
MatchPositionCookie = "cookie"
MatchTypeEqual = "EQUAL" //全等匹配
MatchTypePrefix = "PREFIX" //前缀匹配
MatchTypeSuffix = "SUFFIX" //后缀匹配
MatchTypeSubstr = "SUBSTR" //子串匹配
MatchTypeUnEqual = "UNEQUAL" //非等匹配
MatchTypeNull = "NULL" //空值匹配
MatchTypeExist = "EXIST" //存在匹配
MatchTypeUnExist = "UNEXIST" //不存在匹配
MatchTypeRegexp = "REGEXP" //区分大小写的正则匹配
MatchTypeRegexpG = "REGEXPG" //不区分大小写的匹配
MatchTypeAny = "ANY" //任意匹配
MethodGET = "GET"
MethodPOST = "POST"
MethodPUT = "PUT"
MethodDELETE = "DELETE"
MethodPATCH = "PATCH"
MethodHEAD = "HEAD"
MethodOPTIONS = "OPTIONS"
RestfulLabel = "{rest}"
//来源类型
SourceSelfBuild = "self-build" //自建
SourceImport = "import" //导入
SourceSync = "sync" //同步
)
+68
View File
@@ -0,0 +1,68 @@
package common
import (
"fmt"
"strconv"
)
func FmtIntFromInterface(val interface{}) int64 {
if val == nil {
return 0
}
switch ret := val.(type) {
case int8:
return int64(ret)
case int16:
return int64(ret)
case int32:
return int64(ret)
case int64:
return ret
case uint8:
return int64(ret)
case uint16:
return int64(ret)
case uint32:
return int64(ret)
case uint64:
return int64(ret)
case int:
return int64(ret)
default:
return 0
}
}
func FmtStringFromInterface(val interface{}) string {
if val == nil {
return ""
}
switch ret := val.(type) {
case string:
return ret
case int8, uint8, int16, uint16, int, uint, int64, uint64, float32, float64:
return fmt.Sprintf("%v", ret)
}
return ""
}
func FmtFloatFromInterface(val interface{}) float64 {
if val == nil {
return 0
}
switch ret := val.(type) {
case float64:
return ret
case float32:
return float64(ret)
default:
return 0
}
}
func FloatToString(val float64) string {
float, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", val), 64)
return strconv.FormatFloat(float, 'g', -1, 64)
}
+131
View File
@@ -0,0 +1,131 @@
package common
import (
"errors"
"fmt"
"regexp"
)
type RegexpPattern string
const (
// EnglishOrNumber_ 英文开头,数字字母下划线组合
EnglishOrNumber_ RegexpPattern = `^[a-zA-Z][a-zA-Z0-9_]*$`
// AnyEnglishOrNumber_ 数字字母下划线任意组合
AnyEnglishOrNumber_ = `^[a-zA-Z0-9_]+$`
// UUIDExp UUID正则 数字字母横杠下划线任意组合
UUIDExp = `^[a-zA-Z0-9-_]+$`
// DomainPortExp 域名或者域名:端口
DomainPortExp = `^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.?[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?(:[0-9]+)?$`
// IPPortExp IP:PORT
IPPortExp = `^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:[0-9]+$`
// SchemeIPPortExp scheme://IP:PORT
SchemeIPPortExp = `^[a-zA-z]+://((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:[0-9]+$`
// CIDRIpv4Exp IPV4或者IPV4的CIDR
CIDRIpv4Exp = `^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([1-9]|[1-2]\d|3[0-2]))?$`
// CheckPathIPPortExp (scheme://)?ip:port
CheckPathIPPortExp = `([a-zA-z]+://)?((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:[0-9]+`
)
var (
//环境变量专用 匹配字母开头,有字母数字下划线组合而成的字符串 环境变量专用
variableRegexp = regexp.MustCompile(`^\${([a-zA-Z][a-zA-Z0-9_]*)}$`)
//筛选条件 APPKEY专用匹配字母开头,有字母数字下划线组合而成的字符串
filterAppKeyRegexp = regexp.MustCompile(`^appkey{([a-zA-Z][a-zA-Z0-9_]*)}$`)
// 域名或者域名:PORT正则
domainPortRegexp = regexp.MustCompile(DomainPortExp)
//IP:PORT 正则
ipPortRegexp = regexp.MustCompile(IPPortExp)
//scheme://IP:PORT 正则
schemeIpPortRegexp = regexp.MustCompile(SchemeIPPortExp)
//IPv4或者IPv4CIDR 正则
cidrIpv4Regexp = regexp.MustCompile(CIDRIpv4Exp)
//checkIPPortRegexp 检查路径上是否包含xxx://ip:port的字符串
checkIPPortRegexp = regexp.MustCompile(CheckPathIPPortExp)
//restfulPathMatchRegexp 匹配包含restful参数的路径
restfulPathMatchRegexp = regexp.MustCompile(`({[0-9a-zA-Z-_]+})+`)
//restfulParamMatchRegexp 匹配restful参数 {xxx}
restfulParamMatchRegexp = regexp.MustCompile(`^{[0-9a-zA-Z-_]+}$`)
)
func IsMatchString(regexpPattern RegexpPattern, s string) error {
b, _ := regexp.MatchString(string(regexpPattern), s)
if b {
return nil
}
switch regexpPattern {
case EnglishOrNumber_:
return errors.New("只能使用英文字母、数字、下划线,英文字母开头")
case AnyEnglishOrNumber_:
return errors.New("只能使用英文字母、数字、下划线")
case UUIDExp:
return errors.New("只能使用英文字母、数字、横杠、下划线")
default:
return errors.New("非法字符串")
}
}
// IsMatchVariable 判断字符串是否匹配环境变量标准格式${abc}
func IsMatchVariable(s string) bool {
return variableRegexp.MatchString(s)
}
// IsMatchFilterAppKey 判断字符串是否匹配策略筛选条件key(应用)标准格式appkey{abc}
func IsMatchFilterAppKey(s string) bool {
return filterAppKeyRegexp.MatchString(s)
}
// IsMatchDomainPort 判断字符串是否符合域名或者域名:port
func IsMatchDomainPort(s string) bool {
return domainPortRegexp.MatchString(s)
}
// IsMatchIpPort 判断字符串是否符合ip:port
func IsMatchIpPort(s string) bool {
return ipPortRegexp.MatchString(s)
}
// IsMatchSchemeIpPort 判断字符串是否符合scheme://ip:port
func IsMatchSchemeIpPort(s string) bool {
return schemeIpPortRegexp.MatchString(s)
}
// IsMatchCIDRIpv4 判断字符串是否符合ipv4或者ipv4的cidr
func IsMatchCIDRIpv4(s string) bool {
return cidrIpv4Regexp.MatchString(s)
}
// GetVariableKey 从环境变量标准格式${abc}中取得key abc
func GetVariableKey(s string) string {
return variableRegexp.ReplaceAllString(s, "$1")
}
// GetFilterAppKey 从标准格式appkey{abc}中取得key abc
func GetFilterAppKey(s string) string {
return filterAppKeyRegexp.ReplaceAllString(s, "$1")
}
func SetFilterAppKey(key string) string {
return fmt.Sprintf("appkey{%s}", key)
}
// IsRestfulPath 检查路径是否有restful参数
func IsRestfulPath(path string) bool {
return restfulPathMatchRegexp.MatchString(path)
}
// IsRestfulParam 检查是否为restful参数
func IsRestfulParam(param string) bool {
return restfulParamMatchRegexp.MatchString(param)
}
// ReplaceRestfulPath 将restful路径转换成apinto的正则匹配路径
func ReplaceRestfulPath(path, replaceStr string) string {
return restfulPathMatchRegexp.ReplaceAllString(path, replaceStr)
}
// CheckPathContainsIPPort 检查路径中是否包含xxx://ip:port
func CheckPathContainsIPPort(path string) bool {
return checkIPPortRegexp.MatchString(path)
}
+40
View File
@@ -0,0 +1,40 @@
package version
import (
"bytes"
"fmt"
"github.com/urfave/cli/v2"
)
// These should be set via go build -ldflags -X 'xxxx'.
var Version = "unknown"
var goVersion = "unknown"
var gitCommit = "unknown"
var BuildTime = "unknown"
var buildUser = "unknown"
//var eoscVersion = "unknown"
var profileInfo []byte
func init() {
buffer := &bytes.Buffer{}
fmt.Fprintf(buffer, "Apinto version: %s\n", Version)
fmt.Fprintf(buffer, "Golang version: %s\n", goVersion)
fmt.Fprintf(buffer, "Git commit hash: %s\n", gitCommit)
fmt.Fprintf(buffer, "Built on: %s\n", BuildTime)
fmt.Fprintf(buffer, "Built by: %s\n", buildUser)
//fmt.Fprintf(buffer, "Built by eosc version: %s\n", eoscVersion)
profileInfo = buffer.Bytes()
}
func Build() *cli.Command {
return &cli.Command{
Name: "version",
Action: func(context *cli.Context) error {
fmt.Print(string(profileInfo))
return nil
},
}
}
+43
View File
@@ -0,0 +1,43 @@
package api
import (
"reflect"
"github.com/gin-gonic/gin"
"github.com/eolinker/go-common/autowire"
api_dto "github.com/APIParkLab/APIPark/module/api/dto"
)
type IAPIController interface {
// Detail 获取API详情
Detail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiDetail, error)
// SimpleDetail 获取API简要详情
SimpleDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiSimpleDetail, error)
// Search 获取API列表
Search(ctx *gin.Context, keyword string, serviceId string) ([]*api_dto.ApiItem, error)
// SimpleSearch 获取API简要列表
SimpleSearch(ctx *gin.Context, keyword string, serviceId string) ([]*api_dto.ApiSimpleItem, error)
//SimpleList(ctx *gin.Context, serviceId string) ([]*api_dto.ApiSimpleItem, error)
// Create 创建API
Create(ctx *gin.Context, serviceId string, dto *api_dto.CreateApi) (*api_dto.ApiSimpleDetail, error)
// Edit 编辑API
Edit(ctx *gin.Context, serviceId string, apiId string, dto *api_dto.EditApi) (*api_dto.ApiSimpleDetail, error)
// Delete 删除API
Delete(ctx *gin.Context, serviceId string, apiId string) error
// Copy 复制API
Copy(ctx *gin.Context, serviceId string, apiId string, dto *api_dto.CreateApi) (*api_dto.ApiSimpleDetail, error)
// ApiDocDetail 获取API文档详情
ApiDocDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiDocDetail, error)
// ApiProxyDetail 获取API代理详情
ApiProxyDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiProxyDetail, error)
// Prefix 获取API前缀
Prefix(ctx *gin.Context, serviceId string) (string, bool, error)
}
func init() {
autowire.Auto[IAPIController](func() reflect.Value {
return reflect.ValueOf(new(imlAPIController))
})
}
+65
View File
@@ -0,0 +1,65 @@
package api
import (
"github.com/APIParkLab/APIPark/module/api"
api_dto "github.com/APIParkLab/APIPark/module/api/dto"
"github.com/gin-gonic/gin"
)
var _ IAPIController = (*imlAPIController)(nil)
type imlAPIController struct {
module api.IApiModule `autowired:""`
}
//func (i *imlAPIController) SimpleList(ctx *gin.Context, serviceId string) ([]*api_dto.ApiSimpleItem, error) {
// return i.module.SimpleList(ctx, serviceId)
//}
func (i *imlAPIController) Detail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiDetail, error) {
return i.module.Detail(ctx, serviceId, apiId)
}
func (i *imlAPIController) SimpleDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiSimpleDetail, error) {
return i.module.SimpleDetail(ctx, serviceId, apiId)
}
func (i *imlAPIController) Search(ctx *gin.Context, keyword string, serviceId string) ([]*api_dto.ApiItem, error) {
return i.module.Search(ctx, keyword, serviceId)
}
func (i *imlAPIController) SimpleSearch(ctx *gin.Context, keyword string, serviceId string) ([]*api_dto.ApiSimpleItem, error) {
return i.module.SimpleSearch(ctx, keyword, serviceId)
}
func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, dto *api_dto.CreateApi) (*api_dto.ApiSimpleDetail, error) {
return i.module.Create(ctx, serviceId, dto)
}
func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string, dto *api_dto.EditApi) (*api_dto.ApiSimpleDetail, error) {
return i.module.Edit(ctx, serviceId, apiId, dto)
}
func (i *imlAPIController) Delete(ctx *gin.Context, serviceId string, apiId string) error {
return i.module.Delete(ctx, serviceId, apiId)
}
func (i *imlAPIController) Copy(ctx *gin.Context, serviceId string, apiId string, dto *api_dto.CreateApi) (*api_dto.ApiSimpleDetail, error) {
return i.module.Copy(ctx, serviceId, apiId, dto)
}
func (i *imlAPIController) ApiDocDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiDocDetail, error) {
return i.module.ApiDocDetail(ctx, serviceId, apiId)
}
func (i *imlAPIController) ApiProxyDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiProxyDetail, error) {
return i.module.ApiProxyDetail(ctx, serviceId, apiId)
}
func (i *imlAPIController) Prefix(ctx *gin.Context, serviceId string) (string, bool, error) {
prefix, err := i.module.Prefix(ctx, serviceId)
if err != nil {
return "", false, err
}
return prefix, true, nil
}
@@ -0,0 +1,32 @@
package application_authorization
import (
"reflect"
"github.com/gin-gonic/gin"
"github.com/eolinker/go-common/autowire"
application_authorization_dto "github.com/APIParkLab/APIPark/module/application-authorization/dto"
)
type IAuthorizationController interface {
// AddAuthorization 添加项目鉴权信息
AddAuthorization(ctx *gin.Context, pid string, info *application_authorization_dto.CreateAuthorization) (*application_authorization_dto.Authorization, error)
// EditAuthorization 修改项目鉴权信息
EditAuthorization(ctx *gin.Context, pid string, aid string, info *application_authorization_dto.EditAuthorization) (*application_authorization_dto.Authorization, error)
// DeleteAuthorization 删除项目鉴权
DeleteAuthorization(ctx *gin.Context, pid string, aid string) error
// Authorizations 获取项目鉴权列表
Authorizations(ctx *gin.Context, pid string) ([]*application_authorization_dto.AuthorizationItem, error)
// Detail 获取项目鉴权详情(弹窗用)
Detail(ctx *gin.Context, pid string, aid string) ([]application_authorization_dto.DetailItem, error)
// Info 获取项目鉴权详情
Info(ctx *gin.Context, pid string, aid string) (*application_authorization_dto.Authorization, error)
}
func init() {
autowire.Auto[IAuthorizationController](func() reflect.Value {
return reflect.ValueOf(new(imlAuthorizationController))
})
}
@@ -0,0 +1,37 @@
package application_authorization
import (
application_authorization "github.com/APIParkLab/APIPark/module/application-authorization"
application_authorization_dto "github.com/APIParkLab/APIPark/module/application-authorization/dto"
"github.com/gin-gonic/gin"
)
var _ IAuthorizationController = (*imlAuthorizationController)(nil)
type imlAuthorizationController struct {
module application_authorization.IAuthorizationModule `autowired:""`
}
func (i *imlAuthorizationController) AddAuthorization(ctx *gin.Context, pid string, info *application_authorization_dto.CreateAuthorization) (*application_authorization_dto.Authorization, error) {
return i.module.AddAuthorization(ctx, pid, info)
}
func (i *imlAuthorizationController) EditAuthorization(ctx *gin.Context, pid string, aid string, info *application_authorization_dto.EditAuthorization) (*application_authorization_dto.Authorization, error) {
return i.module.EditAuthorization(ctx, pid, aid, info)
}
func (i *imlAuthorizationController) DeleteAuthorization(ctx *gin.Context, pid string, aid string) error {
return i.module.DeleteAuthorization(ctx, pid, aid)
}
func (i *imlAuthorizationController) Authorizations(ctx *gin.Context, pid string) ([]*application_authorization_dto.AuthorizationItem, error) {
return i.module.Authorizations(ctx, pid)
}
func (i *imlAuthorizationController) Detail(ctx *gin.Context, pid string, aid string) ([]application_authorization_dto.DetailItem, error) {
return i.module.Detail(ctx, pid, aid)
}
func (i *imlAuthorizationController) Info(ctx *gin.Context, pid string, aid string) (*application_authorization_dto.Authorization, error) {
return i.module.Info(ctx, pid, aid)
}
+36
View File
@@ -0,0 +1,36 @@
package catalogue
import (
"github.com/gin-gonic/gin"
"reflect"
tag_dto "github.com/APIParkLab/APIPark/module/tag/dto"
catalogue_dto "github.com/APIParkLab/APIPark/module/catalogue/dto"
"github.com/eolinker/go-common/autowire"
)
type ICatalogueController interface {
// Search 搜索目录、标签列表
Search(ctx *gin.Context, keyword string) ([]*catalogue_dto.Item, []*tag_dto.Item, error)
// Create 创建目录
Create(ctx *gin.Context, input *catalogue_dto.CreateCatalogue) error
// Edit 修改目录
Edit(ctx *gin.Context, id string, input *catalogue_dto.EditCatalogue) error
// Delete 删除目录
Delete(ctx *gin.Context, id string) error
// Services 服务列表
Services(ctx *gin.Context, keyword string) ([]*catalogue_dto.ServiceItem, error)
// ServiceDetail 服务详情
ServiceDetail(ctx *gin.Context, sid string) (*catalogue_dto.ServiceDetail, error)
// Subscribe 订阅服务
Subscribe(ctx *gin.Context, subscribeInfo *catalogue_dto.SubscribeService) error
Sort(ctx *gin.Context, sorts *[]*catalogue_dto.SortItem) error
}
func init() {
autowire.Auto[ICatalogueController](func() reflect.Value {
return reflect.ValueOf(new(imlCatalogueController))
})
}
+62
View File
@@ -0,0 +1,62 @@
package catalogue
import (
"github.com/APIParkLab/APIPark/module/catalogue"
catalogue_dto "github.com/APIParkLab/APIPark/module/catalogue/dto"
"github.com/APIParkLab/APIPark/module/tag"
tag_dto "github.com/APIParkLab/APIPark/module/tag/dto"
"github.com/gin-gonic/gin"
)
var (
_ ICatalogueController = (*imlCatalogueController)(nil)
)
type imlCatalogueController struct {
catalogueModule catalogue.ICatalogueModule `autowired:""`
tagModule tag.ITagModule `autowired:""`
}
func (i *imlCatalogueController) Sort(ctx *gin.Context, sorts *[]*catalogue_dto.SortItem) error {
return i.catalogueModule.Sort(ctx, *sorts)
}
func (i *imlCatalogueController) Subscribe(ctx *gin.Context, subscribeInfo *catalogue_dto.SubscribeService) error {
return i.catalogueModule.Subscribe(ctx, subscribeInfo)
}
func (i *imlCatalogueController) ServiceDetail(ctx *gin.Context, sid string) (*catalogue_dto.ServiceDetail, error) {
return i.catalogueModule.ServiceDetail(ctx, sid)
}
func (i *imlCatalogueController) Search(ctx *gin.Context, keyword string) ([]*catalogue_dto.Item, []*tag_dto.Item, error) {
catalogues, err := i.catalogueModule.Search(ctx, keyword)
if err != nil {
return nil, nil, err
}
tags, err := i.tagModule.Search(ctx, keyword)
if err != nil {
return nil, nil, err
}
return catalogues, tags, nil
}
func (i *imlCatalogueController) Create(ctx *gin.Context, input *catalogue_dto.CreateCatalogue) error {
return i.catalogueModule.Create(ctx, input)
}
func (i *imlCatalogueController) Edit(ctx *gin.Context, id string, input *catalogue_dto.EditCatalogue) error {
return i.catalogueModule.Edit(ctx, id, input)
}
func (i *imlCatalogueController) Delete(ctx *gin.Context, id string) error {
return i.catalogueModule.Delete(ctx, id)
}
func (i *imlCatalogueController) Services(ctx *gin.Context, keyword string) ([]*catalogue_dto.ServiceItem, error) {
items, err := i.catalogueModule.Services(ctx, keyword)
if err != nil {
return nil, err
}
return items, nil
}
+23
View File
@@ -0,0 +1,23 @@
package certificate
import (
"reflect"
certificate_dto "github.com/APIParkLab/APIPark/module/certificate/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type ICertificateController interface {
Create(ctx *gin.Context, create *certificate_dto.FileInput) error
Update(ctx *gin.Context, id string, edit *certificate_dto.FileInput) error
ListForPartition(ctx *gin.Context) ([]*certificate_dto.Certificate, error)
Detail(ctx *gin.Context, id string) (*certificate_dto.Certificate, *certificate_dto.File, error)
Delete(ctx *gin.Context, id string) (string, error)
}
func init() {
autowire.Auto[ICertificateController](func() reflect.Value {
return reflect.ValueOf(new(imlCertificate))
})
}
+39
View File
@@ -0,0 +1,39 @@
package certificate
import (
"github.com/APIParkLab/APIPark/module/certificate"
certificate_dto "github.com/APIParkLab/APIPark/module/certificate/dto"
"github.com/gin-gonic/gin"
)
var (
_ ICertificateController = (*imlCertificate)(nil)
)
type imlCertificate struct {
module certificate.ICertificateModule `autowired:""`
}
func (c *imlCertificate) Create(ctx *gin.Context, create *certificate_dto.FileInput) error {
return c.module.Create(ctx, create)
}
func (c *imlCertificate) Update(ctx *gin.Context, id string, edit *certificate_dto.FileInput) error {
return c.module.Update(ctx, id, edit)
}
func (c *imlCertificate) ListForPartition(ctx *gin.Context) ([]*certificate_dto.Certificate, error) {
return c.module.List(ctx)
}
func (c *imlCertificate) Detail(ctx *gin.Context, id string) (*certificate_dto.Certificate, *certificate_dto.File, error) {
return c.module.Detail(ctx, id)
}
func (c *imlCertificate) Delete(ctx *gin.Context, id string) (string, error) {
err := c.module.Delete(ctx, id)
if err != nil {
return "", err
}
return id, nil
}
+22
View File
@@ -0,0 +1,22 @@
package cluster
import (
"reflect"
cluster_dto "github.com/APIParkLab/APIPark/module/cluster/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type IClusterController interface {
Nodes(ctx *gin.Context, clusterId string) ([]*cluster_dto.Node, error)
ResetCluster(ctx *gin.Context, clusterId string, input *cluster_dto.ResetCluster) ([]*cluster_dto.Node, error)
Check(ctx *gin.Context, input *cluster_dto.CheckCluster) ([]*cluster_dto.Node, error)
}
func init() {
autowire.Auto[IClusterController](func() reflect.Value {
return reflect.ValueOf(new(imlCluster))
})
}
+73
View File
@@ -0,0 +1,73 @@
package cluster
import (
"github.com/APIParkLab/APIPark/module/cluster"
cluster_dto "github.com/APIParkLab/APIPark/module/cluster/dto"
"github.com/gin-gonic/gin"
)
var (
_ IClusterController = (*imlCluster)(nil)
)
type imlCluster struct {
module cluster.IClusterModule `autowired:""`
}
func (p *imlCluster) Nodes(ctx *gin.Context, clusterId string) ([]*cluster_dto.Node, error) {
if clusterId == "" {
clusterId = "default"
}
return p.module.ClusterNodes(ctx, clusterId)
}
func (p *imlCluster) ResetCluster(ctx *gin.Context, clusterId string, input *cluster_dto.ResetCluster) ([]*cluster_dto.Node, error) {
if clusterId == "" {
clusterId = "default"
}
return p.module.ResetCluster(ctx, clusterId, input.ManagerAddress)
}
func (p *imlCluster) Check(ctx *gin.Context, input *cluster_dto.CheckCluster) ([]*cluster_dto.Node, error) {
return p.module.CheckCluster(ctx, input.Address)
}
//
//func (p *imlCluster) SimpleWithCluster(ctx *gin.Context) ([]*parition_dto.SimpleWithCluster, error) {
// return p.module.SimpleWithCluster(ctx)
//}
//
//func (p *imlCluster) Delete(ctx *gin.Context, id string) (string, error) {
// err := p.module.Delete(ctx, id)
// if err != nil {
// return "", err
// }
// return id, nil
//}
//
//func (p *imlCluster) Search(ctx *gin.Context, keyword string) ([]*parition_dto.Item, error) {
// return p.module.Search(ctx, keyword)
//}
//
//func (p *imlCluster) Simple(ctx *gin.Context) ([]*parition_dto.Simple, error) {
// return p.module.Simple(ctx)
//}
//
//func (p *imlCluster) Info(ctx *gin.Context, id string) (*parition_dto.Detail, error) {
// if id == "" {
// return nil, errors.New("id is empty")
// }
// return p.module.Get(ctx, id)
//}
//
//func (p *imlCluster) Update(ctx *gin.Context, id string, input *parition_dto.Edit) (*parition_dto.Detail, error) {
// return p.module.Update(ctx, id, input)
//}
//
//func (p *imlCluster) Create(ctx *gin.Context, input *parition_dto.Create) (*parition_dto.Detail, string, auto.TimeLabel, error) {
// detail, err := p.module.CreatePartition(ctx, input)
// if err != nil {
// return nil, "", auto.TimeLabel{}, err
// }
// return detail, detail.Id, detail.UpdateTime, nil
//}
+17
View File
@@ -0,0 +1,17 @@
package common
import (
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
"reflect"
)
type ICommonController interface {
Version(ctx *gin.Context) (string, string, error)
}
func init() {
autowire.Auto[ICommonController](func() reflect.Value {
return reflect.ValueOf(new(imlCommonController))
})
}
+14
View File
@@ -0,0 +1,14 @@
package common
import (
"github.com/APIParkLab/APIPark/common/version"
"github.com/gin-gonic/gin"
)
var _ ICommonController = (*imlCommonController)(nil)
type imlCommonController struct{}
func (i imlCommonController) Version(ctx *gin.Context) (string, string, error) {
return version.Version, version.BuildTime, nil
}
+5
View File
@@ -0,0 +1,5 @@
// Description: This package is used to handle all the request from the client
// and return the response to the client.
// 只能使用 module 下面的封装好的接口,不能直接使用 service 下面的接口
package controller
@@ -0,0 +1,29 @@
package dynamic_module
import (
"reflect"
dynamic_module_dto "github.com/APIParkLab/APIPark/module/dynamic-module/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type IDynamicModuleController interface {
Create(ctx *gin.Context, module string, input *dynamic_module_dto.CreateDynamicModule) (*dynamic_module_dto.DynamicModule, error)
Edit(ctx *gin.Context, module string, id string, input *dynamic_module_dto.EditDynamicModule) (*dynamic_module_dto.DynamicModule, error)
Delete(ctx *gin.Context, module string, ids string) error
Get(ctx *gin.Context, module string, id string) (*dynamic_module_dto.DynamicModule, error)
List(ctx *gin.Context, module string, keyword string, cluster string, page string, pageSize string) ([]map[string]interface{}, *dynamic_module_dto.PluginInfo, int64, error)
Render(ctx *gin.Context, module string) (*dynamic_module_dto.PluginBasic, map[string]interface{}, error)
ModuleDrivers(ctx *gin.Context, group string) ([]*dynamic_module_dto.ModuleDriver, error)
Online(ctx *gin.Context, module string, id string, partitionInput *dynamic_module_dto.ClusterInput) error
Offline(ctx *gin.Context, module string, id string, partitionInput *dynamic_module_dto.ClusterInput) error
//PartitionStatuses(ctx *gin.Context, module string, keyword string, page string, pageSize string) (map[string]map[string]string, error)
//PartitionStatus(ctx *gin.Context, module string, id string) (*dynamic_module_dto.OnlineInfo, error)
}
func init() {
autowire.Auto[IDynamicModuleController](func() reflect.Value {
return reflect.ValueOf(new(imlDynamicModuleController))
})
}
+109
View File
@@ -0,0 +1,109 @@
package dynamic_module
import (
"encoding/json"
"strconv"
dynamic_module "github.com/APIParkLab/APIPark/module/dynamic-module"
dynamic_module_dto "github.com/APIParkLab/APIPark/module/dynamic-module/dto"
"github.com/gin-gonic/gin"
)
var _ IDynamicModuleController = (*imlDynamicModuleController)(nil)
type imlDynamicModuleController struct {
module dynamic_module.IDynamicModuleModule `autowired:""`
}
func (i *imlDynamicModuleController) Online(ctx *gin.Context, module string, id string, partitionInput *dynamic_module_dto.ClusterInput) error {
return i.module.Online(ctx, module, id, partitionInput)
}
func (i *imlDynamicModuleController) Offline(ctx *gin.Context, module string, id string, partitionInput *dynamic_module_dto.ClusterInput) error {
return i.module.Offline(ctx, module, id, partitionInput)
}
//func (i *imlDynamicModuleController) PartitionStatuses(ctx *gin.Context, module string, keyword string, page string, pageSize string) (map[string]map[string]string, error) {
// p, err := strconv.Atoi(page)
// if err != nil {
// p = 1
// }
// ps, err := strconv.Atoi(pageSize)
// if err != nil {
// ps = 20
// }
// return i.module.PartitionStatuses(ctx, module, keyword, p, ps)
//}
//
//func (i *imlDynamicModuleController) PartitionStatus(ctx *gin.Context, module string, id string) (*dynamic_module_dto.OnlineInfo, error) {
// return i.module.PartitionStatus(ctx, module, id)
//}
func (i *imlDynamicModuleController) ModuleDrivers(ctx *gin.Context, group string) ([]*dynamic_module_dto.ModuleDriver, error) {
return i.module.ModuleDrivers(ctx, group)
}
func (i *imlDynamicModuleController) Render(ctx *gin.Context, module string) (*dynamic_module_dto.PluginBasic, map[string]interface{}, error) {
render, err := i.module.Render(ctx, module)
if err != nil {
return nil, nil, err
}
pluginInfo, err := i.module.PluginInfo(ctx, module)
if err != nil {
return nil, nil, err
}
return pluginInfo.PluginBasic, render, nil
}
func (i *imlDynamicModuleController) Create(ctx *gin.Context, module string, input *dynamic_module_dto.CreateDynamicModule) (*dynamic_module_dto.DynamicModule, error) {
return i.module.Create(ctx, module, input)
}
func (i *imlDynamicModuleController) Edit(ctx *gin.Context, module string, id string, input *dynamic_module_dto.EditDynamicModule) (*dynamic_module_dto.DynamicModule, error) {
return i.module.Edit(ctx, module, id, input)
}
func (i *imlDynamicModuleController) Delete(ctx *gin.Context, module string, idStr string) error {
ids := make([]string, 0)
err := json.Unmarshal([]byte(idStr), &ids)
if err != nil {
return err
}
if len(ids) == 0 {
return nil
}
return i.module.Delete(ctx, module, ids)
}
func (i *imlDynamicModuleController) Get(ctx *gin.Context, module string, id string) (*dynamic_module_dto.DynamicModule, error) {
return i.module.Get(ctx, module, id)
}
func (i *imlDynamicModuleController) List(ctx *gin.Context, module string, keyword string, clusterId string, page string, pageSize string) ([]map[string]interface{}, *dynamic_module_dto.PluginInfo, int64, error) {
p, err := strconv.Atoi(page)
if err != nil {
p = 1
}
ps, err := strconv.Atoi(pageSize)
if err != nil {
ps = 20
}
list, total, err := i.module.List(ctx, module, keyword, p, ps)
if err != nil {
return nil, nil, 0, err
}
//if clusterId == "" {
// clusterId = "[]"
//}
//ids := make([]string, 0)
//err = json.Unmarshal([]byte(clusterId), &ids)
//if err != nil {
// return nil, nil, 0, err
//}
plugin, err := i.module.PluginInfo(ctx, module)
if err != nil {
return nil, nil, 0, err
}
return list, plugin, total, nil
}
+52
View File
@@ -0,0 +1,52 @@
package my_team
import (
my_team "github.com/APIParkLab/APIPark/module/my-team"
team_dto "github.com/APIParkLab/APIPark/module/my-team/dto"
"github.com/gin-gonic/gin"
)
var (
_ ITeamController = (*imlTeamController)(nil)
)
type imlTeamController struct {
module my_team.ITeamModule `autowired:""`
}
func (c *imlTeamController) UpdateMemberRole(ctx *gin.Context, id string, input *team_dto.UpdateMemberRole) error {
return c.module.UpdateMemberRole(ctx, id, input)
}
func (c *imlTeamController) GetTeam(ctx *gin.Context, id string) (*team_dto.Team, error) {
return c.module.GetTeam(ctx, id)
}
func (c *imlTeamController) Search(ctx *gin.Context, keyword string) ([]*team_dto.Item, error) {
return c.module.Search(ctx, keyword)
}
func (c *imlTeamController) EditTeam(ctx *gin.Context, id string, team *team_dto.EditTeam) (*team_dto.Team, error) {
return c.module.Edit(ctx, id, team)
}
func (c *imlTeamController) SimpleTeams(ctx *gin.Context, keyword string) ([]*team_dto.SimpleTeam, error) {
return c.module.SimpleTeams(ctx, keyword)
}
func (c *imlTeamController) AddMember(ctx *gin.Context, id string, users *team_dto.UserIDs) error {
return c.module.AddMember(ctx, id, users.Users...)
}
func (c *imlTeamController) RemoveMember(ctx *gin.Context, id string, uuid string) error {
return c.module.RemoveMember(ctx, id, uuid)
}
func (c *imlTeamController) Members(ctx *gin.Context, id string, keyword string) ([]*team_dto.Member, error) {
return c.module.Members(ctx, id, keyword)
}
func (c *imlTeamController) SimpleMembers(ctx *gin.Context, id string, keyword string) ([]*team_dto.SimpleMember, error) {
return c.module.SimpleMembers(ctx, id, keyword)
}
+28
View File
@@ -0,0 +1,28 @@
package my_team
import (
"reflect"
team_dto "github.com/APIParkLab/APIPark/module/my-team/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type ITeamController interface {
// GetTeam 获取团队信息
GetTeam(ctx *gin.Context, id string) (*team_dto.Team, error)
Search(ctx *gin.Context, keyword string) ([]*team_dto.Item, error)
EditTeam(ctx *gin.Context, id string, team *team_dto.EditTeam) (*team_dto.Team, error)
SimpleTeams(ctx *gin.Context, keyword string) ([]*team_dto.SimpleTeam, error)
AddMember(ctx *gin.Context, id string, users *team_dto.UserIDs) error
RemoveMember(ctx *gin.Context, id string, uuid string) error
Members(ctx *gin.Context, id string, keyword string) ([]*team_dto.Member, error)
SimpleMembers(ctx *gin.Context, id string, keyword string) ([]*team_dto.SimpleMember, error)
UpdateMemberRole(ctx *gin.Context, id string, input *team_dto.UpdateMemberRole) error
}
func init() {
autowire.Auto[ITeamController](func() reflect.Value {
return reflect.ValueOf(new(imlTeamController))
})
}
+24
View File
@@ -0,0 +1,24 @@
package permit_system
import (
"github.com/APIParkLab/APIPark/module/permit/system"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
var (
_ ISystemPermitController = (*imlSystemPermitController)(nil)
_ autowire.Complete = (*imlSystemPermitController)(nil)
)
type imlSystemPermitController struct {
systemPermitModule system.ISystemPermitModule `autowired:""`
}
func (c *imlSystemPermitController) Permissions(ctx *gin.Context) ([]string, error) {
return c.systemPermitModule.Permissions(ctx)
}
func (c *imlSystemPermitController) OnComplete() {
}
+18
View File
@@ -0,0 +1,18 @@
package permit_system
import (
"reflect"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type ISystemPermitController interface {
Permissions(ctx *gin.Context) ([]string, error)
}
func init() {
autowire.Auto[ISystemPermitController](func() reflect.Value {
return reflect.ValueOf(new(imlSystemPermitController))
})
}
+18
View File
@@ -0,0 +1,18 @@
package permit_team
import (
"github.com/APIParkLab/APIPark/module/permit/team"
"github.com/gin-gonic/gin"
)
var (
_ ITeamPermitController = (*imlTeamPermitController)(nil)
)
type imlTeamPermitController struct {
teamPermitModule team.ITeamPermitModule `autowired:""`
}
func (c *imlTeamPermitController) Permissions(ctx *gin.Context, team string) ([]string, error) {
return c.teamPermitModule.Permissions(ctx, team)
}
+18
View File
@@ -0,0 +1,18 @@
package permit_team
import (
"reflect"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type ITeamPermitController interface {
Permissions(ctx *gin.Context, team string) ([]string, error)
}
func init() {
autowire.Auto[ITeamPermitController](func() reflect.Value {
return reflect.ValueOf(new(imlTeamPermitController))
})
}
+36
View File
@@ -0,0 +1,36 @@
package plugin_cluster
import (
"github.com/APIParkLab/APIPark/model/plugin_model"
"github.com/APIParkLab/APIPark/module/plugin-cluster"
"github.com/APIParkLab/APIPark/module/plugin-cluster/dto"
"github.com/gin-gonic/gin"
)
var (
_ IPluginClusterController = (*imlPluginClusterController)(nil)
)
type imlPluginClusterController struct {
module plugin_cluster.IPluginClusterModule `autowired:""`
}
func (i *imlPluginClusterController) Info(ctx *gin.Context, name string) (*dto.Define, error) {
return i.module.GetDefine(ctx, name)
}
func (i *imlPluginClusterController) Option(ctx *gin.Context, project string) ([]*dto.PluginOption, error) {
return i.module.Options(ctx)
}
func (i *imlPluginClusterController) List(ctx *gin.Context, clusterId string) ([]*dto.Item, error) {
return i.module.List(ctx, clusterId)
}
func (i *imlPluginClusterController) Get(ctx *gin.Context, clusterId string, name string) (config *dto.PluginOutput, render plugin_model.Render, er error) {
return i.module.Get(ctx, clusterId, name)
}
func (i *imlPluginClusterController) Set(ctx *gin.Context, clusterId string, name string, config *dto.PluginSetting) error {
return i.module.Set(ctx, clusterId, name, config)
}
@@ -0,0 +1,24 @@
package plugin_cluster
import (
"reflect"
"github.com/APIParkLab/APIPark/model/plugin_model"
"github.com/APIParkLab/APIPark/module/plugin-cluster/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type IPluginClusterController interface {
List(ctx *gin.Context, clusterId string) ([]*dto.Item, error)
Get(ctx *gin.Context, clusterId string, name string) (config *dto.PluginOutput, render plugin_model.Render, er error)
Set(ctx *gin.Context, clusterId string, name string, config *dto.PluginSetting) error
Option(ctx *gin.Context, project string) ([]*dto.PluginOption, error)
Info(ctx *gin.Context, name string) (*dto.Define, error)
}
func init() {
autowire.Auto[IPluginClusterController](func() reflect.Value {
return reflect.ValueOf(new(imlPluginClusterController))
})
}
+132
View File
@@ -0,0 +1,132 @@
package publish
import (
"strconv"
"github.com/APIParkLab/APIPark/module/publish"
"github.com/APIParkLab/APIPark/module/publish/dto"
"github.com/APIParkLab/APIPark/module/release"
dto2 "github.com/APIParkLab/APIPark/module/release/dto"
"github.com/gin-gonic/gin"
)
var (
_ IPublishController = (*imlPublishController)(nil)
)
type imlPublishController struct {
publishModule publish.IPublishModule `autowired:""`
releaseModule release.IReleaseModule `autowired:""`
}
func (c *imlPublishController) ReleaseDo(ctx *gin.Context, serviceId string, input *dto.ApplyOnReleaseInput) (*dto.Publish, error) {
newReleaseId, err := c.releaseModule.Create(ctx, serviceId, &dto2.CreateInput{
Version: input.Version,
Remark: input.VersionRemark,
})
if err != nil {
return nil, err
}
apply, err := c.publishModule.Apply(ctx, serviceId, &dto.ApplyInput{
Release: newReleaseId,
Remark: input.PublishRemark,
})
if err != nil {
return nil, err
}
err = c.publishModule.Accept(ctx, serviceId, apply.Id, "")
if err != nil {
c.releaseModule.Delete(ctx, serviceId, newReleaseId)
return nil, err
}
err = c.publishModule.Publish(ctx, serviceId, apply.Id)
if err != nil {
c.releaseModule.Delete(ctx, serviceId, newReleaseId)
return nil, err
}
err = c.publishModule.Publish(ctx, serviceId, apply.Id)
if err != nil {
c.releaseModule.Delete(ctx, serviceId, newReleaseId)
return nil, err
}
return apply, err
}
func (c *imlPublishController) PublishStatuses(ctx *gin.Context, serviceId string, id string) ([]*dto.PublishStatus, error) {
return c.publishModule.PublishStatuses(ctx, serviceId, id)
}
func (c *imlPublishController) ApplyOnRelease(ctx *gin.Context, serviceId string, input *dto.ApplyOnReleaseInput) (*dto.Publish, error) {
newReleaseId, err := c.releaseModule.Create(ctx, serviceId, &dto2.CreateInput{
Version: input.Version,
Remark: input.VersionRemark,
})
if err != nil {
return nil, err
}
apply, err := c.publishModule.Apply(ctx, serviceId, &dto.ApplyInput{
Release: newReleaseId,
Remark: input.PublishRemark,
})
if err != nil {
return nil, err
}
return apply, nil
}
func (c *imlPublishController) Apply(ctx *gin.Context, serviceId string, input *dto.ApplyInput) (*dto.Publish, error) {
apply, err := c.publishModule.Apply(ctx, serviceId, input)
if err != nil {
return nil, err
}
return apply, nil
}
func (c *imlPublishController) CheckPublish(ctx *gin.Context, serviceId string, releaseId string) (*dto.DiffOut, error) {
return c.publishModule.CheckPublish(ctx, serviceId, releaseId)
}
func (c *imlPublishController) Close(ctx *gin.Context, serviceId string, id string) error {
err := c.publishModule.Stop(ctx, serviceId, id)
if err != nil {
return err
}
return nil
}
func (c *imlPublishController) Stop(ctx *gin.Context, serviceId string, id string) error {
return c.publishModule.Stop(ctx, serviceId, id)
}
func (c *imlPublishController) Refuse(ctx *gin.Context, serviceId string, id string, input *dto.Comments) error {
return c.publishModule.Refuse(ctx, serviceId, id, input.Comments)
}
func (c *imlPublishController) Accept(ctx *gin.Context, serviceId string, id string, input *dto.Comments) error {
return c.publishModule.Accept(ctx, serviceId, id, input.Comments)
}
func (c *imlPublishController) Publish(ctx *gin.Context, serviceId string, id string) error {
return c.publishModule.Publish(ctx, serviceId, id)
}
func (c *imlPublishController) ListPage(ctx *gin.Context, serviceId string, page, pageSize string) ([]*dto.Publish, int, int, int64, error) {
pageNum, _ := strconv.Atoi(page)
pageSizeNum, _ := strconv.Atoi(pageSize)
if pageNum < 1 {
pageNum = 1
}
if pageSizeNum <= 0 {
pageSizeNum = 50
}
list, total, err := c.publishModule.List(ctx, serviceId, pageNum, pageSizeNum)
if err != nil {
return nil, 0, 0, 0, err
}
return list, pageNum, pageSizeNum, total, nil
}
func (c *imlPublishController) Detail(ctx *gin.Context, serviceId string, id string) (*dto.PublishDetail, error) {
return c.publishModule.Detail(ctx, serviceId, id)
}
+34
View File
@@ -0,0 +1,34 @@
package publish
import (
"reflect"
"github.com/APIParkLab/APIPark/module/publish/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
var (
_ IPublishController = (*imlPublishController)(nil)
)
type IPublishController interface {
CheckPublish(ctx *gin.Context, serviceId string, releaseId string) (*dto.DiffOut, error)
ReleaseDo(ctx *gin.Context, serviceId string, input *dto.ApplyOnReleaseInput) (*dto.Publish, error)
ApplyOnRelease(ctx *gin.Context, serviceId string, input *dto.ApplyOnReleaseInput) (*dto.Publish, error)
Apply(ctx *gin.Context, serviceId string, input *dto.ApplyInput) (*dto.Publish, error)
Close(ctx *gin.Context, serviceId string, id string) error
Stop(ctx *gin.Context, serviceId string, id string) error
Refuse(ctx *gin.Context, serviceId string, id string, input *dto.Comments) error
Accept(ctx *gin.Context, serviceId string, id string, input *dto.Comments) error
Publish(ctx *gin.Context, serviceId string, id string) error
ListPage(ctx *gin.Context, serviceId string, page, pageSize string) ([]*dto.Publish, int, int, int64, error)
Detail(ctx *gin.Context, serviceId string, id string) (*dto.PublishDetail, error)
PublishStatuses(ctx *gin.Context, serviceId string, id string) ([]*dto.PublishStatus, error)
}
func init() {
autowire.Auto[IPublishController](func() reflect.Value {
return reflect.ValueOf(new(imlPublishController))
})
}
+44
View File
@@ -0,0 +1,44 @@
package release
import (
"github.com/APIParkLab/APIPark/module/release"
"github.com/APIParkLab/APIPark/module/release/dto"
service_diff "github.com/APIParkLab/APIPark/module/service-diff"
"github.com/gin-gonic/gin"
)
var (
_ IReleaseController = (*imlReleaseController)(nil)
)
type imlReleaseController struct {
module release.IReleaseModule `autowired:""`
diffModule service_diff.IServiceDiffModule `autowired:""`
}
func (c *imlReleaseController) Create(ctx *gin.Context, project string, input *dto.CreateInput) error {
_, err := c.module.Create(ctx, project, input)
return err
}
func (c *imlReleaseController) Delete(ctx *gin.Context, project string, id string) error {
return c.module.Delete(ctx, project, id)
}
func (c *imlReleaseController) Detail(ctx *gin.Context, project string, id string) (*dto.Detail, error) {
return c.module.Detail(ctx, project, id)
}
func (c *imlReleaseController) List(ctx *gin.Context, project string) ([]*dto.Release, error) {
return c.module.List(ctx, project)
}
func (c *imlReleaseController) Preview(ctx *gin.Context, project string) (*dto.Release, *service_diff.DiffOut, bool, error) {
releaseInfo, diff, complete, err := c.module.Preview(ctx, project)
if err != nil {
return nil, nil, false, err
}
out, err := c.diffModule.Out(ctx, diff)
if err != nil {
return nil, nil, false, err
}
return releaseInfo, out, complete, nil
}
+25
View File
@@ -0,0 +1,25 @@
package release
import (
"reflect"
service_diff "github.com/APIParkLab/APIPark/module/service-diff"
"github.com/APIParkLab/APIPark/module/release/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type IReleaseController interface {
Create(ctx *gin.Context, project string, input *dto.CreateInput) error
Delete(ctx *gin.Context, project string, id string) error
Detail(ctx *gin.Context, project string, id string) (*dto.Detail, error)
List(ctx *gin.Context, project string) ([]*dto.Release, error)
Preview(ctx *gin.Context, project string) (*dto.Release, *service_diff.DiffOut, bool, error)
}
func init() {
autowire.Auto[IReleaseController](func() reflect.Value {
return reflect.ValueOf(new(imlReleaseController))
})
}
+91
View File
@@ -0,0 +1,91 @@
package service
import (
"github.com/APIParkLab/APIPark/module/service"
service_dto "github.com/APIParkLab/APIPark/module/service/dto"
"github.com/gin-gonic/gin"
)
var (
_ IServiceController = (*imlServiceController)(nil)
_ IAppController = (*imlAppController)(nil)
)
type imlServiceController struct {
module service.IServiceModule `autowired:""`
}
func (i *imlServiceController) SearchMyServices(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.ServiceItem, error) {
return i.module.SearchMyServices(ctx, teamId, keyword)
}
func (i *imlServiceController) Simple(ctx *gin.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
return i.module.Simple(ctx, keyword)
}
func (i *imlServiceController) MySimple(ctx *gin.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
return i.module.MySimple(ctx, keyword)
}
func (i *imlServiceController) Get(ctx *gin.Context, id string) (*service_dto.Service, error) {
return i.module.Get(ctx, id)
}
func (i *imlServiceController) Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) {
return i.module.Search(ctx, teamID, keyword)
}
func (i *imlServiceController) Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) {
return i.module.Create(ctx, teamID, input)
}
func (i *imlServiceController) Edit(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) {
return i.module.Edit(ctx, id, input)
}
func (i *imlServiceController) Delete(ctx *gin.Context, id string) error {
return i.module.Delete(ctx, id)
}
func (i *imlServiceController) ServiceDoc(ctx *gin.Context, id string) (*service_dto.ServiceDoc, error) {
return i.module.ServiceDoc(ctx, id)
}
func (i *imlServiceController) SaveServiceDoc(ctx *gin.Context, id string, input *service_dto.SaveServiceDoc) error {
return i.module.SaveServiceDoc(ctx, id, input)
}
type imlAppController struct {
module service.IAppModule `autowired:""`
}
func (i *imlAppController) Search(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.AppItem, error) {
return i.module.Search(ctx, teamId, keyword)
}
func (i *imlAppController) CreateApp(ctx *gin.Context, teamID string, input *service_dto.CreateApp) (*service_dto.App, error) {
return i.module.CreateApp(ctx, teamID, input)
}
func (i *imlAppController) UpdateApp(ctx *gin.Context, appId string, input *service_dto.UpdateApp) (*service_dto.App, error) {
return i.module.UpdateApp(ctx, appId, input)
}
func (i *imlAppController) SearchMyApps(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.AppItem, error) {
return i.module.SearchMyApps(ctx, teamId, keyword)
}
func (i *imlAppController) SimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error) {
return i.module.SimpleApps(ctx, keyword)
}
func (i *imlAppController) MySimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error) {
return i.module.MySimpleApps(ctx, keyword)
}
func (i *imlAppController) GetApp(ctx *gin.Context, appId string) (*service_dto.App, error) {
return i.module.GetApp(ctx, appId)
}
func (i *imlAppController) DeleteApp(ctx *gin.Context, appId string) error {
return i.module.DeleteApp(ctx, appId)
}
+55
View File
@@ -0,0 +1,55 @@
package service
import (
"reflect"
service_dto "github.com/APIParkLab/APIPark/module/service/dto"
"github.com/gin-gonic/gin"
"github.com/eolinker/go-common/autowire"
)
type IServiceController interface {
// Get 获取
Get(ctx *gin.Context, id string) (*service_dto.Service, error)
// SearchMyServices 搜索服务
SearchMyServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
// Create 创建
Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error)
// Edit 编辑
Edit(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error)
// Delete 删除
Delete(ctx *gin.Context, id string) error
// Simple 获取简易列表
Simple(ctx *gin.Context, keyword string) ([]*service_dto.SimpleServiceItem, error)
// MySimple 获取我的简易列表
MySimple(ctx *gin.Context, keyword string) ([]*service_dto.SimpleServiceItem, error)
ServiceDoc(ctx *gin.Context, id string) (*service_dto.ServiceDoc, error)
SaveServiceDoc(ctx *gin.Context, id string, input *service_dto.SaveServiceDoc) error
}
type IAppController interface {
// CreateApp 创建应用
CreateApp(ctx *gin.Context, teamID string, project *service_dto.CreateApp) (*service_dto.App, error)
UpdateApp(ctx *gin.Context, appId string, project *service_dto.UpdateApp) (*service_dto.App, error)
Search(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.AppItem, error)
SearchMyApps(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.AppItem, error)
// SimpleApps 获取简易项目列表
SimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
MySimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
GetApp(ctx *gin.Context, appId string) (*service_dto.App, error)
DeleteApp(ctx *gin.Context, appId string) error
}
func init() {
autowire.Auto[IServiceController](func() reflect.Value {
return reflect.ValueOf(new(imlServiceController))
})
autowire.Auto[IAppController](func() reflect.Value {
return reflect.ValueOf(new(imlAppController))
})
}
+74
View File
@@ -0,0 +1,74 @@
package subscribe
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/APIParkLab/APIPark/module/subscribe"
subscribe_dto "github.com/APIParkLab/APIPark/module/subscribe/dto"
)
var (
_ ISubscribeController = (*imlSubscribeController)(nil)
)
type imlSubscribeController struct {
module subscribe.ISubscribeModule `autowired:""`
}
//func (i *imlSubscribeController) PartitionServices(ctx *gin.Context, app string) ([]*subscribe_dto.PartitionServiceItem, error) {
// return i.module.PartitionServices(ctx, app)
//}
func (i *imlSubscribeController) SearchSubscriptions(ctx *gin.Context, appId string, keyword string) ([]*subscribe_dto.SubscriptionItem, error) {
return i.module.SearchSubscriptions(ctx, appId, keyword)
}
func (i *imlSubscribeController) RevokeSubscription(ctx *gin.Context, service string, uuid string) error {
return i.module.RevokeSubscription(ctx, service, uuid)
}
func (i *imlSubscribeController) DeleteSubscription(ctx *gin.Context, service string, uuid string) error {
return i.module.DeleteSubscription(ctx, service, uuid)
}
func (i *imlSubscribeController) AddSubscriber(ctx *gin.Context, service string, input *subscribe_dto.AddSubscriber) error {
return i.module.AddSubscriber(ctx, service, input)
}
func (i *imlSubscribeController) DeleteSubscriber(ctx *gin.Context, service string, serviceId string, applicationId string) error {
return i.module.DeleteSubscriber(ctx, service, serviceId, applicationId)
}
func (i *imlSubscribeController) RevokeApply(ctx *gin.Context, service string, uuid string) error {
return i.module.RevokeApply(ctx, service, uuid)
}
func (i *imlSubscribeController) Search(ctx *gin.Context, service string, keyword string) ([]*subscribe_dto.Subscriber, error) {
return i.module.SearchSubscribers(ctx, service, keyword)
}
var _ ISubscribeApprovalController = (*imlSubscribeApprovalController)(nil)
type imlSubscribeApprovalController struct {
module subscribe.ISubscribeApprovalModule `autowired:""`
}
func (i *imlSubscribeApprovalController) GetApprovalList(ctx *gin.Context, service string, status int) ([]*subscribe_dto.ApprovalItem, error) {
return i.module.GetApprovalList(ctx, service, status)
}
func (i *imlSubscribeApprovalController) GetApprovalDetail(ctx *gin.Context, service string, id string) (*subscribe_dto.Approval, error) {
return i.module.GetApprovalDetail(ctx, service, id)
}
func (i *imlSubscribeApprovalController) Approval(ctx *gin.Context, service string, id string, approveInfo *subscribe_dto.Approve) error {
switch approveInfo.Operate {
case "pass":
return i.module.Pass(ctx, service, id, approveInfo)
case "refuse":
return i.module.Reject(ctx, service, id, approveInfo)
}
return fmt.Errorf("unknown operate: %s", approveInfo.Operate)
}
+47
View File
@@ -0,0 +1,47 @@
package subscribe
import (
"reflect"
"github.com/gin-gonic/gin"
subscribe_dto "github.com/APIParkLab/APIPark/module/subscribe/dto"
"github.com/eolinker/go-common/autowire"
)
type ISubscribeController interface {
// AddSubscriber 添加订阅者
AddSubscriber(ctx *gin.Context, project string, input *subscribe_dto.AddSubscriber) error
// DeleteSubscriber 删除订阅者
DeleteSubscriber(ctx *gin.Context, project string, serviceId string, applicationId string) error
// Search 关键字获取订阅者列表
Search(ctx *gin.Context, project string, keyword string) ([]*subscribe_dto.Subscriber, error)
// SearchSubscriptions 关键字获取订阅服务列表
SearchSubscriptions(ctx *gin.Context, appId string, keyword string) ([]*subscribe_dto.SubscriptionItem, error)
// RevokeSubscription 取消订阅
RevokeSubscription(ctx *gin.Context, project string, uuid string) error
// DeleteSubscription 删除订阅
DeleteSubscription(ctx *gin.Context, project string, uuid string) error
// RevokeApply 取消申请
RevokeApply(ctx *gin.Context, project string, uuid string) error
//PartitionServices(ctx *gin.Context, app string) ([]*subscribe_dto.PartitionServiceItem, error)
}
type ISubscribeApprovalController interface {
// GetApprovalList 获取审批列表
GetApprovalList(ctx *gin.Context, project string, status int) ([]*subscribe_dto.ApprovalItem, error)
// GetApprovalDetail 获取审批详情
GetApprovalDetail(ctx *gin.Context, project string, id string) (*subscribe_dto.Approval, error)
// Approval 审批
Approval(ctx *gin.Context, project string, id string, approveInfo *subscribe_dto.Approve) error
}
func init() {
autowire.Auto[ISubscribeController](func() reflect.Value {
return reflect.ValueOf(new(imlSubscribeController))
})
autowire.Auto[ISubscribeApprovalController](func() reflect.Value {
return reflect.ValueOf(new(imlSubscribeApprovalController))
})
}
+39
View File
@@ -0,0 +1,39 @@
package team_manager
import (
"github.com/APIParkLab/APIPark/module/team"
team_dto "github.com/APIParkLab/APIPark/module/team/dto"
"github.com/gin-gonic/gin"
)
var (
_ ITeamManagerController = (*imlTeamManagerController)(nil)
)
type imlTeamManagerController struct {
module team.ITeamModule `autowired:""`
}
func (c *imlTeamManagerController) GetTeam(ctx *gin.Context, id string) (*team_dto.Team, error) {
return c.module.GetTeam(ctx, id)
}
func (c *imlTeamManagerController) Search(ctx *gin.Context, keyword string) ([]*team_dto.Item, error) {
return c.module.Search(ctx, keyword)
}
func (c *imlTeamManagerController) CreateTeam(ctx *gin.Context, team *team_dto.CreateTeam) (*team_dto.Team, error) {
return c.module.Create(ctx, team)
}
func (c *imlTeamManagerController) EditTeam(ctx *gin.Context, id string, team *team_dto.EditTeam) (*team_dto.Team, error) {
return c.module.Edit(ctx, id, team)
}
func (c *imlTeamManagerController) DeleteTeam(ctx *gin.Context, id string) (string, error) {
err := c.module.Delete(ctx, id)
if err != nil {
return "", err
}
return id, nil
}
+23
View File
@@ -0,0 +1,23 @@
package team_manager
import (
team_dto "github.com/APIParkLab/APIPark/module/team/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
"reflect"
)
type ITeamManagerController interface {
// GetTeam 获取团队信息
GetTeam(ctx *gin.Context, id string) (*team_dto.Team, error)
Search(ctx *gin.Context, keyword string) ([]*team_dto.Item, error)
CreateTeam(ctx *gin.Context, team *team_dto.CreateTeam) (*team_dto.Team, error)
EditTeam(ctx *gin.Context, id string, team *team_dto.EditTeam) (*team_dto.Team, error)
DeleteTeam(ctx *gin.Context, id string) (string, error)
}
func init() {
autowire.Auto[ITeamManagerController](func() reflect.Value {
return reflect.ValueOf(new(imlTeamManagerController))
})
}
+27
View File
@@ -0,0 +1,27 @@
package upstream
import (
"github.com/APIParkLab/APIPark/module/cluster"
"github.com/APIParkLab/APIPark/module/service"
"github.com/APIParkLab/APIPark/module/upstream"
upstream_dto "github.com/APIParkLab/APIPark/module/upstream/dto"
"github.com/gin-gonic/gin"
)
var (
_ IUpstreamController = (*imlUpstreamController)(nil)
)
type imlUpstreamController struct {
upstreamModule upstream.IUpstreamModule `autowired:""`
projectModule service.IServiceModule `autowired:""`
partitionModule cluster.IClusterModule `autowired:""`
}
func (i *imlUpstreamController) Get(ctx *gin.Context, serviceId string) (upstream_dto.UpstreamConfig, error) {
return i.upstreamModule.Get(ctx, serviceId)
}
func (i *imlUpstreamController) Save(ctx *gin.Context, serviceId string, upstream *upstream_dto.UpstreamConfig) (upstream_dto.UpstreamConfig, error) {
return i.upstreamModule.Save(ctx, serviceId, *upstream)
}
+21
View File
@@ -0,0 +1,21 @@
package upstream
import (
"reflect"
"github.com/eolinker/go-common/autowire"
upstream_dto "github.com/APIParkLab/APIPark/module/upstream/dto"
"github.com/gin-gonic/gin"
)
type IUpstreamController interface {
Get(ctx *gin.Context, serviceId string) (upstream_dto.UpstreamConfig, error)
Save(ctx *gin.Context, serviceId string, upstream *upstream_dto.UpstreamConfig) (upstream_dto.UpstreamConfig, error)
}
func init() {
autowire.Auto[IUpstreamController](func() reflect.Value {
return reflect.ValueOf(new(imlUpstreamController))
})
}
+30
View File
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
market_dist
tenant_dist
*/.clinic
dist-ssr
*.local
packages/core/public/tinymce/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/pnpm-lock.yaml
+16
View File
@@ -0,0 +1,16 @@
<!--
* @Date: 2024-06-05 16:00:58
* @LastEditors: maggieyyy
* @LastEditTime: 2024-07-12 20:15:05
* @FilePath: \frontend\README.md
-->
# 部署
## 安装依赖
建议使用pnpm
`npm install -g pnpm`
使用pnpm安装依赖
`pnpm install`
## 编译
`pnpm run build`
+17
View File
@@ -0,0 +1,17 @@
# 部署
## 代码同步
packages目录下,部分子项目为企业版独有,不要同步到开源版:
packages/businessEntry, packages/dashboard, packages/openApi, packages/systemRunning, README.pro.md
## 安装依赖
建议使用pnpm
`npm install -g pnpm`
使用pnpm安装依赖
`pnpm install`
## 编译
### 开源版本
`pnpm run build`
### 企业版本
`pnpm run build:pro`
+108
View File
@@ -0,0 +1,108 @@
package frontend
import (
"embed"
_ "embed"
"fmt"
"io/fs"
"net/http"
"path"
"strings"
"time"
"github.com/eolinker/go-common/pm3"
"github.com/eolinker/go-common/server"
"github.com/gabriel-vasile/mimetype"
"github.com/gin-gonic/gin"
)
var (
//go:embed dist/favicon.ico
iconContent []byte
iconType string
//go:embed dist/vite.svg
viteContent []byte
viteContentType string
//go:embed dist
dist embed.FS
//go:embed dist/index.html
indexHtml []byte
)
var (
expires = time.Hour * 24 * 7
cacheControl = fmt.Sprintf("public, max-age=%d", 3600*24*7)
)
func AddExpires(ginCtx *gin.Context) {
ginCtx.Header("Expires", time.Now().Add(expires).UTC().Format(http.TimeFormat))
ginCtx.Header("Cache-Control", cacheControl)
}
func init() {
iconType = mimetype.Detect(iconContent).String()
viteContentType = mimetype.Detect(viteContent).String()
server.SetIndexHtmlHandler(IndexHtml)
server.AddSystemPlugin(new(Frontend))
}
func getFileSystem(dir string) http.FileSystem {
fDir, err := fs.Sub(dist, path.Join("dist", dir))
if err != nil {
panic(err)
}
return http.FS(fDir)
}
type Frontend struct {
}
func (f *Frontend) Middlewares() []pm3.IMiddleware {
return []pm3.IMiddleware{
pm3.CreateMiddle(func(method, path string) bool {
if method != http.MethodGet {
return false
}
if strings.HasPrefix(path, "/api") {
return false
}
return true
}, AddExpires, 0),
}
}
func (f *Frontend) Name() string {
return "baseFrontend"
}
func IndexHtml(ginCtx *gin.Context) {
AddExpires(ginCtx)
ginCtx.Header("Cache-Control", "no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate")
ginCtx.Data(http.StatusOK, "text/html; charset=utf-8", indexHtml)
}
func (f *Frontend) Api() []pm3.Api {
return []pm3.Api{
pm3.CreateApiSimple(http.MethodGet, "/favicon.ico", func(ginCtx *gin.Context) {
ginCtx.Data(http.StatusOK, iconType, iconContent)
}),
pm3.CreateApiSimple(http.MethodGet, "/vite.svg", func(ginCtx *gin.Context) {
ginCtx.Data(http.StatusOK, viteContentType, viteContent)
}),
}
}
func (f *Frontend) Files() []pm3.FrontendFiles {
return []pm3.FrontendFiles{
{
Path: "/assets/",
FileSystem: getFileSystem("assets"),
}, {
Path: "/tinymce/",
FileSystem: getFileSystem("tinymce"),
}, {
Path: "/frontend/",
FileSystem: getFileSystem("/"),
},
}
}
+18
View File
@@ -0,0 +1,18 @@
/*
* @Date: 2024-05-10 14:19:56
* @LastEditors: maggieyyy
* @LastEditTime: 2024-05-10 15:55:29
* @FilePath: \frontend\jest.config.js
*/
module.exports = {
roots: ['<rootDir>/packages'],
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
testEnvironment: 'jsdom',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};
+7
View File
@@ -0,0 +1,7 @@
/*
* @Date: 2024-05-10 14:22:41
* @LastEditors: maggieyyy
* @LastEditTime: 2024-05-10 15:49:31
* @FilePath: \frontend\jest.setup.js
*/
// import '@testing-library/jest-dom/extend-expect';
+6
View File
@@ -0,0 +1,6 @@
{
"packages": [
"packages/*"
],
"version": "independent"
}
+81
View File
@@ -0,0 +1,81 @@
{
"name": "frontend",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*"
],
"description": "",
"scripts": {
"test": "jest",
"build": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=core --stream --verbose ",
"build:pro": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=business-entry --stream --verbose ",
"serve": "lerna run preview --parallel",
"serve:remotes": "lerna run serve --scope=remote --parallel",
"dev": "lerna run dev --scope=core --stream",
"dev:pro": "lerna run dev --scope=business-entry --stream",
"stop": "kill-port --port 5000,5001"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@ant-design/pro-components": "2.7.9",
"@originjs/vite-plugin-federation": "^1.3.3",
"@rollup/plugin-dynamic-import-vars": "^2.1.2",
"@types/lodash-es": "^4.17.12",
"@types/uuid": "^9.0.7",
"@vitejs/plugin-react": "^4.2.0",
"autoprefixer": "^10.4.16",
"dayjs": "^1.11.10",
"js-base64": "^3.7.5",
"moment": "^2.29.4",
"postcss": "^8.4.31",
"postcss-import": "^16.1.0",
"postcss-nesting": "^12.1.5",
"react": "^18.2.0",
"react-ace": "^10.1.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.0",
"tailwindcss": "^3.3.5",
"uuid": "^9.0.1",
"vite-tsconfig-paths": "^4.3.2"
},
"devDependencies": {
"@ant-design/cssinjs": "^1.18.2",
"@antv/g6": "^4.8.24",
"@iconify/react": "^5.0.2",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@testing-library/react-hooks": "^8.0.1",
"@types/file-saver": "^2.0.7",
"@types/jest": "^29.5.12",
"@types/node": "^20.10.5",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@vitejs/plugin-react": "^4.2.0",
"antd": "^5.19.4",
"babel-jest": "^29.7.0",
"eslint": "^8.53.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"file-saver": "^2.0.5",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-fetch-mock": "^3.0.3",
"jsdom": "^24.0.0",
"lerna": "^8.1.3",
"less": "^4.2.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"postcss-nested": "^6.0.1",
"react-test-renderer": "^18.3.1",
"ts-jest": "^29.1.2",
"typescript": "^5.2.2",
"vite": "^5.0.0",
"vite-jest": "^0.1.4"
}
}
+5
View File
@@ -0,0 +1,5 @@
// .env.pro
VITE_APP_MODE=pro
VITE_APP_TITLE=My Production App
VITE_API_BASE_URL=https://api.production.example.com
@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs','public','code-snippet','ace-editor'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
@@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
public/tinymce
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+11
View File
@@ -0,0 +1,11 @@
# `businessEntry`
> TODO: description
## Usage
```
const businessEntry = require('businessEntry');
// TODO: DEMONSTRATE API
```
@@ -0,0 +1,7 @@
'use strict';
const businessEntry = require('..');
const assert = require('assert').strict;
assert.strictEqual(businessEntry(), 'Hello from businessEntry');
console.info('businessEntry tests passed');
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/frontend/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>APIPark - 企业API数据开放平台</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script src="/frontend/iconpark_eolink.js"></script>
<script src="/frontend/iconpark_apinto.js"></script>
</body>
</html>
@@ -0,0 +1,17 @@
{
"name": "business-entry",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": " vite --port 5000 --strictPort",
"build": "vite build ",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview --port 5000 --strictPort",
"serve": "vite preview --port 5000 --strictPort"
},
"dependencies": {
},
"devDependencies": {
}
}
@@ -0,0 +1,15 @@
/*
* @Date: 2023-11-27 17:31:54
* @LastEditors: maggieyyy
* @LastEditTime: 2024-06-05 10:42:18
* @FilePath: \frontend\packages\core\postcss.config.js
*/
export default {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {}
},
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+158
View File
@@ -0,0 +1,158 @@
import '@core/App.css'
import { ConfigProvider } from 'antd';
import RenderRoutes from '@businessEntry/components/aoplatform/RenderRoutes';
import {BreadcrumbProvider} from "@common/contexts/BreadcrumbContext.tsx";
import { StyleProvider } from '@ant-design/cssinjs';
import zhCN from 'antd/locale/zh_CN';
import useInitializeMonaco from "@common/hooks/useInitializeMonaco";
import ThemeSwitcher from '@common/components/aoplatform/ThemeSwitcher'
const antdComponentThemeToken = {
token: {
// Seed Token,影响范围大
colorPrimary: '#3D46F2',
colorLink:'#3D46F2',
colorBorder:'#ededed',
colorText:'#333',
borderRadius: 4,
// 派生变量,影响范围小
colorBgContainer: '#fff',
colorPrimaryBg:'#EBEEF2',
colorTextQuaternary:'#BBB',
colorTextTertiary:'#999'
},
components:{
// 派生变量,影响范围小
Input:{
activeShadow:'none'
},
Select:{
activeShadow:'none'
},
Checkbox:{
activeShadow:'none'
},
Cascader:{
activeShadow:'none',
optionSelectedBg:'#EBEEF2',
optionHoverBg:'#EBEEF2'
},
Layout: {
bodyBg: '#17163E',
headerBg: 'transparent',
headerColor: '#333',
headerPadding: '10 20px',
lightSiderBg: 'transparent',
siderBg: 'transparent',
},
Breadcrumb:{
itemColor:'#666',
linkColor:'#666',
lastItemColor:'#333',
},
Table:{
headerBorderRadius:0,
headerSplitColor:'#ededed',
borderColor:'#ededed',
cellPaddingBlockMD:'10px',
cellPaddingInlineMD:'12px',
cellPaddingBlockSM:'8px',
cellPaddingInlineSM:'12px',
headerFilterHoverBg:'#EBEEF2',
headerSortActiveBg:'#F7F8FA',
headerSortHoverBg:'#F7F8FA',
fixedHeaderSortActiveBg:'#F7F8FA',
headerBg:'#F7F8FA',
rowHoverBg:'#EBEEF2'
},
Segmented:{
itemColor:'#333',
itemSelectedColor:'#333',
trackBg:'#f7f8fa',
trackPadding:0,
// itemHoverColor:'#EBEEF2',
itemActiveBg:'#EBEEF2',
itemHoverBg:'#EBEEF2',
itemSelectedBg:'#EBEEF2',
},
Tree:{
// titleHeight:30,
// fontSize:12,
directoryNodeSelectedBg:'#EBEEF2',
directoryNodeSelectedColor:'#333',
nodeSelectedBg:'#EBEEF2',
nodeHoverBg:'#EBEEF2'
},
Collapse:{
headerBg:'#f7f8fa',
headerPadding:"12px",
contentPadding:"0 10px 12px 10px"
},
Button:{
// paddingInline:8,
dangerShadow:'none',
defaultShadow:'none',
primaryShadow:'none'
},
Tabs:{
cardBg:'#EBEEF2',
cardHeight:42,
horizontalItemGutter:8,
horizontalItemPaddingSM:'12px 8px 8px 8px',
horizontalItemPadding:'12px 8px 8px 8px',
},
Menu:{
// itemBg:'#F7F8FA',
// subMenuItemBg:'#F7F8FA',
// itemMarginBlock:0,
// activeBarBorderWidth:0,
// itemSelectedColor:'#333',
// itemSelectedBg:'#EBEEF2',
// itemHoverBg:'#EBEEF2'
// itemHeight:'72px',
// darkItemBg:'transparent',
// itemBg:'transparent',
// itemSelectedBg:'transparent',
// darkItemSelectedBg:'transparent',
// subMenuItemBg:'transparent',
// itemActiveBg:'transparent',
// darkSubMenuItemBg:'transparent',
// activeBarHeight:'2px',
// activeBarBorderWidth:2
},
List:{
itemPadding:'8px 0'
},
Form:{
itemMarginBottom:10,
},
Alert:{
defaultPadding:'12px 16px'
},
Tag:{
defaultBg:"#f7f8fa"
},
}
}
function App() {
useInitializeMonaco()
return (
<StyleProvider hashPriority={"high"}>
<ConfigProvider
locale={zhCN}
wave={{disabled:true}}
theme={antdComponentThemeToken}>
<ThemeSwitcher />
<BreadcrumbProvider>
<RenderRoutes />
</BreadcrumbProvider>
</ConfigProvider>
</StyleProvider>
);
}
export default App
@@ -0,0 +1,471 @@
import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from 'react-router-dom';
import Login from "@core/pages/Login.tsx"
import BasicLayout from '@common/components/aoplatform/BasicLayout';
import {createElement, ReactElement,ReactNode,Suspense} from 'react';
import { v4 as uuidv4 } from 'uuid'
import {App, Skeleton} from "antd";
import ApprovalPage from "@core/pages/approval/ApprovalPage.tsx";
import {SystemProvider} from "@core/contexts/SystemContext.tsx";
import {useGlobalContext} from "@common/contexts/GlobalStateContext.tsx";
import {FC,lazy} from 'react';
import { TeamProvider } from '@core/contexts/TeamContext.tsx';
import SystemOutlet from '@core/pages/system/SystemOutlet.tsx';
import { DashboardProvider } from '@core/contexts/DashboardContext.tsx';
import { PartitionProvider } from '@core/contexts/PartitionContext.tsx';
import { TenantManagementProvider } from '@market/contexts/TenantManagementContext.tsx';
type RouteConfig = {
path:string
component?:ReactElement
children?:(RouteConfig|false)[]
key:string
provider?:FC<{ children: ReactNode; }>
lazy?:unknown
}
const APP_MODE = import.meta.env.VITE_APP_MODE;
export type RouterParams = {
teamId:string
apiId:string
serviceId:string
clusterId:string;
memberGroupId:string
userGroupId:string
pluginName:string
moduleId:string
accessType:'project'|'team'|'service'
categoryId:string
tagId:string
dashboardType:string
dashboardDetailId:string
topologyId:string
appId:string
roleType:string
roleId:string
}
const PUBLIC_ROUTES:RouteConfig[] = [
{
path:'/',
component:<Login/>,
key: uuidv4(),
},
{
path:'/login',
component:<Login/>,
key: uuidv4()
},
{
path:'/',
component:<ProtectedRoute/>,
key: uuidv4(),
children:[
{
path:'approval/*',
component:<ApprovalPage />,
key:uuidv4()
},
{
path:'team',
component:<Outlet/>,
key: uuidv4(),
provider: TeamProvider,
children:[
{
path:'',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path:'list',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamList.tsx'))
},
{
path:'inside/:teamId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsidePage.tsx')),
key: uuidv4(),
children:[
{
path:'member',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsideMember.tsx')),
},
{
path:'setting',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamConfig.tsx')),
},
]
}
]
},
{
path:'service',
component:<SystemOutlet />,
key: uuidv4(),
provider: SystemProvider,
children:[
{
path:'',
key:uuidv4(),
component:<Navigate to="list" />
},
{
path:'list',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
},
{
path:'list/:teamId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
},
{
path:':teamId',
component:<Outlet/>,
key: uuidv4(),
children:[
{
path:'inside/:serviceId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsidePage.tsx')),
children:[
{
path:'api',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiList.tsx')),
},
{
path:'upstream',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/upstream/SystemInsideUpstreamContent.tsx')),
},
{
path:'document',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideDocument.tsx')),
},
{
path:'subscriber',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideSubscriber.tsx')),
children:[
]
},
{
path:'approval',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApproval.tsx')),
children:[
{
path:'',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
},
{
path:'*',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
}
]
},
{
path:'topology',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemTopology.tsx')),
key: uuidv4(),
children:[
]
},
{
path:'publish',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublish.tsx')),
children:[
{
path:'*',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
}
]
},
{
path:'setting',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemConfig.tsx')),
children:[
]
},
]
}
]
}
]
},
{
path:'cluster',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCluster.tsx')),
},
{
path:'cert',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCert.tsx')),
},
{
path:'serviceHub',
component:<Outlet />,
key:uuidv4(),
children:[
{
path:'',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubList.tsx')),
},
{
path:'detail/:serviceId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubDetail.tsx')),
}]
},
{
path:'servicecategories',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceCategory/ServiceCategory.tsx')),
key:uuidv4(),
},
{
path:'tenantManagement',
component:<Outlet />,
provider:TenantManagementProvider,
key:uuidv4(),
children:[
{
path:'',
key:uuidv4(),
component:<Navigate to="list" />
},
{
path:':teamId/inside/:appId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsidePage.tsx')),
children:[
{
path:'service',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideService.tsx')),
},
{
path:'authorization',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideAuth.tsx')),
},
{
path:'setting',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementAppSetting.tsx')),
},
]
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
},
{
path:'list/:teamId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
},
]
},
{
path:'member',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberPage.tsx')),
children:[
{
path:'',
key:uuidv4(),
component:<Navigate to="list" />
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
},
{
path:'list/:memberGroupId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
}
]
},
{
path:'role',
key:uuidv4(),
component:<Outlet></Outlet>,
children:[
{
path: '',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleList.tsx')),
},
{
path:':roleType/config',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
},
{
path:':roleType/config/:roleId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
}
]
},
APP_MODE === 'pro' &&{
path:'openapi',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@openApi/pages/OpenApiList.tsx')),
key:uuidv4(),
},
{
path:'logretrieval',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/logRetrieval/LogRetrieval.tsx')),
key:uuidv4(),
},
{
path:'auditlog',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/auditLog/AuditLog.tsx')),
key:uuidv4(),
},
{
path:'assets',
component:<p></p>,
key:uuidv4()
},
APP_MODE === 'pro' &&{
path:'dashboard',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/Dashboard.tsx')),
key:uuidv4(),
children:[
{
path:':dashboardType',
component:<Outlet/>,
key:uuidv4(),
provider:DashboardProvider,
children:[
{
path:'list',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardList.tsx')),
key:uuidv4()
},
{
path:'detail/:dashboardDetailId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardDetail.tsx')),
key:uuidv4()
},
]
},
]
},
{
path:'systemrunning',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@systemRunning/pages/SystemRunning.tsx')),
key:uuidv4()
},
{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '../../../../common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
},
{
path:'logsettings/*',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/logsettings/LogSettings.tsx')),
key: uuidv4(),
children:[{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '../../../../common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
}]
},
APP_MODE ==='pro' && {
path:'resourcesettings/*',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/resourcesettings/ResourceSettings.tsx')),
key: uuidv4(),
children:[{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '../../../../common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
}]
}
]
},
]
const RenderRoutes = ()=> {
return (
<App className="h-full" message={{ maxCount: 1 }}>
<Router>
<Routes>
{generateRoutes(PUBLIC_ROUTES)}
</Routes>
</Router>
</App>
)
}
const generateRoutes = (routerConfig: RouteConfig[]) => {
return routerConfig?.map((route: RouteConfig) => {
let routeElement;
if (route.lazy) {
const LazyComponent = route.lazy as React.ExoticComponent<unknown>;
routeElement = (
<Suspense fallback={ <div className=''><Skeleton className='m-btnbase w-[calc(100%-20px)]' active /></div>}>
{route.provider ? (
createElement(route.provider, {}, <LazyComponent />)
) : (
<LazyComponent />
)}
</Suspense>
);
} else {
routeElement = route.provider ? (
createElement(route.provider, {}, route.component)
) : (
route.component
);
}
return (
<Route
key={route.key}
path={route.path}
element={routeElement}
>
{route.children && generateRoutes(route.children as RouteConfig[])}
</Route>
);
}
)
}
// 保护的路由组件
function ProtectedRoute() {
const {state} = useGlobalContext()
return state.isAuthenticated? <BasicLayout project="core" /> : <Navigate to="/login" />;
}
export default RenderRoutes
@@ -0,0 +1,27 @@
import {StrictMode} from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import '@core/index.css'
import {GlobalProvider} from "@common/contexts/GlobalStateContext.tsx";
async function initializeApp() {
try {
// 初始化行为
// await fetchInitialConfig(); // 示例:获取初始配置
// 异步操作完成后,渲染React应用
ReactDOM.createRoot(document.getElementById('root')!).render(
<StrictMode>
<GlobalProvider>
<App />
</GlobalProvider>
</StrictMode>,
);
} catch (error) {
console.error('Initialization failed:', error);
// 处理初始化失败的情况,比如渲染一个错误界面
}
}
// 执行初始化
initializeApp();
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
@@ -0,0 +1,22 @@
/*
* @Date: 2024-06-05 09:35:25
* @LastEditors: maggieyyy
* @LastEditTime: 2024-06-05 10:50:12
* @FilePath: \frontend\packages\core\start-vite.js
*/
// start-vite.js// start-vite.js
import { exec } from 'child_process';
const viteProcess = exec('pnpm run build');
viteProcess.stdout.on('data', (data) => {
console.log(data.toString());
});
viteProcess.stderr.on('data', (data) => {
console.error(data.toString());
});
viteProcess.on('close', (code) => {
console.log(`Vite process exited with code ${code}`);
});
@@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"paths": {
"@core/*": ["../core/src/*"],
"@common/*": ["../common/src/*"],
"@market/*": ["../market/src/*"],
"@dashboard/*": ["../dashboard/src/*"],
"@openApi/*": ["../openApi/src/*"],
"@systemRunning/*": ["../systemRunning/src/*"],
"@businessEntry/*": ["./src/*"],
},
},
"include": ["src", "public/iconpark_eolink.js", "public/iconpark_apinto.js", "../common/src/component/aoplatform/EditableTableWithModal.tsx", "../common/src/components/aoplatform/TransferTable.tsx", "../common/src/components/aoplatform/TreeWithMore.tsx", "../common/src/components/aoplatform/DatePicker.tsx", "../common/src/components/aoplatform/TimeRangeSelector.tsx", "../common/src/components/aoplatform/TimePicker.tsx", "../common/src/components/aoplatform/MemberTransfer.tsx", "../common/src/components/aoplatform/Navigation.tsx", "../common/src/components/aoplatform/PageList.tsx", "../common/src/components/aoplatform/GroupTree.tsx", "../common/src/components/aoplatform/ErrorBoundary.tsx", "../common/src/components/aoplatform/ScrollableSection.tsx", "../common/src/utils/postcat.tsx", "../common/src/utils/curl.ts", "../common/src/components/aoplatform/ResetPsw.tsx", "../common/src/components/aoplatform/SubscribeApprovalModalContent.tsx", "../common/src/components/aoplatform/InsidePageForHub.tsx", "src/components/aoplatform/RenderRoutes.tsx", "../common/src/components/aoplatform/PublishApprovalModalContent.tsx", "../common/src/components/aoplatform/InsidePage.tsx", "../common/src/const/type.ts", "../common/src/components/aoplatform/intelligent-plugin", "../common/src/const/domain"],
"references": [{ "path": "./tsconfig.node.json" }]
}
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
@@ -0,0 +1,80 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
import tailwindcss from 'tailwindcss';
import autoprefixer from 'autoprefixer';
export default defineConfig({
cacheDir: './node_modules/.vite',
build:{
outDir:'../../dist',
sourcemap: false,
chunkSizeWarningLimit: 50000,
cacheDir: './node_modules/.vite',
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
// 针对 pnpm 和 Monorepo 特殊处理
if (id.includes('.pnpm')) {
const segments = id.split(path.sep);
const packageName = segments[segments.indexOf('.pnpm') + 1].split('@')[0];
return packageName;
}
}
},
},
css: {
postcss: {
plugins: [
tailwindcss(path.resolve(__dirname, '../common/tailwind.config.js')),
autoprefixer
],
},
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
modules:{
localsConvention:"camelCase",
generateScopedName:"[local]_[hash:base64:2]"
}
},
plugins: [react(),
dynamicImportVars({
include:["src"],
exclude:[],
warnOnError:false
}),
],
resolve: {
alias: [
{ find: /^~/, replacement: '' },
{ find: '@common', replacement: path.resolve(__dirname, '../common/src') },
{ find: '@market', replacement: path.resolve(__dirname, '../market/src') },
{ find: '@core', replacement: path.resolve(__dirname, '../core/src') },
{ find: '@dashboard', replacement: path.resolve(__dirname, '../dashboard/src') },
{ find: '@openApi', replacement: path.resolve(__dirname, '../openApi/src') },
{ find: '@systemRunning', replacement: path.resolve(__dirname, '../systemRunning/src') },
{ find: '@businessEntry', replacement: path.resolve(__dirname, './src') },
]
},
server: {
proxy: {
'/api/v1': {
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
target: 'http://172.18.166.219:8288/',
changeOrigin: true,
},
'/api2/v1': {
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
target: 'http://172.18.166.219:8288/',
changeOrigin: true,
}
}
},
logLevel:'info'
})
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
# `common`
> TODO: description
## Usage
```
const common = require('common');
// TODO: DEMONSTRATE API
```
@@ -0,0 +1,7 @@
'use strict';
const common = require('..');
const assert = require('assert').strict;
assert.strictEqual(common(), 'Hello from common');
console.info('common tests passed');
+32
View File
@@ -0,0 +1,32 @@
{
"name": "common",
"version": "1.0.0",
"description": "Common library for AO Platform",
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "node ./__tests__/common.test.js"
},
"dependencies": {
"@formkit/auto-animate": "^0.8.1",
"@mui/icons-material": "^5.15.6",
"@mui/lab": "5.0.0-alpha.150",
"@mui/material": "5.14.14",
"@mui/x-data-grid-pro": "6.18.1",
"allotment": "^1.20.0",
"echarts": "^5.5.0",
"mockjs": "^1.1.0",
"rc-picker": "^4.1.1",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.49.3"
},
"devDependencies": {
"@formily/antd-v5": "^1.2.1",
"@formily/core": "^2.2.13",
"@formily/react": "^2.2.13",
"@formily/reactive": "^2.2.13",
"@monaco-editor/react": "^4.6.0",
"exceljs": "^4.4.0",
"monaco-editor": "^0.45.0"
}
}
@@ -0,0 +1,15 @@
/*
* @Date: 2023-11-27 17:31:54
* @LastEditors: maggieyyy
* @LastEditTime: 2023-11-29 15:49:05
* @FilePath: \applatform\frontend\packages\core\postcss.config.js
*/
export default {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {}
},
}
@@ -0,0 +1,17 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_652_44370)">
<path d="M26.9998 0.501787H27.0086L27.0174 0.501479C27.3465 0.489919 27.6745 0.546231 27.981 0.66691C28.2875 0.78759 28.5658 0.970053 28.7987 1.20294C29.0316 1.43583 29.214 1.71417 29.3347 2.02062C29.4554 2.32707 29.5117 2.65508 29.5001 2.98424L29.4998 2.99301V3.00179V27.0018V27.0106L29.5001 27.0193C29.5117 27.3485 29.4554 27.6765 29.3347 27.983C29.214 28.2894 29.0316 28.5677 28.7987 28.8006C28.5658 29.0335 28.2875 29.216 27.981 29.3367C27.6745 29.4573 27.3465 29.5137 27.0174 29.5021L27.0086 29.5018H26.9998H2.99983H2.99106L2.98228 29.5021C2.65313 29.5137 2.32512 29.4573 2.01867 29.3367C1.71221 29.216 1.43388 29.0335 1.20099 28.8006C0.9681 28.5677 0.785636 28.2894 0.664957 27.983C0.544278 27.6765 0.487966 27.3485 0.499526 27.0193L0.499834 27.0106V27.0018V3.00179V2.99301L0.499526 2.98424C0.487966 2.65508 0.544278 2.32707 0.664957 2.02062C0.785636 1.71417 0.9681 1.43583 1.20099 1.20294C1.43388 0.970053 1.71221 0.787589 2.01867 0.66691C2.32512 0.546231 2.65313 0.489919 2.98228 0.501479L2.99106 0.501787H2.99983H26.9998Z" fill="#E8EFFE" stroke="#E8EFFE"/>
<rect x="12.2227" y="23.6289" width="5.53122" height="4.14832" fill="#F9D6C2"/>
<ellipse cx="22.248" cy="17.0625" rx="1.0371" ry="1.72847" fill="#FFE6D8"/>
<ellipse cx="7.72851" cy="17.0625" rx="1.0371" ry="1.72847" fill="#FFE6D8"/>
<ellipse cx="14.9882" cy="16.7166" rx="7.60543" ry="8.29663" fill="#FFE6D8"/>
<path d="M10 11.5C8.5 11.5 8.30468 13.7205 8.07421 15.3337L7.72851 10.494C7.26757 10.494 6.3457 10.0792 6.3457 8.41985C6.3457 6.76052 7.03711 6.34569 7.38281 6.34569C9.11131 6.23046 13.1214 6 15.3339 6C18.0995 6 19.1366 6 20.1738 7.38277C21.0034 8.48899 20.289 9.91786 19.8281 10.494C16.832 10.494 12.3662 11.5 10 11.5Z" fill="#333333" stroke="#333333" stroke-width="0.691394"/>
<path d="M21.9023 9.11089C23.2851 9.94055 22.709 13.144 22.248 14.642L19.1367 9.8028C19.252 9.57234 20.5195 8.28123 21.9023 9.11089Z" fill="#333333" stroke="#333333" stroke-width="0.691394"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9891 27.7778C16.5165 27.7778 17.7547 26.8492 17.7547 25.7036C17.7547 25.536 17.7282 25.3729 17.6781 25.2168C21.3287 25.7883 23.9774 27.4926 23.9774 29.5063C23.9774 31.9882 19.9533 34.0003 14.9892 34.0003C10.0251 34.0003 6.00098 31.9882 6.00098 29.5063C6.00098 27.4926 8.64969 25.7883 12.3001 25.2168C12.2501 25.373 12.2235 25.536 12.2235 25.7036C12.2235 26.8492 13.4617 27.7778 14.9891 27.7778Z" fill="#1861F2"/>
</g>
<defs>
<clipPath id="clip0_652_44370">
<rect width="30" height="30" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

@@ -0,0 +1,271 @@
import {
ConfigProvider,
Dropdown,
MenuProps,
App} from 'antd';
import Logo from '@common/assets/layout-logo.png';
import AvatarPic from '@common/assets/avatar_default.svg'
import { routerKeyMap, TOTAL_MENU_ITEMS } from "./Navigation";
import {Outlet, useLocation, useNavigate} from "react-router-dom";
import {useEffect, useMemo, useRef, useState} from "react";
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx';
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts';
import {
ProConfigProvider,
ProLayout,
} from '@ant-design/pro-components';
import { UserProfile } from './UserProfile.tsx';
import { ResetPsw, ResetPswHandle } from './ResetPsw.tsx';
import { BasicResponse, STATUS_CODE } from '@common/const/const.ts';
import { UserInfoType, UserProfileHandle } from '@common/const/type.ts';
import { useFetch } from '@common/hooks/http.ts';
const themeToken = {
bgLayout:'#17163E;',
header: {
heightLayoutHeader:72
},
pageContainer:{
paddingBlockPageContainerContent:0,
paddingInlinePageContainerContent:0,
}
}
function BasicLayout({project = 'core'}:{project:string}){
const navigator = useNavigate()
const location = useLocation()
const currentUrl = location.pathname
const { accessData,checkPermission} = useGlobalContext()
const [pathname, setPathname] = useState(currentUrl);
const mainPage = project === 'core' ?'/service/list':'/serviceHub/list'
useEffect(() => {
if(currentUrl === '/'){
navigator(mainPage)
}
}, [currentUrl]);
const headerMenuData = useMemo(() => {
// 判断权限
const hasAccess = (access: unknown) => checkPermission(access as keyof typeof PERMISSION_DEFINITION[0]);
// 过滤菜单项
const filterMenu = (menu: Array<{ [k: string]: unknown }>) => {
return [...menu]
.filter(x => x) // 过滤掉空数据
.map((item: any) => {
if (item.routes && item.routes.length > 0) {
// 递归处理子菜单
const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes);
if(filteredRoutes.length === 0){
return false
}
return {...item, routes: filteredRoutes};
}
// 处理没有 routes 的菜单项
if (item.access) {
return hasAccess(item.access) ? item : null;
}
// 如果没有 access 和 routes,则保留
return item;
})
.filter(x => x); // 过滤掉处理后为 null 的项
};
// 初始过滤操作
const res = [...TOTAL_MENU_ITEMS]!.filter(x => x).map((x: any) => (x.routes ? { ...x, routes: filterMenu(x.routes) } : x));
// 返回处理后的数据
return { path: '/', routes: res.map(x=> ({...x, routes: x.routes?.filter(x=> (x.access || x.routes?.length > 0))})).filter(x=> (x.access || x.routes?.length > 0)) };
}, [accessData]);
const { modal,message } = App.useApp()
const { dispatch,resetAccess,getGlobalAccessData} = useGlobalContext()
const [userInfo,setUserInfo] = useState<UserInfoType>()
const resetPswRef = useRef<ResetPswHandle>(null)
const userProfileRef = useRef<UserProfileHandle>(null)
const {fetchData} = useFetch()
const navigate = useNavigate();
const getUserInfo = ()=>{
fetchData<BasicResponse<{profile:UserInfoType}>>('account/profile',{method:'GET'})
.then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setUserInfo(data.profile)
dispatch({type:'UPDATE_USERDATA',userData:data.profile})
}else{
message.error(msg || '操作失败')
}
})
}
useEffect(() => {
getUserInfo()
getGlobalAccessData()
}, []);
const logOut = ()=>{
fetchData<BasicResponse<null>>('account/logout',{method:'GET'}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
dispatch({type:'LOGOUT'})
resetAccess()
message.success(msg || '退出成功,将跳转至登录页')
navigate('/login')
}else{
message.error(msg ||'操作失败')
}
})
}
const items: MenuProps['items'] = [
{
key: '3',
label: (
<a className="block px-btnbase leading-[32px]" target="_blank" rel="noopener noreferrer" onClick={logOut}>
退
</a>
),
},
];
const openModal = (type:'userSetting'|'resetPsw')=>{
let title:string = ''
let content:string|React.ReactNode = ''
switch (type){
case 'userSetting':
title='用户设置'
content=<UserProfile ref={userProfileRef} entity={userInfo}/>
break;
case 'resetPsw':
title='重置密码'
content=<ResetPsw ref={resetPswRef} entity={userInfo} />
break;
}
modal.confirm({
title,
content,
onOk:()=>{
switch (type){
case 'userSetting':
return userProfileRef.current?.save().then((res)=>{if(res === true) getUserInfo()})
case 'resetPsw':
return resetPswRef.current?.save().then((res)=>{if(res === true) logOut()})
}
},
width:600,
okText:'确认',
cancelText:'取消',
closable:true,
icon:<></>,
})
}
return(
<div
id="test-pro-layout"
style={{
height: '100vh',
overflow: 'auto',
}}
>
<ProConfigProvider hashed={false}>
<ConfigProvider
getTargetContainer={() => {
return document.getElementById('test-pro-layout') || document.body;
}}
>
<ProLayout
prefixCls="apipark-layout"
location={{
pathname,
}}
siderWidth={220}
breakpoint={'lg'}
route={headerMenuData}
token={themeToken}
siderMenuType="group"
menu={{
type: 'group',
collapsedShowGroupTitle: true,
}}
disableMobile={true}
avatarProps={{
src: AvatarPic || userInfo?.avatar,
size: 'small',
title: userInfo?.username||'unknown',
render: (props, dom) => {
return (
<Dropdown
menu={{
items
}}
>
<div className='avatar-dom'>{dom}
</div>
</Dropdown>
);
},
}}
// actionsRender={(props) => {
// if (props.isMobile) return [];
// if (typeof window === 'undefined') return [];
// return [
// <Button className="mr-[20px]">
// <span className='flex items-center'><QuestionCircleOutlined className="mr-[4px]" />帮助文档</span>
// </Button>
// ];
// }}
headerTitleRender={() => (
<div className="w-[192px] flex items-center">
<img
className="h-[20px] cursor-pointer"
src={Logo}
onClick={()=> navigator(mainPage)}
/>
</div>
)}
logo={Logo}
pageTitleRender={()=>'APIPark - 企业API数据开放平台'}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
}}
menuItemRender={(item, dom) => (
<div
onClick={() => {
// 同级目录点击无效
if(item.key && routerKeyMap.get(item.key) && routerKeyMap.get(item.key).length > 0 && routerKeyMap.get(item.key)?.indexOf(pathname.split('/')[1]) !== -1){
return
}
if(item.key === pathname.split('/')[1]){
return
}
if(item.path){
navigator(item.path)
}
setPathname(item.path || '');
}}
>
{dom}
</div>
)}
fixSiderbar={true}
layout='mix'
splitMenus={true}
collapsed={false}
collapsedButtonRender={false}
>
<div className={`w-full h-calc-100vh-minus-navbar px-[40px] pt-[30px] ${currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden' }`}>
<Outlet />
</div>
</ProLayout>
</ConfigProvider>
</ProConfigProvider>
</div>
)
}
export default BasicLayout
@@ -0,0 +1,15 @@
import { Breadcrumb } from "antd"
import { useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
import {FC,useEffect} from "react";
const TopBreadcrumb: FC = () => {
const { breadcrumb } = useBreadcrumb()
useEffect(() => {
}, [breadcrumb]);
return (
<Breadcrumb items={breadcrumb} />
)
}
export default TopBreadcrumb
@@ -0,0 +1,124 @@
import { FC } from 'react';
import { Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
interface DataType {
httpStatusCode: string;
systemStatusCode: string;
description: string;
}
const columns: ColumnsType<DataType> = [
{
title: 'HTTP 状态码',
dataIndex: 'httpStatusCode',
key: 'httpStatusCode',
},
{
title: '系统状态码',
dataIndex: 'systemStatusCode',
key: 'systemStatusCode',
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
ellipsis:true
},
];
const data: DataType[] = [
// {
// httpStatusCode: '416',
// systemStatusCode: '10001',
// description: '尚未购买该 API 或 API 调用次数已用完',
// },
// {
// httpStatusCode: '401',
// systemStatusCode: '10002',
// description: 'Header 参数中找不到 X-APISpace-Token 或 X-APISpace-Token 非法',
// },
{
httpStatusCode: '413',
systemStatusCode: '10003',
description: '请求频率过高',
},
{
httpStatusCode: '403',
systemStatusCode: '10004',
description: '请求来源非法,不在白名单中',
},
// {
// httpStatusCode: '416',
// systemStatusCode: '10005',
// description: '该接口超 90 天未完成企业认证,请尽快于平台内完成认证',
// },
{
httpStatusCode: '504',
systemStatusCode: '10006',
description: '网关超时',
},
// {
// httpStatusCode: '504',
// systemStatusCode: '10006',
// description: '网关超时,请联系 APISpace 客服',
// },
{
httpStatusCode: '404',
systemStatusCode: '10007',
description: '接口不存在',
},
// {
// httpStatusCode: '416',
// systemStatusCode: '10008',
// description: '内部错误,请联系 APISpace 技术支持',
// },
// {
// httpStatusCode: '401',
// systemStatusCode: '10009',
// description: 'Header 参数中找不到 Authorization-Type 或 Authorization-Type 非法',
// },
{
httpStatusCode: '400',
systemStatusCode: '10010',
description: '无法识别请求内容,请检查请求体是否正确',
},
{
httpStatusCode: '400',
systemStatusCode: '10011',
description: '请求头部缺少 Content-Type 字段',
},
{
httpStatusCode: '400',
systemStatusCode: '10011',
description: '请求头部 Content-Type 字段错误',
},
{
httpStatusCode: '400',
systemStatusCode: '10014',
description: '批量参数超出单次批量数量的最大限制',
},
{
httpStatusCode: '400',
systemStatusCode: '10016',
description: '参数缺少内容',
},
{
httpStatusCode: '500',
systemStatusCode: '10017',
description: '参数类型错误',
},
];
const CodePage: FC = () =>
<Table
size="small"
columns={columns}
className='table-border border-b-0 rounded'
dataSource={data?.map((item, index) => ({...item, key: index})) || []}
pagination={false}
/>;
export default CodePage;
@@ -0,0 +1,86 @@
import { useState,FC } from 'react';
import { Tooltip, Button } from 'antd';
import useCopyToClipboard from '@common/hooks/copy';
import { Icon } from '@iconify/react/dist/iconify.js';
type AddressItem = {
expand?: boolean;
[key: string]: unknown;
}
type CopyAddrListProps = {
addrItem: AddressItem;
onAddrItemChange?: (addrItem: AddressItem) => void;
keyName: string;
}
const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyName }) => {
const [localAddrItem, setLocalAddrItem] = useState<AddressItem>(addrItem);
const { copyToClipboard } = useCopyToClipboard();
const toggleExpand = () => {
const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand };
setLocalAddrItem(updatedAddrItem);
onAddrItemChange?.(updatedAddrItem);
};
const renderTooltipTitle = () => {
// 假设keyName对应的值是一个字符串数组
const addresses:string[] = localAddrItem[keyName] as string[]
return (
<div>
{addresses?.map((addr, index) => (
<div key={index} className="flex justify-between">
<span className="leading-6">{addr}</span>
</div>
))}
</div>
);
};
const renderAddresses = () => {
if (!localAddrItem.expand) {
return (
<span className="overflow-ellipsis w-full inline-block overflow-hidden align-middle">
<Tooltip title={renderTooltipTitle}>
<span className='flex items-center'>
<span className={`overflow-ellipsis inline-block overflow-hidden align-middle ${((localAddrItem[keyName] as string[]).length > 1) ? 'w-5/6' : 'w-full'}`}>
{(localAddrItem[keyName] as string[]).join(',')}
</span>
{(localAddrItem[keyName] as string[]).length === 1 && (
<Button type="primary" className="border-none ant-typography-copy text-theme hover:text-A_HOVER " ghost onClick={() => copyToClipboard((localAddrItem[keyName] as string))} icon={<Icon icon="ic:baseline-file-copy" width="14" height="14"/>} size="small" />
)}
{(localAddrItem[keyName] as string[]).length !== 1 && (
<Button className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]" icon={<iconpark-icon name="zhankai" style={{marginTop:'4px'}}></iconpark-icon>} onClick={toggleExpand} />
)}
</span>
</Tooltip>
</span>
);
} else {
return (
<div className="flex flex-nowrap items-center justify-between">
<div>
{(localAddrItem[keyName] as string[])?.map((addr: string, index: number) => (
<div key={index} className="block w-full">
<span className="leading-6">{addr}</span>
<Button type="primary" className="border-none bg-transparent w-[16px] h-[22px] p-[0px] ml-2 text-theme hover:text-A_HOVER" ghost onClick={() => copyToClipboard(addr)} icon={<Icon icon="ic:baseline-file-copy" width="14" height="14"/>} size="small" />
</div>
))}
</div>
<Button className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]" icon={<iconpark-icon name="shouqi-2"></iconpark-icon>} onClick={toggleExpand} />
</div>
);
}
};
return (
<div>
{renderAddresses()}
</div>
);
};
export default CopyAddrList;
@@ -0,0 +1,8 @@
import { DatePicker } from 'antd';
import type { Moment } from 'moment';
import momentGenerateConfig from 'rc-picker/lib/generate/moment';
const MyDatePicker = DatePicker.generatePicker<Moment>(momentGenerateConfig);
export default MyDatePicker;
@@ -0,0 +1,55 @@
import { Button, Drawer, DrawerProps, Space } from "antd";
import WithPermission from "./WithPermission";
import { useEffect, useState } from "react";
export type DrawerWithFooterProps = DrawerProps & {
onSubmit?: () => Promise<boolean|string>|undefined
submitAccess?: string
submitDisabled?:boolean
onClose?:()=>void
showLastStep?:boolean
onLastStep?:()=>void
notAutoClose?:boolean
showOkBtn?:boolean
extraBtn?:React.ReactNode
okBtnTitle?:string
cancelBtnTitle?:string
}
export function DrawerWithFooter(props:DrawerWithFooterProps){
const {children,title,placement='right',onClose,onSubmit,submitDisabled = false,okBtnTitle='提交',cancelBtnTitle,open,submitAccess,showLastStep,onLastStep,notAutoClose,showOkBtn=true,extraBtn} = props
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
const handlerSubmit = ()=>{
setSubmitLoading(true)
onSubmit?.()?.then(()=>{!notAutoClose && onClose?.()}).finally(()=>{setSubmitLoading(false)})
}
useEffect(()=>{!open && setSubmitLoading(false)},[open])
return (<>
<Drawer
{...props}
push={false}
title={title}
placement={placement}
width="60%"
destroyOnClose={true}
maskClosable={false}
footer={
<Space >
{showOkBtn && <WithPermission access={submitAccess}>
<Button onClick={handlerSubmit} type="primary" loading={submitLoading} disabled={submitDisabled}>
{okBtnTitle}
</Button>
</WithPermission>}
{ showLastStep && <Button onClick={onLastStep ?? onClose}></Button>}
{ extraBtn }
<Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? '取消':'关闭')}</Button>
</Space>
}
onClose={onClose}
open={open}
>
{children}
</Drawer>
</>)
}
@@ -0,0 +1,91 @@
import {FC } from 'react';
import { Input, Space } from 'antd';
import { Icon } from '@iconify/react/dist/iconify.js';
type KeyValueInput = {
key: string;
value: string;
};
type DynamicKeyValueInputProps = {
value?: KeyValueInput[];
onChange?: (newValue: KeyValueInput[]) => void;
};
export function transferToList (rawData:unknown):Array<{key:string, value:string}> {
const res:Array<{key:string, value:string}> = []
if(!rawData)
return res
const keys:Array<string> = Object.keys(rawData)
if (keys?.length > 0) {
for (const key of keys) {
res.push({ key: key, value: rawData[key] })
}
return [...res, { key: '', value: '' }]
}
return [{ key: '', value: '' }]
}
export function transferToMap (rawData:Array<{key:string, value:string}>):{[key:string]:string} {
const res:{[key:string]:string} = {}
for (const kv of rawData) {
if (kv.key && kv.value) { res[kv.key] = kv.value }
}
return res
}
export const DynamicKeyValueInput: FC<DynamicKeyValueInputProps> = ({value = [{key:'',value:''}],onChange}) => {
// const [keyValuePairs, setKeyValuePairs] = useState<KeyValueInput[]>([{ key: '', value: '' }]);
// Define a handler for when the inputs change
const handleInputChange = (index: number, type: 'key' | 'value', newValue: string) => {
// Create a new array with the updated value
const newKeyValuePairs = value ? [...value] : [];
if (newKeyValuePairs[index]) {
newKeyValuePairs[index][type] = newValue;
// If we're changing the last input and it's not empty, add a new pair
if (index === newKeyValuePairs.length - 1 && (newKeyValuePairs[index].key || newKeyValuePairs[index].value)) {
newKeyValuePairs.push({ key: '', value: '' });
}
// Call the onChange handler if it exists
onChange?.(newKeyValuePairs);
}
};
const addNewPair = () => {
const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }];
onChange?.(newKeyValuePairs);
};
const removePair = (index: number) => {
const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || [];
onChange?.(newKeyValuePairs);
};
return (
<>
{value && value?.map((pair, index) => (
<Space key={index} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
<Input
placeholder="Key"
value={pair.key}
onChange={(e) => handleInputChange(index, 'key', e.target.value)}
style={{ width: 162 }} />
<Input
placeholder="Value"
value={pair.value}
onChange={(e) => handleInputChange(index, 'value', e.target.value)}
style={{ width: 162 }} />
{index !== value.length - 1 && (
<>
<Icon icon="ic:baseline-delete" onClick={() => removePair(index)} width="14" height="14"/>
<Icon icon="ic:baseline-add" onClick={addNewPair} width="14" height="14"/>
</>
)}
</Space>
))}
</>
);
};
@@ -0,0 +1,121 @@
import { EditableProTable, ProColumns } from "@ant-design/pro-components";
import { Button } from "antd";
import { useState, useEffect } from "react";
import { v4 as uuidv4} from 'uuid';
import WithPermission from "./WithPermission";
interface EditableTableProps<T> {
configFields: ProColumns<T>[];
value?: T[]; // 外部传入的值
className?: string;
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
// tableProps?: TableProps<T>;
disabled?:boolean
extendsId?:string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id
}
const EditableTable = <T extends { _id: string }>({
configFields,
value, // value 现在是外部传入的配置项数组
onChange, // onChange 现在是当配置项数组变化时的回调函数
// tableProps,
disabled,
className,
extendsId,
}: EditableTableProps<T>) => {
// const [form] = Form.useForm<FormInstance>();
// const [isModalVisible, setIsModalVisible] = useState(false);
const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]);
// const [editingConfig, setEditingConfig] = useState<T | null>(null);
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
value?.map((item) => item._id) || ['1234']
);
useEffect(() => {
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}]);
}, [value]);
const getNotEmptyValue = (value:unknown)=>{
return value
}
return (
<EditableProTable<T>
className={className}
columns={configFields}
rowKey="_id"
value={configurations as T[]}
size="small"
bordered={true}
recordCreatorProps={false}
editable={ {
type: 'multiple',
editableKeys:disabled ? [] : configurations?.map(x=>x._id),
actionRender: (row, config) => {
return [
<WithPermission access="" key="addPermission" ><Button type="text" className="h-[22px] border-none p-0 flex items-center bg-transparent "
key="add"
onClick={() => {
const newId = uuidv4();
setConfigurations((prev)=>{
const tmpPreData = [...prev];
const newId = uuidv4()
const lastRecord:{[k:string]:unknown} = tmpPreData[tmpPreData.length - 1];
const newRecord :{[k:string]:unknown, _id:string}= { _id: newId };
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
if(extendsId && extendsId.length > 0) {
extendsId.forEach(field => {
newRecord[field] = lastRecord[field];
});
}
tmpPreData.splice(Number(config.index) + 1, 0,newRecord);
onChange?.(getNotEmptyValue(tmpPreData));
return tmpPreData});
setEditableRowKeys((prev)=>([...prev,newId]))
}}
>
</Button></WithPermission>,
(config.index !== configurations.length - 1 )&& <WithPermission access=""><Button type="text" className="h-[22px] border-none p-0 flex items-center bg-transparent "
key="edit"
onClick={() => {
setConfigurations((prev)=>{
const tmpPreData = [...prev];
tmpPreData.splice(Number(config.index), 1);
onChange?.(tmpPreData);
return tmpPreData});
setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id)))
}}
>
</Button></WithPermission>,
];
},
onValuesChange: (record, recordList) => {
if(record._id === recordList[recordList.length - 1]._id){
const newId = uuidv4()
const lastRecord:{[k:string]:unknown} = recordList[recordList.length - 1];
const newRecord :{[k:string]:unknown, _id:string}= { _id: newId };
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
if(extendsId && extendsId.length > 0) {
extendsId.forEach(field => {
newRecord[field] = lastRecord[field];
});
}
recordList = ([...recordList, newRecord as T]);
setEditableRowKeys((prev)=>[...prev, newId])
}
setConfigurations(recordList);
onChange?.(recordList);
},
onChange: setEditableRowKeys,
}}
/>
)
}
export default EditableTable;
@@ -0,0 +1,148 @@
import {useEffect, useState} from 'react';
import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd';
import { v4 as uuidv4 } from 'uuid';
import { ColumnsType } from 'antd/es/table';
import WithPermission from './WithPermission';
export interface ConfigField<T> {
title: string;
key: keyof T;
component: React.ReactNode;
renderText?: (value: unknown, record: T) => React.ReactNode;
required?: boolean;
ellipsis?:boolean
}
interface EditableTableWithModalProps<T> {
configFields: ConfigField<T>[];
value?: T[]; // 外部传入的值
className?: string;
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
tableProps?: TableProps<T>;
disabled?:boolean
}
const EditableTableWithModal = <T extends { _id?: string }>({
configFields,
value, // value 现在是外部传入的配置项数组
onChange, // onChange 现在是当配置项数组变化时的回调函数
tableProps,
disabled,
className
}: EditableTableWithModalProps<T>) => {
const [form] = Form.useForm<FormInstance>();
const [isModalVisible, setIsModalVisible] = useState(false);
const [configurations, setConfigurations] = useState<T[]>(value ||[]);
const [editingConfig, setEditingConfig] = useState<T | null>(null);
const showModal = (config?: T) => {
if (config) {
form.setFieldsValue(config as Record<string, unknown>);
setEditingConfig(config);
} else {
form.resetFields();
setEditingConfig(null);
}
setIsModalVisible(true);
};
const handleCancel = () => {
setIsModalVisible(false);
};
const handleDelete = (_id: string) => {
const newConfigurations = configurations.filter(config => config._id !== _id);
setConfigurations(newConfigurations);
onChange?.(newConfigurations);
};
const handleOk = () => {
form.validateFields()
.then(values => {
let newConfigurations = [...configurations];
if (editingConfig && editingConfig._id) {
newConfigurations = newConfigurations?.map(config =>
config._id === editingConfig._id ? { ...config, ...values } : config
);
} else {
const newConfig = { _id: uuidv4(), ...values } as Record<string, unknown>;
newConfigurations.push(newConfig as T);
}
setConfigurations(newConfigurations);
onChange?.(newConfigurations);
setIsModalVisible(false);
})
.catch(info => {
console.log('Validate Failed:', info);
});
};
useEffect(() => {
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || []);
}, [value]);
const columns: ColumnsType<T> = configFields.map(({ title, key, renderText }) => ({
title,
dataIndex: key as string,
key: key as string,
render: renderText ? (value, record) => renderText(value, record) : undefined,
ellipsis:true
}));
!disabled && columns.push({
title: '操作',
key: 'action',
width:117,
render: (_: unknown, record: T) => (
<>
<div className="flex items-center">
<Button key="edit" disabled={disabled} onClick={()=>{showModal(record)}} className={`h-[22px] border-none p-0 flex items-center bg-transparent`}></Button>
<Divider key="div1" type="vertical" />
<Button key="delete" disabled={disabled} onClick={()=>{handleDelete(record._id || '')}} className={`h-[22px] border-none p-0 flex items-center bg-transparent`} ></Button>
</div>
</>
),
});
const formItems = configFields.map(({ title,key, component, required }) => {
return (
<Form.Item
label={title as string}
name={key as string}
rules={[{ required, message: `必填项`}]}
>
{component}
</Form.Item>
)
}
);
return (
<>
{!disabled && <Button className="" disabled={disabled} onClick={() => showModal()}></Button>}
{configurations.length > 0 &&
<Table
className={`mt-btnybase border-solid border-[1px] border-BORDER border-b-0 rounded ${className}`} {...tableProps} dataSource={configurations} size="small" columns={columns} rowKey="_id" pagination={false}/>}
<Modal
title={editingConfig ? '编辑配置' : '添加配置'}
open={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
width={600}
maskClosable={false}
>
<WithPermission access=""><Form form={form} name="editableTableWithModal"
layout="vertical"
scrollToFirstError
// labelCol={{ span: 7 }}
// wrapperCol={{ span: 17}}
autoComplete="off">
{formItems}
</Form></WithPermission>
</Modal>
</>
);
};
export default EditableTableWithModal;
@@ -0,0 +1,23 @@
import { useState, useEffect } from "react";
function ErrorBoundary({ children }) {
const [error, setError] = useState(null);
useEffect(() => {
window.addEventListener("error", (event) => {
setError(event.error);
});
}, []);
if (error) {
return (
<div>
<h1>An error occurred</h1>
<pre>{error.message}</pre>
</div>
);
}
return children;
}
export default ErrorBoundary
@@ -0,0 +1,105 @@
import DirectoryTree from "antd/es/tree/DirectoryTree";
import { DataNode, DirectoryTreeProps } from "antd/lib/tree";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import TreeWithMore from "@common/components/aoplatform/TreeWithMore";
import { SearchOutlined } from "@ant-design/icons";
import { Input, Button, MenuProps } from "antd";
import { debounce } from "lodash-es";
import WithPermission from "@common/components/aoplatform/WithPermission";
import { v4 as uuidv4 } from 'uuid'
type T = unknown
export interface GroupTreeProps extends DirectoryTreeProps{
groupData?:(DataNode & T )[]
addBtnName?:React.ReactNode
addBtnAccess?:string
treeNameSuffixKey?:string
dropdownMenu?:(data:(DataNode & T )) => MenuProps['items']
withMore?:boolean
onEditGroup:(type:'rename'|'addChild'|'addPeer', entity:DataNode & T, val:string) => Promise<boolean>|undefined
placeholder?:string
}
export interface GroupTreeHandle {
startEdit:(id:string)=>void;
startAdd:(type:'peer',entity?:DataNode & T)=>void
}
const GroupTree = forwardRef<GroupTreeHandle,GroupTreeProps>((props, ref)=>{
const {groupData,selectedKeys,onSelect,addBtnName,addBtnAccess,treeNameSuffixKey,dropdownMenu,onEditGroup,placeholder="输入以搜索"} = props
const [treeData, setTreeData] = useState<DataNode[]>([])
const [searchWord, setSearchWord] = useState<string>('')
const [editingId, setEditingId] = useState<string>('')
const [addStatus, setAddStatus] = useState<boolean>(false)
useImperativeHandle(ref, ()=>({
startEdit:setEditingId,
startAdd:handlerAction
}))
const handlerAction = (type:'peer')=>{
if(type === 'peer'){
setAddStatus(true)
setEditingId(uuidv4())
}
}
const getTreeData = (rawData?:DataNode[])=>{
const loop = (data: DataNode[]): DataNode[] =>{
const newData = [...data,...(addStatus? [{title:'',key:editingId,id:editingId}]:[])]
return newData.map((item) => {
const strTitle = item.title as string;
const index = strTitle.indexOf(searchWord);
const beforeStr = strTitle.substring(0, index);
const afterStr = strTitle.slice(index + searchWord.length);
const title =
index > -1 ? (
<span >
{beforeStr}
<span className="text-theme">{searchWord}</span>
{afterStr} {treeNameSuffixKey && <span>({item?.[treeNameSuffixKey as keyof DataNode] as string ?? 0})</span>}
</span>) : (
<span className='w-[100%] truncate'>{strTitle}{treeNameSuffixKey && <span>({item?.[treeNameSuffixKey as keyof DataNode] as string?? 0})</span>}</span>
)
return {
title:<TreeWithMore dropdownMenu={dropdownMenu?.(item)} onBlur={()=>{setAddStatus(false);setEditingId('')}} editable editingId={editingId} entity={item} afterEdit={(val)=>onEditGroup?.(addStatus && editingId === item.key ? 'addPeer':'rename',item, val)?.then((res)=>{res && setEditingId('') ;res && setAddStatus(false) ; return res})}>{title}</TreeWithMore>,
key: item.key,
id:item.key
};
})
};
return rawData ? loop(rawData) :[];
}
const onSearchWordChange = (e:string)=>{
setSearchWord(e || '')
}
useEffect(()=>{
const n = getTreeData(groupData)
setTreeData(n)
},[groupData,editingId,searchWord])
return (
<>
<Input className="w-[calc(100%-24px)] mx-btnbase my-btnybase" onChange={(e) => debounce(onSearchWordChange, 100)(e.target.value)}
allowClear placeholder={placeholder}
prefix={ <SearchOutlined className="cursor-pointer" />}/>
<div className="max-h-[calc(100%-140px)] overflow-y-auto">
<DirectoryTree
icon={<></>}
blockNode={true}
treeData={treeData}
selectedKeys={selectedKeys}
onSelect={onSelect}
/>
</div>
{addBtnName && <WithPermission access={addBtnAccess}><Button className="h-[22px] mt-[20px] mb-[16px] bottom-[0px] sticky border-none p-0 flex items-center bg-transparent text-theme ml-[10px] hover:text-A_HOVER" key='add' onClick={()=>handlerAction('peer')} >{addBtnName}</Button></WithPermission>}
</>
)
})
export default GroupTree
@@ -0,0 +1,55 @@
import { Button, Tag } from "antd"
import {useNavigate} from "react-router-dom";
import WithPermission from "@common/components/aoplatform/WithPermission";
import { FC, ReactNode } from "react";
import { ArrowLeftOutlined, LeftOutlined } from "@ant-design/icons";
class InsidePageProps {
showBanner?:boolean = true
pageTitle:string = ''
tagList?:Array<{label:string|ReactNode}> = []
children:React.ReactNode
showBtn?:boolean = false
btnTitle?:string = ''
description?:string = ''
onBtnClick?:()=>void
backUrl?:string = '/'
btnAccess?:string
}
const InsidePage:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showBtn,btnTitle,btnAccess,description,children,onBtnClick,backUrl})=>{
const navigate = useNavigate();
const goBack = () => {
navigate(backUrl || '/');
};
return (
// <div className="h-full flex flex-col flex-1 overflow-hidden bg-[#f7f8fa]">
<div className="h-full flex flex-col flex-1 overflow-hidden ">
{ showBanner && <div className=" mx-[4px] border-[0px] border-b-[1px] border-solid border-BORDER">
{backUrl &&<div className="text-[18px] leading-[25px] pb-[12px]">
<Button type="text" onClick={goBack}><ArrowLeftOutlined className="max-h-[14px]" /></Button>
</div>}
<div className="flex justify-between">
<div className="flex items-center">
<p className="text-theme text-[26px] pr-[10px]">{pageTitle}</p>
{tagList && tagList?.length > 0 && tagList?.map((tag)=>{
return ( <Tag className="" key={tag.label as string} bordered={false} >{tag.label}</Tag>)
})}
</div>
{showBtn && <WithPermission access={btnAccess}><Button type="primary" onClick={()=> {
onBtnClick&&onBtnClick()
}}>{btnTitle}</Button></WithPermission>}
</div>
<p className="mb-[30px]">
{description}
</p>
</div>}
<div className="h-full overflow-y-hidden">{children}</div>
</div>
)
}
export default InsidePage
@@ -0,0 +1,54 @@
import { Button, Tag } from "antd"
import {useNavigate} from "react-router-dom";
import WithPermission from "@common/components/aoplatform/WithPermission";
import { FC, ReactNode } from "react";
import { ArrowLeftOutlined } from "@ant-design/icons";
class InsidePageProps {
showBanner?:boolean = true
pageTitle:string = ''
tagList?:Array<{label:string|ReactNode}> = []
children:React.ReactNode
showBtn?:boolean = false
btnTitle?:string = ''
description?:string = ''
onBtnClick?:()=>void
backUrl:string = '/'
btnAccess?:string
}
const InsidePageForHub:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showBtn,btnTitle,btnAccess,description,children,onBtnClick,backUrl})=>{
const navigate = useNavigate();
const goBack = () => {
navigate(backUrl);
};
return (
<div className="h-full flex flex-col flex-1 overflow-hidden max-w-[1500px] m-auto">
{ showBanner && <div className="p-btnbase mx-[4px]">
<div className="text-[18px] leading-[25px] pb-[12px]">
<Button type="text" onClick={goBack}><ArrowLeftOutlined className="max-h-[14px]" /></Button>
</div>
<div className="flex justify-between">
<div className="">
<span className="text-[26px] text-theme">{pageTitle}</span>
{tagList && tagList?.length > 0 && tagList?.map((tag)=>{
return ( <Tag key={tag.label as string} bordered={false}>{tag.label}</Tag>)
})}
</div>
{showBtn && <WithPermission access={btnAccess}><Button type="primary" onClick={()=> {
onBtnClick&&onBtnClick()
}}>{btnTitle}</Button></WithPermission>}
</div>
<p className="mb-[30px]">
{description}
</p>
</div>}
<div className="h-full overflow-y-hidden">{children}</div>
</div>
)
}
export default InsidePageForHub
@@ -0,0 +1,250 @@
import { GetProp, TransferProps, TreeDataNode, theme, Transfer, Tree, Spin } from "antd";
import { DataNode, TreeProps } from "antd/es/tree";
import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
import { TransferTableHandle, TransferTableProps } from "./TransferTable";
import { ApartmentOutlined, LoadingOutlined, UserOutlined } from "@ant-design/icons";
import { debounce } from "lodash-es";
type TransferItem = GetProp<TransferProps, 'dataSource'>[number];
interface TreeTransferProps {
dataSource: TreeDataNode[];
targetKeys: TransferProps['targetKeys'];
onChange: TransferProps['onChange'];
}
// Customize Table Transfer
const isChecked = (selectedKeys: React.Key[], eventKey: React.Key) =>
selectedKeys.includes(eventKey);
const generateTree = (
treeNodes: TreeDataNode[] = [],
checkedKeys: TreeTransferProps['targetKeys'] = [],
filterUnchecked: boolean = false,
disabledData:string[],
filteredItems?:Set<string>
): TreeDataNode[] => {
const checkedKeysSet = new Set(checkedKeys);
return treeNodes
.map(({ children, ...props }) => {
const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems);
const isDisabled = (!filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1)
? true
: (filterUnchecked ? false : checkedKeysSet.has(props.id as string));
const hasEnabledChild = childNodes.some(node => !node.disabled);
return {
...props,
title: <span className="w-full truncate ml-[4px] block">{props.name}</span>,
key: props.id,
disabled: isDisabled && !hasEnabledChild,
children: childNodes,
};
})
.filter(node => {
let res:boolean= true
if(filterUnchecked){
res =(!disabledData || disabledData.indexOf(node.key as string) === -1) && (checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0) )
}
if(filterUnchecked && filteredItems &&((filteredItems.size && !filteredItems.has(node.key as string))&& !(node.children && node.children.length > 0) )){
return false
}
return res
}
)
};
const TransferTree = (props)=>{
const { direction, token, tableHeight, dataSource, targetKeys, onItemSelect, onItemSelectAll,checkedKey,selectedKeys, filteredItems ,disabledData} = props;
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
const getExpandedKeys = (newData:TreeDataNode[], expandedSet:Set<string> = new Set())=>{
newData.forEach((item)=>{
if(item.children && item.children.length > 0){
expandedSet.add(item.key)
getExpandedKeys(item.children,expandedSet)
}
})
return expandedSet
}
const treeData:TreeDataNode[] = useMemo(()=>{
const filteredSet = filteredItems && filteredItems.length > 0 ? new Set(filteredItems.map((x)=>x.id)) : new Set()
const res = dataSource && dataSource.length > 0 ? generateTree(dataSource, targetKeys,direction === 'right',disabledData,filteredSet) : []
setExpandedKeys(Array.from(getExpandedKeys(res)))
return res
},[
dataSource, targetKeys,direction ,disabledData,filteredItems
])
const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => {
setExpandedKeys(expandedKeysValue as string[]);
};
return (
<div style={{ padding: token.paddingXS }}>
<Tree
className="icon-tree"
blockNode
checkable
showIcon
checkedKeys={direction === 'left' ? Array.from(new Set([...checkedKey,...disabledData])) : selectedKeys }
defaultExpandAll
expandedKeys={expandedKeys}
onExpand={onExpand}
height={tableHeight}
icon={(props)=> { return (props.type === 'member' ? <UserOutlined /> :<ApartmentOutlined /> )} }
treeData={treeData}
onCheck={(_checkedKeys, e:{checked: boolean, checkedNodes, node, event, halfCheckedKeys}) => {
if(e.checked){
onItemSelectAll( _checkedKeys, e.checked);
}else{
const checkedKeyArrFromTree = e.checkedNodes.map(node => node.key)
onItemSelectAll((checkedKey as string[]).filter(key => checkedKeyArrFromTree.indexOf(key) === -1),e.checked)
}
}}
onSelect={(_, { node: { key } }) => {
onItemSelect(key as string, !isChecked(checkedKey, key));
}}
/>
</div>
)
}
const MemberTransfer= forwardRef<TransferTableHandle<{[k:string]:unknown}>, TransferTableProps<{[k:string]:unknown}>>(
<T extends {[k:string]:unknown}>(props: TransferTableProps<T>, ref:Ref<TransferTableHandle<T>>) => {
const {request,columns,primaryKey,onSelect,tableType,disabledData = [],searchPlaceholder} = props
const [tableHeight, setTableHeight] = useState(window.innerHeight * 80 / 100 - 64 - 72 - 56 - 16 -3);
const [targetKeys, setTargetKeys] = useState<TreeTransferProps['targetKeys']>([]);
const [dataSource, setDataSource] = useState<DataNode[] >([])
const parentRef = useRef<HTMLDivElement>(null);
const [loading, setLoading] = useState<boolean>(false)
useEffect(()=>{
setTargetKeys(disabledData)
},[disabledData])
useImperativeHandle(ref, () =>({
selectedData: () => dataSource,
selectedRowKeys: () => targetKeys,}))
const onChange: TreeTransferProps['onChange'] = (keys) => {
onSelect?.(new Set(keys))
setTargetKeys(Array.from(new Set(keys)));
};
const { token } = theme.useToken();
const transferDataSource: TransferItem[] = useMemo(()=>{
function flatten(list: TreeDataNode[] = [], res:TransferItem[]) {
list.forEach((item) => {
res.push(item as TransferItem);
flatten(item.children,res);
});
}
const res:TransferItem[] =[]
flatten(dataSource,res);
return res
},[
dataSource
])
let memo: Record<string, boolean> = {};
const handlerFilterOption = (inputValue: string, item: any, parentResult: boolean = false, childrenSet: Set<string> = new Set()): boolean => {
const cacheKey = `${inputValue}_${item.key}`;
if (memo[cacheKey]) {
return memo[cacheKey];
}
childrenSet.add(item.key);
let result = item.title.includes(inputValue) || parentResult
if (item.children) {
for (const child of item.children) {
if (handlerFilterOption(inputValue, child, result,childrenSet)) {
result = true;
}
}
}
if (result) {
memo[cacheKey] = result;
childrenSet.forEach((key) => {
memo[`${inputValue}_${key}`] = result;
});
}
return result;
};
const getDataSource = ()=>{
setLoading(true)
request && request().then((res)=>{
const {data,success} = res
setDataSource(success? data : [])
}).finally(()=>{setLoading(false)})
}
useEffect(() => {
getDataSource()
const handleResize = () => {
setTableHeight(window.innerHeight * 80 / 100 - 64 - 72 - 56 - 16 -3)
};
const debouncedHandleResize = debounce(handleResize, 200);
// 监听窗口大小变化
window.addEventListener('resize', debouncedHandleResize);
handleResize();
return () => {
window.removeEventListener('resize', debouncedHandleResize);
};
}, []);
return (
<div ref={parentRef}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className=''>
<Transfer
showSearch
onSearch={(dir)=>{
memo = {};
}}
listStyle={{width:'408px'}}
disabledData={disabledData}
filterOption={(inputValue: string, item: any) => handlerFilterOption(inputValue, item)}
targetKeys={targetKeys}
dataSource={transferDataSource}
className="tree-transfer"
render={(item) => item.title!}
showSelectAll={false}
onChange={onChange}
titles={['','']}
>
{({ direction, onItemSelect, selectedKeys,onItemSelectAll ,filteredItems}) => {
const treeProps = {
dataSource, direction, onItemSelect, selectedKeys,onItemSelectAll ,filteredItems,token,tableHeight,targetKeys,disabledData
}
if (direction === 'left') {
const checkedKey = [...selectedKeys, ...targetKeys as string[]];
return (
<TransferTree {...treeProps} checkedKey={checkedKey} />
);
}
if(direction === 'right'){
const checkedKey = [...selectedKeys,...targetKeys as string[]];
return (
<TransferTree {...treeProps} checkedKey={checkedKey} />
);
}
}}
</Transfer>
</Spin>
</div>
);
})
export default MemberTransfer;
@@ -0,0 +1,20 @@
import { useEffect } from "react";
import { editor } from "monaco-editor";
import useInitializeMonaco from "@common/hooks/useInitializeMonaco";
import { Editor, useMonaco } from '@monaco-editor/react'
export type MonacoEditorRefType = editor.IStandaloneCodeEditor;
const MonacoEditorWrapper: React.FC = (props) => {
useInitializeMonaco();
const monacoInstance = useMonaco();
useEffect(() => {
if (monacoInstance) {
// 在这里你可以访问并配置Monaco实例
}
}, [monacoInstance]);
return <Editor {...props} />;
};
export default MonacoEditorWrapper;
@@ -0,0 +1,100 @@
import {FC, useEffect, useMemo, useState} from 'react';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
import { useLocation, useNavigate} from "react-router-dom";
import { getNavItem } from '@common/utils/navigation';
import { PERMISSION_DEFINITION } from '@common/const/permissions';
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
import { ProjectFilled } from '@ant-design/icons';
import { Icon } from '@iconify/react';
export type MenuItem = Required<MenuProps>['items'][number];
const APP_MODE = import.meta.env.VITE_APP_MODE;
// avoid changing route within ths same category
export const routerKeyMap = new Map<string, string[]|string>([
['workspace',['tenantManagement','service','team','serviceHub']],
['my',['tenantManagement','service','team']],
['mainPage',['dashboard','systemrunning']],
['operationCenter',['member','user','role','servicecategories']],
['organization',['member','user','role']],
['serviceHubSetting',['servicecategories']],
['maintenanceCenter',['partition','logsettings','resourcesettings','openapi']
]])
export const TOTAL_MENU_ITEMS: MenuProps['items'] = [
getNavItem('工作空间', 'workspace','/tenantManagement',<Icon icon="ic:baseline-space-dashboard" width="18" height="18"/>, [
getNavItem('我的', 'my','/tenantManagement',null,[
getNavItem(<a></a>, 'tenantManagement','/tenantManagement',<Icon icon="ic:baseline-apps" width="18" height="18"/>,undefined,undefined,''),
getNavItem(<a></a>, 'service','/service',<Icon icon="ic:baseline-blinds-closed" width="18" height="18"/>,undefined,undefined,''),
getNavItem(<a></a>, 'team','/team',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,''),
],undefined,''),
getNavItem(<a>API </a>, 'serviceHub','/serviceHub',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.workspace.api_market.view'),
]),
APP_MODE === 'pro' ? getNavItem('仪表盘', 'mainPage', '/dashboard',<Icon icon="ic:baseline-bar-chart" width="18" height="18"/>,[
getNavItem(<a ></a>, 'dashboard','/dashboard',<ProjectFilled />,undefined,undefined,''),
getNavItem(<a ></a>, 'systemrunning','/systemrunning',<ProjectFilled />,undefined,undefined,''),
]):null,
getNavItem('系统设置', 'operationCenter','/member',<Icon icon="ic:baseline-settings" width="18" height="18"/>, [
getNavItem('组织', 'organization','/member',null,[
getNavItem(<a></a>, 'member','/member',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,'system.organization.member.view'),
getNavItem(<a></a>, 'role','/role',<Icon icon="ic:baseline-verified-user" width="18" height="18"/>,undefined,undefined,'system.organization.role.view'),
],undefined,''),
getNavItem('API 市场', 'serviceHubSetting','/servicecategories',null,[
getNavItem(<a></a>, 'servicecategories','/servicecategories',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.api_market.service_classification.view'),
],undefined,'system.api_market.service_classification.view'),
getNavItem('运维与集成', 'maintenanceCenter','/cluster', null, [
getNavItem(<a></a>, 'cluster','/cluster',<Icon icon="ic:baseline-device-hub" width="18" height="18"/>,undefined,undefined,'system.devops.cluster.view'),
getNavItem(<a></a>, 'cert','/cert',<Icon icon="ic:baseline-security" width="18" height="18"/>,undefined,undefined,'system.devops.ssl_certificate.view'),
getNavItem(<a></a>, 'logsettings','/logsettings',<Icon icon="ic:baseline-sticky-note-2" width="18" height="18"/>,undefined,undefined,'system.devops.log_configuration.view'),
APP_MODE === 'pro' ? getNavItem(<a></a>, 'resourcesettings','/resourcesettings',null,undefined,undefined,'system.partition.self.view'):null,
APP_MODE === 'pro' ? getNavItem(<a>Open API</a>, 'openapi','/openapi',null,undefined,undefined,'system.openapi.self.view'):null,
]),
]),
];
const Navigation: FC = () => {
const location = useLocation()
const [selectedKeys, setSelectedKeys] = useState<string>('')
const currentUrl = location.pathname
const navigateTo = useNavigate()
const { accessData,checkPermission} = useGlobalContext()
const onClick: MenuProps['onClick'] = (e) => {
if(location.pathname.split('/')[1] === e.key) return
const newUrl = routerKeyMap.get(e.key)
newUrl && navigateTo(newUrl)
};
const menuData = useMemo(()=>{
const filterMenu = (menu:Array<{[k:string]:unknown}>)=>{
return menu.filter(x=> x && (x.access ? checkPermission(x.access as keyof typeof PERMISSION_DEFINITION[0]): true))
}
return TOTAL_MENU_ITEMS!.filter(x=>x).map((x)=> ( x.children ? {...x, children:filterMenu(x.children)} : x))?.filter(x=> x.key === 'service' || (x.children && x.children?.length > 0))
},[accessData])
useEffect(() => {
setSelectedKeys(currentUrl.split('/')[1] === 'template' ? currentUrl.split('/')[2] : currentUrl.split('/')[1])
}, [currentUrl]);
return (
<Menu
onClick={onClick}
theme="dark"
style={{height:'100%' }}
selectedKeys={[selectedKeys]}
defaultOpenKeys={['mainPage','dataAssets','operationCenter','maintenanceCenter']}
mode="inline"
items={[...menuData]}
/>
);
};
export default Navigation;
@@ -0,0 +1,48 @@
:global .eo_page_list .ant-pro-card .ant-pro-card-body{
padding:0 !important;
}
:global .eo_page_list .ant-pro-table-list-toolbar-container{
padding:10px 20px 10px 10px !important;
.ant-pro-table-list-toolbar-right{
justify-content: flex-start;
flex-direction: row-reverse;
}
.ant-input-group-addon .ant-input-search-button{
display:none;
}
}
:global .eo_page_list .ant-table-wrapper {
.ant-table-pagination.ant-pagination {
margin: 1px 10px 0 !important;
padding: 10px 0;
/* box-shadow: 0 -2px 2px -2px var(--border-color); */
}
.ant-table.ant-table-middle{
.ant-table-thead>tr>th,
.ant-table-thead>tr>td{
border-top:1px solid var(--border-color);
border-bottom:1px solid var(--border-color);
}
.ant-table-footer,.ant-table-cell,
.ant-table-thead>tr>th,
.ant-table-tbody>tr>th{
padding:8px 10px;
}
.ant-table-thead>tr>th{
color:#666666;
font-weight:normal;
}
}
.ant-table.ant-table-middle tfoot>tr>th,
.ant-table.ant-table-middle tfoot>tr>td{
padding:8px 10px;
}
}
@@ -0,0 +1,232 @@
import {Button, Dropdown, Input, MenuProps, TablePaginationConfig} from 'antd';
import {ChangeEvent, RefAttributes, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import type {ActionType, ParamsType, ProColumns, ProTableProps} from '@ant-design/pro-components';
import {
DragSortTable,
ProTable,
} from '@ant-design/pro-components';
import './PageList.module.css'
import {SearchOutlined} from "@ant-design/icons";
import { debounce } from 'lodash-es'
import WithPermission from '@common/components/aoplatform/WithPermission';
import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface';
import { useGlobalContext } from '../../contexts/GlobalStateContext';
import { PERMISSION_DEFINITION } from '@common/const/permissions';
import { withMinimumDelay } from '@common/utils/ux';
interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<ActionType> {
id?:string
columns: ProColumns<T,'text'>[]
request?:(params: (ParamsType & {pageSize?: number | undefined, current?: number | undefined, keyword?: string | undefined}), sorter: unknown, filter: unknown)=>Promise<{data:T[], success:boolean}>
dropMenu?:MenuProps
searchPlaceholder?:string
showPagination?:boolean
primaryKey?:string
addNewBtnTitle?:string
addNewBtnAccess?:string
tableClickAccess?:string
onAddNewBtnClick?:()=>void
beforeSearchNode?:React.ReactNode[]
onSearchWordChange?:(e:ChangeEvent<HTMLInputElement>) => void
afterNewBtn?:React.ReactNode[]
dragSortKey?:string
onDragSortEnd?:(beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise<void>
tableTitle?:string
dataSource?:T[]
onRowClick?:(record:T)=>void
showColSetting?:boolean
minVirtualHeight?:number
besidesTableHeight?:number
noTop?:boolean
tableClass?:string
tableTitleClass?:string
addNewBtnWrapperClass?:string
delayLoading?:boolean
noScroll?:boolean
/* 前端分页的表格,需要传入该字段以支持后端搜索 */
manualReloadTable?:()=>void
}
const PageList = <T extends Record<string, unknown>>(props: React.PropsWithChildren<PageListProps<T>>,ref: React.Ref<ActionType>) => {
const {id,columns,request,dropMenu,searchPlaceholder,showPagination=true,primaryKey='id',addNewBtnTitle,addNewBtnAccess,tableClickAccess,tableClass,onAddNewBtnClick,beforeSearchNode,onSearchWordChange,manualReloadTable,afterNewBtn,dragSortKey,onDragSortEnd,tableTitle,rowSelection,onChange,dataSource,onRowClick,showColSetting=false,minVirtualHeight,noTop,addNewBtnWrapperClass,tableTitleClass,delayLoading = true,besidesTableHeight, noScroll} = props
const parentRef = useRef<HTMLDivElement>(null);
const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight);
const [tableWidth, setTableWidth] = useState<number|undefined>(undefined);
const actionRef = useRef<ActionType>();
const [allowTableClick,setAllowTableClick] = useState<boolean>(false)
const {accessData,checkPermission} = useGlobalContext()
const [minTableWidth, setMinTableWidth] = useState<number>(0)
// 使用useImperativeHandle来自定义暴露给父组件的实例值
useImperativeHandle(ref, () => actionRef.current!);
const lastAccess = useMemo(()=>{
if(!tableClickAccess) return true
return checkPermission(tableClickAccess as keyof typeof PERMISSION_DEFINITION[0])
},[allowTableClick, accessData])
useEffect(()=>{
tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true)
},[accessData])
const resizeObserverRef = useRef<ResizeObserver |null >(null);
useEffect(() => {
const handleResize = () => {
if (parentRef.current && !noScroll) {
const res = parentRef.current.getBoundingClientRect();
const height = res.height - ((noTop ? 0 : 52) + 40 + (showPagination && !dragSortKey ? 52 : 0) +( besidesTableHeight ?? 0)); // 减去顶部按钮、底部分页、表头高度
setTableWidth(minTableWidth > res.width ? minTableWidth : undefined);
height && setTableHeight(minVirtualHeight === undefined ? height : (height > minVirtualHeight ? height : minVirtualHeight));
}
};
const debouncedHandleResize = debounce(handleResize, 200);
if (!resizeObserverRef.current && !noScroll) {
// 创建一个 ResizeObserver 来监听高度变化,只创建一次
resizeObserverRef.current = new ResizeObserver(debouncedHandleResize);
// 开始监听
if (parentRef.current && !minVirtualHeight) {
resizeObserverRef.current.observe(parentRef.current);
}
}
// 在 minTableWidth 变化时手动触发 handleResize
handleResize();
// 清理函数
return () => {
if (resizeObserverRef.current) {
resizeObserverRef.current.disconnect();
resizeObserverRef.current = null;
}
};
}, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]); // 将相关依赖项作为 useEffect 的依赖项
const newColumns = useMemo(()=>{
let width:number = 0
const res = columns?.map(
(x)=>{
width += Number(x.width ?? ((x.filters || x.sorter) ? 120 : 100))
x.copyable = x.copyable === false? false: true
const sorter = localStorage.getItem(`${id}_sorter`)
const filters = localStorage.getItem(`${id}_filters`)
if(sorter && x.sorter){
const sorterObj = JSON.parse(sorter)
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex
x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined
// x.showSorterTooltip = {target:'sorter-icon'}
}
if(filters && x.filters){
const filtersObj = JSON.parse(filters)
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex
x.defaultFilteredValue = filtersObj?.[xName as string]
}
return x})
setMinTableWidth(width)
return res
},[columns])
const headerTitle = ()=>{
return (
<>{
tableTitle ? <span className={`text-[30px] leading-[42px] my-mbase pl-[20px] ${tableTitleClass}`}>{tableTitle}</span> : (
addNewBtnTitle ? <WithPermission access={addNewBtnAccess} ><Button type="primary" className={`mr-btnbase ${addNewBtnWrapperClass}`} onClick={onAddNewBtnClick}>{addNewBtnTitle}</Button></WithPermission> : undefined
)
}
{afterNewBtn ? afterNewBtn as React.ReactNode[] :undefined}
</>
)
}
const requestWithDelay = (params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined;}, sort: unknown, filter: unknown) => {
return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false? 0 : undefined);
};
return (
<div ref={parentRef} className={`eo_page_list bg-MAIN_BG ${dragSortKey ? 'eo_page_drag':''} ${tableClass ?? ''}`}style={{ height: '100%' }}>
{dragSortKey? <DragSortTable<T>
actionRef={actionRef}
columns={newColumns}
rowKey={primaryKey}
search={false}
pagination={false}
request={request}
dragSortKey={dragSortKey}
onDragSortEnd={onDragSortEnd}
scroll={noScroll ? undefined :{ y: tableHeight }}
options={{
reload: false,
density: false,
setting: false,
}}
headerTitle={
headerTitle()
}
/> : <ProTable<T>
actionRef={actionRef}
columns={newColumns}
virtual
scroll={noScroll ? undefined : {x:tableWidth,y: tableHeight }}
size="middle"
rowSelection={rowSelection}
tableAlertRender={false}
tableAlertOptionRender={false}
request={request ? requestWithDelay : undefined}
toolBarRender={() => [
dropMenu ? (<Dropdown
key="menu"
menu={dropMenu}
>
<Button>
</Button>
</Dropdown>):null,
]}
toolbar={{
actions:[...[beforeSearchNode],...[searchPlaceholder?<Input className="" onChange={ onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined } onPressEnter={()=>manualReloadTable ? manualReloadTable():actionRef.current?.reload?.()} allowClear placeholder={searchPlaceholder} prefix={<SearchOutlined className="cursor-pointer" onClick={()=>{actionRef.current?.reload?.()}}/>}/>:null]],
}}
options={{
reload: false,
density: false,
setting: showColSetting ? {
draggable:false,
showListItemOption:false
} :false,
}}
showSorterTooltip={false}
columnsState={{persistenceType:'localStorage',persistenceKey:id}}
pagination={showPagination ? {
showSizeChanger: true,
showQuickJumper: true,
size:'default'
}:false}
rowKey={primaryKey}
onChange={(pagination: TablePaginationConfig, filters: Record<string, FilterValue | null>, sorter: SorterResult<T> | SorterResult<T>[],extra:TableCurrentDataSource<T>) =>{
localStorage.setItem(`${id}_filters`,JSON.stringify(filters))
!Array.isArray(sorter) && localStorage.setItem(`${id}_sorter`,JSON.stringify({columnKey:sorter?.columnKey, order: sorter?.order}))
onChange?.(pagination,filters,sorter,extra)}}
dataSource={dataSource}
search={false}
headerTitle={
headerTitle()
}
onRow={onRowClick && allowTableClick ? (record) => ({
onClick: () => {
onRowClick(record);
}
}):undefined}
rowClassName={()=>onRowClick && allowTableClick ?"cursor-pointer":''}
/>}
</div>
);
};
export default forwardRef(PageList) as <T extends Record<string,unknown>>(props: React.PropsWithChildren<PageListProps<T>> & { ref?: React.Ref<ActionType> }) => ReturnType<typeof PageList>;
@@ -0,0 +1,296 @@
import {App, Col, Form, Input, Row, Table, Tooltip} from "antd";
import {forwardRef, useEffect, useImperativeHandle} from "react";
import {PublishApprovalInfoType, PublishVersionTableListItem} from "@common/const/approval/type.tsx";
import {useFetch} from "@common/hooks/http.ts";
import {BasicResponse, STATUS_CODE} from "@common/const/const.ts";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from "@core/const/system/const.tsx";
import { SystemInsidePublishOnlineItems } from "@core/pages/system/publish/SystemInsidePublishOnline.tsx";
enum ChangeTypeEnum {
'new' = '新增',
'update' = '变更',
'delete' = '删除',
'none' = '无变更',
'error' = '缺失字段'
}
const statusColorClass = {
new: 'text-[#138913]', // 使用 Tailwind 的 Arbitrary Properties
update: 'text-[#03a9f4]',
delete: 'text-[#ff3b30]',
none: 'text-[var(--MAIN_TEXT)]', // 假设你也有一个“none”的状态
};
const apiColumns = [
{
title:'API 名称',
dataIndex:'name',
copyable: true,
ellipsis:true
},
{
title:'请求方式',
dataIndex:'method',
copyable: true,
ellipsis:true
},
{
title:'路径',
dataIndex:'path',
copyable: true,
ellipsis:true
},
{
title:'类型',
dataIndex:'change',
render:(_,entity)=>(
<Tooltip placement="top" title={entity.change === 'error' ?`该 API 缺失 ${entity.proxyStatus == 1 && '转发信息,'} ${entity.docStatus == 1 && '文档信息,'} ${entity.upstreamStatus == 1 && '上游信息,'}请先补充`:''}>
<span className={`${statusColorClass[entity.change as keyof typeof statusColorClass]} truncate block`}>
{ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-'}
{entity.change === 'error' ?` 该 API 缺失 ${entity.proxyStatus == 1 && '转发信息,'} ${entity.docStatus == 1 && '文档信息,'} ${entity.upstreamStatus == 1 && '上游信息,'}请先补充`:''}
</span>
</Tooltip>)
}
]
const upstreamColumns = [
{
title:'上游类型',
dataIndex:'type',
ellipsis:true,
// filters: true,
// onFilter: true,
// valueType: 'select',
// filterSearch: true,
valueEnum:{
'static':{
text:'静态上游'
},
// 'dynamic':{
// text:'动态上游'
// }
}
},
{
title:'地址',
dataIndex:'addr',
render:(text:string[])=>(<>{text.join(',')}</>),
copyable: true,
ellipsis:true
},
{
title:'类型',
dataIndex:'change',
render:(_,entity)=>(
<Tooltip placement="top" title={entity.change === 'error' ?`该 API 缺失 ${entity.proxyStatus == 1 && '转发信息,'} ${entity.docStatus == 1 && '文档信息,'} ${entity.upstreamStatus == 1 && '上游信息,'}请先补充`:''}>
<span className={`${statusColorClass[entity.change as keyof typeof statusColorClass]} truncate block`}>{ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-'}
{entity.change === 'error' ?` 该 API 缺失 ${entity.proxyStatus == 1 && '转发信息,'} ${entity.docStatus == 1 && '文档信息,'} ${entity.upstreamStatus == 1 && '上游信息,'}请先补充`:''}</span>
</Tooltip>)
}
]
type PublishApprovalModalProps = {
type:'approval'|'view'|'add'|'publish'|'online'
data:PublishApprovalInfoType | PublishApprovalInfoType &{id?:string} | PublishVersionTableListItem
insideSystem?:boolean
serviceId:string
teamId:string
clusterPublishStatus?:SystemInsidePublishOnlineItems[]
}
export type PublishApprovalModalHandle = {
save:(operate:'pass'|'refuse') =>Promise<boolean|string>
publish:(notSave?:boolean)=>Promise<boolean|string|Record<string, unknown>>
online:()=>Promise<boolean|string>
}
export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle,PublishApprovalModalProps>((props, ref) => {
const { message } = App.useApp()
const { type,data,insideSystem = false,serviceId, teamId} = props
const [form] = Form.useForm();
const {fetchData} = useFetch()
const save:(operate:'pass'|'refuse')=>Promise<boolean | string> = (operate)=>{
if(type === 'view'){
return Promise.resolve(true)
}
return form.validateFields().then((value)=>{
if(operate === 'refuse' && form.getFieldValue('opinion') === '' ){
form.setFields([{
name:'opinion',errors:['选择拒绝时,审批意见为必填']
}])
form.scrollToField('opinion')
return Promise.reject('未填写审核意见')
}
return fetchData<BasicResponse<null>>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`,{method: 'PUT',eoBody:({comments:value.opinion}), eoParams:{id:data!.id, project:serviceId},eoTransformKeys:['versionRemark']}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || '操作成功!')
return Promise.resolve(true)
}else{
message.error(msg || '操作失败')
return Promise.reject(msg || '操作失败')
}
}).catch((errorInfo)=> Promise.reject(errorInfo))
}).catch((err)=> {form.scrollToField(err.errorFields[0].name[0]); return Promise.reject(err)})
}
const publish:(notSave?:boolean)=>Promise<boolean | string | Record<string, unknown>> = (notSave)=>{
return new Promise((resolve, reject)=>{
form.validateFields().then((value)=>{
const body = {...value, ...(type === 'publish'&&{release:data.id})}
fetchData<BasicResponse<null>>(
notSave ? 'service/publish/apply' : 'service/publish/release/do',{method: 'POST',eoBody:body, eoParams:{service:serviceId, team:teamId},eoTransformKeys:['versionRemark']}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || '操作成功!')
resolve(response)
}else{
message.error(msg || '操作失败')
reject(msg || '操作失败')
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo)=> reject(errorInfo))
})
}
const online:()=>Promise<boolean | string> = ()=>{
return new Promise((resolve, reject)=>{
form.validateFields().then(()=>{
fetchData<BasicResponse<null>>('service/publish/execute',{method: 'PUT', eoParams:{project:serviceId,id:(data as PublishVersionTableListItem).flowId},eoTransformKeys:['versionRemark']}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || '操作成功!')
resolve(true)
}else{
message.error(msg || '操作失败')
reject(msg || '操作失败')
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo)=> reject(errorInfo))
})
}
useImperativeHandle(ref, ()=>({
save,
publish,
online
})
)
useEffect(()=>{
form.setFieldsValue({ opinion:'',...data})
},[])
return (
<>
{!insideSystem && <>
<Row className="my-mbase">
<Col className="text-left" span={4}><span ></span></Col>
<Col span={18}>{(data as PublishApprovalInfoType).project || '-'}</Col>
</Row>
<Row className="my-mbase">
<Col className="text-left" span={4}><span ></span></Col>
<Col span={18}>{(data as PublishApprovalInfoType).team || '-'}</Col>
</Row>
<Row className="my-mbase">
<Col className="text-left" span={4}><span ></span></Col>
<Col span={18}>{(data as PublishApprovalInfoType).applier || '-'}</Col>
</Row>
<Row className="my-mbase">
<Col className="text-left" span={4}><span ></span></Col>
<Col span={18}>{(data as PublishApprovalInfoType).applyTime || '-'}</Col>
</Row>
</> }
<WithPermission access=""><Form
className=" mx-auto"
form={form}
labelAlign='left'
layout='vertical'
scrollToFirstError
name="publishApprovalModalContent"
// labelCol={{span: 3}}
// wrapperCol={{span: 21}}
autoComplete="off"
disabled={type === 'view'}
>
{
insideSystem &&
<>
<Form.Item
label="版本号"
name="version"
rules={[{required: true, message: '必填项',whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder="请输入" />
</Form.Item>
<Form.Item
label="版本说明"
name="versionRemark"
>
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder="请输入" />
</Form.Item>
</>
}
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >API </span></Row>
<Row className="mb-mbase ">
<Table
columns={apiColumns}
bordered={true}
rowKey="id"
size="small"
dataSource={data.diffs?.apis || []}
pagination={false}
/></Row>
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span ></span></Row>
<Row className="mb-mbase ">
<Table
bordered={true}
columns={upstreamColumns}
size="small"
rowKey="id"
dataSource={data.diffs?.upstreams || []}
pagination={false}
/></Row>
<Form.Item
label="备注"
name="remark"
>
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder="请输入" />
</Form.Item>
{type !== 'add' && type !== 'publish' && <Form.Item
label="审批意见"
name="opinion"
extra="选择拒绝时,审批意见为必填"
>
<Input.TextArea className="w-INPUT_NORMAL" placeholder="请输入" onChange={()=>{ form.setFields([
{
name: 'opinion',
errors: [], // 设置为空数组来移除错误信息
},
]);}}/>
</Form.Item>}
{['error','done'].indexOf(data.status) !== -1 && data.clusterPublishStatus &&data.clusterPublishStatus.length > 0 && <> <Row className="text-left h-[32px] mb-8px]" span={3}><span>线</span></Row>
<Row span={24} className="mb-mbase">
<Table
bordered={true}
columns={[...SYSTEM_PUBLISH_ONLINE_COLUMNS]}
size="small"
rowKey="id"
dataSource={data.clusterPublishStatus || []}
pagination={false}
/>
</Row></>}
</Form>
</WithPermission>
</>)
})
@@ -0,0 +1,134 @@
import { Form, Input} from "antd";
import {forwardRef, useEffect, useImperativeHandle} from "react";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import { UserInfoType } from "@common/const/type.ts";
type FieldType = {
userName:string
old:string
password:string
confirm:string
}
type ResetPswProps = {
entity?:UserInfoType
}
export type ResetPswHandle = {
save:()=>Promise<boolean|string>
}
export const ResetPsw = forwardRef<ResetPswHandle,ResetPswProps>((props,ref)=>{
const [form] = Form.useForm();
const save:()=>Promise<boolean | string> = ()=>{
return new Promise((resolve)=>{
// form.validateFields().then((value)=>{
// fetchData<BasicResponse<null>>(url,{method,eoBody:(value), eoTransformKeys:['departmentIds']}).then(response=>{
// const {code,msg} = response
// if(code === STATUS_CODE.SUCCESS){
// message.success(msg || '操作成功!')
resolve(true)
// }else{
// message.error(msg || '操作失败')
// reject(msg || '操作失败')
// }
// })
// }).catch((errorInfo)=> reject(errorInfo))
})
}
const getPswStrength = (value: string) => {
const pswRegNum: RegExp = /[0-9]/
const pswRegLowercase: RegExp = /[a-z]/
const pswRegUppercase: RegExp = /[A-Z]/
const pswRegSymbol: RegExp = /!@#$%^&*`~()-+=/
let strength: number = 0
if (pswRegNum.test(value)) {
strength++
}
if (pswRegLowercase.test(value)) {
strength++
}
if (pswRegUppercase.test(value)) {
strength++
}
if (pswRegSymbol.test(value)) {
strength++
}
return strength
}
useImperativeHandle(ref, ()=>({
save
})
)
useEffect(() => {
// form.setFieldsValue({id:entity!.id})
}, []);
return (<WithPermission access="">
<Form
labelAlign='left'
layout='vertical'
form={form}
scrollToFirstError
className="mx-auto mt-mbase "
name="resetPsw"
// labelCol={{ span: 8 }}
// wrapperCol={{ span: 10}}
autoComplete="off"
>
<Form.Item<FieldType>
label="账号"
name="userName"
hidden
rules={[{ required: true, message: '必填项',whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder="账号" disabled={true}/>
</Form.Item>
<Form.Item<FieldType>
label="旧密码"
name="old"
rules={[{ required: true, message: '必填项',whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder="请输入6-32位字符"/>
</Form.Item>
<Form.Item<FieldType>
label="新密码"
name="password"
hidden
// eslint-disable-next-line @typescript-eslint/no-unused-vars
rules={[{ required: true, message: '必填项',whitespace:true }, ({ getFieldValue }) => ({
validator(_, value) {
if (!value || getPswStrength(value)>1) {
return Promise.resolve();
}
return Promise.reject(new Error('密码强度:弱,建议使用英文、数字、特殊字符组合'));
},
})]}
>
<Input className="w-INPUT_NORMAL" placeholder="请输入6-32位字符"/>
</Form.Item>
<Form.Item<FieldType>
label="确认新密码"
name="confirm"
dependencies={['password']}
rules={[{ required: true, message: '必填项',whitespace:true }, ({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('新密码与确认新密码不一致'));
},
})]}
>
<Input className="w-INPUT_NORMAL" placeholder="请输入6-32位字符"/>
</Form.Item>
</Form>
</WithPermission>)
})
@@ -0,0 +1,64 @@
import {FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react';
interface ScrollableSectionProps {
children: React.ReactNode;
}
const ScrollableSection: FC<ScrollableSectionProps> = ({ children }) => {
const scrollAreaRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleScroll = () => {
if (scrollAreaRef.current) {
const scrollTop = scrollAreaRef.current.scrollTop;
const scrollHeight = scrollAreaRef.current.scrollHeight;
const clientHeight = scrollAreaRef.current.clientHeight;
// 如果滚动到顶部,.content-before 应该显示阴影
const showTopShadow = scrollTop > 0;
// 如果滚动到底部,.content-after 应该显示阴影
const showBottomShadow = scrollHeight - scrollTop < clientHeight;
// 这里我们不直接更新状态,而是通过ref来设置样式
if (showTopShadow && !showBottomShadow) {
setElementShadow('.content-before', true);
setElementShadow('.content-after', false);
} else if (!showTopShadow && showBottomShadow) {
setElementShadow('.content-before', false);
setElementShadow('.content-after', true);
} else {
setElementShadow('.content-before', false);
setElementShadow('.content-after', false);
}
}
};
scrollAreaRef.current?.addEventListener('scroll', handleScroll);
return () => {
scrollAreaRef.current?.removeEventListener('scroll', handleScroll);
};
}, []);
const setElementShadow = (elementSelector: string, showShadow: boolean) => {
const element = document.querySelector(elementSelector);
if (element) {
element.style.boxShadow = showShadow ? ( elementSelector === '.content-before' ? '0 2px 2px #0000000d':'0 -2px 2px -2px var(--border-color)') : 'none';
}
}
const childrenWithRef = Children.toArray(children).map((child) => {
if (isValidElement(child) && child.props.className && child.props.className.includes('scroll-area')) {
// 将 ref 附加到具有 'scroll-area' 类名的子元素
return cloneElement(child, { ref: scrollAreaRef });
}
return child;
});
return (
<> {childrenWithRef}
</>
);
};
export default ScrollableSection;
@@ -0,0 +1,133 @@
import {App, Checkbox, Col, Form, Input, Row} from "antd";
import { forwardRef, useEffect, useImperativeHandle} from "react";
import {SubscribeApprovalInfoType} from "@common/const/approval/type.tsx";
import {BasicResponse, STATUS_CODE} from "@common/const/const.ts";
import {useFetch} from "@common/hooks/http.ts";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
type SubscribeApprovalModalProps = {
type:'approval'|'view'
data?:SubscribeApprovalInfoType
inSystem?:boolean
serviceId:string
teamId:string
}
export type SubscribeApprovalModalHandle = {
save:(operate:'pass'|'refuse') =>Promise<boolean|string>
}
type FieldType = {
reason?:string;
opinion?:string;
};
const list = [
{
title:'申请方应用',key:'application'
},
{
title:'申请方所属团队',key:'applyTeam'
},
{
title:'申请人',key:'applier'
},
{
title:'申请时间',key:'applyTime'
},
{
title:'申请服务',key:'service'
},
{
title:'服务所属团队',key:'team'
}
]
export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHandle,SubscribeApprovalModalProps>((props, ref) => {
const { message } = App.useApp()
const {data, type,inSystem=false, teamId, serviceId} = props
const [form] = Form.useForm();
const {fetchData} = useFetch()
const save:(operate:'pass'|'refuse')=>Promise<boolean | string> = (operate)=>{
return new Promise((resolve, reject)=>{
if(type === 'view'){
resolve(true)
return
}
form.validateFields().then((value)=>{
if(operate === 'refuse' && form.getFieldValue('opinion') === ''){
form.setFields([{
name:'opinion',errors:['必填项']
}])
form.scrollToField('opinion')
reject('未填写审核意见')
return
}
fetchData<BasicResponse<null>>(`${inSystem?'service/':''}approval/subscribe`,{method: 'POST',eoBody:({opinion:value.opinion,operate}), eoParams:(inSystem ? {apply:data!.id, team:teamId} : {id:data!.id,team:teamId})}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || '操作成功!')
resolve(true)
}else{
message.error(msg || '操作失败')
reject(msg || '操作失败')
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo)=> reject(errorInfo))
})
}
useImperativeHandle(ref, ()=>({
save
})
)
useEffect(()=>{
form.setFieldsValue({opinion:'',...data})
},[])
return (
<div className="my-btnybase">{
list?.map((x)=>(
<Row key={x.key} className="leading-[32px] mb-btnbase mx-auto">
<Col className="text-left" span={6}>{x.title}</Col>
<Col >{(data as {[k:string]:unknown})?.[x.key]?.name || (data as {[k:string]:unknown})?.[x.key] || '-'}</Col>
</Row>
))
}
<WithPermission access="">
<Form
labelAlign='left'
layout='vertical'
form={form}
className="mx-auto "
name="subscribeApprovalModalContent"
// labelCol={{ span: 6}}
// wrapperCol={{ span: 18}}
autoComplete="off"
disabled={type === 'view'}
>
<Form.Item<FieldType>
label="申请原因"
name="reason"
>
<Input.TextArea className="w-INPUT_NORMAL" disabled={true} placeholder=" " />
</Form.Item>
<Form.Item<FieldType>
label="审核意见"
name="opinion"
extra="选择拒绝时,审批意见为必填"
>
<Input.TextArea className="w-INPUT_NORMAL" placeholder="请输入" onChange={()=>{ form.setFields([
{
name: 'opinion',
errors: [], // 设置为空数组来移除错误信息
},
])}} />
</Form.Item>
</Form>
</WithPermission>
</div>
)
})
@@ -0,0 +1,43 @@
import { Button, Tooltip } from "antd"
import { useState, useMemo, useEffect } from "react"
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
import { useNavigate } from "react-router-dom"
type TableBtnWithPermissionProps = {
btnTitle:string
access:string,
tooltip?:string,
disabled?:boolean,
navigateTo?:string,
onClick?:(args?:unknown)=>void
className?:string
}
// 表格操作栏按钮,受权限控制
const TableBtnWithPermission = ({btnTitle, access, tooltip, disabled, navigateTo, onClick,className}:TableBtnWithPermissionProps) => {
const [btnAccess, setBtnAccess] = useState<boolean>(false)
const {accessData,checkPermission} = useGlobalContext()
const navigate = useNavigate()
const lastAccess = useMemo(()=>{
if(!access) return true
return checkPermission(access)
},[access, accessData])
useEffect(()=>{
access ? setBtnAccess(lastAccess) : setBtnAccess(true)
},[])
return (<>{
!btnAccess || (disabled&&tooltip) ?
<Tooltip placement="top" title={tooltip ?? `暂无${btnTitle}权限,请联系管理员分配。`}>
<Button type="text" disabled={true} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className}`} key="view" >{btnTitle}</Button>
</Tooltip>
:
<Button type="text" disabled={disabled} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className} `} key="view" onClick={(e)=>{e.stopPropagation();navigateTo ? navigate(navigateTo) :onClick?.() }}>{btnTitle}</Button>
}</>
);
}
export default TableBtnWithPermission
@@ -0,0 +1,37 @@
import { Tag, TagProps } from "antd";
import { useState, useMemo, useEffect } from "react";
import { PERMISSION_DEFINITION } from "@common/const/permissions";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
export interface TagWithPermission extends TagProps{
access?:string
}
export default function TagWithPermission(props:TagWithPermission){
const {access,onClose} = props
const [editAccess, setEditAccess] = useState<boolean>(access ? false:true)
const {accessData,checkPermission} = useGlobalContext()
const lastAccess = useMemo(()=>{
if(!access) return true
return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0])
},[access, accessData,checkPermission])
useEffect(()=>{
access ? setEditAccess(lastAccess) : setEditAccess(true)
},[lastAccess])
const handleTagClose = (e: React.MouseEvent<HTMLElement>)=>{
e.preventDefault();
if(!editAccess) return
onClose?.(e)
}
return <Tag
closeIcon
{...props}
className={` rounded-SEARCH_RADIUS h-[32px] text-[14px] leading-[22px] py-[5px] px-btnbase bg-transparent mb-[8px] ${props.className}`}
onClose={handleTagClose}>
{props.children}
</Tag>
}

Some files were not shown because too many files have changed in this diff Show More