diff --git a/homer/assets/custom.css b/homer/assets/custom.css
index 25b9df7..a3da684 100755
--- a/homer/assets/custom.css
+++ b/homer/assets/custom.css
@@ -139,4 +139,71 @@ body #bighead .navbar a:focus, body #bighead .navbar a:hover {
/* Hide theme toggle button only */
.navbar-item[aria-label="Toggle dark mode"] {
display: none !important;
+}
+
+/* Prod/Dev 버튼 스타일 */
+.prod-dev-buttons {
+ display: flex;
+ gap: 0.5rem;
+ margin-top: 0.75rem;
+ padding-top: 0.75rem;
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+[data-theme="dark"] .prod-dev-buttons,
+.dark .prod-dev-buttons {
+ border-top-color: rgba(255, 255, 255, 0.1);
+}
+
+.env-button {
+ flex: 1;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.5rem;
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 0.875rem;
+ transition: all 0.2s ease;
+ border: 2px solid transparent;
+}
+
+.prod-button {
+ background-color: #48c774;
+ color: white;
+}
+
+.prod-button:hover {
+ background-color: #3ab66d;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 4px rgba(72, 199, 116, 0.3);
+}
+
+.dev-button {
+ background-color: #ffdd57;
+ color: #363636;
+}
+
+.dev-button:hover {
+ background-color: #ffd83d;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 4px rgba(255, 221, 87, 0.3);
+}
+
+.env-button i {
+ font-size: 0.875rem;
+}
+
+/* Production 카드의 기존 링크 비활성화 */
+.card[data-has-buttons] > a,
+.card[data-has-buttons] .card-content > a {
+ pointer-events: none;
+ cursor: default;
+}
+
+/* 버튼이 있는 카드의 하단 여백 조정 */
+.card:has(.prod-dev-buttons) .card-content {
+ padding-bottom: 0.5rem;
}
\ No newline at end of file
diff --git a/homer/assets/custom.js b/homer/assets/custom.js
new file mode 100644
index 0000000..078d78b
--- /dev/null
+++ b/homer/assets/custom.js
@@ -0,0 +1,153 @@
+// Production 애플리케이션의 Prod/Dev URL 매핑
+const productionApps = {
+ 'Joossam': {
+ prod: 'https://joossameng.com',
+ dev: 'https://dev.joossameng.com'
+ },
+ 'Jaejadle': {
+ prod: 'https://jaejadle.kro.kr',
+ dev: 'https://dev.jaejadle.kro.kr'
+ },
+ 'Jotion': {
+ prod: 'https://jotion.kro.kr',
+ dev: 'https://dev.jotion.kro.kr'
+ },
+ 'Portfolio': {
+ prod: 'https://minjo0213.kro.kr',
+ dev: 'https://dev.minjo0213.kro.kr'
+ },
+ 'Todo': {
+ prod: 'https://todo0213.kro.kr',
+ dev: 'https://dev.todo0213.kro.kr'
+ },
+ 'Jovies': {
+ prod: 'https://jovies.kro.kr',
+ dev: 'https://dev.jovies.kro.kr'
+ }
+};
+
+// Production 카드에 Prod/Dev 버튼 추가
+function addProdDevButtons() {
+ // Production 섹션의 모든 카드 찾기
+ const productionSection = document.querySelector('[data-group="Production"]') ||
+ Array.from(document.querySelectorAll('.group-title'))
+ .find(el => el.textContent.trim() === 'Production')?.closest('.column');
+
+ if (!productionSection) {
+ // 다른 방법으로 Production 섹션 찾기
+ const allCards = document.querySelectorAll('.card');
+ allCards.forEach(card => {
+ const titleElement = card.querySelector('.title');
+ if (titleElement && productionApps[titleElement.textContent.trim()]) {
+ addButtonsToCard(card, titleElement.textContent.trim());
+ }
+ });
+ return;
+ }
+
+ // Production 섹션 내의 모든 카드 찾기
+ const cards = productionSection.querySelectorAll('.card');
+ cards.forEach(card => {
+ const titleElement = card.querySelector('.title');
+ if (titleElement) {
+ const appName = titleElement.textContent.trim();
+ if (productionApps[appName]) {
+ addButtonsToCard(card, appName);
+ }
+ }
+ });
+}
+
+// 카드에 Prod/Dev 버튼 추가
+function addButtonsToCard(card, appName) {
+ // 이미 버튼이 추가되었는지 확인
+ if (card.querySelector('.prod-dev-buttons')) {
+ return;
+ }
+
+ const appUrls = productionApps[appName];
+ if (!appUrls) return;
+
+ // 기존 링크 제거 또는 비활성화
+ const existingLink = card.querySelector('a[href]');
+ if (existingLink) {
+ existingLink.style.pointerEvents = 'none';
+ existingLink.style.cursor = 'default';
+ }
+
+ // 버튼 컨테이너 생성
+ const buttonContainer = document.createElement('div');
+ buttonContainer.className = 'prod-dev-buttons';
+
+ // Prod 버튼
+ const prodButton = document.createElement('a');
+ prodButton.href = appUrls.prod;
+ prodButton.target = '_blank';
+ prodButton.className = 'env-button prod-button';
+ prodButton.innerHTML = ' Prod';
+ prodButton.title = 'Production Environment';
+
+ // Dev 버튼
+ const devButton = document.createElement('a');
+ devButton.href = appUrls.dev;
+ devButton.target = '_blank';
+ devButton.className = 'env-button dev-button';
+ devButton.innerHTML = ' Dev';
+ devButton.title = 'Development Environment';
+
+ buttonContainer.appendChild(prodButton);
+ buttonContainer.appendChild(devButton);
+
+ // 카드의 content 영역에 버튼 추가
+ const cardContent = card.querySelector('.card-content') || card;
+ cardContent.appendChild(buttonContainer);
+}
+
+// DOM이 로드된 후 실행
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', () => {
+ // 약간의 지연을 두고 실행 (Homer가 카드를 렌더링할 시간 필요)
+ setTimeout(addProdDevButtons, 500);
+ // MutationObserver로 동적 콘텐츠 감지
+ observeChanges();
+ });
+} else {
+ setTimeout(addProdDevButtons, 500);
+ observeChanges();
+}
+
+// DOM 변경 감지하여 동적으로 추가된 카드에도 버튼 추가
+function observeChanges() {
+ const observer = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ if (mutation.addedNodes.length) {
+ mutation.addedNodes.forEach((node) => {
+ if (node.nodeType === 1) { // Element node
+ if (node.classList && node.classList.contains('card')) {
+ const titleElement = node.querySelector('.title');
+ if (titleElement && productionApps[titleElement.textContent.trim()]) {
+ addButtonsToCard(node, titleElement.textContent.trim());
+ }
+ }
+ // 자식 노드에서도 카드 찾기
+ const cards = node.querySelectorAll && node.querySelectorAll('.card');
+ if (cards) {
+ cards.forEach(card => {
+ const titleElement = card.querySelector('.title');
+ if (titleElement && productionApps[titleElement.textContent.trim()]) {
+ addButtonsToCard(card, titleElement.textContent.trim());
+ }
+ });
+ }
+ }
+ });
+ }
+ });
+ });
+
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true
+ });
+}
+
diff --git a/homer/config.yml b/homer/config.yml
index c73a9a9..f23a846 100644
--- a/homer/config.yml
+++ b/homer/config.yml
@@ -20,7 +20,7 @@ defaults:
colorTheme: "light" # Set default theme to light mode
# Optional columns
-columns: "4" # You can change this to any number that is a factor of 12: (1, 2, 3, 4, 6, 12)
+columns: "3" # You can change this to any number that is a factor of 12: (1, 2, 3, 4, 6, 12)
# Optional colors
colors:
@@ -59,6 +59,10 @@ websocket:
url: "ws://localhost:8080"
reconnectInterval: 5000
+# Custom HTML (for loading JavaScript)
+custom:
+ - ''
+
# Optional services
services:
- name: "Production"
@@ -113,7 +117,7 @@ services:
url: "https://jovies.kro.kr"
target: "_blank"
- - name: "Development"
+ - name: "Development Tools"
icon: "fas fa-laptop-code"
items:
- name: "Chainlit"
@@ -148,22 +152,6 @@ services:
keywords: "git gitea"
url: "https://gitea0213.kro.kr"
target: "_blank"
- - name: "Goldilocks"
- logo: "/assets/icons/goldilocks.svg"
- subtitle: "Resource Management"
- tag: "dev"
- tagstyle: "is-warning"
- keywords: "goldilocks resource management"
- url: "https://goldilocks0213.kro.kr"
- target: "_blank"
- - name: "Homer Dashboard"
- logo: "/assets/icons/favicon-32x32.png"
- subtitle: "Dashboard"
- tag: "dev"
- tagstyle: "is-warning"
- keywords: "dashboard"
- url: "https://mayne.kro.kr"
- target: "_blank"
- name: "Minecraft Server"
logo: "/assets/icons/minecraft.svg"
subtitle: "Coming Soon"
@@ -180,14 +168,6 @@ services:
keywords: "Auto Hot Reload"
url: "https://tilt0213.kro.kr"
target: "_blank"
- - name: "Velero"
- logo: "/assets/icons/velero.webp"
- subtitle: "Backup & Disaster Recovery"
- tag: "dev"
- tagstyle: "is-warning"
- keywords: "backup restore"
- url: "https://velero0213.kro.kr"
- target: "_blank"
- name: "Infrastructure"
icon: "fas fa-server"
@@ -200,21 +180,13 @@ services:
keywords: "argocd gitops"
url: "https://argocd0213.kro.kr"
target: "_blank"
- - name: "Falco"
- logo: "/assets/icons/falco.svg"
- subtitle: "Runtime Security"
+ - name: "Homer Dashboard"
+ logo: "/assets/icons/favicon-32x32.png"
+ subtitle: "Dashboard"
tag: "infra"
tagstyle: "is-info"
- keywords: "falco security runtime"
- url: "https://falco0213.kro.kr"
- target: "_blank"
- - name: "Grafana"
- logo: "/assets/icons/grafana.svg"
- subtitle: "Monitoring Dashboard"
- tag: "infra"
- tagstyle: "is-info"
- keywords: "grafana monitoring"
- url: "https://grafana0213.kro.kr"
+ keywords: "dashboard"
+ url: "https://mayne.kro.kr"
target: "_blank"
- name: "Kubernetes Dashboard"
logo: "/assets/icons/kubernetes.svg"
@@ -224,38 +196,6 @@ services:
keywords: "kubernetes k8s"
url: "https://kubernetes0213.kro.kr"
target: "_blank"
- - name: "Longhorn"
- logo: "/assets/icons/longhorn.webp"
- subtitle: "Block Storage Management"
- tag: "infra"
- tagstyle: "is-info"
- keywords: "longhorn storage"
- url: "https://longhorn0213.kro.kr"
- target: "_blank"
- - name: "MinIO Console"
- logo: "/assets/icons/minio.svg"
- subtitle: "S3 Storage Management"
- tag: "infra"
- tagstyle: "is-info"
- keywords: "minio s3 storage"
- url: "https://minio0213.kro.kr"
- target: "_blank"
- - name: "PgWeb"
- logo: "/assets/icons/postgresql.svg"
- subtitle: "PostgreSQL Management"
- tag: "infra"
- tagstyle: "is-info"
- keywords: "postgresql pgweb"
- url: "https://pgweb0213.kro.kr"
- target: "_blank"
- - name: "Umami"
- logo: "/assets/icons/umami.svg"
- subtitle: "Website Analytics"
- tag: "infra"
- tagstyle: "is-warning"
- keywords: "analytics umami"
- url: "https://umami0213.kro.kr"
- target: "_blank"
- name: "Vault"
logo: "/assets/icons/vault.svg"
subtitle: "Secret Management"
@@ -264,6 +204,78 @@ services:
keywords: "vault management"
url: "https://vault0213.kro.kr"
target: "_blank"
+ - name: "Velero"
+ logo: "/assets/icons/velero.webp"
+ subtitle: "Backup & Disaster Recovery"
+ tag: "infra"
+ tagstyle: "is-info"
+ keywords: "backup restore"
+ url: "https://velero0213.kro.kr"
+ target: "_blank"
+
+ - name: "Monitoring"
+ icon: "fas fa-chart-line"
+ items:
+ - name: "Falco"
+ logo: "/assets/icons/falco.svg"
+ subtitle: "Runtime Security"
+ tag: "infra"
+ tagstyle: "is-info"
+ keywords: "falco security runtime"
+ url: "https://falco0213.kro.kr"
+ target: "_blank"
+ - name: "Goldilocks"
+ logo: "/assets/icons/goldilocks.svg"
+ subtitle: "Resource Management"
+ tag: "monitoring"
+ tagstyle: "is-info"
+ keywords: "goldilocks resource management"
+ url: "https://goldilocks0213.kro.kr"
+ target: "_blank"
+ - name: "Grafana"
+ logo: "/assets/icons/grafana.svg"
+ subtitle: "Monitoring Dashboard"
+ tag: "monitoring"
+ tagstyle: "is-info"
+ keywords: "grafana monitoring"
+ url: "https://grafana0213.kro.kr"
+ target: "_blank"
+ - name: "Umami"
+ logo: "/assets/icons/umami.svg"
+ subtitle: "Website Analytics"
+ tag: "monitoring"
+ tagstyle: "is-info"
+ keywords: "analytics umami"
+ url: "https://umami0213.kro.kr"
+ target: "_blank"
+
+ - name: "Storage & Database"
+ icon: "fas fa-database"
+ items:
+ - name: "Longhorn"
+ logo: "/assets/icons/longhorn.webp"
+ subtitle: "Block Storage Management"
+ tag: "storage"
+ tagstyle: "is-info"
+ keywords: "longhorn storage"
+ url: "https://longhorn0213.kro.kr"
+ target: "_blank"
+ - name: "MinIO Console"
+ logo: "/assets/icons/minio.svg"
+ subtitle: "S3 Storage Management"
+ tag: "storage"
+ tagstyle: "is-info"
+ keywords: "minio s3 storage"
+ url: "https://minio0213.kro.kr"
+ target: "_blank"
+ - name: "PgWeb"
+ logo: "/assets/icons/postgresql.svg"
+ subtitle: "PostgreSQL Management"
+ tag: "storage"
+ tagstyle: "is-info"
+ keywords: "postgresql pgweb"
+ url: "https://pgweb0213.kro.kr"
+ target: "_blank"
- name: "Demos"
icon: "fas fa-code-branch"