mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47f6519006 | |||
| 9679376cb2 | |||
| 087e598be0 | |||
| 31aa8243ee | |||
| d3d05ef539 | |||
| d6062ea4e7 | |||
| 7949748951 | |||
| 0d737bad57 | |||
| e5d85bb3df | |||
| 0f0204b647 | |||
| 44f0b70461 | |||
| 588cf839e3 | |||
| b0b9affbe7 |
@@ -3,4 +3,3 @@
|
||||
/config.yml
|
||||
/build/
|
||||
/apipark
|
||||
/aoplatform
|
||||
|
||||
@@ -296,12 +296,12 @@ export const PERMISSION_DEFINITION = [
|
||||
},
|
||||
"team.service.service_intro.add": {
|
||||
"granted": {
|
||||
"anyOf": [{ "backend": [" team.service.service_intro.manager"] }]
|
||||
"anyOf": [{ "backend": ["team.service.service_intro.manager"] }]
|
||||
}
|
||||
},
|
||||
"team.service.service_intro.edit": {
|
||||
"granted": {
|
||||
"anyOf": [{ "backend": [" team.service.service_intro.manager"] }]
|
||||
"anyOf": [{ "backend": ["team.service.service_intro.manager"] }]
|
||||
}
|
||||
},
|
||||
"team.service.api_doc.import": {
|
||||
|
||||
@@ -58,7 +58,6 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setLlmList(data.llms)
|
||||
console.log(data)
|
||||
form.setFieldsValue({
|
||||
id:data.provider.defaultLlm,
|
||||
config:data.llms.find(x=>x.id===data.provider.defaultLlm)?.config})
|
||||
|
||||
@@ -346,7 +346,7 @@ const MemberList = ()=>{
|
||||
<WithPermission access="system.organization.member.edit">
|
||||
<Select
|
||||
className="w-full"
|
||||
mode="multiple"
|
||||
mode="multiple"
|
||||
value={entity.roles?.map((x:EntityItem)=>x.id)}
|
||||
options={roleSelectableList?.map((x:{id:string,name:string})=>({label:(x.name), value:x.id}))}
|
||||
onChange={(value)=>{
|
||||
|
||||
@@ -252,6 +252,7 @@ const TeamInsideMember:FC = ()=>{
|
||||
<Select
|
||||
className="w-full"
|
||||
mode="multiple"
|
||||
maxTagCount="responsive"
|
||||
value={entity.roles?.map((x:EntityItem)=>x.id)}
|
||||
options={roleList?.map((x:{id:string,name:string})=>({label:(x.name), value:x.id}))}
|
||||
onChange={(value)=>{
|
||||
|
||||
@@ -25,7 +25,6 @@ export const SERVICE_HUB_TABLE_COLUMNS: PageProColumns<ServiceHubTableListItem>[
|
||||
title:('团队'),
|
||||
dataIndex: ['team','name'],
|
||||
ellipsis:true,
|
||||
renderText:(_,entity:ServiceHubTableListItem)=>entity.tags?.map(x=>x.name).join(',') || '-'
|
||||
},
|
||||
{
|
||||
title:('订阅服务数量'),
|
||||
|
||||
@@ -37,13 +37,14 @@ export default function ServiceHubManagement() {
|
||||
const [tableListDataSource, setTableListDataSource] = useState<ServiceHubAppListItem[]>([]);
|
||||
const [tableSearchWord, setTableSearchWord] = useState<string>('')
|
||||
|
||||
const getServiceList = ()=>{
|
||||
const getServiceList = (dataType?:'block'|'list')=>{
|
||||
dataType = dataType ?? dataShowType
|
||||
if(!accessInit){
|
||||
getGlobalAccessData()?.then?.(()=>{getServiceList()})
|
||||
getGlobalAccessData()?.then?.(()=>{getServiceList(dataType)})
|
||||
return Promise.resolve({data:[], success:false})
|
||||
}
|
||||
|
||||
if(dataShowType === 'list' && !tableHttpReload){
|
||||
if(dataType === 'list' && !tableHttpReload){
|
||||
setTableHttpReload(true)
|
||||
return Promise.resolve({
|
||||
data: tableListDataSource,
|
||||
@@ -52,7 +53,7 @@ const getServiceList = ()=>{
|
||||
}
|
||||
|
||||
setServiceLoading(true)
|
||||
return fetchData<BasicResponse<{apps:ServiceHubAppListItem}>>(!checkPermission('system.workspace.application.view_all') ? 'my_apps':'apps',{method:'GET',eoParams:{ team:teamId,keyword:tableSearchWord},eoTransformKeys:['api_num','subscribe_num','subscribe_verify_num','auth_num']}).then(response=>{
|
||||
return fetchData<BasicResponse<{apps:ServiceHubAppListItem}>>(!checkPermission('system.workspace.application.view_all') ? 'my_apps':'apps',{method:'GET',eoParams:{ team: dataType === 'list' ? undefined : teamId,keyword:tableSearchWord},eoTransformKeys:['api_num','subscribe_num','subscribe_verify_num','auth_num','create_time','can_delete']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setServiceList([...data.apps,{type:'addNewItem'}])
|
||||
@@ -75,28 +76,69 @@ const getServiceList = ()=>{
|
||||
};
|
||||
|
||||
|
||||
const getTeamsList = ()=>{
|
||||
if(!accessInit){
|
||||
setTimeout(()=>{
|
||||
getGlobalAccessData()?.then?.(()=>{getTeamsList()})
|
||||
},200)
|
||||
return
|
||||
}
|
||||
setPageLoading(true)
|
||||
fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>(!checkPermission('system.workspace.team.view_all') ?'simple/teams/mine' :'simple/teams',{method:'GET',eoTransformKeys:['app_num','subscribe_num']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setTeamList(data.teams.map((x:SimpleTeamItem)=>({label:<div className="flex items-center justify-between "><span className="w-[calc(100%-42px)] truncate" title={x.name}>{x.name}</span><span className="bg-[#fff] rounded-[5px] h-[20px] w-[30px] flex items-center justify-center">{x.appNum || 0}</span></div>, key:x.id})))
|
||||
if(!teamId && data.teams?.[0]?.id){
|
||||
navigateTo(data.teams[0].id)
|
||||
const getTeamsList = () => {
|
||||
setPageLoading(true);
|
||||
|
||||
const fetchTeams = () => {
|
||||
fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>(
|
||||
!checkPermission('system.workspace.team.view_all')
|
||||
? 'simple/teams/mine'
|
||||
: 'simple/teams',
|
||||
{
|
||||
method: 'GET',
|
||||
eoTransformKeys: ['app_num', 'subscribe_num'],
|
||||
}
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).finally(()=>{
|
||||
setPageLoading(false)
|
||||
})
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
const { code, data, msg } = response;
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setTeamList(
|
||||
data.teams.map((x: SimpleTeamItem) => ({
|
||||
label: (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="w-[calc(100%-42px)] truncate" title={x.name}>
|
||||
{x.name}
|
||||
</span>
|
||||
<span className="bg-[#fff] rounded-[5px] h-[20px] w-[30px] flex items-center justify-center">
|
||||
{x.appNum || 0}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
key: x.id,
|
||||
}))
|
||||
);
|
||||
if (!teamId && data.teams?.[0]?.id) {
|
||||
navigateTo(data.teams[0].id);
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setPageLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
if (!accessInit) {
|
||||
const checkAccessData = () => {
|
||||
const accessInitd = getGlobalAccessData();
|
||||
if (!accessInitd) {
|
||||
setTimeout(checkAccessData, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof accessInitd.then === 'function') {
|
||||
accessInitd.then(fetchTeams);
|
||||
} else {
|
||||
fetchTeams();
|
||||
}
|
||||
};
|
||||
|
||||
checkAccessData();
|
||||
} else {
|
||||
fetchTeams();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const openModal = async (type:'add'|'edit'|'delete')=>{
|
||||
@@ -169,7 +211,6 @@ useEffect(() => {
|
||||
)
|
||||
getTeamsList()
|
||||
setAppName('')
|
||||
console.log()
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -244,13 +285,13 @@ useEffect(() => {
|
||||
customBtn={
|
||||
<Radio.Group
|
||||
options={dataShowTypeOptions}
|
||||
onChange={(e)=>setDataShowType(e.target.value)}
|
||||
onChange={(e)=>{setDataShowType(e.target.value); setTableHttpReload(true); if(e.target.value === 'block'){getServiceList(e.target.value)}}}
|
||||
value={dataShowType}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
/>}
|
||||
>{
|
||||
dataShowType === 'block' ? <BlockArea /> : <TableArea language={state.language} getServiceList={getServiceList} addNewApp={()=>openModal('add')} setTableHttpReload={setTableHttpReload} setTableSearchWord={setTableSearchWord} editApp={(row:ServiceHubAppListItem)=>{setAppName(row.name);navigateTo(`/consumer/${row.team.id}/inside/${row.id}/service`)}}/>
|
||||
dataShowType === 'block' ? <BlockArea /> : <TableArea language={state.language} getServiceList={()=>getServiceList('list')} addNewApp={()=>openModal('add')} setTableHttpReload={setTableHttpReload} setTableSearchWord={setTableSearchWord} editApp={(row:ServiceHubAppListItem)=>{setAppName(row.name);navigateTo(`/consumer/${row.team.id}/inside/${row.id}/service`)}}/>
|
||||
}
|
||||
</InsidePage> :
|
||||
<Empty className="mt-[100px]" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package main
|
||||
|
||||
// init module
|
||||
import (
|
||||
_ "github.com/APIParkLab/APIPark/frontend"
|
||||
_ "github.com/APIParkLab/APIPark/gateway/apinto"
|
||||
|
||||
@@ -9,5 +9,4 @@ import (
|
||||
)
|
||||
|
||||
func doCheck() {
|
||||
|
||||
}
|
||||
|
||||
+14
-14
@@ -6,6 +6,11 @@ import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/APIParkLab/APIPark/resources/access"
|
||||
_ "github.com/APIParkLab/APIPark/resources/permit"
|
||||
_ "github.com/APIParkLab/APIPark/resources/plugin"
|
||||
@@ -14,19 +19,15 @@ import (
|
||||
"github.com/eolinker/go-common/permit"
|
||||
"github.com/eolinker/go-common/pm3"
|
||||
"github.com/eolinker/go-common/utils"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const unsetValue = "-"
|
||||
|
||||
func doCheck() {
|
||||
accessConf, unset := loadAccess()
|
||||
|
||||
|
||||
drivers := pm3.List()
|
||||
|
||||
|
||||
newAccess := 0
|
||||
for _, p := range drivers {
|
||||
if ac, ok := p.(pm3.AccessConfig); ok {
|
||||
@@ -39,9 +40,9 @@ func doCheck() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
for asKey := range permit.All() {
|
||||
key := strings.ToLower(asKey)
|
||||
@@ -53,12 +54,11 @@ func doCheck() {
|
||||
if newAccess > 0 || unset > 0 {
|
||||
f := accessFile()
|
||||
fmt.Printf("%d access need set, see : %s and %s", newAccess+unset, saveTemplate(accessConf, f), saveCsv(accessConf, f))
|
||||
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
func accessFile() string {
|
||||
|
||||
|
||||
if version == "" {
|
||||
return time.Now().Format("20060102-150405")
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func saveCsv(as map[string]*Access, key string) string {
|
||||
err = os.WriteFile(filePath, buf.Bytes(), 0666)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
@@ -111,9 +111,9 @@ func (ls AccessListSort) Swap(i, j int) {
|
||||
|
||||
func saveTemplate(as map[string]*Access, key string) string {
|
||||
out := make(map[string][]access.Access)
|
||||
|
||||
|
||||
for _, a := range as {
|
||||
|
||||
|
||||
out[a.Group] = append(out[a.Group], access.Access{
|
||||
Name: a.Name,
|
||||
CName: a.Cname,
|
||||
@@ -130,7 +130,7 @@ func saveTemplate(as map[string]*Access, key string) string {
|
||||
err = os.WriteFile(filePath, buf.Bytes(), 0666)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/eolinker/eosc/log"
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
"github.com/eolinker/go-common/cftool"
|
||||
"github.com/eolinker/go-common/permit"
|
||||
"github.com/eolinker/go-common/server"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -54,7 +55,6 @@ func main() {
|
||||
for access, paths := range srv.Permits() {
|
||||
permit.AddPermitRule(access, paths...)
|
||||
}
|
||||
|
||||
err = http.Serve(ln, srv)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
@@ -3,7 +3,7 @@ package permit_middleware
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
|
||||
permit_identity "github.com/APIParkLab/APIPark/middleware/permit/identity"
|
||||
"github.com/eolinker/eosc/log"
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
@@ -42,11 +42,11 @@ func (p *PermitMiddleware) Sort() int {
|
||||
func (p *PermitMiddleware) Check(method string, path string) (bool, []gin.HandlerFunc) {
|
||||
// 当前路径是否有配置权限
|
||||
accessRules, has := permit.GetPathRule(method, path)
|
||||
|
||||
|
||||
if !has || len(accessRules) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
||||
return true, []gin.HandlerFunc{
|
||||
func(ginCtx *gin.Context) {
|
||||
userId := utils.UserId(ginCtx)
|
||||
@@ -56,19 +56,14 @@ func (p *PermitMiddleware) Check(method string, path string) (bool, []gin.Handle
|
||||
ginCtx.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
//if userId == "admin" {
|
||||
// // 超级管理员不校验
|
||||
// return
|
||||
//}
|
||||
|
||||
|
||||
for _, group := range checkSort {
|
||||
accessList, has := accessRules[group]
|
||||
if !has {
|
||||
// 当前分组没有配置权限
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
domainHandler, has := permit.SelectDomain(group)
|
||||
if !has {
|
||||
// 当前分组没有配置身份handler
|
||||
|
||||
@@ -9,6 +9,7 @@ type Kind int
|
||||
|
||||
func (k *Kind) UnmarshalJSON(bytes []byte) error {
|
||||
str := ""
|
||||
|
||||
err := json.Unmarshal(bytes, &str)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+4
-11
@@ -348,20 +348,13 @@ func (i *imlProviderModule) UpdateProviderDefaultLLM(ctx context.Context, id str
|
||||
})
|
||||
}
|
||||
|
||||
func (i *imlProviderModule) getAiProviders(ctx context.Context, clusterId string) ([]*gateway.DynamicRelease, error) {
|
||||
list, err := i.providerService.List(ctx, clusterId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (i *imlProviderModule) getAiProviders(ctx context.Context) ([]*gateway.DynamicRelease, error) {
|
||||
list, err := i.providerService.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providers := make([]*gateway.DynamicRelease, 0, len(list))
|
||||
for _, p := range list {
|
||||
if !p.Status {
|
||||
// 关闭
|
||||
continue
|
||||
}
|
||||
cfg := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(p.Config), &cfg)
|
||||
if err != nil {
|
||||
@@ -384,7 +377,7 @@ func (i *imlProviderModule) getAiProviders(ctx context.Context, clusterId string
|
||||
return providers, nil
|
||||
}
|
||||
func (i *imlProviderModule) initGateway(ctx context.Context, clusterId string, clientDriver gateway.IClientDriver) error {
|
||||
providers, err := i.getAiProviders(ctx, clusterId)
|
||||
providers, err := i.getAiProviders(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -401,7 +394,7 @@ func (i *imlProviderModule) initGateway(ctx context.Context, clusterId string, c
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = client.Online(ctx, providers...)
|
||||
err = client.Online(ctx, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ system:
|
||||
- "DELETE:/api/v1/certificate"
|
||||
dependents:
|
||||
- system.settings.ssl_certificate.view
|
||||
- name: Data Source
|
||||
- name: data source
|
||||
value: 'data_source'
|
||||
children:
|
||||
- name: view
|
||||
@@ -264,7 +264,7 @@ team:
|
||||
- "DELETE:/api/v1/service/release"
|
||||
dependents:
|
||||
- team.service.release.view
|
||||
- name: subscription management
|
||||
- name: subscription review
|
||||
value: 'subscription'
|
||||
children:
|
||||
- name: view
|
||||
@@ -296,13 +296,12 @@ team:
|
||||
- name: consumer
|
||||
value: 'consumer'
|
||||
children:
|
||||
- name: subscription Service
|
||||
- name: subscription service
|
||||
cname: 订阅服务
|
||||
value: 'subscription'
|
||||
children:
|
||||
- name: subscribe
|
||||
- name: allow subscribe service
|
||||
value: 'subscribe'
|
||||
|
||||
- name: view subscribed services
|
||||
value: 'view_subscribed_service'
|
||||
guest_allow: true
|
||||
@@ -371,20 +370,15 @@ team:
|
||||
value: 'manager'
|
||||
dependents:
|
||||
- team.team.member.view
|
||||
- name: team
|
||||
- name: team settings
|
||||
value: 'team'
|
||||
children:
|
||||
- name: view
|
||||
value: 'view'
|
||||
guest_allow: true
|
||||
apis:
|
||||
- "GET:/api/v1/manager/teams"
|
||||
- "GET:/api/v1/manager/team"
|
||||
- name: manager
|
||||
value: 'manager'
|
||||
apis:
|
||||
- "POST:/api/v1/manager/team"
|
||||
- "PUT:/api/v1/manager/team"
|
||||
- "DELETE:/api/v1/manager/team"
|
||||
dependents:
|
||||
- team.team.team.view
|
||||
@@ -1,44 +1,46 @@
|
||||
{
|
||||
"ai provider": "AI供应商",
|
||||
"api market": "API门户",
|
||||
"account": "账号",
|
||||
"ai provider": "AI模型供应商",
|
||||
"analysis": "分析报告",
|
||||
"api": "API",
|
||||
"api doc": "API文档",
|
||||
"application": "应用",
|
||||
"application admin": "应用管理员",
|
||||
"application developer": "应用开发者",
|
||||
"authorization": "鉴权",
|
||||
"cluster": "集群",
|
||||
"dashboard": "仪表盘",
|
||||
"api gateway": "API网关",
|
||||
"api portal": "API门户",
|
||||
"authorization": "访问授权",
|
||||
"consumer": "消费者",
|
||||
"consumer admin": "消费者管理员",
|
||||
"consumer developer": "消费者开发者",
|
||||
"create": "创建",
|
||||
"data source": "数据源",
|
||||
"devops": "运维",
|
||||
"devops admin": "运维管理员",
|
||||
"general": "常规设置",
|
||||
"general member": "普通成员",
|
||||
"guest": "访客",
|
||||
"log configuration": "日志配置",
|
||||
"manager": "管理",
|
||||
"manager all consumer": "管理所有消费者",
|
||||
"manager subscribed services": "管理已订阅的服务",
|
||||
"member": "成员",
|
||||
"organization": "组织管理",
|
||||
"release": "发布",
|
||||
"role": "角色",
|
||||
"router": "路由",
|
||||
"run view": "运行视图",
|
||||
"service": "服务",
|
||||
"service admin": "服务管理员",
|
||||
"service classification": "服务目录",
|
||||
"service developer": "服务开发者",
|
||||
"service intro": "服务文档",
|
||||
"ssl certificate": "SSL证书",
|
||||
"subscription management": "订阅方管理",
|
||||
"allow subscribe service": "允许订阅服务",
|
||||
"subscription review": "订阅审核",
|
||||
"subscription service": "订阅服务",
|
||||
"super admin": "超级管理员",
|
||||
"system settings": "系统设置",
|
||||
"team": "团队",
|
||||
"team settings": "团队设置",
|
||||
"team admin": "团队管理员",
|
||||
"upstream": "上游",
|
||||
"view": "查看",
|
||||
"view all application": "查看所有应用",
|
||||
"view all consumer": "查看所有消费者",
|
||||
"view all service": "查看所有服务",
|
||||
"view all team": "查看所有团队",
|
||||
"view system role": "查看系统角色",
|
||||
"view team role": "查看团队角色",
|
||||
"view subscribed services": "查看已经订阅的服务",
|
||||
"workspace": "工作空间"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# 名称:apinto通用镜像
|
||||
# 名称:apipark通用镜像
|
||||
# 创建时间:2022-10-25
|
||||
FROM centos:7.9.2009
|
||||
MAINTAINER liujian
|
||||
|
||||
Executable → Regular
+1
@@ -25,6 +25,7 @@ cd "./eosc" && git pull
|
||||
# =========================================================================
|
||||
echo "更新 aoaccount"
|
||||
cd "${BASEPATH}/"
|
||||
|
||||
if [ ! -d "./aoaccount" ]; then
|
||||
git clone http://gitlab.eolink.com/apinto/aoaccount.git
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user