Browse Source

feat(gui-v2): add ssl file select option, show monaco editor for config edit

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/2730/head
Pranav C 2 years ago
parent
commit
313f77eefe
  1. 40
      packages/nc-gui-v2/components/monaco/index.vue
  2. 20
      packages/nc-gui-v2/components/monaco/json.vue
  3. 215
      packages/nc-gui-v2/pages/projects/index/create-external.vue
  4. 42
      packages/nc-gui-v2/utils/fileUtils.ts
  5. 110
      packages/nc-gui-v2/utils/projectCreateUtils.ts

40
packages/nc-gui-v2/components/monaco/index.vue

@ -0,0 +1,40 @@
<script setup lang="ts">
import * as monaco from 'monaco-editor'
import { onMounted } from '#imports'
const { modelValue } = defineProps<{ modelValue: any }>()
const emit = defineEmits(['update:modelValue'])
const root = ref<HTMLDivElement>()
const editor = ref<monaco.editor.IStandaloneCodeEditor>()
onMounted(() => {
if (root.value) {
editor.value = monaco.editor.create(root?.value, {
value: JSON.stringify(modelValue, null, 2),
language: 'json',
})
editor.value.onDidChangeModelContent((e) => {
try {
emit('update:modelValue', JSON.parse(editor?.value?.getValue() as string))
} catch {}
})
}
})
watch(
() => modelValue && JSON.stringify(modelValue, null, 2),
(v) => {
if (editor?.value && v) {
editor.value.setValue(v)
}
},
)
</script>
<template>
<div ref="root"></div>
</template>
<style scoped></style>

20
packages/nc-gui-v2/components/monaco/json.vue

@ -1,20 +0,0 @@
<script setup lang="ts">
import * as monaco from 'monaco-editor'
import { onMounted } from '#imports'
const root = ref<HTMLDivElement>()
onMounted(() => {
if (root.value)
monaco.editor.create(root?.value, {
value: 'console.log("Hello, world")',
language: 'json',
})
})
</script>
<template>
<div ref="root"></div>
</template>
<style scoped></style>

215
packages/nc-gui-v2/pages/projects/index/create-external.vue

@ -3,7 +3,6 @@ import { ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import { Form } from 'ant-design-vue' import { Form } from 'ant-design-vue'
import form from '../../../../nc-gui/components/project/spreadsheet/mixins/form'
import { navigateTo, useNuxtApp } from '#app' import { navigateTo, useNuxtApp } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { import {
@ -14,7 +13,8 @@ import {
projectTitleValidator, projectTitleValidator,
sslUsage, sslUsage,
} from '~/utils/projectCreateUtils' } from '~/utils/projectCreateUtils'
import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline'
import { readFile } from '~/utils/fileUtils'
const useForm = Form.useForm const useForm = Form.useForm
const name = ref('') const name = ref('')
@ -69,10 +69,6 @@ const testConnection = async () => {
...projectDatasource.value.connection, ...projectDatasource.value.connection,
database: getTestDatabaseName(projectDatasource.value), database: getTestDatabaseName(projectDatasource.value),
}, },
inflection: {
tableName: 'camelize',
columnName: 'camelize',
},
} }
const result = await $api.utils.testConnection(testConnectionConfig) const result = await $api.utils.testConnection(testConnectionConfig)
@ -90,7 +86,13 @@ const testConnection = async () => {
} }
} }
const formState = reactive<Record<string, any>>({ dataSource: { ...getDefaultConnectionConfig('mysql2') } }) const formState = reactive<Record<string, any>>({
dataSource: { ...getDefaultConnectionConfig('mysql2') },
inflection: {
tableName: 'camelize',
columnName: 'camelize',
},
})
const validators = reactive({ const validators = reactive({
'title': [ 'title': [
@ -121,12 +123,21 @@ watch(
() => formState.title, () => formState.title,
(v) => (formState.dataSource.connection.database = `${v}_noco`), (v) => (formState.dataSource.connection.database = `${v}_noco`),
) )
const caFileInput = ref<HTMLInputElement>()
const keyFileInput = ref<HTMLInputElement>()
const certFileInput = ref<HTMLInputElement>()
const onFileSelect = (key: 'ca' | 'cert' | 'key', el: HTMLInputElement) => {
readFile(el, (content) => {
formState.dataSource.connection.ssl[key] = content ?? ''
})
}
</script> </script>
<template> <template>
<a-card class="max-w-[600px] mx-auto !mt-100px" :title="$t('activity.createProject')"> <a-card class="max-w-[600px] mx-auto !mt-100px" :title="$t('activity.createProject')">
<a-form :model="formState" name="validate_other" layout="horizontal" :label-col="{ span: 8 }" :wrapper-col="{ span: 18 }"> <a-form :model="formState" name="validate_other" layout="horizontal" :label-col="{ span: 8 }" :wrapper-col="{ span: 18 }">
<a-form-item :label="$t('placeholder.projName')" v-bind="validateInfos.title"> <a-form-item :label="$t('placeholder.projName')" v-bind="validateInfos.title">
<a-input v-model:value="formState.title" size="small" /> <a-input v-model:value="formState.title" size="small" />
</a-form-item> </a-form-item>
@ -137,85 +148,115 @@ watch(
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item :label="$t('labels.hostAddress')" v-bind="validateInfos['dataSource.connection.host']">
<a-input v-model:value="formState.dataSource.connection.host" size="small" /> <!-- SQLite File -->
</a-form-item> <a-form-item
<a-form-item :label="$t('labels.port')" v-bind="validateInfos['dataSource.connection.port']"> v-if="formState.dataSource.client === 'sqlite3'"
<a-input-number v-model:value="formState.dataSource.connection.port" class="!w-full" size="small" /> :label="$t('labels.sqliteFile')"
</a-form-item> v-bind="validateInfos['dataSource.connection.host']"
<a-form-item :label="$t('labels.username')" v-bind="validateInfos['dataSource.connection.user']"> >
<a-input v-model:value="formState.dataSource.connection.user" size="small" /> <a-input v-model:value="formState.dataSource.connection.connection.filename" size="small" />
</a-form-item>
<a-form-item :label="$t('labels.password')">
<a-input-password v-model:value="formState.dataSource.connection.password" size="small" />
</a-form-item>
<a-form-item :label="$t('labels.database')" v-bind="validateInfos['dataSource.connection.database']">
<a-input
v-model:value="formState.dataSource.connection.database"
:placeholder="$t('labels.dbCreateIfNotExists')"
size="small"
/>
</a-form-item> </a-form-item>
<a-collapse ghost expand-icon-position="right">
<a-collapse-panel key="1" header="Advance options"> <template v-else>
<a-form-item :label="$t('lables.ssl')" v-bind="validateInfos['dataSource.ssl']"> <!-- Host Address -->
<a-select v-model:value="formState.dataSource.ssl" size="small" @change="onClientChange"> <a-form-item :label="$t('labels.hostAddress')" v-bind="validateInfos['dataSource.connection.host']">
<a-select-option v-for="opt in Object.keys(sslUsage)" :key="opt" :value="opt">{{ opt }}</a-select-option> <a-input v-model:value="formState.dataSource.connection.host" size="small" />
</a-select> </a-form-item>
</a-form-item>
<!-- Port Number -->
<a-form-item :label="$t('labels.dbCredentials')"> <a-form-item :label="$t('labels.port')" v-bind="validateInfos['dataSource.connection.port']">
<div class="flex gap-2"> <a-input-number v-model:value="formState.dataSource.connection.port" class="!w-full" size="small" />
<a-tooltip placement="top"> </a-form-item>
<template #title>
<span>{{ $t('tooltip.clientCert') }}</span> <!-- Username -->
</template> <a-form-item :label="$t('labels.username')" v-bind="validateInfos['dataSource.connection.user']">
<a-button size="small"> <a-input v-model:value="formState.dataSource.connection.user" size="small" />
{{ $t('labels.clientCert') }} </a-form-item>
</a-button>
</a-tooltip> <!-- Password -->
<a-tooltip placement="top"> <a-form-item :label="$t('labels.password')">
<template #title> <a-input-password v-model:value="formState.dataSource.connection.password" size="small" />
<span>{{ $t('tooltip.clientKey') }}</span> </a-form-item>
</template>
<a-button size="small"> <!-- Database -->
{{ $t('labels.clientKey') }} <a-form-item :label="$t('labels.database')" v-bind="validateInfos['dataSource.connection.database']">
</a-button> <!-- Database : create if not exists -->
</a-tooltip> <a-input
<a-tooltip placement="top"> v-model:value="formState.dataSource.connection.database"
<template #title> :placeholder="$t('labels.dbCreateIfNotExists')"
<span>{{ $t('tooltip.clientCA') }}</span> size="small"
</template> />
<a-button size="small"> </a-form-item>
{{ $t('labels.serverCA') }} <!-- Schema name -->
</a-button> <a-form-item v-if="['mssql', 'pg'].includes(formState.dataSource.client)" :label="$t('labels.schemaName')">
</a-tooltip> <a-input v-model:value="formState.dataSource.connection.searchPath[0]" size="small" />
</a-form-item>
<a-collapse ghost expand-icon-position="right">
<a-collapse-panel key="1" :header="$t('title.advancedParameters')">
<!-- todo: add in i18n -->
<a-form-item label="SSL Mode" v-bind="validateInfos['dataSource.ssl']">
<a-select v-model:value="formState.dataSource.sslUse" size="small" @change="onClientChange">
<a-select-option v-for="opt in sslUsage" :key="opt" :value="opt">{{ opt }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('labels.dbCredentials')">
<div class="flex gap-2">
<a-tooltip placement="top">
<!-- Select .cert file -->
<template #title>
<span>{{ $t('tooltip.clientCert') }}</span>
</template>
<a-button size="small" @click="certFileInput.click()">
{{ $t('labels.clientCert') }}
</a-button>
</a-tooltip>
<a-tooltip placement="top">
<!-- Select .key file -->
<template #title>
<span>{{ $t('tooltip.clientKey') }}</span>
</template>
<a-button size="small" @click="keyFileInput.click()">
{{ $t('labels.clientKey') }}
</a-button>
</a-tooltip>
<a-tooltip placement="top">
<!-- Select CA file -->
<template #title>
<span>{{ $t('tooltip.clientCA') }}</span>
</template>
<a-button size="small" @click="caFileInput.click()">
{{ $t('labels.serverCA') }}
</a-button>
</a-tooltip>
</div>
</a-form-item>
<input ref="caFileInput" type="file" class="!hidden" @change="onFileSelect('ca', caFileInput)" />
<input ref="certFileInput" type="file" class="!hidden" @change="onFileSelect('cert', certFileInput)" />
<input ref="keyFileInput" type="file" class="!hidden" @change="onFileSelect('key', keyFileInput)" />
<a-form-item :label="$t('labels.inflection.tableName')" v-bind="validateInfos['dataSource.client']">
<a-select v-model:value="formState.inflection.tableName" size="small" @change="onClientChange">
<a-select-option v-for="type in inflectionTypes" :key="type" :value="type">{{ type }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('labels.inflection.columnName')" v-bind="validateInfos['dataSource.type']">
<a-select v-model:value="formState.inflection.columnName" size="small" @change="onClientChange">
<a-select-option v-for="type in inflectionTypes" :key="type" :value="type">{{ type }}</a-select-option>
</a-select>
</a-form-item>
<div class="flex justify-end">
<a-button size="small" @click="configEditDlg = true">
<!-- Edit connection JSON -->
{{ $t('activity.editConnJson') }}
</a-button>
</div> </div>
</a-form-item> </a-collapse-panel>
</a-collapse>
</template>
<a-form-item :label="$t('labels.inflection.tableName')" v-bind="validateInfos['dataSource.client']">
<a-select v-model:value="formState.dataSource.client" size="small" @change="onClientChange">
<a-select-option v-for="client in clientTypes" :key="client.value" :value="client.value"
>{{ client.text }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('labels.inflection.columnName')" v-bind="validateInfos['dataSource.client']">
<a-select v-model:value="formState.dataSource.client" size="small" @change="onClientChange">
<a-select-option v-for="client in clientTypes" :key="client.value" :value="client.value"
>{{ client.text }}
</a-select-option>
</a-select>
</a-form-item>
<div class="flex justify-end">
<a-button size="small" @click="configEditDlg = true">
<!-- Edit connection JSON -->
{{ $t('activity.editConnJson') }}
</a-button>
</div>
</a-collapse-panel>
</a-collapse>
<a-form-item class="flex justify-center"> <a-form-item class="flex justify-center">
<div class="flex justify-center gap-2"> <div class="flex justify-center gap-2">
@ -226,8 +267,10 @@ watch(
</a-form> </a-form>
<v-dialog v-model="configEditDlg"> <v-dialog v-model="configEditDlg">
<MonacoJson v-if="configEditDlg" class="h-[320px] w-[600px]"></MonacoJson> <Monaco v-if="configEditDlg" v-model="formState" class="h-[320px] w-[600px]"></Monaco>
</v-dialog> </v-dialog>
{{ formState }}
</a-card> </a-card>
</template> </template>

42
packages/nc-gui-v2/utils/fileUtils.ts

@ -5,3 +5,45 @@ const isImage = (name: string, mimetype?: string) => {
} }
export { isImage, imageExt } export { isImage, imageExt }
// Ref : https://stackoverflow.com/a/12002275
// Tested in Mozilla Firefox browser, Chrome
export function readFile(FileElement: HTMLInputElement, CallBackFunction: (content?: any) => void) {
try {
if (!FileElement.files || !FileElement.files.length) {
return CallBackFunction()
}
const file = FileElement.files[0]
if (file) {
const reader = new FileReader()
reader.readAsText(file, 'UTF-8')
reader.onload = function (evt) {
CallBackFunction(evt.target?.result)
}
reader.onerror = function () {
CallBackFunction()
}
}
} catch (Exception) {
const fallBack = ieReadFile(FileElement.value)
// eslint-disable-next-line eqeqeq
if (fallBack != false) {
CallBackFunction(fallBack)
}
}
}
/// Reading files with Internet Explorer
function ieReadFile(filename: string) {
try {
const fso = new ActiveXObject('Scripting.FileSystemObject')
const fh = fso.OpenTextFile(filename, 1)
const contents = fh.ReadAll()
fh.Close()
return contents
} catch (Exception) {
return false
}
}

110
packages/nc-gui-v2/utils/projectCreateUtils.ts

@ -29,7 +29,7 @@ export const clientTypes = [
}, },
{ {
text: 'SQLite', text: 'SQLite',
value: 'sqlite', value: 'sqlite3',
}, },
] ]
@ -42,11 +42,11 @@ const sampleConnectionData = {
password: 'password', password: 'password',
database: '_test', database: '_test',
searchPath: ['public'], searchPath: ['public'],
// ssl: { ssl: {
// ca: '', ca: '',
// key: '', key: '',
// cert: '', cert: '',
// }, },
}, },
mysql2: { mysql2: {
host: 'localhost', host: 'localhost',
@ -54,11 +54,11 @@ const sampleConnectionData = {
user: 'root', user: 'root',
password: 'password', password: 'password',
database: '_test', database: '_test',
// ssl: { ssl: {
// ca: '', ca: '',
// key: '', key: '',
// cert: '', cert: '',
// }, },
}, },
vitess: { vitess: {
host: 'localhost', host: 'localhost',
@ -66,11 +66,11 @@ const sampleConnectionData = {
user: 'root', user: 'root',
password: 'password', password: 'password',
database: '_test', database: '_test',
// ssl: { ssl: {
// ca: '', ca: '',
// key: '', key: '',
// cert: '', cert: '',
// }, },
}, },
tidb: { tidb: {
host: 'localhost', host: 'localhost',
@ -78,11 +78,11 @@ const sampleConnectionData = {
user: 'root', user: 'root',
password: '', password: '',
database: '_test', database: '_test',
// ssl: { ssl: {
// ca: '', ca: '',
// key: '', key: '',
// cert: '', cert: '',
// }, },
}, },
yugabyte: { yugabyte: {
host: 'localhost', host: 'localhost',
@ -90,11 +90,11 @@ const sampleConnectionData = {
user: 'postgres', user: 'postgres',
password: '', password: '',
database: '_test', database: '_test',
// ssl: { ssl: {
// ca: '', ca: '',
// key: '', key: '',
// cert: '', cert: '',
// }, },
}, },
citusdb: { citusdb: {
host: 'localhost', host: 'localhost',
@ -102,11 +102,11 @@ const sampleConnectionData = {
user: 'postgres', user: 'postgres',
password: '', password: '',
database: '_test', database: '_test',
// ssl: { ssl: {
// ca: '', ca: '',
// key: '', key: '',
// cert: '', cert: '',
// }, },
}, },
cockroachdb: { cockroachdb: {
host: 'localhost', host: 'localhost',
@ -114,11 +114,11 @@ const sampleConnectionData = {
user: 'postgres', user: 'postgres',
password: '', password: '',
database: '_test', database: '_test',
// ssl: { ssl: {
// ca: '', ca: '',
// key: '', key: '',
// cert: '', cert: '',
// }, },
}, },
greenplum: { greenplum: {
host: 'localhost', host: 'localhost',
@ -126,11 +126,11 @@ const sampleConnectionData = {
user: 'postgres', user: 'postgres',
password: '', password: '',
database: '_test', database: '_test',
// ssl: { ssl: {
// ca: '', ca: '',
// key: '', key: '',
// cert: '', cert: '',
// }, },
}, },
mssql: { mssql: {
host: 'localhost', host: 'localhost',
@ -139,11 +139,11 @@ const sampleConnectionData = {
password: 'Password123.', password: 'Password123.',
database: '_test', database: '_test',
searchPath: ['dbo'], searchPath: ['dbo'],
// ssl: { ssl: {
// ca: '', ca: '',
// key: '', key: '',
// cert: '', cert: '',
// }, },
}, },
oracledb: { oracledb: {
host: 'localhost', host: 'localhost',
@ -151,11 +151,11 @@ const sampleConnectionData = {
user: 'system', user: 'system',
password: 'Oracle18', password: 'Oracle18',
database: '_test', database: '_test',
// ssl: { ssl: {
// ca: '', ca: '',
// key: '', key: '',
// cert: '', cert: '',
// }, },
}, },
sqlite3: { sqlite3: {
client: 'sqlite3', client: 'sqlite3',
@ -187,10 +187,4 @@ export const fieldRequiredValidator = {
message: 'Field is required', message: 'Field is required',
} }
export const sslUsage = { export const sslUsage = ['No', 'Preferred', 'Required', 'Required-CA', 'Required-IDENTITY']
'No': 'No',
'Preferred': 'Preferred',
'Required': 'pg',
'Required-CA': 'Required-CA',
'Required-IDENTITY': 'Required-IDENTITY',
}

Loading…
Cancel
Save