123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- <script setup lang="ts">
- import type ReconnectingWebSocket from 'reconnecting-websocket'
- import type { Ref } from 'vue'
- import type { Node } from '@/api/environment'
- import Icon, { LinkOutlined, SendOutlined, ThunderboltOutlined } from '@ant-design/icons-vue'
- import analytic from '@/api/analytic'
- import environment from '@/api/environment'
- import logo from '@/assets/img/logo.png'
- import pulse from '@/assets/svg/pulse.svg?component'
- import { formatDateTime } from '@/lib/helper'
- import { useSettingsStore } from '@/pinia'
- import { version } from '@/version.json'
- import NodeAnalyticItem from './components/NodeAnalyticItem.vue'
- const data = ref([]) as Ref<Node[]>
- const nodeMap = computed(() => {
- const o = {} as Record<number, Node>
- data.value.forEach(v => {
- o[v.id] = v
- })
- return o
- })
- let websocket: ReconnectingWebSocket | WebSocket
- onMounted(() => {
- environment.getList({ enabled: true }).then(r => {
- data.value.push(...r.data)
- })
- })
- onMounted(() => {
- websocket = analytic.nodes()
- websocket.onmessage = async m => {
- const nodes = JSON.parse(m.data)
- Object.keys(nodes).forEach((v: string) => {
- const key = Number.parseInt(v)
- // update node online status
- if (nodeMap.value[key]) {
- Object.assign(nodeMap.value[key], nodes[key])
- nodeMap.value[key].response_at = new Date()
- }
- })
- }
- })
- onUnmounted(() => {
- websocket.close()
- })
- const { environment: env } = useSettingsStore()
- function linkStart(node: Node) {
- env.id = node.id
- env.name = node.name
- }
- const visible = computed(() => {
- if (env.id > 0)
- return false
- else
- return data.value?.length
- })
- </script>
- <template>
- <ACard
- v-if="visible"
- class="env-list-card"
- :title="$gettext('Environments')"
- >
- <AList
- item-layout="horizontal"
- :data-source="data"
- >
- <template #renderItem="{ item }">
- <AListItem>
- <AListItemMeta>
- <template #title>
- <div class="mb-1">
- {{ item.name }}
- <ATag
- v-if="item.status"
- color="blue"
- class="ml-2"
- >
- {{ $gettext('Online') }}
- </ATag>
- <ATag
- v-else
- color="error"
- class="ml-2"
- >
- {{ $gettext('Offline') }}
- </ATag>
- </div>
- <template v-if="item.status">
- <div class="runtime-meta mr-2 mb-1">
- <Icon :component="pulse" /> {{ formatDateTime(item.response_at) }}
- </div>
- <div class="runtime-meta mr-2 mb-1">
- <ThunderboltOutlined />{{ item.version }}
- </div>
- </template>
- <div class="runtime-meta">
- <LinkOutlined />{{ item.url }}
- </div>
- </template>
- <template #avatar>
- <AAvatar :src="logo" />
- </template>
- <template #description>
- <div class="md:flex lg:flex justify-between md:items-center">
- <NodeAnalyticItem
- :item="item"
- class="mt-1 mb-1"
- />
- <AButton
- v-if="item.version === version"
- type="primary"
- :disabled="!item.status || env.id === item.id"
- ghost
- @click="linkStart(item)"
- >
- <SendOutlined />
- {{ env.id !== item.id ? $gettext('Link Start') : $gettext('Connected') }}
- </AButton>
- <ATooltip
- v-else
- placement="topLeft"
- >
- <template #title>
- {{ $gettext('The remote Nginx UI version is not compatible with the local Nginx UI version. '
- + 'To avoid potential errors, please upgrade the remote Nginx UI to match the local version.') }}
- </template>
- <AButton
- ghost
- disabled
- >
- <SendOutlined />
- {{ $gettext('Link Start') }}
- </AButton>
- </ATooltip>
- </div>
- </template>
- </AListItemMeta>
- </AListItem>
- </template>
- </AList>
- </ACard>
- </template>
- <style scoped lang="less">
- :deep(.ant-list-item-meta-title) {
- display: flex;
- align-items: center;
- @media (max-width: 700px) {
- display: block;
- }
- }
- .env-list-card {
- margin-top: 16px;
- .runtime-meta {
- display: inline-flex;
- @media (max-width: 700px) {
- align-items: center;
- }
- font-weight: 400;
- color: #9b9b9b;
- .anticon {
- margin-right: 4px;
- }
- }
- }
- :deep(.ant-list-item-action) {
- @media(max-width: 500px) {
- display: none;
- }
- }
- </style>
|