mirror of
https://gitee.com/dromara/mayfly-go
synced 2026-05-28 05:45:19 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a17fa5a103 | ||
|
|
519089d8d0 |
@@ -19,7 +19,6 @@
|
||||
"@xterm/addon-web-links": "^0.12.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"asciinema-player": "^3.15.1",
|
||||
"axios": "^1.16.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.20",
|
||||
"echarts": "^6.0.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { templateResolve } from '@/common/utils/string';
|
||||
import { RequestOptions, useApiFetch } from '@/hooks/useRequest';
|
||||
import config from './config';
|
||||
import request, { joinClientParams } from './request';
|
||||
import { getToken } from './utils/storage';
|
||||
|
||||
/**
|
||||
* 文件上传选项
|
||||
@@ -78,17 +78,6 @@ class Api<T = any, P = any> {
|
||||
return (data.value as T) || (res as T);
|
||||
}
|
||||
|
||||
/**
|
||||
* xhr 请求对应的该api
|
||||
* @param {Object} param 请求该api的参数
|
||||
*/
|
||||
async xhrReq(param: any = null, options: any = {}): Promise<T> {
|
||||
if (this.beforeHandler) {
|
||||
await this.beforeHandler(param);
|
||||
}
|
||||
return request.xhrReq(this.method, this.url, param, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传请求
|
||||
* @param formData FormData 对象(调用方自行构建,包含文件和其他参数)
|
||||
@@ -137,14 +126,44 @@ class Api<T = any, P = any> {
|
||||
/**
|
||||
* 原始文件流上传请求(直接使用文件流作为 body,参数通过 URL query 传递)
|
||||
* @param file 文件对象
|
||||
* @param queryParams URL 查询参数字符串
|
||||
* @param queryParams URL 查询参数对象(可选)
|
||||
* @param options 上传选项(可包含自定义 headers)
|
||||
* @returns { abort: () => void } 返回中止方法
|
||||
*/
|
||||
uploadRaw(file: File, queryParams: string, options: UploadOptions & { headers?: Record<string, string> } = {}): { abort: () => void } {
|
||||
uploadRaw(file: File, queryParams?: Record<string, string>, options: UploadOptions & { headers?: Record<string, string> } = {}): { abort: () => void } {
|
||||
const { onSuccess, onError, headers = {} } = options;
|
||||
|
||||
const url = `${config.baseApiUrl}${this.url}?${queryParams}&${joinClientParams()}`;
|
||||
// 构建 URL,兼容没有 queryParams 的情况
|
||||
let url = `${config.baseApiUrl}${this.url}`;
|
||||
// 简单判断该url是否是restful风格
|
||||
if (url.indexOf('{') != -1 && queryParams) {
|
||||
url = templateResolve(url, queryParams);
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
// 添加业务参数
|
||||
if (queryParams) {
|
||||
Object.entries(queryParams).forEach(([key, value]) => {
|
||||
searchParams.append(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加客户端参数
|
||||
const clientParams = joinClientParams();
|
||||
if (clientParams) {
|
||||
// 将 joinClientParams 返回的字符串追加到 searchParams
|
||||
const clientParamsObj = new URLSearchParams(clientParams);
|
||||
clientParamsObj.forEach((value, key) => {
|
||||
searchParams.append(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
// 拼接完整的 query string
|
||||
const queryString = searchParams.toString();
|
||||
if (queryString) {
|
||||
url += `?${queryString}`;
|
||||
}
|
||||
|
||||
// 创建 AbortController 用于取消请求
|
||||
const abortController = new AbortController();
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import router from '../router';
|
||||
import config from './config';
|
||||
import { getClientId, getToken } from './utils/storage';
|
||||
import { templateResolve } from './utils/string';
|
||||
import { Msg } from '@/hooks/useI18n';
|
||||
import axios from 'axios';
|
||||
import JSONBig from 'json-bigint';
|
||||
import { useApiFetch } from '../hooks/useRequest';
|
||||
import Api from './Api';
|
||||
|
||||
// 配置 JSONBig:将大数(int64/uint64)转为字符串,避免精度丢失
|
||||
// storeAsString: 将大数存储为字符串,而不是 BigNumber 对象
|
||||
const JSONBigString = JSONBig({ storeAsString: true });
|
||||
import config from './config';
|
||||
import { getClientId, getToken } from './utils/storage';
|
||||
|
||||
export default {
|
||||
request,
|
||||
xhrReq,
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
@@ -50,138 +40,6 @@ export const baseUrl: string = config.baseApiUrl;
|
||||
// const baseUrl: string = 'http://localhost:18888/api';
|
||||
// const baseWsUrl: string = config.baseWsUrl;
|
||||
|
||||
/**
|
||||
* 通知错误消息
|
||||
* @param msg 错误消息
|
||||
*/
|
||||
function notifyErrorMsg(msg: string) {
|
||||
// 危险通知
|
||||
Msg.error(msg);
|
||||
}
|
||||
|
||||
// create an axios instance
|
||||
const axiosInst = axios.create({
|
||||
baseURL: baseUrl, // url = base url + request url
|
||||
timeout: 60000, // request timeout
|
||||
// 使用 json-bigint 处理响应数据,解决 int64/uint64 精度丢失问题
|
||||
transformResponse: [
|
||||
function (data) {
|
||||
// 对响应数据进行转换
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
// 使用 JSONBigString 解析,大数会被转为字符串
|
||||
return JSONBigString.parse(data);
|
||||
} catch (err) {
|
||||
// 如果解析失败,返回原始数据
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// request interceptor
|
||||
axiosInst.interceptors.request.use(
|
||||
(config: any) => {
|
||||
// do something before request is sent
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
// 设置token
|
||||
config.headers['Authorization'] = token;
|
||||
config.headers['ClientId'] = getClientId();
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// response interceptor
|
||||
axiosInst.interceptors.response.use(
|
||||
(response) => response,
|
||||
(e: any) => {
|
||||
const rejectPromise = Promise.reject(e);
|
||||
|
||||
if (axios.isCancel(e)) {
|
||||
console.log('请求已取消');
|
||||
return rejectPromise;
|
||||
}
|
||||
|
||||
const statusCode = e.response?.status;
|
||||
if (statusCode == 500) {
|
||||
notifyErrorMsg('服务器未知异常');
|
||||
return rejectPromise;
|
||||
}
|
||||
|
||||
if (statusCode == 404) {
|
||||
notifyErrorMsg('请求接口未找到');
|
||||
return rejectPromise;
|
||||
}
|
||||
|
||||
if (e.message) {
|
||||
// 对响应错误做点什么
|
||||
if (e.message.indexOf('timeout') != -1) {
|
||||
notifyErrorMsg('网络请求超时');
|
||||
return rejectPromise;
|
||||
}
|
||||
|
||||
if (e.message == 'Network Error') {
|
||||
notifyErrorMsg('网络连接错误');
|
||||
return rejectPromise;
|
||||
}
|
||||
}
|
||||
|
||||
notifyErrorMsg('网络请求错误');
|
||||
return rejectPromise;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* xhr请求url
|
||||
*
|
||||
* @param method 请求方法
|
||||
* @param url url
|
||||
* @param params 参数
|
||||
* @param options 可选
|
||||
* @returns
|
||||
*/
|
||||
export function xhrReq(method: string, url: string, params: any = null, options: any = {}) {
|
||||
if (!url) {
|
||||
throw new Error('请求url不能为空');
|
||||
}
|
||||
|
||||
// 简单判断该url是否是restful风格
|
||||
if (url.indexOf('{') != -1) {
|
||||
url = templateResolve(url, params);
|
||||
}
|
||||
|
||||
const req: any = {
|
||||
method,
|
||||
url,
|
||||
...options,
|
||||
};
|
||||
|
||||
// post和put使用json格式传参
|
||||
if (method === 'post' || method === 'put') {
|
||||
req.data = params;
|
||||
} else {
|
||||
req.params = params;
|
||||
}
|
||||
|
||||
return axiosInst
|
||||
.request(req)
|
||||
.then((response) => {
|
||||
// 获取请求返回结果
|
||||
const result: Result = response.data;
|
||||
return parseResult(result);
|
||||
})
|
||||
.catch((e) => {
|
||||
return Promise.reject(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch请求url
|
||||
*
|
||||
@@ -277,23 +135,3 @@ export function downloadFile(key: string) {
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
|
||||
function parseResult(result: Result) {
|
||||
if (result.code === ResultEnum.SUCCESS) {
|
||||
return result.data;
|
||||
}
|
||||
|
||||
// 如果提示没有权限,则移除token,使其重新登录
|
||||
if (result.code === ResultEnum.NO_PERMISSION) {
|
||||
router.push({
|
||||
path: '/401',
|
||||
});
|
||||
}
|
||||
|
||||
// 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
|
||||
if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
|
||||
notifyErrorMsg(result.msg);
|
||||
}
|
||||
|
||||
return Promise.reject(result);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ export default {
|
||||
stopImageConfirm: 'Are you sure to stop image [{name}] ?',
|
||||
export: 'Export',
|
||||
imageUploading: 'Image uploading, please wait...',
|
||||
uploadSuccess: 'Image uploaded successfully',
|
||||
imageTips: 'Support manual input and select',
|
||||
forcePull: 'Force Pull Image',
|
||||
hostPortPlaceholder: '80',
|
||||
|
||||
@@ -38,6 +38,7 @@ export default {
|
||||
stopImageConfirm: '确定删除该镜像?',
|
||||
export: '导出',
|
||||
imageUploading: '镜像导入中,请稍后...',
|
||||
uploadSuccess: '镜像导入成功',
|
||||
imageTips: '支持手动输入并选择',
|
||||
forcePull: '强制拉取镜像',
|
||||
hostPortPlaceholder: '80',
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<img :src="userInfo.photo" class="w-full h-full rounded transition-transform duration-300 hover:scale-110" />
|
||||
</el-upload>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 px-3.75">
|
||||
<div class="mb-4 text-lg truncate">{{ $t('home.welcomeMsg', { name: userInfo.name }) }}</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-1.5 text-[13px]">
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { OptionsApi, SearchItem } from '@/components/pagetable/SearchForm';
|
||||
import { ContextmenuItem } from '@/components/contextmenu';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { tagApi } from '../tag/api';
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
// 资源配置
|
||||
@@ -223,24 +220,6 @@ export class NodeType {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签搜索项配置
|
||||
* @param resourceType 资源类型
|
||||
* @returns
|
||||
*/
|
||||
export function getTagPathSearchItem(resourceType: any) {
|
||||
return SearchItem.select('tagPath', 'common.tag').withOptionsApi(
|
||||
OptionsApi.new(tagApi.getResourceTagPaths, { resourceType }).withConvertFn((res: any) => {
|
||||
return res.map((x: any) => {
|
||||
return {
|
||||
label: x,
|
||||
value: x,
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function expandCodePath(codePath: string) {
|
||||
const parts = codePath.split('/');
|
||||
const result = [];
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
@@ -87,7 +86,6 @@ import { Msg, useI18nCreateTitle, useI18nDeleteConfirm, useI18nEditTitle } from
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import TagCodePath from '../component/TagCodePath.vue';
|
||||
import { dbApi } from './api';
|
||||
import { getDbDialect } from './dialect';
|
||||
@@ -110,7 +108,7 @@ const perms = {
|
||||
saveDb: 'db:save',
|
||||
};
|
||||
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('db.keywordPlaceholder'), getTagPathSearchItem(TagResourceTypePath.Db)];
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('db.keywordPlaceholder')];
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('name', 'common.name').isSlot('name').setAddWidth(15),
|
||||
|
||||
@@ -103,12 +103,12 @@ export function uploadSqlFile(
|
||||
// 生成 uploadId
|
||||
const uploadId = `sql_exec_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
||||
|
||||
// 使用 URLSearchParams 构建查询参数
|
||||
const queryParams = new URLSearchParams({
|
||||
// 构建查询参数对象
|
||||
const queryParams: Record<string, string> = {
|
||||
db: params.dbName,
|
||||
uploadId: uploadId,
|
||||
filename: file.name,
|
||||
}).toString();
|
||||
};
|
||||
|
||||
// 创建 Api 实例
|
||||
const api = Api.newPost(`/dbs/${params.dbId}/exec-sql-file`);
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
@@ -67,7 +66,6 @@ import { Msg, useI18nCreateTitle, useI18nDeleteConfirm, useI18nEditTitle } from
|
||||
import TagCodePath from '@/views/ops/component/TagCodePath.vue';
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import { dockerApi } from './api';
|
||||
|
||||
const ContainerConfEdit = defineAsyncComponent(() => import('./CotainerConfEdit.vue'));
|
||||
@@ -82,10 +80,7 @@ const props = defineProps({
|
||||
const route = useRoute();
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
|
||||
const searchItems = [
|
||||
SearchItem.input('keyword', 'common.keyword').withPlaceholder('redis.keywordPlaceholder'),
|
||||
getTagPathSearchItem(TagResourceTypeEnum.Container.value),
|
||||
];
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('redis.keywordPlaceholder')];
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('name', 'common.name').isSlot('name').setAddWidth(15),
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<el-table :data="filterTableDatas" v-loading="state.loadingImages">
|
||||
<el-table-column prop="id" label="ID" :min-width="100" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" :underline="false">
|
||||
<el-link type="primary" underline="never">
|
||||
{{ row.id.split(':')[1].substring(0, 12) }}
|
||||
</el-link>
|
||||
</template>
|
||||
@@ -86,6 +86,7 @@
|
||||
<script lang="ts" setup>
|
||||
import config from '@/common/config';
|
||||
import { joinClientParams } from '@/common/request';
|
||||
import { downloadFile } from '@/common/utils/file';
|
||||
import { formatByteSize, formatDate } from '@/common/utils/format';
|
||||
import { getToken } from '@/common/utils/storage';
|
||||
import { fuzzyMatchField } from '@/common/utils/string';
|
||||
@@ -159,33 +160,28 @@ const getImages = async () => {
|
||||
};
|
||||
|
||||
const exportImage = async (row: any) => {
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('href', `${config.baseApiUrl}/docker/${props.id}/images/save?id=${props.id}&tag=${row.tags[0]}&${joinClientParams()}`);
|
||||
a.setAttribute('target', '_blank');
|
||||
a.click();
|
||||
downloadFile(`${config.baseApiUrl}/docker/${props.id}/images/save?id=${props.id}&tag=${row.tags[0]}&${joinClientParams()}`);
|
||||
};
|
||||
|
||||
const uploadImage = (content: any) => {
|
||||
const params = new FormData();
|
||||
// const path = state.nowPath;
|
||||
params.append('file', content.file);
|
||||
params.append('id', props.id + '');
|
||||
params.append('token', token);
|
||||
dockerApi.imageUpload
|
||||
.xhrReq(params, {
|
||||
headers: { 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryF1uyUD0tWdqmJqpl' },
|
||||
// onUploadProgress: onUploadProgress,
|
||||
timeout: 3 * 60 * 60 * 1000,
|
||||
})
|
||||
.then(() => {
|
||||
Msg.success('machine.uploadSuccess');
|
||||
setTimeout(() => {
|
||||
getImages();
|
||||
}, 3000);
|
||||
})
|
||||
.catch(() => {
|
||||
// state.uploadProgressShow = false;
|
||||
});
|
||||
const file = content.file;
|
||||
// 直接使用文件流作为 body,不包装为 FormData
|
||||
dockerApi.imageUpload.uploadRaw(
|
||||
file,
|
||||
{ id: String(props.id) },
|
||||
{
|
||||
onSuccess: () => {
|
||||
Msg.success('docker.uploadSuccess');
|
||||
setTimeout(() => {
|
||||
getImages();
|
||||
}, 1000);
|
||||
},
|
||||
onError: (error) => {
|
||||
Msg.error(error.message);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Msg.info('docker.imageUploading');
|
||||
};
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
@@ -73,7 +72,6 @@ import { SearchItem } from '@/components/pagetable/SearchForm';
|
||||
import { Msg, useI18nCreateTitle, useI18nDeleteConfirm, useI18nEditTitle } from '@/hooks/useI18n';
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import TagCodePath from '../component/TagCodePath.vue';
|
||||
import { esApi } from './api';
|
||||
|
||||
@@ -91,7 +89,7 @@ const perms = {
|
||||
delInstance: 'es:instance:del',
|
||||
};
|
||||
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('es.keywordPlaceholder'), getTagPathSearchItem(TagResourceTypePath.Es)];
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('es.keywordPlaceholder')];
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('name', 'common.name').isSlot('name').setAddWidth(15),
|
||||
|
||||
@@ -262,7 +262,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { formatByteSize, formatDate } from '@/common/utils/format';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
@@ -272,7 +271,6 @@ import { Msg, useI18nDeleteConfirm } from '@/hooks/useI18n';
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import TagCodePath from '../component/TagCodePath.vue';
|
||||
import { getMachineTerminalSocketUrl, machineApi } from './api';
|
||||
import { MachineProtocolEnum } from './enums';
|
||||
@@ -310,10 +308,7 @@ const perms = {
|
||||
terminal: 'machine:terminal',
|
||||
};
|
||||
|
||||
const searchItems = [
|
||||
SearchItem.input('keyword', 'common.keyword').withPlaceholder('machine.keywordPlaceholder'),
|
||||
getTagPathSearchItem(TagResourceTypePath.MachineAuthCert),
|
||||
];
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('machine.keywordPlaceholder')];
|
||||
|
||||
const columns = [
|
||||
TableColumn.new('name', 'common.name').isSlot('name').setAddWidth(15),
|
||||
|
||||
@@ -117,8 +117,8 @@ export function uploadFile(file: File, params: UploadParams, options: UploadOpti
|
||||
// 业务层生成 uploadId
|
||||
const uploadId = params.uploadId || `upload_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
||||
|
||||
// 构建查询参数
|
||||
const queryParams = new URLSearchParams({
|
||||
// 构建查询参数对象
|
||||
const queryParams: Record<string, string> = {
|
||||
machineId: String(params.machineId),
|
||||
authCertName: params.authCertName,
|
||||
protocol: String(params.protocol),
|
||||
@@ -126,15 +126,15 @@ export function uploadFile(file: File, params: UploadParams, options: UploadOpti
|
||||
path: params.path,
|
||||
uploadId: uploadId,
|
||||
filename: file.name,
|
||||
});
|
||||
};
|
||||
|
||||
// 如果是文件夹上传,添加标识参数
|
||||
if (params.isFolderUpload) {
|
||||
queryParams.set('isFolderUpload', 'true');
|
||||
queryParams['isFolderUpload'] = 'true';
|
||||
}
|
||||
|
||||
// 直接使用文件流作为 body,不包装为 FormData
|
||||
const { abort } = machineApi.uploadFile.uploadRaw(file, queryParams.toString(), {
|
||||
const { abort } = machineApi.uploadFile.uploadRaw(file, queryParams, {
|
||||
...options,
|
||||
});
|
||||
|
||||
|
||||
@@ -37,17 +37,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { SearchItem } from '@/components/pagetable/SearchForm';
|
||||
import { Msg, useI18nCreateTitle, useI18nDeleteConfirm, useI18nEditTitle } from '@/hooks/useI18n';
|
||||
import { getTagPathSearchItem } from '@/views/ops/component/tag';
|
||||
import { defineAsyncComponent, ref, Ref } from 'vue';
|
||||
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
|
||||
import TagCodePath from '../component/TagCodePath.vue';
|
||||
import { milvusApi, perms } from './api';
|
||||
import type { IMilvus } from './types';
|
||||
import TagCodePath from '../component/TagCodePath.vue';
|
||||
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
|
||||
|
||||
const MilvusEdit = defineAsyncComponent(() => import('./MilvusEdit.vue'));
|
||||
|
||||
@@ -60,7 +58,7 @@ const query = ref({
|
||||
|
||||
const selectionData = ref([]);
|
||||
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('db.keywordPlaceholder'), getTagPathSearchItem(TagResourceTypePath.Db)];
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('db.keywordPlaceholder')];
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('name', 'common.name').isSlot('name').setAddWidth(15),
|
||||
|
||||
@@ -46,14 +46,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { SearchItem } from '@/components/pagetable/SearchForm';
|
||||
import { Msg, useI18nCreateTitle, useI18nDeleteConfirm, useI18nEditTitle } from '@/hooks/useI18n';
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import TagCodePath from '../component/TagCodePath.vue';
|
||||
import { mongoApi } from './api';
|
||||
|
||||
@@ -71,10 +69,7 @@ const props = defineProps({
|
||||
const route = useRoute();
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
|
||||
const searchItems = [
|
||||
SearchItem.input('keyword', 'common.keyword').withPlaceholder('mongo.keywordPlaceholder'),
|
||||
getTagPathSearchItem(TagResourceTypeEnum.Mongo.value),
|
||||
];
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('mongo.keywordPlaceholder')];
|
||||
|
||||
const columns = [
|
||||
TableColumn.new('name', 'common.name').isSlot('name').setAddWidth(25),
|
||||
|
||||
@@ -33,12 +33,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { SearchItem } from '@/components/pagetable/SearchForm';
|
||||
import { Msg, useI18nCreateTitle, useI18nDeleteConfirm, useI18nEditTitle } from '@/hooks/useI18n';
|
||||
import { getTagPathSearchItem } from '@/views/ops/component/tag';
|
||||
import { mqApi } from '@/views/ops/mq/api';
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
@@ -56,10 +54,7 @@ const props = defineProps({
|
||||
const route = useRoute();
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
|
||||
const searchItems = [
|
||||
SearchItem.input('keyword', 'common.keyword').withPlaceholder('mq.kafka.keywordPlaceholder'),
|
||||
getTagPathSearchItem(TagResourceTypeEnum.MqKafka.value),
|
||||
];
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('mq.kafka.keywordPlaceholder')];
|
||||
|
||||
const columns = [
|
||||
TableColumn.new('name', 'common.name').isSlot('name').setAddWidth(15),
|
||||
|
||||
@@ -140,7 +140,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
@@ -149,7 +148,6 @@ import { Msg, useI18nCreateTitle, useI18nDeleteConfirm, useI18nEditTitle } from
|
||||
import { onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import TagCodePath from '../component/TagCodePath.vue';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import Info from './Info.vue';
|
||||
import RedisEdit from './RedisEdit.vue';
|
||||
import { redisApi } from './api';
|
||||
@@ -164,10 +162,7 @@ const props = defineProps({
|
||||
const route = useRoute();
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
|
||||
const searchItems = [
|
||||
SearchItem.input('keyword', 'common.keyword').withPlaceholder('redis.keywordPlaceholder'),
|
||||
getTagPathSearchItem(TagResourceTypeEnum.Redis.value),
|
||||
];
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('redis.keywordPlaceholder')];
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('name', 'common.name').isSlot('name').setAddWidth(15),
|
||||
|
||||
@@ -370,7 +370,7 @@ const allowDrop = (draggingNode: any, dropNode: any, type: any) => {
|
||||
// 如果是插入至目标节点
|
||||
if (type === 'inner') {
|
||||
// 只有目标节点下没有子节点才允许移动
|
||||
if (!dropNode.data.children || dropNode.data.children == 0) {
|
||||
if (!dropNode.data.children || dropNode.data.children?.length == 0) {
|
||||
// 只有权限节点可移动至菜单节点下 或者移动菜单
|
||||
return (
|
||||
(draggingNode.data.type == permissionTypeValue && dropNode.data.type == menuTypeValue) ||
|
||||
|
||||
@@ -13,7 +13,8 @@ server:
|
||||
cert-file: ./default.pem
|
||||
jwt:
|
||||
# jwt key,不设置默认使用随机字符串
|
||||
key: 333333000000
|
||||
# key: 生产环境请务必修改为强随机密钥: openssl rand -base64 32
|
||||
key:
|
||||
# accessToken过期时间单位分钟
|
||||
expire-time: 720
|
||||
# refreshToken过期时间单位分钟
|
||||
@@ -24,7 +25,7 @@ db:
|
||||
address: mysql:3306
|
||||
name: mayfly-go
|
||||
username: root
|
||||
password: 111049
|
||||
password:
|
||||
config: charset=utf8&loc=Local&parseTime=true
|
||||
max-idle-conns: 5
|
||||
# db:
|
||||
@@ -35,7 +36,7 @@ db:
|
||||
# redis:
|
||||
# host: localhost
|
||||
# port: 6379
|
||||
# password: 111049
|
||||
# password:
|
||||
# db: 0
|
||||
log:
|
||||
# 日志等级, debug, info, warn, error
|
||||
@@ -56,4 +57,4 @@ log:
|
||||
# compress: true
|
||||
# 资源密码aes加密key
|
||||
aes:
|
||||
key: 1111111111111111
|
||||
key: # 需设置16/24/32位AES密钥
|
||||
@@ -5,7 +5,7 @@ go 1.26
|
||||
require (
|
||||
gitee.com/chunanyong/dm v1.8.21
|
||||
gitee.com/liuzongyang/libpq v1.10.11
|
||||
github.com/cloudwego/eino v0.8.13
|
||||
github.com/cloudwego/eino v0.9.0
|
||||
github.com/cloudwego/eino-ext/components/model/openai v0.1.13
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/docker/go-connections v0.7.0
|
||||
@@ -39,7 +39,7 @@ require (
|
||||
github.com/twmb/franz-go v1.20.7
|
||||
github.com/twmb/franz-go/pkg/kadm v1.17.2
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // mongo
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/text v0.37.0
|
||||
@@ -212,8 +212,8 @@ require (
|
||||
golang.org/x/arch v0.22.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect
|
||||
golang.org/x/image v0.31.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/sys v0.44.0 // indirect
|
||||
golang.org/x/net v0.54.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mayfly-go/internal/db/api/form"
|
||||
"mayfly-go/internal/db/api/vo"
|
||||
"mayfly-go/internal/db/application"
|
||||
@@ -180,8 +183,12 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
|
||||
body := rc.GetRequest().Body
|
||||
defer body.Close()
|
||||
|
||||
// 支持 .zip 文件:如果是 zip 格式则解压后读取第一个文件内容
|
||||
reader, err := d.getSqlReader(body, filename)
|
||||
biz.ErrIsNilAppendErr(err, "failed to read sql file: %s")
|
||||
|
||||
biz.ErrIsNil(d.dbSqlExecApp.ExecReader(rc.MetaCtx, &dto.SqlReaderExec{
|
||||
Reader: body,
|
||||
Reader: reader,
|
||||
Filename: filename,
|
||||
DbConn: dbConn,
|
||||
ClientId: clientId,
|
||||
@@ -189,6 +196,41 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
|
||||
}))
|
||||
}
|
||||
|
||||
// getSqlReader 如果文件名是 .zip 结尾,则解压并返回第一个文件内容;否则直接返回原 reader
|
||||
func (d *Db) getSqlReader(body io.Reader, filename string) (io.Reader, error) {
|
||||
if !strings.HasSuffix(strings.ToLower(filename), ".zip") {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// 限制10MB避免解压过大文件
|
||||
data, err := io.ReadAll(io.LimitReader(body, 10*1024*1024))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read zip file error: %w", err)
|
||||
}
|
||||
|
||||
zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid zip file: %w", err)
|
||||
}
|
||||
|
||||
for _, f := range zr.File {
|
||||
if !f.FileInfo().IsDir() {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open zip entry error: %w", err)
|
||||
}
|
||||
content, err := io.ReadAll(rc)
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read zip entry error: %w", err)
|
||||
}
|
||||
return bytes.NewReader(content), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("zip file is empty")
|
||||
}
|
||||
|
||||
// 数据库dump
|
||||
func (d *Db) DumpSql(rc *req.Ctx) {
|
||||
dbId := getDbId(rc)
|
||||
|
||||
@@ -66,17 +66,14 @@ func (d *Image) ImageRemove(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (d *Image) ImageLoad(rc *req.Ctx) {
|
||||
fileheader, err := rc.FormFile("file")
|
||||
biz.ErrIsNilAppendErr(err, "read form file error: %s")
|
||||
|
||||
file, err := fileheader.Open()
|
||||
biz.ErrIsNil(err)
|
||||
defer file.Close()
|
||||
// 从 body 直接读取文件流
|
||||
body := rc.GetRequest().Body
|
||||
defer body.Close()
|
||||
|
||||
cli := GetCli(rc)
|
||||
rc.ReqParam = cli.Server
|
||||
|
||||
resp, err := cli.DockerClient.ImageLoad(rc.MetaCtx, file)
|
||||
resp, err := cli.DockerClient.ImageLoad(rc.MetaCtx, body)
|
||||
biz.ErrIsNil(err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package req
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"time"
|
||||
|
||||
@@ -64,6 +65,9 @@ func ParseToken(tokenStr string) (uint64, string, error) {
|
||||
|
||||
// Parse token
|
||||
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (any, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(jwtConf.Key), nil
|
||||
})
|
||||
if err != nil || token == nil {
|
||||
|
||||
Reference in New Issue
Block a user