1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-02-28 11:04:14 +08:00

feat: Merge code of gpu from dev (#7982)

This commit is contained in:
ssongliu 2025-02-24 18:48:47 +08:00 committed by GitHub
parent efa2fa9eac
commit 90eca45347
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
57 changed files with 4067 additions and 359 deletions

256
agent/app/api/v2/ai.go Normal file
View File

@ -0,0 +1,256 @@
package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
"github.com/gin-gonic/gin"
)
// @Tags AI
// @Summary Create Ollama model
// @Accept json
// @Param request body dto.OllamaModelName true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型 [name]","formatEN":"add Ollama model [name]"}
func (b *BaseApi) CreateOllamaModel(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := aiToolService.Create(req.Name); err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags AI
// @Summary Rereate Ollama model
// @Accept json
// @Param request body dto.OllamaModelName true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/recreate [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型重试 [name]","formatEN":"re-add Ollama model [name]"}
func (b *BaseApi) RecreateOllamaModel(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := aiToolService.Recreate(req.Name); err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags AI
// @Summary Close Ollama model conn
// @Accept json
// @Param request body dto.OllamaModelName true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/close [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"关闭 Ollama 模型连接 [name]","formatEN":"close conn for Ollama model [name]"}
func (b *BaseApi) CloseOllamaModel(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := aiToolService.Close(req.Name); err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags AI
// @Summary Sync Ollama model list
// @Success 200 {array} dto.OllamaModelDropList
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/sync [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 Ollama 模型列表","formatEN":"sync Ollama model list"}
func (b *BaseApi) SyncOllamaModel(c *gin.Context) {
list, err := aiToolService.Sync()
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags AI
// @Summary Page Ollama models
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/search [post]
func (b *BaseApi) SearchOllamaModel(c *gin.Context) {
var req dto.SearchWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := aiToolService.Search(req)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags AI
// @Summary Page Ollama models
// @Accept json
// @Param request body dto.OllamaModelName true "request"
// @Success 200 {string} details
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/load [post]
func (b *BaseApi) LoadOllamaModelDetail(c *gin.Context) {
var req dto.OllamaModelName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
detail, err := aiToolService.LoadDetail(req.Name)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, detail)
}
// @Tags AI
// @Summary Delete Ollama model
// @Accept json
// @Param request body dto.ForceDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"ollama_models","output_column":"name","output_value":"names"}],"formatZH":"删除 Ollama 模型 [names]","formatEN":"remove Ollama model [names]"}
func (b *BaseApi) DeleteOllamaModel(c *gin.Context) {
var req dto.ForceDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := aiToolService.Delete(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags AI
// @Summary Load gpu / xpu info
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/gpu/load [get]
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
ok, client := gpu.New()
if ok {
info, err := client.LoadGpuInfo()
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, info)
return
}
xpuOK, xpuClient := xpu.New()
if xpuOK {
info, err := xpuClient.LoadGpuInfo()
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, info)
return
}
helper.SuccessWithData(c, &common.GpuInfo{})
}
// @Tags AI
// @Summary Bind domain
// @Accept json
// @Param request body dto.OllamaBindDomain true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/domain/bind [post]
func (b *BaseApi) BindDomain(c *gin.Context) {
var req dto.OllamaBindDomain
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := aiToolService.BindDomain(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags AI
// @Summary Get bind domain
// @Accept json
// @Param request body dto.OllamaBindDomainReq true "request"
// @Success 200 {object} dto.OllamaBindDomainRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/domain/get [post]
func (b *BaseApi) GetBindDomain(c *gin.Context) {
var req dto.OllamaBindDomainReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, err := aiToolService.GetBindDomain(req)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, res)
}
// Tags AI
// Summary Update bind domain
// Accept json
// Param request body dto.OllamaBindDomain true "request"
// Success 200
// Security ApiKeyAuth
// Security Timestamp
// Router /ai/domain/update [post]
func (b *BaseApi) UpdateBindDomain(c *gin.Context) {
var req dto.OllamaBindDomain
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := aiToolService.UpdateBindDomain(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@ -16,6 +16,8 @@ var (
appService = service.NewIAppService()
appInstallService = service.NewIAppInstalledService()
aiToolService = service.NewIAIToolService()
containerService = service.NewIContainerService()
composeTemplateService = service.NewIComposeTemplateService()
imageRepoService = service.NewIImageRepoService()

View File

@ -10,6 +10,7 @@ import (
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/terminal"
@ -165,6 +166,70 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
}
}
func (b *BaseApi) OllamaWsSsh(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
if global.CONF.Base.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return
}
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
name := c.Query("name")
if cmd.CheckIllegal(name) {
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
return
}
}
container, err := service.LoadContainerName()
if wshandleError(wsConn, errors.WithMessage(err, " load container name for ollama failed")) {
return
}
commands := []string{"ollama", "run", name}
pidMap := loadMapFromDockerTop(container)
fmt.Println("pidMap")
for k, v := range pidMap {
fmt.Println(k, v)
}
itemCmds := append([]string{"exec", "-it", container}, commands...)
slave, err := terminal.NewCommand(itemCmds)
if wshandleError(wsConn, err) {
return
}
defer killBash(container, strings.Join(commands, " "), pidMap)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
if wshandleError(wsConn, err) {
return
}
quitChan := make(chan bool, 3)
tty.Start(quitChan)
go slave.Wait(quitChan)
<-quitChan
global.LOG.Info("websocket finished")
if wshandleError(wsConn, err) {
return
}
}
func wshandleError(ws *websocket.Conn, err error) bool {
if err != nil {
global.LOG.Errorf("handler ws faled:, err: %v", err)

44
agent/app/dto/ai.go Normal file
View File

@ -0,0 +1,44 @@
package dto
import "time"
type OllamaModelInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Size string `json:"size"`
From string `json:"from"`
LogFileExist bool `json:"logFileExist"`
Status string `json:"status"`
Message string `json:"message"`
CreatedAt time.Time `json:"createdAt"`
}
type OllamaModelDropList struct {
ID uint `json:"id"`
Name string `json:"name"`
}
type OllamaModelName struct {
Name string `json:"name"`
}
type OllamaBindDomain struct {
Domain string `json:"domain" validate:"required"`
AppInstallID uint `json:"appInstallID" validate:"required"`
SSLID uint `json:"sslID"`
WebsiteID uint `json:"websiteID"`
IPList string `json:"ipList"`
}
type OllamaBindDomainReq struct {
AppInstallID uint `json:"appInstallID" validate:"required"`
}
type OllamaBindDomainRes struct {
Domain string `json:"domain"`
SSLID uint `json:"sslID"`
AllowIPs []string `json:"allowIPs"`
WebsiteID uint `json:"websiteID"`
ConnUrl string `json:"connUrl"`
}

View File

@ -71,3 +71,8 @@ type UpdateGroup struct {
type OperateWithTask struct {
TaskID string `json:"taskID"`
}
type ForceDelete struct {
IDs []uint `json:"ids"`
ForceDelete bool `json:"forceDelete"`
}

11
agent/app/model/ai.go Normal file
View File

@ -0,0 +1,11 @@
package model
type OllamaModel struct {
BaseModel
Name string `json:"name"`
Size string `json:"size"`
From string `json:"from"`
Status string `json:"status"`
Message string `json:"message"`
}

69
agent/app/repo/ai.go Normal file
View File

@ -0,0 +1,69 @@
package repo
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
)
type AiRepo struct{}
type IAiRepo interface {
Get(opts ...DBOption) (model.OllamaModel, error)
List(opts ...DBOption) ([]model.OllamaModel, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.OllamaModel, error)
Create(cronjob *model.OllamaModel) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
}
func NewIAiRepo() IAiRepo {
return &AiRepo{}
}
func (u *AiRepo) Get(opts ...DBOption) (model.OllamaModel, error) {
var item model.OllamaModel
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&item).Error
return item, err
}
func (u *AiRepo) List(opts ...DBOption) ([]model.OllamaModel, error) {
var list []model.OllamaModel
db := global.DB.Model(&model.OllamaModel{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&list).Error
return list, err
}
func (u *AiRepo) Page(page, size int, opts ...DBOption) (int64, []model.OllamaModel, error) {
var list []model.OllamaModel
db := global.DB.Model(&model.OllamaModel{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&list).Error
return count, list, err
}
func (u *AiRepo) Create(item *model.OllamaModel) error {
return global.DB.Create(item).Error
}
func (u *AiRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.OllamaModel{}).Where("id = ?", id).Updates(vars).Error
}
func (u *AiRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.OllamaModel{}).Error
}

393
agent/app/service/ai.go Normal file
View File

@ -0,0 +1,393 @@
package service
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/jinzhu/copier"
)
type AIToolService struct{}
type IAIToolService interface {
Search(search dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error)
Create(name string) error
Close(name string) error
Recreate(name string) error
Delete(req dto.ForceDelete) error
Sync() ([]dto.OllamaModelDropList, error)
LoadDetail(name string) (string, error)
BindDomain(req dto.OllamaBindDomain) error
GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error)
UpdateBindDomain(req dto.OllamaBindDomain) error
}
func NewIAIToolService() IAIToolService {
return &AIToolService{}
}
func (u *AIToolService) Search(req dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error) {
var options []repo.DBOption
if len(req.Info) != 0 {
options = append(options, repo.WithByLikeName(req.Info))
}
total, list, err := aiRepo.Page(req.Page, req.PageSize, options...)
if err != nil {
return 0, nil, err
}
var dtoLists []dto.OllamaModelInfo
for _, itemModel := range list {
var item dto.OllamaModelInfo
if err := copier.Copy(&item, &itemModel); err != nil {
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
logPath := path.Join(global.Dir.DataDir, "log", "AITools", itemModel.Name)
if _, err := os.Stat(logPath); err == nil {
item.LogFileExist = true
}
dtoLists = append(dtoLists, item)
}
return int64(total), dtoLists, err
}
func (u *AIToolService) LoadDetail(name string) (string, error) {
if cmd.CheckIllegal(name) {
return "", buserr.New("ErrCmdIllegal")
}
containerName, err := LoadContainerName()
if err != nil {
return "", err
}
stdout, err := cmd.Execf("docker exec %s ollama show %s", containerName, name)
if err != nil {
return "", err
}
return stdout, err
}
func (u *AIToolService) Create(name string) error {
if cmd.CheckIllegal(name) {
return buserr.New("ErrCmdIllegal")
}
modelInfo, _ := aiRepo.Get(repo.WithByName(name))
if modelInfo.ID != 0 {
return buserr.New("ErrRecordExist")
}
containerName, err := LoadContainerName()
if err != nil {
return err
}
logItem := path.Join(global.Dir.DataDir, "log", "AITools", name)
if _, err := os.Stat(path.Dir(logItem)); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(logItem), os.ModePerm); err != nil {
return err
}
}
info := model.OllamaModel{
Name: name,
From: "local",
Status: constant.StatusWaiting,
}
if err := aiRepo.Create(&info); err != nil {
return err
}
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
go pullOllamaModel(file, containerName, info)
return nil
}
func (u *AIToolService) Close(name string) error {
if cmd.CheckIllegal(name) {
return buserr.New("ErrCmdIllegal")
}
containerName, err := LoadContainerName()
if err != nil {
return err
}
stdout, err := cmd.Execf("docker exec %s ollama stop %s", containerName, name)
if err != nil {
return fmt.Errorf("handle ollama stop %s failed, stdout: %s, err: %v", name, stdout, err)
}
return nil
}
func (u *AIToolService) Recreate(name string) error {
if cmd.CheckIllegal(name) {
return buserr.New("ErrCmdIllegal")
}
modelInfo, _ := aiRepo.Get(repo.WithByName(name))
if modelInfo.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
containerName, err := LoadContainerName()
if err != nil {
return err
}
if err := aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusWaiting, "from": "local"}); err != nil {
return err
}
logItem := path.Join(global.Dir.DataDir, "log", "AITools", name)
if _, err := os.Stat(path.Dir(logItem)); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(logItem), os.ModePerm); err != nil {
return err
}
}
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
go pullOllamaModel(file, containerName, modelInfo)
return nil
}
func (u *AIToolService) Delete(req dto.ForceDelete) error {
ollamaList, _ := aiRepo.List(repo.WithByIDs(req.IDs))
if len(ollamaList) == 0 {
return buserr.New("ErrRecordNotFound")
}
containerName, err := LoadContainerName()
if err != nil && !req.ForceDelete {
return err
}
for _, item := range ollamaList {
if item.Status != constant.StatusDeleted {
stdout, err := cmd.Execf("docker exec %s ollama rm %s", containerName, item.Name)
if err != nil && !req.ForceDelete {
return fmt.Errorf("handle ollama rm %s failed, stdout: %s, err: %v", item.Name, stdout, err)
}
}
_ = aiRepo.Delete(repo.WithByID(item.ID))
logItem := path.Join(global.Dir.DataDir, "log", "AITools", item.Name)
_ = os.Remove(logItem)
}
return nil
}
func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) {
containerName, err := LoadContainerName()
if err != nil {
return nil, err
}
stdout, err := cmd.Execf("docker exec %s ollama list", containerName)
if err != nil {
return nil, err
}
var list []model.OllamaModel
lines := strings.Split(stdout, "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) < 5 {
continue
}
if parts[0] == "NAME" {
continue
}
list = append(list, model.OllamaModel{Name: parts[0], Size: parts[2] + " " + parts[3]})
}
listInDB, _ := aiRepo.List()
var dropList []dto.OllamaModelDropList
for _, itemModel := range listInDB {
isExit := false
for i := 0; i < len(list); i++ {
if list[i].Name == itemModel.Name {
_ = aiRepo.Update(itemModel.ID, map[string]interface{}{"status": constant.StatusSuccess, "message": "", "size": list[i].Size})
list = append(list[:i], list[(i+1):]...)
isExit = true
break
}
}
if !isExit && itemModel.Status != constant.StatusWaiting {
_ = aiRepo.Update(itemModel.ID, map[string]interface{}{"status": constant.StatusDeleted, "message": "not exist", "size": ""})
dropList = append(dropList, dto.OllamaModelDropList{ID: itemModel.ID, Name: itemModel.Name})
continue
}
}
for _, item := range list {
item.Status = constant.StatusSuccess
item.From = "remote"
_ = aiRepo.Create(&item)
}
return dropList, nil
}
func (u *AIToolService) BindDomain(req dto.OllamaBindDomain) error {
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
if nginxInstall.ID == 0 {
return buserr.New("ErrOpenrestyInstall")
}
var (
ipList []string
err error
)
if len(req.IPList) > 0 {
ipList, err = common.HandleIPList(req.IPList)
if err != nil {
return err
}
}
createWebsiteReq := request.WebsiteCreate{
Domains: []request.WebsiteDomain{{Domain: req.Domain}},
Alias: strings.ToLower(req.Domain),
Type: constant.Deployment,
AppType: constant.InstalledApp,
AppInstallID: req.AppInstallID,
}
websiteService := NewIWebsiteService()
if err := websiteService.CreateWebsite(createWebsiteReq); err != nil {
return err
}
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(strings.ToLower(req.Domain)))
if err != nil {
return err
}
if len(ipList) > 0 {
if err = ConfigAllowIPs(ipList, website); err != nil {
return err
}
}
if req.SSLID > 0 {
sslReq := request.WebsiteHTTPSOp{
WebsiteID: website.ID,
Enable: true,
Type: "existed",
WebsiteSSLID: req.SSLID,
HttpConfig: "HTTPSOnly",
}
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
return err
}
}
if err = ConfigAIProxy(website); err != nil {
return err
}
return nil
}
func (u *AIToolService) GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error) {
install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID))
if err != nil {
return nil, err
}
res := &dto.OllamaBindDomainRes{}
website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(install.ID))
if website.ID == 0 {
return res, nil
}
res.WebsiteID = website.ID
res.Domain = website.PrimaryDomain
if website.WebsiteSSLID > 0 {
res.SSLID = website.WebsiteSSLID
}
res.ConnUrl = fmt.Sprintf("%s://%s", strings.ToLower(website.Protocol), website.PrimaryDomain)
res.AllowIPs = GetAllowIps(website)
return res, nil
}
func (u *AIToolService) UpdateBindDomain(req dto.OllamaBindDomain) error {
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
if nginxInstall.ID == 0 {
return buserr.New("ErrOpenrestyInstall")
}
var (
ipList []string
err error
)
if len(req.IPList) > 0 {
ipList, err = common.HandleIPList(req.IPList)
if err != nil {
return err
}
}
websiteService := NewIWebsiteService()
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
if err != nil {
return err
}
if err = ConfigAllowIPs(ipList, website); err != nil {
return err
}
if req.SSLID > 0 {
sslReq := request.WebsiteHTTPSOp{
WebsiteID: website.ID,
Enable: true,
Type: "existed",
WebsiteSSLID: req.SSLID,
HttpConfig: "HTTPSOnly",
}
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
return err
}
return nil
}
if website.WebsiteSSLID > 0 && req.SSLID == 0 {
sslReq := request.WebsiteHTTPSOp{
WebsiteID: website.ID,
Enable: false,
}
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
return err
}
}
return nil
}
func LoadContainerName() (string, error) {
ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "")
if err != nil {
return "", fmt.Errorf("ollama service is not found, err: %v", err)
}
if ollamaBaseInfo.Status != constant.Running {
return "", fmt.Errorf("container %s of ollama is not running, please check and retry!", ollamaBaseInfo.ContainerName)
}
return ollamaBaseInfo.ContainerName, nil
}
func pullOllamaModel(file *os.File, containerName string, info model.OllamaModel) {
defer file.Close()
cmd := exec.Command("docker", "exec", containerName, "ollama", "pull", info.Name)
multiWriter := io.MultiWriter(os.Stdout, file)
cmd.Stdout = multiWriter
cmd.Stderr = multiWriter
_ = cmd.Run()
itemSize, err := loadModelSize(info.Name, containerName)
if len(itemSize) != 0 {
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize})
} else {
_ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
}
_, _ = file.WriteString("ollama pull completed!")
}
func loadModelSize(name string, containerName string) (string, error) {
stdout, err := cmd.Execf("docker exec %s ollama list | grep %s", containerName, name)
if err != nil {
return "", err
}
lines := strings.Split(string(stdout), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) < 5 {
continue
}
return parts[2] + " " + parts[3], nil
}
return "", fmt.Errorf("no such model %s in ollama list, std: %s", name, string(stdout))
}

View File

@ -17,10 +17,11 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
@ -492,7 +493,17 @@ func loadDiskInfo() []dto.DiskInfo {
}
func loadGPUInfo() []dto.GPUInfo {
list := xpack.LoadGpuInfo()
ok, client := gpu.New()
var list []interface{}
if ok {
info, err := client.LoadGpuInfo()
if err != nil || len(info.GPUs) == 0 {
return nil
}
for _, item := range info.GPUs {
list = append(list, item)
}
}
if len(list) == 0 {
return nil
}
@ -542,7 +553,17 @@ func ArryContains(arr []string, element string) bool {
}
func loadXpuInfo() []dto.XPUInfo {
list := xpack.LoadXpuInfo()
var list []interface{}
ok, xpuClient := xpu.New()
if ok {
xpus, err := xpuClient.LoadDashData()
if err != nil || len(xpus) == 0 {
return nil
}
for _, item := range xpus {
list = append(list, item)
}
}
if len(list) == 0 {
return nil
}

View File

@ -11,6 +11,8 @@ var (
launcherRepo = repo.NewILauncherRepo()
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
aiRepo = repo.NewIAiRepo()
mysqlRepo = repo.NewIMysqlRepo()
postgresqlRepo = repo.NewIPostgresqlRepo()
databaseRepo = repo.NewIDatabaseRepo()

View File

@ -1231,3 +1231,55 @@ func openProxyCache(website model.Website) error {
proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:5m max_size=1g inactive=24h", website.Alias, website.Alias)
return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website)
}
func ConfigAllowIPs(ips []string, website model.Website) error {
nginxFull, err := getNginxFull(&website)
if err != nil {
return err
}
nginxConfig := nginxFull.SiteConfig
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
server.RemoveDirective("allow", nil)
server.RemoveDirective("deny", nil)
if len(ips) > 0 {
server.UpdateAllowIPs(ips)
}
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
}
return nginxCheckAndReload(nginxConfig.OldContent, config.FilePath, nginxFull.Install.ContainerName)
}
func GetAllowIps(website model.Website) []string {
nginxFull, err := getNginxFull(&website)
if err != nil {
return nil
}
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
dirs := server.GetDirectives()
var ips []string
for _, dir := range dirs {
if dir.GetName() == "allow" {
ips = append(ips, dir.GetParameters()...)
}
}
return ips
}
func ConfigAIProxy(website model.Website) error {
nginxFull, err := getNginxFull(&website)
if err != nil {
return nil
}
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
dirs := server.GetDirectives()
for _, dir := range dirs {
if dir.GetName() == "location" && dir.GetParameters()[0] == "/" {
server.UpdateRootProxyForAi([]string{fmt.Sprintf("http://%s", website.Proxy)})
}
}
return nil
}

View File

@ -24,6 +24,7 @@ func InitAgentDB() {
migrations.InitBackup,
migrations.UpdateAppTag,
migrations.UpdateApp,
migrations.AddOllamaModel,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View File

@ -265,3 +265,13 @@ var UpdateApp = &gormigrate.Migration{
return nil
},
}
var AddOllamaModel = &gormigrate.Migration{
ID: "20250218-add-ollama-model",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.OllamaModel{}); err != nil {
return err
}
return nil
},
}

29
agent/router/ai.go Normal file
View File

@ -0,0 +1,29 @@
package router
import (
v1 "github.com/1Panel-dev/1Panel/agent/app/api/v2"
"github.com/gin-gonic/gin"
)
type AIToolsRouter struct {
}
func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter := Router.Group("ai")
baseApi := v1.ApiGroupApp.BaseApi
{
aiToolsRouter.GET("/ollama/exec", baseApi.OllamaWsSsh)
aiToolsRouter.POST("/ollama/close", baseApi.CloseOllamaModel)
aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel)
aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel)
aiToolsRouter.POST("/ollama/model/search", baseApi.SearchOllamaModel)
aiToolsRouter.POST("/ollama/model/sync", baseApi.SyncOllamaModel)
aiToolsRouter.POST("/ollama/model/load", baseApi.LoadOllamaModelDetail)
aiToolsRouter.POST("/ollama/model/del", baseApi.DeleteOllamaModel)
aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo)
aiToolsRouter.POST("/domain/bind", baseApi.BindDomain)
aiToolsRouter.POST("/domain/get", baseApi.GetBindDomain)
aiToolsRouter.POST("/domain/update", baseApi.UpdateBindDomain)
}
}

View File

@ -21,5 +21,6 @@ func commonGroups() []CommonRouter {
&RuntimeRouter{},
&ProcessRouter{},
&WebsiteCARouter{},
&AIToolsRouter{},
}
}

View File

@ -0,0 +1,37 @@
package common
type GpuInfo struct {
CudaVersion string `json:"cudaVersion"`
DriverVersion string `json:"driverVersion"`
Type string `json:"type"`
GPUs []GPU `json:"gpu"`
}
type GPU struct {
Index uint `json:"index"`
ProductName string `json:"productName"`
PersistenceMode string `json:"persistenceMode"`
BusID string `json:"busID"`
DisplayActive string `json:"displayActive"`
ECC string `json:"ecc"`
FanSpeed string `json:"fanSpeed"`
Temperature string `json:"temperature"`
PerformanceState string `json:"performanceState"`
PowerDraw string `json:"powerDraw"`
MaxPowerLimit string `json:"maxPowerLimit"`
MemUsed string `json:"memUsed"`
MemTotal string `json:"memTotal"`
GPUUtil string `json:"gpuUtil"`
ComputeMode string `json:"computeMode"`
MigMode string `json:"migMode"`
Processes []Process `json:"processes"`
}
type Process struct {
Pid string `json:"pid"`
Type string `json:"type"`
ProcessName string `json:"processName"`
UsedMemory string `json:"usedMemory"`
}

View File

@ -0,0 +1,65 @@
package gpu
import (
"bytes"
_ "embed"
"encoding/xml"
"errors"
"fmt"
"io"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/schema_v12"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)
type NvidiaSMI struct{}
func New() (bool, NvidiaSMI) {
return cmd.Which("nvidia-smi"), NvidiaSMI{}
}
func (n NvidiaSMI) LoadGpuInfo() (*common.GpuInfo, error) {
itemData, err := cmd.ExecWithTimeOut("nvidia-smi -q -x", 5*time.Second)
if err != nil {
return nil, fmt.Errorf("calling nvidia-smi failed, err: %w", err)
}
data := []byte(itemData)
schema := "v11"
buf := bytes.NewBuffer(data)
decoder := xml.NewDecoder(buf)
for {
token, err := decoder.Token()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, fmt.Errorf("reading token failed: %w", err)
}
d, ok := token.(xml.Directive)
if !ok {
continue
}
directive := string(d)
if !strings.HasPrefix(directive, "DOCTYPE") {
continue
}
parts := strings.Split(directive, " ")
s := strings.Trim(parts[len(parts)-1], "\" ")
if strings.HasPrefix(s, "nvsmi_device_") && strings.HasSuffix(s, ".dtd") {
schema = strings.TrimSuffix(strings.TrimPrefix(s, "nvsmi_device_"), ".dtd")
} else {
global.LOG.Debugf("Cannot find schema version in %q", directive)
}
break
}
if schema != "v12" {
return &common.GpuInfo{}, nil
}
return schema_v12.Parse(data)
}

View File

@ -0,0 +1,55 @@
package schema_v12
import (
"encoding/xml"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
)
func Parse(buf []byte) (*common.GpuInfo, error) {
var (
s smi
info common.GpuInfo
)
if err := xml.Unmarshal(buf, &s); err != nil {
return nil, err
}
info.Type = "nvidia"
info.CudaVersion = s.CudaVersion
info.DriverVersion = s.DriverVersion
if len(s.Gpu) == 0 {
return &info, nil
}
for i := 0; i < len(s.Gpu); i++ {
var gpuItem common.GPU
gpuItem.Index = uint(i)
gpuItem.ProductName = s.Gpu[i].ProductName
gpuItem.PersistenceMode = s.Gpu[i].PersistenceMode
gpuItem.BusID = s.Gpu[i].ID
gpuItem.DisplayActive = s.Gpu[i].DisplayActive
gpuItem.ECC = s.Gpu[i].EccErrors.Volatile.DramUncorrectable
gpuItem.FanSpeed = s.Gpu[i].FanSpeed
gpuItem.Temperature = s.Gpu[i].Temperature.GpuTemp
gpuItem.PerformanceState = s.Gpu[i].PerformanceState
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.PowerDraw
gpuItem.MaxPowerLimit = s.Gpu[i].GpuPowerReadings.MaxPowerLimit
gpuItem.MemUsed = s.Gpu[i].FbMemoryUsage.Used
gpuItem.MemTotal = s.Gpu[i].FbMemoryUsage.Total
gpuItem.GPUUtil = s.Gpu[i].Utilization.GpuUtil
gpuItem.ComputeMode = s.Gpu[i].ComputeMode
gpuItem.MigMode = s.Gpu[i].MigMode.CurrentMig
for _, process := range s.Gpu[i].Processes.ProcessInfo {
gpuItem.Processes = append(gpuItem.Processes, common.Process{
Pid: process.Pid,
Type: process.Type,
ProcessName: process.ProcessName,
UsedMemory: process.UsedMemory,
})
}
info.GPUs = append(info.GPUs, gpuItem)
}
return &info, nil
}

View File

@ -0,0 +1,294 @@
package schema_v12
type smi struct {
AttachedGpus string `xml:"attached_gpus"`
CudaVersion string `xml:"cuda_version"`
DriverVersion string `xml:"driver_version"`
Gpu []struct {
ID string `xml:"id,attr"`
AccountedProcesses struct{} `xml:"accounted_processes"`
AccountingMode string `xml:"accounting_mode"`
AccountingModeBufferSize string `xml:"accounting_mode_buffer_size"`
AddressingMode string `xml:"addressing_mode"`
ApplicationsClocks struct {
GraphicsClock string `xml:"graphics_clock"`
MemClock string `xml:"mem_clock"`
} `xml:"applications_clocks"`
Bar1MemoryUsage struct {
Free string `xml:"free"`
Total string `xml:"total"`
Used string `xml:"used"`
} `xml:"bar1_memory_usage"`
BoardID string `xml:"board_id"`
BoardPartNumber string `xml:"board_part_number"`
CcProtectedMemoryUsage struct {
Free string `xml:"free"`
Total string `xml:"total"`
Used string `xml:"used"`
} `xml:"cc_protected_memory_usage"`
ClockPolicy struct {
AutoBoost string `xml:"auto_boost"`
AutoBoostDefault string `xml:"auto_boost_default"`
} `xml:"clock_policy"`
Clocks struct {
GraphicsClock string `xml:"graphics_clock"`
MemClock string `xml:"mem_clock"`
SmClock string `xml:"sm_clock"`
VideoClock string `xml:"video_clock"`
} `xml:"clocks"`
ClocksEventReasons struct {
ClocksEventReasonApplicationsClocksSetting string `xml:"clocks_event_reason_applications_clocks_setting"`
ClocksEventReasonDisplayClocksSetting string `xml:"clocks_event_reason_display_clocks_setting"`
ClocksEventReasonGpuIdle string `xml:"clocks_event_reason_gpu_idle"`
ClocksEventReasonHwPowerBrakeSlowdown string `xml:"clocks_event_reason_hw_power_brake_slowdown"`
ClocksEventReasonHwSlowdown string `xml:"clocks_event_reason_hw_slowdown"`
ClocksEventReasonHwThermalSlowdown string `xml:"clocks_event_reason_hw_thermal_slowdown"`
ClocksEventReasonSwPowerCap string `xml:"clocks_event_reason_sw_power_cap"`
ClocksEventReasonSwThermalSlowdown string `xml:"clocks_event_reason_sw_thermal_slowdown"`
ClocksEventReasonSyncBoost string `xml:"clocks_event_reason_sync_boost"`
} `xml:"clocks_event_reasons"`
ComputeMode string `xml:"compute_mode"`
DefaultApplicationsClocks struct {
GraphicsClock string `xml:"graphics_clock"`
MemClock string `xml:"mem_clock"`
} `xml:"default_applications_clocks"`
DeferredClocks struct {
MemClock string `xml:"mem_clock"`
} `xml:"deferred_clocks"`
DisplayActive string `xml:"display_active"`
DisplayMode string `xml:"display_mode"`
DriverModel struct {
CurrentDm string `xml:"current_dm"`
PendingDm string `xml:"pending_dm"`
} `xml:"driver_model"`
EccErrors struct {
Aggregate struct {
DramCorrectable string `xml:"dram_correctable"`
DramUncorrectable string `xml:"dram_uncorrectable"`
SramCorrectable string `xml:"sram_correctable"`
SramUncorrectable string `xml:"sram_uncorrectable"`
} `xml:"aggregate"`
Volatile struct {
DramCorrectable string `xml:"dram_correctable"`
DramUncorrectable string `xml:"dram_uncorrectable"`
SramCorrectable string `xml:"sram_correctable"`
SramUncorrectable string `xml:"sram_uncorrectable"`
} `xml:"volatile"`
} `xml:"ecc_errors"`
EccMode struct {
CurrentEcc string `xml:"current_ecc"`
PendingEcc string `xml:"pending_ecc"`
} `xml:"ecc_mode"`
EncoderStats struct {
AverageFps string `xml:"average_fps"`
AverageLatency string `xml:"average_latency"`
SessionCount string `xml:"session_count"`
} `xml:"encoder_stats"`
Fabric struct {
State string `xml:"state"`
Status string `xml:"status"`
} `xml:"fabric"`
FanSpeed string `xml:"fan_speed"`
FbMemoryUsage struct {
Free string `xml:"free"`
Reserved string `xml:"reserved"`
Total string `xml:"total"`
Used string `xml:"used"`
} `xml:"fb_memory_usage"`
FbcStats struct {
AverageFps string `xml:"average_fps"`
AverageLatency string `xml:"average_latency"`
SessionCount string `xml:"session_count"`
} `xml:"fbc_stats"`
GpuFruPartNumber string `xml:"gpu_fru_part_number"`
GpuModuleID string `xml:"gpu_module_id"`
GpuOperationMode struct {
CurrentGom string `xml:"current_gom"`
PendingGom string `xml:"pending_gom"`
} `xml:"gpu_operation_mode"`
GpuPartNumber string `xml:"gpu_part_number"`
GpuPowerReadings struct {
CurrentPowerLimit string `xml:"current_power_limit"`
DefaultPowerLimit string `xml:"default_power_limit"`
MaxPowerLimit string `xml:"max_power_limit"`
MinPowerLimit string `xml:"min_power_limit"`
PowerDraw string `xml:"power_draw"`
PowerState string `xml:"power_state"`
RequestedPowerLimit string `xml:"requested_power_limit"`
} `xml:"gpu_power_readings"`
GpuResetStatus struct {
DrainAndResetRecommended string `xml:"drain_and_reset_recommended"`
ResetRequired string `xml:"reset_required"`
} `xml:"gpu_reset_status"`
GpuVirtualizationMode struct {
HostVgpuMode string `xml:"host_vgpu_mode"`
VirtualizationMode string `xml:"virtualization_mode"`
} `xml:"gpu_virtualization_mode"`
GspFirmwareVersion string `xml:"gsp_firmware_version"`
Ibmnpu struct {
RelaxedOrderingMode string `xml:"relaxed_ordering_mode"`
} `xml:"ibmnpu"`
InforomVersion struct {
EccObject string `xml:"ecc_object"`
ImgVersion string `xml:"img_version"`
OemObject string `xml:"oem_object"`
PwrObject string `xml:"pwr_object"`
} `xml:"inforom_version"`
MaxClocks struct {
GraphicsClock string `xml:"graphics_clock"`
MemClock string `xml:"mem_clock"`
SmClock string `xml:"sm_clock"`
VideoClock string `xml:"video_clock"`
} `xml:"max_clocks"`
MaxCustomerBoostClocks struct {
GraphicsClock string `xml:"graphics_clock"`
} `xml:"max_customer_boost_clocks"`
MigDevices struct {
MigDevice []struct {
Index string `xml:"index"`
GpuInstanceID string `xml:"gpu_instance_id"`
ComputeInstanceID string `xml:"compute_instance_id"`
EccErrorCount struct {
Text string `xml:",chardata" json:"text"`
VolatileCount struct {
SramUncorrectable string `xml:"sram_uncorrectable"`
} `xml:"volatile_count" json:"volatile_count"`
} `xml:"ecc_error_count" json:"ecc_error_count"`
FbMemoryUsage struct {
Total string `xml:"total"`
Reserved string `xml:"reserved"`
Used string `xml:"used"`
Free string `xml:"free"`
} `xml:"fb_memory_usage" json:"fb_memory_usage"`
Bar1MemoryUsage struct {
Total string `xml:"total"`
Used string `xml:"used"`
Free string `xml:"free"`
} `xml:"bar1_memory_usage" json:"bar1_memory_usage"`
} `xml:"mig_device" json:"mig_device"`
} `xml:"mig_devices" json:"mig_devices"`
MigMode struct {
CurrentMig string `xml:"current_mig"`
PendingMig string `xml:"pending_mig"`
} `xml:"mig_mode"`
MinorNumber string `xml:"minor_number"`
ModulePowerReadings struct {
CurrentPowerLimit string `xml:"current_power_limit"`
DefaultPowerLimit string `xml:"default_power_limit"`
MaxPowerLimit string `xml:"max_power_limit"`
MinPowerLimit string `xml:"min_power_limit"`
PowerDraw string `xml:"power_draw"`
PowerState string `xml:"power_state"`
RequestedPowerLimit string `xml:"requested_power_limit"`
} `xml:"module_power_readings"`
MultigpuBoard string `xml:"multigpu_board"`
Pci struct {
AtomicCapsInbound string `xml:"atomic_caps_inbound"`
AtomicCapsOutbound string `xml:"atomic_caps_outbound"`
PciBridgeChip struct {
BridgeChipFw string `xml:"bridge_chip_fw"`
BridgeChipType string `xml:"bridge_chip_type"`
} `xml:"pci_bridge_chip"`
PciBus string `xml:"pci_bus"`
PciBusID string `xml:"pci_bus_id"`
PciDevice string `xml:"pci_device"`
PciDeviceID string `xml:"pci_device_id"`
PciDomain string `xml:"pci_domain"`
PciGpuLinkInfo struct {
LinkWidths struct {
CurrentLinkWidth string `xml:"current_link_width"`
MaxLinkWidth string `xml:"max_link_width"`
} `xml:"link_widths"`
PcieGen struct {
CurrentLinkGen string `xml:"current_link_gen"`
DeviceCurrentLinkGen string `xml:"device_current_link_gen"`
MaxDeviceLinkGen string `xml:"max_device_link_gen"`
MaxHostLinkGen string `xml:"max_host_link_gen"`
MaxLinkGen string `xml:"max_link_gen"`
} `xml:"pcie_gen"`
} `xml:"pci_gpu_link_info"`
PciSubSystemID string `xml:"pci_sub_system_id"`
ReplayCounter string `xml:"replay_counter"`
ReplayRolloverCounter string `xml:"replay_rollover_counter"`
RxUtil string `xml:"rx_util"`
TxUtil string `xml:"tx_util"`
} `xml:"pci"`
PerformanceState string `xml:"performance_state"`
PersistenceMode string `xml:"persistence_mode"`
PowerReadings struct {
PowerState string `xml:"power_state"`
PowerManagement string `xml:"power_management"`
PowerDraw string `xml:"power_draw"`
PowerLimit string `xml:"power_limit"`
DefaultPowerLimit string `xml:"default_power_limit"`
EnforcedPowerLimit string `xml:"enforced_power_limit"`
MinPowerLimit string `xml:"min_power_limit"`
MaxPowerLimit string `xml:"max_power_limit"`
} `xml:"power_readings"`
Processes struct {
ProcessInfo []struct {
Pid string `xml:"pid"`
Type string `xml:"type"`
ProcessName string `xml:"process_name"`
UsedMemory string `xml:"used_memory"`
} `xml:"process_info"`
} `xml:"processes"`
ProductArchitecture string `xml:"product_architecture"`
ProductBrand string `xml:"product_brand"`
ProductName string `xml:"product_name"`
RemappedRows struct {
// Manually added
Correctable string `xml:"remapped_row_corr"`
Uncorrectable string `xml:"remapped_row_unc"`
Pending string `xml:"remapped_row_pending"`
Failure string `xml:"remapped_row_failure"`
} `xml:"remapped_rows"`
RetiredPages struct {
DoubleBitRetirement struct {
RetiredCount string `xml:"retired_count"`
RetiredPagelist string `xml:"retired_pagelist"`
} `xml:"double_bit_retirement"`
MultipleSingleBitRetirement struct {
RetiredCount string `xml:"retired_count"`
RetiredPagelist string `xml:"retired_pagelist"`
} `xml:"multiple_single_bit_retirement"`
PendingBlacklist string `xml:"pending_blacklist"`
PendingRetirement string `xml:"pending_retirement"`
} `xml:"retired_pages"`
Serial string `xml:"serial"`
SupportedClocks struct {
SupportedMemClock []struct {
SupportedGraphicsClock []string `xml:"supported_graphics_clock"`
Value string `xml:"value"`
} `xml:"supported_mem_clock"`
} `xml:"supported_clocks"`
SupportedGpuTargetTemp struct {
GpuTargetTempMax string `xml:"gpu_target_temp_max"`
GpuTargetTempMin string `xml:"gpu_target_temp_min"`
} `xml:"supported_gpu_target_temp"`
Temperature struct {
GpuTargetTemperature string `xml:"gpu_target_temperature"`
GpuTemp string `xml:"gpu_temp"`
GpuTempMaxGpuThreshold string `xml:"gpu_temp_max_gpu_threshold"`
GpuTempMaxMemThreshold string `xml:"gpu_temp_max_mem_threshold"`
GpuTempMaxThreshold string `xml:"gpu_temp_max_threshold"`
GpuTempSlowThreshold string `xml:"gpu_temp_slow_threshold"`
GpuTempTlimit string `xml:"gpu_temp_tlimit"`
MemoryTemp string `xml:"memory_temp"`
} `xml:"temperature"`
Utilization struct {
DecoderUtil string `xml:"decoder_util"`
EncoderUtil string `xml:"encoder_util"`
GpuUtil string `xml:"gpu_util"`
JpegUtil string `xml:"jpeg_util"`
MemoryUtil string `xml:"memory_util"`
OfaUtil string `xml:"ofa_util"`
} `xml:"utilization"`
UUID string `xml:"uuid"`
VbiosVersion string `xml:"vbios_version"`
Voltage struct {
GraphicsVolt string `xml:"graphics_volt"`
} `xml:"voltage"`
} `xml:"gpu"`
Timestamp string `xml:"timestamp"`
}

View File

@ -0,0 +1,43 @@
package xpu
type DeviceUtilByProc struct {
DeviceID int `json:"device_id"`
MemSize float64 `json:"mem_size"`
ProcessID int `json:"process_id"`
ProcessName string `json:"process_name"`
SharedMemSize float64 `json:"shared_mem_size"`
}
type DeviceUtilByProcList struct {
DeviceUtilByProcList []DeviceUtilByProc `json:"device_util_by_proc_list"`
}
type Device struct {
DeviceFunctionType string `json:"device_function_type"`
DeviceID int `json:"device_id"`
DeviceName string `json:"device_name"`
DeviceType string `json:"device_type"`
DrmDevice string `json:"drm_device"`
PciBdfAddress string `json:"pci_bdf_address"`
PciDeviceID string `json:"pci_device_id"`
UUID string `json:"uuid"`
VendorName string `json:"vendor_name"`
MemoryPhysicalSizeByte string `json:"memory_physical_size_byte"`
MemoryFreeSizeByte string `json:"memory_free_size_byte"`
DriverVersion string `json:"driver_version"`
}
type DeviceInfo struct {
DeviceList []Device `json:"device_list"`
}
type DeviceLevelMetric struct {
MetricsType string `json:"metrics_type"`
Value float64 `json:"value"`
}
type DeviceStats struct {
DeviceID int `json:"device_id"`
DeviceLevel []DeviceLevelMetric `json:"device_level"`
}

View File

@ -0,0 +1,254 @@
package xpu
import (
"encoding/json"
"fmt"
"sort"
"strconv"
"sync"
"time"
baseGlobal "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)
type XpuSMI struct{}
func New() (bool, XpuSMI) {
return cmd.Which("xpu-smi"), XpuSMI{}
}
func (x XpuSMI) loadDeviceData(device Device, wg *sync.WaitGroup, res *[]XPUSimpleInfo, mu *sync.Mutex) {
defer wg.Done()
var xpu XPUSimpleInfo
xpu.DeviceID = device.DeviceID
xpu.DeviceName = device.DeviceName
var xpuData, statsData string
var xpuErr, statsErr error
var wgCmd sync.WaitGroup
wgCmd.Add(2)
go func() {
defer wgCmd.Done()
xpuData, xpuErr = cmd.ExecWithTimeOut(fmt.Sprintf("xpu-smi discovery -d %d -j", device.DeviceID), 5*time.Second)
}()
go func() {
defer wgCmd.Done()
statsData, statsErr = cmd.ExecWithTimeOut(fmt.Sprintf("xpu-smi stats -d %d -j", device.DeviceID), 5*time.Second)
}()
wgCmd.Wait()
if xpuErr != nil {
baseGlobal.LOG.Errorf("calling xpu-smi discovery failed for device %d, err: %v\n", device.DeviceID, xpuErr)
return
}
var info Device
if err := json.Unmarshal([]byte(xpuData), &info); err != nil {
baseGlobal.LOG.Errorf("xpuData json unmarshal failed for device %d, err: %v\n", device.DeviceID, err)
return
}
bytes, err := strconv.ParseInt(info.MemoryPhysicalSizeByte, 10, 64)
if err != nil {
baseGlobal.LOG.Errorf("Error parsing memory size for device %d, err: %v\n", device.DeviceID, err)
return
}
xpu.Memory = fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024))
if statsErr != nil {
baseGlobal.LOG.Errorf("calling xpu-smi stats failed for device %d, err: %v\n", device.DeviceID, statsErr)
return
}
var stats DeviceStats
if err := json.Unmarshal([]byte(statsData), &stats); err != nil {
baseGlobal.LOG.Errorf("statsData json unmarshal failed for device %d, err: %v\n", device.DeviceID, err)
return
}
for _, stat := range stats.DeviceLevel {
switch stat.MetricsType {
case "XPUM_STATS_POWER":
xpu.Power = fmt.Sprintf("%.1fW", stat.Value)
case "XPUM_STATS_GPU_CORE_TEMPERATURE":
xpu.Temperature = fmt.Sprintf("%.1f°C", stat.Value)
case "XPUM_STATS_MEMORY_USED":
xpu.MemoryUsed = fmt.Sprintf("%.1fMB", stat.Value)
case "XPUM_STATS_MEMORY_UTILIZATION":
xpu.MemoryUtil = fmt.Sprintf("%.1f%%", stat.Value)
}
}
mu.Lock()
*res = append(*res, xpu)
mu.Unlock()
}
func (x XpuSMI) LoadDashData() ([]XPUSimpleInfo, error) {
data, err := cmd.ExecWithTimeOut("xpu-smi discovery -j", 5*time.Second)
if err != nil {
return nil, fmt.Errorf("calling xpu-smi failed, err: %w", err)
}
var deviceInfo DeviceInfo
if err := json.Unmarshal([]byte(data), &deviceInfo); err != nil {
return nil, fmt.Errorf("deviceInfo json unmarshal failed, err: %w", err)
}
var res []XPUSimpleInfo
var wg sync.WaitGroup
var mu sync.Mutex
for _, device := range deviceInfo.DeviceList {
wg.Add(1)
go x.loadDeviceData(device, &wg, &res, &mu)
}
wg.Wait()
sort.Slice(res, func(i, j int) bool {
return res[i].DeviceID < res[j].DeviceID
})
return res, nil
}
func (x XpuSMI) LoadGpuInfo() (*XpuInfo, error) {
data, err := cmd.ExecWithTimeOut("xpu-smi discovery -j", 5*time.Second)
if err != nil {
return nil, fmt.Errorf("calling xpu-smi failed, err: %w", err)
}
var deviceInfo DeviceInfo
if err := json.Unmarshal([]byte(data), &deviceInfo); err != nil {
return nil, fmt.Errorf("deviceInfo json unmarshal failed, err: %w", err)
}
res := &XpuInfo{
Type: "xpu",
}
var wg sync.WaitGroup
var mu sync.Mutex
for _, device := range deviceInfo.DeviceList {
wg.Add(1)
go x.loadDeviceInfo(device, &wg, res, &mu)
}
wg.Wait()
processData, err := cmd.ExecWithTimeOut(fmt.Sprintf("xpu-smi ps -j"), 5*time.Second)
if err != nil {
return nil, fmt.Errorf("calling xpu-smi ps failed, err: %w", err)
}
var psList DeviceUtilByProcList
if err := json.Unmarshal([]byte(processData), &psList); err != nil {
return nil, fmt.Errorf("processData json unmarshal failed, err: %w", err)
}
for _, ps := range psList.DeviceUtilByProcList {
process := Process{
PID: ps.ProcessID,
Command: ps.ProcessName,
}
if ps.SharedMemSize > 0 {
process.SHR = fmt.Sprintf("%.1f MB", ps.SharedMemSize/1024)
}
if ps.MemSize > 0 {
process.Memory = fmt.Sprintf("%.1f MB", ps.MemSize/1024)
}
for index, xpu := range res.Xpu {
if xpu.Basic.DeviceID == ps.DeviceID {
res.Xpu[index].Processes = append(res.Xpu[index].Processes, process)
}
}
}
return res, nil
}
func (x XpuSMI) loadDeviceInfo(device Device, wg *sync.WaitGroup, res *XpuInfo, mu *sync.Mutex) {
defer wg.Done()
xpu := Xpu{
Basic: Basic{
DeviceID: device.DeviceID,
DeviceName: device.DeviceName,
VendorName: device.VendorName,
PciBdfAddress: device.PciBdfAddress,
},
}
var xpuData, statsData string
var xpuErr, statsErr error
var wgCmd sync.WaitGroup
wgCmd.Add(2)
go func() {
defer wgCmd.Done()
xpuData, xpuErr = cmd.ExecWithTimeOut(fmt.Sprintf("xpu-smi discovery -d %d -j", device.DeviceID), 5*time.Second)
}()
go func() {
defer wgCmd.Done()
statsData, statsErr = cmd.ExecWithTimeOut(fmt.Sprintf("xpu-smi stats -d %d -j", device.DeviceID), 5*time.Second)
}()
wgCmd.Wait()
if xpuErr != nil {
baseGlobal.LOG.Errorf("calling xpu-smi discovery failed for device %d, err: %v\n", device.DeviceID, xpuErr)
return
}
var info Device
if err := json.Unmarshal([]byte(xpuData), &info); err != nil {
baseGlobal.LOG.Errorf("xpuData json unmarshal failed for device %d, err: %v\n", device.DeviceID, err)
return
}
res.DriverVersion = info.DriverVersion
xpu.Basic.DriverVersion = info.DriverVersion
bytes, err := strconv.ParseInt(info.MemoryPhysicalSizeByte, 10, 64)
if err != nil {
baseGlobal.LOG.Errorf("Error parsing memory size for device %d, err: %v\n", device.DeviceID, err)
return
}
xpu.Basic.Memory = fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024))
xpu.Basic.FreeMemory = info.MemoryFreeSizeByte
if statsErr != nil {
baseGlobal.LOG.Errorf("calling xpu-smi stats failed for device %d, err: %v\n", device.DeviceID, statsErr)
return
}
var stats DeviceStats
if err := json.Unmarshal([]byte(statsData), &stats); err != nil {
baseGlobal.LOG.Errorf("statsData json unmarshal failed for device %d, err: %v\n", device.DeviceID, err)
return
}
for _, stat := range stats.DeviceLevel {
switch stat.MetricsType {
case "XPUM_STATS_POWER":
xpu.Stats.Power = fmt.Sprintf("%.1fW", stat.Value)
case "XPUM_STATS_GPU_FREQUENCY":
xpu.Stats.Frequency = fmt.Sprintf("%.1fMHz", stat.Value)
case "XPUM_STATS_GPU_CORE_TEMPERATURE":
xpu.Stats.Temperature = fmt.Sprintf("%.1f°C", stat.Value)
case "XPUM_STATS_MEMORY_USED":
xpu.Stats.MemoryUsed = fmt.Sprintf("%.1fMB", stat.Value)
case "XPUM_STATS_MEMORY_UTILIZATION":
xpu.Stats.MemoryUtil = fmt.Sprintf("%.1f%%", stat.Value)
}
}
mu.Lock()
res.Xpu = append(res.Xpu, xpu)
mu.Unlock()
}

View File

@ -0,0 +1,49 @@
package xpu
type XpuInfo struct {
Type string `json:"type"`
DriverVersion string `json:"driverVersion"`
Xpu []Xpu `json:"xpu"`
}
type Xpu struct {
Basic Basic `json:"basic"`
Stats Stats `json:"stats"`
Processes []Process `json:"processes"`
}
type Basic struct {
DeviceID int `json:"deviceID"`
DeviceName string `json:"deviceName"`
VendorName string `json:"vendorName"`
DriverVersion string `json:"driverVersion"`
Memory string `json:"memory"`
FreeMemory string `json:"freeMemory"`
PciBdfAddress string `json:"pciBdfAddress"`
}
type Stats struct {
Power string `json:"power"`
Frequency string `json:"frequency"`
Temperature string `json:"temperature"`
MemoryUsed string `json:"memoryUsed"`
MemoryUtil string `json:"memoryUtil"`
}
type Process struct {
PID int `json:"pid"`
Command string `json:"command"`
SHR string `json:"shr"`
Memory string `json:"memory"`
}
type XPUSimpleInfo struct {
DeviceID int `json:"deviceID"`
DeviceName string `json:"deviceName"`
Memory string `json:"memory"`
Temperature string `json:"temperature"`
MemoryUsed string `json:"memoryUsed"`
Power string `json:"power"`
MemoryUtil string `json:"memoryUtil"`
}

View File

@ -16,6 +16,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"golang.org/x/net/idna"
)
@ -357,3 +358,22 @@ func GetLang(c *gin.Context) string {
}
return lang
}
func HandleIPList(content string) ([]string, error) {
ipList := strings.Split(content, "\n")
var res []string
for _, ip := range ipList {
if ip == "" {
continue
}
if net.ParseIP(ip) != nil {
res = append(res, ip)
continue
}
if _, _, err := net.ParseCIDR(ip); err != nil {
return nil, buserr.New("ErrParseIP")
}
res = append(res, ip)
}
return res, nil
}

View File

@ -260,6 +260,53 @@ func (s *Server) UpdateRoot(path string) {
s.UpdateDirective("root", []string{path})
}
func (s *Server) UpdateRootProxyForAi(proxy []string) {
newDir := Directive{
Name: "location",
Parameters: []string{"/"},
Block: &Block{},
}
block := &Block{}
block.Directives = []IDirective{
&Directive{
Name: "proxy_buffering",
Parameters: []string{
"off",
},
},
&Directive{
Name: "proxy_cache",
Parameters: []string{
"off",
},
},
&Directive{
Name: "proxy_http_version",
Parameters: []string{
"1.1",
},
},
&Directive{
Name: "proxy_set_header",
Parameters: []string{
"Connection", "''",
},
},
&Directive{
Name: "chunked_transfer_encoding",
Parameters: []string{
"off",
},
},
}
block.Directives = append(block.Directives, &Directive{
Name: "proxy_pass",
Parameters: proxy,
})
newDir.Block = block
s.UpdateDirectiveBySecondKey("location", "/", newDir)
}
func (s *Server) UpdateRootLocation() {
newDir := Directive{
Name: "location",
@ -393,3 +440,30 @@ func (s *Server) AddHTTP2HTTPS() {
newDir.Block = block
s.UpdateDirectiveBySecondKey("if", "($scheme", newDir)
}
func (s *Server) UpdateAllowIPs(ips []string) {
index := -1
for i, directive := range s.Directives {
if directive.GetName() == "location" && directive.GetParameters()[0] == "/" {
index = i
break
}
}
ipDirectives := make([]IDirective, 0)
for _, ip := range ips {
ipDirectives = append(ipDirectives, &Directive{
Name: "allow",
Parameters: []string{ip},
})
}
ipDirectives = append(ipDirectives, &Directive{
Name: "deny",
Parameters: []string{"all"},
})
if index != -1 {
newDirectives := append(ipDirectives, s.Directives[index:]...)
s.Directives = append(s.Directives[:index], newDirectives...)
} else {
s.Directives = append(s.Directives, ipDirectives...)
}
}

View File

@ -47,6 +47,10 @@ var WebUrlMap = map[string]struct{}{
"/apps/upgrade": {},
"/apps/setting": {},
"/ai": {},
"/ai/model": {},
"/ai/gpu": {},
"/containers": {},
"/containers/container": {},
"/containers/image": {},

View File

@ -0,0 +1,111 @@
import { ReqPage } from '.';
export namespace AI {
export interface OllamaModelInfo {
id: number;
name: string;
size: string;
from: string;
logFileExist: boolean;
status: string;
message: string;
createdAt: Date;
}
export interface OllamaModelDropInfo {
id: number;
name: string;
}
export interface OllamaModelSearch extends ReqPage {
info: string;
}
export interface Info {
cudaVersion: string;
driverVersion: string;
type: string;
gpu: GPU[];
}
export interface GPU {
index: number;
productName: string;
persistenceMode: string;
busID: string;
displayActive: string;
ecc: string;
fanSpeed: string;
temperature: string;
performanceState: string;
powerDraw: string;
maxPowerLimit: string;
memUsed: string;
memTotal: string;
gpuUtil: string;
computeMode: string;
migMode: string;
processes: Process[];
}
export interface Process {
pid: string;
type: string;
processName: string;
usedMemory: string;
}
export interface XpuInfo {
type: string;
driverVersion: string;
xpu: Xpu[];
}
interface Xpu {
basic: Basic;
stats: Stats;
processes: XpuProcess[];
}
interface Basic {
deviceID: number;
deviceName: string;
vendorName: string;
driverVersion: string;
memory: string;
freeMemory: string;
pciBdfAddress: string;
}
interface Stats {
power: string;
frequency: string;
temperature: string;
memoryUsed: string;
memoryUtil: string;
}
interface XpuProcess {
pid: number;
command: string;
shr: string;
memory: string;
}
export interface BindDomain {
domain: string;
sslID: number;
ipList: string;
appInstallID: number;
websiteID?: number;
}
export interface BindDomainReq {
appInstallID: number;
}
export interface BindDomainRes {
domain: string;
sslID: number;
allowIPs: string[];
websiteID?: number;
connUrl: string;
}
}

View File

@ -0,0 +1,41 @@
import { AI } from '@/api/interface/ai';
import http from '@/api';
import { ResPage } from '../interface';
export const createOllamaModel = (name: string) => {
return http.post(`/ai/ollama/model`, { name: name });
};
export const recreateOllamaModel = (name: string) => {
return http.post(`/ai/ollama/model/recreate`, { name: name });
};
export const deleteOllamaModel = (ids: Array<number>, force: boolean) => {
return http.post(`/ai/ollama/model/del`, { ids: ids, forceDelete: force });
};
export const searchOllamaModel = (params: AI.OllamaModelSearch) => {
return http.post<ResPage<AI.OllamaModelInfo>>(`/ai/ollama/model/search`, params);
};
export const loadOllamaModel = (name: string) => {
return http.post<string>(`/ai/ollama/model/load`, { name: name });
};
export const syncOllamaModel = () => {
return http.post<Array<AI.OllamaModelDropInfo>>(`/ai/ollama/model/sync`);
};
export const closeOllamaModel = (name: string) => {
return http.post(`/ai/ollama/close`, { name: name });
};
export const loadGPUInfo = () => {
return http.get<any>(`/ai/gpu/load`);
};
export const bindDomain = (req: AI.BindDomain) => {
return http.post(`/ai/domain/bind`, req);
};
export const getBindDomain = (req: AI.BindDomainReq) => {
return http.post<AI.BindDomainRes>(`/ai/domain/get`, req);
};
export const updateBindDomain = (req: AI.BindDomain) => {
return http.post(`/ai/domain/update`, req);
};

View File

@ -1,9 +1,9 @@
@font-face {
font-family: "iconfont"; /* Project id 4776196 */
src: url('iconfont.woff2?t=1740384606757') format('woff2'),
url('iconfont.woff?t=1740384606757') format('woff'),
url('iconfont.ttf?t=1740384606757') format('truetype'),
url('iconfont.svg?t=1740384606757#iconfont') format('svg');
src: url('iconfont.woff2?t=1740392092454') format('woff2'),
url('iconfont.woff?t=1740392092454') format('woff'),
url('iconfont.ttf?t=1740392092454') format('truetype'),
url('iconfont.svg?t=1740392092454#iconfont') format('svg');
}
.iconfont {
@ -14,6 +14,10 @@
-moz-osx-font-smoothing: grayscale;
}
.p-jiqiren2:before {
content: "\e61b";
}
.p-terminal2:before {
content: "\e82f";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,13 @@
"css_prefix_text": "p-",
"description": "",
"glyphs": [
{
"icon_id": "10505865",
"name": "机器人",
"font_class": "jiqiren2",
"unicode": "e61b",
"unicode_decimal": 58907
},
{
"icon_id": "5127551",
"name": "terminal",

View File

@ -14,6 +14,8 @@
/>
<missing-glyph />
<glyph glyph-name="jiqiren2" unicode="&#58907;" d="M554.667 533.333h-320V64h554.666V533.333H554.667z m-85.334 85.334V704H448a21.333 21.333 0 0 0-21.333 21.333V810.667A21.333 21.333 0 0 0 448 832h128a21.333 21.333 0 0 0 21.333-21.333v-85.334A21.333 21.333 0 0 0 576 704h-21.333v-85.333H832A42.667 42.667 0 0 0 874.667 576v-554.667A42.667 42.667 0 0 0 832-21.333H192a42.667 42.667 0 0 0-42.667 42.666V576A42.667 42.667 0 0 0 192 618.667h277.333zM21.333 384H64a21.333 21.333 0 0 0 21.333-21.333v-128A21.333 21.333 0 0 0 64 213.333H21.333A21.333 21.333 0 0 0 0 234.667v128A21.333 21.333 0 0 0 21.333 384zM320 362.667h128v-128H320v128z m256 0h128v-128H576v128zM960 384h42.667A21.333 21.333 0 0 0 1024 362.667v-128a21.333 21.333 0 0 0-21.333-21.334H960a21.333 21.333 0 0 0-21.333 21.334v128A21.333 21.333 0 0 0 960 384z" horiz-adv-x="1024" />
<glyph glyph-name="terminal2" unicode="&#59439;" d="M512 298.666667h256v-85.333334h-256z m-183.168-72.832l128 128a42.624 42.624 0 0 1 0 60.330666l-128 128-60.330667-60.330666L366.336 384l-97.834667-97.834667 60.330667-60.330666zM896 725.333333H128a42.666667 42.666667 0 0 1-42.666667-42.666666v-597.333334a42.666667 42.666667 0 0 1 42.666667-42.666666h768a42.666667 42.666667 0 0 1 42.666667 42.666666V682.666667a42.666667 42.666667 0 0 1-42.666667 42.666666z m-42.666667-597.333333H170.666667V640h682.666666v-512z" horiz-adv-x="1024" />
<glyph glyph-name="tuijian" unicode="&#58919;" d="M64 832m128 0l640 0q128 0 128-128l0-640q0-128-128-128l-640 0q-128 0-128 128l0 640q0 128 128 128ZM585.6 448h88.96a64 64 0 0 0 19.2 0 50.56 50.56 0 0 0 29.44-64c-10.88-55.68-22.4-110.72-33.28-165.76a64 64 0 0 0-19.2-39.68 37.12 37.12 0 0 0-23.04-9.6H410.24V423.68c0 5.12 0 7.04 6.4 8.32A107.52 107.52 0 0 1 499.2 529.92a112.64 112.64 0 0 0 6.4 38.4 41.6 41.6 0 0 0 52.48 24.96 39.68 39.68 0 0 0 17.92-10.88 54.4 54.4 0 0 0 16-33.28A238.72 238.72 0 0 0 586.24 448z m-198.4-38.4c0 13.44-7.04 21.76-19.2 21.76H320a17.92 17.92 0 0 1-18.56-17.92V192a17.92 17.92 0 0 1 16-20.48h49.28c14.72 0 21.76 7.04 21.76 24.32V407.68z" horiz-adv-x="1024" />

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View File

@ -30,7 +30,7 @@
>
{{ $t('commons.operate.restart') }}
</el-button>
<el-divider direction="vertical" />
<el-divider v-if="!hideSetting" direction="vertical" />
<el-button
type="primary"
link
@ -42,6 +42,7 @@
</el-button>
<el-divider v-if="data.app === 'OpenResty'" direction="vertical" />
<el-button
v-if="!hideSetting"
type="primary"
@click="setting"
link
@ -89,6 +90,10 @@ const props = defineProps({
type: String,
default: '',
},
hideSetting: {
type: Boolean,
default: false,
},
});
let key = ref('');

View File

@ -56,7 +56,6 @@ interface DialogProps {
upgradeVersion: string;
}
const acceptParams = (params: DialogProps): void => {
console.log(params);
upgradeInfo.value = params.upgradeInfo;
upgradeVersion.value = params.upgradeVersion;
drawerVisible.value = true;

View File

@ -9,6 +9,7 @@ const message = {
lingxia: 'Lingxia',
colon: ': ',
button: {
run: 'Run',
prev: 'Previous',
next: 'Next',
create: 'Create ',
@ -333,6 +334,7 @@ const message = {
firewall: 'Firewall',
ssl: 'Certificate',
database: 'Database',
aiTools: 'AI',
container: 'Container',
cronjob: 'Cronjob',
host: 'Host',
@ -589,6 +591,67 @@ const message = {
remoteConnHelper2: 'Use this address for non-container or external connections',
localIP: 'Local IP',
},
aiTools: {
model: {
model: 'Model',
create: 'Add Model',
create_helper: 'Pull "{0}"',
ollama_doc: 'You can visit the Ollama official website to search and find more models.',
container_conn_helper: 'Use this address for inter-container access or connection',
ollama_sync: 'Syncing Ollama model found the following models do not exist, do you want to delete them?',
from_remote: 'This model was not downloaded via 1Panel, no related pull logs.',
no_logs: 'The pull logs for this model have been deleted and cannot be viewed.',
},
proxy: {
proxy: 'AI Proxy Enhancement',
proxyHelper1: 'Bind domain and enable HTTPS for enhanced transmission security',
proxyHelper2: 'Limit IP access to prevent exposure on the public internet',
proxyHelper3: 'Enable streaming',
proxyHelper4: 'Once created, you can view and manage it in the website list',
proxyHelper5:
'After enabling, you can disable external access to the port in the App Store - Installed - Ollama - Parameters to improve security.',
proxyHelper6: 'To disable proxy configuration, you can delete it from the website list.',
whiteListHelper: 'Restrict access to only IPs in the whitelist',
},
gpu: {
gpu: 'GPU Monitor',
base: 'Basic Information',
gpuHelper: 'NVIDIA-SMI or XPU-SMI command not detected on the current system. Please check and try again!',
driverVersion: 'Driver Version',
cudaVersion: 'CUDA Version',
process: 'Process Information',
type: 'Type',
typeG: 'Graphics',
typeC: 'Compute',
typeCG: 'Compute + Graphics',
processName: 'Process Name',
processMemoryUsage: 'Memory Usage',
temperatureHelper: 'High GPU temperature can cause GPU frequency throttling',
performanceStateHelper: 'From P0 (maximum performance) to P12 (minimum performance)',
busID: 'Bus ID',
persistenceMode: 'Persistence Mode',
enabled: 'Enabled',
disabled: 'Disabled',
persistenceModeHelper:
'Persistence mode allows quicker task responses but increases standby power consumption.',
displayActive: 'Graphics Card Initialized',
displayActiveT: 'Yes',
displayActiveF: 'No',
ecc: 'Error Correction and Check Technology',
computeMode: 'Compute Mode',
default: 'Default',
exclusiveProcess: 'Exclusive Process',
exclusiveThread: 'Exclusive Thread',
prohibited: 'Prohibited',
defaultHelper: 'Default: Processes can execute concurrently',
exclusiveProcessHelper:
'Exclusive Process: Only one CUDA context can use the GPU, but can be shared by multiple threads',
exclusiveThreadHelper: 'Exclusive Thread: Only one thread in a CUDA context can use the GPU',
prohibitedHelper: 'Prohibited: Processes are not allowed to execute simultaneously',
migModeHelper: 'Used to create MIG instances for physical isolation of the GPU at the user level.',
migModeNA: 'Not Supported',
},
},
container: {
create: 'Create',
createByCommand: 'Create by command',
@ -1807,7 +1870,6 @@ const message = {
waf: 'Upgrading to the professional version can provide features such as interception map, logs, block records, geographical location blocking, custom rules, custom interception pages, etc.',
tamper: 'Upgrading to the professional version can protect websites from unauthorized modifications or tampering.',
tamperHelper: 'Operation failed, the file or folder has tamper protection enabled. Please check and try again!',
gpu: 'Upgrading to the professional version can help users visually monitor important parameters of GPU such as workload, temperature, memory usage in real time.',
setting:
'Upgrading to the professional version allows customization of panel logo, welcome message, and other information.',
monitor:
@ -2995,44 +3057,6 @@ const message = {
disableHelper:
'The anti-tampering function of website {0} is about to be disabled. Do you want to continue?',
},
gpu: {
gpu: 'GPU Monitor',
base: 'Basic Information',
gpuHelper: 'NVIDIA-SMI or XPU-SMI command not detected on the current system. Please check and try again!',
driverVersion: 'Driver Version',
cudaVersion: 'CUDA Version',
process: 'Process Information',
type: 'Type',
typeG: 'Graphics',
typeC: 'Compute',
typeCG: 'Compute + Graphics',
processName: 'Process Name',
processMemoryUsage: 'Memory Usage',
temperatureHelper: 'High GPU temperature can cause GPU frequency throttling',
performanceStateHelper: 'From P0 (maximum performance) to P12 (minimum performance)',
busID: 'Bus ID',
persistenceMode: 'Persistence Mode',
enabled: 'Enabled',
disabled: 'Disabled',
persistenceModeHelper:
'Persistence mode allows quicker task responses but increases standby power consumption.',
displayActive: 'Graphics Card Initialized',
displayActiveT: 'Yes',
displayActiveF: 'No',
ecc: 'Error Correction and Check Technology',
computeMode: 'Compute Mode',
default: 'Default',
exclusiveProcess: 'Exclusive Process',
exclusiveThread: 'Exclusive Thread',
prohibited: 'Prohibited',
defaultHelper: 'Default: Processes can execute concurrently',
exclusiveProcessHelper:
'Exclusive Process: Only one CUDA context can use the GPU, but can be shared by multiple threads',
exclusiveThreadHelper: 'Exclusive Thread: Only one thread in a CUDA context can use the GPU',
prohibitedHelper: 'Prohibited: Processes are not allowed to execute simultaneously',
migModeHelper: 'Used to create MIG instances for physical isolation of the GPU at the user level.',
migModeNA: 'Not Supported',
},
setting: {
setting: 'Panel Settings',
title: 'Panel Description',
@ -3076,11 +3100,6 @@ const message = {
tamperContent4:
'Record file access and operation logs for subsequent auditing and analysis by administrators, as well as to identify potential security threats.',
gpuTitle1: 'Overview Monitoring',
gpuContent1: 'Display the current GPU usage on the overview page.',
gpuTitle2: 'GPU Details',
gpuContent2: 'Show GPU parameters in finer detail.',
settingTitle1: 'Custom Welcome Message',
settingContent1: 'Set a custom welcome message on the 1Panel login page.',
settingTitle2: 'Custom Logo',

View File

@ -9,6 +9,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: '実行',
create: '作成する',
add: '追加',
save: '保存',
@ -327,6 +328,7 @@ const message = {
firewall: 'ファイアウォール',
ssl: '証明書|証明書',
database: 'データベース|データベース',
aiTools: 'AI',
container: 'コンテナ|コンテナ',
cronjob: 'クロンジョブ|クロンの仕事',
host: 'ホスト|ホスト',
@ -581,6 +583,67 @@ const message = {
'この接続アドレスは非コンテナまたは外部アプリケーションで実行されているアプリケーションで使用できます',
localIP: 'ローカルIP',
},
aiTools: {
model: {
model: 'モデル',
create: 'モデルを追加',
create_helper: 'を取得 "{0}"',
ollama_doc: 'Ollama の公式ウェブサイトを訪れてさらに多くのモデルを検索して見つけることができます',
container_conn_helper: 'コンテナ間のアクセスまたは接続にこのアドレスを使用',
ollama_sync: 'Ollamaモデルの同期中に以下のモデルが存在しないことが判明しました削除しますか',
from_remote: 'このモデルは1Panelを介してダウンロードされておらず関連するプルログはありません',
no_logs: 'このモデルのプルログは削除されており関連するログを表示できません',
},
proxy: {
proxy: 'AI プロキシ強化',
proxyHelper1: 'ドメインをバインドしHTTPS を有効にして通信のセキュリティを強化',
proxyHelper2: 'IP アクセスを制限しパブリックインターネットでの露出を防止',
proxyHelper3: 'ストリーミングを有効にする',
proxyHelper4: '作成後ウェブサイトリストで確認および管理できます',
proxyHelper5:
'有効にするとアプリストア - インストール済み - Ollama - パラメータでポートの外部アクセスを無効にしセキュリティを向上させることができます',
proxyHelper6: 'プロキシ設定を無効にするにはウェブサイトリストから削除できます',
whiteListHelper: 'ホワイトリスト内のIPのみアクセスを許可する',
},
gpu: {
gpu: 'GPUモニター',
base: '基本情報',
gpuHelper:
'現在のシステムでNVIDIA-SMIまたはXPU-SMIコマンドが検出されませんでした確認して再試行してください',
driverVersion: 'ドライバーバージョン',
cudaVersion: 'CUDAバージョン',
process: 'プロセス情報',
type: 'タイプ',
typeG: 'グラフィックス',
typeC: 'コンピュート',
typeCG: 'コンピュート + グラフィックス',
processName: 'プロセス名',
processMemoryUsage: 'メモリ使用量',
temperatureHelper: '高いGPU温度はGPUの周波数制限を引き起こす可能性があります',
performanceStateHelper: 'P0最大性能からP12最小性能まで',
busID: 'バスID',
persistenceMode: '永続モード',
enabled: '有効',
disabled: '無効',
persistenceModeHelper: '永続モードはタスクの応答速度を速くしますが待機時の消費電力が増加します',
displayActive: 'グラフィックカード初期化済み',
displayActiveT: 'はい',
displayActiveF: 'いいえ',
ecc: 'エラー訂正およびチェック技術',
computeMode: 'コンピュートモード',
default: 'デフォルト',
exclusiveProcess: '専用プロセス',
exclusiveThread: '専用スレッド',
prohibited: '禁止',
defaultHelper: 'デフォルトプロセスは並行して実行できます',
exclusiveProcessHelper:
'専用プロセス1つのCUDAコンテキストのみがGPUを使用できますが複数のスレッドで共有できます',
exclusiveThreadHelper: '専用スレッドCUDAコンテキスト内の1つのスレッドのみがGPUを使用できます',
prohibitedHelper: '禁止プロセスは同時に実行できません',
migModeHelper: 'ユーザーレベルでGPUの物理的分離を行うためのMIGインスタンスを作成するために使用されます',
migModeNA: 'サポートされていません',
},
},
container: {
create: 'コンテナを作成します',
edit: 'コンテナを編集します',
@ -1670,7 +1733,6 @@ const message = {
introduce: '機能の紹介',
waf: 'プロフェッショナルバージョンにアップグレードするとインターセプトマップログブロックレコード地理的位置ブロッキングカスタムルールカスタムインターセプトページなどの機能を提供できます',
tamper: 'プロのバージョンにアップグレードすると不正な変更や改ざんからWebサイトを保護できます',
gpu: 'プロのバージョンにアップグレードすることでユーザーはワークロード温度メモリ使用量などのGPUの重要なパラメーターをリアルタイムで視覚的に監視するのに役立ちます',
setting:
'プロのバージョンにアップグレードすることでパネルロゴウェルカムメッセージその他の情報のカスタマイズが可能になります',
monitor:
@ -2806,44 +2868,6 @@ const message = {
'ウェブサイト {0} の改ざん防止機能が有効になろうとしていますセキュリティを強化するために続行しますか',
disableHelper: 'ウェブサイト {0} の改ざん防止機能が無効になろうとしています続行しますか',
},
gpu: {
gpu: 'GPUモニター',
base: '基本情報',
gpuHelper:
'現在のシステムでNVIDIA-SMIまたはXPU-SMIコマンドが検出されませんでした確認して再試行してください',
driverVersion: 'ドライバーバージョン',
cudaVersion: 'CUDAバージョン',
process: 'プロセス情報',
type: 'タイプ',
typeG: 'グラフィックス',
typeC: 'コンピュート',
typeCG: 'コンピュート + グラフィックス',
processName: 'プロセス名',
processMemoryUsage: 'メモリ使用量',
temperatureHelper: '高いGPU温度はGPUの周波数制限を引き起こす可能性があります',
performanceStateHelper: 'P0最大性能からP12最小性能まで',
busID: 'バスID',
persistenceMode: '永続モード',
enabled: '有効',
disabled: '無効',
persistenceModeHelper: '永続モードはタスクの応答速度を速くしますが待機時の消費電力が増加します',
displayActive: 'グラフィックカード初期化済み',
displayActiveT: 'はい',
displayActiveF: 'いいえ',
ecc: 'エラー訂正およびチェック技術',
computeMode: 'コンピュートモード',
default: 'デフォルト',
exclusiveProcess: '専用プロセス',
exclusiveThread: '専用スレッド',
prohibited: '禁止',
defaultHelper: 'デフォルトプロセスは並行して実行できます',
exclusiveProcessHelper:
'専用プロセス1つのCUDAコンテキストのみがGPUを使用できますが複数のスレッドで共有できます',
exclusiveThreadHelper: '専用スレッドCUDAコンテキスト内の1つのスレッドのみがGPUを使用できます',
prohibitedHelper: '禁止プロセスは同時に実行できません',
migModeHelper: 'ユーザーレベルでGPUの物理的分離を行うためのMIGインスタンスを作成するために使用されます',
migModeNA: 'サポートされていません',
},
setting: {
setting: 'パネル設定',
title: 'パネルの説明',
@ -2887,10 +2911,6 @@ const message = {
tamperTitle4: 'ログ記録と分析',
tamperContent4:
'ファイルのアクセスおよび操作ログを記録し後の監査および分析に使用また潜在的なセキュリティ脅威を特定',
gpuTitle1: '概要ページモニタリング',
gpuContent1: '概要ページでGPUの現在の使用状況を表示します',
gpuTitle2: 'GPU詳細情報',
gpuContent2: 'GPUの各パラメータをより詳細に表示します',
settingTitle1: 'カスタムウェルカムメッセージ',
settingContent1: '1Panelのログインページにカスタムウェルカムメッセージを設定',

View File

@ -9,6 +9,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: '실행',
create: '생성',
add: '추가',
save: '저장',
@ -328,6 +329,7 @@ const message = {
firewall: '방화벽',
ssl: '인증서 | 인증서들',
database: '데이터베이스 | 데이터베이스들',
aiTools: 'AI',
container: '컨테이너 | 컨테이너들',
cronjob: '크론 작업 | 크론 작업들',
host: '호스트 | 호스트들',
@ -577,6 +579,66 @@ const message = {
' 연결 주소는 컨테이너 외부 또는 외부 애플리케이션에서 실행 중인 애플리케이션에서 사용할 있습니다.',
localIP: '로컬 IP',
},
aiTools: {
model: {
model: '모델',
create: '모델 추가',
create_helper: '가져오기 "{0}"',
ollama_doc: 'Ollama 공식 웹사이트를 방문하여 많은 모델을 검색하고 찾을 있습니다.',
container_conn_helper: '컨테이너 접근 또는 연결에 주소를 사용',
ollama_sync: 'Ollama 모델 동기화 다음 모델이 존재하지 않음을 발견했습니다. 삭제하시겠습니까?',
from_remote: ' 모델은 1Panel을 통해 다운로드되지 않았으며 관련 로그가 없습니다.',
no_logs: ' 모델의 로그가 삭제되어 관련 로그를 없습니다.',
},
proxy: {
proxy: 'AI 프록시 강화',
proxyHelper1: '도메인을 바인딩하고 HTTPS를 활성화하여 전송 보안을 강화',
proxyHelper2: 'IP 접근을 제한하여 공용 인터넷에서의 노출을 방지',
proxyHelper3: '스트리밍을 활성화',
proxyHelper4: '생성 , 웹사이트 목록에서 이를 보고 관리할 있습니다',
proxyHelper5:
'활성화한 , 스토어 - 설치됨 - Ollama - 매개변수에서 포트 외부 접근을 비활성화하여 보안을 강화할 있습니다.',
proxyHelper6: '프록시 구성을 비활성화하려면 웹사이트 목록에서 삭제할 있습니다.',
whiteListHelper: '화이트리스트에 있는 IP만 접근 허용',
},
gpu: {
gpu: 'GPU 모니터',
base: '기본 정보',
gpuHelper: '현재 시스템에서 NVIDIA-SMI 또는 XPU-SMI 명령이 감지되지 않았습니다. 확인 다시 시도하세요!',
driverVersion: '드라이버 버전',
cudaVersion: 'CUDA 버전',
process: '프로세스 정보',
type: '유형',
typeG: '그래픽',
typeC: '연산',
typeCG: '연산 + 그래픽',
processName: '프로세스 이름',
processMemoryUsage: '메모리 사용량',
temperatureHelper: 'GPU 온도가 높으면 GPU 주파수 제한이 발생할 있습니다.',
performanceStateHelper: 'P0(최대 성능)부터 P12(최소 성능)까지',
busID: '버스 ID',
persistenceMode: '지속 모드',
enabled: '활성화됨',
disabled: '비활성화됨',
persistenceModeHelper: '지속 모드는 작업 응답 속도를 빠르게 하지만 대기 전력 소비를 증가시킵니다.',
displayActive: '그래픽 카드 초기화됨',
displayActiveT: '예',
displayActiveF: '아니요',
ecc: '오류 감지 수정 기술',
computeMode: '연산 모드',
default: '기본값',
exclusiveProcess: '단독 프로세스',
exclusiveThread: '단독 스레드',
prohibited: '금지됨',
defaultHelper: '기본값: 프로세스가 동시에 실행될 있음',
exclusiveProcessHelper:
'단독 프로세스: 하나의 CUDA 컨텍스트만 GPU 사용할 있지만, 여러 스레드에서 공유 가능',
exclusiveThreadHelper: '단독 스레드: CUDA 컨텍스트의 하나의 스레드만 GPU 사용할 있음',
prohibitedHelper: '금지됨: 프로세스가 동시에 실행되는 것이 허용되지 않음',
migModeHelper: '사용자 수준에서 GPU 물리적으로 분리하는 MIG 인스턴스를 생성하는 사용됩니다.',
migModeNA: '지원되지 않음',
},
},
container: {
create: '컨테이너 만들기',
edit: '컨테이너 편집',
@ -1643,7 +1705,6 @@ const message = {
introduce: '기능 소개',
waf: '전문 버전으로 업그레이드하면 차단 , 로그, 차단 기록, 지리적 위치 차단, 사용자 정의 규칙, 사용자 정의 차단 페이지 등의 기능을 제공받을 있습니다.',
tamper: '전문 버전으로 업그레이드하면 웹사이트를 무단 수정이나 변조로부터 보호할 있습니다.',
gpu: '전문 버전으로 업그레이드하면 GPU 작업 부하, 온도, 메모리 사용량 중요한 매개변수를 실시간으로 시각적으로 모니터링할 있습니다.',
setting: '전문 버전으로 업그레이드하면 패널 로고, 환영 메시지 정보를 사용자 정의할 있습니다.',
monitor:
'전문 버전으로 업그레이드하면 웹사이트의 실시간 상태, 방문자 트렌드, 방문자 출처, 요청 로그 정보를 확인할 있습니다.',
@ -2763,43 +2824,6 @@ const message = {
'웹사이트 {0} 방지 조작 기능을 활성화하여 웹사이트 보안을 강화하려고 합니다. 계속하시겠습니까?',
disableHelper: '웹사이트 {0} 방지 조작 기능을 비활성화하려고 합니다. 계속하시겠습니까?',
},
gpu: {
gpu: 'GPU 모니터',
base: '기본 정보',
gpuHelper: '현재 시스템에서 NVIDIA-SMI 또는 XPU-SMI 명령이 감지되지 않았습니다. 확인 다시 시도하세요!',
driverVersion: '드라이버 버전',
cudaVersion: 'CUDA 버전',
process: '프로세스 정보',
type: '유형',
typeG: '그래픽',
typeC: '연산',
typeCG: '연산 + 그래픽',
processName: '프로세스 이름',
processMemoryUsage: '메모리 사용량',
temperatureHelper: 'GPU 온도가 높으면 GPU 주파수 제한이 발생할 있습니다.',
performanceStateHelper: 'P0(최대 성능)부터 P12(최소 성능)까지',
busID: '버스 ID',
persistenceMode: '지속 모드',
enabled: '활성화됨',
disabled: '비활성화됨',
persistenceModeHelper: '지속 모드는 작업 응답 속도를 빠르게 하지만 대기 전력 소비를 증가시킵니다.',
displayActive: '그래픽 카드 초기화됨',
displayActiveT: '예',
displayActiveF: '아니요',
ecc: '오류 감지 수정 기술',
computeMode: '연산 모드',
default: '기본값',
exclusiveProcess: '단독 프로세스',
exclusiveThread: '단독 스레드',
prohibited: '금지됨',
defaultHelper: '기본값: 프로세스가 동시에 실행될 있음',
exclusiveProcessHelper:
'단독 프로세스: 하나의 CUDA 컨텍스트만 GPU 사용할 있지만, 여러 스레드에서 공유 가능',
exclusiveThreadHelper: '단독 스레드: CUDA 컨텍스트의 하나의 스레드만 GPU 사용할 있음',
prohibitedHelper: '금지됨: 프로세스가 동시에 실행되는 것이 허용되지 않음',
migModeHelper: '사용자 수준에서 GPU 물리적으로 분리하는 MIG 인스턴스를 생성하는 사용됩니다.',
migModeNA: '지원되지 않음',
},
setting: {
setting: '패널 설정',
title: '패널 설명',
@ -2840,11 +2864,6 @@ const message = {
tamperContent4:
'파일 접근 작업 로그를 기록하여 관리자가 감사 분석을 수행할 있도록 하고, 잠재적 보안 위협을 식별합니다.',
gpuTitle1: '개요 모니터링',
gpuContent1: '개요 페이지에서 현재 GPU 사용량을 표시합니다.',
gpuTitle2: 'GPU 세부 정보',
gpuContent2: 'GPU 매개변수를 세부적으로 표시합니다.',
settingTitle1: '사용자 정의 환영 메시지',
settingContent1: '1Panel 로그인 페이지에 사용자 정의 환영 메시지를 설정합니다.',
settingTitle2: '사용자 정의 로고',

View File

@ -9,6 +9,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: 'Jalankan',
create: 'Cipta',
add: 'Tambah',
save: 'Simpan',
@ -334,6 +335,7 @@ const message = {
firewall: 'Firewall',
ssl: 'Certificate | Certificates',
database: 'Database | Databases',
aiTools: 'AI',
container: 'Container | Containers',
cronjob: 'Cron Job | Cron Jobs',
host: 'Host | Hosts',
@ -592,6 +594,68 @@ const message = {
'Alamat sambungan ini boleh digunakan oleh aplikasi yang berjalan di luar kontena atau aplikasi luaran.',
localIP: 'IP Tempatan',
},
aiTools: {
model: {
model: 'Model',
create: 'Tambah Model',
create_helper: 'Tarik "{0}"',
ollama_doc: 'Anda boleh melawat laman web rasmi Ollama untuk mencari dan menemui lebih banyak model.',
container_conn_helper: 'Gunakan alamat ini untuk akses atau sambungan antara kontena',
ollama_sync:
'Sincronizando o modelo Ollama, encontrou que os seguintes modelos não existem, deseja excluí-los?',
from_remote: 'Este modelo não foi baixado via 1Panel, sem logs de pull relacionados.',
no_logs: 'Os logs de pull deste modelo foram excluídos e não podem ser visualizados.',
},
proxy: {
proxy: 'Peningkatan Proksi AI',
proxyHelper1: 'Ikatkan domain dan aktifkan HTTPS untuk meningkatkan keselamatan penghantaran',
proxyHelper2: 'Hadkan akses IP untuk mengelakkan pendedahan di internet awam',
proxyHelper3: 'Aktifkan penstriman',
proxyHelper4: 'Setelah selesai, anda boleh melihat dan mengurusnya dalam senarai laman web',
proxyHelper5:
'Selepas diaktifkan, anda boleh melumpuhkan akses luaran ke port dalam App Store - Dipasang - Ollama - Parameter untuk meningkatkan keselamatan.',
proxyHelper6: 'Untuk melumpuhkan konfigurasi proksi, anda boleh memadamnya dari senarai laman web.',
whiteListHelper: 'Hadkan akses kepada hanya IP dalam senarai putih',
},
gpu: {
gpu: 'Monitor GPU',
base: 'Maklumat Asas',
gpuHelper: 'Perintah NVIDIA-SMI atau XPU-SMI tidak dikesan pada sistem semasa. Sila periksa dan cuba lagi!',
driverVersion: 'Versi Pemacu',
cudaVersion: 'Versi CUDA',
process: 'Maklumat Proses',
type: 'Jenis',
typeG: 'Grafik',
typeC: 'Pengiraan',
typeCG: 'Pengiraan + Grafik',
processName: 'Nama Proses',
processMemoryUsage: 'Penggunaan Memori',
temperatureHelper: 'Suhu GPU yang tinggi boleh menyebabkan pelambatan frekuensi GPU',
performanceStateHelper: 'Dari P0 (prestasi maksimum) hingga P12 (prestasi minimum)',
busID: 'ID Bas',
persistenceMode: 'Mod Ketekalan',
enabled: 'Diaktifkan',
disabled: 'Dilumpuhkan',
persistenceModeHelper:
'Mod ketekalan membolehkan respons tugas lebih cepat tetapi meningkatkan penggunaan kuasa sedia.',
displayActive: 'Kad Grafik Dimulakan',
displayActiveT: 'Ya',
displayActiveF: 'Tidak',
ecc: 'Teknologi Pemeriksaan dan Pembetulan Ralat',
computeMode: 'Mod Pengiraan',
default: 'Asal',
exclusiveProcess: 'Proses Eksklusif',
exclusiveThread: 'Thread Eksklusif',
prohibited: 'Dilarang',
defaultHelper: 'Asal: Proses boleh dilaksanakan secara serentak',
exclusiveProcessHelper:
'Proses Eksklusif: Hanya satu konteks CUDA boleh menggunakan GPU, tetapi boleh dikongsi oleh berbilang thread',
exclusiveThreadHelper: 'Thread Eksklusif: Hanya satu thread dalam konteks CUDA boleh menggunakan GPU',
prohibitedHelper: 'Dilarang: Proses tidak dibenarkan dilaksanakan serentak',
migModeHelper: 'Digunakan untuk membuat contoh MIG bagi pengasingan fizikal GPU pada tahap pengguna.',
migModeNA: 'Tidak Disokong',
},
},
container: {
create: 'Cipta kontena',
edit: 'Sunting kontena',
@ -1726,7 +1790,6 @@ const message = {
introduce: 'Pengenalan Ciri',
waf: 'Menaik taraf ke versi profesional boleh menyediakan ciri seperti peta pencegahan, log, rekod blok, sekatan lokasi geografi, peraturan tersuai, halaman pencegahan tersuai, dan sebagainya.',
tamper: 'Menaik taraf ke versi profesional boleh melindungi laman web daripada pengubahsuaian atau manipulasi tanpa kebenaran.',
gpu: 'Menaik taraf ke versi profesional boleh membantu pengguna memantau parameter penting GPU secara visual seperti beban kerja, suhu, penggunaan memori secara masa nyata.',
setting:
'Menaik taraf ke versi profesional membolehkan penyesuaian logo panel, mesej selamat datang, dan maklumat lain.',
monitor:
@ -2871,44 +2934,6 @@ const message = {
'Ciri anti-pemalsuan laman web {0} akan diaktifkan untuk meningkatkan keselamatan laman web. Adakah anda mahu meneruskan?',
disableHelper: 'Ciri anti-pemalsuan laman web {0} akan dilumpuhkan. Adakah anda mahu meneruskan?',
},
gpu: {
gpu: 'Monitor GPU',
base: 'Maklumat Asas',
gpuHelper: 'Perintah NVIDIA-SMI atau XPU-SMI tidak dikesan pada sistem semasa. Sila periksa dan cuba lagi!',
driverVersion: 'Versi Pemacu',
cudaVersion: 'Versi CUDA',
process: 'Maklumat Proses',
type: 'Jenis',
typeG: 'Grafik',
typeC: 'Pengiraan',
typeCG: 'Pengiraan + Grafik',
processName: 'Nama Proses',
processMemoryUsage: 'Penggunaan Memori',
temperatureHelper: 'Suhu GPU yang tinggi boleh menyebabkan pelambatan frekuensi GPU',
performanceStateHelper: 'Dari P0 (prestasi maksimum) hingga P12 (prestasi minimum)',
busID: 'ID Bas',
persistenceMode: 'Mod Ketekalan',
enabled: 'Diaktifkan',
disabled: 'Dilumpuhkan',
persistenceModeHelper:
'Mod ketekalan membolehkan respons tugas lebih cepat tetapi meningkatkan penggunaan kuasa sedia.',
displayActive: 'Kad Grafik Dimulakan',
displayActiveT: 'Ya',
displayActiveF: 'Tidak',
ecc: 'Teknologi Pemeriksaan dan Pembetulan Ralat',
computeMode: 'Mod Pengiraan',
default: 'Asal',
exclusiveProcess: 'Proses Eksklusif',
exclusiveThread: 'Thread Eksklusif',
prohibited: 'Dilarang',
defaultHelper: 'Asal: Proses boleh dilaksanakan secara serentak',
exclusiveProcessHelper:
'Proses Eksklusif: Hanya satu konteks CUDA boleh menggunakan GPU, tetapi boleh dikongsi oleh berbilang thread',
exclusiveThreadHelper: 'Thread Eksklusif: Hanya satu thread dalam konteks CUDA boleh menggunakan GPU',
prohibitedHelper: 'Dilarang: Proses tidak dibenarkan dilaksanakan serentak',
migModeHelper: 'Digunakan untuk membuat contoh MIG bagi pengasingan fizikal GPU pada tahap pengguna.',
migModeNA: 'Tidak Disokong',
},
setting: {
setting: 'Tetapan Panel',
title: 'Deskripsi Panel',
@ -2952,11 +2977,6 @@ const message = {
tamperContent4:
'Rekod log akses dan operasi fail untuk audit dan analisis selanjutnya oleh pentadbir serta mengenal pasti potensi ancaman keselamatan.',
gpuTitle1: 'Pemantauan Gambaran Keseluruhan',
gpuContent1: 'Papar penggunaan semasa GPU pada halaman gambaran keseluruhan.',
gpuTitle2: 'Butiran GPU',
gpuContent2: 'Tunjukkan parameter GPU dengan lebih terperinci.',
settingTitle1: 'Mesej Selamat Datang Tersuai',
settingContent1: 'Tetapkan mesej selamat datang tersuai pada halaman log masuk 1Panel.',
settingTitle2: 'Logo Tersuai',

View File

@ -9,6 +9,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: 'Executar',
create: 'Criar',
add: 'Adicionar',
save: 'Salvar',
@ -332,6 +333,7 @@ const message = {
firewall: 'Firewall',
ssl: 'Certificado | Certificados',
database: 'Banco de Dados | Bancos de Dados',
aiTools: 'AI',
container: 'Container | Containers',
cronjob: 'Tarefa Cron | Tarefas Cron',
host: 'Host | Hosts',
@ -589,6 +591,67 @@ const message = {
'Este endereço de conexão pode ser utilizado por aplicações que estão fora do contêiner ou por aplicações externas.',
localIP: 'IP local',
},
aiTools: {
model: {
model: 'Modelo',
create: 'Adicionar Modelo',
create_helper: 'Puxar "{0}"',
ollama_doc: 'Você pode visitar o site oficial da Ollama para pesquisar e encontrar mais modelos.',
container_conn_helper: 'Use este endereço para acesso ou conexão entre contêineres',
ollama_sync:
'Menyelaraskan model Ollama mendapati model berikut tidak wujud, adakah anda ingin memadamnya?',
from_remote: 'Model ini tidak dimuat turun melalui 1Panel, tiada log pengambilan berkaitan.',
no_logs: 'Log pengambilan untuk model ini telah dipadam dan tidak dapat dilihat.',
},
proxy: {
proxy: 'Melhoria de Proxy AI',
proxyHelper1: 'Vincule o domínio e habilite o HTTPS para aumentar a segurança na transmissão',
proxyHelper2: 'Limite o acesso por IP para evitar exposição na internet pública',
proxyHelper3: 'Habilite a transmissão em fluxo',
proxyHelper4: 'Após a criação, você pode visualizar e gerenciar no lista de sites',
proxyHelper6: 'Para desativar a configuração de proxy, você pode excluí-la da lista de sites.',
whiteListHelper: 'Restringir o acesso apenas aos IPs na lista branca',
},
gpu: {
gpu: 'Monitor de GPU',
base: 'Informações Básicas',
gpuHelper:
'Comando NVIDIA-SMI ou XPU-SMI não detectado no sistema atual. Por favor, verifique e tente novamente!',
driverVersion: 'Versão do Driver',
cudaVersion: 'Versão do CUDA',
process: 'Informações do Processo',
type: 'Tipo',
typeG: 'Gráficos',
typeC: 'Cálculo',
typeCG: 'Cálculo + Gráficos',
processName: 'Nome do Processo',
processMemoryUsage: 'Uso de Memória',
temperatureHelper: 'Temperaturas altas da GPU podem causar limitação de frequência da GPU.',
performanceStateHelper: 'De P0 (máximo desempenho) a P12 (mínimo desempenho).',
busID: 'ID do Barramento',
persistenceMode: 'Modo de Persistência',
enabled: 'Ativado',
disabled: 'Desativado',
persistenceModeHelper:
'O modo de persistência permite respostas mais rápidas às tarefas, mas aumenta o consumo de energia em standby.',
displayActive: 'Placa Gráfica Inicializada',
displayActiveT: 'Sim',
displayActiveF: 'Não',
ecc: 'Tecnologia de Correção e Verificação de Erros',
computeMode: 'Modo de Cálculo',
default: 'Padrão',
exclusiveProcess: 'Processo Exclusivo',
exclusiveThread: 'Thread Exclusivo',
prohibited: 'Proibido',
defaultHelper: 'Padrão: Processos podem ser executados simultaneamente.',
exclusiveProcessHelper:
'Processo Exclusivo: Apenas um contexto CUDA pode usar a GPU, mas pode ser compartilhado por múltiplas threads.',
exclusiveThreadHelper: 'Thread Exclusivo: Apenas uma thread em um contexto CUDA pode usar a GPU.',
prohibitedHelper: 'Proibido: Não é permitido que processos sejam executados simultaneamente.',
migModeHelper: 'Usado para criar instâncias MIG para isolamento físico da GPU no nível do usuário.',
migModeNA: 'Não Suportado',
},
},
container: {
create: 'Criar contêiner',
edit: 'Editar contêiner',
@ -1714,7 +1777,6 @@ const message = {
introduce: 'Introdução de recursos',
waf: 'O upgrade para a versão profissional pode fornecer recursos como mapa de intercepção, logs, registros de bloqueio, bloqueio por localização geográfica, regras personalizadas, páginas de intercepção personalizadas, etc.',
tamper: 'O upgrade para a versão profissional pode proteger sites contra modificações ou adulterações não autorizadas.',
gpu: 'O upgrade para a versão profissional pode ajudar os usuários a monitorar visualmente parâmetros importantes da GPU, como carga de trabalho, temperatura e uso de memória em tempo real.',
setting:
'O upgrade para a versão profissional permite a personalização do logo do painel, mensagem de boas-vindas e outras informações.',
monitor:
@ -2875,45 +2937,6 @@ const message = {
'A função de anti-alteração do site {0} está prestes a ser ativada para aumentar a segurança do site. Deseja continuar?',
disableHelper: 'A função de anti-alteração do site {0} está prestes a ser desativada. Deseja continuar?',
},
gpu: {
gpu: 'Monitor de GPU',
base: 'Informações Básicas',
gpuHelper:
'Comando NVIDIA-SMI ou XPU-SMI não detectado no sistema atual. Por favor, verifique e tente novamente!',
driverVersion: 'Versão do Driver',
cudaVersion: 'Versão do CUDA',
process: 'Informações do Processo',
type: 'Tipo',
typeG: 'Gráficos',
typeC: 'Cálculo',
typeCG: 'Cálculo + Gráficos',
processName: 'Nome do Processo',
processMemoryUsage: 'Uso de Memória',
temperatureHelper: 'Temperaturas altas da GPU podem causar limitação de frequência da GPU.',
performanceStateHelper: 'De P0 (máximo desempenho) a P12 (mínimo desempenho).',
busID: 'ID do Barramento',
persistenceMode: 'Modo de Persistência',
enabled: 'Ativado',
disabled: 'Desativado',
persistenceModeHelper:
'O modo de persistência permite respostas mais rápidas às tarefas, mas aumenta o consumo de energia em standby.',
displayActive: 'Placa Gráfica Inicializada',
displayActiveT: 'Sim',
displayActiveF: 'Não',
ecc: 'Tecnologia de Correção e Verificação de Erros',
computeMode: 'Modo de Cálculo',
default: 'Padrão',
exclusiveProcess: 'Processo Exclusivo',
exclusiveThread: 'Thread Exclusivo',
prohibited: 'Proibido',
defaultHelper: 'Padrão: Processos podem ser executados simultaneamente.',
exclusiveProcessHelper:
'Processo Exclusivo: Apenas um contexto CUDA pode usar a GPU, mas pode ser compartilhado por múltiplas threads.',
exclusiveThreadHelper: 'Thread Exclusivo: Apenas uma thread em um contexto CUDA pode usar a GPU.',
prohibitedHelper: 'Proibido: Não é permitido que processos sejam executados simultaneamente.',
migModeHelper: 'Usado para criar instâncias MIG para isolamento físico da GPU no nível do usuário.',
migModeNA: 'Não Suportado',
},
setting: {
setting: 'Configurações do Painel',
title: 'Descrição do Painel',
@ -2958,11 +2981,6 @@ const message = {
tamperContent4:
'Registra logs de acesso e operações em arquivos para auditoria e análise posteriores, ajudando administradores a identificar ameaças de segurança potenciais.',
gpuTitle1: 'Monitoramento Geral',
gpuContent1: 'Exibir o uso atual do GPU na página de visão geral.',
gpuTitle2: 'Detalhes do GPU',
gpuContent2: 'Mostrar os parâmetros do GPU com mais detalhes.',
settingTitle1: 'Mensagem de Boas-vindas Personalizada',
settingContent1: 'Defina uma mensagem de boas-vindas personalizada na página de login do 1Panel.',
settingTitle2: 'Logo Personalizado',

View File

@ -9,6 +9,7 @@ const message = {
fit2cloud: 'FIT2CLOUD',
lingxia: 'Lingxia',
button: {
run: 'Запуск',
create: 'Создать ',
add: 'Добавить ',
save: 'Сохранить ',
@ -328,6 +329,7 @@ const message = {
firewall: 'Firewall',
ssl: 'Сертификат | Сертификаты',
database: 'База данных | Базы данных',
aiTools: 'AI',
container: 'Контейнер | Контейнеры',
cronjob: 'Cron | Задачи Cron',
host: 'Хост | Хосты',
@ -586,6 +588,69 @@ const message = {
'Этот адрес подключения может использоваться приложениями, работающими вне контейнера или внешними приложениями.',
localIP: 'Локальный IP',
},
aiTools: {
model: {
model: 'Модель',
create: 'Добавить модель',
create_helper: 'Загрузить "{0}"',
ollama_doc: 'Вы можете посетить официальный сайт Ollama, чтобы искать и находить больше моделей.',
container_conn_helper: 'Используйте этот адрес для доступа или подключения между контейнерами',
ollama_sync:
'Синхронизация модели Ollama обнаружила, что следующие модели не существуют, хотите удалить их?',
from_remote: 'Эта модель не была загружена через 1Panel, нет связанных журналов извлечения.',
no_logs: 'Журналы извлечения для этой модели были удалены и не могут быть просмотрены.',
},
proxy: {
proxy: 'Усиление AI-прокси',
proxyHelper1: 'Привяжите домен и включите HTTPS для повышения безопасности передачи данных',
proxyHelper2: 'Ограничьте доступ по IP, чтобы предотвратить утечку данных в публичной сети',
proxyHelper3: 'Включите потоковую передачу',
proxyHelper4: 'После создания вы можете просматривать и управлять этим в списке сайтов',
proxyHelper5:
'После включения вы можете отключить внешний доступ к порту в Магазине приложений - Установленные - Ollama - Параметры для повышения безопасности.',
proxyHelper6: 'Чтобы отключить настройку прокси, вы можете удалить её из списка сайтов.',
whiteListHelper: 'Ограничить доступ только для IP-адресов из белого списка',
},
gpu: {
gpu: 'Мониторинг GPU',
base: 'Основная информация',
gpuHelper: 'Команда NVIDIA-SMI или XPU-SMI не обнаружена в текущей системе. Проверьте и попробуйте снова!',
driverVersion: 'Версия драйвера',
cudaVersion: 'Версия CUDA',
process: 'Информация о процессе',
type: 'Тип',
typeG: 'Графика',
typeC: 'Вычисления',
typeCG: 'Вычисления + Графика',
processName: 'Имя процесса',
processMemoryUsage: 'Использование памяти',
temperatureHelper: 'Высокая температура GPU может вызвать снижение частоты GPU',
performanceStateHelper: 'От P0 (максимальная производительность) до P12 (минимальная производительность)',
busID: 'ID шины',
persistenceMode: 'Режим постоянства',
enabled: 'Включен',
disabled: 'Выключен',
persistenceModeHelper:
'Режим постоянства позволяет быстрее реагировать на задачи, но увеличивает потребление энергии в режиме ожидания.',
displayActive: 'Инициализация видеокарты',
displayActiveT: 'Да',
displayActiveF: 'Нет',
ecc: 'Технология проверки и коррекции ошибок (ECC)',
computeMode: 'Режим вычислений',
default: 'По умолчанию',
exclusiveProcess: 'Исключительный процесс',
exclusiveThread: 'Исключительный поток',
prohibited: 'Запрещено',
defaultHelper: 'По умолчанию: процессы могут выполняться одновременно',
exclusiveProcessHelper:
'Исключительный процесс: только один контекст CUDA может использовать GPU, но его могут разделять несколько потоков',
exclusiveThreadHelper: 'Исключительный поток: только один поток в контексте CUDA может использовать GPU',
prohibitedHelper: 'Запрещено: процессам не разрешено выполняться одновременно',
migModeHelper:
'Используется для создания MIG-инстансов для физической изоляции GPU на уровне пользователя.',
migModeNA: 'Не поддерживается',
},
},
container: {
create: 'Создать контейнер',
edit: 'Редактировать контейнер',
@ -1710,7 +1775,6 @@ const message = {
introduce: 'Описание функций',
waf: 'Обновление до профессиональной версии предоставляет такие функции, как карта перехватов, логи, записи блокировок, блокировка по географическому положению, пользовательские правила, пользовательские страницы перехвата и т.д.',
tamper: 'Обновление до профессиональной версии может защитить веб-сайты от несанкционированных изменений или подделок.',
gpu: 'Обновление до профессиональной версии помогает пользователям визуально отслеживать важные параметры GPU, такие как нагрузка, температура, использование памяти в реальном времени.',
setting:
'Обновление до профессиональной версии позволяет настраивать логотип панели, приветственное сообщение и другую информацию.',
monitor:
@ -2864,45 +2928,6 @@ const message = {
'Функция защиты от модификации для сайта {0} будет включена для повышения безопасности сайта. Вы хотите продолжить?',
disableHelper: 'Функция защиты от модификации для сайта {0} будет отключена. Вы хотите продолжить?',
},
gpu: {
gpu: 'Мониторинг GPU',
base: 'Основная информация',
gpuHelper: 'Команда NVIDIA-SMI или XPU-SMI не обнаружена в текущей системе. Проверьте и попробуйте снова!',
driverVersion: 'Версия драйвера',
cudaVersion: 'Версия CUDA',
process: 'Информация о процессе',
type: 'Тип',
typeG: 'Графика',
typeC: 'Вычисления',
typeCG: 'Вычисления + Графика',
processName: 'Имя процесса',
processMemoryUsage: 'Использование памяти',
temperatureHelper: 'Высокая температура GPU может вызвать снижение частоты GPU',
performanceStateHelper: 'От P0 (максимальная производительность) до P12 (минимальная производительность)',
busID: 'ID шины',
persistenceMode: 'Режим постоянства',
enabled: 'Включен',
disabled: 'Выключен',
persistenceModeHelper:
'Режим постоянства позволяет быстрее реагировать на задачи, но увеличивает потребление энергии в режиме ожидания.',
displayActive: 'Инициализация видеокарты',
displayActiveT: 'Да',
displayActiveF: 'Нет',
ecc: 'Технология проверки и коррекции ошибок (ECC)',
computeMode: 'Режим вычислений',
default: 'По умолчанию',
exclusiveProcess: 'Исключительный процесс',
exclusiveThread: 'Исключительный поток',
prohibited: 'Запрещено',
defaultHelper: 'По умолчанию: процессы могут выполняться одновременно',
exclusiveProcessHelper:
'Исключительный процесс: только один контекст CUDA может использовать GPU, но его могут разделять несколько потоков',
exclusiveThreadHelper: 'Исключительный поток: только один поток в контексте CUDA может использовать GPU',
prohibitedHelper: 'Запрещено: процессам не разрешено выполняться одновременно',
migModeHelper:
'Используется для создания MIG-инстансов для физической изоляции GPU на уровне пользователя.',
migModeNA: 'Не поддерживается',
},
setting: {
setting: 'Настройки Панели',
title: 'Описание Панели',
@ -2947,11 +2972,6 @@ const message = {
tamperContent4:
'Записывайте логи доступа и операций с файлами для последующего аудита и анализа администраторами, а также для выявления потенциальных угроз безопасности.',
gpuTitle1: 'Мониторинг обзора',
gpuContent1: 'Отображение текущего использования GPU на странице обзора.',
gpuTitle2: 'Детали GPU',
gpuContent2: 'Показать параметры GPU с большей точностью.',
settingTitle1: 'Пользовательское Приветственное Сообщение',
settingContent1: 'Установите пользовательское приветственное сообщение на странице входа в 1Panel.',
settingTitle2: 'Пользовательский Логотип',

View File

@ -9,6 +9,7 @@ const message = {
lingxia: '凌霞',
colon: ' ',
button: {
run: '執行',
prev: '上一步',
next: '下一步',
create: '創建',
@ -327,6 +328,7 @@ const message = {
firewall: '防火墻',
ssl: '證書',
database: '數據庫',
aiTools: 'AI',
container: '容器',
cronjob: '計劃任務',
host: '主機',
@ -570,6 +572,64 @@ const message = {
remoteConnHelper2: '非容器或外部連接使用此地址',
localIP: '本機 IP',
},
aiTools: {
model: {
model: '模型',
create: '新增模型',
create_helper: '拉取 "{0}"',
ollama_doc: '您可以瀏覽 Ollama 官方網站搜尋並查找更多模型',
container_conn_helper: '容器間瀏覽或連接使用此地址',
ollama_sync: '同步 Ollama 模型發現下列模型不存在是否刪除',
from_remote: '該模型並非透過 1Panel 下載無相關拉取日誌',
no_logs: '該模型的拉取日誌已被刪除無法查看相關日誌',
},
proxy: {
proxy: 'AI 代理增強',
proxyHelper1: '綁定域名並啟用 HTTPS提高傳輸安全性',
proxyHelper2: '限制 IP 瀏覽防止在網路上暴露',
proxyHelper3: '啟用即時串流',
proxyHelper4: '創建後您可以在網站列表中查看並管理',
proxyHelper5: '啟用後您可以在應用商店 - 已安裝 - Ollama - 參數中取消埠外部瀏覽以提高安全性',
proxyHelper6: '如需關閉代理配置可以在網站列表中刪除',
whiteListHelper: '限制僅白名單中的 IP 可瀏覽',
},
gpu: {
gpu: 'GPU 監控',
base: '基礎資訊',
gpuHelper: '目前系統未檢測到 NVIDIA-SMI 或者 XPU-SMI 指令請檢查後重試',
driverVersion: '驅動版本',
cudaVersion: 'CUDA 版本',
process: '行程資訊',
type: '類型',
typeG: '圖形',
typeC: '計算',
typeCG: '計算+圖形',
processName: '行程名稱',
processMemoryUsage: '記憶體使用',
temperatureHelper: 'GPU 溫度過高會導致 GPU 頻率下降',
performanceStateHelper: ' P0 (最大性能) P12 (最小性能)',
busID: '匯流排地址',
persistenceMode: '持續模式',
enabled: '開啟',
disabled: '關閉',
persistenceModeHelper: '持續模式能更加快速地響應任務但相應待機功耗也會增加',
displayActive: '顯卡初始化',
displayActiveT: '是',
displayActiveF: '否',
ecc: '是否開啟錯誤檢查和糾正技術',
computeMode: '計算模式',
default: '預設',
exclusiveProcess: '行程排他',
exclusiveThread: '執行緒排他',
prohibited: '禁止',
defaultHelper: '預設: 行程可以並發執行',
exclusiveProcessHelper: '行程排他: 只有一個 CUDA 上下文可以使用 GPU 但可以由多個執行緒共享',
exclusiveThreadHelper: '執行緒排他: 只有一個執行緒在 CUDA 上下文中可以使用 GPU',
prohibitedHelper: '禁止: 不允許行程同時執行',
migModeHelper: '用於建立 MIG 實例在用戶層實現 GPU 的物理隔離',
migModeNA: '不支援',
},
},
container: {
create: '創建容器',
createByCommand: '命令創建',
@ -1697,7 +1757,6 @@ const message = {
waf: '升級專業版可以獲得攔截地圖日誌封鎖記錄地理位置封禁自定義規則自定義攔截頁面等功能',
tamper: '升級專業版可以保護網站免受未經授權的修改或篡改',
tamperHelper: '操作失敗該文件或文件夾已經開啟防篡改請檢查後重試',
gpu: '升級專業版可以幫助用戶實時直觀查看到 GPU 的工作負載溫度顯存等重要參數',
setting: '升級專業版可以自定義面板 Logo歡迎簡介等信息',
monitor: '升級專業版可以查看網站的即時狀態訪客趨勢訪客來源請求日誌等資訊 ',
alert: '陞級專業版可通過簡訊接收告警資訊並查看告警日誌全面掌控各類關鍵事件確保系統運行無憂',
@ -2774,42 +2833,6 @@ const message = {
enableHelper: '即將啟用 {0} 網站的防篡改功能以提升網站安全性是否繼續',
disableHelper: '即將關閉 {0} 網站的防篡改功能是否繼續',
},
gpu: {
gpu: 'GPU 监控',
base: '基礎資訊',
gpuHelper: '目前系統未檢測到 NVIDIA-SMI或者XPU-SMI 指令請檢查後重試',
driverVersion: '驅動版本',
cudaVersion: 'CUDA 版本',
process: '行程資訊',
type: '類型',
typeG: '圖形',
typeC: '計算',
typeCG: '計算+圖形',
processName: '行程名稱',
processMemoryUsage: '顯存使用',
temperatureHelper: 'GPU 溫度過高會導致 GPU 頻率下降',
performanceStateHelper: ' P0 (最大性能) P12 (最小性能)',
busID: '總線地址',
persistenceMode: '持續模式',
enabled: '開啟',
disabled: '關閉',
persistenceModeHelper: '持續模式能更加快速地響應任務但相應待機功耗也會增加',
displayActive: '顯卡初始化',
displayActiveT: '是',
displayActiveF: '否',
ecc: '是否開啟錯誤檢查和紀正技術',
computeMode: '計算模式',
default: '預設',
exclusiveProcess: '行程排他',
exclusiveThread: '線程排他',
prohibited: '禁止',
defaultHelper: '預設: 行程可以並發執行',
exclusiveProcessHelper: '行程排他: 只有一個 CUDA 上下文可以使用 GPU 但可以由多個線程共享',
exclusiveThreadHelper: '線程排他: 只有一個線程在 CUDA 上下文中可以使用 GPU',
prohibitedHelper: '禁止: 不允許行程同時執行',
migModeHelper: '用於建立 MIG 實例在用戶層實現 GPU 的物理隔離',
migModeNA: '不支援',
},
setting: {
setting: '界面設定',
title: '面板描述',
@ -2847,11 +2870,6 @@ const message = {
tamperTitle4: '日誌紀錄與分析',
tamperContent4: '紀錄檔案瀏覽和操作日誌以便管理員進行後續的審計和分析以及發現潛在的安全威脅',
gpuTitle1: '概覽頁監控',
gpuContent1: '在概覽頁上顯示 GPU 當前使用情況',
gpuTitle2: 'GPU 詳細資訊',
gpuContent2: '更加細緻地顯示 GPU 各項參數',
settingTitle1: '自訂歡迎語',
settingContent1: ' 1Panel 登入頁上設定自訂的歡迎語',
settingTitle2: '自訂 Logo',

View File

@ -9,6 +9,7 @@ const message = {
lingxia: '凌霞',
colon: ' ',
button: {
run: '运行',
prev: '上一步',
next: '下一步',
create: '创建',
@ -325,6 +326,7 @@ const message = {
firewall: '防火墙',
ssl: '证书',
database: '数据库',
aiTools: 'AI',
container: '容器',
cronjob: '计划任务',
host: '主机',
@ -568,6 +570,65 @@ const message = {
remoteConnHelper2: '非容器或外部连接使用此地址',
localIP: '本机 IP',
},
aiTools: {
model: {
model: '模型',
create: '添加模型',
create_helper: '拉取 "{0}"',
ollama_doc: '您可以访问 Ollama 官网搜索并查找更多模型',
container_conn_helper: '容器间访问或连接使用此地址',
ollama_sync: '同步 Ollama 模型发现下列模型不存在是否删除',
from_remote: '该模型并非通过 1Panel 下载无相关拉取日志',
no_logs: '该模型的拉取日志已被删除无法查看相关日志',
},
proxy: {
proxy: 'AI 代理增强',
proxyHelper1: '绑定域名并开启 HTTPS增强传输安全性',
proxyHelper2: '限制 IP 访问防止在公网暴露',
proxyHelper3: '开启流式传输',
proxyHelper4: '创建完成之后可以在网站列表中查看并管理',
proxyHelper5: '创建完成之后可以在应用商店 - 已安装 - ollama - 参数中取消端口外部访问以提高安全性',
proxyHelper6: '如需关闭代理配置可以在网站列表中删除',
whiteListHelper: '限制仅白名单中的 IP 可访问',
},
gpu: {
gpu: 'GPU 监控',
base: '基础信息',
gpuHelper: '当前系统未检测到 NVIDIA-SMI或者XPU-SMI 指令请检查后重试',
driverVersion: '驱动版本',
cudaVersion: 'CUDA 版本',
process: '进程信息',
type: '类型',
typeG: '图形',
typeC: '计算',
typeCG: '计算+图形',
processName: '进程名称',
processMemoryUsage: '显存使用',
temperatureHelper: 'GPU 温度过高会导致 GPU 频率下降',
performanceStateHelper: ' P0 (最大性能) P12 (最小性能)',
busID: '总线地址',
persistenceMode: '持续模式',
enabled: '开启',
disabled: '关闭',
persistenceModeHelper: '持续模式能更加快速地响应任务但相应待机功耗也会增加',
displayActive: '显卡初始化',
displayActiveT: '是',
displayActiveF: '否',
ecc: '是否开启错误检查和纠正技术',
computeMode: '计算模式',
default: '默认',
exclusiveProcess: '进程排他',
exclusiveThread: '线程排他',
prohibited: '禁止',
defaultHelper: '默认: 进程可以并发执行',
exclusiveProcessHelper: '进程排他: 只有一个 CUDA 上下文可以使用 GPU, 但可以由多个线程共享',
exclusiveThreadHelper: '线程排他: 只有一个线程在 CUDA 上下文中可以使用 GPU',
prohibitedHelper: '禁止: 不允许进程同时执行',
migModeHelper: '用于创建 MIG 实例在用户层实现 GPU 的物理隔离',
migModeNA: '不支持',
shr: '共享显存',
},
},
container: {
create: '创建容器',
createByCommand: '命令创建',
@ -1665,7 +1726,6 @@ const message = {
waf: '升级专业版可以获得拦截地图日志封锁记录地理位置封禁自定义规则自定义拦截页面等功能',
tamper: '升级专业版可以保护网站免受未经授权的修改或篡改',
tamperHelper: '操作失败该文件或文件夹已经开启防篡改请检查后重试',
gpu: '升级专业版可以帮助用户实时直观查看到 GPU 的工作负载温度显存等重要参数',
setting: '升级专业版可以自定义面板 Logo欢迎简介等信息',
monitor: '升级专业版可以查看网站的实时状态访客趋势访客来源请求日志等信息',
alert: '升级专业版可通过短信接收告警信息并查看告警日志全面掌控各类关键事件确保系统运行无忧',
@ -2803,38 +2863,6 @@ const message = {
enableHelper: '即将启用下列网站的防篡改功能以提升网站安全性是否继续',
disableHelper: '即将关闭下列网站的防篡改功能是否继续',
},
gpu: {
gpu: 'GPU 监控',
base: '基础信息',
gpuHelper: '当前系统未检测到 NVIDIA-SMI 指令请检查后重试',
driverVersion: '驱动版本',
cudaVersion: 'CUDA 版本',
process: '进程信息',
typeG: '图形',
typeC: '计算',
typeCG: '计算+图形',
processName: '进程名称',
processMemoryUsage: '显存使用',
temperatureHelper: 'GPU 温度过高会导致 GPU 频率下降',
performanceStateHelper: ' P0 (最大性能) P12 (最小性能)',
busID: '总线地址',
persistenceMode: '持续模式',
persistenceModeHelper: '持续模式能更加快速地响应任务但相应待机功耗也会增加',
displayActive: '显卡初始化',
displayActiveT: '是',
displayActiveF: '否',
ecc: '是否开启错误检查和纠正技术',
computeMode: '计算模式',
exclusiveProcess: '进程排他',
exclusiveThread: '线程排他',
prohibited: '禁止',
defaultHelper: '默认: 进程可以并发执行',
exclusiveProcessHelper: '进程排他: 只有一个 CUDA 上下文可以使用 GPU, 但可以由多个线程共享',
exclusiveThreadHelper: '线程排他: 只有一个线程在 CUDA 上下文中可以使用 GPU',
prohibitedHelper: '禁止: 不允许进程同时执行',
migModeHelper: '用于创建 MIG 实例在用户层实现 GPU 的物理隔离',
migModeNA: '不支持',
},
setting: {
setting: '界面设置',
title: '面板描述',
@ -2870,11 +2898,6 @@ const message = {
tamperTitle4: '日志记录与分析',
tamperContent4: '记录文件访问和操作日志以便管理员进行后续的审计和分析以及发现潜在的安全威胁',
gpuTitle1: '概览页监控',
gpuContent1: '在概览页上显示 GPU 当前使用情况',
gpuTitle2: 'GPU 详细信息',
gpuContent2: '更加细粒度的显示出 GPU 各项参数',
settingTitle1: '自定义欢迎语',
settingContent1: ' 1Panel 登录页上设置自定义的欢迎语',
settingTitle2: '自定义 Logo',

View File

@ -0,0 +1,34 @@
import { Layout } from '@/routers/constant';
const databaseRouter = {
sort: 4,
path: '/ai',
component: Layout,
redirect: '/ai/model',
meta: {
icon: 'p-jiqiren2',
title: 'menu.aiTools',
},
children: [
{
path: '/ai/model',
name: 'OllamaModel',
component: () => import('@/views/ai/model/index.vue'),
meta: {
title: 'aiTools.model.model',
requiresAuth: true,
},
},
{
path: '/ai/gpu',
name: 'GPU',
component: () => import('@/views/ai/gpu/index.vue'),
meta: {
title: 'aiTools.gpu.gpu',
requiresAuth: true,
},
},
],
};
export default databaseRouter;

View File

@ -1,7 +1,7 @@
import { Layout } from '@/routers/constant';
const containerRouter = {
sort: 5,
sort: 6,
path: '/containers',
component: Layout,
redirect: '/containers/container',

View File

@ -1,7 +1,7 @@
import { Layout } from '@/routers/constant';
const databaseRouter = {
sort: 4,
sort: 5,
path: '/databases',
component: Layout,
redirect: '/databases/mysql',

View File

@ -1,7 +1,7 @@
import { Layout } from '@/routers/constant';
const hostRouter = {
sort: 6,
sort: 7,
path: '/hosts',
component: Layout,
redirect: '/hosts/security',

View File

@ -1,7 +1,7 @@
import { Layout } from '@/routers/constant';
const terminalRouter = {
sort: 7,
sort: 8,
path: '/terminal',
component: Layout,
redirect: '/terminal',

View File

@ -1,7 +1,7 @@
import { Layout } from '@/routers/constant';
const toolboxRouter = {
sort: 8,
sort: 9,
path: '/toolbox',
component: Layout,
redirect: '/toolbox/supervisor',

View File

@ -0,0 +1,367 @@
<template>
<div>
<RouterButton
:buttons="[
{
label: $t('aiTools.gpu.gpu'),
path: '/xpack/gpu',
},
]"
/>
<div v-if="gpuType == 'nvidia'">
<LayoutContent
v-loading="loading"
:title="$t('aiTools.gpu.gpu')"
:divider="true"
v-if="gpuInfo.driverVersion.length !== 0 && !loading"
>
<template #toolbar>
<el-row>
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16" />
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<TableSetting @search="refresh()" />
</el-col>
</el-row>
</template>
<template #main>
<el-descriptions direction="vertical" :column="14" border>
<el-descriptions-item :label="$t('aiTools.gpu.driverVersion')" width="50%" :span="7">
{{ gpuInfo.driverVersion }}
</el-descriptions-item>
<el-descriptions-item :label="$t('aiTools.gpu.cudaVersion')" :span="7">
{{ gpuInfo.cudaVersion }}
</el-descriptions-item>
</el-descriptions>
<el-collapse v-model="activeNames" class="mt-5">
<el-collapse-item v-for="item in gpuInfo.gpu" :key="item.index" :name="item.index">
<template #title>
<span class="name-class">{{ item.index + '. ' + item.productName }}</span>
</template>
<span class="title-class">{{ $t('aiTools.gpu.base') }}</span>
<el-descriptions direction="vertical" :column="6" border size="small" class="mt-2">
<el-descriptions-item :label="$t('monitor.gpuUtil')">
{{ item.gpuUtil }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
{{ $t('monitor.temperature') }}
<el-tooltip placement="top" :content="$t('aiTools.gpu.temperatureHelper')">
<el-icon class="icon-item"><InfoFilled /></el-icon>
</el-tooltip>
</div>
</template>
{{ item.temperature.replaceAll('C', '°C') }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
{{ $t('monitor.performanceState') }}
<el-tooltip
placement="top"
:content="$t('aiTools.gpu.performanceStateHelper')"
>
<el-icon class="icon-item"><InfoFilled /></el-icon>
</el-tooltip>
</div>
</template>
{{ item.performanceState }}
</el-descriptions-item>
<el-descriptions-item :label="$t('monitor.powerUsage')">
{{ item.powerDraw }} / {{ item.maxPowerLimit }}
</el-descriptions-item>
<el-descriptions-item :label="$t('monitor.memoryUsage')">
{{ item.memUsed }} / {{ item.memTotal }}
</el-descriptions-item>
<el-descriptions-item :label="$t('monitor.fanSpeed')">
{{ item.fanSpeed }}
</el-descriptions-item>
<el-descriptions-item :label="$t('aiTools.gpu.busID')">
{{ item.busID }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
{{ $t('aiTools.gpu.persistenceMode') }}
<el-tooltip
placement="top"
:content="$t('aiTools.gpu.persistenceModeHelper')"
>
<el-icon class="icon-item"><InfoFilled /></el-icon>
</el-tooltip>
</div>
</template>
{{ $t('aiTools.gpu.' + item.persistenceMode.toLowerCase()) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('aiTools.gpu.displayActive')">
{{
lowerCase(item.displayActive) === 'disabled'
? $t('aiTools.gpu.displayActiveF')
: $t('aiTools.gpu.displayActiveT')
}}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
Uncorr. ECC
<el-tooltip placement="top" :content="$t('aiTools.gpu.ecc')">
<el-icon class="icon-item"><InfoFilled /></el-icon>
</el-tooltip>
</div>
</template>
{{ loadEcc(item.ecc) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('aiTools.gpu.computeMode')">
<template #label>
<div class="cell-item">
{{ $t('aiTools.gpu.computeMode') }}
<el-tooltip placement="top">
<template #content>
{{ $t('aiTools.gpu.defaultHelper') }}
<br />
{{ $t('aiTools.gpu.exclusiveProcessHelper') }}
<br />
{{ $t('aiTools.gpu.exclusiveThreadHelper') }}
<br />
{{ $t('aiTools.gpu.prohibitedHelper') }}
</template>
<el-icon class="icon-item"><InfoFilled /></el-icon>
</el-tooltip>
</div>
</template>
{{ loadComputeMode(item.computeMode) }}
</el-descriptions-item>
<el-descriptions-item label="MIG.M">
<template #label>
<div class="cell-item">
MIG M.
<el-tooltip placement="top">
<template #content>
{{ $t('aiTools.gpu.migModeHelper') }}
</template>
<el-icon class="icon-item"><InfoFilled /></el-icon>
</el-tooltip>
</div>
</template>
{{
item.migMode === 'N/A'
? $t('aiTools.gpu.migModeNA')
: $t('aiTools.gpu.' + lowerCase(item.migMode))
}}
</el-descriptions-item>
</el-descriptions>
<div class="mt-5">
<span class="title-class">{{ $t('aiTools.gpu.process') }}</span>
</div>
<el-table :data="item.processes" v-if="item.processes?.length !== 0">
<el-table-column label="PID" prop="pid" />
<el-table-column :label="$t('aiTools.gpu.type')" prop="type">
<template #default="{ row }">
{{ loadProcessType(row.type) }}
</template>
</el-table-column>
<el-table-column :label="$t('aiTools.gpu.processName')" prop="processName" />
<el-table-column :label="$t('aiTools.gpu.processMemoryUsage')" prop="usedMemory" />
</el-table>
</el-collapse-item>
</el-collapse>
</template>
</LayoutContent>
</div>
<div v-else>
<LayoutContent
v-loading="loading"
:title="$t('aiTools.gpu.gpu')"
:divider="true"
v-if="xpuInfo.driverVersion.length !== 0 && !loading"
>
<template #toolbar>
<el-row>
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16" />
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<TableSetting @search="refresh()" />
</el-col>
</el-row>
</template>
<template #main>
<el-descriptions direction="vertical" :column="14" border>
<el-descriptions-item :label="$t('aiTools.gpu.driverVersion')" width="50%" :span="7">
{{ xpuInfo.driverVersion }}
</el-descriptions-item>
</el-descriptions>
<el-collapse v-model="activeNames" class="mt-5">
<el-collapse-item
v-for="item in xpuInfo.xpu"
:key="item.basic.deviceID"
:name="item.basic.deviceID"
>
<template #title>
<span class="name-class">{{ item.basic.deviceID + '. ' + item.basic.deviceName }}</span>
</template>
<span class="title-class">{{ $t('aiTools.gpu.base') }}</span>
<el-descriptions direction="vertical" :column="6" border size="small" class="mt-2">
<el-descriptions-item :label="$t('monitor.gpuUtil')">
{{ item.stats.memoryUtil }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
{{ $t('monitor.temperature') }}
<el-tooltip placement="top" :content="$t('aiTools.gpu.temperatureHelper')">
<el-icon class="icon-item"><InfoFilled /></el-icon>
</el-tooltip>
</div>
</template>
{{ item.stats.temperature }}
</el-descriptions-item>
<el-descriptions-item :label="$t('monitor.powerUsage')">
{{ item.stats.power }}
</el-descriptions-item>
<el-descriptions-item :label="$t('monitor.memoryUsage')">
{{ item.stats.memoryUsed }} / {{ item.basic.memory }}
</el-descriptions-item>
<el-descriptions-item :label="$t('aiTools.gpu.busID')">
{{ item.basic.pciBdfAddress }}
</el-descriptions-item>
</el-descriptions>
<div class="mt-5">
<span class="title-class">{{ $t('aiTools.gpu.process') }}</span>
</div>
<el-table :data="item.processes" v-if="item.processes?.length !== 0">
<el-table-column label="PID" prop="pid" />
<el-table-column :label="$t('aiTools.gpu.processName')" prop="command" />
<el-table-column :label="$t('aiTools.gpu.shr')" prop="shr" />
<el-table-column :label="$t('aiTools.gpu.processMemoryUsage')" prop="memory" />
</el-table>
</el-collapse-item>
</el-collapse>
</template>
</LayoutContent>
</div>
<LayoutContent
:title="$t('aiTools.gpu.gpu')"
:divider="true"
v-if="gpuInfo.driverVersion.length === 0 && xpuInfo.driverVersion.length == 0 && !loading"
>
<template #main>
<div class="app-warn">
<div class="flx-center">
<span>{{ $t('aiTools.gpu.gpuHelper') }}</span>
</div>
<div>
<img src="@/assets/images/no_app.svg" />
</div>
</div>
</template>
</LayoutContent>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { loadGPUInfo } from '@/api/modules/ai';
import { AI } from '@/api/interface/ai';
import i18n from '@/lang';
const loading = ref();
const activeNames = ref(0);
const gpuInfo = ref<AI.Info>({
cudaVersion: '',
driverVersion: '',
type: 'nvidia',
gpu: [],
});
const xpuInfo = ref<AI.XpuInfo>({
driverVersion: '',
type: 'xpu',
xpu: [],
});
const gpuType = ref('nvidia');
const search = async () => {
loading.value = true;
await loadGPUInfo()
.then((res) => {
loading.value = false;
gpuType.value = res.data.type;
if (res.data.type == 'nvidia') {
gpuInfo.value = res.data;
} else {
xpuInfo.value = res.data;
}
})
.catch(() => {
loading.value = false;
});
};
const refresh = async () => {
const res = await loadGPUInfo();
gpuInfo.value = res.data;
};
const lowerCase = (val: string) => {
return val.toLowerCase();
};
const loadComputeMode = (val: string) => {
switch (val) {
case 'Default':
return i18n.global.t('aiTools.gpu.default');
case 'Exclusive Process':
return i18n.global.t('aiTools.gpu.exclusiveProcess');
case 'Exclusive Thread':
return i18n.global.t('aiTools.gpu.exclusiveThread');
case 'Prohibited':
return i18n.global.t('aiTools.gpu.prohibited');
}
};
const loadEcc = (val: string) => {
if (val === 'N/A') {
return i18n.global.t('aiTools.gpu.migModeNA');
}
if (val === 'Disabled') {
return i18n.global.t('aiTools.gpu.disabled');
}
if (val === 'Enabled') {
return i18n.global.t('aiTools.gpu.enabled');
}
return val;
};
const loadProcessType = (val: string) => {
if (val === 'C' || val === 'G') {
return i18n.global.t('aiTools.gpu.type' + val);
}
if (val === 'C+G') {
return i18n.global.t('aiTools.gpu.typeCG');
}
return val;
};
onMounted(() => {
search();
});
</script>
<style lang="scss" scoped>
.name-class {
font-size: 18px;
font-weight: 500;
}
.title-class {
font-size: 14px;
font-weight: 500;
}
.cell-item {
display: flex;
align-items: center;
.icon-item {
margin-left: 4px;
margin-top: -1px;
}
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<DrawerPro v-model="drawerVisible" :header="$t('aiTools.model.create')" :back="handleClose">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-alert type="info" :closable="false">
<template #title>
<span class="flx-align-center">
{{ $t('aiTools.model.ollama_doc') }}
<el-button link class="ml-5" icon="Position" @click="goSearch()" type="primary">
{{ $t('firewall.quickJump') }}
</el-button>
</span>
</template>
</el-alert>
<el-form ref="formRef" label-position="top" class="mt-5" :model="form">
<el-form-item :label="$t('commons.table.name')" :rules="Rules.requiredInput" prop="name">
<el-input v-model.trim="form.name" />
<span class="input-help" v-if="form.name">
{{
$t('aiTools.model.create_helper', [
form.name.replaceAll('ollama run ', '').replaceAll('ollama pull ', ''),
])
}}
</span>
</el-form-item>
</el-form>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.add') }}
</el-button>
</span>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
import { createOllamaModel } from '@/api/modules/ai';
const drawerVisible = ref(false);
const form = reactive({
name: '',
});
const acceptParams = async (): Promise<void> => {
form.name = '';
drawerVisible.value = true;
};
const emit = defineEmits(['search', 'log']);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let itemName = form.name.replaceAll('ollama run ', '').replaceAll('ollama pull ', '');
await createOllamaModel(itemName);
drawerVisible.value = false;
emit('search');
emit('log', { logFileExist: true, name: itemName, from: 'local' });
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
});
};
const goSearch = () => {
window.open('https://ollama.com/search', '_blank', 'noopener,noreferrer');
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>

View File

@ -0,0 +1,139 @@
<template>
<DrawerPro v-model="drawerVisible" :header="$t('database.databaseConnInfo')" :back="handleClose" size="small">
<el-form @submit.prevent v-loading="loading" :model="form" label-position="top">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('database.containerConn')">
<el-card class="mini-border-card">
<el-descriptions :column="1">
<el-descriptions-item :label="$t('database.connAddress')">
{{ form.containerName }}
<CopyButton :content="form.containerName" type="icon" />
</el-descriptions-item>
<el-descriptions-item :label="$t('commons.table.port')">
11434
<CopyButton content="11434" type="icon" />
</el-descriptions-item>
</el-descriptions>
</el-card>
<span class="input-help">
{{ $t('aiTools.model.container_conn_helper') }}
</span>
</el-form-item>
<el-form-item :label="$t('setting.proxyUrl')" v-if="bindDomain.connUrl != ''">
<el-card class="mini-border-card">
<el-descriptions :column="1">
<el-descriptions-item :label="$t('database.connAddress')">
{{ bindDomain.connUrl }}
<CopyButton :content="bindDomain.connUrl" type="icon" />
</el-descriptions-item>
</el-descriptions>
</el-card>
<span class="input-help">
{{ $t('database.remoteConnHelper2') }}
</span>
</el-form-item>
<el-form-item :label="$t('database.remoteConn')" v-else>
<el-card class="mini-border-card">
<el-descriptions :column="1">
<el-descriptions-item :label="$t('database.connAddress')">
{{ form.systemIP }}
<CopyButton :content="form.systemIP" type="icon" />
</el-descriptions-item>
<el-descriptions-item :label="$t('commons.table.port')">
{{ form.port }}
<CopyButton :content="form.port + ''" type="icon" />
</el-descriptions-item>
</el-descriptions>
</el-card>
<span class="input-help">
{{ $t('database.remoteConnHelper2') }}
</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="loading" @click="drawerVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
</span>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { getSettingInfo } from '@/api/modules/setting';
import { getBindDomain } from '@/api/modules/ai';
const loading = ref(false);
const drawerVisible = ref(false);
const form = reactive({
systemIP: '',
containerName: '',
port: 0,
remoteIP: '',
});
const bindDomain = ref({
connUrl: '',
});
interface DialogProps {
port: number;
containerName: string;
appinstallID: number;
}
const acceptParams = (param: DialogProps): void => {
form.containerName = param.containerName;
form.port = param.port;
loadSystemIP();
loadBindDomain(param.appinstallID);
drawerVisible.value = true;
};
const handleClose = () => {
drawerVisible.value = false;
};
const loadSystemIP = async () => {
const res = await getSettingInfo();
form.systemIP = res.data.systemIP || i18n.global.t('database.localIP');
};
const loadBindDomain = async (appInstallID: number) => {
if (appInstallID == undefined || appInstallID <= 0) {
return;
}
try {
const res = await getBindDomain({
appInstallID: appInstallID,
});
if (res.data.websiteID > 0) {
bindDomain.value.connUrl = res.data.connUrl;
}
} catch (e) {}
};
defineExpose({
acceptParams,
});
</script>
<style lang="scss" scoped>
.copy_button {
border-radius: 0px;
border-left-width: 0px;
}
:deep(.el-input__wrapper) {
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
</style>

View File

@ -0,0 +1,105 @@
<template>
<DialogPro v-model="open" :title="$t('commons.button.sync')" size="small" @close="handleClose">
<div v-loading="loading">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-alert class="mt-2" :show-icon="true" type="warning" :closable="false">
{{ $t('aiTools.model.ollama_sync') }}
</el-alert>
<el-checkbox
class="mt-2"
v-model="checkAll"
:indeterminate="isIndeterminate"
@change="handleCheckAllChange"
>
{{ $t('setting.all') }}
</el-checkbox>
<el-checkbox-group v-model="checkedItems" @change="handleCheckedChange">
<el-checkbox v-for="(item, index) in list" :key="index" :label="item.name" :value="item.id" />
</el-checkbox-group>
</el-col>
</el-row>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose()" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onConfirm" :disabled="loading || checkedItems.length === 0">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DialogPro>
</template>
<script setup lang="ts">
import { AI } from '@/api/interface/ai';
import { deleteOllamaModel } from '@/api/modules/ai';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { CheckboxValueType } from 'element-plus';
import { onMounted, ref } from 'vue';
defineOptions({ name: 'OpDialog' });
const checkAll = ref(false);
const isIndeterminate = ref(true);
const checkedItems = ref([]);
const list = ref([]);
const loading = ref();
const open = ref();
interface DialogProps {
list: Array<AI.OllamaModelDropInfo>;
}
const acceptParams = (props: DialogProps): void => {
list.value = props.list;
checkAll.value = true;
handleCheckAllChange(true);
open.value = true;
};
const emit = defineEmits(['search']);
const handleCheckAllChange = (val: CheckboxValueType) => {
checkedItems.value = [];
if (val) {
for (const item of list.value) {
checkedItems.value.push(item.id);
}
}
isIndeterminate.value = false;
};
const handleCheckedChange = (value: CheckboxValueType[]) => {
const checkedCount = value.length;
checkAll.value = checkedCount === list.value.length;
isIndeterminate.value = checkedCount > 0 && checkedCount < list.value.length;
};
const onConfirm = async () => {
loading.value = true;
await deleteOllamaModel(checkedItems.value, true)
.then(() => {
emit('search');
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
open.value = false;
loading.value = false;
})
.catch(() => {
loading.value = false;
});
};
const handleClose = () => {
emit('search');
open.value = false;
};
onMounted(() => {});
defineExpose({
acceptParams,
});
</script>

View File

@ -0,0 +1,260 @@
<template>
<DrawerPro v-model="open" :title="$t('aiTools.proxy.proxy')" :back="handleClose" size="large">
<div v-loading="loading">
<el-form ref="formRef" label-position="top" @submit.prevent :model="req" :rules="rules">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-alert class="common-prompt" :closable="false" type="warning">
<template #default>
<ul>
<li>{{ $t('aiTools.proxy.proxyHelper1') }}</li>
<li>{{ $t('aiTools.proxy.proxyHelper2') }}</li>
<li>{{ $t('aiTools.proxy.proxyHelper3') }}</li>
</ul>
</template>
</el-alert>
<el-form-item :label="$t('website.domain')" prop="domain">
<el-input v-model.trim="req.domain" :disabled="operate === 'update'" />
<span class="input-help">
{{ $t('aiTools.proxy.proxyHelper4') }}
</span>
<span class="input-help">
{{ $t('aiTools.proxy.proxyHelper6') }}
<el-link
class="pageRoute"
icon="Position"
@click="toWebsite(req.websiteID)"
type="primary"
>
{{ $t('firewall.quickJump') }}
</el-link>
</span>
</el-form-item>
<el-form-item :label="$t('xpack.waf.whiteList') + ' IP'" prop="ipList">
<el-input
:rows="3"
type="textarea"
clearable
v-model="req.ipList"
:placeholder="$t('xpack.waf.ipGroupHelper')"
/>
<span class="input-help">
{{ $t('aiTools.proxy.whiteListHelper') }}
</span>
</el-form-item>
<el-form-item>
<el-checkbox v-model="req.enableSSL" @change="changeSSL">
{{ $t('website.enable') + ' ' + 'HTTPS' }}
</el-checkbox>
</el-form-item>
<el-form-item
:label="$t('website.acmeAccountManage')"
prop="acmeAccountID"
v-if="req.enableSSL"
>
<el-select
v-model="req.acmeAccountID"
:placeholder="$t('website.selectAcme')"
@change="listSSL"
>
<el-option :key="0" :label="$t('website.imported')" :value="0"></el-option>
<el-option
v-for="(acme, index) in acmeAccounts"
:key="index"
:label="acme.email"
:value="acme.id"
>
<span>
{{ acme.email }}
<el-tag class="ml-5">{{ getAccountName(acme.type) }}</el-tag>
</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('website.ssl')" prop="sslID" v-if="req.enableSSL">
<el-select
v-model="req.sslID"
:placeholder="$t('website.selectSSL')"
@change="changeSSl(req.sslID)"
>
<el-option
v-for="(ssl, index) in ssls"
:key="index"
:label="ssl.primaryDomain"
:value="ssl.id"
></el-option>
</el-select>
</el-form-item>
<el-alert :closable="false">
{{ $t('aiTools.proxy.proxyHelper5') }}
<el-link class="pageRoute" icon="Position" @click="toInstalled()" type="primary">
{{ $t('firewall.quickJump') }}
</el-link>
</el-alert>
</el-col>
</el-row>
</el-form>
</div>
<template #footer>
<el-button @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.add') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { Website } from '@/api/interface/website';
import { listSSL, searchAcmeAccount } from '@/api/modules/website';
import { Rules } from '@/global/form-rules';
import { FormInstance, FormRules } from 'element-plus';
import { reactive, ref } from 'vue';
import { getAccountName } from '@/utils/util';
import { bindDomain, getBindDomain, updateBindDomain } from '@/api/modules/ai';
import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang';
const open = ref(false);
const operate = ref('create');
const loading = ref(false);
const ssls = ref([]);
const websiteSSL = ref<Website.SSL>();
const acmeAccounts = ref();
const formRef = ref();
const req = ref({
domain: '',
sslID: undefined,
ipList: '',
acmeAccountID: 0,
enableSSL: false,
allowIPs: [],
appInstallID: 0,
websiteID: 0,
});
const rules = reactive<FormRules>({
domain: [Rules.domainWithPort],
sslID: [Rules.requiredSelectBusiness],
});
const emit = defineEmits(['search']);
const handleClose = () => {
emit('search');
open.value = false;
};
const acceptParams = (installID: number) => {
req.value.appInstallID = installID;
search(installID);
open.value = true;
};
const changeSSl = (sslid: number) => {
const res = ssls.value.filter((element: Website.SSL) => {
return element.id == sslid;
});
websiteSSL.value = res[0];
};
const changeSSL = () => {
if (!req.value.enableSSL) {
req.value.sslID = undefined;
} else {
listAcmeAccount();
}
};
const loadSSL = () => {
const sslReq = {
acmeAccountID: String(req.value.acmeAccountID),
};
listSSL(sslReq).then((res) => {
ssls.value = res.data || [];
if (ssls.value.length > 0) {
let exist = false;
for (const ssl of ssls.value) {
if (ssl.id === req.value.sslID) {
exist = true;
break;
}
}
if (!exist) {
req.value.sslID = ssls.value[0].id;
}
changeSSl(req.value.sslID);
} else {
req.value.sslID = undefined;
}
});
};
const listAcmeAccount = () => {
searchAcmeAccount({ page: 1, pageSize: 100 }).then((res) => {
acmeAccounts.value = res.data.items || [];
if (acmeAccounts.value.length > 0) {
req.value.acmeAccountID = acmeAccounts.value[0].id;
}
loadSSL();
});
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (operate.value === 'update') {
await updateBindDomain(req.value);
} else {
await bindDomain(req.value);
}
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
handleClose();
});
};
const search = async (appInstallID: number) => {
try {
const res = await getBindDomain({
appInstallID: appInstallID,
});
if (res.data.websiteID > 0) {
operate.value = 'update';
req.value.domain = res.data.domain;
req.value.websiteID = res.data.websiteID;
if (res.data.allowIPs && res.data.allowIPs.length > 0) {
req.value.ipList = res.data.allowIPs.join('\n');
}
if (res.data.sslID > 0) {
req.value.enableSSL = true;
req.value.sslID = res.data.sslID;
listAcmeAccount();
}
}
} catch (e) {}
};
const toWebsite = (websiteID: number) => {
if (websiteID != undefined && websiteID > 0) {
window.location.href = `/websites/${websiteID}/config/basic`;
} else {
window.location.href = '/websites';
}
};
const toInstalled = () => {
window.location.href = '/apps/installed';
};
defineExpose({
acceptParams,
});
</script>
<style lang="scss" scoped>
.pageRoute {
font-size: 12px;
margin-left: 5px;
}
</style>

View File

@ -0,0 +1,457 @@
<template>
<div v-loading="loading">
<RouterButton
:buttons="[
{
label: i18n.global.t('aiTools.model.model'),
path: '/ai-tools/model',
},
]"
/>
<LayoutContent title="Ollama">
<template #app>
<AppStatus
app-key="ollama"
v-model:loading="loading"
:hide-setting="true"
v-model:mask-show="maskShow"
v-model:appInstallID="appInstallID"
@is-exist="checkExist"
ref="appStatusRef"
></AppStatus>
</template>
<template #prompt>
<el-alert type="info" :closable="false">
<template #title>
<span>{{ $t('runtime.systemRestartHelper') }}</span>
</template>
</el-alert>
</template>
<template #leftToolBar>
<el-button :disabled="modelInfo.status !== 'Running'" type="primary" @click="onCreate()">
{{ $t('aiTools.model.create') }}
</el-button>
<el-button plain type="primary" :disabled="modelInfo.status !== 'Running'" @click="bindDomain">
{{ $t('aiTools.proxy.proxy') }}
</el-button>
<el-button :disabled="modelInfo.status !== 'Running'" @click="onLoadConn" type="primary" plain>
{{ $t('database.databaseConnInfo') }}
</el-button>
<el-button :disabled="modelInfo.status !== 'Running'" type="primary" plain @click="onSync()">
{{ $t('database.loadFromRemote') }}
</el-button>
<el-button
:disabled="modelInfo.status !== 'Running'"
icon="Position"
@click="goDashboard()"
type="primary"
plain
>
OpenWebUI
</el-button>
<el-button plain :disabled="selects.length === 0" type="primary" @click="onDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
</template>
<template #rightToolBar>
<TableSearch @search="search()" v-model:searchName="searchName" />
<TableRefresh @search="search()" />
<TableSetting title="model-refresh" @search="search()" />
</template>
<template #main>
<ComplexTable
:pagination-config="paginationConfig"
v-model:selects="selects"
:class="{ mask: maskShow }"
@sort-change="search"
@search="search"
:data="data"
>
<el-table-column type="selection" :selectable="selectable" fix />
<el-table-column :label="$t('aiTools.model.model')" prop="name" min-width="90">
<template #default="{ row }">
<el-text v-if="row.size" type="primary" class="cursor-pointer" @click="onLoad(row.name)">
{{ row.name }}
</el-text>
<span v-else>{{ row.name }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('file.size')" prop="size">
<template #default="{ row }">
<span>{{ row.size || '-' }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" prop="status">
<template #default="{ row }">
<el-tag v-if="row.status === 'Success'" type="success">
{{ $t('commons.status.success') }}
</el-tag>
<el-tag v-if="row.status === 'Deleted'" type="info">
{{ $t('database.isDelete') }}
</el-tag>
<el-tag v-if="row.status === 'Canceled'" type="danger">
{{ $t('commons.status.systemrestart') }}
</el-tag>
<el-tag v-if="row.status === 'Failed'" type="danger">
{{ $t('commons.status.failed') }}
</el-tag>
<el-tag v-if="row.status === 'Waiting'">
<el-icon v-if="row.status === 'Waiting'" class="is-loading">
<Loading />
</el-icon>
{{ $t('commons.status.waiting') }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('commons.button.log')">
<template #default="{ row }">
<el-button @click="onLoadLog(row)" link type="primary">
{{ $t('website.check') }}
</el-button>
</template>
</el-table-column>
<el-table-column
min-width="80"
:label="$t('commons.table.date')"
prop="createdAt"
:formatter="dateFormat"
/>
<fu-table-operations
:ellipsis="mobile ? 0 : 10"
:min-width="mobile ? 'auto' : 200"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable>
<el-card v-if="modelInfo.status != 'Running' && !loading && maskShow" class="mask-prompt">
<span v-if="modelInfo.isExist">
{{ $t('commons.service.serviceNotStarted', ['Ollama']) }}
</span>
<span v-else>
{{ $t('app.checkInstalledWarn', ['Ollama']) }}
<el-button @click="goInstall('ollama')" link icon="Position" type="primary">
{{ $t('database.goInstall') }}
</el-button>
</span>
</el-card>
</template>
</LayoutContent>
<DialogPro v-model="dashboardVisible" :title="$t('app.checkTitle')" size="mini">
<div class="flex justify-center items-center gap-2 flex-wrap">
{{ $t('app.checkInstalledWarn', ['OpenWebUI']) }}
<el-link icon="Position" @click="goInstall('ollama-webui')" type="primary">
{{ $t('database.goInstall') }}
</el-link>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dashboardVisible = false">{{ $t('commons.button.cancel') }}</el-button>
</span>
</template>
</DialogPro>
<OpDialog ref="opRef" @search="search" @submit="onSubmitDelete()">
<template #content>
<el-form class="mt-4 mb-1" ref="deleteForm" label-position="left">
<el-form-item>
<el-checkbox v-model="forceDelete" :label="$t('website.forceDelete')" />
<span class="input-help">
{{ $t('website.forceDeleteHelper') }}
</span>
</el-form-item>
</el-form>
</template>
</OpDialog>
<AddDialog ref="addRef" @search="search" @log="onLoadLog" />
<Log ref="logRef" @close="search" />
<Del ref="delRef" @search="search" />
<Terminal ref="terminalRef" />
<Conn ref="connRef" />
<CodemirrorDialog ref="detailRef" />
<PortJumpDialog ref="dialogPortJumpRef" />
<BindDomain ref="bindDomainRef" />
</div>
</template>
<script lang="ts" setup>
import AppStatus from '@/components/app-status/index.vue';
import AddDialog from '@/views/ai/model/add/index.vue';
import Conn from '@/views/ai/model/conn/index.vue';
import Terminal from '@/views/ai/model/terminal/index.vue';
import Del from '@/views/ai/model/del/index.vue';
import Log from '@/components/log-dialog/index.vue';
import PortJumpDialog from '@/components/port-jump/index.vue';
import CodemirrorDialog from '@/components/codemirror-dialog/index.vue';
import { computed, onMounted, reactive, ref } from 'vue';
import i18n from '@/lang';
import { App } from '@/api/interface/app';
import { GlobalStore } from '@/store';
import {
deleteOllamaModel,
loadOllamaModel,
recreateOllamaModel,
searchOllamaModel,
syncOllamaModel,
} from '@/api/modules/ai';
import { AI } from '@/api/interface/ai';
import { getAppPort } from '@/api/modules/app';
import { dateFormat } from '@/utils/util';
import router from '@/routers';
import { MsgInfo, MsgSuccess } from '@/utils/message';
import BindDomain from '@/views/ai/model/domain/index.vue';
const globalStore = GlobalStore();
const loading = ref(false);
const selects = ref<any>([]);
const maskShow = ref(true);
const addRef = ref();
const logRef = ref();
const detailRef = ref();
const delRef = ref();
const connRef = ref();
const terminalRef = ref();
const openWebUIPort = ref();
const dashboardVisible = ref(false);
const dialogPortJumpRef = ref();
const appStatusRef = ref();
const bindDomainRef = ref();
const data = ref();
const paginationConfig = reactive({
cacheSizeKey: 'model-page-size',
currentPage: 1,
pageSize: Number(localStorage.getItem('page-size')) || 10,
total: 0,
});
const searchName = ref();
const appInstallID = ref(0);
const opRef = ref();
const operateIDs = ref();
const forceDelete = ref();
const modelInfo = reactive({
status: '',
container: '',
isExist: null,
version: '',
port: 11434,
});
const mobile = computed(() => {
return globalStore.isMobile();
});
function selectable(row) {
return row.status !== 'Waiting';
}
const search = async () => {
let params = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
info: searchName.value,
};
loading.value = true;
await searchOllamaModel(params)
.then((res) => {
loading.value = false;
data.value = res.data.items || [];
paginationConfig.total = res.data.total;
})
.catch(() => {
loading.value = false;
});
};
const onCreate = async () => {
addRef.value.acceptParams();
};
const onSync = async () => {
loading.value = true;
await syncOllamaModel()
.then((res) => {
loading.value = false;
if (res.data) {
delRef.value.acceptParams({ list: res.data });
} else {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
}
})
.catch(() => {
loading.value = false;
});
};
const onLoadConn = async () => {
connRef.value.acceptParams({
port: modelInfo.port,
containerName: modelInfo.container,
appinstallID: appInstallID.value,
});
};
const onLoad = async (name: string) => {
const res = await loadOllamaModel(name);
let detailInfo = res.data;
let param = {
header: i18n.global.t('commons.button.view'),
detailInfo: detailInfo,
};
detailRef.value!.acceptParams(param);
};
const goDashboard = async () => {
if (openWebUIPort.value === 0) {
dashboardVisible.value = true;
return;
}
dialogPortJumpRef.value.acceptParams({ port: openWebUIPort.value });
};
const bindDomain = () => {
bindDomainRef.value.acceptParams(appInstallID.value);
};
const goInstall = (name: string) => {
router.push({ name: 'AppAll', query: { install: name } });
};
const loadWebUIPort = async () => {
const res = await getAppPort('ollama-webui', '');
openWebUIPort.value = res.data;
};
const checkExist = (data: App.CheckInstalled) => {
modelInfo.isExist = data.isExist;
modelInfo.status = data.status;
modelInfo.version = data.version;
modelInfo.container = data.containerName;
modelInfo.port = data.httpPort;
if (modelInfo.isExist && modelInfo.status === 'Running') {
search();
}
};
const onSubmitDelete = async () => {
loading.value = true;
await deleteOllamaModel(operateIDs.value, forceDelete.value)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
search();
})
.catch(() => {
loading.value = false;
});
};
const onReCreate = async (name: string) => {
loading.value = true;
await recreateOllamaModel(name)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
})
.catch(() => {
loading.value = false;
});
};
const onDelete = async (row: AI.OllamaModelInfo) => {
let names = [];
let ids = [];
if (row) {
ids = [row.id];
names = [row.name];
} else {
for (const item of selects.value) {
names.push(item.name);
ids.push(item.id);
}
}
operateIDs.value = ids;
opRef.value.acceptParams({
title: i18n.global.t('commons.button.delete'),
names: names,
msg: i18n.global.t('commons.msg.operatorHelper', [
i18n.global.t('cronjob.cronTask'),
i18n.global.t('commons.button.delete'),
]),
api: null,
params: null,
});
};
const onLoadLog = (row: any) => {
if (row.from === 'remote') {
MsgInfo(i18n.global.t('aiTools.model.from_remote'));
return;
}
if (!row.logFileExist) {
MsgInfo(i18n.global.t('aiTools.model.no_logs'));
return;
}
logRef.value.acceptParams({ id: 0, type: 'ollama-model', name: row.name, tail: true });
};
const buttons = [
{
label: i18n.global.t('commons.button.run'),
click: (row: AI.OllamaModelInfo) => {
terminalRef.value.acceptParams({ name: row.name });
},
disabled: (row: any) => {
return row.status !== 'Success';
},
},
{
label: i18n.global.t('commons.button.retry'),
click: (row: AI.OllamaModelInfo) => {
onReCreate(row.name);
},
disabled: (row: any) => {
return row.status === 'Success' || row.status === 'Waiting';
},
},
{
label: i18n.global.t('commons.button.delete'),
click: (row: AI.OllamaModelInfo) => {
onDelete(row);
},
disabled: (row: any) => {
return row.status === 'Waiting';
},
},
];
onMounted(() => {
loadWebUIPort();
});
</script>
<style lang="scss" scoped>
.iconInTable {
margin-left: 5px;
margin-top: 3px;
}
.jumpAdd {
margin-top: 10px;
margin-left: 15px;
margin-bottom: 5px;
font-size: 12px;
}
.tagClass {
float: right;
font-size: 12px;
margin-top: 5px;
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<DrawerPro
v-model="open"
:title="$t('menu.terminal')"
:back="handleClose"
:resource="title"
:size="globalStore.isFullScreen ? 'full' : 'large'"
>
<el-alert type="error" :closable="false">
<template #title>
<span>{{ $t('commons.msg.disConn', ['/bye exit']) }}</span>
</template>
</el-alert>
<Terminal class="mt-2" style="height: calc(100vh - 225px)" ref="terminalRef"></Terminal>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="handleClose">
{{ $t('commons.button.disConn') }}
</el-button>
</span>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { nextTick, ref } from 'vue';
import Terminal from '@/components/terminal/index.vue';
import { closeOllamaModel } from '@/api/modules/ai';
import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
const title = ref();
const open = ref(false);
const itemName = ref();
const terminalRef = ref();
interface DialogProps {
name: string;
}
const acceptParams = async (params: DialogProps): Promise<void> => {
itemName.value = params.name;
open.value = true;
initTerm();
};
const initTerm = () => {
nextTick(() => {
terminalRef.value.acceptParams({
endpoint: '/api/v1/ai/ollama/exec',
args: `name=${itemName.value}`,
error: '',
initCmd: '',
});
});
};
const onClose = async () => {
await closeOllamaModel(itemName.value)
.then(() => {
terminalRef.value?.onClose();
})
.catch(() => {
terminalRef.value?.onClose();
});
};
function handleClose() {
onClose();
open.value = false;
}
defineExpose({
acceptParams,
});
</script>