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.
706 lines
23 KiB
706 lines
23 KiB
<template> |
|
<v-container fluid class="pa-0 ma-0 nc-table-tab" style="height: 100%"> |
|
<v-alert v-if="error" type="error" class="ma-2"> |
|
{{ error }} |
|
</v-alert> |
|
<template v-else> |
|
<v-tabs |
|
v-model="active" |
|
:height="relationTabs && relationTabs.length ?38:0" |
|
class="table-tabs" |
|
:class="{'hidden-tab':!relationTabs || !relationTabs.length}" |
|
color="pink" |
|
@change="onTabChange" |
|
> |
|
<template v-if="_isUIAllowed('model')"> |
|
<v-tab v-show="false" class=""> |
|
<v-icon small> |
|
mdi-table-eye |
|
</v-icon> |
|
<span class="caption text-capitalize font-weight-bold"> Model</span> |
|
</v-tab> |
|
<v-tab-item |
|
style="height:100%" |
|
> |
|
<v-tabs |
|
color="pink" |
|
height="38" |
|
class="table-tabs" |
|
ma-0 |
|
pa-0 |
|
style="height:100%" |
|
> |
|
<v-tab ripple class=""> |
|
<v-icon small> |
|
mdi-view-column |
|
</v-icon> |
|
<span class="caption font-weight-bold text-capitalize">Columns</span> |
|
</v-tab> |
|
|
|
<v-tab-item |
|
style="height:100%" |
|
> |
|
<columnList |
|
ref="tabs0" |
|
:nodes="nodes" |
|
:new-table="newTableCopy" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
/> |
|
</v-tab-item> |
|
|
|
<v-tab ripple class="" @click="loadIndexList = true"> |
|
<v-icon small> |
|
mdi-blur |
|
</v-icon> |
|
<span class="caption font-weight-bold text-capitalize">Indexes</span> |
|
</v-tab> |
|
<v-tab-item |
|
:transition="false" |
|
style="height:100%" |
|
:reverse-transition="false" |
|
> |
|
<indexList |
|
v-if="loadIndexList" |
|
ref="tabs1" |
|
:nodes="nodes" |
|
:new-table="newTableCopy" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
/> |
|
</v-tab-item> |
|
|
|
<v-tab ripple class="" @click="loadTriggerList = true"> |
|
<v-icon small> |
|
mdi-clock-in |
|
</v-icon> |
|
<span class="caption font-weight-bold text-capitalize"> Triggers</span> |
|
</v-tab> |
|
<v-tab-item |
|
style="height:100%" |
|
> |
|
<triggerList |
|
v-if="loadTriggerList" |
|
ref="tabs2" |
|
:nodes="nodes" |
|
:new-table="newTableCopy" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
/> |
|
</v-tab-item> |
|
|
|
<!-- <v-tab ripple class="divider v-tab-border-right" @click="loadConstraintList = true">--> |
|
<!-- <v-icon small>mdi-view-column</v-icon>--> |
|
<!-- Constraints--> |
|
<!-- </v-tab>--> |
|
<!-- <v-tab-item :transition="false" >--> |
|
<!-- <constraintList--> |
|
<!-- v-if="loadConstraintList"--> |
|
<!-- :nodes="this.nodes"--> |
|
<!-- :newTable="this.newTableCopy"--> |
|
<!-- :mtdNewTableUpdate="this.mtdNewTableUpdate"--> |
|
<!-- :deleteTable="this.deleteTable"--> |
|
<!-- />--> |
|
<!-- </v-tab-item>--> |
|
|
|
<!-- </v-tabs>--> |
|
|
|
<!-- </v-tab-item> |
|
</template> |
|
<template v-if="_isUIAllowed('api')"> |
|
<v-tab class=""> |
|
<v-icon small>mdi-code-braces</v-icon> |
|
<span class="caption text-capitalize font-weight-bold"> APIs</span></v-tab> |
|
<v-tab-item>--> |
|
<!-- <v-tabs--> |
|
<!-- height="38"--> |
|
<!-- class="table-tabs"--> |
|
<!-- ma-0--> |
|
<!-- pa-0--> |
|
<!-- style="height:100%">--> |
|
|
|
<template v-if="!isMvc && !isMetaTable"> |
|
<template v-if="isRest"> |
|
<v-tab ripple class=""> |
|
<v-icon small> |
|
mdi-nodejs |
|
</v-icon> |
|
<span class="caption font-weight-bold text-capitalize">Routes</span> |
|
</v-tab> |
|
|
|
<v-tab-item :transition="false"> |
|
<logic-rest |
|
ref="tabs0" |
|
:nodes="nodes" |
|
:new-table="newTableCopy" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
/> |
|
</v-tab-item> |
|
</template> |
|
<template v-if="isGraphql"> |
|
<v-tab ripple class=""> |
|
<v-icon small> |
|
mdi-graphql |
|
</v-icon> |
|
<span class="caption font-weight-bold text-capitalize">Schema & Resolvers</span> |
|
</v-tab> |
|
|
|
<v-tab-item :transition="false"> |
|
<logic-gql |
|
ref="tabs0" |
|
:nodes="nodes" |
|
:new-table="newTableCopy" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
/> |
|
</v-tab-item> |
|
</template> |
|
<template v-if="isGrpc"> |
|
<v-tab ripple class=""> |
|
<v-icon small> |
|
mdi-nodejs |
|
</v-icon> |
|
<span class="caption font-weight-bold text-capitalize">gRPC Services</span> |
|
</v-tab> |
|
|
|
<v-tab-item :transition="false"> |
|
<logic-grpc |
|
ref="tabs0" |
|
:nodes="nodes" |
|
:new-table="newTableCopy" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
/> |
|
</v-tab-item> |
|
</template> |
|
</template> |
|
|
|
<template v-if="!isMetaTable"> |
|
<v-tab ripple class=""> |
|
<v-icon small> |
|
mdi-shield-edit-outline |
|
</v-icon> |
|
<span class="caption font-weight-bold text-capitalize"> ACL</span> |
|
</v-tab> |
|
<v-tab-item :transition="false"> |
|
<table-acl |
|
:nodes="nodes" |
|
:new-table="newTableCopy" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
/> |
|
</v-tab-item> |
|
<!-- <template v-if="!isNoApis"> |
|
<v-tab ripple class="divider v-tab-border-right" > |
|
<v-icon small>mdi-shield-edit-outline</v-icon> |
|
<span class="caption font-weight-bold text-capitalize"> ACL</span> |
|
</v-tab> |
|
<v-tab-item :transition="false" > |
|
<template v-if="isRest"> |
|
<acl-typeorm-db v-if="isPackage || isDocker" :nodes="nodes"></acl-typeorm-db> |
|
<template v-else-if="isTs"> |
|
<acl-typeorm |
|
v-if="isMvc" |
|
ref="tabs4" |
|
:nodes="this.nodes" |
|
/> |
|
</template> |
|
<template v-else> |
|
<acl-js |
|
ref="tabs4" |
|
:nodes="this.nodes" |
|
/> |
|
</template> |
|
</template> |
|
|
|
<template v-else-if="isGraphql"> |
|
|
|
<acl-ts-file-db-gql v-if="isPackage || isDocker" :nodes="nodes"></acl-ts-file-db-gql> |
|
|
|
<template v-else-if="isTs"> |
|
<acl-ts-file-gql v-if="isMvc" |
|
ref="tabs4" |
|
:nodes="this.nodes"></acl-ts-file-gql> |
|
</template> |
|
<template v-else> |
|
<acl-gql |
|
ref="tabs4" |
|
:nodes="this.nodes"></acl-gql> |
|
</template> |
|
|
|
</template> |
|
|
|
<template v-else-if="isGrpc"> |
|
|
|
<acl-grpc-db v-if="isPackage" :nodes="nodes"></acl-grpc-db> |
|
|
|
</template> |
|
|
|
</v-tab-item> |
|
|
|
</template>--> |
|
<!-- <v-tab ripple class="divider v-tab-border-right" @click="loadColumnsMock = true" >--> |
|
<!-- <v-icon small>mdi-seed</v-icon> --> |
|
<!-- <span class="caption font-weight-bold text-capitalize"> Mock</span>--> |
|
<!-- </v-tab>--> |
|
<!-- <v-tab-item :transition="false" >--> |
|
<!-- <mocks--> |
|
<!-- ref="tabs4"--> |
|
<!-- v-if="loadColumnsMock"--> |
|
<!-- :nodes="this.nodes"--> |
|
<!-- :newTable="this.newTableCopy"--> |
|
<!-- :mtdNewTableUpdate="this.mtdNewTableUpdate"--> |
|
<!-- :deleteTable="this.deleteTable"--> |
|
<!-- />--> |
|
<!-- </v-tab-item>--> |
|
|
|
<template v-if="!isMetaTable"> |
|
<v-tab ripple class=""> |
|
<v-icon small> |
|
mdi-sticker-check-outline |
|
</v-icon> |
|
<span class="caption font-weight-bold text-capitalize">Validators</span> |
|
</v-tab> |
|
|
|
<v-tab-item :transition="false"> |
|
<validation |
|
:nodes="nodes" |
|
:new-table="newTableCopy" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
/> |
|
</v-tab-item> |
|
</template> |
|
<template v-if="!isMvc && !isMetaTable"> |
|
<v-tab ripple class=""> |
|
<v-icon small> |
|
mdi-hook |
|
</v-icon> |
|
<span class="caption font-weight-bold text-capitalize">Webhooks</span> |
|
</v-tab> |
|
|
|
<v-tab-item :transition="false"> |
|
<webhooks |
|
ref="tabs0" |
|
:nodes="nodes" |
|
:new-table="newTableCopy" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
/> |
|
</v-tab-item> |
|
</template> |
|
</template> |
|
</v-tabs> |
|
</v-tab-item> |
|
</template> |
|
|
|
<template v-if="_isUIAllowed('airTable')"> |
|
<v-tab v-show="relationTabs && relationTabs.length" class=""> |
|
<v-icon small> |
|
mdi-table-edit |
|
</v-icon> <span |
|
class="caption text-capitalize font-weight-bold" |
|
> {{ nodes._tn }}</span> |
|
</v-tab> |
|
<v-tab-item |
|
style="height:100%" |
|
> |
|
<rows-xc-data-table |
|
ref="tabs7" |
|
:is-active="isActive" |
|
:tab-id="tabId" |
|
:show-tabs="relationTabs && relationTabs.length" |
|
:table="nodes.tn" |
|
:nodes="nodes" |
|
:new-table="newTableCopy" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
:add-new-relation-tab="addNewRelationTab" |
|
/> |
|
</v-tab-item> |
|
</template> |
|
<!-- Closable tabs : START --> |
|
<template |
|
v-for="({ |
|
relation, |
|
// table, |
|
relationType, |
|
relationIdValue, |
|
relationRow, |
|
primaryValue, |
|
refTable, |
|
refTableAlias, |
|
tableAlias |
|
},i) in relationTabs" |
|
> |
|
<v-tab |
|
:key="i" |
|
ripple |
|
class="" |
|
:href="`#relRow${i}`" |
|
style="position: relative;" |
|
@click="loadRows = true" |
|
> |
|
<v-tooltip bottom nudge-bottom=""> |
|
<template #activator="{on}"> |
|
<div v-on="on"> |
|
<!-- <span class="rel-row-parent"> {{ refTable }} - {{ primaryValue }} </span>--> |
|
<v-icon small> |
|
mdi-table-arrow-{{ relationType === 'hm' ? 'right' : 'left' }} |
|
</v-icon> |
|
<span |
|
class="caption font-weight-bold text-capitalize" |
|
> |
|
{{ refTableAlias }} ({{ ((primaryValue || '') + '').slice(0, 13) }}) -> |
|
{{ tableAlias }} |
|
</span> |
|
<v-icon icon x-small class="ml-2" @click="removeRelationTab(i)"> |
|
mdi-close |
|
</v-icon> |
|
</div> |
|
</template> |
|
<span class="caption">{{ |
|
refTableAlias |
|
}}({{ primaryValue }}) -> {{ relationType === 'hm' ? ' Has Many ' : ' Belongs To ' }} -> {{ |
|
tableAlias |
|
}}</span> |
|
</v-tooltip> |
|
</v-tab> |
|
<v-tab-item |
|
:key="i" |
|
:value="`relRow${i}`" |
|
style="height:100%" |
|
> |
|
<template> |
|
<rows-xc-data-table |
|
ref="tabs7" |
|
:is-active="isActive" |
|
:show-tabs="relationTabs && relationTabs.length" |
|
:table="nodes.tn" |
|
:nodes="nodes" |
|
:relation="relation" |
|
:relation-type="relationType" |
|
:new-table="newTableCopy" |
|
:relation-id-value="relationIdValue" |
|
:mtd-new-table-update="mtdNewTableUpdate" |
|
:delete-table="deleteTable" |
|
:is-meta-table="isMetaTable" |
|
:relation-row="relationRow" |
|
:relation-primary-value="primaryValue" |
|
:ref-table="refTable" |
|
:add-new-relation-tab="addNewRelationTab" |
|
/> |
|
</template> |
|
</v-tab-item> |
|
</template> |
|
<!-- Closable tabs : END --> |
|
</v-tabs> |
|
</template> |
|
<dlgLabelSubmitCancel |
|
v-if="dialogShow" |
|
type="error" |
|
:actions-mtd="deleteTable" |
|
:dialog-show="dialogShow" |
|
heading="Click Submit to Delete the Table" |
|
/> |
|
</v-container> |
|
</template> |
|
|
|
<script> |
|
import { mapGetters, mapActions } from 'vuex' |
|
import dlgLabelSubmitCancel from '../utils/dlgLabelSubmitCancel' |
|
import columnList from './tableTabs/columns' |
|
import indexList from './tableTabs/indexes' |
|
import triggerList from './tableTabs/triggers' |
|
import { isMetaTable } from '@/helpers/xutils' |
|
import Webhooks from '@/components/project/tableTabs/webhooks' |
|
import LogicRest from '@/components/project/tableTabs/logicRest' |
|
import LogicGql from '@/components/project/tableTabs/logicGql' |
|
import LogicGrpc from '@/components/project/tableTabs/logicGrpc' |
|
import Validation from '@/components/project/tableTabs/validation' |
|
import TableAcl from '@/components/project/tableTabs/tableAcl' |
|
import RowsXcDataTable from '@/components/project/spreadsheet/rowsXcDataTable' |
|
// import TrialExpired from '../trialExpired' |
|
// import rows from './tableTabs/rows' |
|
|
|
// import AclGql from './tableTabs/aclGql' |
|
// import AclTypeorm from './tableTabs/aclTsFile' |
|
// import AclTsFileGql from './tableTabs/aclTsFileGql' |
|
// import AclJs from './tableTabs/aclJs' |
|
// import AclTypeormDb from './tableTabs/aclTsFileDb' |
|
// import AclGrpcDb from './tableTabs/aclGrpcDb' |
|
// import AclTsFileDbGql from './tableTabs/aclTsFileDbGql' |
|
|
|
export default { |
|
components: { |
|
RowsXcDataTable, |
|
TableAcl, |
|
Validation, |
|
LogicGrpc, |
|
LogicGql, |
|
LogicRest, |
|
Webhooks, |
|
// AclTsFileDbGql, |
|
// AclTypeormDb, |
|
// TrialExpired, |
|
// AclJs, |
|
// AclTsFileGql, |
|
// AclTypeorm, |
|
// AclGql, |
|
// AclGrpcDb, |
|
columnList, |
|
indexList, |
|
// constraintList, |
|
triggerList, |
|
dlgLabelSubmitCancel |
|
// rows |
|
// mocks |
|
}, |
|
data() { |
|
return { |
|
error: false, |
|
active: 1, // this.nodes.newTable ? 0 : 1, |
|
newTableCopy: !!this.nodes.newTable, |
|
dialogShow: false, |
|
loadIndexList: false, |
|
loadTriggerList: false, |
|
loadRelationList: false, |
|
loadConstraintList: false, |
|
loadRows: false, |
|
loadColumnsMock: false, |
|
relationTabs: [] |
|
} |
|
}, |
|
methods: { |
|
addNewRelationTab(relation, refTable, refTableAlias, table, tableAlias, relationIdValue, relationType, relationRow, primaryValue) { |
|
this.relationTabs.push({ |
|
relation, |
|
refTable, |
|
table, |
|
relationIdValue, |
|
relationType, |
|
relationRow, |
|
primaryValue, |
|
refTableAlias, |
|
tableAlias |
|
}) |
|
this.active = 'relRow' + (this.relationTabs.length - 1) |
|
}, |
|
removeRelationTab(i) { |
|
this.relationTabs.splice(i, 1) |
|
}, |
|
|
|
async handleKeyDown(event) { |
|
const activeTabEleKey = `tabs${this.active}` |
|
if (this.$refs[activeTabEleKey] && |
|
this.$refs[activeTabEleKey].handleKeyDown |
|
) { |
|
await this.$refs[activeTabEleKey].handleKeyDown(event) |
|
} |
|
}, |
|
...mapActions({ |
|
removeTableTab: 'tabs/removeTableTab', |
|
loadTablesFromParentTreeNode: 'project/loadTablesFromParentTreeNode' |
|
}), |
|
mtdNewTableUpdate(value) { |
|
this.newTableCopy = value |
|
}, |
|
async deleteTable(action = '') { |
|
if (action === 'showDialog') { |
|
this.dialogShow = true |
|
} else if (action === 'hideDialog') { |
|
this.dialogShow = false |
|
} else { |
|
let relationListAll = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
|
env: this.nodes.env, |
|
dbAlias: this.nodes.dbAlias |
|
}, 'relationListAll']) |
|
|
|
relationListAll = relationListAll.data.list.filter(rel => rel.rtn === this.nodes.tn).map(({ tn }) => tn) |
|
|
|
if (relationListAll.length) { |
|
this.$toast.info('Table can\'t be deleted since Table is being referred in following tables : ' + relationListAll.join(', ')).goAway(10000) |
|
this.dialogShow = false |
|
return |
|
} |
|
|
|
const triggerList = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
|
env: this.nodes.env, |
|
dbAlias: this.nodes.dbAlias |
|
}, 'triggerList', { |
|
tn: this.nodes.tn |
|
}]) |
|
|
|
for (const trigger of triggerList.data.list) { |
|
const result = await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [ |
|
{ |
|
env: this.nodes.env, |
|
dbAlias: this.nodes.dbAlias |
|
}, |
|
'triggerDelete', |
|
{ |
|
...trigger, |
|
tn: this.nodes.tn, |
|
oldStatement: trigger.statement |
|
}]) |
|
|
|
console.log('triggerDelete result ', result) |
|
|
|
this.$toast.success('Trigger deleted successfully').goAway(1000) |
|
} |
|
|
|
let columns = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
|
env: this.nodes.env, |
|
dbAlias: this.nodes.dbAlias |
|
}, 'columnList', { |
|
tn: this.nodes.tn |
|
}]) |
|
|
|
columns = columns.data.list |
|
|
|
await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [{ |
|
env: this.nodes.env, |
|
dbAlias: this.nodes.dbAlias |
|
}, |
|
'tableDelete', |
|
{ tn: this.nodes.tn, columns }]) |
|
|
|
this.removeTableTab({ |
|
env: this.nodes.env, |
|
dbAlias: this.nodes.dbAlias, |
|
tn: this.nodes.tn |
|
}) |
|
|
|
await this.loadTablesFromParentTreeNode({ |
|
_nodes: { |
|
...this.nodes |
|
} |
|
}) |
|
this.dialogShow = false |
|
} |
|
}, |
|
onTabChange() { |
|
this.$emit('update:hideLogWindows', this.active === 2) |
|
} |
|
}, |
|
computed: { |
|
...mapGetters({ |
|
isGraphql: 'project/GtrProjectIsGraphql', |
|
isNoApis: 'project/GtrProjectIsNoApis', |
|
isMvc: 'project/GtrProjectIsMvc', |
|
isDocker: 'project/GtrProjectIsDocker', |
|
isPackage: 'project/GtrProjectIsPackage', |
|
isTs: 'project/GtrProjectIsTs', |
|
isRest: 'project/GtrProjectIsRest', |
|
isGrpc: 'project/GtrProjectIsGrpc' |
|
}), |
|
|
|
scaffoldOnSave: { |
|
get() { |
|
return this.$store.state.windows.scaffoldOnSave |
|
}, |
|
set(status) { |
|
this.$store.commit('windows/MutToggleScaffoldOnSave', status) |
|
this.$toast.success(`Scaffolding of source code ${status ? 'ENABLED' : 'DISABLED'} successfully`).goAway(4000) |
|
} |
|
}, |
|
isTsEnabled() { |
|
return process.env.TS_ENABLED |
|
}, |
|
isMetaTable() { |
|
return isMetaTable(this.nodes.tn) |
|
} |
|
}, |
|
beforeCreated() { |
|
}, |
|
watch: {}, |
|
created() { |
|
}, |
|
mounted() { |
|
this.onTabChange() |
|
}, |
|
beforeDestroy() { |
|
}, |
|
destroy() { |
|
}, |
|
directives: {}, |
|
validate({ params }) { |
|
return true |
|
}, |
|
head() { |
|
return {} |
|
}, |
|
props: ['nodes', 'hideLogWindows', 'tabId', 'isActive'] |
|
} |
|
</script> |
|
|
|
<style scoped> |
|
|
|
/deep/ .table-tabs > .v-tabs-items { |
|
border-top: 1px solid #7F828B33; |
|
} |
|
|
|
/deep/ .scaffoldOnSave .v-input__control { |
|
margin-top: -2px; |
|
} |
|
|
|
.table-tabs, /deep/ .table-tabs > .v-windows { |
|
height: 100%; |
|
} |
|
|
|
/deep/ .v-window-item { |
|
height: 100% |
|
} |
|
|
|
.rel-row-parent { |
|
text-align: center; |
|
left: 0; |
|
padding: 2px 3px; |
|
text-transform: none; |
|
display: inline-block; |
|
position: absolute; |
|
top: 0; |
|
font-size: 8px; |
|
white-space: nowrap; |
|
text-overflow: ellipsis; |
|
width: 100%; |
|
overflow: hidden; |
|
color: grey; |
|
} |
|
|
|
</style> |
|
<!-- |
|
/** |
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
|
* |
|
* @author Naveen MR <oof1lab@gmail.com> |
|
* @author Pranav C Balan <pranavxc@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/>. |
|
* |
|
*/ |
|
-->
|
|
|