diff --git a/client/components/admin.vue b/client/components/admin.vue
index b063278e..6de48bcf 100644
--- a/client/components/admin.vue
+++ b/client/components/admin.vue
@@ -29,6 +29,9 @@
v-list-item-action(style='min-width:auto;')
v-chip(x-small, :color='darkMode ? `grey darken-3-d4` : `grey lighten-5`')
.caption.grey--text {{ info.pagesTotal }}
+ v-list-item(to='/tags', v-if='hasPermission([`manage:system`])', disabled)
+ v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-tag-multiple
+ v-list-item-title {{ $t('admin:tags.title') }}
v-list-item(to='/theme', v-if='hasPermission([`manage:system`, `manage:theme`])')
v-list-item-avatar(size='24'): v-icon mdi-palette-outline
v-list-item-title {{ $t('admin:theme.title') }}
diff --git a/client/components/admin/admin-theme.vue b/client/components/admin/admin-theme.vue
index 083f8aa7..67a4c27e 100644
--- a/client/components/admin/admin-theme.vue
+++ b/client/components/admin/admin-theme.vue
@@ -52,21 +52,49 @@
:hint='$t(`admin:theme.darkModeHint`)'
)
- //- v-card.mt-3.animated.fadeInUp.wait-p1s
- //- v-toolbar(color='primary', dark, dense, flat)
- //- v-toolbar-title.subtitle-1 {{$t(`admin:theme.options`)}}
- //- v-spacer
- //- v-chip(label, color='white', small).primary--text coming soon
- //- v-card-text
- //- v-select(
- //- :items='iconsets'
- //- outlined
- //- prepend-icon='mdi-border-vertical'
- //- v-model='config.iconset'
- //- label='Table of Contents Position'
- //- persistent-hint
- //- hint='Select whether the table of contents is shown on the left, right or not at all.'
- //- )
+ v-card.mt-3.animated.fadeInUp.wait-p1s
+ v-toolbar(color='primary', dark, dense, flat)
+ v-toolbar-title.subtitle-1 {{$t(`admin:theme.options`)}}
+ v-spacer
+ v-chip(label, color='white', small).primary--text coming soon
+ v-card-text
+ v-select(
+ :items='[]'
+ outlined
+ prepend-icon='mdi-border-vertical'
+ v-model='config.iconset'
+ label='Table of Contents Position'
+ persistent-hint
+ hint='Select whether the table of contents is shown on the left, right or not at all.'
+ disabled
+ )
+
+ v-flex(lg6 xs12)
+ v-card.animated.fadeInUp.wait-p2s
+ v-toolbar(color='teal', dark, dense, flat)
+ v-toolbar-title.subtitle-1 {{$t('admin:theme.downloadThemes')}}
+ v-spacer
+ v-chip(label, color='white', small).teal--text coming soon
+ v-data-table(
+ :headers='headers',
+ :items='themes',
+ hide-default-footer,
+ item-key='value',
+ :items-per-page='1000'
+ )
+ template(v-slot:item='thm')
+ td
+ strong {{thm.item.text}}
+ td
+ span {{ thm.item.author }}
+ td.text-xs-center
+ v-progress-circular(v-if='thm.item.isDownloading', indeterminate, color='blue', size='20', :width='2')
+ v-btn(v-else-if='thm.item.isInstalled && thm.item.installDate < thm.item.updatedAt', icon)
+ v-icon.blue--text mdi-cached
+ v-btn(v-else-if='thm.item.isInstalled', icon)
+ v-icon.green--text mdi-check-bold
+ v-btn(v-else, icon)
+ v-icon.grey--text mdi-cloud-download
v-card.mt-3.animated.fadeInUp.wait-p2s
v-toolbar(color='primary', dark, dense, flat)
@@ -102,32 +130,6 @@
:hint='$t(`admin:theme.bodyHtmlInjectionHint`)'
auto-grow
)
- v-flex(lg6 xs12)
- v-card.animated.fadeInUp.wait-p2s
- v-toolbar(color='teal', dark, dense, flat)
- v-toolbar-title.subtitle-1 {{$t('admin:theme.downloadThemes')}}
- v-spacer
- v-chip(label, color='white', small).teal--text coming soon
- v-data-table(
- :headers='headers',
- :items='themes',
- hide-default-footer,
- item-key='value',
- :items-per-page='1000'
- )
- template(v-slot:item='thm')
- td
- strong {{thm.item.text}}
- td
- span {{ thm.item.author }}
- td.text-xs-center
- v-progress-circular(v-if='thm.item.isDownloading', indeterminate, color='blue', size='20', :width='2')
- v-btn(v-else-if='thm.item.isInstalled && thm.item.installDate < thm.item.updatedAt', icon)
- v-icon.blue--text mdi-cached
- v-btn(v-else-if='thm.item.isInstalled', icon)
- v-icon.green--text mdi-check-bold
- v-btn(v-else, icon)
- v-icon.grey--text mdi-cloud-download
diff --git a/client/components/editor/editor-api.vue b/client/components/editor/editor-api.vue
new file mode 100644
index 00000000..6eee8741
--- /dev/null
+++ b/client/components/editor/editor-api.vue
@@ -0,0 +1,433 @@
+
+ .editor-api
+ .editor-api-main
+ v-list.editor-api-sidebar.radius-0(nav, :class='$vuetify.theme.dark ? `grey darken-4` : `primary`', dark)
+ v-list-item-group(v-model='tab')
+ v-list-item.animated.fadeInLeft(value='info')
+ v-list-item-icon: v-icon mdi-book-information-variant
+ v-list-item-title Info
+ v-list-item.mt-3.animated.fadeInLeft.wait-p2s(value='servers')
+ v-list-item-icon: v-icon mdi-server
+ v-list-item-title Servers
+ v-list-item.mt-3.animated.fadeInLeft.wait-p3s(value='endpoints')
+ v-list-item-icon: v-icon mdi-code-braces
+ v-list-item-title Endpoints
+ v-list-item.mt-3.animated.fadeInLeft.wait-p4s(value='models')
+ v-list-item-icon: v-icon mdi-buffer
+ v-list-item-title Models
+ v-list-item.mt-3.animated.fadeInLeft.wait-p5s(value='auth')
+ v-list-item-icon: v-icon mdi-lock
+ v-list-item-title Authentication
+ .editor-api-editor
+ template(v-if='tab === `info`')
+ v-container.px-2.pt-1(fluid)
+ v-row(dense)
+ v-col(cols='12')
+ .pa-3
+ .subtitle-2 API General Information
+ .caption.grey--text.text--darken-1 Global metadata about the API
+ v-col(cols='12', lg='6')
+ v-card.pt-2
+ v-card-text
+ v-text-field(
+ label='Title'
+ outlined
+ hint='Required - Title of the API'
+ persistent-hint
+ v-model='info.title'
+ )
+ v-divider.mt-2.mb-4
+ v-text-field(
+ label='Version'
+ outlined
+ hint='Required - Semantic versioning like 1.0.0 or an arbitrary string like 0.99-beta.'
+ persistent-hint
+ v-model='info.version'
+ )
+ v-divider.mt-2.mb-4
+ v-textarea(
+ label='Description'
+ outlined
+ hint='Optional - Markdown formatting is supported.'
+ persistent-hint
+ v-model='info.description'
+ )
+ v-col(cols='12', lg='6')
+ v-card.pt-2
+ v-card-text
+ v-list(nav, two-line)
+ v-list-item-group(v-model='kind', mandatory, color='primary')
+ v-list-item(value='rest')
+ v-list-item-avatar
+ img(src='/svg/icon-transaction-list.svg', alt='REST')
+ v-list-item-content
+ v-list-item-title REST API
+ v-list-item-subtitle Classic REST Endpoints
+ v-list-item-avatar
+ v-icon(:color='kind === `rest` ? `primary` : `grey lighten-3`') mdi-check-circle
+ v-list-item(value='graphql', disabled)
+ v-list-item-avatar
+ img(src='/svg/icon-graphql.svg', alt='GraphQL')
+ v-list-item-content
+ v-list-item-title GraphQL
+ v-list-item-subtitle.grey--text.text--lighten-1 Schema-based API
+ v-list-item-action
+ //- v-icon(:color='kind === `graphql` ? `primary` : `grey lighten-3`') mdi-check-circle
+ v-chip(label, small) Coming soon
+ template(v-else-if='tab === `servers`')
+ v-container.px-2.pt-1(fluid)
+ v-row(dense)
+ v-col(cols='12')
+ .pa-3
+ .d-flex.align-center.justify-space-between
+ div
+ .subtitle-2 List of servers / load balancers where this API reside
+ .caption.grey--text.text--darken-1 Enter all environments, e.g. Integration, QA, Pre-production, Production, etc.
+ v-btn(color='primary', large, @click='addServer')
+ v-icon(left) mdi-plus
+ span Add Server
+ v-col(cols='12', lg='6', v-for='srv of servers', :key='srv.id')
+ v-card.pt-1
+ v-card-text
+ .d-flex
+ .d-flex.flex-column.justify-space-between
+ v-menu(offset-y, min-width='200')
+ template(v-slot:activator='{ on }')
+ v-btn(text, x-large, style='min-width: 0;', v-on='on')
+ v-icon(large, :color='iconColor(srv.icon)') {{iconKey(srv.icon)}}
+ v-list(nav, dense)
+ v-list-item-group(v-model='srv.icon', mandatory)
+ v-list-item(:value='srvKey', v-for='(srv, srvKey) in serverTypes', :key='srvKey')
+ v-list-item-icon: v-icon(large, :color='srv.color', v-text='srv.icon')
+ v-list-item-content: v-list-item-title(v-text='srv.title')
+ v-btn.mb-2(depressed, small, @click='removeServer(srv.id)')
+ v-icon(left) mdi-close
+ span Delete
+ v-divider.ml-5(vertical)
+ .pl-5(style='flex: 1 1 100%;')
+ v-text-field(
+ label='Environment / Server Name'
+ outlined
+ hint='Required - Name of the environment (e.g. QA, Production)'
+ persistent-hint
+ v-model='srv.name'
+ )
+ v-text-field.mt-4(
+ label='URL'
+ outlined
+ hint='Required - URL of the environment (e.g. https://api.example.com/v1)'
+ persistent-hint
+ v-model='srv.url'
+ )
+
+ template(v-else-if='tab === `endpoints`')
+ v-container.px-2.pt-1(fluid)
+ v-row(dense)
+ v-col(cols='12')
+ .pa-3
+ .d-flex.align-center.justify-space-between
+ div
+ .subtitle-2 List of endpoints
+ .caption.grey--text.text--darken-1 Groups of REST endpoints (GET, POST, PUT, DELETE).
+ v-btn(color='primary', large, @click='addGroup')
+ v-icon(left) mdi-plus
+ span Add Group
+ v-col(cols='12', v-for='grp of endpointGroups', :key='grp.id')
+ v-card(color='grey darken-2')
+ v-card-text
+ v-toolbar(color='grey darken-2', flat, height='86')
+ v-text-field.mr-1(
+ flat
+ dark
+ label='Group Name'
+ solo
+ hint='Group Name'
+ persistent-hint
+ v-model='grp.name'
+ )
+ v-text-field.mx-1(
+ flat
+ dark
+ label='Group Description'
+ solo
+ hint='Group Description'
+ persistent-hint
+ v-model='grp.description'
+ )
+ v-divider.mx-3(vertical, dark)
+ v-btn.mx-1.align-self-start(color='grey lighten-2', @click='addEndpoint(grp)', dark, text, height='48')
+ v-icon(left) mdi-trash-can
+ span Delete
+ v-divider.mx-3(vertical, dark)
+ v-btn.ml-1.align-self-start(color='pink', @click='addEndpoint(grp)', dark, depressed, height='48')
+ v-icon(left) mdi-plus
+ span Add Endpoint
+ v-container.pa-0.mt-2(fluid)
+ v-row(dense)
+ v-col(cols='12', v-for='ept of grp.endpoints', :key='ept.id')
+ v-card.pt-1
+ v-card-text
+ .d-flex
+ .d-flex.flex-column
+ v-menu(offset-y, min-width='140')
+ template(v-slot:activator='{ on }')
+ v-btn.subtitle-1(depressed, large, dark, style='min-width: 140px;', height='48', v-on='on', :color='methodColor(ept.method)')
+ strong {{ept.method}}
+ v-list(nav, dense)
+ v-list-item-group(v-model='ept.method', mandatory)
+ v-list-item(:value='mtd.key', v-for='mtd of endpointMethods', :key='mtd.key')
+ v-list-item-content
+ v-chip.text-center(label, :color='mtd.color', dark) {{mtd.key}}
+ v-btn.mt-2(v-if='!ept.expanded', small, @click='ept.expanded = true', color='pink', outlined)
+ v-icon(left) mdi-arrow-down-box
+ span Expand
+ v-btn.mt-2(v-else, small, @click='ept.expanded = false', color='pink', outlined)
+ v-icon(left) mdi-arrow-up-box
+ span Collapse
+ template(v-if='ept.expanded')
+ v-spacer
+ v-btn.my-2(depressed, small, @click='removeEndpoint(grp, ept.id)')
+ v-icon(left) mdi-close
+ span Delete
+ v-divider.ml-5(vertical)
+ .pl-5(style='flex: 1 1 100%;')
+ .d-flex
+ v-text-field.mr-2(
+ label='Path'
+ outlined
+ hint='Required - Path to the endpoint (e.g. /planets/{planetId})'
+ persistent-hint
+ v-model='ept.path'
+ )
+ v-text-field.ml-2(
+ label='Summary'
+ outlined
+ hint='Required - A short summary of the endpoint (a few words).'
+ persistent-hint
+ v-model='ept.summary'
+ )
+ template(v-if='ept.expanded')
+ v-text-field.mt-3(
+ label='Description'
+ outlined
+ v-model='ept.description'
+ )
+
+ v-system-bar.editor-api-sysbar(dark, status, color='grey darken-3')
+ .caption.editor-api-sysbar-locale {{locale.toUpperCase()}}
+ .caption.px-3 /{{path}}
+ template(v-if='$vuetify.breakpoint.mdAndUp')
+ v-spacer
+ .caption API Docs
+ v-spacer
+ .caption OpenAPI 3.0
+
+
+
+
+
diff --git a/client/components/editor/editor-modal-editorselect.vue b/client/components/editor/editor-modal-editorselect.vue
index 404b6867..d10b7945 100644
--- a/client/components/editor/editor-modal-editorselect.vue
+++ b/client/components/editor/editor-modal-editorselect.vue
@@ -1,6 +1,6 @@
v-dialog(v-model='isShown', persistent, max-width='700', no-click-animation)
- v-btn(fab, fixed, bottom, right, color='grey darken-3', dark, @click='goBack'): v-icon mdi-undo-variant
+ v-btn(fab, fixed, bottom, right, color='grey darken-3', dark, @click='goBack', style='width: 50px;'): v-icon mdi-undo-variant
v-card.radius-7(color='blue darken-3', dark)
v-card-text.text-center.py-4
.subtitle-1.white--text {{$t('editor:select.title')}}
@@ -9,23 +9,15 @@
v-flex(xs4)
v-hover
template(v-slot:default='{ hover }')
- v-card.radius-7.primary.animated.fadeInUp(
+ v-card.radius-7.animated.fadeInUp(
hover
light
ripple
)
- v-card-text.text-center(@click='')
- img(src='/svg/editor-icon-api.svg', alt='API', style='width: 36px; opacity: .5;')
- .body-2.mt-2.blue--text.text--lighten-2 API Docs
- .caption.blue--text.text--lighten-1 REST / GraphQL
- v-fade-transition
- v-overlay(
- v-if='hover'
- absolute
- color='primary'
- opacity='.8'
- )
- .body-2.mt-7 Coming Soon
+ v-card-text.text-center(@click='selectEditor(`api`)')
+ img(src='/svg/editor-icon-api.svg', alt='API', style='width: 36px;')
+ .body-2.primary--text.mt-2 API Docs
+ .caption.grey--text REST / GraphQL
v-flex(xs4)
v-hover
template(v-slot:default='{ hover }')
diff --git a/client/static/svg/icon-graphql.svg b/client/static/svg/icon-graphql.svg
new file mode 100644
index 00000000..45558119
--- /dev/null
+++ b/client/static/svg/icon-graphql.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/static/svg/icon-transaction-list.svg b/client/static/svg/icon-transaction-list.svg
new file mode 100644
index 00000000..aade7ac1
--- /dev/null
+++ b/client/static/svg/icon-transaction-list.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/themes/default/components/nav-sidebar.vue b/client/themes/default/components/nav-sidebar.vue
index 897dfc03..7152f99e 100644
--- a/client/themes/default/components/nav-sidebar.vue
+++ b/client/themes/default/components/nav-sidebar.vue
@@ -1,15 +1,23 @@
- v-list.py-2(dense, :class='color', :dark='dark')
- template(v-for='item of items')
- v-list-item(
- v-if='item.kind === `link`'
- :href='item.target'
- )
- v-list-item-avatar(size='24')
- v-icon {{ item.icon }}
- v-list-item-title {{ item.label }}
- v-divider.my-2(v-else-if='item.kind === `divider`')
- v-subheader.pl-4(v-else-if='item.kind === `header`') {{ item.label }}
+ div
+ .blue.darken-3.pa-3.d-flex
+ v-btn(depressed, color='blue darken-2', style='min-width:0;', href='/')
+ v-icon(size='20') mdi-home
+ v-btn.ml-3(depressed, color='blue darken-2', style='flex: 1 1 100%;')
+ v-icon(left) mdi-file-tree
+ .body-2.text-none Browse
+ v-divider
+ v-list.py-2(dense, :class='color', :dark='dark')
+ template(v-for='item of items')
+ v-list-item(
+ v-if='item.kind === `link`'
+ :href='item.target'
+ )
+ v-list-item-avatar(size='24')
+ v-icon {{ item.icon }}
+ v-list-item-title {{ item.label }}
+ v-divider.my-2(v-else-if='item.kind === `divider`')
+ v-subheader.pl-4(v-else-if='item.kind === `header`') {{ item.label }}