本笔记介绍了如何通过 Docker 部署一个名为 Sun-Panel
的私有化 NAS 导航主页。该教程以“非牛系统”为例,采用“迷你行模式”进行快速部署,整个过程仅需复制粘贴两行代码。
目录
部署与初始化 (00:07)
本章节介绍 Sun-Panel
的完整部署流程,从连接服务器到完成基本设置。
1. 部署步骤
该教程使用 飞牛系统
作为演示平台,通过 SSH 执行 Docker 命令进行部署。
1. 连接服务器:使用 SSH 工具连接到 飞牛
服务器。
2. 切换用户:输入 su
或 sudo -i
命令切换到 root
用户权限。
3. 执行代码:复制并执行视频简介中提供的部署代码。该代码会自动拉取 Sun-Panel
的 Docker 镜像并创建容器。
4. 验证运行:代码执行成功后,回到 飞牛系统
的管理界面,可以看到 Sun-Panel
容器已处于运行状态。
2. 初始化设置
首次访问并进行基础账户配置。
1. 访问页面:在浏览器中打开 Sun-Panel
的 Web 页面。
2. 默认登录:
* 用户名:[email protected]
* 密码:12345678
3. 修改账户:
* 登录后,点击右上角的 设置 图标。
* 在账户管理中,添加一个新账户,并设置为自己熟悉的用户名和密码。
* 保存新账户后,刷新页面并使用新账户登录。
* 登录成功后,可返回设置将默认的 [email protected]
账户删除,以增强安全性。
核心功能配置 (01:31)
完成初始化后,可以开始对导航页进行个性化配置,添加服务和整理布局。
1. 添加容器到首页
Sun-Panel
可以集中管理和展示服务器上运行的所有 Docker 容器。
* 进入 Docker 管理:在 Sun-Panel
侧边栏点击 Docker 选项,这里会列出服务器上所有的容器。
* 添加容器:
1. 点击目标容器旁的 添加 按钮。
2. 在弹出的窗口中,打开 显示状态 开关,以便在首页看到容器的运行状态。
3. 将容器的访问地址(IP + 端口号,例如 http://192.168.1.10:8080
)填入 默认地址 栏。
4. 点击 在线获取图标,系统会自动抓取网站图标。
5. 保存后,该服务就会显示在首页。
* 状态指示:正在运行的容器,其图标左上角会显示一个 绿色小圆点。
2. 获取与替换图标
如果在线获取的图标不清晰或获取失败,可以手动替换。
* 推荐网站:视频中推荐了一个专门用于获取高质量应用图标的网站。
* 手动上传:在添加或编辑服务项目的窗口中,可以手动上传本地图标文件。
3. 分组管理
为了更好地组织服务,可以创建和管理分组。
* 进入分组管理:在侧边栏进入 分组管理 页面。
* 操作:
* 可以修改默认分组的名称。
* 可以点击 添加分组 来创建新的分类,例如“影音服务”、“下载工具”等。
* 在添加服务时,可以选择将其归入指定的分组。
高级个性化设置 (03:16)
Sun-Panel
提供了一些高级选项,用于进一步美化和定制导航页。
1. 系统状态显示
在首页添加硬件资源监控小部件。
* 进入系统状态:在侧边栏进入 系统状态 设置。
* 添加部件:可以勾选并添加 内存 和 硬盘 的显示信息,实时监控系统资源占用情况。
2. 自定义分页
当服务项目非常多时,可以使用分页功能来保持页面整洁。
* 启用方法:此功能通过添加自定义 CSS 代码实现。
* 操作步骤:
1. 进入 个性化 -> 自定义样式 页面。
2. 将视频简介中提供的分页代码粘贴到 CSS 编辑框中。
3. 保存后刷新页面。
* 效果:页面左侧会出现分页选项卡,可以将不同分组的服务置于不同的页面中,使导航页更加有序。
全部命令
图标网址
1 2 3
| [https://github.com/gkyang2022/dashboard-icons](https://github.com/gkyang2022/dashboard-icons)
|
docker命令
1 2 3 4 5 6 7
| docker pull hslr/sun-panel:latest
docker run -d --restart=always -p 3002:3002 \ -v ~/docker_data/sun-panel/conf:/app/conf \ -v /var/run/docker.sock:/var/run/docker.sock \ --name sun-panel \ hslr/sun-panel:latest
|
自定义页脚
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
| <script> (function () { const scrollOffset = 80
const displayStyle = 'auto'
const mobileWidth = 800
const SunPanelTOCDomIdName = 'sun-panel-toc-dom'
const svgTocMobileBtn = '<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" viewBox="0 0 24 24"><path fill="currentColor" d="M17.5 4.5c-1.95 0-4.05.4-5.5 1.5c-1.45-1.1-3.55-1.5-5.5-1.5c-1.45 0-2.99.22-4.28.79C1.49 5.62 1 6.33 1 7.14v11.28c0 1.3 1.22 2.26 2.48 1.94c.98-.25 2.02-.36 3.02-.36c1.56 0 3.22.26 4.56.92c.6.3 1.28.3 1.87 0c1.34-.67 3-.92 4.56-.92c1 0 2.04.11 3.02.36c1.26.33 2.48-.63 2.48-1.94V7.14c0-.81-.49-1.52-1.22-1.85c-1.28-.57-2.82-.79-4.27-.79M21 17.23c0 .63-.58 1.09-1.2.98c-.75-.14-1.53-.2-2.3-.2c-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5c.92 0 1.83.09 2.7.28c.46.1.8.51.8.98z"/><path fill="currentColor" d="M13.98 11.01c-.32 0-.61-.2-.71-.52c-.13-.39.09-.82.48-.94c1.54-.5 3.53-.66 5.36-.45c.41.05.71.42.66.83s-.42.71-.83.66c-1.62-.19-3.39-.04-4.73.39c-.08.01-.16.03-.23.03m0 2.66c-.32 0-.61-.2-.71-.52c-.13-.39.09-.82.48-.94c1.53-.5 3.53-.66 5.36-.45c.41.05.71.42.66.83s-.42.71-.83.66c-1.62-.19-3.39-.04-4.73.39a1 1 0 0 1-.23.03m0 2.66c-.32 0-.61-.2-.71-.52c-.13-.39.09-.82.48-.94c1.53-.5 3.53-.66 5.36-.45c.41.05.71.42.66.83s-.42.7-.83.66c-1.62-.19-3.39-.04-4.73.39a1 1 0 0 1-.23.03"/></svg>'
const scrollContainerElementClassName = '.scroll-container'
const isMobile = () => { if (displayStyle === 'mobile') { return true } else if (displayStyle === 'pc') { return false } const width = window.innerWidth return width < mobileWidth }
function createDom() { (function () { const element = document.getElementById(SunPanelTOCDomIdName) if (element) { element.remove() } })()
const SunPanelTOCDom = document.createElement('div') SunPanelTOCDom.id = SunPanelTOCDomIdName document.body.appendChild(SunPanelTOCDom)
const style = document.createElement('style') const SunPanelTOCDomStyleId = `#${SunPanelTOCDomIdName}` style.textContent = ` ${SunPanelTOCDomStyleId} #toc-mobile-btn { top: 20px !important; left: 20px !important; position: fixed; width: 46px; height: 46px; background-color: #2a2a2a6b; color: white; border-radius: 0.5rem; display: flex; justify-content: center; align-items: center; cursor: pointer; }
${SunPanelTOCDomStyleId} .hidden { display: none !important; }
${SunPanelTOCDomStyleId} #toc-sidebar { width: 40px; padding: 10px; position: fixed; top: 0; left: 0; height: 100%; overflow: hidden; display: flex; flex-direction: column; justify-content: center; transition: width 0.3s ease, background-color 0.3s ease; border-top-right-radius: 20px; border-bottom-right-radius: 20px; background-color: none; }
${SunPanelTOCDomStyleId} .toc-mobile-btn-svg-container{ width:21px; height:21px; }
${SunPanelTOCDomStyleId} .toc-sidebar-expansion { width: 200px !important; display: flex; background-color: rgb(42 42 42 / 90%); box-shadow: 2px 0 5px rgba(0, 0, 0, 0.2); }
${SunPanelTOCDomStyleId} #toc-sidebar .toc-sidebar-box { width: 500px; }
${SunPanelTOCDomStyleId} .title-bar-box { display: flex; align-items: center; position: relative; cursor: pointer; }
${SunPanelTOCDomStyleId} .title-bar-slip { width: 20px; height: 6px; background-color: white; border-radius: 4px; margin: 15px 0; transition: height 0.3s ease, width 0.3s ease; box-shadow: 2px 0 5px rgba(0, 0, 0, 0.5); }
${SunPanelTOCDomStyleId} .title-bar-title { opacity: 0; white-space: nowrap; transition: opacity 0.3s ease, transform 0.3s ease, margin-left 0.3s ease; font-size: 14px; color: white; }
${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-title { opacity: 1; margin-left: 10px; }
${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-slip { box-shadow: none; }
${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-box:hover .title-bar-slip { width: 40px; }
${SunPanelTOCDomStyleId} .toc-sidebar-expansion .title-bar-box:hover .title-bar-title { font-size: 20px; }
` SunPanelTOCDom.appendChild(style)
const tocMobileBtn = document.createElement('div') tocMobileBtn.id = 'toc-mobile-btn' tocMobileBtn.classList.add('backdrop-blur-[2px]') SunPanelTOCDom.appendChild(tocMobileBtn)
const tocMobileBtnSvgcContainer = document.createElement('div') tocMobileBtnSvgcContainer.innerHTML = svgTocMobileBtn tocMobileBtnSvgcContainer.classList.add('toc-mobile-btn-svg-container') tocMobileBtn.appendChild(tocMobileBtnSvgcContainer)
const sidebar = document.createElement('div') sidebar.id = 'toc-sidebar'
const sidebarBox = document.createElement('div') sidebarBox.className = 'toc-sidebar-box'
const items = document.querySelectorAll('[class*="item-group-index-"]')
items.forEach((item) => { item.classList.forEach((className) => { if (className.startsWith('item-group-index-')) { const titleBarBox = document.createElement('div') titleBarBox.className = 'title-bar-box' titleBarBox.dataset.groupClassName = className
const titleBarSlip = document.createElement('div') titleBarSlip.className = 'title-bar-slip'
const titleBarTitle = document.createElement('div') titleBarTitle.className = 'title-bar-title'
const titleElement = item.querySelector('.group-title') const titleText = titleElement ? titleElement.textContent : item.id titleBarTitle.textContent = titleText
titleBarBox.appendChild(titleBarSlip) titleBarBox.appendChild(titleBarTitle)
sidebarBox.appendChild(titleBarBox) } }) })
sidebar.appendChild(sidebarBox)
SunPanelTOCDom.appendChild(sidebar)
function mobileHideSidebar() { sidebar.classList.remove('toc-sidebar-expansion') sidebar.classList.add('hidden') }
function hideSidebar() { sidebar.classList.remove('toc-sidebar-expansion') }
function showSidebar() { sidebar.classList.add('toc-sidebar-expansion') sidebar.classList.remove('hidden') }
function debounce(func, wait) { let timeout return function (...args) { clearTimeout(timeout) timeout = setTimeout(() => { func.apply(this, args) }, wait) } }
function handleResize() { if (isMobile()) { tocMobileBtn.classList.remove('hidden') sidebar.classList.add('hidden') } else { tocMobileBtn.classList.add('hidden') sidebar.classList.remove('hidden') } }
const debouncedHandleResize = debounce(handleResize, 200)
window.addEventListener('resize', debouncedHandleResize)
handleResize()
tocMobileBtn.addEventListener('click', () => { if (sidebar.classList.contains('toc-sidebar-expansion')) { mobileHideSidebar() } else { showSidebar() } })
sidebar.addEventListener('mouseleave', () => { if (isMobile()) { mobileHideSidebar() } else { hideSidebar() } })
sidebar.addEventListener('mouseenter', () => { showSidebar() })
document.querySelectorAll('.title-bar-box').forEach((box) => { box.addEventListener('click', function (event) { if (this.dataset.groupClassName) { const groupClassName = this.dataset.groupClassName const targetElement = document.querySelector(`.${groupClassName}`) if (targetElement) { const targetTop = targetElement.offsetTop const scrollContainerElement = document.querySelector(scrollContainerElementClassName) if (scrollContainerElement) { scrollContainerElement.scrollTo({ top: targetTop - scrollOffset, behavior: 'smooth', }) } } } }) }) }
const items = document.querySelectorAll('[class*="item-group-index-"]') if (items.length > 0) { createDom() return }
const interval = setInterval(() => { const items = document.querySelectorAll('[class*="item-group-index-"]') if (items.length > 0) { createDom() clearInterval(interval) } }, 1000) })() </script>
|