소스 검색

Brand control UI with ferrite.fm logo

main
Jan 1 개월 전
부모
커밋
c1c50b23b3
7개의 변경된 파일22개의 추가작업 그리고 11개의 파일을 삭제
  1. BIN
      docs/Logo/Minimalist logo.png
  2. BIN
      docs/Logo/unused/ferrite-fm-Logo.png
  3. BIN
      docs/Logo/unused/ferrite-fm-Logo.psd
  4. BIN
      docs/Logo/unused/ferrite-fm-Logo_ultrahighres.png
  5. +10
    -0
      internal/control/control.go
  6. BIN
      internal/control/logo.png
  7. +12
    -11
      internal/control/ui.html

BIN
docs/Logo/Minimalist logo.png 파일 보기

Before After
Width: 2046  |  Height: 769  |  Size: 245KB

BIN
docs/Logo/unused/ferrite-fm-Logo.png 파일 보기

Before After
Width: 644  |  Height: 154  |  Size: 166KB

BIN
docs/Logo/unused/ferrite-fm-Logo.psd 파일 보기


BIN
docs/Logo/unused/ferrite-fm-Logo_ultrahighres.png 파일 보기

Before After
Width: 16100  |  Height: 3850  |  Size: 14MB

+ 10
- 0
internal/control/control.go 파일 보기

@@ -22,6 +22,9 @@ import (
//go:embed ui.html //go:embed ui.html
var uiHTML []byte var uiHTML []byte


//go:embed logo.png
var logoPNG []byte

// TXController is an optional interface the Server uses to start/stop TX // TXController is an optional interface the Server uses to start/stop TX
// and apply live config changes. // and apply live config changes.
type TXController interface { type TXController interface {
@@ -279,6 +282,7 @@ func (s *Server) SetHardReload(fn func()) {
func (s *Server) Handler() http.Handler { func (s *Server) Handler() http.Handler {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/", s.handleUI) mux.HandleFunc("/", s.handleUI)
mux.HandleFunc("/logo", s.handleLogo)
mux.HandleFunc("/healthz", s.handleHealth) mux.HandleFunc("/healthz", s.handleHealth)
mux.HandleFunc("/status", s.handleStatus) mux.HandleFunc("/status", s.handleStatus)
mux.HandleFunc("/dry-run", s.handleDryRun) mux.HandleFunc("/dry-run", s.handleDryRun)
@@ -307,6 +311,12 @@ func (s *Server) handleUI(w http.ResponseWriter, r *http.Request) {
w.Write(uiHTML) w.Write(uiHTML)
} }


func (s *Server) handleLogo(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Write(logoPNG)
}

func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) { func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) {
s.mu.RLock() s.mu.RLock()
cfg := s.cfg cfg := s.cfg


BIN
internal/control/logo.png 파일 보기

Before After
Width: 2046  |  Height: 769  |  Size: 245KB

+ 12
- 11
internal/control/ui.html 파일 보기

@@ -22,16 +22,21 @@ body{background:linear-gradient(180deg,#fbfcfe 0%,var(--bg) 100%);color:var(--te
button,input,select{font:inherit}button{user-select:none} button,input,select{font:inherit}button{user-select:none}
.app{max-width:1560px;margin:0 auto;padding:24px} .app{max-width:1560px;margin:0 auto;padding:24px}
/* Header */ /* Header */
.header{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:4px 0 20px;border-bottom:1px solid var(--border);margin-bottom:20px}
.header-main{display:flex;flex-direction:column;gap:8px}
.header h1{font-size:28px;font-weight:800;letter-spacing:-.03em}
.header{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:2px 0 18px;border-bottom:1px solid var(--border);margin-bottom:20px}
.header-main{display:flex;align-items:center;min-height:64px}
.brand-lockup{display:flex;align-items:center;gap:0}
.brand-logo{height:62px;width:auto;display:block;object-fit:contain}
.brand-title{font-size:28px;font-weight:800;letter-spacing:-.03em}
.header-note{font-size:13px;color:var(--text-muted)} .header-note{font-size:13px;color:var(--text-muted)}
.header-sub{display:flex;flex-wrap:wrap;gap:8px} .header-sub{display:flex;flex-wrap:wrap;gap:8px}
.badge{display:inline-flex;align-items:center;gap:8px;min-height:28px;padding:0 10px;border:1px solid var(--border);border-radius:999px;background:var(--surface2);color:var(--text-dim);font-size:11px;text-transform:uppercase;letter-spacing:.08em} .badge{display:inline-flex;align-items:center;gap:8px;min-height:28px;padding:0 10px;border:1px solid var(--border);border-radius:999px;background:var(--surface2);color:var(--text-dim);font-size:11px;text-transform:uppercase;letter-spacing:.08em}
.badge strong{white-space:nowrap} .badge strong{white-space:nowrap}
.badge .tag{margin-left:2px} .badge .tag{margin-left:2px}
.badge strong{color:var(--text);font-weight:700} .badge strong{color:var(--text);font-weight:700}
.header-status{display:flex;align-items:center;gap:10px;padding-top:6px}
.header-status{display:flex;align-items:center;gap:8px;padding-top:0;padding-right:2px}
.header-status .danger-btn{min-height:36px;padding:0 14px;border-radius:10px;background:rgba(176,48,48,.06);border-color:rgba(176,48,48,.22);font-size:11px;letter-spacing:.08em}
.header-status .danger-btn:hover:not(:disabled){background:rgba(176,48,48,.1)}
.header-status .led{margin-left:4px}
/* LED */ /* LED */
.led{width:10px;height:10px;border-radius:50%;background:#333;transition:all .25s;flex-shrink:0} .led{width:10px;height:10px;border-radius:50%;background:#333;transition:all .25s;flex-shrink:0}
.led.on-green{background:var(--green);box-shadow:0 0 0 3px rgba(13,148,74,.16)} .led.on-green{background:var(--green);box-shadow:0 0 0 3px rgba(13,148,74,.16)}
@@ -285,19 +290,15 @@ input.input-error{border-color:var(--red);box-shadow:0 0 0 3px rgba(176,48,48,.1
.toast.warn{background:var(--amber);color:#141414}.toast.info{background:var(--text-dim);color:#fff} .toast.warn{background:var(--amber);color:#141414}.toast.info{background:var(--text-dim);color:#fff}
/* Responsive */ /* Responsive */
@media(max-width:980px){.app{max-width:100%;padding:14px}.tab-columns.two{grid-template-columns:1fr}.tx-bar{grid-template-columns:1fr}.tx-state-wrap{align-items:flex-start}.status-hint{text-align:left}.quick-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.signal-grid{grid-template-columns:1fr}.flow-master{grid-template-columns:1fr}.flow-topbar,.flow-bottom{grid-template-columns:repeat(2,minmax(0,1fr))}.flow-chain{grid-template-columns:repeat(4,minmax(140px,1fr))}} @media(max-width:980px){.app{max-width:100%;padding:14px}.tab-columns.two{grid-template-columns:1fr}.tx-bar{grid-template-columns:1fr}.tx-state-wrap{align-items:flex-start}.status-hint{text-align:left}.quick-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.signal-grid{grid-template-columns:1fr}.flow-master{grid-template-columns:1fr}.flow-topbar,.flow-bottom{grid-template-columns:repeat(2,minmax(0,1fr))}.flow-chain{grid-template-columns:repeat(4,minmax(140px,1fr))}}
@media(max-width:640px){.app{padding:12px}.header{flex-direction:column;gap:10px}.header h1{font-size:22px}.badge{width:100%;justify-content:space-between}.badge strong{max-width:52%;overflow:hidden;text-overflow:ellipsis}.quick-grid{grid-template-columns:1fr 1fr;gap:8px}.ctrl-row{flex-direction:column;align-items:stretch}.ctrl-label-wrap{min-width:auto}.ctrl-input{flex-wrap:wrap}.ingest-grid{grid-template-columns:1fr}input[type="number"]{width:100%;text-align:left}.actions-row,.tx-actions{flex-direction:column}.tx-btn,.ghost-btn,.apply-btn,.preset-btn,.danger-btn{width:100%}.freq-display{font-size:31px}.flow-master,.flow-topbar,.flow-bottom,.flow-chain{grid-template-columns:1fr}}
@media(max-width:640px){.app{padding:12px}.header{flex-direction:column;align-items:flex-start;gap:10px}.brand-logo{height:54px}.badge{width:100%;justify-content:space-between}.badge strong{max-width:52%;overflow:hidden;text-overflow:ellipsis}.quick-grid{grid-template-columns:1fr 1fr;gap:8px}.ctrl-row{flex-direction:column;align-items:stretch}.ctrl-label-wrap{min-width:auto}.ctrl-input{flex-wrap:wrap}.ingest-grid{grid-template-columns:1fr}input[type="number"]{width:100%;text-align:left}.actions-row,.tx-actions{flex-direction:column}.tx-btn,.ghost-btn,.apply-btn,.preset-btn,.danger-btn{width:100%}.freq-display{font-size:31px}.flow-master,.flow-topbar,.flow-bottom,.flow-chain{grid-template-columns:1fr}}
</style> </style>
</head> </head>
<body> <body>
<div class="app"> <div class="app">
<div class="header"> <div class="header">
<div class="header-main"> <div class="header-main">
<h1>ferrite.fm Control Plane</h1>
<div class="header-note">Operate confidently: tune fast, inspect state instantly, diagnose only when needed.</div>
<div class="header-sub">
<div class="badge"><span>Backend</span><strong id="badge-backend">--</strong></div>
<div class="badge"><span>Mode</span><strong id="badge-mode">Control Plane</strong></div>
<div class="badge"><span>Live Config</span><strong id="badge-live">--</strong></div>
<div class="brand-lockup">
<img class="brand-logo" src="/logo" alt="ferrite.fm logo">
</div> </div>
</div> </div>
<div class="header-status"> <div class="header-status">


불러오는 중...
취소
저장