mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
feat(header): add self check error banner
This commit is contained in:
parent
309ca81ee3
commit
597175940f
8 changed files with 187 additions and 55 deletions
1
app/components.d.ts
vendored
1
app/components.d.ts
vendored
|
@ -107,6 +107,7 @@ declare module 'vue' {
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SelfCheckSelfCheck: typeof import('./src/components/SelfCheck/SelfCheck.vue')['default']
|
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']
|
SensitiveStringSensitiveString: typeof import('./src/components/SensitiveString/SensitiveString.vue')['default']
|
||||||
SetLanguageSetLanguage: typeof import('./src/components/SetLanguage/SetLanguage.vue')['default']
|
SetLanguageSetLanguage: typeof import('./src/components/SetLanguage/SetLanguage.vue')['default']
|
||||||
StdDesignStdDataDisplayStdBatchEdit: typeof import('./src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue')['default']
|
StdDesignStdDataDisplayStdBatchEdit: typeof import('./src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue')['default']
|
||||||
|
|
|
@ -1,48 +1,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { TaskReport } from './tasks'
|
|
||||||
import { CheckCircleOutlined, CloseCircleOutlined, WarningOutlined } from '@ant-design/icons-vue'
|
import { CheckCircleOutlined, CloseCircleOutlined, WarningOutlined } from '@ant-design/icons-vue'
|
||||||
|
import { useSelfCheckStore } from './store'
|
||||||
import { taskManager } from './tasks'
|
import { taskManager } from './tasks'
|
||||||
|
|
||||||
const data = ref<TaskReport[]>()
|
const store = useSelfCheckStore()
|
||||||
const requestError = ref(false)
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
async function check() {
|
const { data, loading, fixing } = storeToRefs(store)
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
data.value = await taskManager.runAllChecks()
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
requestError.value = true
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
check()
|
store.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,
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -50,7 +16,10 @@ defineExpose({
|
||||||
<ACard :title="$gettext('Self Check')">
|
<ACard :title="$gettext('Self Check')">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<AButton
|
<AButton
|
||||||
type="link" size="small" :loading="loading" @click="check"
|
type="link"
|
||||||
|
size="small"
|
||||||
|
:loading="loading"
|
||||||
|
@click="store.check"
|
||||||
>
|
>
|
||||||
{{ $gettext('Recheck') }}
|
{{ $gettext('Recheck') }}
|
||||||
</AButton>
|
</AButton>
|
||||||
|
@ -58,7 +27,7 @@ defineExpose({
|
||||||
<AList>
|
<AList>
|
||||||
<AListItem v-for="(item, index) in data" :key="index">
|
<AListItem v-for="(item, index) in data" :key="index">
|
||||||
<template v-if="item.status === 'error'" #actions>
|
<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') }}
|
{{ $gettext('Attempt to fix') }}
|
||||||
</AButton>
|
</AButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
88
app/src/components/SelfCheck/SelfCheckHeaderBanner.vue
Normal file
88
app/src/components/SelfCheck/SelfCheckHeaderBanner.vue
Normal file
|
@ -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>
|
|
@ -1,3 +1,10 @@
|
||||||
import SelfCheck from './SelfCheck.vue'
|
import SelfCheck from './SelfCheck.vue'
|
||||||
|
import SelfCheckHeaderBanner from './SelfCheckHeaderBanner.vue'
|
||||||
|
import { useSelfCheckStore } from './store'
|
||||||
|
|
||||||
|
export {
|
||||||
|
SelfCheckHeaderBanner,
|
||||||
|
useSelfCheckStore,
|
||||||
|
}
|
||||||
|
|
||||||
export default SelfCheck
|
export default SelfCheck
|
||||||
|
|
54
app/src/components/SelfCheck/store.ts
Normal file
54
app/src/components/SelfCheck/store.ts
Normal file
|
@ -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 }
|
||||||
|
})
|
|
@ -1,11 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ShallowRef } from 'vue'
|
|
||||||
import auth from '@/api/auth'
|
import auth from '@/api/auth'
|
||||||
import NginxControl from '@/components/NginxControl/NginxControl.vue'
|
import NginxControl from '@/components/NginxControl'
|
||||||
import Notification from '@/components/Notification/Notification.vue'
|
import Notification from '@/components/Notification'
|
||||||
import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
|
import { SelfCheckHeaderBanner } from '@/components/SelfCheck'
|
||||||
import SwitchAppearance from '@/components/SwitchAppearance/SwitchAppearance.vue'
|
import SetLanguage from '@/components/SetLanguage'
|
||||||
|
import SwitchAppearance from '@/components/SwitchAppearance'
|
||||||
import { DesktopOutlined, HomeOutlined, LogoutOutlined, MenuUnfoldOutlined } from '@ant-design/icons-vue'
|
import { DesktopOutlined, HomeOutlined, LogoutOutlined, MenuUnfoldOutlined } from '@ant-design/icons-vue'
|
||||||
|
import { useElementSize } from '@vueuse/core'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { useRouter } from 'vue-router'
|
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(() => {
|
const isWorkspace = computed(() => {
|
||||||
return !!window.inWorkspace
|
return !!window.inWorkspace
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { width: headerWidth } = useElementSize(headerRef)
|
||||||
|
|
||||||
|
const { width: userWrapperWidth } = useElementSize(userWrapperRef)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -36,7 +42,13 @@ const isWorkspace = computed(() => {
|
||||||
<MenuUnfoldOutlined @click="emit('clickUnFold')" />
|
<MenuUnfoldOutlined @click="emit('clickUnFold')" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SelfCheckHeaderBanner
|
||||||
|
:header-weight="headerWidth"
|
||||||
|
:user-wrapper-width="userWrapperWidth"
|
||||||
|
/>
|
||||||
|
|
||||||
<ASpace
|
<ASpace
|
||||||
|
ref="userWrapperRef"
|
||||||
class="user-wrapper"
|
class="user-wrapper"
|
||||||
:size="24"
|
:size="24"
|
||||||
>
|
>
|
||||||
|
@ -74,7 +86,7 @@ const isWorkspace = computed(() => {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.05);
|
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.05);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
a {
|
a {
|
||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import install from '@/api/install'
|
import install from '@/api/install'
|
||||||
import SelfCheck from '@/components/SelfCheck'
|
import SelfCheck, { useSelfCheckStore } from '@/components/SelfCheck'
|
||||||
import SystemRestoreContent from '@/components/SystemRestore'
|
import SystemRestoreContent from '@/components/SystemRestore'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import InstallFooter from './InstallFooter.vue'
|
import InstallFooter from './InstallFooter.vue'
|
||||||
|
@ -11,7 +11,8 @@ import TimeoutAlert from './TimeoutAlert.vue'
|
||||||
const installTimeout = ref(false)
|
const installTimeout = ref(false)
|
||||||
const activeTab = ref('1')
|
const activeTab = ref('1')
|
||||||
const step = ref(1)
|
const step = ref(1)
|
||||||
const selfCheckRef = useTemplateRef('selfCheckRef')
|
const selfCheckStore = useSelfCheckStore()
|
||||||
|
const { hasError } = storeToRefs(selfCheckStore)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ function handleRestoreSuccess(options: { restoreNginx: boolean, restoreNginxUI:
|
||||||
}
|
}
|
||||||
|
|
||||||
const canProceed = computed(() => {
|
const canProceed = computed(() => {
|
||||||
return !installTimeout.value && !selfCheckRef.value?.hasError
|
return !installTimeout.value && !hasError.value
|
||||||
})
|
})
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
|
@ -87,7 +88,7 @@ const steps = [
|
||||||
</ASteps>
|
</ASteps>
|
||||||
|
|
||||||
<div v-if="step === 1">
|
<div v-if="step === 1">
|
||||||
<SelfCheck ref="selfCheckRef" class="mb-4" />
|
<SelfCheck class="mb-4" />
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<AButton v-if="canProceed" type="primary" @click="step = 2">
|
<AButton v-if="canProceed" type="primary" @click="step = 2">
|
||||||
{{ $gettext('Next') }}
|
{{ $gettext('Next') }}
|
||||||
|
|
|
@ -18,15 +18,15 @@ func getToken(c *gin.Context) (token string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if token, _ = c.Cookie("token"); token != "" {
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
if token = c.Query("token"); token != "" {
|
if token = c.Query("token"); token != "" {
|
||||||
tokenBytes, _ := base64.StdEncoding.DecodeString(token)
|
tokenBytes, _ := base64.StdEncoding.DecodeString(token)
|
||||||
return string(tokenBytes)
|
return string(tokenBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if token, _ = c.Cookie("token"); token != "" {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue