mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-06 05:24:33 +08:00
feat(task): Add Execution Validation for Tasks (#7559)
This commit is contained in:
parent
42c97f6eeb
commit
e11a0d5766
21 changed files with 73 additions and 204 deletions
|
@ -135,6 +135,7 @@ type TaskReq struct {
|
|||
TaskID string `json:"taskID"`
|
||||
TaskType string `json:"taskType"`
|
||||
TaskOperate string `json:"taskOperate"`
|
||||
ResourceID uint `json:"resourceID"`
|
||||
}
|
||||
|
||||
type FileExistReq struct {
|
||||
|
|
5
agent/app/repo/entry.go
Normal file
5
agent/app/repo/entry.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package repo
|
||||
|
||||
var (
|
||||
commonRepo = NewCommonRepo()
|
||||
)
|
|
@ -18,6 +18,7 @@ type ITaskRepo interface {
|
|||
GetFirst(opts ...DBOption) (model.Task, error)
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.Task, error)
|
||||
Update(ctx context.Context, task *model.Task) error
|
||||
UpdateRunningTaskToFailed() error
|
||||
|
||||
WithByID(id string) DBOption
|
||||
WithResourceID(id uint) DBOption
|
||||
|
@ -90,3 +91,7 @@ func (t TaskRepo) Page(page, size int, opts ...DBOption) (int64, []model.Task, e
|
|||
func (t TaskRepo) Update(ctx context.Context, task *model.Task) error {
|
||||
return getTaskTx(ctx).Save(&task).Error
|
||||
}
|
||||
|
||||
func (t TaskRepo) UpdateRunningTaskToFailed() error {
|
||||
return getTaskDb(commonRepo.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Updates(map[string]interface{}{"status": constant.StatusFailed, "error_msg": "1Panel restart causes failure"}).Error
|
||||
}
|
||||
|
|
|
@ -170,6 +170,10 @@ func (n NginxService) Build(req request.NginxBuildReq) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskName := task.GetTaskName(nginxInstall.Name, task.TaskBuild, task.TaskScopeApp)
|
||||
if err = task.CheckTaskIsExecuting(taskName); err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
buildPath := path.Join(nginxInstall.GetPath(), "build")
|
||||
if !fileOp.Stat(buildPath) {
|
||||
|
|
|
@ -9,6 +9,7 @@ type TaskLogService struct{}
|
|||
|
||||
type ITaskLogService interface {
|
||||
Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error)
|
||||
SyncForRestart() error
|
||||
}
|
||||
|
||||
func NewITaskService() ITaskLogService {
|
||||
|
@ -40,3 +41,7 @@ func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, e
|
|||
}
|
||||
return total, items, err
|
||||
}
|
||||
|
||||
func (u *TaskLogService) SyncForRestart() error {
|
||||
return taskRepo.UpdateRunningTaskToFailed()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package task
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -75,11 +76,6 @@ const (
|
|||
TaskScopeRuntimeExtension = "RuntimeExtension"
|
||||
)
|
||||
|
||||
const (
|
||||
TaskSuccess = "Success"
|
||||
TaskFailed = "Failed"
|
||||
)
|
||||
|
||||
func GetTaskName(resourceName, operate, scope string) string {
|
||||
return fmt.Sprintf("%s%s [%s]", i18n.GetMsgByKey(operate), i18n.GetMsgByKey(scope), resourceName)
|
||||
}
|
||||
|
@ -88,6 +84,16 @@ func NewTaskWithOps(resourceName, operate, scope, taskID string, resourceID uint
|
|||
return NewTask(GetTaskName(resourceName, operate, scope), operate, scope, taskID, resourceID)
|
||||
}
|
||||
|
||||
func CheckTaskIsExecuting(name string) error {
|
||||
taskRepo := repo.NewITaskRepo()
|
||||
commonRepo := repo.NewCommonRepo()
|
||||
task, _ := taskRepo.GetFirst(commonRepo.WithByStatus(constant.StatusExecuting), commonRepo.WithByName(name))
|
||||
if task.ID != "" {
|
||||
return buserr.New("TaskIsExecuting")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, error) {
|
||||
if taskID == "" {
|
||||
taskID = uuid.New().String()
|
||||
|
@ -109,7 +115,7 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
|
|||
Name: name,
|
||||
Type: taskScope,
|
||||
LogFile: logPath,
|
||||
Status: constant.StatusRunning,
|
||||
Status: constant.StatusExecuting,
|
||||
ResourceID: resourceID,
|
||||
Operate: operate,
|
||||
}
|
||||
|
@ -209,7 +215,7 @@ func (t *Task) Execute() error {
|
|||
break
|
||||
}
|
||||
}
|
||||
if t.Task.Status == constant.Running {
|
||||
if t.Task.Status == constant.StatusExecuting {
|
||||
t.Task.Status = constant.StatusSuccess
|
||||
t.Log(i18n.GetWithName("TaskSuccess", t.Name))
|
||||
} else {
|
||||
|
|
|
@ -11,6 +11,7 @@ const (
|
|||
StatusDisable = "Disable"
|
||||
StatusNone = "None"
|
||||
StatusDeleted = "Deleted"
|
||||
StatusExecuting = "Executing"
|
||||
|
||||
OrderDesc = "descending"
|
||||
OrderAsc = "ascending"
|
||||
|
|
|
@ -291,6 +291,7 @@ TaskSync: "Sync"
|
|||
LocalApp: "Local App"
|
||||
SubTask: "Subtask"
|
||||
RuntimeExtension: "Runtime Extension"
|
||||
TaskIsExecuting: "Task is executing"
|
||||
|
||||
# task - snapshot
|
||||
Snapshot: "Snapshot"
|
||||
|
|
|
@ -292,7 +292,7 @@ TaskSync: "同步"
|
|||
LocalApp: "本地應用"
|
||||
SubTask: "子任務"
|
||||
RuntimeExtension: "運行環境擴展"
|
||||
|
||||
TaskIsExecuting: "任務正在執行中"
|
||||
|
||||
# task - snapshot
|
||||
Snapshot: "快照"
|
||||
|
|
|
@ -292,6 +292,7 @@ TaskSync: "同步"
|
|||
LocalApp: "本地应用"
|
||||
SubTask: "子任务"
|
||||
RuntimeExtension: "运行环境扩展"
|
||||
TaskIsExecuting: "任务正在运行"
|
||||
|
||||
# task - snapshot
|
||||
Snapshot: "快照"
|
||||
|
|
|
@ -11,6 +11,7 @@ func Init() {
|
|||
go syncInstalledApp()
|
||||
go syncRuntime()
|
||||
go syncSSL()
|
||||
go syncTask()
|
||||
}
|
||||
|
||||
func syncApp() {
|
||||
|
@ -38,3 +39,9 @@ func syncSSL() {
|
|||
global.LOG.Errorf("sync ssl status error : %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func syncTask() {
|
||||
if err := service.NewITaskService().SyncForRestart(); err != nil {
|
||||
global.LOG.Errorf("sync task status error : %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
|
|||
Name: name,
|
||||
Type: taskScope,
|
||||
LogFile: logPath,
|
||||
Status: constant.StatusRunning,
|
||||
Status: constant.StatusExecuting,
|
||||
ResourceID: resourceID,
|
||||
Operate: operate,
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ func (t *Task) Execute() error {
|
|||
break
|
||||
}
|
||||
}
|
||||
if t.Task.Status == constant.StatusRunning {
|
||||
if t.Task.Status == constant.StatusExecuting {
|
||||
t.Task.Status = constant.StatusSuccess
|
||||
t.Log(i18n.GetWithName("TaskSuccess", t.Name))
|
||||
} else {
|
||||
|
|
|
@ -18,6 +18,7 @@ const (
|
|||
StatusExceptional = "Exceptional"
|
||||
StatusRetrying = "Retrying"
|
||||
StatusLost = "Lost"
|
||||
StatusExecuting = "Executing"
|
||||
|
||||
StatusEnable = "Enable"
|
||||
StatusDisable = "Disable"
|
||||
|
|
|
@ -175,6 +175,9 @@ export namespace File {
|
|||
page: number;
|
||||
pageSize: number;
|
||||
taskID?: string;
|
||||
taskType?: string;
|
||||
taskOperate?: string;
|
||||
resourceID?: number;
|
||||
}
|
||||
|
||||
export interface Favorite extends CommonModel {
|
||||
|
|
|
@ -34,6 +34,7 @@ interface LogProps {
|
|||
type: string;
|
||||
name?: string;
|
||||
tail?: boolean;
|
||||
taskID?: string;
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -72,6 +73,7 @@ const stopSignals = [
|
|||
'image pull successful!',
|
||||
'image push failed!',
|
||||
'image push successful!',
|
||||
'[TASK-END]',
|
||||
];
|
||||
const emit = defineEmits(['update:loading', 'update:hasContent', 'update:isReading']);
|
||||
const tailLog = ref(false);
|
||||
|
@ -83,6 +85,7 @@ const readReq = reactive({
|
|||
page: 1,
|
||||
pageSize: 500,
|
||||
latest: false,
|
||||
taskID: '',
|
||||
});
|
||||
const isLoading = ref(false);
|
||||
const end = ref(false);
|
||||
|
@ -158,6 +161,7 @@ const getContent = async (pre: boolean) => {
|
|||
readReq.id = props.config.id;
|
||||
readReq.type = props.config.type;
|
||||
readReq.name = props.config.name;
|
||||
readReq.taskID = props.config.taskID;
|
||||
if (readReq.page < 1) {
|
||||
readReq.page = 1;
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ const getType = (status: string) => {
|
|||
case 'done':
|
||||
case 'healthy':
|
||||
case 'used':
|
||||
case 'executing':
|
||||
return 'success';
|
||||
case 'stopped':
|
||||
case 'exceptional':
|
||||
|
@ -80,6 +81,7 @@ const loadingStatus = [
|
|||
'packing',
|
||||
'sending',
|
||||
'waiting',
|
||||
'executing',
|
||||
];
|
||||
|
||||
const loadingIcon = (status: string): boolean => {
|
||||
|
|
|
@ -9,15 +9,12 @@
|
|||
:width="width"
|
||||
>
|
||||
<div>
|
||||
<highlightjs class="editor-main" ref="editorRef" :autodetect="false" :code="content"></highlightjs>
|
||||
<LogFile :config="config"></LogFile>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onUnmounted, reactive, ref } from 'vue';
|
||||
import { ReadByLine } from '@/api/modules/files';
|
||||
|
||||
const editorRef = ref();
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
defineProps({
|
||||
showClose: {
|
||||
|
@ -30,207 +27,30 @@ defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const data = ref({
|
||||
enable: false,
|
||||
content: '',
|
||||
path: '',
|
||||
});
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
const tailLog = ref(false);
|
||||
const content = ref('');
|
||||
const end = ref(false);
|
||||
const lastContent = ref('');
|
||||
const scrollerElement = ref<HTMLElement | null>(null);
|
||||
const minPage = ref(1);
|
||||
const maxPage = ref(1);
|
||||
const open = ref(false);
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
const readReq = reactive({
|
||||
const config = reactive({
|
||||
taskID: '',
|
||||
type: 'task',
|
||||
page: 1,
|
||||
pageSize: 500,
|
||||
latest: false,
|
||||
taskType: '',
|
||||
taskOperate: '',
|
||||
id: 0,
|
||||
resourceID: 0,
|
||||
taskType: '',
|
||||
});
|
||||
|
||||
const stopSignals = ['[TASK-END]'];
|
||||
|
||||
const initData = () => {
|
||||
open.value = true;
|
||||
initCodemirror();
|
||||
init();
|
||||
};
|
||||
const open = ref(false);
|
||||
|
||||
const openWithTaskID = (id: string) => {
|
||||
readReq.taskID = id;
|
||||
initData();
|
||||
config.taskID = id;
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const openWithResourceID = (taskType: string, taskOperate: string, resourceID: number) => {
|
||||
readReq.taskType = taskType;
|
||||
readReq.id = resourceID;
|
||||
readReq.taskOperate = taskOperate;
|
||||
initData();
|
||||
};
|
||||
|
||||
const getContent = (pre: boolean) => {
|
||||
if (readReq.page < 1) {
|
||||
readReq.page = 1;
|
||||
}
|
||||
ReadByLine(readReq).then((res) => {
|
||||
if (!end.value && res.data.end) {
|
||||
lastContent.value = content.value;
|
||||
}
|
||||
|
||||
res.data.content = res.data.content.replace(/\\u(\w{4})/g, function (match, grp) {
|
||||
return String.fromCharCode(parseInt(grp, 16));
|
||||
});
|
||||
data.value = res.data;
|
||||
if (res.data.content != '') {
|
||||
if (stopSignals.some((signal) => res.data.content.includes(signal))) {
|
||||
onCloseLog();
|
||||
}
|
||||
if (end.value) {
|
||||
if (lastContent.value == '') {
|
||||
content.value = res.data.content;
|
||||
} else {
|
||||
content.value = pre
|
||||
? res.data.content + '\n' + lastContent.value
|
||||
: lastContent.value + '\n' + res.data.content;
|
||||
}
|
||||
} else {
|
||||
if (content.value == '') {
|
||||
content.value = res.data.content;
|
||||
} else {
|
||||
content.value = pre
|
||||
? res.data.content + '\n' + content.value
|
||||
: content.value + '\n' + res.data.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
end.value = res.data.end;
|
||||
nextTick(() => {
|
||||
if (pre) {
|
||||
if (scrollerElement.value.scrollHeight > 2000) {
|
||||
scrollerElement.value.scrollTop = 2000;
|
||||
}
|
||||
} else {
|
||||
scrollerElement.value.scrollTop = scrollerElement.value.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
if (readReq.latest) {
|
||||
readReq.page = res.data.total;
|
||||
readReq.latest = false;
|
||||
maxPage.value = res.data.total;
|
||||
minPage.value = res.data.total;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const changeTail = (fromOutSide: boolean) => {
|
||||
if (fromOutSide) {
|
||||
tailLog.value = !tailLog.value;
|
||||
}
|
||||
if (tailLog.value) {
|
||||
timer = setInterval(() => {
|
||||
getContent(false);
|
||||
}, 1000 * 3);
|
||||
} else {
|
||||
onCloseLog();
|
||||
}
|
||||
config.taskType = taskType;
|
||||
config.resourceID = resourceID;
|
||||
config.taskOperate = taskOperate;
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onCloseLog();
|
||||
open.value = false;
|
||||
em('close', open.value);
|
||||
};
|
||||
|
||||
const onCloseLog = async () => {
|
||||
tailLog.value = false;
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
};
|
||||
|
||||
function isScrolledToBottom(element: HTMLElement): boolean {
|
||||
return element.scrollTop + element.clientHeight + 1 >= element.scrollHeight;
|
||||
}
|
||||
|
||||
function isScrolledToTop(element: HTMLElement): boolean {
|
||||
return element.scrollTop === 0;
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
tailLog.value = true;
|
||||
if (tailLog.value) {
|
||||
changeTail(false);
|
||||
}
|
||||
readReq.latest = true;
|
||||
getContent(false);
|
||||
};
|
||||
|
||||
const initCodemirror = () => {
|
||||
nextTick(() => {
|
||||
if (editorRef.value) {
|
||||
scrollerElement.value = editorRef.value.$el as HTMLElement;
|
||||
scrollerElement.value.addEventListener('scroll', function () {
|
||||
if (isScrolledToBottom(scrollerElement.value)) {
|
||||
readReq.page = maxPage.value;
|
||||
getContent(false);
|
||||
}
|
||||
if (isScrolledToTop(scrollerElement.value)) {
|
||||
readReq.page = minPage.value - 1;
|
||||
if (readReq.page < 1) {
|
||||
return;
|
||||
}
|
||||
minPage.value = readReq.page;
|
||||
getContent(true);
|
||||
}
|
||||
});
|
||||
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
|
||||
hljsDom.style['min-height'] = '400px';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
onCloseLog();
|
||||
});
|
||||
|
||||
defineExpose({ openWithResourceID, openWithTaskID });
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.task-log-dialog {
|
||||
--dialog-max-height: 80vh;
|
||||
--dialog-header-height: 50px;
|
||||
--dialog-padding: 20px;
|
||||
.el-dialog {
|
||||
max-width: 60%;
|
||||
max-height: var(--dialog-max-height);
|
||||
margin-top: 5vh !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.el-dialog__body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: var(--dialog-padding);
|
||||
}
|
||||
.log-container {
|
||||
height: calc(var(--dialog-max-height) - var(--dialog-header-height) - var(--dialog-padding) * 2);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-main {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
height: 420px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -301,6 +301,7 @@ const message = {
|
|||
packing: 'Packing',
|
||||
sending: 'Sending',
|
||||
healthy: 'Normal',
|
||||
executing: 'Executing',
|
||||
},
|
||||
units: {
|
||||
second: 'Second',
|
||||
|
|
|
@ -296,6 +296,7 @@ const message = {
|
|||
packing: '打包中',
|
||||
sending: '下發中',
|
||||
healthy: '正常',
|
||||
executing: '執行中',
|
||||
},
|
||||
units: {
|
||||
second: '秒',
|
||||
|
|
|
@ -296,6 +296,7 @@ const message = {
|
|||
packing: '打包中',
|
||||
sending: '下发中',
|
||||
healthy: '正常',
|
||||
executing: '执行中',
|
||||
},
|
||||
units: {
|
||||
second: '秒',
|
||||
|
@ -1165,7 +1166,7 @@ const message = {
|
|||
errLog: '错误日志',
|
||||
task: '任务日志',
|
||||
taskName: '任务名称',
|
||||
taskRunning: '运行中',
|
||||
taskRunning: '执行中',
|
||||
},
|
||||
file: {
|
||||
dir: '文件夹',
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<el-option :label="$t('commons.table.all')" value=""></el-option>
|
||||
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
|
||||
<el-option :label="$t('commons.status.failed')" value="Failed"></el-option>
|
||||
<el-option :label="$t('logs.taskRunning')" value="Running"></el-option>
|
||||
<el-option :label="$t('logs.taskRunning')" value="Executing"></el-option>
|
||||
</el-select>
|
||||
<TableRefresh @search="search()" />
|
||||
<TableSetting title="task-log-refresh" @search="search()" />
|
||||
|
|
Loading…
Add table
Reference in a new issue