rebrand: fork Gatus as PANDUS with custom UI identity
Some checks failed
test-ui / test-ui (push) Has been cancelled
test / test (push) Has been cancelled

New indigo/purple shield+pulse logo, theme colors, localStorage
key prefixes, manifest metadata. Footer and Social link retain
attribution to upstream Gatus repo per Apache 2.0.
This commit is contained in:
Rebecca Valente 2026-05-28 13:45:28 -03:00
parent ed1107b41a
commit 7c6bc53dc7
10 changed files with 104 additions and 3693 deletions

3658
README.md

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
{
"id": "gatus",
"name": "Gatus",
"short_name": "Gatus",
"description": "Gatus is an advanced automated status page that lets you monitor your applications and configure alerts to notify you if there's an issue",
"id": "pandus",
"name": "PANDUS",
"short_name": "PANDUS",
"description": "PANDUS is a status page and health dashboard for monitoring your applications",
"lang": "en",
"scope": "/",
"start_url": "/",

View File

@ -23,13 +23,13 @@
<img
v-if="logo"
:src="logo"
alt="Gatus"
alt="PANDUS"
class="w-full h-full object-contain"
/>
<img
v-else
src="./assets/logo.svg"
alt="Gatus"
alt="PANDUS"
class="w-full h-full object-contain"
/>
</div>
@ -100,7 +100,7 @@
<div class="container mx-auto px-4 py-6 max-w-7xl">
<div class="flex flex-col items-center gap-4">
<div class="text-sm text-muted-foreground text-center">
Powered by <a href="https://gatus.io" target="_blank" class="font-medium text-emerald-800 hover:text-emerald-600">Gatus</a>
Powered by <a href="https://github.com/TwiN/gatus" target="_blank" class="font-medium text-indigo-600 hover:text-indigo-400 dark:text-indigo-400 dark:hover:text-indigo-300">Gatus</a>
</div>
<Social />
</div>
@ -115,9 +115,9 @@
<div v-if="logo" class="flex items-center justify-center gap-4 mb-4">
<img :src="logo" alt="" class="w-20 h-20 object-contain" />
<div class="w-px h-12 bg-border"></div>
<img src="./assets/logo.svg" alt="Gatus" class="w-20 h-20" />
<img src="./assets/logo.svg" alt="PANDUS" class="w-20 h-20" />
</div>
<img v-else src="./assets/logo.svg" alt="Gatus" class="w-20 h-20 mx-auto mb-4" />
<img v-else src="./assets/logo.svg" alt="PANDUS" class="w-20 h-20 mx-auto mb-4" />
<CardTitle class="text-3xl">{{ header }}</CardTitle>
<p class="text-muted-foreground mt-2">{{ loginSubtitle }}</p>
</CardHeader>
@ -181,7 +181,7 @@ const logo = computed(() => {
})
const header = computed(() => {
return window.config && window.config.header && window.config.header !== '{{ .UI.Header }}' ? window.config.header : "Gatus"
return window.config && window.config.header && window.config.header !== '{{ .UI.Header }}' ? window.config.header : "PANDUS"
})
const link = computed(() => {
@ -193,7 +193,7 @@ const buttons = computed(() => {
})
const loginSubtitle = computed(() => {
return window.config && window.config.loginSubtitle && window.config.loginSubtitle !== '{{ .UI.LoginSubtitle }}' ? window.config.loginSubtitle : "System Monitoring Dashboard"
return window.config && window.config.loginSubtitle && window.config.loginSubtitle !== '{{ .UI.LoginSubtitle }}' ? window.config.loginSubtitle : "Status Monitoring Dashboard"
})
// Methods

View File

@ -1 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 89.76 89.75"><defs><style>.cls-1{fill:#3cad4b;}.cls-2{fill:#017400;}.cls-3{fill:#1e9025;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M33.67,65.35a23.35,23.35,0,0,1,.08-41,22.94,22.94,0,0,1,3.8-1.64A23,23,0,0,0,53.6,1C53,0,51,0,44.89,0c-9.08,0-9.21.17-8.81,3.22,1.07,8.12-9.42,12.5-14.45,6-1.94-2.52-2.1-2.52-8.68,4.16-6.22,6.3-6.33,6.28-3.77,8.25a8.09,8.09,0,0,1,2.56,9.53A8.15,8.15,0,0,1,3.08,36C0,35.63,0,35.73,0,45.2.08,53.81,0,54,3.3,53.63A8.06,8.06,0,0,1,9.76,67.52c-3,2.83-2.84,2.61,2.84,8.48,5.43,5.62,6.33,6.73,8.16,5.24L34,68A1.63,1.63,0,0,0,33.67,65.35Z"/><path class="cls-2" d="M85.43,36.13a8.11,8.11,0,0,1-5.27-14.21c2.85-2.5,2.82-2.37-3.55-8.75-4.31-4.31-5.71-5.75-6.87-5.4l-14,14a1.65,1.65,0,0,0,.36,2.61,23.35,23.35,0,0,1-.1,41,24.5,24.5,0,0,1-5.11,2c-8.54,2.28-14.73,9.63-14.73,18.47v1.27c.15,2.54,1.19,2.42,8.06,2.52,9.32.14,9.1.35,9.38-4.66a8.11,8.11,0,0,1,14-5.09c3,3.15,2.39,3.11,8.73-3.14,6.56-6.47,6.86-6.25,3.68-9.14a8.1,8.1,0,0,1,6.06-14.07c3.68.27,3.51.06,3.63-8.09C89.85,36.27,90,36.16,85.43,36.13Z"/><path class="cls-3" d="M41.11,59h8a.76.76,0,0,0,.77-.76V50.43a.76.76,0,0,1,.77-.76h7.84a.78.78,0,0,0,.77-.77V40.84a.77.77,0,0,0-.77-.76H50.7a.76.76,0,0,1-.77-.77V31.47a.76.76,0,0,0-.77-.77h-8a.76.76,0,0,0-.77.77v7.84a.76.76,0,0,1-.77.77H31.73a.77.77,0,0,0-.77.76V48.9a.78.78,0,0,0,.77.77h7.84a.76.76,0,0,1,.77.76v7.85A.76.76,0,0,0,41.11,59Z"/></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<linearGradient id="sg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#818cf8"/>
<stop offset="100%" stop-color="#4f46e5"/>
</linearGradient>
</defs>
<path d="M32 4 L56 18 Q60 20 60 25 L60 44 Q60 52 52 56 L36 62 Q32 64 28 62 L12 56 Q4 52 4 44 L4 25 Q4 20 8 18 Z" fill="url(#sg)"/>
<path d="M32 8 L53 20 Q56 22 56 26 L56 43 Q56 50 49 53 L35 59 Q32 60 29 59 L15 53 Q8 50 8 43 L8 26 Q8 22 11 20 Z" fill="#4f46e5"/>
<path d="M32 12 L50 22 Q52 24 52 27 L52 42 Q52 48 46 50 L34 56 Q32 57 30 56 L18 50 Q12 48 12 42 L12 27 Q12 24 14 22 Z" fill="#6366f1"/>
<polyline points="14,36 24,36 27,28 30,44 33,24 36,40 39,32 42,36 50,36" fill="none" stroke="#ffffff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 838 B

View File

@ -47,8 +47,8 @@ import { Input } from '@/components/ui/input'
import { Select } from '@/components/ui/select'
const searchQuery = ref('')
const filterBy = ref(localStorage.getItem('gatus:filter-by') || (typeof window !== 'undefined' && window.config?.defaultFilterBy) || 'none')
const sortBy = ref(localStorage.getItem('gatus:sort-by') || (typeof window !== 'undefined' && window.config?.defaultSortBy) || 'name')
const filterBy = ref(localStorage.getItem('pandus:filter-by') || (typeof window !== 'undefined' && window.config?.defaultFilterBy) || 'none')
const sortBy = ref(localStorage.getItem('pandus:sort-by') || (typeof window !== 'undefined' && window.config?.defaultSortBy) || 'name')
const filterOptions = [
{ label: 'None', value: 'none' },
@ -67,7 +67,7 @@ const emit = defineEmits(['search', 'update:showOnlyFailing', 'update:showRecent
const handleFilterChange = (value, store = true) => {
filterBy.value = value
if (store)
localStorage.setItem('gatus:filter-by', value)
localStorage.setItem('pandus:filter-by', value)
// Reset all filter states first
emit('update:showOnlyFailing', false)
@ -84,7 +84,7 @@ const handleFilterChange = (value, store = true) => {
const handleSortChange = (value, store = true) => {
sortBy.value = value
if (store)
localStorage.setItem('gatus:sort-by', value)
localStorage.setItem('pandus:sort-by', value)
emit('update:sortBy', value)
emit('update:groupByGroup', value === 'group')

View File

@ -72,7 +72,7 @@ const DEFAULT_REFRESH_INTERVAL = '300'
const THEME_COOKIE_NAME = 'theme'
const THEME_COOKIE_MAX_AGE = 31536000 // 1 year
const STORAGE_KEYS = {
REFRESH_INTERVAL: 'gatus:refresh-interval'
REFRESH_INTERVAL: 'pandus:refresh-interval'
}
// Helper functions

View File

@ -1,6 +1,6 @@
<template>
<div id="social">
<a href="https://github.com/TwiN/gatus" target="_blank" title="Gatus on GitHub">
<a href="https://github.com/TwiN/gatus" target="_blank" title="Based on Gatus">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 16 16" class="hover:scale-110">
<path fill="gray" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
</svg>

View File

@ -4,48 +4,48 @@
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--background: 240 20% 99%;
--foreground: 240 10% 6%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--card-foreground: 240 10% 6%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--popover-foreground: 240 10% 6%;
--primary: 239 84% 67%;
--primary-foreground: 0 0% 100%;
--secondary: 240 20% 95%;
--secondary-foreground: 240 10% 6%;
--muted: 240 20% 95%;
--muted-foreground: 240 5% 46%;
--accent: 240 20% 95%;
--accent-foreground: 240 10% 6%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--destructive-foreground: 0 0% 100%;
--border: 240 12% 91%;
--input: 240 12% 91%;
--ring: 239 84% 67%;
--radius: 0.5rem;
}
:root.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--background: 240 17% 8%;
--foreground: 240 20% 95%;
--card: 240 17% 10%;
--card-foreground: 240 20% 95%;
--popover: 240 17% 10%;
--popover-foreground: 240 20% 95%;
--primary: 239 84% 67%;
--primary-foreground: 0 0% 100%;
--secondary: 240 15% 18%;
--secondary-foreground: 240 20% 95%;
--muted: 240 15% 18%;
--muted-foreground: 240 10% 60%;
--accent: 240 15% 18%;
--accent-foreground: 240 20% 95%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
--destructive-foreground: 240 20% 95%;
--border: 240 15% 18%;
--input: 240 15% 18%;
--ring: 239 84% 67%;
}
}

View File

@ -224,7 +224,7 @@ const events = ref([])
const currentPage = ref(1)
const resultPageSize = 50
const showResponseTimeChartAndBadges = ref(false)
const showAverageResponseTime = ref(localStorage.getItem('gatus:show-average-response-time') !== 'false')
const showAverageResponseTime = ref(localStorage.getItem('pandus:show-average-response-time') !== 'false')
const selectedChartDuration = ref('24h')
const isRefreshing = ref(false)
@ -247,7 +247,7 @@ const hostname = computed(() => {
const toggleShowAverageResponseTime = () => {
showAverageResponseTime.value = !showAverageResponseTime.value
localStorage.setItem('gatus:show-average-response-time', showAverageResponseTime.value ? 'true' : 'false')
localStorage.setItem('pandus:show-average-response-time', showAverageResponseTime.value ? 'true' : 'false')
}
const pageAverageResponseTime = computed(() => {

View File

@ -219,9 +219,9 @@ const itemsPerPage = 96
const searchQuery = ref('')
const showOnlyFailing = ref(false)
const showRecentFailures = ref(false)
const showAverageResponseTime = ref(localStorage.getItem('gatus:show-average-response-time') !== 'false')
const showAverageResponseTime = ref(localStorage.getItem('pandus:show-average-response-time') !== 'false')
const groupByGroup = ref(false)
const sortBy = ref(localStorage.getItem('gatus:sort-by') || 'name')
const sortBy = ref(localStorage.getItem('pandus:sort-by') || 'name')
const uncollapsedGroups = ref(new Set())
const resultPageSize = 50
@ -483,7 +483,7 @@ const goToPage = (page) => {
const toggleShowAverageResponseTime = () => {
showAverageResponseTime.value = !showAverageResponseTime.value
localStorage.setItem('gatus:show-average-response-time', showAverageResponseTime.value ? 'true' : 'false')
localStorage.setItem('pandus:show-average-response-time', showAverageResponseTime.value ? 'true' : 'false')
}
const showTooltip = (result, event, action = 'hover') => {
@ -513,21 +513,21 @@ const toggleGroupCollapse = (groupName) => {
}
// Save to localStorage
const uncollapsed = Array.from(uncollapsedGroups.value)
localStorage.setItem('gatus:uncollapsed-groups', JSON.stringify(uncollapsed))
localStorage.removeItem('gatus:collapsed-groups') // Remove old key if it exists
localStorage.setItem('pandus:uncollapsed-groups', JSON.stringify(uncollapsed))
localStorage.removeItem('pandus:collapsed-groups') // Remove old key if it exists
}
const initializeCollapsedGroups = () => {
// Get saved uncollapsed groups from localStorage
try {
const saved = localStorage.getItem('gatus:uncollapsed-groups')
const saved = localStorage.getItem('pandus:uncollapsed-groups')
if (saved) {
uncollapsedGroups.value = new Set(JSON.parse(saved))
}
// If no saved state, uncollapsedGroups stays empty (all collapsed by default)
} catch (e) {
console.warn('Failed to parse saved uncollapsed groups:', e)
localStorage.removeItem('gatus:uncollapsed-groups')
localStorage.removeItem('pandus:uncollapsed-groups')
// On error, uncollapsedGroups stays empty (all collapsed by default)
}
}