Compare commits
5 Commits
3099add4c9
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
5146532db6
|
|||
| ced8dc905a | |||
| 2cc1984d10 | |||
| 5d97e58cad | |||
|
|
9bc3af946a |
1
status-web/.yarnrc.yml
Normal file
1
status-web/.yarnrc.yml
Normal file
@@ -0,0 +1 @@
|
||||
nodeLinker: node-modules
|
||||
@@ -1,14 +1,36 @@
|
||||
<template>
|
||||
<div class="app-wrapper">
|
||||
<TheApplicationHeader/>
|
||||
<TheApplicationHeader />
|
||||
<div class="app-content">
|
||||
<NuxtPage/>
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TheApplicationHeader from "~/components/Header/TheApplicationHeader.vue";
|
||||
import TheApplicationHeader from '~/components/Header/TheApplicationHeader.vue'
|
||||
import moment from 'moment'
|
||||
|
||||
moment.updateLocale('en', {
|
||||
relativeTime: {
|
||||
future: 'in %s',
|
||||
past: '%s ago',
|
||||
s: 'a few seconds',
|
||||
ss: '%ds',
|
||||
m: 'm',
|
||||
mm: '%dm',
|
||||
h: 'h',
|
||||
hh: '%dh',
|
||||
d: 'd',
|
||||
dd: '%dd',
|
||||
w: 'w',
|
||||
ww: '%dw',
|
||||
M: 'a month',
|
||||
MM: '%d months',
|
||||
y: 'a year',
|
||||
yy: '%d years',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -17,4 +39,4 @@ import TheApplicationHeader from "~/components/Header/TheApplicationHeader.vue";
|
||||
.app-content {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -11,4 +11,29 @@
|
||||
|
||||
html {
|
||||
background-color: $background;
|
||||
}
|
||||
|
||||
.container {
|
||||
&-center {
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.user-select {
|
||||
&__none {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cursor {
|
||||
&-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-default {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
60
status-web/components/Badge/index.vue
Normal file
60
status-web/components/Badge/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div
|
||||
class="badge"
|
||||
:class="[severity]"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface ComponentProps {
|
||||
uptime?: number
|
||||
}
|
||||
|
||||
const props = defineProps<ComponentProps>()
|
||||
|
||||
const severity = computed<string | null>(() => {
|
||||
if (props.uptime === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (props.uptime < 50) {
|
||||
return 'danger'
|
||||
}
|
||||
|
||||
if (props.uptime < 98) {
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
return 'success'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.badge {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #27272a;
|
||||
border-radius: 8px;
|
||||
width: fit-content;
|
||||
|
||||
&.success {
|
||||
background-color: color-mix(in srgb, #22c55e, transparent 0%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: color-mix(in srgb, #f97316, transparent 0%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: color-mix(in srgb, #ef4444, transparent 0%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -14,4 +14,4 @@
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid $textColor;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,128 @@
|
||||
// @ts-check
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
import stylistic from '@stylistic/eslint-plugin'
|
||||
import parser from 'vue-eslint-parser'
|
||||
import eslintParser from '@typescript-eslint/parser'
|
||||
|
||||
export default withNuxt(
|
||||
// Your custom configs here
|
||||
{
|
||||
plugins: {
|
||||
'@stylistic': stylistic,
|
||||
},
|
||||
files: ['**/*.{ts,js,vue}'],
|
||||
languageOptions: {
|
||||
parser: parser,
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
parserOptions: {
|
||||
parser: eslintParser,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'nuxt/no-cjs-in-config': 'off',
|
||||
'vue/no-unused-components': 'off',
|
||||
'import/named': 'off',
|
||||
'vue/max-attributes-per-line': [
|
||||
'error',
|
||||
{
|
||||
singleline: {
|
||||
max: 1,
|
||||
},
|
||||
multiline: {
|
||||
max: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
'vue/html-self-closing': [
|
||||
'error',
|
||||
{
|
||||
html: {
|
||||
void: 'any',
|
||||
normal: 'always',
|
||||
component: 'always',
|
||||
},
|
||||
svg: 'always',
|
||||
math: 'always',
|
||||
},
|
||||
],
|
||||
'camelcase': ['off', { properties: 'never' }],
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
'no-unused-expressions': 'error',
|
||||
'no-dupe-else-if': 1,
|
||||
'no-duplicate-case': 2,
|
||||
'no-duplicate-imports': 2,
|
||||
'no-fallthrough': 2,
|
||||
'no-unreachable': 2,
|
||||
'no-unsafe-optional-chaining': 2,
|
||||
'no-unsafe-negation': 2,
|
||||
'valid-typeof': 2,
|
||||
'max-depth': ['error', 3],
|
||||
'eqeqeq': 2,
|
||||
'no-var': 2,
|
||||
'prefer-const': 2,
|
||||
'consistent-return': 2,
|
||||
'no-useless-return': 2,
|
||||
'@stylistic/indent': ['error', 4],
|
||||
'@stylistic/operator-linebreak': [
|
||||
'error',
|
||||
'after',
|
||||
{
|
||||
overrides: {
|
||||
'?': 'before',
|
||||
':': 'before',
|
||||
},
|
||||
},
|
||||
],
|
||||
'@stylistic/comma-dangle': [
|
||||
'error',
|
||||
{
|
||||
arrays: 'always-multiline',
|
||||
objects: 'always-multiline',
|
||||
imports: 'always-multiline',
|
||||
exports: 'always-multiline',
|
||||
functions: 'always-multiline',
|
||||
},
|
||||
],
|
||||
'@stylistic/max-len': [
|
||||
'error',
|
||||
{
|
||||
code: 120,
|
||||
tabWidth: 5,
|
||||
ignoreUrls: true,
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
ignoreRegExpLiterals: true,
|
||||
},
|
||||
],
|
||||
'@stylistic/array-element-newline': ['error', 'consistent'],
|
||||
'@stylistic/function-call-argument-newline': ['error', 'consistent'],
|
||||
'@stylistic/function-paren-newline': ['error', 'multiline-arguments'],
|
||||
'@stylistic/newline-per-chained-call': ['error', { ignoreChainWithDepth: 2 }],
|
||||
'@stylistic/no-multiple-empty-lines': 'error',
|
||||
'@stylistic/object-curly-spacing': ['error', 'always'],
|
||||
'@stylistic/block-spacing': ['error', 'always'],
|
||||
'@stylistic/brace-style': ['error', '1tbs'],
|
||||
'@stylistic/key-spacing': 'error',
|
||||
'@stylistic/linebreak-style': 'error',
|
||||
'@stylistic/member-delimiter-style': 'error',
|
||||
'@stylistic/no-whitespace-before-property': 'error',
|
||||
'@stylistic/quotes': ['error', 'single'],
|
||||
'@stylistic/space-before-blocks': 'error',
|
||||
'@stylistic/eol-last': ['error', 'always'],
|
||||
'@stylistic/padding-line-between-statements': [
|
||||
'error',
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: '*',
|
||||
next: 'return',
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: 'block-like',
|
||||
next: '*',
|
||||
},
|
||||
],
|
||||
'@stylistic/semi': ['error', 'never'],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,21 +1,55 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
|
||||
import Aura from '@primeuix/themes/aura'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: {enabled: true},
|
||||
modules: [
|
||||
'@nuxt/eslint',
|
||||
'@nuxt/image',
|
||||
'@nuxt/fonts',
|
||||
'@primevue/nuxt-module',
|
||||
],
|
||||
ssr: true,
|
||||
devtools: { enabled: true },
|
||||
|
||||
vite:{
|
||||
runtimeConfig: {
|
||||
apiHost: '',
|
||||
},
|
||||
|
||||
routeRules: {
|
||||
'/api/**': {
|
||||
proxy: {
|
||||
to: process.env.NUXT_API_HOST + '/api/**',
|
||||
},
|
||||
},
|
||||
},
|
||||
compatibilityDate: '2025-07-15',
|
||||
vite: {
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@use '~/assets/variables.scss' as *;`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
additionalData: `@forward '~/assets/global';
|
||||
@use '~/assets/variables' as *;`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
eslint: {
|
||||
checker: true,
|
||||
|
||||
config: {
|
||||
stylistic: true,
|
||||
},
|
||||
},
|
||||
|
||||
primevue: {
|
||||
autoImport: true,
|
||||
options: {
|
||||
ripple: false,
|
||||
inputVariant: 'filled',
|
||||
theme: {
|
||||
preset: Aura,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -18,13 +18,23 @@
|
||||
"@nuxt/image": "1.11.0",
|
||||
"@primeuix/themes": "^1.2.3",
|
||||
"eslint": "^9.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"nuxt": "^3.18.1",
|
||||
"primevue": "^4.3.7",
|
||||
"vue": "^3.5.18",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
||||
"@primevue/nuxt-module": "^4.3.7",
|
||||
"sass-embedded": "^1.90.0"
|
||||
}
|
||||
"@stylistic/eslint-plugin": "^5.2.3",
|
||||
"@typescript-eslint/parser": "^8.39.1",
|
||||
"eslint-plugin-vue": "^10.4.0",
|
||||
"sass-embedded": "^1.90.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"vite-plugin-eslint2": "^5.0.4",
|
||||
"vue-eslint-parser": "^10.2.0"
|
||||
},
|
||||
"packageManager": "yarn@4.9.2"
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import ServicesView from "~/view/public/ServicesView.vue";
|
||||
|
||||
import ServicesView from '~/view/public/ServicesView.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ServicesView/>
|
||||
<ServicesView />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
8
status-web/types/response/publicServices.ts
Normal file
8
status-web/types/response/publicServices.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { ServiceStatus } from '~/types/response/services'
|
||||
|
||||
export interface PublicService {
|
||||
name: string
|
||||
description: string | null
|
||||
statuses: ServiceStatus[]
|
||||
uptime: number
|
||||
}
|
||||
11
status-web/types/response/services.ts
Normal file
11
status-web/types/response/services.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface ServiceStatus {
|
||||
id: number
|
||||
status: 'ok' | 'warn' | 'failed' | 'unchecked'
|
||||
description: string | null
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
// todo
|
||||
export interface Service {
|
||||
|
||||
}
|
||||
@@ -1,13 +1,192 @@
|
||||
<template>
|
||||
<div class="container-center">
|
||||
<div class="services-view">
|
||||
|
||||
<div class="services-view__summary">
|
||||
<div class="services-view__summary-text">
|
||||
<span>
|
||||
All services is alive
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="services-view__items">
|
||||
<div class="services-view__items-item" v-for="(service, index) in services" :key="index">
|
||||
<div class="service-sla">
|
||||
<Badge
|
||||
v-tooltip.bottom="'Uptime based on 24h data'"
|
||||
class="user-select__none cursor-default"
|
||||
:uptime="service.uptime"
|
||||
>
|
||||
{{ service.uptime }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="service-describe">
|
||||
<div class="service-name">
|
||||
<span>{{ service.name }}</span>
|
||||
</div>
|
||||
<div class="service-beats">
|
||||
<div class="service-beats__items">
|
||||
<template v-for="(status, index) in service.statuses" :key="status.id !== 0 ? status.id : index">
|
||||
<div v-tooltip.bottom="`${moment(status.createdAt).format('MMMM Do YYYY on HH:mm:ss')}`" class="service-beats__beat" :class="[status.status !== 'ok' && status.status]"/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="service-beats__time-marks">
|
||||
<div class="service-beats__time-mark">
|
||||
<template v-if="service.statuses[0].id !== 0 && service.statuses[0].createdAt">
|
||||
{{ moment(service.statuses[0].createdAt).fromNow() }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="service-beats__time-mark">
|
||||
<span>Now</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import moment from "moment";
|
||||
import type {PublicService} from "~/types/response/publicServices";
|
||||
import Badge from "~/components/Badge/index.vue";
|
||||
|
||||
const {data, refresh} = useFetch<PublicService[]>('/api/public/service')
|
||||
|
||||
const updateInterval = ref<NodeJS.Timeout | null>(null);
|
||||
|
||||
// onMounted(() => {
|
||||
// updateInterval.value = setInterval(async () => {
|
||||
// await refresh()
|
||||
// }, 5 * 60 * 1000)
|
||||
// })
|
||||
//
|
||||
// onBeforeUnmount(() => {
|
||||
// if (updateInterval.value == null) {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// updateInterval.value.close()
|
||||
//
|
||||
// })
|
||||
|
||||
const services = computed<PublicService[]>(() => {
|
||||
const items = data.value
|
||||
if (!items) {
|
||||
return []
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="services-view">
|
||||
sas
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
.services {
|
||||
&-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 48px;
|
||||
|
||||
&__summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 26px;
|
||||
border-radius: 6px;
|
||||
|
||||
background-color: color-mix(in srgb, #22c55e, transparent 0%);
|
||||
|
||||
&-text {
|
||||
& > span {
|
||||
color: white;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&__items {
|
||||
background-color: #0d1117;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-direction: column;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.service {
|
||||
&-sla {
|
||||
flex-basis: 60px;
|
||||
}
|
||||
|
||||
&-describe {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-beats {
|
||||
&__time {
|
||||
&-mark {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-marks {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&__items {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__beat {
|
||||
background-color: color-mix(in srgb, #22c55e, transparent 0%);
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
border-radius: 10em;
|
||||
margin: 2px;
|
||||
transition: scale .2s;
|
||||
|
||||
&:hover {
|
||||
scale: 1.5;
|
||||
}
|
||||
|
||||
&.warn {
|
||||
background-color: color-mix(in srgb, #f97316, transparent 0%);
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: color-mix(in srgb, #ef4444, transparent 0%);
|
||||
}
|
||||
|
||||
&.unchecked {
|
||||
background-color: #27272a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
21762
status-web/yarn.lock
21762
status-web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user