Jelajahi Sumber

新增单个接口的历史记录查看、对比、还原

mxd 3 tahun lalu
induk
melakukan
0419b181b4

+ 1 - 1
src/components/common/data/magic-navbar.css

@@ -83,8 +83,8 @@
   color: var(--main-color);
 }
 .magic-navbar__horizontal > ul li svg.magic-icon {
-  padding: 0 2px;
   height: 100%;
+  margin-right: 4px;
 }
 .magic-navbar__horizontal > ul {
   border-top: 1px solid var(--main-border-color);

+ 1 - 1
src/components/common/magic-context-menu.vue

@@ -82,7 +82,7 @@ export default {
 .magic-context-menu .magic-context-menu-icon{
     display: inline-block;
     text-align: center;
-    margin-right: 5px;
+    margin-right: 8px;
     width: 13px;
 }
 .magic-context-menu li:hover{

+ 7 - 0
src/components/common/magic-monaco-diff-editor.vue

@@ -4,6 +4,7 @@
 <script>
 import * as monaco from 'monaco-editor'
 import constants from '../../scripts/constants.js'
+import { watch } from 'vue'
 export default {
 	props: {
 		language: { type: String, required: true },
@@ -41,6 +42,12 @@ export default {
 			original: monaco.editor.createModel(this.value[0], this.language),
 			modified: monaco.editor.createModel(this.value[1], this.language)
 		})
+		watch(() => this.language, () => {
+			nextTick(() => this.instance.setModel({
+				original: monaco.editor.createModel(this.value[0], this.language),
+				modified: monaco.editor.createModel(this.value[1], this.language)
+			}))
+		})
 
 	},
 	methods: {

+ 123 - 0
src/components/panel/footer/magic-backup-file.vue

@@ -0,0 +1,123 @@
+<template>
+    <magic-loading :loading="loading">
+        <div class="magic-backup-file">
+            <template v-if="backupData.length > 0">
+                <magic-table :data="backupData" :border="true" @clickRow="clickRow">
+                    <magic-table-column :title="$i('message.date')" width="160" v-slot:default="{ row }" class="selected">{{ formatDate(row.createDate) }}</magic-table-column>
+                    <magic-table-column :title="$i('history.operator')" width="100" v-slot:default="{ row }">{{ row.createBy || 'guest' }}</magic-table-column>
+                </magic-table>
+                <div class="magic-backup-file-diff-container">
+                    <ul>
+                        <li>
+                            {{ formatDate(current.createDate) }} by {{ current.createBy || 'guest' }}
+                            <magic-button :value="$i('backup.rollback')" type="active" @click="doRollback"/>
+                        </li>
+                        <li>{{ $i('backup.current') }}({{ formatDate(oldInfo.updateTime || oldInfo.createTime) }} by {{ oldInfo.updateBy || oldInfo.createBy || 'guest' }})</li>
+                    </ul>
+                    <magic-monaco-diff-editor v-if="!scriptLoading" v-model:value="diffValue" :language="language"/>
+			        <magic-loading v-else :loading="scriptLoading" />
+                </div>
+            </template>
+            <magic-empty v-else :text="$i('message.empty', $i('history.name'))"/>
+        </div>
+    </magic-loading>
+</template>
+<script setup>
+import { inject, reactive, ref, watch } from 'vue'
+import $i from '../../../scripts/i18n.js'
+import request from '../../../scripts/request.js'
+import { formatDate } from '../../../scripts/utils.js'
+import modal from '../../common/dialog/magic-modal.js'
+const props = defineProps({
+    id: String
+})
+const service = inject('service')
+const oldInfo = ref('')
+const loading = ref(true)
+const scriptLoading = ref(true)
+const backupData = reactive([])
+const current = ref({})
+const diffValue = ref([])
+const language = ref('')
+const loadDifference = () => {
+    language.value = service[current.value.type].language || 'magicscript'
+    const promises = []
+    scriptLoading.value = true
+    if(!oldInfo.value){
+        promises.push(new Promise(r => request.sendGet('/resource/file/' + props.id).success(res => (oldInfo.value = res) && r())))
+    }
+    promises.push(new Promise(r => request.sendGet('/backup', { id: props.id, timestamp: current.value.createDate }).success(res => (current.value.script = res) && r())))
+    Promise.all(promises).then(() => {
+        scriptLoading.value = false
+        diffValue.value = [current.value.script, oldInfo.value.script]
+        console.log(diffValue)
+    }).catch(e => {
+        console.error(e)
+        scriptLoading.value = false
+    })
+}
+const load = () => {
+    loading.value = true
+    request.sendGet('/backup/' + props.id).success(res => {
+        backupData.push(...res)
+        current.value = backupData[0] || {}
+        if(current.value){
+            loadDifference()
+        }
+        loading.value = false
+    })
+}
+load();
+watch(() => props.id, load)
+
+const doRollback = () => {
+	const msg = `${current.value.name}(${formatDate(current.value.createDate)})`
+	request.sendPost('/backup/rollback', { id: props.id, timestamp: current.value.createDate}).success(r => {
+		if(!r){
+			modal.alert($i('backup.rollbackFailed', msg))
+			bus.status('backup.rollbackFailed', false, msg)
+		} else {
+			modal.alert($i('backup.rollbackSuccess', msg))
+			bus.status('backup.rollbackSuccess', true, msg)
+			bus.$emit(Message.LOAD_RESOURCES, current.value.type)
+		}
+	})
+}
+const clickRow = rowIndex => {
+    current.value = backupData[rowIndex]
+    loadDifference()
+}
+</script>
+<style scoped>
+.magic-table{
+    width: 270px;
+    border-right: 1px solid var(--table-border-color);
+}
+.magic-backup-file{
+    height: 100%;
+    position: relative;
+    overflow: hidden;
+    display: flex;
+}
+.magic-backup-file-diff-container{
+    flex: 1;
+    overflow: hidden;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+}
+.magic-backup-file-diff-container > ul{
+    display: flex;
+}
+.magic-backup-file-diff-container > ul li{
+    flex: 1;
+    height: 30px;
+    line-height: 30px;
+    padding: 0 5px;
+}
+.magic-backup-file-diff-container > ul li button{
+    float: right;
+    margin-right: 10px;
+    margin-top: 4px;
+}
+</style>

+ 28 - 6
src/components/panel/footer/magic-backup.vue

@@ -15,10 +15,10 @@
 	<magic-dialog v-model:value="dialog" :title="$i('history.name')" width="80%" maxWidth="100%" top="60px" height="80%" className="magic-dialog-diff">
 		<div class="magic-backup-diff-container">
 			<div class="magic-backup-diff-header">
-				<div>{{ currentTime }}</div>
-				<div>{{ $i('backup.current') }}</div>
+				<div>{{ currentTime }} by {{ currentSelected.createBy || 'guest' }}</div>
+				<div>{{ $i('backup.current') }}({{ formatDate(currentVersion.updateTime || currentVersion.createTime) }} by {{ currentVersion.updateBy || currentVersion.createBy || 'guest' }})</div>
 			</div>
-			<magic-monaco-diff-editor v-if="!dialogLoading" v-model:value="diffValue" language="magicscript"/>
+			<magic-monaco-diff-editor v-if="!dialogLoading" v-model:value="diffValue" :language="language"/>
 			<magic-loading v-else :loading="dialogLoading" />
 		</div>
 		<magic-button-group align="right">
@@ -27,7 +27,7 @@
 	</magic-dialog>
 </template>
 <script setup>
-import { getCurrentInstance, inject, onMounted, ref } from 'vue'
+import { getCurrentInstance, inject, onMounted, ref, resolveDynamicComponent, shallowRef } from 'vue'
 import bus from '../../../scripts/bus.js'
 import constants from '../../../scripts/constants.js'
 import Message from '../../../scripts/constants/message.js'
@@ -35,6 +35,7 @@ import request from '../../../scripts/request.js'
 import $i from '../../../scripts/i18n.js'
 import { formatDate } from '../../../scripts/utils.js'
 import modal from '../../common/dialog/magic-modal.js'
+const magicBackupFileComponent = resolveDynamicComponent('magic-backup-file')
 const loading = ref(true)
 const toolbars = [{
 	icon: 'refresh',
@@ -58,6 +59,7 @@ const dialog = ref(false)
 const dialogLoading = ref(true)
 const currentTime = ref('')
 const currentSelected = ref({})
+const language = ref('magicscript')
 let loadingNext = false
 let hasNext = false
 let nextTimestamp = undefined
@@ -76,6 +78,7 @@ const load = (timestamp) => {
 	})
 }
 const service = inject('service')
+const currentVersion = ref({})
 const displayTitle = type => {
 	if(type.endsWith('-group')){
 		return $i('message.group', service[type.replace('-group', '')]?.name || 'Unknown')
@@ -110,19 +113,38 @@ const onContextmenu = (event, row) => {
 				currentTime.value = formatDate(row.createDate)
 				dialog.value = true
 				dialogLoading.value = true
+				language.value = service[row.type].language || 'magicscript'
 				Promise.all([
 					new Promise(r => request.sendGet('/backup', { id: row.id, timestamp: row.createDate}).success(script => r(script))),
-					new Promise(r => request.sendGet('/resource/file/' + row.id).success(res => r(res?.script || '')))
+					new Promise(r => request.sendGet('/resource/file/' + row.id).success(res => r(res)))
 				]).then(values => {
+					currentVersion.value = values[1]
+					console.log(currentVersion)
 					currentSelected.value = row
 					dialogLoading.value = false
-					diffValue.value = values
+					diffValue.value = [values[0], values[1]?.script]
 				}).catch(e => {
 					console.error(e)
 					dialog.value = false
 				})
 			}
 		})
+		menus.push({
+			label: $i('toolbars.history'),
+			icon: 'history',
+			onClick: () => {
+				bus.$emit(Message.ADD_FOOTER_TOOLBAR, {
+					component: shallowRef(magicBackupFileComponent),
+					id: 'backup-file',
+					icon: 'history',
+					title: $i('toolbars.viewHistory', row.name),
+					allowClose: true,
+					data: {
+						id: row.id
+					}
+				})
+			}
+		})
 	}
 	if(row.id === 'full' || !row.id.endsWith('-group')){
 		menus.push({

+ 29 - 2
src/components/panel/footer/magic-toolbar.vue

@@ -7,9 +7,10 @@
                         <label>{{ toolbar.title }}</label>
                         <div class="magic-toolbar-header-buttons">
                             <magic-icon icon="minimize" size="14px" :title="$i('message.hide')" @click="navbar.select(-1)"/>
+                            <magic-icon icon="close" :title="$i('editor.tab.close')" v-if="toolbar.allowClose" @click="closeTab(toolbar)"/>
                         </div>
                     </div>
-                    <component :is="toolbar.component"/>
+                    <component :is="toolbar.component" v-bind="toolbar.data"/>
                 </magic-resizer>
             </magic-navbar-item>
         </magic-navbar>
@@ -65,6 +66,32 @@ const setOpenedToolbars = (type) => {
     }
 }
 setOpenedToolbars('')
+const closeTab = toolbar => {
+    const index = toolbars.findIndex(it => it.id === toolbar.id)
+    if(index > -1){
+        toolbars.splice(index, 1)
+    }
+    navbar.value.select(-1)
+}
+bus.$on(Message.ADD_FOOTER_TOOLBAR, (toolbar) => {
+    if(toolbar.id) {
+        closeTab(toolbar)
+    }
+    navbar.value.select(toolbars.push(toolbar) - 1)
+})
+const closeTab = toolbar => {
+  const index = toolbars.findIndex(it => it.id === toolbar.id)
+  if(index > -1){
+    toolbars.splice(index, 1)
+  }
+  navbar.value.select(-1)
+}
+bus.$on(Message.ADD_FOOTER_TOOLBAR, (toolbar) => {
+  if(toolbar.id) {
+    closeTab(toolbar)
+  }
+  navbar.value.select(toolbars.push(toolbar) - 1)
+})
 bus.$on(Message.OPEN_EMPTY, () => setOpenedToolbars(''))
 bus.$on(Message.OPEN, event => {
     event.responseBlobValue = null
@@ -141,4 +168,4 @@ bus.$on(Message.SWITCH_TOOLBAR, id => {
 .magic-event > div{
     flex: 1;
 }
-</style>
+</style>

+ 20 - 1
src/components/panel/main/magic-resource.vue

@@ -52,13 +52,14 @@
 	</div>
 </template>
 <script setup>
-import { ref, toRaw, computed, getCurrentInstance, reactive, inject, nextTick, onMounted } from 'vue'
+import { ref, toRaw, computed, getCurrentInstance, reactive, inject, nextTick, onMounted, resolveDynamicComponent, shallowRef } from 'vue'
 import bus from '../../../scripts/bus.js'
 import constants from '../../../scripts/constants.js'
 import request from '../../../scripts/request.js'
 import $i from '../../../scripts/i18n.js'
 import Message from '../../../scripts/constants/message.js'
 import { replaceURL, processTree, download, copyToClipboard } from '../../../scripts/utils.js'
+const magicBackupFileComponent = resolveDynamicComponent('magic-backup-file')
 const props = defineProps({
 	/**
 	 * 类型,api、function、resource、cron、websocket
@@ -447,6 +448,24 @@ const onContextMenu = (item, event) => {
 					})
 				}
 			}
+			if(item.id) {
+				menus.push({
+					label: $i('toolbars.history'),
+					icon: 'history',
+					onClick: () => {
+						bus.$emit(Message.ADD_FOOTER_TOOLBAR, {
+							component: shallowRef(magicBackupFileComponent),
+							id: 'backup-file',
+							icon: 'history',
+							title: $i('toolbars.viewHistory', getFullPath(item)),
+							allowClose: true,
+							data: {
+								id: item.id
+							}
+						})
+					}
+				})
+			}
 			menus.push.apply(menus, [{
 				label: $i('message.refresh'),
 				icon: 'refresh'

+ 1 - 0
src/icons/right.svg

@@ -0,0 +1 @@
+<svg class="icon" style="width: 1em;height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M384.30607 256.204389l0 512.41186 320.259338-256.206443L384.30607 256.204389z"></path></svg>

+ 2 - 1
src/scripts/constants/message.js

@@ -26,5 +26,6 @@ export default {
     NOTIFY: 'notify',
     RELOAD_RESOURCES: 'reload-resources',
     RELOAD_RESOURCES_FINISH: 'reload-resources-finish',
-    LOAD_RESOURCES_FINISH: 'load-resources-finish'
+    LOAD_RESOURCES_FINISH: 'load-resources-finish',
+    ADD_FOOTER_TOOLBAR: 'add-footer-toolbar'
 }

+ 2 - 1
src/scripts/i18n/en.js

@@ -264,7 +264,8 @@ export default {
         history: 'History',
         event: 'Event',
         global: 'Global Parameters',
-        response: 'Response'
+        response: 'Response',
+        viewHistory: 'History:{0}'
     },
     event: {
         message: 'Message'

+ 2 - 1
src/scripts/i18n/zh-cn.js

@@ -281,7 +281,8 @@ export default {
         history: '历史记录',
         event: '事件',
         global: '全局参数',
-        response: '执行结果'
+        response: '执行结果',
+        viewHistory: '历史记录:{0}'
     },
     event: {
         message: '事件内容'