1
0
Fork 0

Add more pages in the WebUI

This commit is contained in:
Jorge Gonzalez 2019-09-10 14:40:05 +02:00 committed by Traefiker Bot
parent 2b828765e3
commit fcc1109e76
82 changed files with 5005 additions and 249 deletions

View file

@ -0,0 +1,53 @@
<template>
<div class="block-right-text">
<q-avatar :color="value ? 'positive' : 'negative'" text-color="white">
<q-icon v-if="value" name="eva-toggle-right" />
<q-icon v-if="!value" name="eva-toggle-left" />
</q-avatar>
<div v-bind:class="['block-right-text-label', `block-right-text-label-${!!value}`]">{{value ? 'True' : 'False'}}</div>
</div>
</template>
<script>
export default {
name: 'BooleanState',
props: ['value']
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.q-avatar{
font-size: 32px;
border-radius: 4px;
.q-icon {
font-size: 22px;
margin: 0 0 0 1px;
}
}
.block-right-text{
height: 32px;
line-height: 32px;
.q-avatar{
float: left;
}
&-label{
font-size: 14px;
font-weight: 600;
color: $app-text-grey;
float: left;
margin-left: 10px;
text-transform: capitalize;
&-true {
color: $positive;
}
&-false {
color: $negative;
}
}
}
</style>

View file

@ -0,0 +1,249 @@
<template>
<div class="table-wrapper">
<q-table
:data="data"
:columns="columns"
row-key="name"
:pagination.sync="syncPagination"
:rows-per-page-options="[10, 20, 40, 80, 100, 0]"
:loading="loading"
:filter="filter"
@request="request"
binary-state-sort
:visible-columns="visibleColumns"
color="primary"
table-header-class="table-header">
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @click.native="$router.push({ path: `/${getPath}/${props.row.name}`})">
<q-td key="status" :props="props" auto-width>
<avatar-state :state="props.row.status | status "/>
</q-td>
<q-td key="tls" :props="props" auto-width>
<t-l-s-state :is-t-l-s="props.row.tls"/>
</q-td>
<q-td key="rule" :props="props">
<q-chip
v-if="props.row.rule"
dense
class="app-chip app-chip-rule">
{{ props.row.rule }}
</q-chip>
</q-td>
<q-td key="entryPoints" :props="props">
<div v-if="props.row.using">
<q-chip
v-for="(entryPoints, index) in props.row.using" :key="index"
dense
class="app-chip app-chip-entry-points">
{{ entryPoints }}
</q-chip>
</div>
</q-td>
<q-td key="name" :props="props">
<q-chip
v-if="props.row.name"
dense
class="app-chip app-chip-name">
{{ props.row.name }}
</q-chip>
</q-td>
<q-td key="type" :props="props">
<q-chip
v-if="props.row.type"
dense
class="app-chip app-chip-entry-points">
{{ props.row.type }}
</q-chip>
</q-td>
<q-td key="servers" :props="props">
<span class="servers-label">{{ props.row | servers }}</span>
</q-td>
<q-td key="service" :props="props">
<q-chip
v-if="props.row.service"
dense
class="app-chip app-chip-service">
{{ props.row.service }}
</q-chip>
</q-td>
<q-td key="provider" :props="props" auto-width>
<q-avatar class="provider-logo">
<q-icon :name="`img:statics/providers/${props.row.provider}.svg`" />
</q-avatar>
</q-td>
</q-tr>
</template>
</q-table>
</div>
</template>
<script>
import AvatarState from './AvatarState'
import TLSState from './TLSState'
export default {
name: 'MainTable',
props: ['data', 'request', 'loading', 'pagination', 'filter', 'type'],
components: {
TLSState,
AvatarState
},
data () {
return {
visibleColumnsHttpRouters: ['status', 'rule', 'entryPoints', 'name', 'service', 'tls', 'provider'],
visibleColumnsHttpServices: ['status', 'name', 'type', 'servers', 'provider'],
visibleColumnsHttpMiddlewares: ['status', 'name', 'type', 'provider'],
visibleColumns: ['status', 'name', 'provider'],
columns: [
{
name: 'status',
required: true,
label: 'Status',
align: 'left',
field: row => row.status
},
{
name: 'tls',
align: 'left',
label: 'TLS',
field: row => row
},
{
name: 'rule',
align: 'left',
label: 'Rule',
field: row => row.rule
},
{
name: 'entryPoints',
align: 'left',
label: 'Entrypoints',
field: row => row.entryPoints
},
{
name: 'name',
align: 'left',
label: 'Name',
field: row => row.name
},
{
name: 'type',
align: 'left',
label: 'Type',
field: row => row.type
},
{
name: 'servers',
align: 'right',
label: 'Servers',
field: row => row.servers
},
{
name: 'service',
align: 'left',
label: 'Service',
field: row => row.service
},
{
name: 'provider',
align: 'center',
label: 'Provider',
field: row => row.provider
}
]
}
},
computed: {
syncPagination: {
get () {
return this.pagination
},
set (newValue) {
this.$emit('update:pagination', newValue)
}
},
getPath () {
return this.type.replace('-', '/', 'gi')
}
},
filters: {
status (value) {
if (value === 'enabled') {
return 'positive'
}
if (value === 'disabled') {
return 'negative'
}
return value
},
servers (value) {
if (value.loadBalancer && value.loadBalancer.servers) {
return value.loadBalancer.servers.length
}
return 0
}
},
created () {
if (this.type === 'http-routers' || this.type === 'tcp-routers') {
this.visibleColumns = this.visibleColumnsHttpRouters
}
if (this.type === 'http-services' || this.type === 'tcp-services') {
this.visibleColumns = this.visibleColumnsHttpServices
}
if (this.type === 'http-middlewares') {
this.visibleColumns = this.visibleColumnsHttpMiddlewares
}
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.table-wrapper {
/deep/ .q-table__container{
border-radius: 8px;
.q-table {
.table-header {
th {
font-size: 14px;
font-weight: 700;
}
}
tbody {
tr:hover {
background: rgba( $accent, 0.04 );
}
}
}
.q-table__bottom {
> .q-table__control {
&:nth-last-child(2) {
display: none;
}
&:nth-last-child(1) {
.q-table__bottom-item {
display: none;
}
}
}
}
}
}
.servers-label{
font-size: 14px;
font-weight: 600;
}
.provider-logo {
width: 32px;
height: 32px;
img {
width: 100%;
height: 100%;
}
}
</style>

View file

@ -8,6 +8,8 @@
</div>
<q-tabs align="left" inline-label indicator-color="transparent" active-color="white" stretch>
<q-route-tab to="/" icon="eva-home-outline" no-caps label="Dashboard" />
<q-route-tab to="/http" icon="eva-globe-outline" no-caps label="HTTP" />
<q-route-tab to="/tcp" icon="eva-globe-2-outline" no-caps label="TCP" />
</q-tabs>
<q-space />
<q-btn type="a" :href="`https://docs.traefik.io/${parsedVersion}`" target="_blank" stretch flat no-caps label="Documentation" class="btn-menu" />

View file

@ -0,0 +1,125 @@
<template>
<q-card flat bordered v-bind:class="['panel-health-check', {'panel-health-check-dense':isDense}]">
<q-scroll-area :thumb-style="appThumbStyle" style="height:100%;">
<q-card-section v-if="data.scheme || data.interval">
<div class="row items-start no-wrap">
<div class="col" v-if="data.scheme">
<div class="text-subtitle2">SCHEME</div>
<q-chip
dense
class="app-chip app-chip-options">
{{ data.scheme }}
</q-chip>
</div>
<div class="col" v-if="data.interval">
<div class="text-subtitle2">INTERVAL</div>
<q-chip
dense
class="app-chip app-chip-interval">
{{ data.interval }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.path || data.timeout">
<div class="row items-start no-wrap">
<div class="col" v-if="data.path">
<div class="text-subtitle2">PATH</div>
<q-chip
dense
class="app-chip app-chip-entry-points">
{{ data.path }}
</q-chip>
</div>
<div class="col" v-if="data.timeout">
<div class="text-subtitle2">TIMEOUT</div>
<q-chip
dense
class="app-chip app-chip-interval">
{{ data.timeout }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.port || data.hostname">
<div class="row items-start no-wrap">
<div class="col" v-if="data.port">
<div class="text-subtitle2">PORT</div>
<q-chip
dense
class="app-chip app-chip-name">
{{ data.port }}
</q-chip>
</div>
<div class="col" v-if="data.hostname">
<div class="text-subtitle2">HOSTNAME</div>
<q-chip
dense
class="app-chip app-chip-rule">
{{ data.hostname }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.headers">
<div class="row items-start">
<div class="col-12">
<div class="text-subtitle2">HEADERS</div>
</div>
<div v-for="(header, index) in data.headers" :key="index" class="col-12">
<q-chip
dense
class="app-chip app-chip-wrap app-chip-service">
{{ index }}: {{ header }}
</q-chip>
</div>
</div>
</q-card-section>
</q-scroll-area>
</q-card>
</template>
<script>
export default {
name: 'PanelHealthCheck',
props: ['data', 'dense'],
components: {
},
computed: {
isDense () {
return this.dense !== undefined
}
},
filters: {
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.panel-health-check {
height: 600px;
&-dense {
height: 400px;
}
.q-card__section {
padding: 24px;
+ .q-card__section {
padding-top: 0;
}
}
.text-subtitle2 {
font-size: 11px;
color: $app-text-grey;
line-height: 16px;
margin-bottom: 4px;
text-align: left;
letter-spacing: 2px;
font-weight: 600;
text-transform: uppercase;
}
}
</style>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,133 @@
<template>
<q-card flat bordered v-bind:class="['panel-services', {'panel-services-dense':isDense}]">
<q-scroll-area :thumb-style="appThumbStyle" style="height:100%;">
<q-card-section>
<div class="row items-start no-wrap">
<div class="col-6">
<div class="text-subtitle2 text-table">Name</div>
</div>
<div class="col-3">
<div class="text-subtitle2 text-table" style="text-align: right">Percent</div>
</div>
<div class="col-3">
<div class="text-subtitle2 text-table" style="text-align: right">Provider</div>
</div>
</div>
</q-card-section>
<q-separator />
<div v-for="(service, index) in data.mirroring.mirrors" :key="index">
<q-card-section>
<div class="row items-center no-wrap">
<div class="col-6">
<q-chip
dense
class="app-chip app-chip-rule">
{{ service.name }}
</q-chip>
</div>
<div class="col-3 text-right">
{{ service.percent }}
</div>
<div class="col-3 text-right">
<q-avatar class="provider-logo">
<q-icon :name="`img:statics/providers/${getProvider(service)}.svg`" />
</q-avatar>
</div>
</div>
</q-card-section>
<q-separator />
</div>
</q-scroll-area>
</q-card>
</template>
<script>
export default {
name: 'PanelMirroringServices',
props: ['data', 'dense'],
components: {},
computed: {
isDense () {
return this.dense !== undefined
}
},
methods: {
getProvider (service) {
const words = service.name.split('@')
if (words.length !== 2) {
return this.provider
}
return words[1]
}
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.panel-services {
height: 600px;
&-dense{
height: 400px;
}
.q-card__section {
padding: 12px 24px;
+ .q-card__section {
padding-top: 0;
}
}
.block-right-text{
height: 32px;
line-height: 32px;
&-label{
font-size: 14px;
font-weight: 600;
color: $app-text-grey;
float: left;
margin-left: 10px;
text-transform: capitalize;
&-enabled {
color: $positive;
}
&-disabled {
color: $negative;
}
&-warning {
color: $warning;
}
}
}
.text-subtitle2 {
font-size: 11px;
color: $app-text-grey;
line-height: 16px;
margin-bottom: 4px;
text-align: left;
letter-spacing: 2px;
font-weight: 600;
text-transform: uppercase;
}
.text-table {
font-size: 14px;
font-weight: 700;
letter-spacing: normal;
text-transform: none;
}
.provider-logo {
width: 32px;
height: 32px;
img {
width: 100%;
height: 100%;
}
}
}
</style>

View file

@ -0,0 +1,204 @@
<template>
<q-card flat bordered v-bind:class="['panel-router-details']">
<q-scroll-area :thumb-style="appThumbStyle" style="height:100%;">
<q-card-section>
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">STATUS</div>
<div class="block-right-text">
<avatar-state :state="data.status | status "/>
<div v-bind:class="['block-right-text-label', `block-right-text-label-${data.status}`]">{{data.status | statusLabel}}</div>
</div>
</div>
<div class="col">
<div class="text-subtitle2">PROVIDER</div>
<div class="block-right-text">
<q-avatar class="provider-logo">
<q-icon :name="`img:statics/providers/${data.provider}.svg`" />
</q-avatar>
<div class="block-right-text-label">{{data.provider}}</div>
</div>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.rule">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">RULE</div>
<q-chip
dense
class="app-chip app-chip-rule">
{{ data.rule }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.name">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">NAME</div>
<q-chip
dense
class="app-chip app-chip-name">
{{ data.name }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.using">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">ENTRYPOINTS</div>
<q-chip
v-for="(entryPoint, index) in data.using" :key="index"
dense
class="app-chip app-chip-entry-points">
{{ entryPoint }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.service">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">SERVICE</div>
<q-chip
dense
clickable
@click.native="$router.push({ path: `/${protocol}/services/${getServiceId()}`})"
class="app-chip app-chip-service">
{{ data.service }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.error">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">ERRORS</div>
<q-chip
v-for="(errorMsg, index) in data.error" :key="index"
class="app-chip app-chip-error">
{{ errorMsg }}
</q-chip>
</div>
</div>
</q-card-section>
</q-scroll-area>
</q-card>
</template>
<script>
import AvatarState from './AvatarState'
export default {
name: 'PanelRouterDetails',
props: ['data', 'protocol'],
components: {
AvatarState
},
methods: {
getServiceId () {
const words = this.data.service.split('@')
if (words.length === 2) {
return this.data.service
}
return `${this.data.service}@${this.data.provider}`
}
},
filters: {
status (value) {
if (value === 'enabled') {
return 'positive'
}
if (value === 'disabled') {
return 'negative'
}
return value
},
statusLabel (value) {
if (value === 'enabled') {
return 'success'
}
if (value === 'disabled') {
return 'error'
}
return value
}
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.panel-router-details {
height: 600px;
.q-card__section {
padding: 24px;
+ .q-card__section {
padding-top: 0;
}
}
.block-right-text{
height: 32px;
line-height: 32px;
.q-avatar{
float: left;
}
&-label{
font-size: 14px;
font-weight: 600;
color: $app-text-grey;
float: left;
margin-left: 10px;
text-transform: capitalize;
&-enabled {
color: $positive;
}
&-disabled {
color: $negative;
}
&-warning {
color: $warning;
}
}
}
.text-subtitle2 {
font-size: 11px;
color: $app-text-grey;
line-height: 16px;
margin-bottom: 4px;
text-align: left;
letter-spacing: 2px;
font-weight: 600;
text-transform: uppercase;
}
.app-chip {
&-error {
display: flex;
height: 100%;
flex-wrap: wrap;
border-width: 0;
margin-bottom: 8px;
/deep/ .q-chip__content{
white-space: normal;
}
}
}
.provider-logo {
width: 32px;
height: 32px;
img {
width: 100%;
height: 100%;
}
}
}
</style>

View file

@ -0,0 +1,123 @@
<template>
<q-card flat bordered v-bind:class="['panel-servers', {'panel-servers-dense':isDense}]">
<q-scroll-area :thumb-style="appThumbStyle" style="height:100%;">
<q-card-section>
<div class="row items-start no-wrap">
<div class="col-3">
<div class="text-subtitle2 text-table">Status</div>
</div>
<div class="col-9">
<div class="text-subtitle2 text-table">URL</div>
</div>
</div>
</q-card-section>
<q-separator />
<div v-for="(server, index) in data.loadBalancer.servers" :key="index">
<q-card-section>
<div class="row items-center no-wrap">
<div class="col-3">
<div class="block-right-text">
<avatar-state v-if="data.serverStatus" :state="data.serverStatus[server.url || server.address] | status "/>
<avatar-state v-if="!data.serverStatus" :state="'DOWN' | status"/>
</div>
</div>
<div class="col-9">
<q-chip
dense
class="app-chip app-chip-rule">
{{ server.url || server.address}}
</q-chip>
</div>
</div>
</q-card-section>
<q-separator />
</div>
</q-scroll-area>
</q-card>
</template>
<script>
import AvatarState from './AvatarState'
export default {
name: 'PanelServers',
props: ['data', 'dense'],
components: {
AvatarState
},
computed: {
isDense () {
return this.dense !== undefined
}
},
filters: {
status (value) {
if (value === 'UP') {
return 'positive'
}
return 'negative'
}
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.panel-servers {
height: 600px;
&-dense{
height: 400px;
}
.q-card__section {
padding: 12px 24px;
+ .q-card__section {
padding-top: 0;
}
}
.block-right-text{
height: 32px;
line-height: 32px;
.q-avatar{
float: left;
}
&-label{
font-size: 14px;
font-weight: 600;
color: $app-text-grey;
float: left;
margin-left: 10px;
text-transform: capitalize;
&-enabled {
color: $positive;
}
&-disabled {
color: $negative;
}
&-warning {
color: $warning;
}
}
}
.text-subtitle2 {
font-size: 11px;
color: $app-text-grey;
line-height: 16px;
margin-bottom: 4px;
text-align: left;
letter-spacing: 2px;
font-weight: 600;
text-transform: uppercase;
}
.text-table {
font-size: 14px;
font-weight: 700;
letter-spacing: normal;
text-transform: none;
}
}
</style>

View file

@ -0,0 +1,192 @@
<template>
<q-card flat bordered v-bind:class="['panel-service-details', {'panel-service-details-dense':isDense}]">
<q-scroll-area :thumb-style="appThumbStyle" style="height:100%;">
<q-card-section>
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">TYPE</div>
<q-chip
dense
class="app-chip app-chip-entry-points">
{{ data.type }}
</q-chip>
</div>
<div class="col">
<div class="text-subtitle2">PROVIDER</div>
<div class="block-right-text">
<q-avatar class="provider-logo">
<q-icon :name="`img:statics/providers/${data.provider}.svg`" />
</q-avatar>
<div class="block-right-text-label">{{data.provider}}</div>
</div>
</div>
</div>
</q-card-section>
<q-card-section>
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">STATUS</div>
<div class="block-right-text">
<avatar-state :state="data.status | status "/>
<div v-bind:class="['block-right-text-label', `block-right-text-label-${data.status}`]">{{data.status | statusLabel}}</div>
</div>
</div>
</div>
</q-card-section>
<q-card-section>
<div class="row items-start no-wrap">
<div class="col" v-if="data.mirroring">
<div class="text-subtitle2">Main Service</div>
<q-chip
dense
class="app-chip app-chip-name">
{{ data.mirroring.service }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section >
<div class="row items-start no-wrap">
<div class="col" v-if="data.name">
<div class="text-subtitle2">Pass Host Header</div>
<boolean-state :value="data.passHostHeader"/>
</div>
</div>
</q-card-section>
<q-separator v-if="data.weighted && data.weighted.sticky" />
<q-card-section v-if="data.weighted && data.weighted.sticky" >
<div class="row items-start no-wrap">
<div class="text-subtitle1">Sticky: Cookie</div>
</div>
</q-card-section>
<q-card-section v-if="data.weighted && data.weighted.sticky" >
<div class="row items-start no-wrap">
<div class="col" v-if="data.weighted.sticky.cookie && data.weighted.sticky.cookie.name">
<div class="text-subtitle2">NAME</div>
<q-chip
dense
class="app-chip app-chip-entry-points">
{{ data.weighted.sticky.cookie.name }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.weighted && data.weighted.sticky" >
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">SECURE</div>
<boolean-state :value="data.weighted.sticky.cookie.secure"/>
</div>
<div class="col">
<div class="text-subtitle2">HTTP Only</div>
<boolean-state :value="data.weighted.sticky.cookie.httpOnly"/>
</div>
</div>
</q-card-section>
</q-scroll-area>
</q-card>
</template>
<script>
import AvatarState from './AvatarState'
import BooleanState from './BooleanState'
export default {
name: 'PanelServiceDetails',
props: ['data', 'dense'],
components: {
BooleanState,
AvatarState
},
computed: {
isDense () {
return this.dense !== undefined
}
},
filters: {
status (value) {
if (value === 'enabled') {
return 'positive'
}
if (value === 'disabled') {
return 'negative'
}
return value || 'negative'
},
statusLabel (value) {
if (value === 'enabled') {
return 'success'
}
if (value === 'disabled') {
return 'error'
}
return value || 'error'
}
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.panel-service-details {
height: 600px;
&-dense{
height: 400px;
}
.q-card__section {
padding: 24px;
+ .q-card__section {
padding-top: 0;
}
}
.block-right-text{
height: 32px;
line-height: 32px;
.q-avatar{
float: left;
}
&-label{
font-size: 14px;
font-weight: 600;
color: $app-text-grey;
float: left;
margin-left: 10px;
text-transform: capitalize;
&-enabled {
color: $positive;
}
&-disabled {
color: $negative;
}
&-warning {
color: $warning;
}
}
}
.text-subtitle2 {
font-size: 11px;
color: $app-text-grey;
line-height: 16px;
margin-bottom: 4px;
text-align: left;
letter-spacing: 2px;
font-weight: 600;
text-transform: uppercase;
}
.provider-logo {
width: 32px;
height: 32px;
img {
width: 100%;
height: 100%;
}
}
}
</style>

View file

@ -0,0 +1,141 @@
<template>
<q-card flat bordered v-bind:class="['panel-tls']">
<q-scroll-area v-if="data" :thumb-style="appThumbStyle" style="height:100%;">
<q-card-section v-if="data">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">TLS</div>
<boolean-state :value="!!data"/>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.options">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">OPTIONS</div>
<q-chip
dense
class="app-chip app-chip-options">
{{ data.options }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section v-if="protocol == 'tcp'">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">PASSTHROUGH</div>
<boolean-state :value="data.passthrough"></boolean-state>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.certResolver">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">CERTIFICATE RESOLVER</div>
<q-chip
dense
class="app-chip app-chip-service">
{{ data.certResolver }}
</q-chip>
</div>
</div>
</q-card-section>
<q-card-section v-if="data.domains">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">DOMAINS</div>
<div v-for="(domain, key) in data.domains" :key="key" class="flex">
<q-chip
dense
class="app-chip app-chip-rule">
{{ domain.main }}
</q-chip>
<q-chip
v-for="(domain, key) in domain.sans" :key="key"
dense
class="app-chip app-chip-entry-points">
{{ domain }}
</q-chip>
</div>
</div>
</div>
</q-card-section>
</q-scroll-area>
<q-card-section v-else style="height: 100%">
<div class="row items-center" style="height: 100%">
<div class="col-12">
<div class="block-empty"></div>
<div class="q-pb-lg block-empty-logo">
<img alt="empty" src="~assets/middlewares-empty.svg">
</div>
<div class="block-empty-label">There are no<br>TLS configured</div>
</div>
</div>
</q-card-section>
</q-card>
</template>
<script>
import BooleanState from './BooleanState'
export default {
name: 'PanelTLS',
components: {
BooleanState
},
props: ['data', 'protocol']
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.panel-tls {
height: 600px;
.q-card__section {
padding: 24px;
+ .q-card__section {
padding-top: 0;
}
}
.text-subtitle2 {
font-size: 11px;
color: $app-text-grey;
line-height: 16px;
margin-bottom: 4px;
text-align: left;
letter-spacing: 2px;
font-weight: 600;
text-transform: uppercase;
}
.app-chip {
&-entry-points {
display: flex;
height: 100%;
flex-wrap: wrap;
border-width: 0;
margin-bottom: 8px;
/deep/ .q-chip__content{
white-space: normal;
}
}
}
.block-empty {
&-logo {
text-align: center;
}
&-label {
font-size: 20px;
font-weight: 700;
color: #b8b8b8;
text-align: center;
line-height: 1.2;
}
}
}
</style>

View file

@ -0,0 +1,124 @@
<template>
<q-card flat bordered v-bind:class="['panel-services', {'panel-services-dense':isDense}]">
<q-scroll-area :thumb-style="appThumbStyle" style="height:100%;">
<q-card-section>
<div class="row items-start no-wrap">
<div class="col-7">
<div class="text-subtitle2 text-table">Name</div>
</div>
<div class="col-3">
<div class="text-subtitle2 text-table">Weight</div>
</div>
<div class="col-4">
<div class="text-subtitle2 text-table">Provider</div>
</div>
</div>
</q-card-section>
<q-separator />
<div v-for="(service, index) in data.weighted.services" :key="index">
<q-card-section>
<div class="row items-center no-wrap">
<div class="col-7">
<q-chip
dense
class="app-chip app-chip-rule">
{{ service.name }}
</q-chip>
</div>
<div class="col-3">
{{ service.weight }}
</div>
<div class="col-4">
<q-avatar>
<q-icon :name="`img:statics/providers/${getProvider(service)}.svg`" />
</q-avatar>
</div>
</div>
</q-card-section>
<q-separator />
</div>
</q-scroll-area>
</q-card>
</template>
<script>
export default {
name: 'PanelWeightedServices',
props: ['data', 'dense'],
components: {},
computed: {
isDense () {
return this.dense !== undefined
}
},
methods: {
getProvider (service) {
const words = service.name.split('@')
if (words.length !== 2) {
return this.provider
}
return words[1]
}
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.panel-services {
height: 600px;
&-dense{
height: 400px;
}
.q-card__section {
padding: 12px 24px;
+ .q-card__section {
padding-top: 0;
}
}
.block-right-text{
height: 32px;
line-height: 32px;
&-label{
font-size: 14px;
font-weight: 600;
color: $app-text-grey;
float: left;
margin-left: 10px;
text-transform: capitalize;
&-enabled {
color: $positive;
}
&-disabled {
color: $negative;
}
&-warning {
color: $warning;
}
}
}
.text-subtitle2 {
font-size: 11px;
color: $app-text-grey;
line-height: 16px;
margin-bottom: 4px;
text-align: left;
letter-spacing: 2px;
font-weight: 600;
text-transform: uppercase;
}
.text-table {
font-size: 14px;
font-weight: 700;
letter-spacing: normal;
text-transform: none;
}
}
</style>

View file

@ -0,0 +1,26 @@
<template>
<q-avatar text-color="dark">
<q-icon v-if="isTLS" name="eva-shield" />
</q-avatar>
</template>
<script>
export default {
name: 'TLSState',
props: ['isTLS']
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.q-avatar{
font-size: 32px;
border-radius: 4px;
color: green;
.q-icon {
font-size: 22px;
margin: 0 0 0 1px;
}
}
</style>

View file

@ -0,0 +1,113 @@
<template>
<q-toolbar class="row no-wrap items-center">
<q-tabs align="left" inline-label indicator-color="transparent" stretch>
<q-route-tab :to="`/${protocol}/routers`" no-caps :label="`${protocolLabel} Routers`">
<q-badge v-if="routerTotal !== 0" align="middle" :label="routerTotal" class="q-ml-sm"/>
</q-route-tab>
<q-route-tab :to="`/${protocol}/services`" no-caps :label="`${protocolLabel} Services`">
<q-badge v-if="servicesTotal !== 0" align="middle" :label="servicesTotal" class="q-ml-sm"/>
</q-route-tab>
<q-route-tab v-if="protocol === 'http'" :to="`/${protocol}/middlewares`" no-caps :label="`${protocolLabel} Middlewares`">
<q-badge v-if="middlewaresTotal !== 0" align="middle" :label="middlewaresTotal" class="q-ml-sm"/>
</q-route-tab>
</q-tabs>
</q-toolbar>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'ToolBar',
data () {
return {
loadingOverview: true
}
},
computed: {
...mapGetters('core', { overviewAll: 'allOverview' }),
protocol () {
return this.$route.meta.protocol
},
protocolLabel () {
return this.protocol.toUpperCase()
},
routerTotal () {
const data = this.overviewAll.items && this.overviewAll.items[`${this.protocol}`]
return (data && data.routers && data.routers.total) || 0
},
servicesTotal () {
const data = this.overviewAll.items && this.overviewAll.items[`${this.protocol}`]
return (data && data.services && data.services.total) || 0
},
middlewaresTotal () {
const data = this.overviewAll.items && this.overviewAll.items[`${this.protocol}`]
return (data && data.middlewares && data.middlewares.total) || 0
}
},
methods: {
...mapActions('core', { getOverview: 'getOverview' }),
refreshAll () {
this.onGetAll()
},
onGetAll () {
this.getOverview()
.then(body => {
console.log('Success -> toolbar/overview', body)
if (!body) {
this.loadingOverview = false
}
})
.catch(error => {
console.log('Error -> toolbar/overview', error)
})
}
},
created () {
this.refreshAll()
},
beforeDestroy () {
this.$store.commit('core/getOverviewClear')
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.q-toolbar {
min-height: 48px;
padding: 0;
}
.q-tabs {
/deep/ .q-tabs__content {
.q-tab__label {
color: $app-text-grey;
font-size: 16px;
font-weight: 700;
}
.q-badge {
font-size: 13px;
font-weight: 700;
border-radius: 12px;
text-align: center;
align-items: center;
justify-content: center;
min-width: 30px;
padding: 6px;
color: $app-text-grey;
background-color: rgba( $app-text-grey, .1 );
}
.q-tab--active {
.q-tab__label {
color: $accent;
}
.q-badge {
color: $accent;
background-color: rgba( $accent, .1 );
}
}
}
}
</style>

View file

@ -0,0 +1,92 @@
<template>
<q-toolbar class="row no-wrap items-center">
<q-btn-toggle
v-model="getStatus"
class="bar-toggle"
toggle-color="app-toggle"
text-color="app-grey"
size="14px"
no-caps
rounded
unelevated
:options="[
{label: 'All Status', value: ''},
{label: 'Success', value: 'enabled'},
{label: 'Warnings', value: 'warning'},
{label: 'Errors', value: 'disabled'}
]"
/>
<q-space />
<q-input v-model="getFilter" rounded dense outlined type="search" debounce="500" placeholder="Search" bg-color="white" class="bar-search">
<template v-slot:append>
<q-icon name="eva-search-outline" />
</template>
</q-input>
</q-toolbar>
</template>
<script>
export default {
name: 'ToolBarTable',
props: ['status', 'filter'],
components: {
},
data () {
return {
}
},
computed: {
getStatus: {
get () {
return this.status
},
set (newValue) {
this.$emit('update:status', newValue)
}
},
getFilter: {
get () {
return this.filter
},
set (newValue) {
this.$emit('update:filter', newValue)
}
}
},
methods: {
},
created () {
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.q-toolbar {
padding: 0;
/deep/ .bar-toggle {
.q-btn {
font-weight: 600;
margin-right: 12px;
&.q-btn--rounded {
border-radius: 12px;
}
&.bg-app-toggle {
color: $accent !important;
}
}
}
/deep/ .bar-search {
.q-field__inner {
.q-field__control {
border-radius: 12px;
}
}
}
}
</style>

View file

@ -5,6 +5,9 @@
<div class="col">
<div class="text-h6 text-weight-bold">{{getName}}</div>
</div>
<div class="col-auto">
<q-btn :to="getUrl" color="accent" dense flat icon-right="eva-arrow-forward-outline" no-caps label="Explore" size="md" class="text-weight-bold"/>
</div>
</div>
</q-card-section>
<q-card-section>
@ -104,7 +107,7 @@ export default {
} else {
result = num
}
return isNaN(result) ? 0 : result
return isNaN(result) || result < 0 ? 0 : result
},
getWarnings (inPercent = false) {
const num = this.data.warnings
@ -114,7 +117,7 @@ export default {
} else {
result = num
}
return isNaN(result) ? 0 : result
return isNaN(result) || result < 0 ? 0 : result
},
getErrors (inPercent = false) {
const num = this.data.errors
@ -124,7 +127,7 @@ export default {
} else {
result = num
}
return isNaN(result) ? 0 : result
return isNaN(result) || result < 0 ? 0 : result
},
getData () {
return [this.getSuccess(), this.getWarnings(), this.getErrors()]

View file

@ -1,9 +1,9 @@
<template>
<q-card flat bordered>
<q-card flat bordered v-bind:class="['panel-entry', {'panel-entry-detail':type === 'detail'}, {'panel-entry-focus':focus}, {'panel-entry-ex-size':exSize}]">
<q-card-section>
<div class="row items-center no-wrap">
<div class="col">
<div class="text-subtitle2 text-uppercase text-center text-app-grey" style="letter-spacing: 3px;">{{name}}</div>
<div class="text-subtitle2">{{name}}</div>
</div>
</div>
</q-card-section>
@ -16,10 +16,41 @@
<script>
export default {
name: 'PanelEntry',
props: ['address', 'name']
props: ['address', 'name', 'type', 'focus', 'exSize']
}
</script>
<style scoped>
<style scoped lang="scss">
@import "../../css/sass/variables";
.panel-entry {
.text-subtitle2 {
font-weight: 600;
letter-spacing: 3px;
color: $app-text-grey;
text-transform: uppercase;
text-align: center;
}
&-detail{
.text-subtitle2 {
font-size: 11px;
font-weight: 600;
line-height: 11px;
text-align: left;
}
.text-h3 {
font-size: 16px;
text-align: left;
line-height: 16px;
}
}
&-focus {
border: solid 2px $accent;
}
&-ex-size{
.text-h3 {
font-size: 22px;
}
}
}
</style>

View file

@ -1,18 +1,15 @@
<template>
<q-card flat bordered>
<q-card flat bordered v-bind:class="['panel-feature']">
<q-card-section>
<div class="row items-center no-wrap">
<div class="col">
<div class="text-subtitle2 text-uppercase text-center text-app-grey" style="letter-spacing: 3px;">{{featureKey}}</div>
<div class="text-subtitle2">{{featureKey}}</div>
</div>
</div>
</q-card-section>
<q-card-section>
<div class="text-h3 text-center text-weight-bold">
<q-chip
outline
color="primary"
text-color="white"
v-bind:class="['feature-chip', {'feature-chip-string':isString}, {'feature-chip-boolean':isBoolean}, {'feature-chip-boolean-true':isTrue}]">
{{getVal}}
</q-chip>
@ -51,26 +48,37 @@ export default {
<style scoped lang="scss">
@import "../../css/sass/variables";
.panel-feature {
.text-subtitle2 {
font-weight: 600;
letter-spacing: 3px;
color: $app-text-grey;
text-transform: uppercase;
text-align: center;
}
}
.feature-chip {
border-radius: 12px;
border-width: 2px;
height: 56px;
padding: 12px 24px;
color: $primary;
&-string{
border-color: $app-text-grey;
font-size: 24px;
color: $app-text-grey !important;
font-size: 20px;
color: $app-text-grey;
background-color: rgba( $app-text-grey, .1 );
}
&-boolean{
font-size: 40px;
font-weight: 700;
border-color: $negative;
color: $negative !important;
color: $negative;
background-color: rgba( $negative, .1 );
&-true{
border-color: $positive;
color: $positive !important;
color: $positive;
background-color: rgba( $positive, .1 );
}
}

View file

@ -1,65 +0,0 @@
<template>
<q-toolbar class="row no-wrap items-center">
<q-tabs align="left" inline-label indicator-color="transparent" stretch>
<q-route-tab :to="`/http/routers`" no-caps label="HTTP Routers">
<q-badge align="middle" label="10" class="q-ml-sm"/>
</q-route-tab>
<q-route-tab :to="`/http/services`" no-caps label="HTTP Services">
<q-badge align="middle" label="12" class="q-ml-sm"/>
</q-route-tab>
<q-route-tab :to="`/http/middlewares`" no-caps label="HTTP Middlewares">
<q-badge align="middle" label="8" class="q-ml-sm"/>
</q-route-tab>
</q-tabs>
</q-toolbar>
</template>
<script>
export default {
name: 'HTTPToolBar',
data () {
return {
}
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.q-toolbar {
min-height: 48px;
padding: 0;
}
.q-tabs {
/deep/ .q-tabs__content {
.q-tab__label {
color: $app-text-grey;
font-size: 16px;
font-weight: 700;
}
.q-badge {
font-size: 13px;
font-weight: 700;
border-radius: 12px;
text-align: center;
align-items: center;
justify-content: center;
min-width: 30px;
padding: 6px;
color: $app-text-grey;
background-color: rgba( $app-text-grey, .1 );
}
.q-tab--active {
.q-tab__label {
color: $accent;
}
.q-badge {
color: $accent;
background-color: rgba( $accent, .1 );
}
}
}
}
</style>

View file

@ -1,62 +0,0 @@
<template>
<q-toolbar class="row no-wrap items-center">
<q-tabs align="left" inline-label indicator-color="transparent" stretch>
<q-route-tab :to="`/tcp/routers`" no-caps label="TCP Routers">
<q-badge align="middle" label="10" class="q-ml-sm"/>
</q-route-tab>
<q-route-tab :to="`/tcp/services`" no-caps label="TCP Services">
<q-badge align="middle" label="12" class="q-ml-sm"/>
</q-route-tab>
</q-tabs>
</q-toolbar>
</template>
<script>
export default {
name: 'TCPToolBar',
data () {
return {
}
}
}
</script>
<style scoped lang="scss">
@import "../../css/sass/variables";
.q-toolbar {
min-height: 48px;
padding: 0;
}
.q-tabs {
/deep/ .q-tabs__content {
.q-tab__label {
color: $app-text-grey;
font-size: 16px;
font-weight: 700;
}
.q-badge {
font-size: 13px;
font-weight: 700;
border-radius: 12px;
text-align: center;
align-items: center;
justify-content: center;
min-width: 30px;
padding: 6px;
color: $app-text-grey;
background-color: rgba( $app-text-grey, .1 );
}
.q-tab--active {
.q-tab__label {
color: $accent;
}
.q-badge {
color: $accent;
background-color: rgba( $accent, .1 );
}
}
}
}
</style>