Compare commits

..

5 Commits

Author SHA1 Message Date
5146532db6 feat: Working on main page 2025-08-21 13:06:27 +03:00
ced8dc905a dev 2025-08-12 18:35:30 +03:00
2cc1984d10 feat: Bump lockfile 2025-08-12 13:08:28 +03:00
5d97e58cad Merge remote-tracking branch 'origin/main' into dev 2025-08-12 13:07:24 +03:00
Николай Аношин
9bc3af946a feat: Set new yarn version 2025-08-12 12:49:33 +03:00
13 changed files with 14081 additions and 8208 deletions

1
status-web/.yarnrc.yml Normal file
View File

@@ -0,0 +1 @@
nodeLinker: node-modules

View File

@@ -1,14 +1,36 @@
<template> <template>
<div class="app-wrapper"> <div class="app-wrapper">
<TheApplicationHeader/> <TheApplicationHeader />
<div class="app-content"> <div class="app-content">
<NuxtPage/> <NuxtPage />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <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> </script>
<style lang="scss"> <style lang="scss">
@@ -17,4 +39,4 @@ import TheApplicationHeader from "~/components/Header/TheApplicationHeader.vue";
.app-content { .app-content {
padding: 20px; padding: 20px;
} }
</style> </style>

View File

@@ -11,4 +11,29 @@
html { html {
background-color: $background; 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;
}
} }

View 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>

View File

@@ -14,4 +14,4 @@
padding: 20px; padding: 20px;
border-bottom: 1px solid $textColor; border-bottom: 1px solid $textColor;
} }
</style> </style>

View File

@@ -1,6 +1,128 @@
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs' 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( 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'],
},
},
) )

View File

@@ -1,21 +1,55 @@
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
import Aura from '@primeuix/themes/aura'
export default defineNuxtConfig({ export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: {enabled: true},
modules: [ modules: [
'@nuxt/eslint', '@nuxt/eslint',
'@nuxt/image', '@nuxt/image',
'@nuxt/fonts', '@nuxt/fonts',
'@primevue/nuxt-module', '@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: { css: {
preprocessorOptions: { preprocessorOptions: {
scss: { 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,
},
},
},
})

View File

@@ -18,13 +18,23 @@
"@nuxt/image": "1.11.0", "@nuxt/image": "1.11.0",
"@primeuix/themes": "^1.2.3", "@primeuix/themes": "^1.2.3",
"eslint": "^9.0.0", "eslint": "^9.0.0",
"moment": "^2.30.1",
"nuxt": "^3.18.1", "nuxt": "^3.18.1",
"primevue": "^4.3.7", "primevue": "^4.3.7",
"vue": "^3.5.18", "vue": "^3.5.18",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@nuxtjs/eslint-config-typescript": "^12.1.0",
"@primevue/nuxt-module": "^4.3.7", "@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"
} }

View File

@@ -1,12 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import ServicesView from "~/view/public/ServicesView.vue"; import ServicesView from '~/view/public/ServicesView.vue'
</script> </script>
<template> <template>
<ServicesView/> <ServicesView />
</template> </template>
<style scoped> <style scoped>
</style> </style>

View File

@@ -0,0 +1,8 @@
import type { ServiceStatus } from '~/types/response/services'
export interface PublicService {
name: string
description: string | null
statuses: ServiceStatus[]
uptime: number
}

View File

@@ -0,0 +1,11 @@
export interface ServiceStatus {
id: number
status: 'ok' | 'warn' | 'failed' | 'unchecked'
description: string | null
createdAt: string
}
// todo
export interface Service {
}

View File

@@ -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"> <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> </script>
<template>
<div class="services-view">
sas
</div>
</template>
<style scoped lang="scss"> <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>

File diff suppressed because it is too large Load Diff