| 
					
				 | 
			
			
				@@ -0,0 +1,208 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+<template lang='pug'> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+q-page.admin-terminal 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  .row.q-pa-md.items-center 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    .col-auto 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-network.svg') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    .col.q-pl-md 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      .text-h5.text-primary.animated.fadeInLeft {{ t('admin.instances.title') }} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.instances.subtitle') }} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    .col-auto.flex 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      q-btn.q-mr-sm.acrylic-btn( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        icon='las la-question-circle' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        flat 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        color='grey' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        :href='siteStore.docsBase + `/admin/instances`' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        target='_blank' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        type='a' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      q-btn.q-mr-sm.acrylic-btn( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        icon='las la-redo-alt' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        flat 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        color='secondary' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        :loading='state.loading > 0' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        @click='load' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  q-separator(inset) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  .q-pa-md.q-gutter-md 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    q-card.shadow-1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      q-table( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        :rows='state.instances' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        :columns='instancesHeaders' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        row-key='name' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        flat 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        hide-bottom 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        :rows-per-page-options='[0]' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        :loading='state.loading > 0' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        template(v-slot:body-cell-icon='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          q-td(:props='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            q-icon(name='las la-server', color='positive', size='sm') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        template(v-slot:body-cell-id='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          q-td(:props='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            strong {{props.value}} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            div: small.text-grey: strong {{props.row.ip}} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            div: small.text-grey {{props.row.dbUser}} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        template(v-slot:body-cell-cons='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          q-td(:props='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            q-chip( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              icon='las la-plug' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              square 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              size='md' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              color='blue' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              text-color='white' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              span.font-robotomono {{ props.value }} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        template(v-slot:body-cell-subs='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          q-td(:props='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            q-chip( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              icon='las la-broadcast-tower' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              square 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              size='md' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              color='green' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              text-color='white' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              small.text-uppercase {{ props.value }} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        template(v-slot:body-cell-firstseen='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          q-td(:props='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            span {{props.value}} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            div: small.text-grey {{humanizeDate(props.row.dbFirstSeen)}} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        template(v-slot:body-cell-lastseen='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          q-td(:props='props') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            span {{props.value}} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            div: small.text-grey {{humanizeDate(props.row.dbLastSeen)}} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+</template> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+<script setup> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import { onMounted, reactive } from 'vue' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import { useMeta, useQuasar } from 'quasar' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import { useI18n } from 'vue-i18n' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import gql from 'graphql-tag' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import { DateTime, Duration, Interval } from 'luxon' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import { useSiteStore } from 'src/stores/site' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// QUASAR 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const $q = useQuasar() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// STORES 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const siteStore = useSiteStore() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// I18N 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const { t } = useI18n() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// META 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+useMeta({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  title: t('admin.instances.title') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+}) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// DATA 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const state = reactive({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  instances: [], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  loading: 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+}) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const instancesHeaders = [ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    align: 'center', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    field: 'id', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    name: 'icon', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    sortable: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    style: 'width: 15px; padding-right: 0;' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    label: t('common.field.id'), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    align: 'left', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    field: 'id', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    name: 'id', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    sortable: true 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    label: t('admin.instances.activeConnections'), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    align: 'left', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    field: 'activeConnections', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    name: 'cons', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    sortable: true 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    label: t('admin.instances.activeListeners'), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    align: 'left', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    field: 'activeListeners', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    name: 'subs', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    sortable: true 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    label: t('admin.instances.firstSeen'), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    align: 'left', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    field: 'dbFirstSeen', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    name: 'firstseen', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    sortable: true, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    format: v => DateTime.fromISO(v).toRelative() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    label: t('admin.instances.lastSeen'), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    align: 'left', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    field: 'dbLastSeen', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    name: 'lastseen', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    sortable: true, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    format: v => DateTime.fromISO(v).toRelative() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// METHODS 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+function humanizeDate (val) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  return DateTime.fromISO(val).toFormat('fff') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+function humanizeDuration (start, end) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  const dur = Interval.fromDateTimes(DateTime.fromISO(start), DateTime.fromISO(end)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    .toDuration(['hours', 'minutes', 'seconds', 'milliseconds']) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  return Duration.fromObject({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ...dur.hours > 0 && { hours: dur.hours }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ...dur.minutes > 0 && { minutes: dur.minutes }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ...dur.seconds > 0 && { seconds: dur.seconds }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ...dur.milliseconds > 0 && { milliseconds: dur.milliseconds } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }).toHuman({ unitDisplay: 'narrow', listStyle: 'short' }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+async function load () { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  state.loading++ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  try { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const resp = await APOLLO_CLIENT.query({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      query: gql` 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        query getSystemInstances { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          systemInstances { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            id 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            activeConnections 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            activeListeners 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            dbUser 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            dbFirstSeen 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            dbLastSeen 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ip 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      `, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      fetchPolicy: 'network-only' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    state.instances = resp?.data?.systemInstances 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } catch (err) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    $q.notify({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      type: 'negative', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      message: 'Failed to load list of instances.', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      caption: err.message 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  state.loading-- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// MOUNTED 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+onMounted(() => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  load() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+}) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+</script> 
			 |