Add more pages in the WebUI
This commit is contained in:
parent
2b828765e3
commit
fcc1109e76
82 changed files with 5005 additions and 249 deletions
53
webui/src/components/_commons/BooleanState.vue
Normal file
53
webui/src/components/_commons/BooleanState.vue
Normal 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>
|
249
webui/src/components/_commons/MainTable.vue
Normal file
249
webui/src/components/_commons/MainTable.vue
Normal 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>
|
|
@ -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" />
|
||||
|
|
125
webui/src/components/_commons/PanelHealthCheck.vue
Normal file
125
webui/src/components/_commons/PanelHealthCheck.vue
Normal 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>
|
1204
webui/src/components/_commons/PanelMiddlewares.vue
Normal file
1204
webui/src/components/_commons/PanelMiddlewares.vue
Normal file
File diff suppressed because it is too large
Load diff
133
webui/src/components/_commons/PanelMirroringServices.vue
Normal file
133
webui/src/components/_commons/PanelMirroringServices.vue
Normal 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>
|
204
webui/src/components/_commons/PanelRouterDetails.vue
Normal file
204
webui/src/components/_commons/PanelRouterDetails.vue
Normal 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>
|
123
webui/src/components/_commons/PanelServers.vue
Normal file
123
webui/src/components/_commons/PanelServers.vue
Normal 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>
|
192
webui/src/components/_commons/PanelServiceDetails.vue
Normal file
192
webui/src/components/_commons/PanelServiceDetails.vue
Normal 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>
|
141
webui/src/components/_commons/PanelTLS.vue
Normal file
141
webui/src/components/_commons/PanelTLS.vue
Normal 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>
|
124
webui/src/components/_commons/PanelWeightedServices.vue
Normal file
124
webui/src/components/_commons/PanelWeightedServices.vue
Normal 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>
|
26
webui/src/components/_commons/TLSState.vue
Normal file
26
webui/src/components/_commons/TLSState.vue
Normal 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>
|
113
webui/src/components/_commons/ToolBar.vue
Normal file
113
webui/src/components/_commons/ToolBar.vue
Normal 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>
|
92
webui/src/components/_commons/ToolBarTable.vue
Normal file
92
webui/src/components/_commons/ToolBarTable.vue
Normal 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>
|
|
@ -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()]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue