mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-02-07 17:10:07 +08:00
feat: 已安装应用增加日志页面 (#2403)
This commit is contained in:
parent
4ca25d51d7
commit
ce6069da37
@ -706,3 +706,31 @@ func (b *BaseApi) ComposeUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Container Compose
|
||||||
|
// @Summary Container Compose logs
|
||||||
|
// @Description docker-compose 日志
|
||||||
|
// @Param compose query string false "compose 文件地址"
|
||||||
|
// @Param since query string false "时间筛选"
|
||||||
|
// @Param follow query string false "是否追踪"
|
||||||
|
// @Param tail query string false "显示行号"
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /containers/compose/search/log [post]
|
||||||
|
func (b *BaseApi) ComposeLogs(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()
|
||||||
|
|
||||||
|
compose := c.Query("compose")
|
||||||
|
since := c.Query("since")
|
||||||
|
follow := c.Query("follow") == "true"
|
||||||
|
tail := c.Query("tail")
|
||||||
|
|
||||||
|
if err := containerService.ComposeLogs(wsConn, compose, since, tail, follow); err != nil {
|
||||||
|
_ = wsConn.WriteMessage(1, []byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -66,6 +66,7 @@ type IContainerService interface {
|
|||||||
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
|
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
|
||||||
|
|
||||||
LoadContainerLogs(req dto.OperationWithNameAndType) string
|
LoadContainerLogs(req dto.OperationWithNameAndType) string
|
||||||
|
ComposeLogs(wsConn *websocket.Conn, composePath, since, tail string, follow bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIContainerService() IContainerService {
|
func NewIContainerService() IContainerService {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -261,3 +262,45 @@ func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) ComposeLogs(wsConn *websocket.Conn, composePath, since, tail string, follow bool) error {
|
||||||
|
if cmd.CheckIllegal(composePath, since, tail) {
|
||||||
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
|
}
|
||||||
|
command := fmt.Sprintf("docker-compose -f %s logs", composePath)
|
||||||
|
if tail != "0" {
|
||||||
|
command += " --tail " + tail
|
||||||
|
}
|
||||||
|
if since != "all" {
|
||||||
|
command += " --since " + since
|
||||||
|
}
|
||||||
|
if follow {
|
||||||
|
command += " -f"
|
||||||
|
}
|
||||||
|
command += " 2>&1"
|
||||||
|
cmd := exec.Command("bash", "-c", command)
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, 1024)
|
||||||
|
for {
|
||||||
|
n, err := stdout.Read(buffer)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
global.LOG.Errorf("read bytes from compose log failed, err: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
|
||||||
|
global.LOG.Errorf("send message with compose log to ws failed, err: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -276,7 +276,11 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
env, err := gotenv.Read(path.Join(projectDir, ".env"))
|
envPath := path.Join(projectDir, ".env")
|
||||||
|
if !fileOp.Stat(envPath) {
|
||||||
|
_ = fileOp.CreateFile(envPath)
|
||||||
|
}
|
||||||
|
env, err := gotenv.Read(envPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -316,7 +320,7 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = gotenv.Write(env, path.Join(projectDir, ".env")); err != nil {
|
if err = gotenv.Write(env, envPath); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
envContent = []byte(envStr)
|
envContent = []byte(envStr)
|
||||||
|
@ -45,6 +45,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
|||||||
baRouter.POST("/compose/test", baseApi.TestCompose)
|
baRouter.POST("/compose/test", baseApi.TestCompose)
|
||||||
baRouter.POST("/compose/operate", baseApi.OperatorCompose)
|
baRouter.POST("/compose/operate", baseApi.OperatorCompose)
|
||||||
baRouter.POST("/compose/update", baseApi.ComposeUpdate)
|
baRouter.POST("/compose/update", baseApi.ComposeUpdate)
|
||||||
|
baRouter.GET("/compose/search/log", baseApi.ComposeLogs)
|
||||||
|
|
||||||
baRouter.GET("/template", baseApi.ListComposeTemplate)
|
baRouter.GET("/template", baseApi.ListComposeTemplate)
|
||||||
baRouter.POST("/template/search", baseApi.SearchComposeTemplate)
|
baRouter.POST("/template/search", baseApi.SearchComposeTemplate)
|
||||||
|
@ -1232,6 +1232,47 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/containers/compose/search/log": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "docker-compose 日志",
|
||||||
|
"tags": [
|
||||||
|
"Container Compose"
|
||||||
|
],
|
||||||
|
"summary": "Container Compose logs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "compose 文件地址",
|
||||||
|
"name": "compose",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "时间筛选",
|
||||||
|
"name": "since",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "是否追踪",
|
||||||
|
"name": "follow",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "显示行号",
|
||||||
|
"name": "tail",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/containers/compose/test": {
|
"/containers/compose/test": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -1225,6 +1225,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/containers/compose/search/log": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "docker-compose 日志",
|
||||||
|
"tags": [
|
||||||
|
"Container Compose"
|
||||||
|
],
|
||||||
|
"summary": "Container Compose logs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "compose 文件地址",
|
||||||
|
"name": "compose",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "时间筛选",
|
||||||
|
"name": "since",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "是否追踪",
|
||||||
|
"name": "follow",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "显示行号",
|
||||||
|
"name": "tail",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/containers/compose/test": {
|
"/containers/compose/test": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -4893,6 +4893,32 @@ paths:
|
|||||||
summary: Page composes
|
summary: Page composes
|
||||||
tags:
|
tags:
|
||||||
- Container Compose
|
- Container Compose
|
||||||
|
/containers/compose/search/log:
|
||||||
|
post:
|
||||||
|
description: docker-compose 日志
|
||||||
|
parameters:
|
||||||
|
- description: compose 文件地址
|
||||||
|
in: query
|
||||||
|
name: compose
|
||||||
|
type: string
|
||||||
|
- description: 时间筛选
|
||||||
|
in: query
|
||||||
|
name: since
|
||||||
|
type: string
|
||||||
|
- description: 是否追踪
|
||||||
|
in: query
|
||||||
|
name: follow
|
||||||
|
type: string
|
||||||
|
- description: 显示行号
|
||||||
|
in: query
|
||||||
|
name: tail
|
||||||
|
type: string
|
||||||
|
responses: {}
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Container Compose logs
|
||||||
|
tags:
|
||||||
|
- Container Compose
|
||||||
/containers/compose/test:
|
/containers/compose/test:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
@ -5,7 +5,7 @@ VITE_GLOB_APP_TITLE = '1Panel'
|
|||||||
VITE_PORT = 4004
|
VITE_PORT = 4004
|
||||||
|
|
||||||
# open 运行 npm run dev 时自动打开浏览器
|
# open 运行 npm run dev 时自动打开浏览器
|
||||||
VITE_OPEN = true
|
VITE_OPEN = false
|
||||||
|
|
||||||
# 是否生成包预览文件
|
# 是否生成包预览文件
|
||||||
VITE_REPORT = false
|
VITE_REPORT = false
|
||||||
|
196
frontend/src/components/compose-log/index.vue
Normal file
196
frontend/src/components/compose-log/index.vue
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer v-model="open" :size="globalStore.isFullScreen ? '100%' : '50%'">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('commons.button.log')" :resource="resource" :back="handleClose">
|
||||||
|
<template #extra v-if="!mobile">
|
||||||
|
<el-tooltip :content="loadTooltip()" placement="top">
|
||||||
|
<el-button @click="toggleFullscreen" class="fullScreen" icon="FullScreen" plain></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</DrawerHeader>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<el-select @change="searchLogs" v-model="logSearch.mode" class="selectWidth">
|
||||||
|
<template #prefix>{{ $t('container.fetch') }}</template>
|
||||||
|
<el-option v-for="item in timeOptions" :key="item.label" :value="item.value" :label="item.label" />
|
||||||
|
</el-select>
|
||||||
|
<el-select @change="searchLogs" class="ml-5 selectWidth" v-model.number="logSearch.tail">
|
||||||
|
<template #prefix>{{ $t('container.lines') }}</template>
|
||||||
|
<el-option :value="0" :label="$t('commons.table.all')" />
|
||||||
|
<el-option :value="100" :label="100" />
|
||||||
|
<el-option :value="200" :label="200" />
|
||||||
|
<el-option :value="500" :label="500" />
|
||||||
|
<el-option :value="1000" :label="1000" />
|
||||||
|
</el-select>
|
||||||
|
<div class="ml-5">
|
||||||
|
<el-checkbox border @change="searchLogs" v-model="logSearch.isWatch">
|
||||||
|
{{ $t('commons.button.watch') }}
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
|
<el-button class="ml-5" @click="onDownload" icon="Download">
|
||||||
|
{{ $t('file.download') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<codemirror
|
||||||
|
:autofocus="true"
|
||||||
|
:placeholder="$t('commons.msg.noneData')"
|
||||||
|
:indent-with-tab="true"
|
||||||
|
:tabSize="4"
|
||||||
|
style="margin-top: 10px; height: calc(100vh - 375px)"
|
||||||
|
:lineWrapping="true"
|
||||||
|
:matchBrackets="true"
|
||||||
|
theme="cobalt"
|
||||||
|
:styleActiveLine="true"
|
||||||
|
:extensions="extensions"
|
||||||
|
@ready="handleReady"
|
||||||
|
v-model="logInfo"
|
||||||
|
:disabled="true"
|
||||||
|
/>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { dateFormatForName, downloadWithContent } from '@/utils/util';
|
||||||
|
import { computed, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue';
|
||||||
|
import { Codemirror } from 'vue-codemirror';
|
||||||
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
|
import { MsgError } from '@/utils/message';
|
||||||
|
import { GlobalStore } from '@/store';
|
||||||
|
import screenfull from 'screenfull';
|
||||||
|
|
||||||
|
const extensions = [javascript(), oneDark];
|
||||||
|
|
||||||
|
const logInfo = ref();
|
||||||
|
const view = shallowRef();
|
||||||
|
const handleReady = (payload) => {
|
||||||
|
view.value = payload.view;
|
||||||
|
};
|
||||||
|
const terminalSocket = ref<WebSocket>();
|
||||||
|
const open = ref(false);
|
||||||
|
const resource = ref('');
|
||||||
|
const globalStore = GlobalStore();
|
||||||
|
const logVisiable = ref(false);
|
||||||
|
|
||||||
|
const mobile = computed(() => {
|
||||||
|
return globalStore.isMobile();
|
||||||
|
});
|
||||||
|
|
||||||
|
const logSearch = reactive({
|
||||||
|
isWatch: true,
|
||||||
|
compose: '',
|
||||||
|
mode: 'all',
|
||||||
|
tail: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
open.value = false;
|
||||||
|
terminalSocket.value?.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
function toggleFullscreen() {
|
||||||
|
if (screenfull.isEnabled) {
|
||||||
|
screenfull.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const loadTooltip = () => {
|
||||||
|
return i18n.global.t('commons.button.' + (screenfull.isFullscreen ? 'quitFullscreen' : 'fullscreen'));
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(logVisiable, (val) => {
|
||||||
|
if (screenfull.isEnabled && !val && !mobile.value) screenfull.exit();
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeOptions = ref([
|
||||||
|
{ label: i18n.global.t('container.all'), value: 'all' },
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.lastDay'),
|
||||||
|
value: new Date(new Date().getTime() - 3600 * 1000 * 24 * 1).getTime() / 1000 + '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.last4Hour'),
|
||||||
|
value: new Date(new Date().getTime() - 3600 * 1000 * 4).getTime() / 1000 + '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.lastHour'),
|
||||||
|
value: new Date(new Date().getTime() - 3600 * 1000).getTime() / 1000 + '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.last10Min'),
|
||||||
|
value: new Date(new Date().getTime() - 600 * 1000).getTime() / 1000 + '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const searchLogs = async () => {
|
||||||
|
if (Number(logSearch.tail) < 0) {
|
||||||
|
MsgError(i18n.global.t('container.linesHelper'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
terminalSocket.value?.close();
|
||||||
|
logInfo.value = '';
|
||||||
|
const href = window.location.href;
|
||||||
|
const protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
|
||||||
|
const host = href.split('//')[1].split('/')[0];
|
||||||
|
terminalSocket.value = new WebSocket(
|
||||||
|
`${protocol}://${host}/api/v1/containers/compose/search/log?compose=${logSearch.compose}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}`,
|
||||||
|
);
|
||||||
|
terminalSocket.value.onmessage = (event) => {
|
||||||
|
logInfo.value += event.data;
|
||||||
|
const state = view.value.state;
|
||||||
|
view.value.dispatch({
|
||||||
|
selection: { anchor: state.doc.length, head: state.doc.length },
|
||||||
|
scrollIntoView: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDownload = async () => {
|
||||||
|
let msg =
|
||||||
|
logSearch.tail === 0
|
||||||
|
? i18n.global.t('app.downloadLogHelper1', [resource.value])
|
||||||
|
: i18n.global.t('app.downloadLogHelper2', [resource.value, logSearch.tail]);
|
||||||
|
ElMessageBox.confirm(msg, i18n.global.t('file.download'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
}).then(async () => {
|
||||||
|
downloadWithContent(logInfo.value, resource.value + '-' + dateFormatForName(new Date()) + '.log');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
compose: string;
|
||||||
|
resource: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const acceptParams = (props: DialogProps): void => {
|
||||||
|
logSearch.compose = props.compose;
|
||||||
|
logSearch.tail = 1000;
|
||||||
|
logSearch.mode = timeOptions.value[3].value;
|
||||||
|
logSearch.isWatch = true;
|
||||||
|
resource.value = props.resource;
|
||||||
|
searchLogs();
|
||||||
|
open.value = true;
|
||||||
|
if (!mobile.value) {
|
||||||
|
screenfull.on('change', () => {
|
||||||
|
globalStore.isFullScreen = screenfull.isFullscreen;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
terminalSocket.value?.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.selectWidth {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1295,6 +1295,9 @@ const message = {
|
|||||||
delete: 'Delete',
|
delete: 'Delete',
|
||||||
openrestyDeleteHelper:
|
openrestyDeleteHelper:
|
||||||
'Forcibly deleting OpenResty will delete all websites, please confirm the risk before operation',
|
'Forcibly deleting OpenResty will delete all websites, please confirm the risk before operation',
|
||||||
|
downloadLogHelper1: 'All logs of {0} application are about to be downloaded. Do you want to continue? ',
|
||||||
|
downloadLogHelper2:
|
||||||
|
'The latest {1} logs of {0} application are about to be downloaded. Do you want to continue? ',
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
website: 'Website',
|
website: 'Website',
|
||||||
|
@ -1230,6 +1230,8 @@ const message = {
|
|||||||
backupAppHelper: '升級失敗可以使用應用備份回滾',
|
backupAppHelper: '升級失敗可以使用應用備份回滾',
|
||||||
delete: '刪除',
|
delete: '刪除',
|
||||||
openrestyDeleteHelper: '強制刪除 OpenResty 會刪除所有的網站,請確認風險後操作',
|
openrestyDeleteHelper: '強制刪除 OpenResty 會刪除所有的網站,請確認風險後操作',
|
||||||
|
downloadLogHelper1: '即將下載 {0} 套用所有日誌,是否繼續? ',
|
||||||
|
downloadLogHelper2: '即將下載 {0} 應用最近 {1} 條日誌,是否繼續? ',
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
website: '網站',
|
website: '網站',
|
||||||
|
@ -1230,6 +1230,8 @@ const message = {
|
|||||||
backupAppHelper: '升级失败可以使用应用备份回滚',
|
backupAppHelper: '升级失败可以使用应用备份回滚',
|
||||||
delete: '删除',
|
delete: '删除',
|
||||||
openrestyDeleteHelper: '强制删除 OpenResty 会删除所有的网站,请确认风险之后操作',
|
openrestyDeleteHelper: '强制删除 OpenResty 会删除所有的网站,请确认风险之后操作',
|
||||||
|
downloadLogHelper1: '即将下载 {0} 应用所有日志,是否继续?',
|
||||||
|
downloadLogHelper2: '即将下载 {0} 应用最近 {1} 条日志,是否继续?',
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
website: '网站',
|
website: '网站',
|
||||||
|
@ -261,6 +261,7 @@
|
|||||||
<AppUpgrade ref="upgradeRef" @close="search" />
|
<AppUpgrade ref="upgradeRef" @close="search" />
|
||||||
<PortJumpDialog ref="dialogPortJumpRef" />
|
<PortJumpDialog ref="dialogPortJumpRef" />
|
||||||
<AppIgnore ref="ignoreRef" @close="search" />
|
<AppIgnore ref="ignoreRef" @close="search" />
|
||||||
|
<ComposeLogs ref="composeLogRef" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -282,6 +283,7 @@ import AppDelete from './delete/index.vue';
|
|||||||
import AppParams from './detail/index.vue';
|
import AppParams from './detail/index.vue';
|
||||||
import AppUpgrade from './upgrade/index.vue';
|
import AppUpgrade from './upgrade/index.vue';
|
||||||
import AppIgnore from './ignore/index.vue';
|
import AppIgnore from './ignore/index.vue';
|
||||||
|
import ComposeLogs from '@/components/compose-log/index.vue';
|
||||||
import { App } from '@/api/interface/app';
|
import { App } from '@/api/interface/app';
|
||||||
import Status from '@/components/status/index.vue';
|
import Status from '@/components/status/index.vue';
|
||||||
import { getAge } from '@/utils/util';
|
import { getAge } from '@/utils/util';
|
||||||
@ -314,6 +316,7 @@ const appParamRef = ref();
|
|||||||
const upgradeRef = ref();
|
const upgradeRef = ref();
|
||||||
const ignoreRef = ref();
|
const ignoreRef = ref();
|
||||||
const dialogPortJumpRef = ref();
|
const dialogPortJumpRef = ref();
|
||||||
|
const composeLogRef = ref();
|
||||||
const tags = ref<App.Tag[]>([]);
|
const tags = ref<App.Tag[]>([]);
|
||||||
const activeTag = ref('all');
|
const activeTag = ref('all');
|
||||||
const searchReq = reactive({
|
const searchReq = reactive({
|
||||||
@ -496,6 +499,15 @@ const buttons = [
|
|||||||
return row.status === 'DownloadErr' || row.status === 'Upgrading' || row.status === 'Rebuilding';
|
return row.status === 'DownloadErr' || row.status === 'Upgrading' || row.status === 'Rebuilding';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.log'),
|
||||||
|
click: (row: any) => {
|
||||||
|
openLog(row);
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return row.status === 'DownloadErr';
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const openBackups = (key: string, name: string) => {
|
const openBackups = (key: string, name: string) => {
|
||||||
@ -528,6 +540,10 @@ const quickJump = () => {
|
|||||||
router.push({ name: 'ContainerSetting' });
|
router.push({ name: 'ContainerSetting' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openLog = (row: any) => {
|
||||||
|
composeLogRef.value.acceptParams({ compose: row.path + '/docker-compose.yml', resource: row.name });
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const path = router.currentRoute.value.path;
|
const path = router.currentRoute.value.path;
|
||||||
if (path == '/apps/upgrade') {
|
if (path == '/apps/upgrade') {
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
<OperateNode ref="operateRef" @close="search" />
|
<OperateNode ref="operateRef" @close="search" />
|
||||||
<Delete ref="deleteRef" @close="search()" />
|
<Delete ref="deleteRef" @close="search" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user