Compare commits

..
26 changed files with 127 additions and 95 deletions
+23 -36
View File
@@ -765,42 +765,6 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
install.Env = string(paramByte)
content = setVllmImageInEnvContent(content, image)
}
if req.PullImage {
composeContent := []byte(detail.DockerCompose)
if install.App.Key == vllmAppKeyForUpgrade {
composeContent = []byte(install.DockerCompose)
}
if req.DockerCompose != "" {
composeContent = []byte(req.DockerCompose)
}
images, err := docker.GetImagesFromDockerCompose(content, composeContent)
if err != nil {
return err
}
dockerCLi, err := docker.NewClient()
if err != nil {
return err
}
defer dockerCLi.Close()
for _, image := range images {
t.Log(i18n.GetWithName("PullImageStart", image))
if err = dockerCLi.PullImageWithProcess(t, image); err != nil {
err = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return err
}
exist, err := dockerCLi.ImageExists(image)
if err != nil {
err = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return err
}
if !exist {
err = buserr.WithNameAndErr("ErrDockerPullImage", "", fmt.Errorf("image %s not found", image))
return err
}
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
}
}
_ = copyAppDetailMissing(fileOp, detailDir, install.GetPath())
if install.App.Key == constant.AppOpenresty {
installBuildDir := path.Join(install.GetPath(), "build")
@@ -856,6 +820,29 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
install.Version = detail.Version
install.AppDetailId = req.DetailID
if req.PullImage {
images, err := docker.GetImagesFromDockerCompose(content, []byte(install.DockerCompose))
if err != nil {
return err
}
dockerCLi, err := docker.NewClient()
if err != nil {
return err
}
defer dockerCLi.Close()
for _, image := range images {
t.Log(i18n.GetWithName("PullImageStart", image))
if err = dockerCLi.PullImageWithProcess(t, image); err != nil {
return buserr.WithNameAndErr("ErrDockerPullImage", "", err)
}
exist, err := dockerCLi.ImageExists(image)
if err != nil || !exist {
return buserr.WithNameAndErr("ErrDockerPullImage", "", fmt.Errorf("image %s does not exist after pull: %v", image, err))
}
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
}
}
if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
upErr = errors.New(out)
+1 -1
View File
@@ -502,7 +502,7 @@ func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
return err
}
defer client.Close()
taskItem, err := task.NewTaskWithOps(task.TaskScopeImage, task.TaskDelete, task.TaskScopeContainer, req.TaskID, 1)
taskItem, err := task.NewTaskWithOps(strings.Join(req.Names, ","), task.TaskDelete, task.TaskScopeImage, req.TaskID, 1)
if err != nil {
global.LOG.Errorf("new task for create container failed, err: %v", err)
return err
@@ -434,5 +434,12 @@
"file": "lz4.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"],
"installed": false
},
{
"name": "msgpack",
"check": "msgpack",
"file": "msgpack.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"],
"installed": false
}
]
+12 -5
View File
@@ -199,10 +199,14 @@ func (c Client) PullImageWithProcessAndOptions(task *task.Task, imageName string
return err
}
defer out.Close()
return handlePullImageProcess(out, task)
}
func handlePullImageProcess(out io.Reader, task *task.Task) error {
decoder := json.NewDecoder(out)
for {
var progress map[string]interface{}
if err = decoder.Decode(&progress); err != nil {
if err := decoder.Decode(&progress); err != nil {
if err == io.EOF {
break
}
@@ -214,11 +218,14 @@ func (c Client) PullImageWithProcessAndOptions(task *task.Task, imageName string
if msg, ok := progress["error"]; ok {
return fmt.Errorf("image pull failed, err: %v", msg)
}
status, _ := progress["status"].(string)
if status == "Downloading" || status == "Extracting" {
logProcess(progress, task)
if task == nil {
continue
}
if status == "Pull complete" || status == "Download complete" {
status, _ := progress["status"].(string)
switch status {
case "Downloading", "Extracting":
logProcess(progress, task)
case "Pull complete", "Download complete", "Already exists", "Verifying Checksum":
id, _ := progress["id"].(string)
progressStr := fmt.Sprintf("%s %s", status, id)
_ = setLog(id, progressStr, task)
+18 -17
View File
@@ -57,23 +57,24 @@ var WebUrlMap = map[string]struct{}{
"/apps/upgrade": {},
"/apps/setting": {},
"/ai": {},
"/ai/ai-proxy": {},
"/ai/ai-proxy/model-pool": {},
"/ai/ai-proxy/api-keys": {},
"/ai/ai-proxy/groups": {},
"/ai/ai-proxy/usage": {},
"/ai/ai-proxy/content": {},
"/ai/skills-hub": {},
"/ai/skills-hub/targets": {},
"/ai/benchmark": {},
"/ai/model/account": {},
"/ai/model/local": {},
"/ai/gpu": {},
"/ai/gpu/current": {},
"/ai/gpu/history": {},
"/ai/mcp": {},
"/ai/agents/agent": {},
"/ai": {},
"/ai/ai-proxy": {},
"/ai/ai-proxy/model-pool": {},
"/ai/ai-proxy/api-keys": {},
"/ai/ai-proxy/groups": {},
"/ai/ai-proxy/model-groups": {},
"/ai/ai-proxy/usage": {},
"/ai/ai-proxy/content": {},
"/ai/skills-hub": {},
"/ai/skills-hub/targets": {},
"/ai/benchmark": {},
"/ai/model/account": {},
"/ai/model/local": {},
"/ai/gpu": {},
"/ai/gpu/current": {},
"/ai/gpu/history": {},
"/ai/mcp": {},
"/ai/agents/agent": {},
"/containers": {},
"/containers/container/operate": {},
-18
View File
@@ -1,18 +0,0 @@
package run
import (
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
)
func Init() {
scriptSync, _ := repo.NewISettingRepo().GetValueByKey("ScriptSync")
if !global.CONF.Base.IsOffline && scriptSync == constant.StatusEnable {
if err := service.NewIScriptService().Sync(dto.OperateByTaskID{}); err != nil {
global.LOG.Errorf("sync scripts from remote failed, err: %v", err)
}
}
}
+2
View File
@@ -27,6 +27,8 @@ func PasswordExpired() gin.HandlerFunc {
c.Request.URL.Path == "/api/v2/core/settings/search" ||
c.Request.URL.Path == "/api/v2/core/settings/search/base" ||
c.Request.URL.Path == "/api/v2/core/xpack/settings/search" ||
c.Request.URL.Path == "/api/v2/core/xapp/verifyQRCode" ||
c.Request.URL.Path == "/api/v2/core/enterprise/users/info" ||
c.Request.URL.Path == "/api/v2/core/enterprise/licenses/info" ||
c.Request.URL.Path == "/api/v2/core/enterprise/licenses/status" ||
c.Request.URL.Path == "/api/v2/core/enterprise/licenses/upload" {
-2
View File
@@ -18,7 +18,6 @@ import (
"github.com/1Panel-dev/1Panel/core/init/log"
"github.com/1Panel-dev/1Panel/core/init/migration"
"github.com/1Panel-dev/1Panel/core/init/proxy"
"github.com/1Panel-dev/1Panel/core/init/run"
"github.com/gin-gonic/gin"
"github.com/soheilhy/cmux"
@@ -50,7 +49,6 @@ func Start() {
hook.Init()
InitOthers()
run.Init()
proxy.Init()
rootRouter := router.Routers()
+2
View File
@@ -2888,6 +2888,8 @@ const message = {
'Please ensure there is sufficient disk space on the server (Snapshot file size: {0}, Available space: {1})',
recoverHelper3:
'Please ensure the server architecture matches the architecture of the server where the snapshot was created (Current server architecture: {0})',
recoverHelper4:
'This operation only rolls back the primary node. If any upgraded secondary nodes exist, roll them back first before continuing.',
rollback: 'Rollback',
rollbackHelper:
'Rolling back this recovery will replace all files from this recovery, and may require restarting Docker and 1Panel services. Continue?',
+2
View File
@@ -2962,6 +2962,8 @@ const message = {
'Asegúrate de que haya suficiente espacio en disco en el servidor (Tamaño del archivo de instantánea: {0}, Espacio disponible: {1})',
recoverHelper3:
'Asegúrate de que la arquitectura del servidor coincida con la del servidor donde se creó la instantánea (Arquitectura actual: {0})',
recoverHelper4:
'Esta operación solo revierte el nodo principal. Si existen nodos secundarios actualizados, reviértelos primero antes de continuar.',
rollback: 'Reversión',
rollbackHelper:
'Revertir esta recuperación reemplazará todos los archivos de esta recuperación y puede requerir reiniciar Docker y los servicios de 1Panel. ¿Deseas continuar?',
+2
View File
@@ -2894,6 +2894,8 @@ const message = {
'サーバーに十分なディスクスペースがあることを確認してください(スナップショットファイルサイズ:{0}、利用可能なスペース:{1})',
recoverHelper3:
'サーバーアーキテクチャが、スナップショットが作成されたサーバーのアーキテクチャと一致していることを確認してください(現在のサーバーアーキテクチャ:{0})',
recoverHelper4:
'この操作はプライマリノードのみをロールバックします。アップグレード済みの子ノードがある場合は、先に子ノードをロールバックしてから続行してください。',
rollback: 'ロールバック',
rollbackHelper:
'この回復をロールバックすると、この回復からすべてのファイルを置き換え、Dockerサービスと1パネルサービスを再起動する必要がある場合があります。続けたいですか?',
+2
View File
@@ -2834,6 +2834,8 @@ const message = {
recoverHelper2: '서버에 충분한 디스크 공간이 있는지 확인하세요. (스냅샷 파일 크기: {0}, 사용 가능한 공간: {1})',
recoverHelper3:
'서버 아키텍처가 스냅샷이 생성된 서버의 아키텍처와 일치하는지 확인하세요. (현재 서버 아키텍처: {0})',
recoverHelper4:
'이 작업은 기본 노드만 롤백합니다. 업그레이드된 하위 노드가 있다면 먼저 하위 노드를 롤백한 후 계속 진행하세요.',
rollback: '롤백',
rollbackHelper:
'이 복구를 롤백하면 해당 복구의 모든 파일이 대체되며 Docker 및 1Panel 서비스를 재시작해야 할 수 있습니다. 계속하시겠습니까?',
+2
View File
@@ -2946,6 +2946,8 @@ const message = {
'Pastikan terdapat ruang cakera yang mencukupi pada pelayan (Saiz fail snapshot: {0}, Ruang tersedia: {1})',
recoverHelper3:
'Pastikan seni bina pelayan sepadan dengan seni bina pelayan di mana snapshot dicipta (Seni bina pelayan semasa: {0})',
recoverHelper4:
'Operasi ini hanya menggulung balik nod utama. Jika terdapat nod anak yang telah dinaik taraf, sila gulungkan balik nod anak terlebih dahulu sebelum meneruskan.',
rollback: 'Pulangkan semula',
rollbackHelper:
'Pulangkan semula pemulihan ini akan menggantikan semua fail dari pemulihan ini, dan mungkin memerlukan mulakan semula perkhidmatan Docker dan 1Panel. Adakah anda mahu meneruskan?',
+2
View File
@@ -3059,6 +3059,8 @@ const message = {
'Por favor, assegure-se de que há espaço suficiente em disco no servidor (Tamanho do arquivo de snapshot: {0}, Espaço disponível: {1})',
recoverHelper3:
'Por favor, assegure-se de que a arquitetura do servidor corresponda à arquitetura do servidor onde o snapshot foi criado (Arquitetura do servidor atual: {0})',
recoverHelper4:
'Esta operação faz rollback apenas do nó principal. Se houver nós secundários atualizados, faça o rollback deles primeiro antes de continuar.',
rollback: 'Rollback',
rollbackHelper:
'Reverter essa recuperação substituirá todos os arquivos dessa recuperação e pode exigir reiniciar os serviços Docker e 1Panel. Você deseja continuar?',
+2
View File
@@ -2923,6 +2923,8 @@ const message = {
'Пожалуйста, убедитесь, что на сервере достаточно места на диске (Размер файла снапшота: {0}, Доступное место: {1})',
recoverHelper3:
'Пожалуйста, убедитесь, что архитектура сервера соответствует архитектуре сервера, где был создан снапшот (Текущая архитектура сервера: {0})',
recoverHelper4:
'Это действие откатывает только основной узел. Если есть обновлённые дочерние узлы, сначала откатите их, прежде чем продолжать.',
rollback: 'Откатить',
rollbackHelper:
'Откат этого восстановления заменит все файлы из этого восстановления и может потребовать перезапуска служб Docker и 1Panel. Хотите продолжить?',
+2
View File
@@ -2949,6 +2949,8 @@ const message = {
'Sunucuda yeterli disk alanının olduğundan emin olun (Anlık görüntü dosyası boyutu: {0}, Mevcut alan: {1})',
recoverHelper3:
'Sunucu mimarisinin, anlık görüntünün oluşturulduğu sunucu mimarisiyle eşleştiğinden emin olun (Geçerli sunucu mimarisi: {0})',
recoverHelper4:
'Bu işlem yalnızca ana düğümü geri alır. Yükseltilmiş alt düğümler varsa, devam etmeden önce önce bunları geri alın.',
rollback: 'Geri al',
rollbackHelper:
'Bu kurtarmayı geri almak, bu kurtarmadan gelen tüm dosyaları değiştirecek ve Docker ile 1Panel servislerinin yeniden başlatılmasını gerektirebilir. Devam etmek istiyor musunuz?',
+1
View File
@@ -2611,6 +2611,7 @@ const message = {
recoverHelper1: '復原需要重新啟動 Docker 以及 1Panel 服務',
recoverHelper2: '請確保伺服器磁碟空間充足 ( 快照檔案大小: {0}, 可用空間: {1} )',
recoverHelper3: '請確保伺服器架構與建立快照伺服器架構資訊保持一致 (目前伺服器架構: {0} )',
recoverHelper4: '此操作僅會回滾主節點,如存在已升級的子節點,請先回滾子節點後再執行此操作',
rollback: '回滾',
rollbackHelper:
'即將回滾本次復原,回滾將取代所有本次復原的檔案,過程中可能需要重新啟動 Docker 以及 1Panel 服務,是否繼續?',
+1
View File
@@ -2585,6 +2585,7 @@ const message = {
recoverHelper1: '恢复需要重启 Docker 以及 1Panel 服务',
recoverHelper2: '请确保服务器磁盘空间充足 ( 快照文件大小: {0}, 可用空间: {1} )',
recoverHelper3: '请确保服务器架构与创建快照服务器架构信息保持一致 (当前服务器架构: {0} )',
recoverHelper4: '该操作仅回滚主节点,如存在已升级的子节点,请先回滚子节点后再执行此操作',
rollback: '回滚',
rollbackHelper:
'即将回滚本次恢复,回滚将替换所有本次恢复的文件,过程中可能需要重启 Docker 以及 1Panel 服务,是否继续?',
+3
View File
@@ -105,6 +105,9 @@ router.beforeEach(async (to, from, next) => {
if (to.path === '/apps/all' && to.query.install != undefined) {
return next();
}
if (to.name === 'Expired') {
return next();
}
const activeMenuKey = 'cachedRoute' + (to.meta.activeMenu || '');
if (to.query.uncached != undefined) {
const query = { ...to.query };
+2 -2
View File
@@ -455,9 +455,9 @@ const openCreateFromQuery = async () => {
await router.replace({ path: route.path, query: nextQuery });
};
const openTaskLog = (taskID: string) => {
const openTaskLog = (taskID: string, operateNode?: string) => {
if (taskLogRef.value?.openWithTaskID) {
taskLogRef.value.openWithTaskID(taskID);
taskLogRef.value.openWithTaskID(taskID, undefined, operateNode);
}
};
+32 -8
View File
@@ -23,12 +23,13 @@
</template>
<script setup lang="ts">
import { computed, defineAsyncComponent } from 'vue';
import { computed, defineAsyncComponent, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import OllamaView from '@/views/ai/model/ollama/index.vue';
import TensorRTView from '@/views/ai/model/tensorrt/index.vue';
import { loadOptionalComponent } from '@/extensions/optional';
import i18n from '@/lang';
import { useGlobalStore } from '@/composables/useGlobalStore';
const VllmView = defineAsyncComponent(() => loadOptionalComponent('/src/xpack/views/vllm/index.vue'));
const ModelDownloaderView = defineAsyncComponent(() =>
@@ -39,6 +40,7 @@ type LocalTab = 'ollama' | 'vllm' | 'tensorrt' | 'downloader';
const route = useRoute();
const router = useRouter();
const { isFxplay } = useGlobalStore();
const tabLabels: Record<LocalTab, string> = {
ollama: 'Ollama',
@@ -47,16 +49,21 @@ const tabLabels: Record<LocalTab, string> = {
downloader: i18n.global.t('aiTools.model.downloader'),
};
const buttons: Array<{ label: string; value: LocalTab }> = [
{ label: tabLabels.ollama, value: 'ollama' },
{ label: tabLabels.vllm, value: 'vllm' },
{ label: tabLabels.tensorrt, value: 'tensorrt' },
{ label: tabLabels.downloader, value: 'downloader' },
];
const buttons = computed<Array<{ label: string; value: LocalTab }>>(() => {
const items: Array<{ label: string; value: LocalTab }> = [
{ label: tabLabels.ollama, value: 'ollama' },
{ label: tabLabels.vllm, value: 'vllm' },
];
if (isFxplay.value) {
items.push({ label: tabLabels.tensorrt, value: 'tensorrt' });
}
items.push({ label: tabLabels.downloader, value: 'downloader' });
return items;
});
const currentTab = computed<LocalTab>(() => {
const tab = route.query.tab;
if (tab === 'vllm' || tab === 'tensorrt' || tab === 'downloader') {
if (tab === 'vllm' || tab === 'downloader' || (tab === 'tensorrt' && isFxplay.value)) {
return tab;
}
return 'ollama';
@@ -84,6 +91,23 @@ const handleChange = async (target: LocalTab) => {
},
});
};
watch(
[() => route.query.tab, isFxplay],
async ([tab, fxplay]) => {
if (tab !== 'tensorrt' || fxplay) {
return;
}
await router.replace({
path: '/ai/model/local',
query: {
...route.query,
tab: 'ollama',
},
});
},
{ immediate: true },
);
</script>
<style lang="scss" scoped>
@@ -1007,7 +1007,7 @@ const loadHistoryVersionCount = async (path: string) => {
};
const saveContent = async () => {
if (!hasManagePermissionAccess()) {
if (!hasManagePermissionAccess(undefined, { nodeAdmin: true })) {
MsgError(i18n.global.t('commons.res.forbidden'));
return;
}
@@ -1367,11 +1367,11 @@ const handleCreate = (command: string) => {
};
const delFile = async (row: File.File | null) => {
deleteRef.value.acceptParams([row]);
deleteRef.value.acceptParams([row], currentNode.value);
};
const batchDelFiles = () => {
deleteRef.value.acceptParams(selects.value);
deleteRef.value.acceptParams(selects.value, currentNode.value);
};
const formatFileSize = (size: number) => {
@@ -213,11 +213,11 @@ const openView = async (
title: string,
rowData: Partial<Alert.AlertInfo> = {
type: isMaster.value && !isEE.value ? 'panelPwdEndTime' : 'sshLogin',
cycle: 15,
cycle: isMaster.value && !isEE.value ? 15 : 30,
count: isMaster.value && !isEE.value ? 0 : 3,
sendCount: 3,
method: '',
project: '',
project: isMaster.value && !isEE.value ? '' : 'all',
status: 'Enable',
title: '',
},
@@ -570,7 +570,6 @@ const acceptParams = (params: DialogProps): void => {
}
initOptions(dialogData.value.rowData.type, dialogData.value.rowData.subType);
visible.value = true;
console.log(dialogData.value.rowData);
};
const rules = reactive({
@@ -7,6 +7,10 @@
<el-button style="margin-top: -4px" type="warning" link icon="WarningFilled" />
{{ $t('setting.recoverHelper1') }}
</div>
<div>
<el-button style="margin-top: -4px" type="warning" link icon="WarningFilled" />
{{ $t('setting.recoverHelper4') }}
</div>
<div>
<el-button
style="margin-top: -4px"