mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-29 16:05:34 +00:00
feat: support mongodb management (#12486)
#### What this PR does / why we need it? Refs https://github.com/1Panel-dev/1Panel/issues/7759 #### Summary of your change #### Please indicate you've done the following: - [ ] Made sure tests are passing and test coverage is added if needed. - [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/). - [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
v-model="open"
|
||||
:header="$t('menu.terminal')"
|
||||
@close="handleClose"
|
||||
:resource="database"
|
||||
:resource="resourceName"
|
||||
:autoClose="!open"
|
||||
size="large"
|
||||
:fullScreen="true"
|
||||
@@ -22,14 +22,32 @@ const open = ref(false);
|
||||
const terminalRef = ref<InstanceType<typeof Terminal> | null>(null);
|
||||
const database = ref();
|
||||
const databaseType = ref();
|
||||
const resourceName = ref();
|
||||
const command = ref('/bin/sh');
|
||||
const user = ref('');
|
||||
const containerID = ref('');
|
||||
const initCmd = ref('');
|
||||
const waitForPrompt = ref('');
|
||||
|
||||
interface DialogProps {
|
||||
databaseType: string;
|
||||
database: string;
|
||||
databaseType?: string;
|
||||
database?: string;
|
||||
resourceName?: string;
|
||||
command?: string;
|
||||
user?: string;
|
||||
containerID?: string;
|
||||
initCmd?: string;
|
||||
waitForPrompt?: string;
|
||||
}
|
||||
const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
database.value = params.database;
|
||||
databaseType.value = params.databaseType;
|
||||
database.value = params.database || '';
|
||||
databaseType.value = params.databaseType || '';
|
||||
resourceName.value = params.resourceName || params.database || params.containerID || '';
|
||||
command.value = params.command || '/bin/sh';
|
||||
user.value = params.user || '';
|
||||
containerID.value = params.containerID || '';
|
||||
initCmd.value = params.initCmd || '';
|
||||
waitForPrompt.value = params.waitForPrompt || '';
|
||||
open.value = false;
|
||||
await initTerm();
|
||||
};
|
||||
@@ -37,11 +55,15 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
const initTerm = async () => {
|
||||
open.value = true;
|
||||
await nextTick();
|
||||
const args = containerID.value
|
||||
? `source=container&containerid=${containerID.value}&user=${user.value}&command=${command.value}`
|
||||
: `source=database&databaseType=${databaseType.value}&database=${database.value}`;
|
||||
terminalRef.value!.acceptParams({
|
||||
endpoint: '/api/v2/containers/exec',
|
||||
args: `source=database&databaseType=${databaseType.value}&database=${database.value}`,
|
||||
args,
|
||||
error: '',
|
||||
initCmd: '',
|
||||
initCmd: initCmd.value,
|
||||
waitForPrompt: waitForPrompt.value,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ const terminalSocket = ref<WebSocket>();
|
||||
const heartbeatTimer = ref<NodeJS.Timer>();
|
||||
const latency = ref(0);
|
||||
const initCmd = ref('');
|
||||
const hideInitCmdEcho = ref(false);
|
||||
const initCmdEchoBuffer = ref('');
|
||||
const waitForPrompt = ref('');
|
||||
const waitForPromptBuffer = ref('');
|
||||
const aiNotice = ref({
|
||||
visible: false,
|
||||
loading: false,
|
||||
@@ -105,6 +109,7 @@ interface WsProps {
|
||||
args: string;
|
||||
error: string;
|
||||
initCmd: string;
|
||||
waitForPrompt?: string;
|
||||
}
|
||||
|
||||
interface TerminalBufferLine {
|
||||
@@ -117,6 +122,8 @@ const acceptParams = (props: WsProps) => {
|
||||
initError(props.error);
|
||||
} else {
|
||||
initCmd.value = props.initCmd || '';
|
||||
waitForPrompt.value = props.waitForPrompt || '';
|
||||
waitForPromptBuffer.value = '';
|
||||
init(props.endpoint, props.args);
|
||||
}
|
||||
});
|
||||
@@ -258,10 +265,46 @@ const initWebSocket = (endpoint_: string, args: string = '') => {
|
||||
const runRealTerminal = () => {
|
||||
webSocketReady.value = true;
|
||||
if (initCmd.value !== '') {
|
||||
hideInitCmdEcho.value = true;
|
||||
initCmdEchoBuffer.value = '';
|
||||
sendMsg(initCmd.value);
|
||||
}
|
||||
};
|
||||
|
||||
const stripInitCmdEchoLine = (message: string) => {
|
||||
if (!hideInitCmdEcho.value) {
|
||||
return message;
|
||||
}
|
||||
initCmdEchoBuffer.value += message;
|
||||
const lineBreakIndex = initCmdEchoBuffer.value.search(/\r?\n/);
|
||||
if (lineBreakIndex === -1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const lineBreakLength = initCmdEchoBuffer.value[lineBreakIndex] === '\r' ? 2 : 1;
|
||||
const remaining = initCmdEchoBuffer.value.slice(lineBreakIndex + lineBreakLength);
|
||||
hideInitCmdEcho.value = false;
|
||||
initCmdEchoBuffer.value = '';
|
||||
initCmd.value = '';
|
||||
return remaining;
|
||||
};
|
||||
|
||||
const flushPromptBuffer = (message: string) => {
|
||||
if (!waitForPrompt.value) {
|
||||
return message;
|
||||
}
|
||||
waitForPromptBuffer.value += message;
|
||||
const promptIndex = waitForPromptBuffer.value.indexOf(waitForPrompt.value);
|
||||
if (promptIndex === -1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const visible = waitForPromptBuffer.value.slice(promptIndex);
|
||||
waitForPrompt.value = '';
|
||||
waitForPromptBuffer.value = '';
|
||||
return visible;
|
||||
};
|
||||
|
||||
const onWSReceive = (message: MessageEvent) => {
|
||||
const wsMsg = JSON.parse(message.data);
|
||||
switch (wsMsg.type) {
|
||||
@@ -269,9 +312,14 @@ const onWSReceive = (message: MessageEvent) => {
|
||||
term.value.element && term.value.focus();
|
||||
if (wsMsg.data) {
|
||||
let receiveMsg = decodeBase64(wsMsg.data);
|
||||
if (initCmd.value != '') {
|
||||
receiveMsg = receiveMsg?.replace(initCmd.value.trim(), '').trim();
|
||||
initCmd.value = '';
|
||||
if (hideInitCmdEcho.value) {
|
||||
receiveMsg = stripInitCmdEchoLine(receiveMsg);
|
||||
}
|
||||
if (receiveMsg && waitForPrompt.value) {
|
||||
receiveMsg = flushPromptBuffer(receiveMsg);
|
||||
}
|
||||
if (!receiveMsg) {
|
||||
break;
|
||||
}
|
||||
term.value.write(receiveMsg);
|
||||
}
|
||||
@@ -507,9 +555,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
.ai-notice-fade-enter-active,
|
||||
.ai-notice-fade-leave-active {
|
||||
transition:
|
||||
opacity 180ms ease,
|
||||
transform 180ms ease;
|
||||
transition: opacity 180ms ease, transform 180ms ease;
|
||||
}
|
||||
|
||||
.ai-mask-fade-enter-active,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<li v-if="type === 'mysql' || type === 'mariadb' || type === 'mysql-cluster'">
|
||||
{{ $t('database.formatHelper', [remark]) }}
|
||||
</li>
|
||||
<li v-if="isDb()">{{ $t('database.supportUpType') }}</li>
|
||||
<li v-if="isDb() && type !== 'mongodb'">{{ $t('database.supportUpType') }}</li>
|
||||
<li v-if="type === 'website' || type === 'app'">
|
||||
{{ $t('website.websiteBackupWarn') }}
|
||||
</li>
|
||||
@@ -40,7 +40,7 @@
|
||||
:limit="1"
|
||||
class="float-left"
|
||||
ref="uploadRef"
|
||||
accept=".tar.gz,.sql,.gz,.zip"
|
||||
:accept="uploadAccept"
|
||||
:show-file-list="false"
|
||||
:on-exceed="handleExceed"
|
||||
:on-change="fileOnChange"
|
||||
@@ -90,7 +90,7 @@
|
||||
|
||||
<DialogPro
|
||||
v-model="recoverDialog"
|
||||
:title="name ? $t('commons.button.recover') + ' - ' + name : $t('commons.button.recover')"
|
||||
:title="title ? $t('commons.button.recover') + ' - ' + title : $t('commons.button.recover')"
|
||||
@close="handleRecoverClose"
|
||||
size="small"
|
||||
>
|
||||
@@ -134,7 +134,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { computeSize } from '@/utils/size';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { transferTimeToSecond } from '@/utils/validate';
|
||||
@@ -198,6 +198,7 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
case 'postgresql':
|
||||
case 'mysql-cluster':
|
||||
case 'postgresql-cluster':
|
||||
case 'mongodb':
|
||||
title.value = name.value + ' [ ' + detailName.value + ' ]';
|
||||
if (detailName.value) {
|
||||
baseDir.value = `${pathRes.data}/uploads/database/${type.value}/${name.value}/${detailName.value}/`;
|
||||
@@ -253,10 +254,14 @@ const beforeUpload = (fileName: string) => {
|
||||
return false;
|
||||
}
|
||||
if (isDb()) {
|
||||
const allowedExtensions = ['.sql', '.sql.gz', '.tar.gz', '.zip'];
|
||||
const allowedExtensions = type.value === 'mongodb' ? ['.gz'] : ['.sql', '.sql.gz', '.tar.gz', '.zip'];
|
||||
const isValidFile = allowedExtensions.some((ext) => itemName.endsWith(ext));
|
||||
if (!isValidFile) {
|
||||
MsgError(i18n.global.t('database.supportUpType'));
|
||||
MsgError(
|
||||
type.value === 'mongodb'
|
||||
? i18n.global.t('commons.msg.unSupportType')
|
||||
: i18n.global.t('database.supportUpType'),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -340,6 +345,7 @@ const onRecover = async (row: File.File) => {
|
||||
|
||||
const isDb = () => {
|
||||
return (
|
||||
type.value === 'mongodb' ||
|
||||
type.value === 'mysql' ||
|
||||
type.value === 'mariadb' ||
|
||||
type.value === 'postgresql' ||
|
||||
@@ -347,6 +353,9 @@ const isDb = () => {
|
||||
type.value === 'postgresql-cluster'
|
||||
);
|
||||
};
|
||||
const uploadAccept = computed(() => {
|
||||
return type.value === 'mongodb' ? '.gz' : '.tar.gz,.sql,.gz,.zip';
|
||||
});
|
||||
const uploaderFiles = ref<UploadFiles>([]);
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user