mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-05-06 22:12:23 +08:00
@@ -21,6 +21,36 @@ type PerformanceClient struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
mutex sync.RWMutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (c *PerformanceClient) trySend(message interface{}) bool {
|
||||
c.mutex.RLock()
|
||||
if c.closed {
|
||||
c.mutex.RUnlock()
|
||||
return false
|
||||
}
|
||||
|
||||
select {
|
||||
case c.send <- message:
|
||||
c.mutex.RUnlock()
|
||||
return true
|
||||
default:
|
||||
c.mutex.RUnlock()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PerformanceClient) closeSendChannel() {
|
||||
c.mutex.Lock()
|
||||
if c.closed {
|
||||
c.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
close(c.send)
|
||||
c.closed = true
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
// PerformanceHub manages WebSocket connections for Nginx performance monitoring
|
||||
@@ -60,20 +90,18 @@ func (h *PerformanceHub) run() {
|
||||
case client := <-h.register:
|
||||
h.mutex.Lock()
|
||||
h.clients[client] = true
|
||||
currentClients := len(h.clients)
|
||||
h.mutex.Unlock()
|
||||
logger.Debug("Nginx performance client connected, total clients:", len(h.clients))
|
||||
logger.Debug("Nginx performance client connected, total clients:", currentClients)
|
||||
|
||||
// Send initial data to the new client
|
||||
go h.sendPerformanceDataToClient(client)
|
||||
|
||||
case client := <-h.unregister:
|
||||
h.mutex.Lock()
|
||||
if _, ok := h.clients[client]; ok {
|
||||
delete(h.clients, client)
|
||||
close(client.send)
|
||||
currentClients, removed := h.removeClient(client)
|
||||
if removed {
|
||||
logger.Debug("Nginx performance client disconnected, total clients:", currentClients)
|
||||
}
|
||||
h.mutex.Unlock()
|
||||
logger.Debug("Nginx performance client disconnected, total clients:", len(h.clients))
|
||||
|
||||
case <-h.ticker.C:
|
||||
// Send performance data to all connected clients
|
||||
@@ -82,24 +110,55 @@ func (h *PerformanceHub) run() {
|
||||
case <-kernel.Context.Done():
|
||||
logger.Debug("PerformanceHub: Context cancelled, closing WebSocket")
|
||||
// Shutdown all clients
|
||||
h.mutex.Lock()
|
||||
for client := range h.clients {
|
||||
close(client.send)
|
||||
delete(h.clients, client)
|
||||
for _, client := range h.activeClients() {
|
||||
h.removeClient(client)
|
||||
}
|
||||
h.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PerformanceHub) activeClients() []*PerformanceClient {
|
||||
h.mutex.RLock()
|
||||
if len(h.clients) == 0 {
|
||||
h.mutex.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
clients := make([]*PerformanceClient, 0, len(h.clients))
|
||||
for client := range h.clients {
|
||||
clients = append(clients, client)
|
||||
}
|
||||
h.mutex.RUnlock()
|
||||
return clients
|
||||
}
|
||||
|
||||
func (h *PerformanceHub) removeClient(client *PerformanceClient) (remaining int, removed bool) {
|
||||
h.mutex.Lock()
|
||||
_, removed = h.clients[client]
|
||||
if removed {
|
||||
delete(h.clients, client)
|
||||
}
|
||||
remaining = len(h.clients)
|
||||
h.mutex.Unlock()
|
||||
|
||||
if removed {
|
||||
client.closeSendChannel()
|
||||
}
|
||||
return remaining, removed
|
||||
}
|
||||
|
||||
// sendPerformanceDataToClient sends performance data to a specific client
|
||||
func (h *PerformanceHub) sendPerformanceDataToClient(client *PerformanceClient) {
|
||||
select {
|
||||
case <-client.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
response := performance.GetPerformanceData()
|
||||
|
||||
select {
|
||||
case client.send <- response:
|
||||
default:
|
||||
if !client.trySend(response) {
|
||||
// Channel is full, remove client
|
||||
h.unregister <- client
|
||||
}
|
||||
@@ -107,27 +166,20 @@ func (h *PerformanceHub) sendPerformanceDataToClient(client *PerformanceClient)
|
||||
|
||||
// broadcastPerformanceData sends performance data to all connected clients
|
||||
func (h *PerformanceHub) broadcastPerformanceData() {
|
||||
h.mutex.RLock()
|
||||
|
||||
// Check if there are any connected clients
|
||||
if len(h.clients) == 0 {
|
||||
h.mutex.RUnlock()
|
||||
clients := h.activeClients()
|
||||
if len(clients) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Only get performance data if there are connected clients
|
||||
response := performance.GetPerformanceData()
|
||||
|
||||
for client := range h.clients {
|
||||
select {
|
||||
case client.send <- response:
|
||||
default:
|
||||
// Channel is full, remove client
|
||||
close(client.send)
|
||||
delete(h.clients, client)
|
||||
for _, client := range clients {
|
||||
if client.trySend(response) {
|
||||
continue
|
||||
}
|
||||
|
||||
h.removeClient(client)
|
||||
}
|
||||
h.mutex.RUnlock()
|
||||
}
|
||||
|
||||
// WebSocket upgrader configuration
|
||||
|
||||
@@ -18,7 +18,7 @@ func InitRouter(r *gin.RouterGroup) {
|
||||
r.GET("site_navigation/status", GetSiteNavigationStatus)
|
||||
r.POST("site_navigation/order", UpdateSiteOrder)
|
||||
r.GET("site_navigation/health_check/:id", GetHealthCheck)
|
||||
r.PUT("site_navigation/health_check/:id", UpdateHealthCheck)
|
||||
r.POST("site_navigation/health_check/:id", UpdateHealthCheck)
|
||||
r.POST("site_navigation/test_health_check/:id", TestHealthCheck)
|
||||
r.GET("site_navigation_ws", SiteNavigationWebSocket)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/spf13/cast"
|
||||
"github.com/uozi-tech/cosy"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// GetSiteNavigation returns all sites for navigation dashboard
|
||||
@@ -54,16 +55,26 @@ func UpdateSiteOrder(c *gin.Context) {
|
||||
}
|
||||
|
||||
// updateSiteOrderBatchByIds updates site order in batch using IDs
|
||||
// Uses INSERT INTO ... ON DUPLICATE KEY UPDATE for better performance
|
||||
func updateSiteOrderBatchByIds(orderedIds []uint64) error {
|
||||
sc := query.SiteConfig
|
||||
|
||||
for i, id := range orderedIds {
|
||||
if _, err := sc.Where(sc.ID.Eq(id)).Update(sc.CustomOrder, i); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(orderedIds) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
sc := query.SiteConfig
|
||||
|
||||
records := make([]*model.SiteConfig, 0, len(orderedIds))
|
||||
for i, id := range orderedIds {
|
||||
records = append(records, &model.SiteConfig{
|
||||
Model: model.Model{ID: id},
|
||||
CustomOrder: i,
|
||||
})
|
||||
}
|
||||
|
||||
return sc.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"custom_order"}),
|
||||
}).Create(records...)
|
||||
}
|
||||
|
||||
// GetHealthCheck gets health check configuration for a site
|
||||
@@ -102,41 +113,7 @@ func ensureHealthCheckConfig(siteConfig *model.SiteConfig) {
|
||||
|
||||
// UpdateHealthCheck updates health check configuration for a site
|
||||
func UpdateHealthCheck(c *gin.Context) {
|
||||
id := cast.ToUint64(c.Param("id"))
|
||||
|
||||
var req model.SiteConfig
|
||||
|
||||
if !cosy.BindAndValid(c, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
sc := query.SiteConfig
|
||||
siteConfig, err := sc.Where(sc.ID.Eq(id)).First()
|
||||
if err != nil {
|
||||
cosy.ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
siteConfig.HealthCheckEnabled = req.HealthCheckEnabled
|
||||
siteConfig.CheckInterval = req.CheckInterval
|
||||
siteConfig.Timeout = req.Timeout
|
||||
siteConfig.UserAgent = req.UserAgent
|
||||
siteConfig.MaxRedirects = req.MaxRedirects
|
||||
siteConfig.FollowRedirects = req.FollowRedirects
|
||||
siteConfig.CheckFavicon = req.CheckFavicon
|
||||
|
||||
if req.HealthCheckConfig != nil {
|
||||
siteConfig.HealthCheckConfig = req.HealthCheckConfig
|
||||
}
|
||||
|
||||
if err = query.SiteConfig.Save(siteConfig); err != nil {
|
||||
cosy.ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Health check configuration updated successfully",
|
||||
})
|
||||
cosy.Core[model.SiteConfig](c).Modify()
|
||||
}
|
||||
|
||||
// TestHealthCheck tests a health check configuration without saving it
|
||||
|
||||
@@ -74,7 +74,6 @@ func (wm *WSManager) BroadcastUpdate(sites []*sitecheck.SiteInfo) {
|
||||
for conn := range wm.connections {
|
||||
go func(c *websocket.Conn) {
|
||||
if err := sendSiteData(c, MessageTypeUpdate, sites); err != nil {
|
||||
logger.Error("Failed to send broadcast update:", err)
|
||||
wm.RemoveConnection(c)
|
||||
c.Close()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { http } from '@uozi-admin/request'
|
||||
|
||||
export interface SiteInfo {
|
||||
id: number // primary identifier for API operations
|
||||
health_check_enabled: boolean // whether health check is enabled
|
||||
host: string // host:port format
|
||||
port: number
|
||||
scheme: string // http, https, grpc, grpcs
|
||||
@@ -23,6 +24,7 @@ export interface SiteInfo {
|
||||
}
|
||||
|
||||
export interface HealthCheckConfig {
|
||||
health_check_enabled?: boolean
|
||||
check_interval?: number
|
||||
timeout?: number
|
||||
user_agent?: string
|
||||
@@ -135,7 +137,7 @@ export const siteNavigationApi = {
|
||||
|
||||
// Update health check configuration
|
||||
updateHealthCheck(id: number, config: HealthCheckConfig): Promise<{ message: string }> {
|
||||
return http.put(`/site_navigation/health_check/${id}`, config)
|
||||
return http.post(`/site_navigation/health_check/${id}`, config)
|
||||
},
|
||||
|
||||
// Test health check configuration
|
||||
|
||||
@@ -56,10 +56,6 @@ const name = computed(() => {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@media (max-height: 800px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.detail {
|
||||
display: flex;
|
||||
/*margin-bottom: 16px;*/
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { SiteInfo } from '@/api/site_navigation'
|
||||
import { GlobalOutlined } from '@ant-design/icons-vue'
|
||||
import Sortable from 'sortablejs'
|
||||
import VueDraggable from 'vuedraggable'
|
||||
import { siteNavigationApi } from '@/api/site_navigation'
|
||||
import { useWebSocket } from '@/lib/websocket'
|
||||
import SiteCard from './components/SiteCard.vue'
|
||||
import SiteHealthCheckModal from './components/SiteHealthCheckModal.vue'
|
||||
import SiteNavigationToolbar from './components/SiteNavigationToolbar.vue'
|
||||
@@ -11,64 +12,26 @@ const sites = ref<SiteInfo[]>([])
|
||||
const { message } = useGlobalApp()
|
||||
const loading = ref(true)
|
||||
const refreshing = ref(false)
|
||||
const isConnected = ref(false)
|
||||
const settingsMode = ref(false)
|
||||
const draggableSites = ref<SiteInfo[]>([])
|
||||
const configModalVisible = ref(false)
|
||||
const configTarget = ref<SiteInfo>()
|
||||
|
||||
let sortableInstance: Sortable | null = null
|
||||
let websocket: WebSocket | null = null
|
||||
watch(sites, newSites => {
|
||||
if (!settingsMode.value) {
|
||||
draggableSites.value = newSites
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// Display sites - use draggable sites in settings mode, backend sorted sites otherwise
|
||||
const displaySites = computed(() => {
|
||||
return settingsMode.value ? draggableSites.value : sites.value
|
||||
const { status, data, send, close } = useWebSocket(siteNavigationApi.websocketUrl)
|
||||
const isConnected = computed(() => status.value === 'OPEN')
|
||||
|
||||
watch(data, newData => {
|
||||
if (newData.type === 'initial' || newData.type === 'update') {
|
||||
sites.value = newData.data || []
|
||||
}
|
||||
})
|
||||
|
||||
// WebSocket connection
|
||||
async function connectWebSocket() {
|
||||
try {
|
||||
const { useWebSocket } = await import('@/lib/websocket')
|
||||
const { ws } = useWebSocket(siteNavigationApi.websocketUrl)
|
||||
websocket = ws.value!
|
||||
|
||||
if (!websocket) {
|
||||
isConnected.value = false
|
||||
return
|
||||
}
|
||||
|
||||
websocket.onopen = () => {
|
||||
isConnected.value = true
|
||||
}
|
||||
|
||||
websocket.onmessage = (event: MessageEvent) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
if (data.type === 'initial' || data.type === 'update') {
|
||||
sites.value = data.data || []
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error)
|
||||
}
|
||||
}
|
||||
|
||||
websocket.onclose = () => {
|
||||
isConnected.value = false
|
||||
}
|
||||
|
||||
websocket.onerror = error => {
|
||||
console.error('Site navigation WebSocket error:', error)
|
||||
isConnected.value = false
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to connect WebSocket:', error)
|
||||
isConnected.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Load sites via HTTP (fallback)
|
||||
async function loadSites() {
|
||||
try {
|
||||
loading.value = true
|
||||
@@ -83,19 +46,11 @@ async function loadSites() {
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh sites
|
||||
async function handleRefresh() {
|
||||
try {
|
||||
refreshing.value = true
|
||||
|
||||
// Only use WebSocket refresh
|
||||
if (websocket && isConnected.value) {
|
||||
websocket.send(JSON.stringify({ type: 'refresh' }))
|
||||
message.success($gettext('Site refresh initiated'))
|
||||
}
|
||||
else {
|
||||
message.warning($gettext('WebSocket not connected, please wait for connection'))
|
||||
}
|
||||
send(JSON.stringify({ type: 'refresh' }))
|
||||
message.success($gettext('Site refresh initiated'))
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to refresh sites:', error)
|
||||
@@ -106,61 +61,20 @@ async function handleRefresh() {
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle settings mode
|
||||
function toggleSettingsMode() {
|
||||
settingsMode.value = !settingsMode.value
|
||||
|
||||
if (settingsMode.value) {
|
||||
draggableSites.value = [...sites.value]
|
||||
nextTick(() => initSortable())
|
||||
}
|
||||
else {
|
||||
destroySortable()
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize sortable
|
||||
function initSortable() {
|
||||
const gridElement = document.querySelector('.site-grid')
|
||||
if (gridElement && !sortableInstance) {
|
||||
sortableInstance = new Sortable(gridElement as HTMLElement, {
|
||||
animation: 150,
|
||||
ghostClass: 'site-card-ghost',
|
||||
chosenClass: 'site-card-chosen',
|
||||
dragClass: 'site-card-drag',
|
||||
onEnd: () => {
|
||||
// Update draggableSites order based on DOM order
|
||||
const cards = Array.from(gridElement.children)
|
||||
const newOrder = cards.map(card => {
|
||||
const url = card.getAttribute('data-url')
|
||||
return draggableSites.value.find(site => site.url === url)!
|
||||
})
|
||||
draggableSites.value = newOrder
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy sortable
|
||||
function destroySortable() {
|
||||
if (sortableInstance) {
|
||||
sortableInstance.destroy()
|
||||
sortableInstance = null
|
||||
}
|
||||
}
|
||||
|
||||
// Save order
|
||||
async function saveOrder() {
|
||||
try {
|
||||
const orderedIds = draggableSites.value.map(site => site.id)
|
||||
await siteNavigationApi.updateOrder(orderedIds)
|
||||
message.success($gettext('Order saved successfully'))
|
||||
|
||||
// Update sites.value immediately to reflect the new order
|
||||
sites.value = [...draggableSites.value]
|
||||
|
||||
settingsMode.value = false
|
||||
destroySortable()
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to save order:', error)
|
||||
@@ -168,20 +82,16 @@ async function saveOrder() {
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel settings mode
|
||||
function cancelSettingsMode() {
|
||||
draggableSites.value = [...sites.value]
|
||||
settingsMode.value = false
|
||||
destroySortable()
|
||||
draggableSites.value = []
|
||||
}
|
||||
|
||||
// Open config modal
|
||||
function openConfigModal(site: SiteInfo) {
|
||||
configTarget.value = site
|
||||
configModalVisible.value = true
|
||||
}
|
||||
|
||||
// Handle health check config save
|
||||
async function handleConfigSave(config: import('@/api/site_navigation').HealthCheckConfig) {
|
||||
try {
|
||||
if (configTarget.value) {
|
||||
@@ -195,38 +105,37 @@ async function handleConfigSave(config: import('@/api/site_navigation').HealthCh
|
||||
}
|
||||
}
|
||||
|
||||
const mounted = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
// First load data via HTTP
|
||||
await loadSites()
|
||||
// Then connect WebSocket for real-time updates
|
||||
connectWebSocket()
|
||||
mounted.value = true
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
destroySortable()
|
||||
if (websocket) {
|
||||
websocket.close()
|
||||
}
|
||||
close()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="site-navigation">
|
||||
<SiteNavigationToolbar
|
||||
:is-connected="isConnected"
|
||||
:refreshing="refreshing"
|
||||
:settings-mode="settingsMode"
|
||||
@refresh="handleRefresh"
|
||||
@toggle-settings="toggleSettingsMode"
|
||||
@save-order="saveOrder"
|
||||
@cancel-settings="cancelSettingsMode"
|
||||
/>
|
||||
<Teleport v-if="mounted" to=".action">
|
||||
<SiteNavigationToolbar
|
||||
:is-connected="isConnected"
|
||||
:refreshing="refreshing"
|
||||
:settings-mode="settingsMode"
|
||||
@refresh="handleRefresh"
|
||||
@toggle-settings="toggleSettingsMode"
|
||||
@save-order="saveOrder"
|
||||
@cancel-settings="cancelSettingsMode"
|
||||
/>
|
||||
</Teleport>
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<ASpin size="large" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="displaySites.length === 0" class="empty-state">
|
||||
<div v-else-if="draggableSites.length === 0" class="empty-state">
|
||||
<GlobalOutlined class="text-6xl text-gray-400 mb-4" />
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
{{ $gettext('No sites found') }}
|
||||
@@ -236,15 +145,25 @@ onUnmounted(() => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="site-grid">
|
||||
<SiteCard
|
||||
v-for="site in displaySites"
|
||||
:key="site.id"
|
||||
:site="site"
|
||||
:settings-mode="settingsMode"
|
||||
@open-config="openConfigModal"
|
||||
/>
|
||||
</div>
|
||||
<VueDraggable
|
||||
v-else
|
||||
v-model="draggableSites"
|
||||
:disabled="!settingsMode"
|
||||
class="site-grid"
|
||||
item-key="id"
|
||||
:animation="150"
|
||||
ghost-class="site-card-ghost"
|
||||
chosen-class="site-card-chosen"
|
||||
drag-class="site-card-drag"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<SiteCard
|
||||
:site="element"
|
||||
:settings-mode="settingsMode"
|
||||
@open-config="openConfigModal"
|
||||
/>
|
||||
</template>
|
||||
</VueDraggable>
|
||||
|
||||
<SiteHealthCheckModal
|
||||
v-model:open="configModalVisible"
|
||||
@@ -256,10 +175,6 @@ onUnmounted(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.site-navigation {
|
||||
@apply p-6;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
@apply flex flex-col items-center justify-center py-16 text-center;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ function getStatusClass(status: string): string {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!settingsMode" class="site-status">
|
||||
<div v-if="!settingsMode && site.health_check_enabled" class="site-status">
|
||||
<div
|
||||
class="status-indicator"
|
||||
:class="getStatusClass(site.status)"
|
||||
@@ -293,11 +293,11 @@ function getStatusClass(status: string): string {
|
||||
}
|
||||
|
||||
.site-card-config {
|
||||
@apply absolute top-2 right-2;
|
||||
@apply absolute top-3 right-3 opacity-50;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
@apply absolute bottom-2 right-2 opacity-50 hover:opacity-100 transition-opacity;
|
||||
@apply absolute bottom-3 right-3 opacity-50 hover:opacity-100 transition-opacity;
|
||||
}
|
||||
|
||||
.drag-dots {
|
||||
|
||||
@@ -4,12 +4,10 @@ import { CloseOutlined, PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { siteNavigationApi } from '@/api/site_navigation'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
site?: SiteInfo
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:open', value: boolean): void
|
||||
(e: 'save', config: EnhancedHealthCheckConfig): void
|
||||
(e: 'refresh'): void
|
||||
}
|
||||
@@ -18,13 +16,9 @@ const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
const { message } = useGlobalApp()
|
||||
|
||||
const visible = defineModel<boolean>('open', { required: true })
|
||||
const testing = ref(false)
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.open,
|
||||
set: value => emit('update:open', value),
|
||||
})
|
||||
|
||||
const formData = ref<EnhancedHealthCheckConfig>({
|
||||
// Basic settings (health check is always enabled)
|
||||
enabled: true,
|
||||
@@ -76,8 +70,8 @@ async function loadExistingConfig() {
|
||||
|
||||
// Convert backend config to frontend format
|
||||
formData.value = {
|
||||
// Basic settings (health check is always enabled)
|
||||
enabled: true,
|
||||
// Basic settings
|
||||
enabled: config.health_check_enabled ?? true,
|
||||
interval: config.check_interval ?? 300,
|
||||
timeout: config.timeout ?? 10,
|
||||
userAgent: config.user_agent ?? 'Nginx-UI Enhanced Checker/2.0',
|
||||
@@ -268,7 +262,7 @@ async function handleSave() {
|
||||
// Create the config object for the backend
|
||||
const backendConfig = {
|
||||
url: props.site.url,
|
||||
health_check_enabled: true, // Always enabled
|
||||
health_check_enabled: config.enabled,
|
||||
check_interval: config.interval,
|
||||
timeout: config.timeout,
|
||||
user_agent: config.userAgent,
|
||||
@@ -276,7 +270,7 @@ async function handleSave() {
|
||||
follow_redirects: config.followRedirects,
|
||||
check_favicon: config.checkFavicon,
|
||||
|
||||
// Enhanced health check config (always included)
|
||||
// Enhanced health check config
|
||||
health_check_config: {
|
||||
protocol: config.protocol,
|
||||
method: config.method,
|
||||
@@ -366,7 +360,7 @@ async function handleTest() {
|
||||
width="800px"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="p-2">
|
||||
<div>
|
||||
<AForm
|
||||
:model="formData"
|
||||
layout="vertical"
|
||||
@@ -374,6 +368,18 @@ async function handleTest() {
|
||||
:wrapper-col="{ span: 24 }"
|
||||
>
|
||||
<div>
|
||||
<!-- Enable/Disable Health Check -->
|
||||
<AFormItem :label="$gettext('Enable Health Check')">
|
||||
<div class="flex items-center gap-2">
|
||||
<ASwitch v-model:checked="formData.enabled" />
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ formData.enabled ? $gettext('Health check is enabled') : $gettext('Health check is disabled') }}
|
||||
</span>
|
||||
</div>
|
||||
</AFormItem>
|
||||
|
||||
<ADivider />
|
||||
|
||||
<!-- Protocol Selection -->
|
||||
<AFormItem :label="$gettext('Protocol')">
|
||||
<ARadioGroup v-model:value="formData.protocol">
|
||||
|
||||
@@ -25,10 +25,6 @@ defineEmits<Emits>()
|
||||
|
||||
<template>
|
||||
<div class="site-navigation-header">
|
||||
<h2 class="text-2xl font-500 text-gray-900 dark:text-gray-100 mb-4">
|
||||
{{ $gettext('Site Navigation') }}
|
||||
</h2>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
@@ -44,7 +40,6 @@ defineEmits<Emits>()
|
||||
<AButton
|
||||
v-if="settingsMode"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="$emit('saveOrder')"
|
||||
>
|
||||
<template #icon>
|
||||
@@ -55,37 +50,31 @@ defineEmits<Emits>()
|
||||
|
||||
<AButton
|
||||
v-if="settingsMode"
|
||||
size="small"
|
||||
@click="$emit('cancelSettings')"
|
||||
>
|
||||
<template #icon>
|
||||
<CloseOutlined />
|
||||
</template>
|
||||
{{ $gettext('Cancel') }}
|
||||
</AButton>
|
||||
|
||||
<AButton
|
||||
v-if="!settingsMode"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="refreshing"
|
||||
@click="$emit('refresh')"
|
||||
>
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
{{ $gettext('Refresh') }}
|
||||
</AButton>
|
||||
|
||||
<AButton
|
||||
v-if="!settingsMode"
|
||||
size="small"
|
||||
@click="$emit('toggleSettings')"
|
||||
>
|
||||
<template #icon>
|
||||
<SettingOutlined />
|
||||
</template>
|
||||
{{ $gettext('Settings') }}
|
||||
</AButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -94,7 +83,7 @@ defineEmits<Emits>()
|
||||
|
||||
<style scoped>
|
||||
.site-navigation-header {
|
||||
@apply flex items-center justify-between mb-6;
|
||||
@apply flex items-center justify-end;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
|
||||
32
go.mod
32
go.mod
@@ -8,7 +8,7 @@ require (
|
||||
github.com/BurntSushi/toml v1.5.0
|
||||
github.com/blevesearch/bleve/v2 v2.5.4
|
||||
github.com/caarlos0/env/v11 v11.3.1
|
||||
github.com/casdoor/casdoor-go-sdk v1.29.0
|
||||
github.com/casdoor/casdoor-go-sdk v1.30.0
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
@@ -25,7 +25,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.28.0
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/go-webauthn/webauthn v0.14.0
|
||||
github.com/go-webauthn/webauthn v0.15.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||
@@ -46,7 +46,7 @@ require (
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/uozi-tech/cosy v1.27.2
|
||||
github.com/uozi-tech/cosy v1.27.3
|
||||
github.com/uozi-tech/cosy-driver-sqlite v0.2.1
|
||||
github.com/urfave/cli/v3 v3.5.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
@@ -84,7 +84,7 @@ require (
|
||||
github.com/Azure/go-autorest/tracing v0.6.1 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.0 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.2 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 // indirect
|
||||
@@ -93,7 +93,7 @@ require (
|
||||
github.com/alibabacloud-go/tea v1.3.13 // indirect
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
|
||||
github.com/aliyun/aliyun-log-go-sdk v0.1.111 // indirect
|
||||
github.com/aliyun/credentials-go v1.4.7 // indirect
|
||||
github.com/aliyun/credentials-go v1.4.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // indirect
|
||||
@@ -115,13 +115,13 @@ require (
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.3 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.2.10 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.2.11 // indirect
|
||||
github.com/blevesearch/geo v0.2.4 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.25 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.26 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.12 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
@@ -131,7 +131,7 @@ require (
|
||||
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.6 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.7 // indirect
|
||||
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/bsm/redislock v0.9.4 // indirect
|
||||
@@ -182,7 +182,7 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/go-webauthn/x v0.1.25 // indirect
|
||||
github.com/go-webauthn/x v0.1.26 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
@@ -225,7 +225,7 @@ require (
|
||||
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/linode/linodego v1.60.0 // indirect
|
||||
github.com/linode/linodego v1.61.0 // indirect
|
||||
github.com/liquidweb/liquidweb-cli v0.7.0 // indirect
|
||||
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
|
||||
@@ -280,7 +280,7 @@ require (
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/prometheus/prometheus v0.307.3 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.55.0 // indirect
|
||||
github.com/quic-go/quic-go v0.56.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.16.0 // indirect
|
||||
github.com/regfish/regfish-dnsapi-go v0.1.1 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
@@ -341,11 +341,11 @@ require (
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.22.0 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/oauth2 v0.32.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/oauth2 v0.33.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
|
||||
35
go.sum
35
go.sum
@@ -681,6 +681,8 @@ github.com/Netflix/go-env v0.0.0-20220526054621-78278af1949d/go.mod h1:9XMFaCeRy
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.0 h1:NjlfrI3SmA9Zm5yM1FV+IR096NyVt2R8wRp56y6I8zU=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.0/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.2 h1:Axst08mZTSH93IhjLibRQ/0FJKVbRTZfW2b7qosyvSI=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.2/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
@@ -752,6 +754,8 @@ github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmP
|
||||
github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
||||
github.com/aliyun/credentials-go v1.4.7 h1:T17dLqEtPUFvjDRRb5giVvLh6dFT8IcNFJJb7MeyCxw=
|
||||
github.com/aliyun/credentials-go v1.4.7/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
||||
github.com/aliyun/credentials-go v1.4.8 h1:MEfZGWGC3L1icM1nGcYF8rWdQBG2k1Sya2pq9uRwd30=
|
||||
github.com/aliyun/credentials-go v1.4.8/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
|
||||
@@ -824,10 +828,14 @@ github.com/blevesearch/bleve/v2 v2.5.4 h1:1iur8e+PHsxtncV2xIVuqlQme/V8guEDO2uV6W
|
||||
github.com/blevesearch/bleve/v2 v2.5.4/go.mod h1:yB4PnV4N2q5rTEpB2ndG8N2ISexBQEFIYgwx4ztfvoo=
|
||||
github.com/blevesearch/bleve_index_api v1.2.10 h1:FMFmZCmTX6PdoLLvwUnKF2RsmILFFwO3h0WPevXY9fE=
|
||||
github.com/blevesearch/bleve_index_api v1.2.10/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/bleve_index_api v1.2.11 h1:bXQ54kVuwP8hdrXUSOnvTQfgK0KI1+f9A0ITJT8tX1s=
|
||||
github.com/blevesearch/bleve_index_api v1.2.11/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
|
||||
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
|
||||
github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U=
|
||||
github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-faiss v1.0.26 h1:4dRLolFgjPyjkaXwff4NfbZFdE/dfywbzDqporeQvXI=
|
||||
github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
@@ -836,6 +844,8 @@ github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCD
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.12 h1:GGZc2qwbyRBwtckPPkHkLyXw64mmsLJxdturBI1cM+c=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.12/go.mod h1:JBRGAneqgLSI2+jCNjtwMqp2B7EBF3/VUzgDPIU33MM=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
@@ -856,6 +866,8 @@ github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFx
|
||||
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.2.6 h1:OHuUl2GhM+FpBq9RwNsJ4k/QodqbMMHoQEgn/IHYpu8=
|
||||
github.com/blevesearch/zapx/v16 v16.2.6/go.mod h1:cuAPB+YoIyRngNhno1S1GPr9SfMk+x/SgAHBLXSIq3k=
|
||||
github.com/blevesearch/zapx/v16 v16.2.7 h1:xcgFRa7f/tQXOwApVq7JWgPYSlzyUMmkuYa54tMDuR0=
|
||||
github.com/blevesearch/zapx/v16 v16.2.7/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
|
||||
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
|
||||
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
@@ -884,6 +896,8 @@ github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vaui
|
||||
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
github.com/casdoor/casdoor-go-sdk v1.29.0 h1:bINkqgVjTaXRFJwsBshbtAJnSOoP7SB0NL2aWyqXsI8=
|
||||
github.com/casdoor/casdoor-go-sdk v1.29.0/go.mod h1:hVSgmSdwTCsBEJNt9r2K5aLVsoeMc37/N4Zzescy5SA=
|
||||
github.com/casdoor/casdoor-go-sdk v1.30.0 h1:EKwkaQfRaXmryJUWEzq7DWh863gfDXfCr3txi2fJIDw=
|
||||
github.com/casdoor/casdoor-go-sdk v1.30.0/go.mod h1:hVSgmSdwTCsBEJNt9r2K5aLVsoeMc37/N4Zzescy5SA=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
@@ -895,6 +909,7 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -906,6 +921,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
|
||||
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
@@ -1130,8 +1146,12 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-webauthn/webauthn v0.14.0 h1:ZLNPUgPcDlAeoxe+5umWG/tEeCoQIDr7gE2Zx2QnhL0=
|
||||
github.com/go-webauthn/webauthn v0.14.0/go.mod h1:QZzPFH3LJ48u5uEPAu+8/nWJImoLBWM7iAH/kSVSo6k=
|
||||
github.com/go-webauthn/webauthn v0.15.0 h1:LR1vPv62E0/6+sTenX35QrCmpMCzLeVAcnXeH4MrbJY=
|
||||
github.com/go-webauthn/webauthn v0.15.0/go.mod h1:hcAOhVChPRG7oqG7Xj6XKN1mb+8eXTGP/B7zBLzkX5A=
|
||||
github.com/go-webauthn/x v0.1.25 h1:g/0noooIGcz/yCVqebcFgNnGIgBlJIccS+LYAa+0Z88=
|
||||
github.com/go-webauthn/x v0.1.25/go.mod h1:ieblaPY1/BVCV0oQTsA/VAo08/TWayQuJuo5Q+XxmTY=
|
||||
github.com/go-webauthn/x v0.1.26 h1:eNzreFKnwNLDFoywGh9FA8YOMebBWTUNlNSdolQRebs=
|
||||
github.com/go-webauthn/x v0.1.26/go.mod h1:jmf/phPV6oIsF6hmdVre+ovHkxjDOmNH0t6fekWUxvg=
|
||||
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
|
||||
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
|
||||
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
|
||||
@@ -1536,6 +1556,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/linode/linodego v1.60.0 h1:SgsebJFRCi+lSmYy+C40wmKZeJllGGm+W12Qw4+yVdI=
|
||||
github.com/linode/linodego v1.60.0/go.mod h1:1+Bt0oTz5rBnDOJbGhccxn7LYVytXTIIfAy7QYmijDs=
|
||||
github.com/linode/linodego v1.61.0 h1:9g20NWl+/SbhDFj6X5EOZXtM2hBm1Mx8I9h8+F3l1LM=
|
||||
github.com/linode/linodego v1.61.0/go.mod h1:64o30geLNwR0NeYh5HM/WrVCBXcSqkKnRK3x9xoRuJI=
|
||||
github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
|
||||
github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
|
||||
github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ=
|
||||
@@ -1753,6 +1775,7 @@ github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2
|
||||
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
@@ -1830,6 +1853,8 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
||||
github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY=
|
||||
github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
|
||||
@@ -2010,6 +2035,8 @@ github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 h1:/VaznPrb/b6
|
||||
github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419/go.mod h1:QN0/PdenvYWB0GRMz6JJbPeZz2Lph2iys1p8AFVHm2c=
|
||||
github.com/uozi-tech/cosy v1.27.2 h1:iVqMx7+yqqFdfHlGy3XXegQWn9xTNCTNOIHGqill7Cg=
|
||||
github.com/uozi-tech/cosy v1.27.2/go.mod h1:dCaZpbpw/RXLNuonmYZ8WyPbpdvND8GBur2qxoOnQRI=
|
||||
github.com/uozi-tech/cosy v1.27.3 h1:Cj/YyXJbtOgxoXyHWjLDQ+x0P+LphneiVYepKPAosp4=
|
||||
github.com/uozi-tech/cosy v1.27.3/go.mod h1:dCaZpbpw/RXLNuonmYZ8WyPbpdvND8GBur2qxoOnQRI=
|
||||
github.com/uozi-tech/cosy-driver-mysql v0.2.2 h1:22S/XNIvuaKGqxQPsYPXN8TZ8hHjCQdcJKVQ83Vzxoo=
|
||||
github.com/uozi-tech/cosy-driver-mysql v0.2.2/go.mod h1:EZnRIbSj1V5U0gEeTobrXai/d1SV11lkl4zP9NFEmyE=
|
||||
github.com/uozi-tech/cosy-driver-postgres v0.2.1 h1:OICakGuT+omva6QOJCxTJ5Lfr7CGXLmk/zD+aS51Z2o=
|
||||
@@ -2147,6 +2174,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -2374,6 +2403,8 @@ golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw
|
||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -2395,6 +2426,8 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -2525,6 +2558,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
||||
@@ -120,24 +120,15 @@ func (sc *SiteChecker) CollectSites() {
|
||||
}
|
||||
|
||||
// Parse URL components for legacy fields
|
||||
_, hostPort := parseURLComponents(url, protocol)
|
||||
|
||||
// Get or create site config to get ID
|
||||
siteConfig := getOrCreateSiteConfigForURL(url)
|
||||
|
||||
siteInfo := &SiteInfo{
|
||||
ID: siteConfig.ID,
|
||||
Host: siteConfig.Host,
|
||||
Port: siteConfig.Port,
|
||||
Scheme: siteConfig.Scheme,
|
||||
DisplayURL: siteConfig.GetURL(),
|
||||
SiteConfig: *siteConfig,
|
||||
Name: extractDomainName(url),
|
||||
Status: StatusChecking,
|
||||
LastChecked: time.Now().Unix(),
|
||||
// Legacy fields for backward compatibility
|
||||
URL: url,
|
||||
HealthCheckProtocol: protocol,
|
||||
HostPort: hostPort,
|
||||
}
|
||||
sc.sites[url] = siteInfo
|
||||
}
|
||||
@@ -301,23 +292,43 @@ func getOrCreateSiteConfigForURL(url string) *model.SiteConfig {
|
||||
func (sc *SiteChecker) CheckSite(ctx context.Context, siteURL string) (*SiteInfo, error) {
|
||||
// Try enhanced health check first if config exists
|
||||
config, err := LoadSiteConfig(siteURL)
|
||||
|
||||
// If health check is disabled, return a SiteInfo without status
|
||||
if err == nil && config != nil && !config.HealthCheckEnabled {
|
||||
protocol := "http"
|
||||
if config.HealthCheckConfig != nil && config.HealthCheckConfig.Protocol != "" {
|
||||
protocol = config.HealthCheckConfig.Protocol
|
||||
}
|
||||
|
||||
siteInfo := &SiteInfo{
|
||||
SiteConfig: *config,
|
||||
Name: extractDomainName(siteURL),
|
||||
Title: config.DisplayURL,
|
||||
}
|
||||
|
||||
// Try to get favicon if enabled and not a gRPC check
|
||||
if sc.options.CheckFavicon && !isGRPCProtocol(protocol) {
|
||||
faviconURL, faviconData := sc.tryGetFavicon(ctx, siteURL)
|
||||
siteInfo.FaviconURL = faviconURL
|
||||
siteInfo.FaviconData = faviconData
|
||||
}
|
||||
|
||||
return siteInfo, nil
|
||||
}
|
||||
|
||||
if err == nil && config != nil && config.HealthCheckConfig != nil {
|
||||
enhancedChecker := NewEnhancedSiteChecker()
|
||||
siteInfo, err := enhancedChecker.CheckSiteWithConfig(ctx, siteURL, config.HealthCheckConfig)
|
||||
if err == nil && siteInfo != nil {
|
||||
// Fill in additional details
|
||||
siteInfo.ID = config.ID
|
||||
siteInfo.HealthCheckEnabled = config.HealthCheckEnabled
|
||||
siteInfo.Name = extractDomainName(siteURL)
|
||||
siteInfo.LastChecked = time.Now().Unix()
|
||||
|
||||
// Set health check protocol and display URL
|
||||
siteInfo.HealthCheckProtocol = config.HealthCheckConfig.Protocol
|
||||
siteInfo.DisplayURL = generateDisplayURL(siteURL, config.HealthCheckConfig.Protocol)
|
||||
|
||||
// Parse URL components
|
||||
scheme, hostPort := parseURLComponents(siteURL, config.HealthCheckConfig.Protocol)
|
||||
siteInfo.Scheme = scheme
|
||||
siteInfo.HostPort = hostPort
|
||||
|
||||
// Try to get favicon if enabled and not a gRPC check
|
||||
if sc.options.CheckFavicon && !isGRPCProtocol(config.HealthCheckConfig.Protocol) {
|
||||
faviconURL, faviconData := sc.tryGetFavicon(ctx, siteURL)
|
||||
@@ -350,53 +361,31 @@ func (sc *SiteChecker) checkSiteBasic(ctx context.Context, siteURL string, origi
|
||||
|
||||
resp, err := sc.client.Do(req)
|
||||
if err != nil {
|
||||
// Parse URL components for legacy fields
|
||||
_, hostPort := parseURLComponents(siteURL, originalProtocol)
|
||||
|
||||
// Get or create site config to get ID
|
||||
siteConfig := getOrCreateSiteConfigForURL(siteURL)
|
||||
|
||||
return &SiteInfo{
|
||||
ID: siteConfig.ID,
|
||||
Host: siteConfig.Host,
|
||||
Port: siteConfig.Port,
|
||||
Scheme: siteConfig.Scheme,
|
||||
DisplayURL: siteConfig.GetURL(),
|
||||
SiteConfig: *siteConfig,
|
||||
Name: extractDomainName(siteURL),
|
||||
Status: StatusOffline,
|
||||
ResponseTime: time.Since(start).Milliseconds(),
|
||||
LastChecked: time.Now().Unix(),
|
||||
Error: err.Error(),
|
||||
// Legacy fields for backward compatibility
|
||||
URL: siteURL,
|
||||
HealthCheckProtocol: originalProtocol,
|
||||
HostPort: hostPort,
|
||||
}, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
responseTime := time.Since(start).Milliseconds()
|
||||
|
||||
// Parse URL components for legacy fields
|
||||
_, hostPort := parseURLComponents(siteURL, originalProtocol)
|
||||
|
||||
// Get or create site config to get ID
|
||||
siteConfig := getOrCreateSiteConfigForURL(siteURL)
|
||||
|
||||
siteInfo := &SiteInfo{
|
||||
ID: siteConfig.ID,
|
||||
Host: siteConfig.Host,
|
||||
Port: siteConfig.Port,
|
||||
Scheme: siteConfig.Scheme,
|
||||
DisplayURL: siteConfig.GetURL(),
|
||||
SiteConfig: *siteConfig,
|
||||
Name: extractDomainName(siteURL),
|
||||
StatusCode: resp.StatusCode,
|
||||
ResponseTime: responseTime,
|
||||
LastChecked: time.Now().Unix(),
|
||||
// Legacy fields for backward compatibility
|
||||
URL: siteURL,
|
||||
HealthCheckProtocol: originalProtocol,
|
||||
HostPort: hostPort,
|
||||
}
|
||||
|
||||
// Determine status based on status code
|
||||
|
||||
@@ -84,16 +84,9 @@ func (ec *EnhancedSiteChecker) checkHTTP(ctx context.Context, siteURL string, co
|
||||
// Create request
|
||||
req, err := http.NewRequestWithContext(ctx, config.Method, checkURL, nil)
|
||||
if err != nil {
|
||||
// Parse URL components for error case
|
||||
scheme, hostPort := parseURLComponents(siteURL, config.Protocol)
|
||||
|
||||
return &SiteInfo{
|
||||
URL: siteURL,
|
||||
Status: StatusError,
|
||||
Error: fmt.Sprintf("Failed to create request: %v", err),
|
||||
HealthCheckProtocol: config.Protocol,
|
||||
Scheme: scheme,
|
||||
HostPort: hostPort,
|
||||
Status: StatusError,
|
||||
Error: fmt.Sprintf("Failed to create request: %v", err),
|
||||
}, err
|
||||
}
|
||||
|
||||
@@ -147,17 +140,10 @@ func (ec *EnhancedSiteChecker) checkHTTP(ctx context.Context, siteURL string, co
|
||||
// Make request
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
// Parse URL components for error case
|
||||
scheme, hostPort := parseURLComponents(siteURL, config.Protocol)
|
||||
|
||||
return &SiteInfo{
|
||||
URL: siteURL,
|
||||
Status: StatusError,
|
||||
ResponseTime: time.Since(startTime).Milliseconds(),
|
||||
Error: err.Error(),
|
||||
HealthCheckProtocol: config.Protocol,
|
||||
Scheme: scheme,
|
||||
HostPort: hostPort,
|
||||
Status: StatusError,
|
||||
ResponseTime: time.Since(startTime).Milliseconds(),
|
||||
Error: err.Error(),
|
||||
}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
@@ -202,26 +188,15 @@ func (ec *EnhancedSiteChecker) checkHTTP(ctx context.Context, siteURL string, co
|
||||
}
|
||||
}
|
||||
|
||||
// Parse URL components for legacy fields
|
||||
_, hostPort := parseURLComponents(siteURL, config.Protocol)
|
||||
|
||||
// Get or create site config to get ID
|
||||
siteConfig := getOrCreateSiteConfigForURL(siteURL)
|
||||
|
||||
return &SiteInfo{
|
||||
ID: siteConfig.ID,
|
||||
Host: siteConfig.Host,
|
||||
Port: siteConfig.Port,
|
||||
Scheme: siteConfig.Scheme,
|
||||
DisplayURL: siteConfig.GetURL(),
|
||||
SiteConfig: *siteConfig,
|
||||
Status: status,
|
||||
StatusCode: resp.StatusCode,
|
||||
ResponseTime: responseTime,
|
||||
Error: errorMsg,
|
||||
// Legacy fields for backward compatibility
|
||||
URL: siteURL,
|
||||
HealthCheckProtocol: config.Protocol,
|
||||
HostPort: hostPort,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -242,16 +217,9 @@ func (ec *EnhancedSiteChecker) checkGRPC(ctx context.Context, siteURL string, co
|
||||
// Parse URL to get host and port
|
||||
parsedURL, err := parseGRPCURL(siteURL)
|
||||
if err != nil {
|
||||
// Parse URL components for error case
|
||||
scheme, hostPort := parseURLComponents(siteURL, config.Protocol)
|
||||
|
||||
return &SiteInfo{
|
||||
URL: siteURL,
|
||||
Status: StatusError,
|
||||
Error: fmt.Sprintf("Invalid gRPC URL: %v", err),
|
||||
HealthCheckProtocol: config.Protocol,
|
||||
Scheme: scheme,
|
||||
HostPort: hostPort,
|
||||
Status: StatusError,
|
||||
Error: fmt.Sprintf("Invalid gRPC URL: %v", err),
|
||||
}, err
|
||||
}
|
||||
|
||||
@@ -284,11 +252,8 @@ func (ec *EnhancedSiteChecker) checkGRPC(ctx context.Context, siteURL string, co
|
||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
}
|
||||
|
||||
// Create connection with shorter timeout for faster failure detection
|
||||
dialCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
conn, err := grpc.DialContext(dialCtx, parsedURL.Host, opts...)
|
||||
// Create gRPC client (connection established lazily on first RPC call)
|
||||
conn, err := grpc.NewClient(parsedURL.Host, opts...)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("Failed to connect to gRPC server: %v", err)
|
||||
|
||||
@@ -301,17 +266,10 @@ func (ec *EnhancedSiteChecker) checkGRPC(ctx context.Context, siteURL string, co
|
||||
errorMsg = fmt.Sprintf("Protocol mismatch - %s may not be a gRPC server or wrong TLS configuration", parsedURL.Host)
|
||||
}
|
||||
|
||||
// Parse URL components for error case
|
||||
scheme, hostPort := parseURLComponents(siteURL, config.Protocol)
|
||||
|
||||
return &SiteInfo{
|
||||
URL: siteURL,
|
||||
Status: StatusError,
|
||||
ResponseTime: time.Since(startTime).Milliseconds(),
|
||||
Error: errorMsg,
|
||||
HealthCheckProtocol: config.Protocol,
|
||||
Scheme: scheme,
|
||||
HostPort: hostPort,
|
||||
Status: StatusError,
|
||||
ResponseTime: time.Since(startTime).Milliseconds(),
|
||||
Error: errorMsg,
|
||||
}, err
|
||||
}
|
||||
defer conn.Close()
|
||||
@@ -347,17 +305,10 @@ func (ec *EnhancedSiteChecker) checkGRPC(ctx context.Context, siteURL string, co
|
||||
errorMsg = "Connection lost during health check"
|
||||
}
|
||||
|
||||
// Parse URL components for error case
|
||||
scheme, hostPort := parseURLComponents(siteURL, config.Protocol)
|
||||
|
||||
return &SiteInfo{
|
||||
URL: siteURL,
|
||||
Status: StatusError,
|
||||
ResponseTime: responseTime,
|
||||
Error: errorMsg,
|
||||
HealthCheckProtocol: config.Protocol,
|
||||
Scheme: scheme,
|
||||
HostPort: hostPort,
|
||||
Status: StatusError,
|
||||
ResponseTime: responseTime,
|
||||
Error: errorMsg,
|
||||
}, err
|
||||
}
|
||||
|
||||
@@ -367,16 +318,9 @@ func (ec *EnhancedSiteChecker) checkGRPC(ctx context.Context, siteURL string, co
|
||||
status = StatusOnline
|
||||
}
|
||||
|
||||
// Parse URL components
|
||||
scheme, hostPort := parseURLComponents(siteURL, config.Protocol)
|
||||
|
||||
return &SiteInfo{
|
||||
URL: siteURL,
|
||||
Status: status,
|
||||
ResponseTime: responseTime,
|
||||
HealthCheckProtocol: config.Protocol,
|
||||
Scheme: scheme,
|
||||
HostPort: hostPort,
|
||||
Status: status,
|
||||
ResponseTime: responseTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ func applyCustomOrdering(sites []*SiteInfo) []*SiteInfo {
|
||||
|
||||
// Sort sites based on custom order, with fallback to default ordering
|
||||
sort.Slice(sites, func(i, j int) bool {
|
||||
orderI, hasOrderI := orderMap[sites[i].URL]
|
||||
orderJ, hasOrderJ := orderMap[sites[j].URL]
|
||||
orderI, hasOrderI := orderMap[sites[i].DisplayURL]
|
||||
orderJ, hasOrderJ := orderMap[sites[j].DisplayURL]
|
||||
|
||||
// If both have custom order, use custom order
|
||||
if hasOrderI && hasOrderJ {
|
||||
@@ -91,5 +91,5 @@ func defaultCompare(a, b *SiteInfo) bool {
|
||||
}
|
||||
|
||||
// Final sort: by URL (for complete stability)
|
||||
return a.URL < b.URL
|
||||
return a.DisplayURL < b.DisplayURL
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package sitecheck
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
)
|
||||
|
||||
// Site health check status constants
|
||||
@@ -14,24 +16,16 @@ const (
|
||||
|
||||
// SiteInfo represents the information about a site
|
||||
type SiteInfo struct {
|
||||
ID uint64 `json:"id"` // Site config ID for API operations
|
||||
Host string `json:"host"` // host:port format
|
||||
Port int `json:"port"` // port number
|
||||
Scheme string `json:"scheme"` // http, https, grpc, grpcs
|
||||
DisplayURL string `json:"display_url"` // computed URL for display
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"` // StatusOnline, StatusOffline, StatusError, StatusChecking
|
||||
StatusCode int `json:"status_code"`
|
||||
ResponseTime int64 `json:"response_time"` // in milliseconds
|
||||
FaviconURL string `json:"favicon_url"`
|
||||
FaviconData string `json:"favicon_data"` // base64 encoded favicon
|
||||
Title string `json:"title"`
|
||||
LastChecked int64 `json:"last_checked"` // Unix timestamp in seconds
|
||||
Error string `json:"error,omitempty"`
|
||||
// Legacy fields for backward compatibility
|
||||
URL string `json:"url,omitempty"` // deprecated, use display_url instead
|
||||
HealthCheckProtocol string `json:"health_check_protocol,omitempty"` // deprecated, use scheme instead
|
||||
HostPort string `json:"host_port,omitempty"` // deprecated, use host instead
|
||||
model.SiteConfig
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"` // StatusOnline, StatusOffline, StatusError, StatusChecking
|
||||
StatusCode int `json:"status_code"`
|
||||
ResponseTime int64 `json:"response_time"` // in milliseconds
|
||||
FaviconURL string `json:"favicon_url"`
|
||||
FaviconData string `json:"favicon_data"` // base64 encoded favicon
|
||||
Title string `json:"title"`
|
||||
LastChecked int64 `json:"last_checked"` // Unix timestamp in seconds
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CheckOptions represents options for site checking
|
||||
|
||||
@@ -33,19 +33,19 @@ type HealthCheckConfig struct {
|
||||
|
||||
type SiteConfig struct {
|
||||
Model
|
||||
Host string `gorm:"index" json:"host"` // host:port format
|
||||
Port int `gorm:"index" json:"port"` // port number
|
||||
Scheme string `gorm:"default:'http'" json:"scheme"` // http, https, grpc, grpcs
|
||||
DisplayURL string `json:"display_url"` // computed URL for display
|
||||
CustomOrder int `gorm:"default:0" json:"custom_order"`
|
||||
HealthCheckEnabled bool `gorm:"default:true" json:"health_check_enabled"`
|
||||
CheckInterval int `gorm:"default:300" json:"check_interval"` // seconds
|
||||
Timeout int `gorm:"default:10" json:"timeout"` // seconds
|
||||
UserAgent string `gorm:"default:'Nginx-UI Site Checker/1.0'" json:"user_agent"`
|
||||
MaxRedirects int `gorm:"default:3" json:"max_redirects"`
|
||||
FollowRedirects bool `gorm:"default:true" json:"follow_redirects"`
|
||||
CheckFavicon bool `gorm:"default:true" json:"check_favicon"`
|
||||
HealthCheckConfig *HealthCheckConfig `gorm:"serializer:json" json:"health_check_config"`
|
||||
Host string `gorm:"index" json:"host" cosy:"all:omitempty"` // host:port format
|
||||
Port int `gorm:"index" json:"port" cosy:"all:omitempty"` // port number
|
||||
Scheme string `gorm:"default:'http'" json:"scheme" cosy:"all:omitempty"` // http, https, grpc, grpcs
|
||||
DisplayURL string `json:"display_url" cosy:"all:omitempty"` // computed URL for display
|
||||
CustomOrder int `gorm:"default:0" json:"custom_order" cosy:"all:omitempty"`
|
||||
HealthCheckEnabled bool `gorm:"default:true" json:"health_check_enabled" cosy:"all:omitempty"`
|
||||
CheckInterval int `gorm:"default:300" json:"check_interval" cosy:"all:omitempty"` // seconds
|
||||
Timeout int `gorm:"default:10" json:"timeout" cosy:"all:omitempty"` // seconds
|
||||
UserAgent string `gorm:"default:'Nginx-UI Site Checker/1.0'" json:"user_agent" cosy:"all:omitempty"`
|
||||
MaxRedirects int `gorm:"default:3" json:"max_redirects" cosy:"all:omitempty"`
|
||||
FollowRedirects bool `gorm:"default:true" json:"follow_redirects" cosy:"all:omitempty"`
|
||||
CheckFavicon bool `gorm:"default:true" json:"check_favicon" cosy:"all:omitempty"`
|
||||
HealthCheckConfig *HealthCheckConfig `gorm:"serializer:json" json:"health_check_config" cosy:"all:omitempty"`
|
||||
}
|
||||
|
||||
// GetURL returns the computed URL for this site config
|
||||
|
||||
Reference in New Issue
Block a user