mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-04 10:13:53 +08:00
首次提交APIPark代码,面向开源
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
*.DS_Store
|
||||
/.idea/
|
||||
/config.yml
|
||||
@@ -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" //同步
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
//}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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("/"),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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'],
|
||||
};
|
||||
@@ -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';
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "independent"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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?
|
||||
|
||||
@@ -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 |
@@ -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();
|
||||
@@ -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'
|
||||
})
|
||||
+85
File diff suppressed because one or more lines are too long
@@ -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');
|
||||
@@ -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
Reference in New Issue
Block a user