mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
626 lines
20 KiB
626 lines
20 KiB
<template> |
|
<v-container fluid class="ph-no-capture project-container ma-0 pa-0" style="position: relative"> |
|
<v-tabs |
|
ref="projectTabs" |
|
v-model="activeTab" |
|
dark |
|
background-color="primary" |
|
height="40" |
|
class="project-tabs nc-project-tabs" |
|
color="" |
|
next-icon="mdi-arrow-right-bold-box-outline" |
|
prev-icon="mdi-arrow-left-bold-box-outline" |
|
show-arrows |
|
:class="{ 'dark-them': $store.state.settings.darkTheme }" |
|
> |
|
<v-tabs-slider color="" /> |
|
|
|
<v-tab |
|
v-for="(tab, index) in tabs" |
|
:key="`${pid}||${(tab._nodes && tab._nodes).type || ''}||${(tab._nodes && tab._nodes.dbAlias) || ''}||${ |
|
tab.name |
|
}`" |
|
class="divider project-tab xc-border-right" |
|
:title="tab.name" |
|
:href="`#${(tab._nodes && tab._nodes).type || ''}||${(tab._nodes && tab._nodes.dbAlias) || ''}||${tab.name}`" |
|
@change="tabActivated(tab)" |
|
> |
|
<v-icon v-if="treeViewIcons[tab._nodes.type]" icon :small="true"> |
|
{{ treeViewIcons[tab._nodes.type].openIcon }} |
|
</v-icon> |
|
<span |
|
class="flex-grow-1 caption font-weight-bold text-capitalize mx-2" |
|
style="white-space: nowrap; overflow: hidden; max-width: 140px; text-overflow: ellipsis" |
|
>{{ tab.name }}</span |
|
> |
|
<v-icon icon :small="true" @click="removeTab(index)"> mdi-close </v-icon> |
|
</v-tab> |
|
|
|
<v-tabs-items :value="activeTab"> |
|
<v-tab-item |
|
v-for="(tab, index) in tabs" |
|
:key="`${pid}||${(tab._nodes && tab._nodes).type || ''}||${(tab._nodes && tab._nodes.dbAlias) || ''}||${ |
|
tab.name |
|
}`" |
|
class="nc-main-tab-item" |
|
:value="`${(tab._nodes && tab._nodes.type) || ''}||${(tab._nodes && tab._nodes.dbAlias) || ''}||${tab.name}`" |
|
:transition="false" |
|
style="height: 100%" |
|
:reverse-transition="false" |
|
> |
|
<div v-if="tab._nodes.type === 'table'" style="height: 100%"> |
|
<TableView |
|
:ref="'tabs' + index" |
|
:is-active=" |
|
activeTab === |
|
`${(tab._nodes && tab._nodes).type || ''}||${(tab._nodes && tab._nodes.dbAlias) || ''}||${tab.name}` |
|
" |
|
:tab-id="`${pid}||${(tab._nodes && tab._nodes).type || ''}||${ |
|
(tab._nodes && tab._nodes.dbAlias) || '' |
|
}||${tab.name}`" |
|
:hide-log-windows.sync="hideLogWindows" |
|
:nodes="tab._nodes" |
|
/> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'view'" style="height: 100%"> |
|
<TableView |
|
:ref="'tabs' + index" |
|
:is-active=" |
|
activeTab === |
|
`${(tab._nodes && tab._nodes).type || ''}||${(tab._nodes && tab._nodes.dbAlias) || ''}||${tab.name}` |
|
" |
|
:tab-id="`${pid}||${(tab._nodes && tab._nodes).type || ''}||${ |
|
(tab._nodes && tab._nodes.dbAlias) || '' |
|
}||${tab.name}`" |
|
:hide-log-windows.sync="hideLogWindows" |
|
:nodes="tab._nodes" |
|
is-view |
|
/> |
|
<!-- </sqlLogAndOutput>--> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'function'" style="height: 100%"> |
|
<sqlLogAndOutput> |
|
<FunctionTab :ref="'tabs' + index" :nodes="tab._nodes" /> |
|
</sqlLogAndOutput> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'procedure'" style="height: 100%"> |
|
<sqlLogAndOutput> |
|
<ProcedureTab :ref="'tabs' + index" :nodes="tab._nodes" /> |
|
</sqlLogAndOutput> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'sequence'" style="height: 100%"> |
|
<sqlLogAndOutput> |
|
<SequenceTab :ref="'tabs' + index" :nodes="tab._nodes" /> |
|
</sqlLogAndOutput> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'db'" style="height: 100%"> |
|
<audit-tab :ref="'tabs' + index" class="backgroundColor" :nodes="tab._nodes" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'seedParserDir'" style="height: 100%"> |
|
<sqlLogAndOutput> |
|
<SeedTab :ref="'tabs' + index" :nodes="tab._nodes" /> |
|
</sqlLogAndOutput> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'migrationsDir'" style="height: 100%"> |
|
<audit-tab :ref="'tabs' + index" class="backgroundColor" :nodes="tab._nodes" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'apisDir'" style="height: 100%"> |
|
<ApisTab :ref="'tabs' + index" class="backgroundColor" :nodes="tab._nodes" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'apiClientDir'" style="height: 100%"> |
|
<ApiClientTab :ref="'tabs' + index" :nodes="tab._nodes" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'apiClientSwaggerDir'" style="height: 100%"> |
|
<ApiClientSwaggerTab :ref="'tabs' + index" :nodes="tab._nodes" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'sqlClientDir'" style="height: 100%"> |
|
<sqlLogAndOutput> |
|
<SqlClientTab :ref="'tabs' + index" :nodes="tab._nodes" /> |
|
</sqlLogAndOutput> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'terminal'" style="height: 100%"> |
|
<x-term :ref="'tabs' + index" style="height: 100%" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'graphqlClientDir'" style="height: 100%"> |
|
<graphql-client class="backgroundColor" style="height: 100%" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'swaggerClientDir'" style="height: 100%"> |
|
<swagger-client style="height: 100%" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'grpcClient'" style="height: 100%"> |
|
<grpc-client style="height: 100%" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'meta'" style="height: 100%"> |
|
<xc-meta class="backgroundColor" style="height: 100%" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'roles'" style="height: 100%"> |
|
<auth-tab |
|
v-if="_isUIAllowed('team-auth')" |
|
class="backgroundColor" |
|
:nodes="tab._nodes" |
|
style="height: 100%" |
|
/> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'acl'" style="height: 100%"> |
|
<global-acl class="backgroundColor" :nodes="tab._nodes" style="height: 100%" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'projectSettings'" style="height: 100%"> |
|
<project-settings |
|
v-if="_isUIAllowed('projectSettings')" |
|
class="backgroundColor" |
|
:nodes="tab._nodes" |
|
style="height: 100%" |
|
/> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'disableOrEnableModel'" style="height: 100%"> |
|
<disable-or-enable-models |
|
v-if="_isUIAllowed('project-metadata')" |
|
class="backgroundColor" |
|
:nodes="tab._nodes" |
|
style="height: 100%" |
|
/> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'cronJobs'" style="height: 100%"> |
|
<cron-jobs :nodes="tab._nodes" style="height: 100%" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'projectInfo'" style="height: 100%"> |
|
<xc-info :nodes="tab._nodes" class="h-100" /> |
|
</div> |
|
<div v-else-if="tab._nodes.type === 'appStore'" style="height: 100%"> |
|
<app-store :nodes="tab._nodes" class="backgroundColor h-100" /> |
|
</div> |
|
<div v-else style="height: 100%"> |
|
<h1>{{ tab.name }}</h1> |
|
<h1>{{ tab._nodes }}</h1> |
|
</div> |
|
</v-tab-item> |
|
</v-tabs-items> |
|
|
|
<!-- Add / Import --> |
|
<v-menu v-if="_isUIAllowed('addOrImport')" offset-y> |
|
<template #activator="{ on }"> |
|
<v-btn color="primary" style="height: 100%; padding: 5px" v-on="on"> |
|
<x-icon icon-class="add-btn" :color="['white', 'grey lighten-2']"> mdi-plus-box </x-icon> |
|
<span class="flex-grow-1 caption font-weight-bold text-capitalize mx-2"> |
|
<!-- TODO: i18n --> |
|
Add / Import |
|
</span> |
|
<v-spacer /> |
|
</v-btn> |
|
</template> |
|
<v-list class="addOrImport"> |
|
<v-list-item |
|
v-if="_isUIAllowed('addTable')" |
|
v-t="['a:table:import-from-excel']" |
|
@click="dialogCreateTableShowMethod" |
|
> |
|
<v-list-item-title> |
|
<v-icon small> mdi-table </v-icon> |
|
<span class="caption"> |
|
<!-- Add new table --> |
|
{{ $t('tooltip.addTable') }} |
|
</span> |
|
</v-list-item-title> |
|
</v-list-item> |
|
<v-divider class="my-1" /> |
|
<v-subheader class="caption" style="height: 35px"> QUICK IMPORT FROM </v-subheader> |
|
|
|
<v-list-item |
|
v-if="_isUIAllowed('airtableImport')" |
|
v-t="['a:actions:import-airtable']" |
|
@click="airtableImportModal = true" |
|
> |
|
<v-list-item-title> |
|
<v-icon small> mdi-table-large </v-icon> |
|
<span class="caption"> |
|
<!-- TODO: i18n --> |
|
Airtable |
|
</span> |
|
</v-list-item-title> |
|
</v-list-item> |
|
<v-list-item |
|
v-if="_isUIAllowed('csvQuickImport')" |
|
v-t="['a:actions:import-csv']" |
|
@click="onImportFromExcelOrCSV('csv')" |
|
> |
|
<v-list-item-title> |
|
<v-icon small> mdi-file-document-outline </v-icon> |
|
<span class="caption"> |
|
<!-- TODO: i18n --> |
|
CSV file |
|
</span> |
|
</v-list-item-title> |
|
</v-list-item> |
|
<v-list-item |
|
v-if="_isUIAllowed('jsonImport')" |
|
v-t="['a:actions:import-json']" |
|
@click="jsonImportModal = true" |
|
> |
|
<v-list-item-title> |
|
<v-icon small> mdi-code-json </v-icon> |
|
<span class="caption"> |
|
<!-- TODO: i18n --> |
|
JSON file |
|
</span> |
|
</v-list-item-title> |
|
</v-list-item> |
|
<v-list-item |
|
v-if="_isUIAllowed('excelQuickImport')" |
|
v-t="['a:actions:import-excel']" |
|
@click="onImportFromExcelOrCSV('excel')" |
|
> |
|
<v-list-item-title> |
|
<v-icon small> mdi-file-excel </v-icon> |
|
<span class="caption"> |
|
<!-- TODO: i18n --> |
|
Microsoft Excel |
|
</span> |
|
</v-list-item-title> |
|
</v-list-item> |
|
|
|
<v-divider class="my-1" /> |
|
|
|
<v-list-item |
|
v-if="_isUIAllowed('importRequest')" |
|
v-t="['e:datasource:import-request']" |
|
href="https://github.com/nocodb/nocodb/issues/2052" |
|
target="_blank" |
|
> |
|
<v-list-item-title> |
|
<v-icon small> mdi-open-in-new </v-icon> |
|
<span class="caption"> |
|
<!-- TODO: i18n --> |
|
Request a data source you need ? |
|
</span> |
|
</v-list-item-title> |
|
</v-list-item> |
|
</v-list> |
|
</v-menu> |
|
</v-tabs> |
|
<!-- Create Empty Table --> |
|
<dlg-table-create |
|
v-if="dialogCreateTableShow" |
|
v-model="dialogCreateTableShow" |
|
@create=" |
|
$emit('tableCreate', $event); |
|
dialogCreateTableShow = false; |
|
" |
|
/> |
|
<!-- Import From Excel / CSV --> |
|
<quick-import |
|
ref="quickImport" |
|
v-model="quickImportModal" |
|
:quick-import-type="quickImportType" |
|
hide-label |
|
@closeModal="quickImportModal = false" |
|
/> |
|
|
|
<!-- Import From JSON string / file --> |
|
<json-import v-model="jsonImportModal" hide-label @closeModal="jsonImportModal = false" /> |
|
|
|
<import-from-airtable v-if="airtableImportModal" v-model="airtableImportModal" /> |
|
</v-container> |
|
</template> |
|
|
|
<script> |
|
import { mapGetters, mapMutations } from 'vuex'; |
|
import treeViewIcons from '../helpers/treeViewIcons'; |
|
import TableView from './project/Table'; |
|
import FunctionTab from './project/Function'; |
|
import ProcedureTab from './project/Procedure'; |
|
import SequenceTab from './project/Sequence'; |
|
import SeedTab from './project/Seed'; |
|
import SqlClientTab from './project/SqlClient'; |
|
import ApisTab from './project/Apis'; |
|
import ApiClientTab from './project/ApiClientOld'; |
|
import sqlLogAndOutput from './project/SqlLogAndOutput'; |
|
import graphqlClient from './project/GraphqlClient'; |
|
import xTerm from './XTerm'; |
|
|
|
import ApiClientSwaggerTab from './project/ApiClientSwagger'; |
|
import XcMeta from './project/settings/XcMeta'; |
|
import XcInfo from './project/XcInfo'; |
|
import SwaggerClient from '~/components/project/SwaggerClient'; |
|
import DlgTableCreate from '~/components/utils/DlgTableCreate'; |
|
import AppStore from '~/components/project/AppStore'; |
|
import AuthTab from '~/components/AuthTab'; |
|
import CronJobs from '~/components/project/CronJobs'; |
|
import DisableOrEnableModels from '~/components/project/projectMetadata/DisableOrEnableModels'; |
|
import ProjectSettings from '~/components/project/ProjectSettings'; |
|
import GrpcClient from '~/components/project/GrpcClient'; |
|
import GlobalAcl from '~/components/GlobalAcl'; |
|
import AuditTab from '~/components/project/AuditTab'; |
|
import QuickImport from '~/components/import/QuickImport'; |
|
import ImportFromAirtable from '~/components/import/ImportFromAirtable'; |
|
import JsonImport from '~/components/import/JSONImport'; |
|
|
|
export default { |
|
components: { |
|
JsonImport, |
|
ImportFromAirtable, |
|
SwaggerClient, |
|
// Screensaver, |
|
DlgTableCreate, |
|
AuditTab, |
|
AppStore, |
|
XcInfo, |
|
AuthTab, |
|
CronJobs, |
|
DisableOrEnableModels, |
|
// CreateOrEditProject, |
|
ProjectSettings, |
|
GrpcClient, |
|
GlobalAcl, |
|
// Roles, |
|
XcMeta, |
|
ApiClientSwaggerTab, |
|
TableView, |
|
FunctionTab, |
|
ProcedureTab, |
|
ApisTab, |
|
SqlClientTab, |
|
ApiClientTab, |
|
SeedTab, |
|
SequenceTab, |
|
sqlLogAndOutput, |
|
xTerm, |
|
graphqlClient, |
|
QuickImport, |
|
}, |
|
data() { |
|
return { |
|
dragOver: false, |
|
dialogCreateTableShow: false, |
|
test: '', |
|
treeViewIcons, |
|
hideLogWindows: false, |
|
showScreensaver: false, |
|
quickImportModal: false, |
|
quickImportType: '', |
|
airtableImportModal: false, |
|
jsonImportModal: false, |
|
}; |
|
}, |
|
methods: { |
|
dialogCreateTableShowMethod() { |
|
this.dialogCreateTableShow = true; |
|
this.$e('c:table:create:navbar'); |
|
}, |
|
checkInactiveState() { |
|
let position = 0; |
|
let idleTime = 0; |
|
// Increment the idle time counter every minute. |
|
let idleInterval = setInterval(timerIncrement, 1000); |
|
|
|
const self = this; |
|
// Zero the idle timer on mouse movement. |
|
document.addEventListener('mousemove', e => { |
|
self.showScreensaver = false; |
|
idleTime = 0; |
|
clearInterval(idleInterval); |
|
idleInterval = setInterval(timerIncrement, 1000); |
|
}); |
|
document.addEventListener('keypress', e => { |
|
self.showScreensaver = false; |
|
idleTime = 0; |
|
clearInterval(idleInterval); |
|
idleInterval = setInterval(timerIncrement, 1000); |
|
}); |
|
|
|
function timerIncrement() { |
|
idleTime = idleTime + 1; |
|
if (idleTime > 120) { |
|
const title = document.title; |
|
|
|
function scrolltitle() { |
|
document.title = title + Array(position).fill(' .').join(''); |
|
position = ++position % 3; |
|
if (self.showScreensaver) { |
|
window.setTimeout(scrolltitle, 400); |
|
} else { |
|
document.title = title; |
|
} |
|
} |
|
|
|
self.showScreensaver = self.$store.state.settings.screensaver; |
|
scrolltitle(); |
|
clearInterval(idleInterval); |
|
} |
|
} |
|
}, |
|
async handleKeyDown(event) { |
|
const activeTabEleKey = `tabs${this.activeTab}`; |
|
let isHandled = false; |
|
|
|
if ( |
|
this.$refs[activeTabEleKey] && |
|
this.$refs[activeTabEleKey][0] && |
|
this.$refs[activeTabEleKey][0].handleKeyDown |
|
) { |
|
isHandled = await this.$refs[activeTabEleKey][0].handleKeyDown(event); |
|
} |
|
if (!isHandled) { |
|
switch ([this._isMac ? event.metaKey : event.ctrlKey, event.key].join('_')) { |
|
case 'true_w': |
|
this.removeTab(this.activeTab); |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
break; |
|
} |
|
} |
|
}, |
|
...mapMutations({ |
|
setActiveTab: 'tabs/active', |
|
removeTab: 'tabs/remove', |
|
updateActiveTabx: 'tabs/activeTabCtx', |
|
}), |
|
tabActivated(tab) {}, |
|
onImportFromExcelOrCSV(quickImportType) { |
|
this.quickImportModal = true; |
|
this.quickImportType = quickImportType; |
|
}, |
|
onAirtableImport() { |
|
this.airtableImportModal = true; |
|
}, |
|
}, |
|
computed: { |
|
...mapGetters({ tabs: 'tabs/list', activeTabCtx: 'tabs/activeTabCtx' }), |
|
pid() { |
|
return this.$route.params.project_id; |
|
}, |
|
activeTab: { |
|
set(tab) { |
|
if (!tab) { |
|
return this.$router.push({ |
|
query: {}, |
|
}); |
|
} |
|
const [type, dbalias, name] = tab.split('||'); |
|
this.$router.push({ |
|
query: { |
|
...this.$route.query, |
|
type, |
|
dbalias, |
|
name, |
|
}, |
|
}); |
|
}, |
|
get() { |
|
return [this.$route.query.type, this.$route.query.dbalias, this.$route.query.name].join('||'); |
|
}, |
|
}, |
|
}, |
|
|
|
beforeCreated() {}, |
|
watch: {}, |
|
created() { |
|
document.addEventListener('keydown', this.handleKeyDown); |
|
/** |
|
* Listening for tab change so that we can hide/show projectlogs based on tab |
|
*/ |
|
}, |
|
mounted() { |
|
if (this.$route && this.$route.query && this.$route.query.excelUrl) { |
|
this.quickImportModal = true; |
|
} |
|
}, |
|
beforeDestroy() {}, |
|
destroyed() { |
|
document.removeEventListener('keydown', this.handleKeyDown); |
|
}, |
|
directives: {}, |
|
validate({ params }) { |
|
return true; |
|
}, |
|
head() { |
|
return {}; |
|
}, |
|
props: {}, |
|
}; |
|
</script> |
|
|
|
<style scoped> |
|
/deep/ .project-tabs .v-tabs-bar { |
|
max-height: 30px; |
|
} |
|
|
|
/deep/ .project-tabs > .v-tabs-bar { |
|
max-height: 30px; |
|
} |
|
/deep/ .v-window__container .v-window-item { |
|
height: 100%; |
|
} |
|
|
|
/deep/ .project-tabs .v-tab.project-tab { |
|
text-transform: capitalize; |
|
border-top-left-radius: 3px; |
|
border-top-right-radius: 3px; |
|
background: #0002; |
|
margin: 0px 1px 0 1px; |
|
color: white !important; |
|
} |
|
|
|
/deep/ .project-tabs .v-tab.v-tab--active.project-tab { |
|
background-color: white !important; |
|
color: rgba(51, 51, 51, 1) !important; |
|
} |
|
|
|
/deep/ .project-tabs.dark-them .v-tab.v-tab--active.project-tab { |
|
background-color: #272727 !important; |
|
color: white !important; |
|
} |
|
|
|
/deep/ .project-tabs > div > div > div > div > .v-tabs-slider { |
|
color: transparent !important; |
|
} |
|
|
|
/deep/ .project-tabs .v-btn { |
|
text-transform: capitalize; |
|
} |
|
|
|
.powered-by .powered-by-close { |
|
opacity: 0; |
|
transition: 0.4s opacity; |
|
} |
|
|
|
.powered-by a { |
|
transition: 0.1s font-weight; |
|
} |
|
|
|
.powered-by:hover .powered-by-close { |
|
opacity: 1; |
|
} |
|
|
|
.powered-by:hover a { |
|
font-weight: bold; |
|
} |
|
|
|
.addOrImport { |
|
min-width: 200px; |
|
} |
|
|
|
.addOrImport .v-list-item { |
|
min-height: 35px; |
|
} |
|
|
|
/deep/ .add-btn { |
|
margin-left: 5px; |
|
} |
|
|
|
/deep/ .screensaver.body { |
|
position: absolute; |
|
} |
|
|
|
/deep/ .project-tab:first-of-type { |
|
margin-left: 0 !important; |
|
} |
|
|
|
/deep/ .nc-main-tab-item:not(.v-window-item--active) { |
|
display: none; |
|
} |
|
</style> |
|
<!-- |
|
/** |
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
|
* |
|
* @author Naveen MR <oof1lab@gmail.com> |
|
* @author Pranav C Balan <pranavxc@gmail.com> |
|
* @author Wing-Kam Wong <wingkwong.code@gmail.com> |
|
* |
|
* @license GNU AGPL version 3 or any later version |
|
* |
|
* This program is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU Affero General Public License as |
|
* published by the Free Software Foundation, either version 3 of the |
|
* License, or (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU Affero General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Affero General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
* |
|
*/ |
|
-->
|
|
|