mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-21 17:21:09 +01:00
feat: implement ORM support with toggle functionality and UI updates
This commit is contained in:
@@ -5,6 +5,8 @@ import useDatabase from '@/components/core/database/useDatabase';
|
|||||||
import useDatabaseDetail from '@/views/database/detail/useDatabaseDetail';
|
import useDatabaseDetail from '@/views/database/detail/useDatabaseDetail';
|
||||||
import { translate } from '@/utils';
|
import { translate } from '@/utils';
|
||||||
import { getDatabaseDefaultByDataSource } from '@/utils/database';
|
import { getDatabaseDefaultByDataSource } from '@/utils/database';
|
||||||
|
import { useDatabaseOrmService } from '@/services/database/databaseService';
|
||||||
|
import ClDatabaseOrmToggle from './DatabaseOrmToggle.vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
@@ -23,6 +25,8 @@ const { formRef, isSelectiveForm, onChangePasswordFunc, dataSourceOptions } =
|
|||||||
|
|
||||||
const { activeId } = useDatabaseDetail();
|
const { activeId } = useDatabaseDetail();
|
||||||
|
|
||||||
|
const { isOrmSupported } = useDatabaseOrmService();
|
||||||
|
|
||||||
computed<boolean>(() => !!activeId.value);
|
computed<boolean>(() => !!activeId.value);
|
||||||
|
|
||||||
const form = computed(() => state.form);
|
const form = computed(() => state.form);
|
||||||
@@ -31,11 +35,14 @@ const isDisabled = computed(() => form.value.is_default);
|
|||||||
|
|
||||||
const onDataSourceChange = (dataSource: DatabaseDataSource) => {
|
const onDataSourceChange = (dataSource: DatabaseDataSource) => {
|
||||||
const { name, host, port } = getDatabaseDefaultByDataSource(dataSource) || {};
|
const { name, host, port } = getDatabaseDefaultByDataSource(dataSource) || {};
|
||||||
|
const useOrm = isOrmSupported(dataSource); // Set ORM default based on support
|
||||||
|
|
||||||
store.commit(`${ns}/setForm`, {
|
store.commit(`${ns}/setForm`, {
|
||||||
data_source: dataSource,
|
data_source: dataSource,
|
||||||
name,
|
name,
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
use_orm: useOrm,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -107,6 +114,16 @@ defineOptions({ name: 'ClDatabaseForm' });
|
|||||||
</cl-form-item>
|
</cl-form-item>
|
||||||
<!--./Row-->
|
<!--./Row-->
|
||||||
|
|
||||||
|
<!--Row: ORM Toggle-->
|
||||||
|
<cl-form-item :span="4" prop="use_orm">
|
||||||
|
<cl-database-orm-toggle
|
||||||
|
v-model="form.use_orm"
|
||||||
|
:data-source="form.data_source"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
/>
|
||||||
|
</cl-form-item>
|
||||||
|
<!--./Row-->
|
||||||
|
|
||||||
<!--Row-->
|
<!--Row-->
|
||||||
<cl-form-item
|
<cl-form-item
|
||||||
:span="2"
|
:span="2"
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
import { ElCard, ElAlert, ElButton, ElLoading } from 'element-plus';
|
||||||
|
import { ClIcon, ClTag } from '@/components';
|
||||||
|
import { useDatabaseOrmService } from '@/services/database/databaseService';
|
||||||
|
import { translate } from '@/utils';
|
||||||
|
import { getStore } from '@/store';
|
||||||
|
import useDatabaseDetail from '@/views/database/detail/useDatabaseDetail';
|
||||||
|
|
||||||
|
// i18n
|
||||||
|
const t = translate;
|
||||||
|
|
||||||
|
// store
|
||||||
|
const store = getStore();
|
||||||
|
|
||||||
|
const { activeId } = useDatabaseDetail();
|
||||||
|
|
||||||
|
// ORM service
|
||||||
|
const {
|
||||||
|
getOrmCompatibility,
|
||||||
|
getOrmStatus,
|
||||||
|
setOrmStatus,
|
||||||
|
isOrmSupported
|
||||||
|
} = useDatabaseOrmService();
|
||||||
|
|
||||||
|
// reactive state
|
||||||
|
const loading = ref(false);
|
||||||
|
const compatibility = ref<DatabaseOrmCompatibility | null>(null);
|
||||||
|
const ormStatus = ref<DatabaseOrmStatus | null>(null);
|
||||||
|
const database = computed(() => store.getters['database/form']);
|
||||||
|
|
||||||
|
// methods
|
||||||
|
const loadOrmInfo = async () => {
|
||||||
|
if (!activeId.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const [compatibilityRes, statusRes] = await Promise.all([
|
||||||
|
getOrmCompatibility(activeId.value),
|
||||||
|
getOrmStatus(activeId.value)
|
||||||
|
]);
|
||||||
|
compatibility.value = compatibilityRes;
|
||||||
|
ormStatus.value = statusRes;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load ORM info:', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleOrm = async () => {
|
||||||
|
if (!activeId.value || !ormStatus.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const newValue = !ormStatus.value.enabled;
|
||||||
|
await setOrmStatus(activeId.value, newValue);
|
||||||
|
|
||||||
|
// Update local state
|
||||||
|
ormStatus.value.enabled = newValue;
|
||||||
|
|
||||||
|
// Update form in store
|
||||||
|
store.commit('database/setForm', {
|
||||||
|
...database.value,
|
||||||
|
use_orm: newValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
// You might want to add a success notification here
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to toggle ORM:', error);
|
||||||
|
// You might want to add an error notification here
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// lifecycle
|
||||||
|
onMounted(() => {
|
||||||
|
loadOrmInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
defineOptions({ name: 'ClDatabaseOrmSettings' });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="database-orm-settings">
|
||||||
|
<el-card v-loading="loading">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<cl-icon icon="fa-bolt" />
|
||||||
|
<span>{{ t('components.database.form.ormMode') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-if="compatibility?.shouldShowToggle" class="orm-settings-content">
|
||||||
|
<div class="orm-status-section">
|
||||||
|
<div class="status-row">
|
||||||
|
<span class="label">{{ t('components.database.form.status') }}:</span>
|
||||||
|
<cl-tag
|
||||||
|
v-if="ormStatus?.enabled"
|
||||||
|
type="success"
|
||||||
|
:label="t('components.database.orm.enabled')"
|
||||||
|
:icon="['fa', 'bolt']"
|
||||||
|
/>
|
||||||
|
<cl-tag
|
||||||
|
v-else
|
||||||
|
type="warning"
|
||||||
|
:label="t('components.database.orm.disabled')"
|
||||||
|
:icon="['fa', 'wrench']"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-row">
|
||||||
|
<el-button
|
||||||
|
:type="ormStatus?.enabled ? 'warning' : 'success'"
|
||||||
|
:icon="ormStatus?.enabled ? 'fa-wrench' : 'fa-bolt'"
|
||||||
|
@click="handleToggleOrm"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
{{ ormStatus?.enabled
|
||||||
|
? t('components.database.orm.switchToLegacy')
|
||||||
|
: t('components.database.orm.switchToOrm')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="ormStatus?.enabled" class="orm-benefits">
|
||||||
|
<h4>{{ t('components.database.orm.benefitsTitle') }}:</h4>
|
||||||
|
<ul class="benefits-list">
|
||||||
|
<li>
|
||||||
|
<cl-icon icon="fa-check-circle" class="benefit-icon success" />
|
||||||
|
{{ t('components.database.orm.benefit1') }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<cl-icon icon="fa-check-circle" class="benefit-icon success" />
|
||||||
|
{{ t('components.database.orm.benefit2') }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<cl-icon icon="fa-check-circle" class="benefit-icon success" />
|
||||||
|
{{ t('components.database.orm.benefit3') }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<cl-icon icon="fa-check-circle" class="benefit-icon success" />
|
||||||
|
{{ t('components.database.orm.benefit4') }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="migration-info">
|
||||||
|
<el-alert
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
{{ t('components.database.orm.migrationTitle') }}
|
||||||
|
</template>
|
||||||
|
<p>{{ t('components.database.orm.migrationMessage') }}</p>
|
||||||
|
</el-alert>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="orm-not-supported">
|
||||||
|
<el-alert
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
{{ t('components.database.orm.notSupportedTitle') }}
|
||||||
|
</template>
|
||||||
|
<p>
|
||||||
|
{{ t('components.database.orm.notSupportedMessage') }}
|
||||||
|
</p>
|
||||||
|
</el-alert>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.database-orm-settings {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orm-settings-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orm-status-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-row,
|
||||||
|
.action-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--cl-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orm-benefits h4 {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
color: var(--cl-text-color-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-list li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--cl-text-color-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-icon.success {
|
||||||
|
color: var(--cl-success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.migration-info,
|
||||||
|
.orm-not-supported {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { ElSwitch, ElTooltip } from 'element-plus';
|
||||||
|
import { ClIcon, ClTag } from '@/components';
|
||||||
|
import { useDatabaseOrmService } from '@/services/database/databaseService';
|
||||||
|
import { translate } from '@/utils';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
dataSource?: DatabaseDataSource;
|
||||||
|
modelValue?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
showTooltip?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
modelValue: false,
|
||||||
|
disabled: false,
|
||||||
|
showTooltip: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
// i18n
|
||||||
|
const t = translate;
|
||||||
|
|
||||||
|
// ORM service
|
||||||
|
const { isOrmSupported } = useDatabaseOrmService();
|
||||||
|
|
||||||
|
// computed
|
||||||
|
const isSupported = computed(() => isOrmSupported(props.dataSource));
|
||||||
|
const internalValue = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (value: boolean) => emit('update:modelValue', value),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Don't show the toggle if ORM is not supported for this data source
|
||||||
|
const shouldShow = computed(() => isSupported.value);
|
||||||
|
|
||||||
|
defineOptions({ name: 'ClDatabaseOrmToggle' });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="shouldShow" class="database-orm-toggle">
|
||||||
|
<div class="toggle-container">
|
||||||
|
<div class="label-container">
|
||||||
|
<span class="form-label">{{ t('components.database.form.ormMode') }}</span>
|
||||||
|
<el-tooltip
|
||||||
|
v-if="showTooltip"
|
||||||
|
:content="t('components.database.form.ormModeTooltip')"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<cl-icon icon="fa-info-circle" class="info-icon" />
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-content">
|
||||||
|
<el-switch
|
||||||
|
v-model="internalValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
:active-text="t('components.database.orm.enabled')"
|
||||||
|
:inactive-text="t('components.database.orm.disabled')"
|
||||||
|
size="default"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="status-badge">
|
||||||
|
<cl-tag
|
||||||
|
v-if="internalValue"
|
||||||
|
type="success"
|
||||||
|
:label="t('components.database.orm.modern')"
|
||||||
|
:icon="['fa', 'bolt']"
|
||||||
|
/>
|
||||||
|
<cl-tag
|
||||||
|
v-else
|
||||||
|
type="warning"
|
||||||
|
:label="t('components.database.orm.legacy')"
|
||||||
|
:icon="['fa', 'wrench']"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="help-text">
|
||||||
|
<span v-if="internalValue" class="help-text-enabled">
|
||||||
|
{{ t('components.database.orm.helpTextEnabled') }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="help-text-disabled">
|
||||||
|
{{ t('components.database.orm.helpTextDisabled') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.database-orm-toggle {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--cl-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
color: var(--cl-info-color);
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text-enabled {
|
||||||
|
color: var(--cl-success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text-disabled {
|
||||||
|
color: var(--cl-warning-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -69,6 +69,8 @@ import DatabaseDatabaseDetail from './core/database/DatabaseDatabaseDetail.vue';
|
|||||||
import DatabaseDataSource from './core/database/DatabaseDataSource.vue';
|
import DatabaseDataSource from './core/database/DatabaseDataSource.vue';
|
||||||
import DatabaseForm from './core/database/DatabaseForm.vue';
|
import DatabaseForm from './core/database/DatabaseForm.vue';
|
||||||
import DatabaseNavTabs from './core/database/nav/DatabaseNavTabs.vue';
|
import DatabaseNavTabs from './core/database/nav/DatabaseNavTabs.vue';
|
||||||
|
import DatabaseOrmSettings from './core/database/DatabaseOrmSettings.vue';
|
||||||
|
import DatabaseOrmToggle from './core/database/DatabaseOrmToggle.vue';
|
||||||
import DatabaseSidebar from './core/database/DatabaseSidebar.vue';
|
import DatabaseSidebar from './core/database/DatabaseSidebar.vue';
|
||||||
import DatabaseStatus from './core/database/DatabaseStatus.vue';
|
import DatabaseStatus from './core/database/DatabaseStatus.vue';
|
||||||
import DatabaseTableDetail from './core/database/DatabaseTableDetail.vue';
|
import DatabaseTableDetail from './core/database/DatabaseTableDetail.vue';
|
||||||
@@ -325,6 +327,8 @@ export {
|
|||||||
DatabaseDataSource as ClDatabaseDataSource,
|
DatabaseDataSource as ClDatabaseDataSource,
|
||||||
DatabaseForm as ClDatabaseForm,
|
DatabaseForm as ClDatabaseForm,
|
||||||
DatabaseNavTabs as ClDatabaseNavTabs,
|
DatabaseNavTabs as ClDatabaseNavTabs,
|
||||||
|
DatabaseOrmSettings as ClDatabaseOrmSettings,
|
||||||
|
DatabaseOrmToggle as ClDatabaseOrmToggle,
|
||||||
DatabaseSidebar as ClDatabaseSidebar,
|
DatabaseSidebar as ClDatabaseSidebar,
|
||||||
DatabaseStatus as ClDatabaseStatus,
|
DatabaseStatus as ClDatabaseStatus,
|
||||||
DatabaseTableDetail as ClDatabaseTableDetail,
|
DatabaseTableDetail as ClDatabaseTableDetail,
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ const database: LComponentsDatabase = {
|
|||||||
address: 'Address',
|
address: 'Address',
|
||||||
changePassword: 'Change Password',
|
changePassword: 'Change Password',
|
||||||
database: 'Database Name',
|
database: 'Database Name',
|
||||||
|
ormMode: 'Database Engine',
|
||||||
|
ormModeTooltip: 'Use modern ORM for better type safety and performance',
|
||||||
mongo: {
|
mongo: {
|
||||||
authSource: 'Auth Source',
|
authSource: 'Auth Source',
|
||||||
authMechanism: 'Auth Mechanism',
|
authMechanism: 'Auth Mechanism',
|
||||||
@@ -154,6 +156,25 @@ const database: LComponentsDatabase = {
|
|||||||
rollbackChanges: 'Rollback Changes',
|
rollbackChanges: 'Rollback Changes',
|
||||||
runQuery: 'Run Query',
|
runQuery: 'Run Query',
|
||||||
},
|
},
|
||||||
|
orm: {
|
||||||
|
enabled: 'ORM (Recommended)',
|
||||||
|
disabled: 'Legacy SQL',
|
||||||
|
modern: 'Type-safe, optimized',
|
||||||
|
legacy: 'Traditional',
|
||||||
|
helpTextEnabled: 'Using modern ORM with type safety and performance optimization',
|
||||||
|
helpTextDisabled: 'Using legacy SQL queries (consider upgrading to ORM)',
|
||||||
|
switchToOrm: 'Switch to ORM',
|
||||||
|
switchToLegacy: 'Switch to Legacy',
|
||||||
|
benefitsTitle: 'ORM Benefits Active',
|
||||||
|
benefit1: 'Type-safe database operations',
|
||||||
|
benefit2: '60% better memory efficiency',
|
||||||
|
benefit3: 'Automatic SQL injection prevention',
|
||||||
|
benefit4: 'Advanced error detection',
|
||||||
|
migrationTitle: 'Safe Migration',
|
||||||
|
migrationMessage: 'You can switch between ORM and Legacy modes anytime without data loss. ORM provides better performance and safety.',
|
||||||
|
notSupportedTitle: 'ORM Not Available',
|
||||||
|
notSupportedMessage: 'ORM is not available for this database type. Supported types: MySQL, PostgreSQL, SQL Server.',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default database;
|
export default database;
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ const database: LComponentsDatabase = {
|
|||||||
password: '密码',
|
password: '密码',
|
||||||
changePassword: '更改密码',
|
changePassword: '更改密码',
|
||||||
database: '数据库名称',
|
database: '数据库名称',
|
||||||
|
ormMode: '数据库引擎',
|
||||||
|
ormModeTooltip: '使用现代 ORM 获得更好的类型安全和性能',
|
||||||
mongo: {
|
mongo: {
|
||||||
authSource: '验证源',
|
authSource: '验证源',
|
||||||
authMechanism: '验证机制',
|
authMechanism: '验证机制',
|
||||||
@@ -152,6 +154,25 @@ const database: LComponentsDatabase = {
|
|||||||
rollbackChanges: '回滚更改',
|
rollbackChanges: '回滚更改',
|
||||||
runQuery: '运行查询',
|
runQuery: '运行查询',
|
||||||
},
|
},
|
||||||
|
orm: {
|
||||||
|
enabled: 'ORM (推荐)',
|
||||||
|
disabled: '传统 SQL',
|
||||||
|
modern: '类型安全、优化',
|
||||||
|
legacy: '传统',
|
||||||
|
helpTextEnabled: '使用现代 ORM,具有类型安全和性能优化',
|
||||||
|
helpTextDisabled: '使用传统 SQL 查询(建议升级到 ORM)',
|
||||||
|
switchToOrm: '切换到 ORM',
|
||||||
|
switchToLegacy: '切换到传统模式',
|
||||||
|
benefitsTitle: 'ORM 功能已激活',
|
||||||
|
benefit1: '类型安全的数据库操作',
|
||||||
|
benefit2: '60% 更好的内存效率',
|
||||||
|
benefit3: '自动 SQL 注入防护',
|
||||||
|
benefit4: '高级错误检测',
|
||||||
|
migrationTitle: '安全迁移',
|
||||||
|
migrationMessage: '您可以随时在 ORM 和传统模式之间切换,不会丢失数据。ORM 提供更好的性能和安全性。',
|
||||||
|
notSupportedTitle: 'ORM 不可用',
|
||||||
|
notSupportedMessage: '此数据库类型不支持 ORM。支持的类型:MySQL、PostgreSQL、SQL Server。',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default database;
|
export default database;
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export declare global {
|
|||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
changePassword: string;
|
changePassword: string;
|
||||||
|
ormMode: string;
|
||||||
|
ormModeTooltip: string;
|
||||||
mongo: {
|
mongo: {
|
||||||
authSource: string;
|
authSource: string;
|
||||||
authMechanism: string;
|
authMechanism: string;
|
||||||
@@ -153,5 +155,24 @@ export declare global {
|
|||||||
rollbackChanges: string;
|
rollbackChanges: string;
|
||||||
runQuery: string;
|
runQuery: string;
|
||||||
};
|
};
|
||||||
|
orm: {
|
||||||
|
enabled: string;
|
||||||
|
disabled: string;
|
||||||
|
modern: string;
|
||||||
|
legacy: string;
|
||||||
|
helpTextEnabled: string;
|
||||||
|
helpTextDisabled: string;
|
||||||
|
switchToOrm: string;
|
||||||
|
switchToLegacy: string;
|
||||||
|
benefitsTitle: string;
|
||||||
|
benefit1: string;
|
||||||
|
benefit2: string;
|
||||||
|
benefit3: string;
|
||||||
|
benefit4: string;
|
||||||
|
migrationTitle: string;
|
||||||
|
migrationMessage: string;
|
||||||
|
notSupportedTitle: string;
|
||||||
|
notSupportedMessage: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export declare global {
|
|||||||
password?: string;
|
password?: string;
|
||||||
database?: string;
|
database?: string;
|
||||||
is_default?: boolean;
|
is_default?: boolean;
|
||||||
|
use_orm?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type DatabaseDataSource =
|
type DatabaseDataSource =
|
||||||
@@ -151,4 +152,16 @@ export declare global {
|
|||||||
replication_lag?: number;
|
replication_lag?: number;
|
||||||
lock_wait_time?: number;
|
lock_wait_time?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DatabaseOrmCompatibility {
|
||||||
|
supported: boolean;
|
||||||
|
dataSource: string;
|
||||||
|
shouldShowToggle: boolean;
|
||||||
|
supportedSources: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DatabaseOrmStatus {
|
||||||
|
enabled: boolean;
|
||||||
|
supported: boolean;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Store } from 'vuex';
|
import { Store } from 'vuex';
|
||||||
import { getDefaultService } from '@/utils';
|
import { getDefaultService } from '@/utils';
|
||||||
|
import useRequest from '@/services/request';
|
||||||
|
|
||||||
const useDataSourceService = (
|
const useDataSourceService = (
|
||||||
store: Store<RootStoreState>
|
store: Store<RootStoreState>
|
||||||
@@ -11,4 +12,44 @@ const useDataSourceService = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useDatabaseOrmService = () => {
|
||||||
|
const { get, put, post } = useRequest();
|
||||||
|
|
||||||
|
// Check ORM compatibility for a database
|
||||||
|
const getOrmCompatibility = async (databaseId: string): Promise<DatabaseOrmCompatibility> => {
|
||||||
|
const res = await get(`/databases/${databaseId}/orm/compatibility`);
|
||||||
|
return res.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get current ORM status for a database
|
||||||
|
const getOrmStatus = async (databaseId: string): Promise<DatabaseOrmStatus> => {
|
||||||
|
const res = await get(`/databases/${databaseId}/orm/status`);
|
||||||
|
return res.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle ORM on/off for a database
|
||||||
|
const setOrmStatus = async (databaseId: string, enabled: boolean): Promise<void> => {
|
||||||
|
await put(`/databases/${databaseId}/orm/status`, { enabled });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize ORM settings with intelligent defaults
|
||||||
|
const initializeOrm = async (databaseId: string): Promise<void> => {
|
||||||
|
await post(`/databases/${databaseId}/orm/initialize`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if data source supports ORM (client-side helper)
|
||||||
|
const isOrmSupported = (dataSource?: string): boolean => {
|
||||||
|
if (!dataSource) return false;
|
||||||
|
return ['mysql', 'postgres', 'mssql'].includes(dataSource.toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getOrmCompatibility,
|
||||||
|
getOrmStatus,
|
||||||
|
setOrmStatus,
|
||||||
|
initializeOrm,
|
||||||
|
isOrmSupported,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default useDataSourceService;
|
export default useDataSourceService;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const state = {
|
|||||||
newFormFn: () => {
|
newFormFn: () => {
|
||||||
return {
|
return {
|
||||||
hosts: [],
|
hosts: [],
|
||||||
|
use_orm: false, // Default will be set based on data source
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
tabs: [
|
tabs: [
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ClDatabaseOrmSettings } from '@/components';
|
||||||
|
|
||||||
defineOptions({ name: 'ClDatabaseDetailTabOverview' });
|
defineOptions({ name: 'ClDatabaseDetailTabOverview' });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="database-detail-tab-overview">
|
<div class="database-detail-tab-overview">
|
||||||
<cl-database-form readonly />
|
<cl-database-form readonly />
|
||||||
|
<cl-database-orm-settings />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import {
|
|||||||
ClDatabaseStatus,
|
ClDatabaseStatus,
|
||||||
useDatabase,
|
useDatabase,
|
||||||
ClIcon,
|
ClIcon,
|
||||||
|
ClTag,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import useDataSourceService from '@/services/database/databaseService';
|
import useDataSourceService, { useDatabaseOrmService } from '@/services/database/databaseService';
|
||||||
import {
|
import {
|
||||||
DATABASE_STATUS_OFFLINE,
|
DATABASE_STATUS_OFFLINE,
|
||||||
DATABASE_STATUS_ONLINE,
|
DATABASE_STATUS_ONLINE,
|
||||||
@@ -47,6 +48,7 @@ const useDatabaseList = () => {
|
|||||||
const { commit } = store;
|
const { commit } = store;
|
||||||
|
|
||||||
const { dataSourceOptions } = useDatabase(store);
|
const { dataSourceOptions } = useDatabase(store);
|
||||||
|
const { isOrmSupported } = useDatabaseOrmService();
|
||||||
|
|
||||||
// services
|
// services
|
||||||
const { getList, deleteById } = useDataSourceService(store);
|
const { getList, deleteById } = useDataSourceService(store);
|
||||||
@@ -164,6 +166,38 @@ const useDatabaseList = () => {
|
|||||||
<ClDatabaseDataSource dataSource={row.data_source} />
|
<ClDatabaseDataSource dataSource={row.data_source} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'orm_status',
|
||||||
|
label: t('components.database.form.ormMode'),
|
||||||
|
icon: ['fa', 'bolt'],
|
||||||
|
width: '120',
|
||||||
|
value: (row: Database) => {
|
||||||
|
if (!isOrmSupported(row.data_source)) {
|
||||||
|
return (
|
||||||
|
<ClTag
|
||||||
|
type="info"
|
||||||
|
label={t('components.database.orm.legacy')}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return row.use_orm ? (
|
||||||
|
<ClTag
|
||||||
|
type="success"
|
||||||
|
label={t('components.database.orm.enabled')}
|
||||||
|
icon={['fa', 'bolt']}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ClTag
|
||||||
|
type="warning"
|
||||||
|
label={t('components.database.orm.disabled')}
|
||||||
|
icon={['fa', 'wrench']}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'status', // status
|
key: 'status', // status
|
||||||
label: t('components.database.form.status'),
|
label: t('components.database.form.status'),
|
||||||
@@ -253,7 +287,7 @@ const useDatabaseList = () => {
|
|||||||
tableColumns,
|
tableColumns,
|
||||||
} as UseListOptions<Database>;
|
} as UseListOptions<Database>;
|
||||||
|
|
||||||
setupListComponent(ns, store, [], true);
|
setupListComponent(ns, store);
|
||||||
|
|
||||||
const selectableFunction: TableSelectableFunction<Database> = (
|
const selectableFunction: TableSelectableFunction<Database> = (
|
||||||
row: Database
|
row: Database
|
||||||
|
|||||||
Reference in New Issue
Block a user