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']
|
||||
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']
|
||||
|
|
|
@ -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
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 SelfCheckHeaderBanner from './SelfCheckHeaderBanner.vue'
|
||||
import { useSelfCheckStore } from './store'
|
||||
|
||||
export {
|
||||
SelfCheckHeaderBanner,
|
||||
useSelfCheckStore,
|
||||
}
|
||||
|
||||
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">
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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') }}
|
||||
|
|
|
@ -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 ""
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue