AdminInstances.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <template lang='pug'>
  2. q-page.admin-terminal
  3. .row.q-pa-md.items-center
  4. .col-auto
  5. img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-network.svg')
  6. .col.q-pl-md
  7. .text-h5.text-primary.animated.fadeInLeft {{ t('admin.instances.title') }}
  8. .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.instances.subtitle') }}
  9. .col-auto.flex
  10. q-btn.q-mr-sm.acrylic-btn(
  11. icon='las la-question-circle'
  12. flat
  13. color='grey'
  14. :href='siteStore.docsBase + `/admin/instances`'
  15. target='_blank'
  16. type='a'
  17. )
  18. q-btn.q-mr-sm.acrylic-btn(
  19. icon='las la-redo-alt'
  20. flat
  21. color='secondary'
  22. :loading='state.loading > 0'
  23. @click='load'
  24. )
  25. q-separator(inset)
  26. .q-pa-md.q-gutter-md
  27. q-card
  28. q-table(
  29. :rows='state.instances'
  30. :columns='instancesHeaders'
  31. row-key='name'
  32. flat
  33. hide-bottom
  34. :rows-per-page-options='[0]'
  35. :loading='state.loading > 0'
  36. )
  37. template(v-slot:body-cell-icon='props')
  38. q-td(:props='props')
  39. q-icon(name='las la-server', color='positive', size='sm')
  40. template(v-slot:body-cell-id='props')
  41. q-td(:props='props')
  42. strong {{props.value}}
  43. div: small.text-grey: strong {{props.row.ip}}
  44. div: small.text-grey {{props.row.dbUser}}
  45. template(v-slot:body-cell-cons='props')
  46. q-td(:props='props')
  47. q-chip(
  48. icon='las la-plug'
  49. square
  50. size='md'
  51. color='blue'
  52. text-color='white'
  53. )
  54. span.font-robotomono {{ props.value }}
  55. template(v-slot:body-cell-subs='props')
  56. q-td(:props='props')
  57. q-chip(
  58. icon='las la-broadcast-tower'
  59. square
  60. size='md'
  61. color='green'
  62. text-color='white'
  63. )
  64. small.text-uppercase {{ props.value }}
  65. template(v-slot:body-cell-firstseen='props')
  66. q-td(:props='props')
  67. span {{props.value}}
  68. div: small.text-grey {{humanizeDate(props.row.dbFirstSeen)}}
  69. template(v-slot:body-cell-lastseen='props')
  70. q-td(:props='props')
  71. span {{props.value}}
  72. div: small.text-grey {{humanizeDate(props.row.dbLastSeen)}}
  73. </template>
  74. <script setup>
  75. import { onMounted, reactive } from 'vue'
  76. import { useMeta, useQuasar } from 'quasar'
  77. import { useI18n } from 'vue-i18n'
  78. import gql from 'graphql-tag'
  79. import { DateTime, Duration, Interval } from 'luxon'
  80. import { useSiteStore } from 'src/stores/site'
  81. // QUASAR
  82. const $q = useQuasar()
  83. // STORES
  84. const siteStore = useSiteStore()
  85. // I18N
  86. const { t } = useI18n()
  87. // META
  88. useMeta({
  89. title: t('admin.instances.title')
  90. })
  91. // DATA
  92. const state = reactive({
  93. instances: [],
  94. loading: 0
  95. })
  96. const instancesHeaders = [
  97. {
  98. align: 'center',
  99. field: 'id',
  100. name: 'icon',
  101. sortable: false,
  102. style: 'width: 15px; padding-right: 0;'
  103. },
  104. {
  105. label: t('common.field.id'),
  106. align: 'left',
  107. field: 'id',
  108. name: 'id',
  109. sortable: true
  110. },
  111. {
  112. label: t('admin.instances.activeConnections'),
  113. align: 'left',
  114. field: 'activeConnections',
  115. name: 'cons',
  116. sortable: true
  117. },
  118. {
  119. label: t('admin.instances.activeListeners'),
  120. align: 'left',
  121. field: 'activeListeners',
  122. name: 'subs',
  123. sortable: true
  124. },
  125. {
  126. label: t('admin.instances.firstSeen'),
  127. align: 'left',
  128. field: 'dbFirstSeen',
  129. name: 'firstseen',
  130. sortable: true,
  131. format: v => DateTime.fromISO(v).toRelative()
  132. },
  133. {
  134. label: t('admin.instances.lastSeen'),
  135. align: 'left',
  136. field: 'dbLastSeen',
  137. name: 'lastseen',
  138. sortable: true,
  139. format: v => DateTime.fromISO(v).toRelative()
  140. }
  141. ]
  142. // METHODS
  143. function humanizeDate (val) {
  144. return DateTime.fromISO(val).toFormat('fff')
  145. }
  146. function humanizeDuration (start, end) {
  147. const dur = Interval.fromDateTimes(DateTime.fromISO(start), DateTime.fromISO(end))
  148. .toDuration(['hours', 'minutes', 'seconds', 'milliseconds'])
  149. return Duration.fromObject({
  150. ...dur.hours > 0 && { hours: dur.hours },
  151. ...dur.minutes > 0 && { minutes: dur.minutes },
  152. ...dur.seconds > 0 && { seconds: dur.seconds },
  153. ...dur.milliseconds > 0 && { milliseconds: dur.milliseconds }
  154. }).toHuman({ unitDisplay: 'narrow', listStyle: 'short' })
  155. }
  156. async function load () {
  157. state.loading++
  158. try {
  159. const resp = await APOLLO_CLIENT.query({
  160. query: gql`
  161. query getSystemInstances {
  162. systemInstances {
  163. id
  164. activeConnections
  165. activeListeners
  166. dbUser
  167. dbFirstSeen
  168. dbLastSeen
  169. ip
  170. }
  171. }
  172. `,
  173. fetchPolicy: 'network-only'
  174. })
  175. state.instances = resp?.data?.systemInstances
  176. } catch (err) {
  177. $q.notify({
  178. type: 'negative',
  179. message: 'Failed to load list of instances.',
  180. caption: err.message
  181. })
  182. }
  183. state.loading--
  184. }
  185. // MOUNTED
  186. onMounted(() => {
  187. load()
  188. })
  189. </script>