diff --git a/.env b/.env
index 0582fd1e34..04cd8521ff 100644
--- a/.env
+++ b/.env
@@ -1,2 +1 @@
VUE_APP_DATA_TIMEOUT=500
-VUE_APP_GENERAL_CONFIG=http://localhost:5681/config
diff --git a/.env.development b/.env.development
index d641ef60f3..743453e720 100644
--- a/.env.development
+++ b/.env.development
@@ -1,3 +1,3 @@
NODE_ENV=development
-VUE_APP_MOCK_API_ENABLED=true
+VUE_APP_MOCK_API_ENABLED=false
VUE_APP_KUMA_CONFIG=/dev-api-config.json
diff --git a/src/App.vue b/src/App.vue
index 682c5e9341..0c318a8c39 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -113,6 +113,15 @@ export default {
// fetch the config
this.$store.dispatch('getConfig')
+ .then(() => {
+ const config = this.$store.getters.getConfig
+ const mode = config.mode
+
+ // set Kuma's current mode in localStorage.
+ // if the mode is `global`, this denotes that it's
+ // running in Multicluster mode.
+ localStorage.setItem('kumaMode', mode)
+ })
// set the selected mesh in localStorage
const mesh = () => {
diff --git a/src/components/Global/Header.vue b/src/components/Global/Header.vue
index 4eb8c471bb..cbc034286b 100644
--- a/src/components/Global/Header.vue
+++ b/src/components/Global/Header.vue
@@ -21,10 +21,18 @@
v-if="showStatus"
class="py-1 md:py-0 md:px-4"
>
-
+
+
+ {{ statusContent }}
+
+ Multicluster
+
+
+
@@ -52,7 +60,9 @@ export default {
// this checks the status of the API itself
status: 'getStatus',
// the currently selected mesh
- currentMesh: 'getSelectedMesh'
+ currentMesh: 'getSelectedMesh',
+ // the status of multicluster
+ multicluster: 'getMulticlusterStatus'
}),
showStatus () {
return !this.$route.meta.hideStatus && this.status === 'OK'
@@ -63,8 +73,11 @@ export default {
},
methods: {
getGuiStatus () {
+ // these localStorage items are set on app launch
const env = localStorage.getItem('kumaEnv')
const apiUrl = localStorage.getItem('kumaApiUrl')
+
+ // get the other values from our state
const tagline = this.$store.getters.getTagline
const version = this.$store.getters.getVersion
@@ -116,6 +129,23 @@ export default {
margin-left: auto;
}
+.status-badge {
+ --KBadgeWidth: auto;
+ --KBadgePaddingX: var(--spacing-sm);
+}
+
+@media screen and (min-width: 990px) {
+ .status-badge {
+ margin-left: var(--spacing-sm);
+ }
+}
+
+@media screen and (max-width: 989px) {
+ .status-badge {
+ margin-top: var(--spacing-sm);
+ }
+}
+
@media screen and (max-width: 599px) {
.upgrade-check-wrapper {
display: none;
diff --git a/src/components/Sidebar/MenuList.vue b/src/components/Sidebar/MenuList.vue
index ad9ff1a8b2..32ec13513b 100644
--- a/src/components/Sidebar/MenuList.vue
+++ b/src/components/Sidebar/MenuList.vue
@@ -1,6 +1,6 @@
@@ -201,6 +201,7 @@ export default {
{ label: 'Status', key: 'status' },
{ label: 'Name', key: 'name' },
{ label: 'Mesh', key: 'mesh' },
+ { label: 'DP Type', key: 'type' },
{ label: 'Tags', key: 'tags' },
{ label: 'Last Connected', key: 'lastConnected' },
{ label: 'Last Updated', key: 'lastUpdated' },
@@ -260,11 +261,15 @@ export default {
const entity = this.entity
const shareUrl = () => {
- if (this.$route.query.ns) {
- return this.$route.fullPath
+ if (entity.basicData) {
+ if (this.$route.query.ns) {
+ return this.$route.fullPath
+ }
+
+ return `${urlRoot}${this.$route.fullPath}?ns=${entity.basicData.name}`
}
- return `${urlRoot}${this.$route.fullPath}?ns=${entity.name}`
+ return null
}
return shareUrl()
@@ -325,12 +330,12 @@ export default {
*/
const endpoint = () => {
if (mesh === 'all') {
- return this.$api.getAllDataplanes(params)
+ return this.$api.getAllDataplaneOverviews(params)
} else if ((query && query.length) && mesh !== 'all') {
- return this.$api.getDataplaneOverviewsFromMesh(mesh, query)
+ return this.$api.getDataplaneOverviewFromMesh(mesh, query)
}
- return this.$api.getAllDataplanesFromMesh(mesh)
+ return this.$api.getAllDataplaneOverviewsFromMesh(mesh, params)
}
/**
@@ -338,7 +343,7 @@ export default {
* and then collecting them into an array.
*/
const dpFetcher = (mesh, name, finalArr) => {
- this.$api.getDataplaneOverviewsFromMesh(mesh, name)
+ this.$api.getDataplaneOverviewFromMesh(mesh, name)
.then(response => {
const placeholder = 'n/a'
@@ -351,49 +356,101 @@ export default {
const updateTimes = []
/**
- * Iterate through the networking inbound or gateway data
+ * Dataplane type conditions
*/
- const inbound = response.dataplane.networking.inbound
- const gateway = response.dataplane.networking.gateway
+ const inbound = response.dataplane.networking.inbound || null
+ const gateway = response.dataplane.networking.gateway || null
+ const ingress = response.dataplane.networking.ingress || null
- if (inbound || gateway) {
+ /**
+ * Determine the type of Dataplane it is
+ */
+ const dataplaneType = () => {
+ if (gateway) {
+ return 'Gateway'
+ } else if (ingress) {
+ return 'Ingress'
+ }
+
+ return 'Standard'
+ }
+
+ /**
+ * Handle our tag collections based on the dataplane type.
+ * Currently, this is collecting all tags found in case
+ * there is a condition where inbound, gateway, or ingress
+ * might all be present as opposed to one group.
+ */
+ if (inbound || gateway || ingress) {
+ const final = []
+
+ // inbound tags
if (inbound) {
- /** inbound */
for (let i = 0; i < inbound.length; i++) {
- const rawTags = inbound[i].tags
+ const rawTags = inbound[i].tags || null
- const final = []
- const tagKeys = Object.keys(rawTags)
- const tagVals = Object.values(rawTags)
+ if (rawTags) {
+ const tagKeys = Object.keys(rawTags)
+ const tagVals = Object.values(rawTags)
- for (let x = 0; x < tagKeys.length; x++) {
- final.push({
- label: tagKeys[x],
- value: tagVals[x]
- })
+ for (let x = 0; x < tagKeys.length; x++) {
+ final.push({
+ label: tagKeys[x],
+ value: tagVals[x]
+ })
+ }
}
+ }
+ }
+
+ // gateway tags
+ if (gateway) {
+ const gatewayItems = gateway.tags || null
- tags = final
+ if (gatewayItems) {
+ for (let i = 0; i < Object.keys(gatewayItems).length; i++) {
+ const tagKeys = Object.keys(gatewayItems)
+ const tagVals = Object.values(gatewayItems)
+
+ for (let x = 0; x < tagKeys.length; x++) {
+ final.push({
+ label: tagKeys[x],
+ value: tagVals[x]
+ })
+ }
+ }
}
- } else if (gateway) {
- /** gateway */
- const items = gateway.tags
+ }
- for (let i = 0; i < Object.keys(items).length; i++) {
- const final = []
- const tagKeys = Object.keys(items)
- const tagVals = Object.values(items)
+ // ingress tags
+ if (ingress) {
+ for (let i = 0; i < ingress.length; i++) {
+ const ingressTags = ingress[i].tags || null
+ const ingressService = ingress[i].service || null
- for (let x = 0; x < tagKeys.length; x++) {
+ if (ingressService) {
final.push({
- label: tagKeys[x],
- value: tagVals[x]
+ label: 'service',
+ value: ingressService
})
}
- tags = final
+ if (ingressTags) {
+ const tagKeys = Object.keys(ingressTags)
+ const tagVals = Object.values(ingressTags)
+
+ for (let x = 0; x < tagKeys.length; x++) {
+ final.push({
+ label: tagKeys[x],
+ value: tagVals[x]
+ })
+ }
+ }
}
}
+
+ // define the new list
+ tags = final
} else {
tags = 'none'
}
@@ -477,7 +534,7 @@ export default {
lastConnected: lastConnected,
lastUpdated: lastUpdated,
totalUpdates: totalUpdates,
- type: 'dataplane'
+ type: dataplaneType()
})
this.sortEntities(finalArr)
@@ -584,7 +641,7 @@ export default {
let data = null
try {
- const res = await this.$api.getDataplaneOverviewsFromMesh(entityMesh, entity.name)
+ const res = await this.$api.getDataplaneOverviewFromMesh(entityMesh, entity.name)
if (res.dataplaneInsight.mTLS) {
const mtls = res.dataplaneInsight.mTLS
@@ -623,16 +680,91 @@ export default {
return data
}
- // determine between inbound and gateway modes
- // and then get the tags from which condition applies.
- const tagSrc = (response.networking.inbound && response.networking.inbound.length > 0)
- ? response.networking.inbound[0].tags
- : response.networking.gateway.tags
+ // combine all tags into a single object
+ const fullTagSrc = () => {
+ const src = response.networking || null
+ const inbound = src.inbound || null
+ const gateway = src.gateway || null
+ const ingress = src.ingress || null
+
+ if (inbound || gateway || ingress) {
+ const final = []
+
+ // inbound tags
+ if (inbound) {
+ for (let i = 0; i < inbound.length; i++) {
+ const rawTags = inbound[i].tags || null
+
+ if (rawTags) {
+ const tagKeys = Object.keys(rawTags)
+ const tagVals = Object.values(rawTags)
+
+ for (let x = 0; x < tagKeys.length; x++) {
+ final.push({
+ label: tagKeys[x],
+ value: tagVals[x]
+ })
+ }
+ }
+ }
+ }
+
+ // gateway tags
+ if (gateway) {
+ const gatewayItems = gateway.tags || null
+
+ if (gatewayItems) {
+ for (let i = 0; i < Object.keys(gatewayItems).length; i++) {
+ const tagKeys = Object.keys(gatewayItems)
+ const tagVals = Object.values(gatewayItems)
+
+ for (let x = 0; x < tagKeys.length; x++) {
+ final.push({
+ label: tagKeys[x],
+ value: tagVals[x]
+ })
+ }
+ }
+ }
+ }
+
+ // ingress tags
+ if (ingress) {
+ for (let i = 0; i < ingress.length; i++) {
+ const ingressTags = ingress[i].tags || null
+ const ingressService = ingress[i].service || null
+
+ if (ingressService) {
+ final.push({
+ label: 'service',
+ value: ingressService
+ })
+ }
+
+ if (ingressTags) {
+ const tagKeys = Object.keys(ingressTags)
+ const tagVals = Object.values(ingressTags)
+
+ for (let x = 0; x < tagKeys.length; x++) {
+ final.push({
+ label: tagKeys[x],
+ value: tagVals[x]
+ })
+ }
+ }
+ }
+ }
+
+ return final
+ }
+
+ return null
+ }
const newEntity = async () => {
return {
basicData: { ...getSome(response, selected) },
- tags: { ...tagSrc },
+ tags: { ...fullTagSrc() },
mtls: await getMTLSData()
}
}
diff --git a/src/views/Entities/GatewayDataplanes.vue b/src/views/Entities/GatewayDataplanes.vue
new file mode 100644
index 0000000000..fdb2f5bda7
--- /dev/null
+++ b/src/views/Entities/GatewayDataplanes.vue
@@ -0,0 +1,810 @@
+
+
+
+
+
+
+
+ +
+
+ Create Dataplane
+
+
+
+ ←
+
+ View All
+
+
+
+
+
+
+
+
+
+
{{ tabGroupTitle }}
+
+
+
+
+
+
+
+
+
+
+ -
+
{{ key }}
+
+ {{ val }}
+
+
+
+
+
+
+
Tags
+
+
+
+ {{ val.label }}:
+
+
+ {{ val.value }}
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
{{ val.label }}
+
+ {{ val.value }}
+
+
+
+
+
+ This Dataplane does not yet have mTLS configured —
+
+ Learn About Certificates in Kuma
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/Entities/IngressDataplanes.vue b/src/views/Entities/IngressDataplanes.vue
new file mode 100644
index 0000000000..fc2cde2694
--- /dev/null
+++ b/src/views/Entities/IngressDataplanes.vue
@@ -0,0 +1,810 @@
+
+
+
+
+
+
+
+ +
+
+ Create Dataplane
+
+
+
+ ←
+
+ View All
+
+
+
+
+
+
+
+
+
+
{{ tabGroupTitle }}
+
+
+
+
+
+
+
+
+
+
+ -
+
{{ key }}
+
+ {{ val }}
+
+
+
+
+
+
+
Tags
+
+
+
+ {{ val.label }}:
+
+
+ {{ val.value }}
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
{{ val.label }}
+
+ {{ val.value }}
+
+
+
+
+
+ This Dataplane does not yet have mTLS configured —
+
+ Learn About Certificates in Kuma
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/Entities/Meshes.vue b/src/views/Entities/Meshes.vue
index 2b9f0a4e4f..2888e23457 100644
--- a/src/views/Entities/Meshes.vue
+++ b/src/views/Entities/Meshes.vue
@@ -5,7 +5,6 @@
:page-size="pageSize"
:has-error="hasError"
:is-loading="isLoading"
- :is-empty="isEmpty"
:empty-state="empty_state"
:display-data-table="true"
:table-data="tableData"
@@ -38,13 +37,21 @@
+
+
+
{{ tabGroupTitle }}
+
+
+
import { mapState } from 'vuex'
import { getSome, humanReadableDate, getOffset } from '@/helpers'
+import EntityURLControl from '@/components/Utils/EntityURLControl'
import sortEntities from '@/mixins/EntitySorter'
import FrameSkeleton from '@/components/Skeletons/FrameSkeleton'
import Pagination from '@/components/Pagination'
@@ -158,6 +166,7 @@ export default {
title: 'Meshes'
},
components: {
+ EntityURLControl,
FrameSkeleton,
Pagination,
DataOverview,
@@ -272,6 +281,19 @@ export default {
},
countCols () {
return Math.ceil(this.counts.length / this.itemsPerCol)
+ },
+ shareUrl () {
+ const urlRoot = `${window.location.origin}/#`
+
+ const shareUrl = () => {
+ if (this.$route.query.ns) {
+ return this.$route.fullPath
+ }
+
+ return `${urlRoot}${this.$route.fullPath}`
+ }
+
+ return shareUrl()
}
},
watch: {
@@ -368,15 +390,18 @@ export default {
this.tableData.data = [...items]
this.tableDataIsEmpty = false
+ this.isEmpty = false
} else {
this.tableData.data = []
this.tableDataIsEmpty = true
+ this.isEmpty = true
this.getEntity(null)
}
})
.catch(error => {
this.hasError = true
+ this.isEmpty = true
console.error(error)
})
diff --git a/src/views/Entities/RemoteCP.vue b/src/views/Entities/RemoteCP.vue
new file mode 100644
index 0000000000..9c63a74e29
--- /dev/null
+++ b/src/views/Entities/RemoteCP.vue
@@ -0,0 +1,358 @@
+
+
+
+
+ {{ pageTitle }}
+
+
+
+
+
+ Kuma is running in Standalone mode.
+
+
+
+ To access this page, you must be running in Multicluster mode.
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ tabGroupTitle }}
+
+
+
+
+
+
+ -
+
+ {{ key }}
+
+
+
+ {{ value }}
+
+
+
+ {{ value }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/Onboarding/GetStarted.vue b/src/views/Onboarding/GetStarted.vue
index 22645b2d71..838be2220d 100644
--- a/src/views/Onboarding/GetStarted.vue
+++ b/src/views/Onboarding/GetStarted.vue
@@ -102,7 +102,6 @@
Next Step
@@ -161,23 +160,13 @@
Dataplanes.
-
-
- Kubernetes Dataplane Wizard
-
-
-
-
- Skip to Dashboard
-
-
+
+ Kubernetes Dataplane Wizard
+
@@ -190,19 +179,12 @@
Dataplanes.
-
-
- Universal Dataplane Wizard
-
-
-
-
- Skip to Dashboard
-
-
+
+ Universal Dataplane Wizard
+
@@ -215,6 +197,16 @@
-->
+
@@ -355,7 +347,7 @@ export default {
padding: var(--spacing-md) 0;
margin: var(--spacing-md) 0;
border-top: 1px solid var(--tblack-10);
- border-bottom: 1px solid var(--tblack-10);
+ // border-bottom: 1px solid var(--tblack-10);
}
.app-source-check {
@@ -459,6 +451,12 @@ export default {
}
}
+.extra-controls {
+ border-top: 1px solid var(--tblack-10);
+ margin-top: var(--spacing-md);
+ padding-top: var(--spacing-sm);
+}
+
@media (min-width: 768px) {
.dataplane-global-status {
flex: 1;
diff --git a/src/views/Overview.vue b/src/views/Overview.vue
index 0332c54dde..e2b17c9d86 100644
--- a/src/views/Overview.vue
+++ b/src/views/Overview.vue
@@ -87,7 +87,8 @@ export default {
...mapGetters({
title: 'getTagline',
environment: 'getEnvironment',
- selectedMesh: 'getSelectedMesh'
+ selectedMesh: 'getSelectedMesh',
+ multicluster: 'getMulticlusterStatus'
}),
pageTitle () {
const metaTitle = this.$route.meta.title
@@ -184,6 +185,18 @@ export default {
}
]
+ // if Kuma is running in multicluster mode
+ if (this.multicluster) {
+ const clusters = {
+ metric: 'Remote CPs',
+ value: this.$store.state.totalClusters,
+ url: '/remote-cp'
+ }
+
+ tableData.push(clusters)
+ }
+
+ // if the user is viewing data for all meshes
if (mesh !== 'all') {
// if the user is viewing the overview with a mesh selected,
// we hide the mesh count from the metrics grid
@@ -213,6 +226,10 @@ export default {
methods: {
init () {
this.getCounts()
+
+ if (this.multicluster) {
+ this.$store.dispatch('fetchTotalClusterCount')
+ }
},
getCounts () {
let actions
diff --git a/src/views/Wizard/components/StepSkeleton.vue b/src/views/Wizard/components/StepSkeleton.vue
index a1ec94ea6f..11a4e0adb0 100644
--- a/src/views/Wizard/components/StepSkeleton.vue
+++ b/src/views/Wizard/components/StepSkeleton.vue
@@ -334,6 +334,7 @@ $bp-max-width: 1219px;
border-radius: 6px;
padding: var(--spacing-md);
font-size: var(--base-font-size);
+ overflow: auto;
}
}
diff --git a/src/views/Wizard/views/DataplaneKubernetes.vue b/src/views/Wizard/views/DataplaneKubernetes.vue
index d747e271d5..71ae9a2d22 100644
--- a/src/views/Wizard/views/DataplaneKubernetes.vue
+++ b/src/views/Wizard/views/DataplaneKubernetes.vue
@@ -606,6 +606,26 @@
by {{ title }}.
+
+ Example
+
+ Below is an example of a Dataplane resource output:
+
+
+ apiVersion: 'kuma.io/v1alpha1'
+kind: Dataplane
+mesh: default
+metadata:
+ name: dp-echo-1
+networking:
+ address: 10.0.0.1
+ inbound:
+ - port: 10000
+ servicePort: 9000
+ tags:
+ service: echo
+
+
@@ -676,6 +696,9 @@ export default {
sidebarContent: [
{
name: 'dataplane'
+ },
+ {
+ name: 'example'
}
],
startScanner: false,