1
0
Эх сурвалжийг харах

feat(header): add self check error banner

Jacky 1 долоо хоног өмнө
parent
commit
597175940f

+ 1 - 0
app/components.d.ts

@@ -107,6 +107,7 @@ declare module 'vue' {
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     SelfCheckSelfCheck: typeof import('./src/components/SelfCheck/SelfCheck.vue')['default']
+    SelfCheckSelfCheckHeaderBanner: typeof import('./src/components/SelfCheck/SelfCheckHeaderBanner.vue')['default']
     SensitiveStringSensitiveString: typeof import('./src/components/SensitiveString/SensitiveString.vue')['default']
     SetLanguageSetLanguage: typeof import('./src/components/SetLanguage/SetLanguage.vue')['default']
     StdDesignStdDataDisplayStdBatchEdit: typeof import('./src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue')['default']

+ 9 - 40
app/src/components/SelfCheck/SelfCheck.vue

@@ -1,48 +1,14 @@
 <script setup lang="ts">
-import type { TaskReport } from './tasks'
 import { CheckCircleOutlined, CloseCircleOutlined, WarningOutlined } from '@ant-design/icons-vue'
+import { useSelfCheckStore } from './store'
 import { taskManager } from './tasks'
 
-const data = ref<TaskReport[]>()
-const requestError = ref(false)
-const loading = ref(false)
+const store = useSelfCheckStore()
 
-async function check() {
-  loading.value = true
-  try {
-    data.value = await taskManager.runAllChecks()
-  }
-  catch {
-    requestError.value = true
-  }
-  finally {
-    loading.value = false
-  }
-}
+const { data, loading, fixing } = storeToRefs(store)
 
 onMounted(() => {
-  check()
-})
-
-const fixing = reactive({})
-
-async function fix(taskName: string) {
-  fixing[taskName] = true
-  try {
-    await taskManager.fixTask(taskName)
-    check()
-  }
-  finally {
-    fixing[taskName] = false
-  }
-}
-
-const hasError = computed(() => {
-  return requestError.value || data.value?.some(item => item.status === 'error')
-})
-
-defineExpose({
-  hasError,
+  store.check()
 })
 </script>
 
@@ -50,7 +16,10 @@ defineExpose({
   <ACard :title="$gettext('Self Check')">
     <template #extra>
       <AButton
-        type="link" size="small" :loading="loading" @click="check"
+        type="link"
+        size="small"
+        :loading="loading"
+        @click="store.check"
       >
         {{ $gettext('Recheck') }}
       </AButton>
@@ -58,7 +27,7 @@ defineExpose({
     <AList>
       <AListItem v-for="(item, index) in data" :key="index">
         <template v-if="item.status === 'error'" #actions>
-          <AButton type="link" size="small" :loading="fixing[item.name]" @click="fix(item.name)">
+          <AButton type="link" size="small" :loading="fixing[item.name]" @click="store.fix(item.name)">
             {{ $gettext('Attempt to fix') }}
           </AButton>
         </template>

+ 88 - 0
app/src/components/SelfCheck/SelfCheckHeaderBanner.vue

@@ -0,0 +1,88 @@
+<script setup lang="ts">
+import { CloseCircleOutlined } from '@ant-design/icons-vue'
+import { useElementSize } from '@vueuse/core'
+import { storeToRefs } from 'pinia'
+import { useSelfCheckStore } from './store'
+
+const props = defineProps<{
+  headerWeight?: number
+  userWrapperWidth?: number
+}>()
+
+const router = useRouter()
+const selfCheckStore = useSelfCheckStore()
+const { hasError } = storeToRefs(selfCheckStore)
+
+const alertEl = useTemplateRef('alertEl')
+const { width: alertWidth } = useElementSize(alertEl)
+
+const shouldHideAlert = computed(() => {
+  if (!props.headerWeight || !props.userWrapperWidth || !alertWidth.value)
+    return false
+  return (props.headerWeight - props.userWrapperWidth - alertWidth.value - 60) < props.userWrapperWidth
+})
+
+const iconRightPosition = computed(() => {
+  return props.userWrapperWidth ? `${props.userWrapperWidth + 50}px` : '50px'
+})
+
+onMounted(() => {
+  selfCheckStore.check()
+})
+</script>
+
+<template>
+  <div v-show="hasError">
+    <div ref="alertEl" class="self-check-alert" :style="{ visibility: shouldHideAlert ? 'hidden' : 'visible' }">
+      <AAlert type="error" show-icon :message="$gettext('Self check failed, Nginx UI may not work properly')">
+        <template #action>
+          <AButton class="ml-4" size="small" danger @click="router.push('/system/self_check')">
+            {{ $gettext('Check') }}
+          </AButton>
+        </template>
+      </AAlert>
+    </div>
+
+    <APopover
+      v-if="shouldHideAlert"
+      placement="bottomRight"
+      trigger="hover"
+    >
+      <CloseCircleOutlined
+        class="error-icon"
+        :style="{ right: iconRightPosition }"
+        @click="router.push('/system/self_check')"
+      />
+      <template #content>
+        <div class="flex items-center gap-2">
+          <CloseCircleOutlined class="text-red-500" />
+          <div>
+            {{ $gettext('Self check failed, Nginx UI may not work properly') }}
+          </div>
+          <div>
+            <AButton size="small" danger @click="router.push('/system/self_check')">
+              {{ $gettext('Check') }}
+            </AButton>
+          </div>
+        </div>
+      </template>
+    </APopover>
+  </div>
+</template>
+
+<style lang="less" scoped>
+.self-check-alert {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+}
+
+.error-icon {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  color: #f5222d;
+  cursor: pointer;
+}
+</style>

+ 7 - 0
app/src/components/SelfCheck/index.ts

@@ -1,3 +1,10 @@
 import SelfCheck from './SelfCheck.vue'
+import SelfCheckHeaderBanner from './SelfCheckHeaderBanner.vue'
+import { useSelfCheckStore } from './store'
+
+export {
+  SelfCheckHeaderBanner,
+  useSelfCheckStore,
+}
 
 export default SelfCheck

+ 54 - 0
app/src/components/SelfCheck/store.ts

@@ -0,0 +1,54 @@
+import type { TaskReport } from './tasks'
+import { debounce } from 'lodash'
+import { taskManager } from './tasks'
+
+export const useSelfCheckStore = defineStore('selfCheck', () => {
+  const data = ref<TaskReport[]>([])
+
+  const requestError = ref(false)
+  const loading = ref(false)
+
+  async function __check() {
+    if (loading.value)
+      return
+
+    loading.value = true
+    try {
+      data.value = await taskManager.runAllChecks()
+    }
+    catch (error) {
+      console.error(error)
+      requestError.value = true
+    }
+    finally {
+      loading.value = false
+    }
+  }
+
+  const check = debounce(__check, 1000, {
+    leading: true,
+    trailing: false,
+  })
+
+  const fixing = reactive({})
+
+  async function fix(taskName: string) {
+    if (fixing[taskName])
+      return
+
+    fixing[taskName] = true
+    try {
+      await taskManager.fixTask(taskName)
+      check()
+    }
+    finally {
+      fixing[taskName] = false
+    }
+  }
+
+  const hasError = computed(() => {
+    return requestError.value || data.value?.some(item => item.status === 'error')
+  })
+
+  return { data, loading, fixing, hasError, check, fix }
+})

+ 19 - 7
app/src/layouts/HeaderLayout.vue

@@ -1,11 +1,12 @@
 <script setup lang="ts">
-import type { ShallowRef } from 'vue'
 import auth from '@/api/auth'
-import NginxControl from '@/components/NginxControl/NginxControl.vue'
-import Notification from '@/components/Notification/Notification.vue'
-import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
-import SwitchAppearance from '@/components/SwitchAppearance/SwitchAppearance.vue'
+import NginxControl from '@/components/NginxControl'
+import Notification from '@/components/Notification'
+import { SelfCheckHeaderBanner } from '@/components/SelfCheck'
+import SetLanguage from '@/components/SetLanguage'
+import SwitchAppearance from '@/components/SwitchAppearance'
 import { DesktopOutlined, HomeOutlined, LogoutOutlined, MenuUnfoldOutlined } from '@ant-design/icons-vue'
+import { useElementSize } from '@vueuse/core'
 import { message } from 'ant-design-vue'
 import { useRouter } from 'vue-router'
 
@@ -23,11 +24,16 @@ function logout() {
   })
 }
 
-const headerRef = useTemplateRef('headerRef') as Readonly<ShallowRef<HTMLDivElement>>
+const headerRef = useTemplateRef('headerRef')
 
+const userWrapperRef = useTemplateRef('userWrapperRef')
 const isWorkspace = computed(() => {
   return !!window.inWorkspace
 })
+
+const { width: headerWidth } = useElementSize(headerRef)
+
+const { width: userWrapperWidth } = useElementSize(userWrapperRef)
 </script>
 
 <template>
@@ -36,7 +42,13 @@ const isWorkspace = computed(() => {
       <MenuUnfoldOutlined @click="emit('clickUnFold')" />
     </div>
 
+    <SelfCheckHeaderBanner
+      :header-weight="headerWidth"
+      :user-wrapper-width="userWrapperWidth"
+    />
+
     <ASpace
+      ref="userWrapperRef"
       class="user-wrapper"
       :size="24"
     >
@@ -74,7 +86,7 @@ const isWorkspace = computed(() => {
   background: transparent;
   box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.05);
   width: 100%;
-
+  position: relative;
   a {
     color: #000000;
   }

+ 5 - 4
app/src/views/install/components/InstallView.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import install from '@/api/install'
-import SelfCheck from '@/components/SelfCheck'
+import SelfCheck, { useSelfCheckStore } from '@/components/SelfCheck'
 import SystemRestoreContent from '@/components/SystemRestore'
 import { message } from 'ant-design-vue'
 import InstallFooter from './InstallFooter.vue'
@@ -11,7 +11,8 @@ import TimeoutAlert from './TimeoutAlert.vue'
 const installTimeout = ref(false)
 const activeTab = ref('1')
 const step = ref(1)
-const selfCheckRef = useTemplateRef('selfCheckRef')
+const selfCheckStore = useSelfCheckStore()
+const { hasError } = storeToRefs(selfCheckStore)
 
 const router = useRouter()
 
@@ -50,7 +51,7 @@ function handleRestoreSuccess(options: { restoreNginx: boolean, restoreNginxUI:
 }
 
 const canProceed = computed(() => {
-  return !installTimeout.value && !selfCheckRef.value?.hasError
+  return !installTimeout.value && !hasError.value
 })
 
 const steps = [
@@ -87,7 +88,7 @@ const steps = [
           </ASteps>
 
           <div v-if="step === 1">
-            <SelfCheck ref="selfCheckRef" class="mb-4" />
+            <SelfCheck class="mb-4" />
             <div class="flex justify-center">
               <AButton v-if="canProceed" type="primary" @click="step = 2">
                 {{ $gettext('Next') }}

+ 4 - 4
internal/middleware/middleware.go

@@ -18,15 +18,15 @@ func getToken(c *gin.Context) (token string) {
 		return
 	}
 
-	if token, _ = c.Cookie("token"); token != "" {
-		return token
-	}
-
 	if token = c.Query("token"); token != "" {
 		tokenBytes, _ := base64.StdEncoding.DecodeString(token)
 		return string(tokenBytes)
 	}
 
+	if token, _ = c.Cookie("token"); token != "" {
+		return token
+	}
+
 	return ""
 }