@yinuo-ngm/server 1.0.15 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/lib/plugins/error-handler.plugin.js +5 -0
  2. package/lib/routes/api-client/hub-token.routes.js +22 -0
  3. package/lib/routes/index.js +2 -0
  4. package/lib/routes/node-version.routes.d.ts +2 -0
  5. package/lib/routes/node-version.routes.js +56 -0
  6. package/lib/routes/project.routes.js +5 -0
  7. package/lib/routes/sprite-browse.routes.js +19 -5
  8. package/lib/routes/sprite.routes.js +17 -11
  9. package/lib/routes/static-files.routes.js +16 -0
  10. package/lib/routes/task.routes.js +7 -0
  11. package/package.json +3 -3
  12. package/www/3rdpartylicenses.txt +14 -66
  13. package/www/browser/chunk-2BZRE4G7.js +1 -0
  14. package/www/browser/chunk-2X3MRS27.js +1 -0
  15. package/www/browser/chunk-4KETC6EB.js +1 -0
  16. package/www/browser/chunk-5T5KA5PG.js +1 -0
  17. package/www/browser/chunk-6CGHNKJI.js +1 -0
  18. package/www/browser/chunk-76TVIB33.js +63 -0
  19. package/www/browser/chunk-7J24TP36.js +1 -0
  20. package/www/browser/chunk-ACAZUX6C.js +1 -0
  21. package/www/browser/chunk-B6MBYCXI.js +1 -0
  22. package/www/browser/chunk-BYEU6KGP.js +2 -0
  23. package/www/browser/chunk-CZ5AZ3VW.js +1 -0
  24. package/www/browser/chunk-DW7F5PEA.js +1 -0
  25. package/www/browser/chunk-DXF7BVK5.js +1 -0
  26. package/www/browser/chunk-EEVPZGEY.js +1 -0
  27. package/www/browser/chunk-G2W3B7TJ.js +1 -0
  28. package/www/browser/chunk-GDWS2L66.js +1 -0
  29. package/www/browser/chunk-HFZLJHYR.js +1 -0
  30. package/www/browser/chunk-HGONFYP6.js +1 -0
  31. package/www/browser/chunk-HHBPULJW.js +2 -0
  32. package/www/browser/chunk-HRXCR3IN.js +1 -0
  33. package/www/browser/chunk-INL2PELS.js +1 -0
  34. package/www/browser/chunk-JHMEKUZ5.js +1 -0
  35. package/www/browser/chunk-JU3TEDBV.js +37 -0
  36. package/www/browser/chunk-JZULA5JV.js +1 -0
  37. package/www/browser/chunk-KVFR7GFV.js +20 -0
  38. package/www/browser/chunk-L5D75AMV.js +1 -0
  39. package/www/browser/chunk-L7TMCSHV.js +1 -0
  40. package/www/browser/chunk-LQ5OXSW7.js +3 -0
  41. package/www/browser/chunk-MVQTKINJ.js +1 -0
  42. package/www/browser/chunk-N4LRZJBP.js +1 -0
  43. package/www/browser/chunk-O2EYEF7J.js +1 -0
  44. package/www/browser/chunk-OMDHJIUB.js +1 -0
  45. package/www/browser/chunk-OWUAAOHW.js +2 -0
  46. package/www/browser/chunk-PIK5YPIB.js +1 -0
  47. package/www/browser/chunk-RHLQRQDK.js +9 -0
  48. package/www/browser/chunk-RW2JPJV7.js +1 -0
  49. package/www/browser/chunk-S3SJ4VVM.js +11 -0
  50. package/www/browser/chunk-SIVPP74B.js +0 -0
  51. package/www/browser/chunk-SPRWNZHF.js +15 -0
  52. package/www/browser/chunk-SQQNR223.js +1 -0
  53. package/www/browser/chunk-SVQWPHF5.js +1 -0
  54. package/www/browser/chunk-SYCNSLAW.js +4 -0
  55. package/www/browser/chunk-T3KK7ZXB.js +2 -0
  56. package/www/browser/chunk-TMX5TTV3.js +1 -0
  57. package/www/browser/chunk-UFY3FLDK.js +1 -0
  58. package/www/browser/chunk-UJOHBN2Y.js +1 -0
  59. package/www/browser/chunk-UQGCUFNM.js +1 -0
  60. package/www/browser/chunk-UXXWRMM6.js +1 -0
  61. package/www/browser/chunk-UZRJGJTD.js +2 -0
  62. package/www/browser/chunk-WD4IAQR3.js +1 -0
  63. package/www/browser/chunk-WF2QTF5L.js +1 -0
  64. package/www/browser/chunk-WI67LAOV.js +4 -0
  65. package/www/browser/chunk-WNCM6QKB.js +1 -0
  66. package/www/browser/chunk-XJ5KZQNN.js +1 -0
  67. package/www/browser/chunk-YETDFSQE.js +1 -0
  68. package/www/browser/chunk-YMTC5GZK.js +1 -0
  69. package/www/browser/chunk-YVZHJ76K.js +1 -0
  70. package/www/browser/chunk-ZNTJRLVH.js +1 -0
  71. package/www/browser/dark.css +1 -30433
  72. package/www/browser/default.css +1 -30149
  73. package/www/browser/index.html +3 -3
  74. package/www/browser/main-6LN5C22E.js +34 -0
  75. package/www/browser/scripts-U25HCVEI.js +6 -0
  76. package/www/browser/styles-ROAHD7MY.css +1 -0
  77. package/www/browser/add-widget-drawer.component.css.map +0 -7
  78. package/www/browser/advanced-editor.component.css.map +0 -7
  79. package/www/browser/api-client.component.css.map +0 -7
  80. package/www/browser/api-history-drawer.component.css.map +0 -7
  81. package/www/browser/app.css.map +0 -7
  82. package/www/browser/attachment-viewer.component.css.map +0 -7
  83. package/www/browser/auth-editor.component.css.map +0 -7
  84. package/www/browser/body-editor.component.css.map +0 -7
  85. package/www/browser/chunk-2YSUXBGC.js +0 -627
  86. package/www/browser/chunk-2YSUXBGC.js.map +0 -1
  87. package/www/browser/chunk-3G2GEOPV.js +0 -456
  88. package/www/browser/chunk-3G2GEOPV.js.map +0 -1
  89. package/www/browser/chunk-3L72DF3R.js +0 -1683
  90. package/www/browser/chunk-3L72DF3R.js.map +0 -1
  91. package/www/browser/chunk-3MCIETCQ.js +0 -8279
  92. package/www/browser/chunk-3MCIETCQ.js.map +0 -1
  93. package/www/browser/chunk-665WC4RU.js +0 -5383
  94. package/www/browser/chunk-665WC4RU.js.map +0 -1
  95. package/www/browser/chunk-6QMSEZPT.js +0 -10044
  96. package/www/browser/chunk-6QMSEZPT.js.map +0 -1
  97. package/www/browser/chunk-6TBQERYX.js +0 -280
  98. package/www/browser/chunk-6TBQERYX.js.map +0 -1
  99. package/www/browser/chunk-6UW4MRJZ.js +0 -43
  100. package/www/browser/chunk-6UW4MRJZ.js.map +0 -7
  101. package/www/browser/chunk-6XUMIL3W.js +0 -5495
  102. package/www/browser/chunk-6XUMIL3W.js.map +0 -1
  103. package/www/browser/chunk-724YOJI3.js +0 -903
  104. package/www/browser/chunk-724YOJI3.js.map +0 -1
  105. package/www/browser/chunk-766T7YES.js +0 -437
  106. package/www/browser/chunk-766T7YES.js.map +0 -1
  107. package/www/browser/chunk-7FC7DN65.js +0 -728
  108. package/www/browser/chunk-7FC7DN65.js.map +0 -1
  109. package/www/browser/chunk-APGQRYWX.js +0 -156
  110. package/www/browser/chunk-APGQRYWX.js.map +0 -7
  111. package/www/browser/chunk-AQHSGTHS.js +0 -1960
  112. package/www/browser/chunk-AQHSGTHS.js.map +0 -1
  113. package/www/browser/chunk-CTOEAJQK.js +0 -40
  114. package/www/browser/chunk-CTOEAJQK.js.map +0 -7
  115. package/www/browser/chunk-DEGQJKKZ.js +0 -409
  116. package/www/browser/chunk-DEGQJKKZ.js.map +0 -1
  117. package/www/browser/chunk-DIJX2663.js +0 -1166
  118. package/www/browser/chunk-DIJX2663.js.map +0 -1
  119. package/www/browser/chunk-E4XD4GLT.js +0 -427
  120. package/www/browser/chunk-E4XD4GLT.js.map +0 -1
  121. package/www/browser/chunk-EQOY6A3M.js +0 -212
  122. package/www/browser/chunk-EQOY6A3M.js.map +0 -1
  123. package/www/browser/chunk-ETTBRXVA.js +0 -2643
  124. package/www/browser/chunk-ETTBRXVA.js.map +0 -1
  125. package/www/browser/chunk-FLSUSPBD.js +0 -5569
  126. package/www/browser/chunk-FLSUSPBD.js.map +0 -1
  127. package/www/browser/chunk-FZNHBEAZ.js +0 -102
  128. package/www/browser/chunk-FZNHBEAZ.js.map +0 -1
  129. package/www/browser/chunk-GJC7CZIJ.js +0 -361
  130. package/www/browser/chunk-GJC7CZIJ.js.map +0 -7
  131. package/www/browser/chunk-GMCNMOTD.js +0 -844
  132. package/www/browser/chunk-GMCNMOTD.js.map +0 -7
  133. package/www/browser/chunk-H43HGCRW.js +0 -198
  134. package/www/browser/chunk-H43HGCRW.js.map +0 -7
  135. package/www/browser/chunk-J447GBHM.js +0 -160
  136. package/www/browser/chunk-J447GBHM.js.map +0 -1
  137. package/www/browser/chunk-JN3VOWP7.js +0 -384
  138. package/www/browser/chunk-JN3VOWP7.js.map +0 -1
  139. package/www/browser/chunk-KSHAGY2M.js +0 -399
  140. package/www/browser/chunk-KSHAGY2M.js.map +0 -7
  141. package/www/browser/chunk-KV72AKOD.js +0 -59
  142. package/www/browser/chunk-KV72AKOD.js.map +0 -7
  143. package/www/browser/chunk-LAVMY2SJ.js +0 -26
  144. package/www/browser/chunk-LAVMY2SJ.js.map +0 -7
  145. package/www/browser/chunk-LLRVGHG5.js +0 -73
  146. package/www/browser/chunk-LLRVGHG5.js.map +0 -7
  147. package/www/browser/chunk-LPME4AJ3.js +0 -196
  148. package/www/browser/chunk-LPME4AJ3.js.map +0 -1
  149. package/www/browser/chunk-LVPSURVU.js +0 -4615
  150. package/www/browser/chunk-LVPSURVU.js.map +0 -1
  151. package/www/browser/chunk-MMPO7QLO.js +0 -4146
  152. package/www/browser/chunk-MMPO7QLO.js.map +0 -7
  153. package/www/browser/chunk-O7LB6VFM.js +0 -1953
  154. package/www/browser/chunk-O7LB6VFM.js.map +0 -1
  155. package/www/browser/chunk-P5CWA4FO.js +0 -685
  156. package/www/browser/chunk-P5CWA4FO.js.map +0 -1
  157. package/www/browser/chunk-RBWIWNTL.js +0 -203
  158. package/www/browser/chunk-RBWIWNTL.js.map +0 -7
  159. package/www/browser/chunk-RUXJCM3V.js +0 -26199
  160. package/www/browser/chunk-RUXJCM3V.js.map +0 -1
  161. package/www/browser/chunk-S77VVYUZ.js +0 -168
  162. package/www/browser/chunk-S77VVYUZ.js.map +0 -1
  163. package/www/browser/chunk-SCXPYFOB.js +0 -4316
  164. package/www/browser/chunk-SCXPYFOB.js.map +0 -1
  165. package/www/browser/chunk-SS73S33S.js +0 -471
  166. package/www/browser/chunk-SS73S33S.js.map +0 -7
  167. package/www/browser/chunk-T6UAOWNP.js +0 -600
  168. package/www/browser/chunk-T6UAOWNP.js.map +0 -1
  169. package/www/browser/chunk-TEW4MY4J.js +0 -148
  170. package/www/browser/chunk-TEW4MY4J.js.map +0 -7
  171. package/www/browser/chunk-TVORF3SF.js +0 -82
  172. package/www/browser/chunk-TVORF3SF.js.map +0 -7
  173. package/www/browser/chunk-UAN42EUZ.js +0 -2147
  174. package/www/browser/chunk-UAN42EUZ.js.map +0 -1
  175. package/www/browser/chunk-ULOHDK7Y.js +0 -1
  176. package/www/browser/chunk-ULOHDK7Y.js.map +0 -7
  177. package/www/browser/chunk-UYJ3FVRV.js +0 -3978
  178. package/www/browser/chunk-UYJ3FVRV.js.map +0 -1
  179. package/www/browser/chunk-VXHZMSDM.js +0 -5509
  180. package/www/browser/chunk-VXHZMSDM.js.map +0 -1
  181. package/www/browser/chunk-VYNQPZQO.js +0 -281
  182. package/www/browser/chunk-VYNQPZQO.js.map +0 -1
  183. package/www/browser/chunk-W3J23PZI.js +0 -9213
  184. package/www/browser/chunk-W3J23PZI.js.map +0 -1
  185. package/www/browser/chunk-W5HGHCQT.js +0 -1642
  186. package/www/browser/chunk-W5HGHCQT.js.map +0 -7
  187. package/www/browser/chunk-XF5H7E4L.js +0 -4723
  188. package/www/browser/chunk-XF5H7E4L.js.map +0 -1
  189. package/www/browser/chunk-XNFNEBMY.js +0 -398
  190. package/www/browser/chunk-XNFNEBMY.js.map +0 -7
  191. package/www/browser/chunk-XQY5SPGI.js +0 -30811
  192. package/www/browser/chunk-XQY5SPGI.js.map +0 -1
  193. package/www/browser/chunk-Y7JBZHNK.js +0 -514
  194. package/www/browser/chunk-Y7JBZHNK.js.map +0 -1
  195. package/www/browser/chunk-YWHRP4X3.js +0 -406
  196. package/www/browser/chunk-YWHRP4X3.js.map +0 -1
  197. package/www/browser/chunk-Z7FJIV62.js +0 -95
  198. package/www/browser/chunk-Z7FJIV62.js.map +0 -7
  199. package/www/browser/chunk-ZBZHXS46.js +0 -1674
  200. package/www/browser/chunk-ZBZHXS46.js.map +0 -1
  201. package/www/browser/chunk-ZZA5NVAI.js +0 -735
  202. package/www/browser/chunk-ZZA5NVAI.js.map +0 -1
  203. package/www/browser/collection-modal.component.css.map +0 -7
  204. package/www/browser/collection-tree-item.component.css.map +0 -7
  205. package/www/browser/collection-tree.component.css.map +0 -7
  206. package/www/browser/config-change-bar-component.css.map +0 -7
  207. package/www/browser/config-item-component.css.map +0 -7
  208. package/www/browser/config-nav-component.css.map +0 -7
  209. package/www/browser/config-section-component.css.map +0 -7
  210. package/www/browser/create-summary-aside.component.css.map +0 -7
  211. package/www/browser/curl-actions.component.css.map +0 -7
  212. package/www/browser/dark.css.map +0 -7
  213. package/www/browser/dashboard-canvas.component.css.map +0 -7
  214. package/www/browser/dashboard.component.css.map +0 -7
  215. package/www/browser/default.css.map +0 -7
  216. package/www/browser/detail-item-card.component.css.map +0 -7
  217. package/www/browser/ellipsis-text.component.css.map +0 -7
  218. package/www/browser/env-modal.component.css.map +0 -7
  219. package/www/browser/env-picker.component.css.map +0 -7
  220. package/www/browser/feedback.component.css.map +0 -7
  221. package/www/browser/fs-explorer.component.css.map +0 -7
  222. package/www/browser/git-import-modal.component.css.map +0 -7
  223. package/www/browser/hub-v2-personal-token-modal.component.css.map +0 -7
  224. package/www/browser/issue-action-area.component.css.map +0 -7
  225. package/www/browser/issue-add-participants-dialog.component.css.map +0 -7
  226. package/www/browser/issue-assign-dialog.component.css.map +0 -7
  227. package/www/browser/issue-attachment-area.component.css.map +0 -7
  228. package/www/browser/issue-base-info-area.component.css.map +0 -7
  229. package/www/browser/issue-branches.component.css.map +0 -7
  230. package/www/browser/issue-close-dialog.component.css.map +0 -7
  231. package/www/browser/issue-collaborators-panel.component.css.map +0 -7
  232. package/www/browser/issue-comment-editor.component.css.map +0 -7
  233. package/www/browser/issue-create-branch-dialog.component.css.map +0 -7
  234. package/www/browser/issue-description-area.component.css.map +0 -7
  235. package/www/browser/issue-detail.component.css.map +0 -7
  236. package/www/browser/issue-resolve-dialog.component.css.map +0 -7
  237. package/www/browser/issue-start-own-branch-dialog.component.css.map +0 -7
  238. package/www/browser/issues-list-table.component.css.map +0 -7
  239. package/www/browser/issues.component.css.map +0 -7
  240. package/www/browser/kill-port-widget.component.css.map +0 -7
  241. package/www/browser/kv-table.component.css.map +0 -7
  242. package/www/browser/layout-footer.component.css.map +0 -7
  243. package/www/browser/layout-header.component.css.map +0 -7
  244. package/www/browser/layout-menu.component.css.map +0 -7
  245. package/www/browser/layout-project-nav.component.css.map +0 -7
  246. package/www/browser/layout-sidebar.component.css.map +0 -7
  247. package/www/browser/layout.component.css.map +0 -7
  248. package/www/browser/less-viewport-component.css.map +0 -7
  249. package/www/browser/main.js +0 -1872
  250. package/www/browser/main.js.map +0 -1
  251. package/www/browser/markdown-viewer.component.css.map +0 -7
  252. package/www/browser/news-feed-widget.component.css.map +0 -7
  253. package/www/browser/ng-devtool.component.css.map +0 -7
  254. package/www/browser/nginx-config-editor.component.css.map +0 -7
  255. package/www/browser/nginx-log-viewer.component.css.map +0 -7
  256. package/www/browser/nginx-secondary-logs-tab.component.css.map +0 -7
  257. package/www/browser/nginx-secondary-perf-tab.component.css.map +0 -7
  258. package/www/browser/nginx-secondary-settings-tab.component.css.map +0 -7
  259. package/www/browser/nginx-secondary-ssl-tab.component.css.map +0 -7
  260. package/www/browser/nginx-secondary-test-tab.component.css.map +0 -7
  261. package/www/browser/nginx-secondary-traffic-tab.component.css.map +0 -7
  262. package/www/browser/nginx-secondary-upstream-tab.component.css.map +0 -7
  263. package/www/browser/nginx-section-card.component.css.map +0 -7
  264. package/www/browser/nginx-server-drawer.component.css.map +0 -7
  265. package/www/browser/nginx-server-list.component.css.map +0 -7
  266. package/www/browser/nginx-stat-card.component.css.map +0 -7
  267. package/www/browser/nginx.component.css.map +0 -7
  268. package/www/browser/page-layout.component.css.map +0 -7
  269. package/www/browser/pick-workspace-modal.component.css.map +0 -7
  270. package/www/browser/polyfills.js +0 -26584
  271. package/www/browser/polyfills.js.map +0 -7
  272. package/www/browser/project-conf.component.css.map +0 -7
  273. package/www/browser/project-create-modal.component.css.map +0 -7
  274. package/www/browser/project-create.component.css.map +0 -7
  275. package/www/browser/project-deps.component.css.map +0 -7
  276. package/www/browser/project-edit-modal.component.css.map +0 -7
  277. package/www/browser/project-import.component.css.map +0 -7
  278. package/www/browser/project-item-popover.component.css.map +0 -7
  279. package/www/browser/project-item.component.css.map +0 -7
  280. package/www/browser/project-list.component.css.map +0 -7
  281. package/www/browser/projects.component.css.map +0 -7
  282. package/www/browser/quick-task-widget.component.css.map +0 -7
  283. package/www/browser/rd-action-area.component.css.map +0 -7
  284. package/www/browser/rd-advance-stage-dialog.component.css.map +0 -7
  285. package/www/browser/rd-block-dialog.component.css.map +0 -7
  286. package/www/browser/rd-create-dialog.component.css.map +0 -7
  287. package/www/browser/rd-detail.component.css.map +0 -7
  288. package/www/browser/rd-item-card.component.css.map +0 -7
  289. package/www/browser/rd-list-board.component.css.map +0 -7
  290. package/www/browser/rd-list-table.component.css.map +0 -7
  291. package/www/browser/rd.component.css.map +0 -7
  292. package/www/browser/request-collections.component.css.map +0 -7
  293. package/www/browser/request-editor.component.css.map +0 -7
  294. package/www/browser/request-list-item.component.css.map +0 -7
  295. package/www/browser/request-list.component.css.map +0 -7
  296. package/www/browser/request-tabs-bar.component.css.map +0 -7
  297. package/www/browser/request-tabs.component.css.map +0 -7
  298. package/www/browser/request-urlbar.component.css.map +0 -7
  299. package/www/browser/response-viewer.component.css.map +0 -7
  300. package/www/browser/scripts.js +0 -245
  301. package/www/browser/scripts.js.map +0 -7
  302. package/www/browser/setting.component.css.map +0 -7
  303. package/www/browser/sprite-conf-modal.component.css.map +0 -7
  304. package/www/browser/sprite-icons-panel-component.css.map +0 -7
  305. package/www/browser/sprite-images-panel-component.css.map +0 -7
  306. package/www/browser/sprite-result-tabs.component.css.map +0 -7
  307. package/www/browser/sprite-viewport-component.css.map +0 -7
  308. package/www/browser/sprite.component.css.map +0 -7
  309. package/www/browser/step-advance.component.css.map +0 -7
  310. package/www/browser/step-basic.component.css.map +0 -7
  311. package/www/browser/step-config.component.css.map +0 -7
  312. package/www/browser/step-features.component.css.map +0 -7
  313. package/www/browser/step-preset.component.css.map +0 -7
  314. package/www/browser/step-summary-aside.component.css.map +0 -7
  315. package/www/browser/styles.css +0 -258
  316. package/www/browser/styles.css.map +0 -7
  317. package/www/browser/system-log.component.css.map +0 -7
  318. package/www/browser/task-actions.component.css.map +0 -7
  319. package/www/browser/task-console.component.css.map +0 -7
  320. package/www/browser/task-header.component.css.map +0 -7
  321. package/www/browser/task-list.component.css.map +0 -7
  322. package/www/browser/tasks.component.css.map +0 -7
  323. package/www/browser/terminal-view.component.css.map +0 -7
  324. package/www/browser/welcome-widget.component.css.map +0 -7
  325. package/www/browser/widget-base.component.css.map +0 -7
  326. package/www/browser/widget-host.component.css.map +0 -7
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["angular:jit:template:src\\app\\pages\\nginx\\nginx.component.html", "angular:jit:style:src\\app\\pages\\nginx\\nginx.component.less", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-config-editor\\nginx-config-editor.component.ts;CiAgICAuY29uZmlnLWVkaXRvciB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgIGhlaWdodDogMTAwJTsKICAgICAgYm9yZGVyOiAxcHggc29saWQgI2YwZjBmMDsKICAgICAgYm9yZGVyLXJhZGl1czogOHB4OwogICAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgfQoKICAgIC5lZGl0b3ItdG9vbGJhciB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsKICAgICAgYWxpZ24taXRlbXM6IGZsZXgtc3RhcnQ7CiAgICAgIGdhcDogMTJweDsKICAgICAgcGFkZGluZzogMTJweCAxNnB4OwogICAgICBiYWNrZ3JvdW5kOiAjZmFmYWZhOwogICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2YwZjBmMDsKCiAgICAgIC50b29sYmFyLWxlZnQgewogICAgICAgIG1pbi13aWR0aDogMDsKICAgICAgICBmbGV4OiAxOwogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IHJvdzsKICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICAgIGZsZXgtd3JhcDogd3JhcDsKICAgICAgICBnYXA6IDEwcHg7CiAgICAgIH0KCiAgICAgIC5maWxlLWluZm8gewogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgICBnYXA6IDhweDsKICAgICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1iYXNlLCAxNHB4KTsKICAgICAgICBtaW4td2lkdGg6IDA7CgogICAgICAgIC5maWxlLXBhdGggewogICAgICAgICAgZm9udC1mYW1pbHk6IHZhcigKICAgICAgICAgICAgLS1uZ2lueC1mb250LWZhbWlseS1tb25vLAogICAgICAgICAgICB1aS1tb25vc3BhY2UsCiAgICAgICAgICAgIFNGTW9uby1SZWd1bGFyLAogICAgICAgICAgICBNZW5sbywKICAgICAgICAgICAgTW9uYWNvLAogICAgICAgICAgICBDb25zb2xhcywKICAgICAgICAgICAgJ0xpYmVyYXRpb24gTW9ubycsCiAgICAgICAgICAgIG1vbm9zcGFjZQogICAgICAgICAgKTsKICAgICAgICAgIGNvbG9yOiByZ2JhKDAsIDAsIDAsIDAuNjUpOwogICAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgICAgIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzOwogICAgICAgICAgd2hpdGUtc3BhY2U6IG5vd3JhcDsKICAgICAgICB9CiAgICAgIH0KCiAgICAgIC5maWxlLXNlbGVjdG9yIHsKICAgICAgICB3aWR0aDogbWluKDU2MHB4LCAxMDAlKTsKICAgICAgfQoKICAgICAgLmVkaXRvci1hY3Rpb25zIHsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGdhcDogOHB4OwogICAgICAgIGZsZXgtc2hyaW5rOiAwOwogICAgICB9CiAgICB9CgogICAgLmVkaXRvci1jb250YWluZXIgewogICAgICBmbGV4OiAxOwogICAgICBtaW4taGVpZ2h0OiA1MjBweDsKICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgfQoKICAgIC5jb2RlLWVkaXRvciB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICBtaW4taGVpZ2h0OiA1MjBweDsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkICNkOWQ5ZDk7CiAgICB9CgogICAgLnZhbGlkYXRpb24tcmVzdWx0IHsKICAgICAgcGFkZGluZzogMTJweCAxNnB4OwogICAgICBiYWNrZ3JvdW5kOiAjZjZmZmVkOwogICAgICBib3JkZXItdG9wOiAxcHggc29saWQgI2I3ZWI4ZjsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgZ2FwOiA4cHg7CgogICAgICAmLmVycm9yIHsKICAgICAgICBiYWNrZ3JvdW5kOiAjZmZmMmYwOwogICAgICAgIGJvcmRlci10b3AtY29sb3I6ICNmZmNjYzc7CiAgICAgIH0KCiAgICAgIC5zdWNjZXNzLWljb24gewogICAgICAgIGNvbG9yOiAjNTJjNDFhOwogICAgICB9CgogICAgICAuZXJyb3ItaWNvbiB7CiAgICAgICAgY29sb3I6ICNmZjRkNGY7CiAgICAgIH0KCiAgICAgIC5lcnJvci1saXN0LAogICAgICAud2FybmluZy1saXN0IHsKICAgICAgICBtYXJnaW4tdG9wOiA4cHg7CiAgICAgICAgd2lkdGg6IDEwMCU7CgogICAgICAgIC5lcnJvci1pdGVtIHsKICAgICAgICAgIGNvbG9yOiAjZmY0ZDRmOwogICAgICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICAgICAgcGFkZGluZzogNHB4IDA7CiAgICAgICAgICBmb250LWZhbWlseTogdmFyKAogICAgICAgICAgICAtLW5naW54LWZvbnQtZmFtaWx5LW1vbm8sCiAgICAgICAgICAgIHVpLW1vbm9zcGFjZSwKICAgICAgICAgICAgU0ZNb25vLVJlZ3VsYXIsCiAgICAgICAgICAgIE1lbmxvLAogICAgICAgICAgICBNb25hY28sCiAgICAgICAgICAgIENvbnNvbGFzLAogICAgICAgICAgICAnTGliZXJhdGlvbiBNb25vJywKICAgICAgICAgICAgbW9ub3NwYWNlCiAgICAgICAgICApOwogICAgICAgIH0KCiAgICAgICAgLndhcm5pbmctaXRlbSB7CiAgICAgICAgICBjb2xvcjogI2ZhYWQxNDsKICAgICAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLXNtLCAxMnB4KTsKICAgICAgICAgIHBhZGRpbmc6IDRweCAwOwogICAgICAgICAgZm9udC1mYW1pbHk6IHZhcigKICAgICAgICAgICAgLS1uZ2lueC1mb250LWZhbWlseS1tb25vLAogICAgICAgICAgICB1aS1tb25vc3BhY2UsCiAgICAgICAgICAgIFNGTW9uby1SZWd1bGFyLAogICAgICAgICAgICBNZW5sbywKICAgICAgICAgICAgTW9uYWNvLAogICAgICAgICAgICBDb25zb2xhcywKICAgICAgICAgICAgJ0xpYmVyYXRpb24gTW9ubycsCiAgICAgICAgICAgIG1vbm9zcGFjZQogICAgICAgICAgKTsKICAgICAgICB9CiAgICAgIH0KICAgIH0KCiAgICA6aG9zdCA6Om5nLWRlZXAgbnotY29kZS1lZGl0b3IuY29kZS1lZGl0b3IuYW50LWNvZGUtZWRpdG9yIHsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIG1pbi1oZWlnaHQ6IDUyMHB4ICFpbXBvcnRhbnQ7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQoKICAgIDpob3N0IDo6bmctZGVlcCBuei1jb2RlLWVkaXRvci5jb2RlLWVkaXRvciAubW9uYWNvLWRpZmYtZWRpdG9yIHsKICAgICAgbWluLWhlaWdodDogNTIwcHg7CiAgICB9CiAg", "src/app/pages/nginx/services/nginx.service.ts", "src/app/utils/monaco-languages.ts", "src/app/pages/nginx/components/nginx-config-editor/nginx-config-editor.component.ts", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-logs-tab\\nginx-secondary-logs-tab.component.ts;CiAgICA6aG9zdCB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQoKICAgIC5wYW5lbC1oZWFkZXItcm93IHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBnYXA6IDhweDsKICAgICAgbWFyZ2luLWJvdHRvbTogMTJweDsKCiAgICAgICYuY29tcGFjdCB7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTBweDsKICAgICAgfQogICAgfQoKICAgIC5sb2ctdGFicyB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGdhcDogMDsKICAgIH0KCiAgICAubG9nLXRhYiB7CiAgICAgIGJhY2tncm91bmQ6IHRyYW5zcGFyZW50OwogICAgICBjb2xvcjogdmFyKC0tdGV4dC0zKTsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICBmb250LXdlaWdodDogNTAwOwogICAgICBwYWRkaW5nOiA2cHggMTJweDsKICAgICAgY3Vyc29yOiBwb2ludGVyOwogICAgICB0cmFuc2l0aW9uOiBhbGwgMTIwbXMgZWFzZTsKICAgICAgYm9yZGVyOiBub25lOwogICAgICBib3JkZXItYm90dG9tOiAycHggc29saWQgdHJhbnNwYXJlbnQ7CiAgICAgIG1hcmdpbi1ib3R0b206IC0xcHg7CgogICAgICAmOmhvdmVyIHsKICAgICAgICBjb2xvcjogdmFyKC0tdGV4dC0yKTsKICAgICAgfQoKICAgICAgJi5hY3RpdmUgewogICAgICAgIGNvbG9yOiB2YXIoLS1ibHVlKTsKICAgICAgICBib3JkZXItYm90dG9tLWNvbG9yOiB2YXIoLS1ibHVlKTsKICAgICAgICBmb250LXdlaWdodDogNjAwOwogICAgICB9CiAgICB9CgogICAgLmxvZy1hY3Rpb25zIHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAgZ2FwOiA0cHg7CiAgICB9CgogICAgLmxvZy1hY3Rpb24tYnRuIHsKICAgICAgbnotaWNvbiB7CiAgICAgICAgZm9udC1zaXplOiAxMnB4OwogICAgICB9CiAgICB9CiAg", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-log-viewer\\nginx-log-viewer.component.ts;CiAgICA6aG9zdCB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQoKICAgIC5sb2ctcGFuZWwgewogICAgICBiYWNrZ3JvdW5kOiAjMWIyMzMyOwogICAgICBib3JkZXItcmFkaXVzOiA2cHg7CiAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICB9CgogICAgLmxvZy1oZWFkZXIgewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgICAgIHBhZGRpbmc6IDhweCAxNHB4OwogICAgICBiYWNrZ3JvdW5kOiByZ2JhKDAsIDAsIDAsIDAuMTUpOwogICAgfQoKICAgIC5sb2ctaGVhZGVyLXRpdGxlIHsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICBmb250LXdlaWdodDogNzAwOwogICAgICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlOwogICAgICBsZXR0ZXItc3BhY2luZzogMC41cHg7CiAgICAgIGNvbG9yOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNCk7CiAgICB9CgogICAgLmxvZy1oZWFkZXItc3RhdHVzIHsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICBjb2xvcjogcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpOwogICAgICBmb250LWZhbWlseTogdmFyKC0tbmdpbngtZm9udC1mYW1pbHktbW9ubywgdWktbW9ub3NwYWNlLCBTRk1vbm8tUmVndWxhciwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsICdMaWJlcmF0aW9uIE1vbm8nLCBtb25vc3BhY2UpOwogICAgICBmb250LXdlaWdodDogNjAwOwogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBnYXA6IDVweDsKCiAgICAgICYucmVhbHRpbWUgewogICAgICAgIGNvbG9yOiAjM2RkNjhjOwogICAgICB9CgogICAgICAmLnN0YXR1cy1vayB7CiAgICAgICAgY29sb3I6ICMzZGQ2OGM7CiAgICAgIH0KCiAgICAgICYuc3RhdHVzLXdhcm4gewogICAgICAgIGNvbG9yOiAjZjdiYTFlOwogICAgICB9CgogICAgICAmLnN0YXR1cy1lcnJvciB7CiAgICAgICAgY29sb3I6ICNlZjUzNTA7CiAgICAgIH0KICAgIH0KCiAgICAucmVhbHRpbWUtZG90IHsKICAgICAgd2lkdGg6IDZweDsKICAgICAgaGVpZ2h0OiA2cHg7CiAgICAgIGJvcmRlci1yYWRpdXM6IDUwJTsKICAgICAgYmFja2dyb3VuZDogIzNkZDY4YzsKICAgICAgYW5pbWF0aW9uOiBwdWxzZSAycyBlYXNlLWluLW91dCBpbmZpbml0ZTsKICAgIH0KCiAgICBAa2V5ZnJhbWVzIHB1bHNlIHsKICAgICAgMCUsCiAgICAgIDEwMCUgewogICAgICAgIG9wYWNpdHk6IDE7CiAgICAgIH0KCiAgICAgIDUwJSB7CiAgICAgICAgb3BhY2l0eTogMC40OwogICAgICB9CiAgICB9CgogICAgLmxvZy1ib2R5IHsKICAgICAgcGFkZGluZzogMTBweCAxNHB4OwogICAgICBtYXgtaGVpZ2h0OiAxODBweDsKICAgICAgb3ZlcmZsb3cteTogYXV0bzsKICAgICAgZm9udC1mYW1pbHk6IHZhcigtLW5naW54LWZvbnQtZmFtaWx5LW1vbm8sIHVpLW1vbm9zcGFjZSwgU0ZNb25vLVJlZ3VsYXIsIE1lbmxvLCBNb25hY28sIENvbnNvbGFzLCAnTGliZXJhdGlvbiBNb25vJywgbW9ub3NwYWNlKTsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICBsaW5lLWhlaWdodDogMS44OwoKICAgICAgJjo6LXdlYmtpdC1zY3JvbGxiYXIgewogICAgICAgIHdpZHRoOiA2cHg7CiAgICAgIH0KCiAgICAgICY6Oi13ZWJraXQtc2Nyb2xsYmFyLXRyYWNrIHsKICAgICAgICBiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDsKICAgICAgfQoKICAgICAgJjo6LXdlYmtpdC1zY3JvbGxiYXItdGh1bWIgewogICAgICAgIGJhY2tncm91bmQ6ICMzMzM5NDQ7CiAgICAgICAgYm9yZGVyLXJhZGl1czogM3B4OwogICAgICB9CiAgICB9CgogICAgLmxvZy1lbXB0eSB7CiAgICAgIHBhZGRpbmc6IDE2cHggMDsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICBjb2xvcjogcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjM1KTsKICAgICAgdGV4dC1hbGlnbjogY2VudGVyOwogICAgICBmb250LWZhbWlseTogdmFyKC0tbmdpbngtZm9udC1mYW1pbHktbW9ubywgdWktbW9ub3NwYWNlLCBTRk1vbm8tUmVndWxhciwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsICdMaWJlcmF0aW9uIE1vbm8nLCBtb25vc3BhY2UpOwogICAgfQoKICAgIC5sb2ctbGluZSB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGFsaWduLWl0ZW1zOiBmbGV4LXN0YXJ0OwogICAgICBnYXA6IDEwcHg7CiAgICAgIGN1cnNvcjogZGVmYXVsdDsKICAgIH0KCiAgICAubG9nLXRpbWUgewogICAgICBjb2xvcjogcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpOwogICAgICBmbGV4LXNocmluazogMDsKICAgICAgbWluLXdpZHRoOiA0NnB4OwogICAgfQoKICAgIC5sb2ctbGV2ZWwgewogICAgICBmb250LXdlaWdodDogNzAwOwogICAgICBmbGV4LXNocmluazogMDsKICAgICAgd2lkdGg6IDMycHg7CgogICAgICAmLmluZm8gewogICAgICAgIGNvbG9yOiAjMTY1ZGZmOwogICAgICB9CgogICAgICAmLndhcm4gewogICAgICAgIGNvbG9yOiAjZmY3ZDAwOwogICAgICB9CgogICAgICAmLmVycm9yIHsKICAgICAgICBjb2xvcjogI2VmNTM1MDsKICAgICAgfQoKICAgICAgJi5vayB7CiAgICAgICAgY29sb3I6ICMzZGQ2OGM7CiAgICAgIH0KICAgIH0KCiAgICAubG9nLW1zZyB7CiAgICAgIGNvbG9yOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNTUpOwogICAgICBmbGV4OiAxOwogICAgICB3b3JkLWJyZWFrOiBicmVhay1hbGw7CiAgICAgIHdoaXRlLXNwYWNlOiBwcmUtd3JhcDsKICAgIH0KICA=", "src/app/pages/nginx/components/nginx-log-viewer/nginx-log-viewer.component.ts", "src/app/pages/nginx/components/nginx-tabs/nginx-secondary-logs-tab/nginx-secondary-logs-tab.component.ts", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-perf-tab\\nginx-secondary-perf-tab.component.ts;CiAgICA6aG9zdCB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQoKICAgIC5wYW5lbC1oZWFkZXItcm93IHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBnYXA6IDhweDsKICAgICAgbWFyZ2luLWJvdHRvbTogMTJweDsKICAgIH0KCiAgICAucGFuZWwtdGlwIHsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICBjb2xvcjogdmFyKC0tdGV4dC0zKTsKICAgIH0KCiAgICAuc2V0dGluZy1yb3cgewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIGdhcDogMTBweDsKICAgICAgcGFkZGluZzogMTBweCAwOwogICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgdmFyKC0tYm9yZGVyLWxpZ2h0KTsKCiAgICAgICYubm8tYm9yZGVyIHsKICAgICAgICBib3JkZXItYm90dG9tOiBub25lOwogICAgICB9CiAgICB9CgogICAgLnNldHRpbmctbGFiZWwgewogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIGZvbnQtd2VpZ2h0OiA2MDA7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTEpOwogICAgfQoKICAgIC5zZXR0aW5nLWRlc2MgewogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTMpOwogICAgICBtYXJnaW4tdG9wOiAycHg7CiAgICB9CgogICAgLnNldHRpbmctY3RybCB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIGdhcDogOHB4OwogICAgfQoKICAgIC5zZXR0aW5nLWlucHV0IHsKICAgICAgd2lkdGg6IDExMHB4OwogICAgICBwYWRkaW5nOiA1cHggOHB4OwogICAgICBib3JkZXItcmFkaXVzOiA0cHg7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkIHZhcigtLWJvcmRlcik7CiAgICAgIGJhY2tncm91bmQ6IHZhcigtLWJnLWlucHV0KTsKICAgICAgY29sb3I6IHZhcigtLXRleHQtMik7CiAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLXNtLCAxMnB4KTsKICAgICAgdGV4dC1hbGlnbjogY2VudGVyOwogICAgICBmb250LWZhbWlseTogdmFyKC0tbmdpbngtZm9udC1mYW1pbHktbW9ubywgdWktbW9ub3NwYWNlLCBTRk1vbm8tUmVndWxhciwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsICdMaWJlcmF0aW9uIE1vbm8nLCBtb25vc3BhY2UpOwogICAgICBvdXRsaW5lOiBub25lOwogICAgfQoKICAgIC5zZXR0aW5nLWlucHV0OmZvY3VzLAogICAgLnNldHRpbmctdGV4dGFyZWE6Zm9jdXMgewogICAgICBib3JkZXItY29sb3I6IHZhcigtLWJsdWUpOwogICAgICBib3gtc2hhZG93OiAwIDAgMCAycHggdmFyKC0tYmx1ZS1ib3JkZXIpOwogICAgfQoKICAgIC5zZXR0aW5nLXRleHRhcmVhIHsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIG1hcmdpbi10b3A6IDhweDsKICAgICAgYm9yZGVyOiAxcHggc29saWQgdmFyKC0tYm9yZGVyKTsKICAgICAgYm9yZGVyLXJhZGl1czogNnB4OwogICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1iZy1pbnB1dCk7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTIpOwogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIHBhZGRpbmc6IDhweCAxMHB4OwogICAgICByZXNpemU6IHZlcnRpY2FsOwogICAgICBvdXRsaW5lOiBub25lOwogICAgfQogICAgLm1vbm8gewogICAgICBmb250LWZhbWlseTogdmFyKC0tbmdpbngtZm9udC1mYW1pbHktbW9ubywgdWktbW9ub3NwYWNlLCBTRk1vbm8tUmVndWxhciwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsICdMaWJlcmF0aW9uIE1vbm8nLCBtb25vc3BhY2UpOwogICAgfQogIA==", "src/app/pages/nginx/services/nginx-module.store.ts", "src/app/pages/nginx/components/nginx-tabs/nginx-secondary-perf-tab/nginx-secondary-perf-tab.component.ts", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-settings-tab\\nginx-secondary-settings-tab.component.ts;CiAgICAuc2V0dGluZy1yb3cgewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIGdhcDogMTBweDsKICAgICAgcGFkZGluZzogMTBweCAwOwogICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgdmFyKC0tYm9yZGVyLWxpZ2h0KTsKCiAgICAgICY6bGFzdC1jaGlsZCB7CiAgICAgICAgYm9yZGVyLWJvdHRvbTogbm9uZTsKICAgICAgfQoKICAgICAgJi5kYW5nZXItcm93IHsKICAgICAgICBwYWRkaW5nOiA2cHggMCAwOwogICAgICAgIGJvcmRlci1ib3R0b206IG5vbmU7CiAgICAgIH0KICAgIH0KCiAgICAuc2V0dGluZy1sYWJlbCB7CiAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLXNtLCAxMnB4KTsKICAgICAgZm9udC13ZWlnaHQ6IDYwMDsKICAgICAgY29sb3I6IHZhcigtLXRleHQtMSk7CgogICAgICAmLmRhbmdlciB7CiAgICAgICAgY29sb3I6IHZhcigtLXJlZCk7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogNHB4OwogICAgICB9CiAgICB9CgogICAgLnNldHRpbmctZGVzYyB7CiAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLXNtLCAxMnB4KTsKICAgICAgY29sb3I6IHZhcigtLXRleHQtMyk7CiAgICAgIG1hcmdpbi10b3A6IDJweDsKICAgIH0KCiAgICAuc2V0dGluZy1jdHJsIHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgZ2FwOiA4cHg7CiAgICB9CgogICAgLnJldGVudGlvbi1pbnB1dCB7CiAgICAgIHdpZHRoOiA5MnB4OwogICAgfQoKICAgIC5tb25vIHsKICAgICAgZm9udC1mYW1pbHk6IHZhcigtLW5naW54LWZvbnQtZmFtaWx5LW1vbm8sIHVpLW1vbm9zcGFjZSwgU0ZNb25vLVJlZ3VsYXIsIE1lbmxvLCBNb25hY28sIENvbnNvbGFzLCAnTGliZXJhdGlvbiBNb25vJywgbW9ub3NwYWNlKTsKCiAgICAgICYuc3Ryb25nIHsKICAgICAgICBjb2xvcjogdmFyKC0tdGV4dC0xKTsKICAgICAgICBmb250LXdlaWdodDogNjAwOwogICAgICB9CiAgICB9CgogICAgLmRhbmdlci1ib3ggewogICAgICBtYXJnaW4tdG9wOiA4cHg7CiAgICAgIHBhZGRpbmc6IDEwcHggMTJweDsKICAgICAgYm9yZGVyOiAxcHggc29saWQgcmdiYSgyNDUsIDYzLCA2MywgMC4yKTsKICAgICAgYmFja2dyb3VuZDogdmFyKC0tcmVkLWJnKTsKICAgICAgYm9yZGVyLXJhZGl1czogNnB4OwogICAgfQoKICAgIEBtZWRpYSAobWF4LXdpZHRoOiA3NjhweCkgewogICAgICAuc2V0dGluZy1yb3cgewogICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgYWxpZ24taXRlbXM6IGZsZXgtc3RhcnQ7CiAgICAgIH0KICAgIH0KICA=", "src/app/pages/nginx/components/nginx-tabs/nginx-secondary-settings-tab/nginx-secondary-settings-tab.component.ts", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-ssl-tab\\nginx-secondary-ssl-tab.component.ts;CiAgICA6aG9zdCB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQoKICAgIC5wYW5lbC1oZWFkZXItcm93IHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBnYXA6IDhweDsKICAgICAgbWFyZ2luLWJvdHRvbTogMTJweDsKICAgIH0KCiAgICAucGFuZWwtdGlwIHsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICBjb2xvcjogdmFyKC0tdGV4dC0zKTsKICAgIH0KCiAgICAuaGVhZGVyLWFjdGlvbnMgewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBnYXA6IDhweDsKICAgIH0KCiAgICAuc3NsLWNhcmRzIHsKICAgICAgZGlzcGxheTogZ3JpZDsKICAgICAgZ3JpZC10ZW1wbGF0ZS1jb2x1bW5zOiByZXBlYXQoYXV0by1maWxsLCBtaW5tYXgoMzIwcHgsIDFmcikpOwogICAgICBnYXA6IDEwcHg7CiAgICB9CgogICAgLnNzbC1jYXJkIHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgICAgZ2FwOiAxMHB4OwogICAgICBib3JkZXI6IDFweCBzb2xpZCB2YXIoLS1ib3JkZXItbGlnaHQpOwogICAgICBib3JkZXItcmFkaXVzOiA2cHg7CiAgICAgIHBhZGRpbmc6IDEwcHg7CiAgICAgIGJhY2tncm91bmQ6ICNmZmY7CiAgICB9CgogICAgLmNhcmQtaGVhZCB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsKICAgICAgZ2FwOiAxMHB4OwogICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgdmFyKC0tYm9yZGVyLWxpZ2h0KTsKICAgICAgcGFkZGluZy1ib3R0b206IDhweDsKICAgIH0KCiAgICAuY2FyZC1kb21haW4gewogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1iYXNlLCAxNHB4KTsKICAgICAgY29sb3I6IHZhcigtLXRleHQtMSk7CiAgICAgIGZvbnQtd2VpZ2h0OiA2MDA7CiAgICAgIG1pbi13aWR0aDogMDsKICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7CiAgICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7CiAgICB9CgogICAgLnN0YXR1cy1iYWRnZSB7CiAgICAgIGRpc3BsYXk6IGlubGluZS1mbGV4OwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBoZWlnaHQ6IDI0cHg7CiAgICAgIGJvcmRlci1yYWRpdXM6IDEycHg7CiAgICAgIHBhZGRpbmc6IDAgMTBweDsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICBsaW5lLWhlaWdodDogMjRweDsKICAgICAgYm9yZGVyOiAxcHggc29saWQgdHJhbnNwYXJlbnQ7CiAgICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7CiAgICB9CgogICAgLnN0YXR1cy1iYWRnZS52YWxpZCB7CiAgICAgIGNvbG9yOiB2YXIoLS1ncmVlbik7CiAgICAgIGJvcmRlci1jb2xvcjogcmdiYSgwLCAxODAsIDQyLCAwLjIpOwogICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1ncmVlbi1iZyk7CiAgICB9CgogICAgLnN0YXR1cy1iYWRnZS5leHBpcmluZyB7CiAgICAgIGNvbG9yOiB2YXIoLS1vcmFuZ2UpOwogICAgICBib3JkZXItY29sb3I6IHJnYmEoMjU1LCAxMjUsIDAsIDAuMik7CiAgICAgIGJhY2tncm91bmQ6IHZhcigtLW9yYW5nZS1iZyk7CiAgICB9CgogICAgLnN0YXR1cy1iYWRnZS5leHBpcmVkIHsKICAgICAgY29sb3I6IHZhcigtLXJlZCk7CiAgICAgIGJvcmRlci1jb2xvcjogcmdiYSgyNDUsIDYzLCA2MywgMC4yKTsKICAgICAgYmFja2dyb3VuZDogdmFyKC0tcmVkLWJnKTsKICAgIH0KCiAgICAuc3RhdHVzLWJhZGdlLnBlbmRpbmcgewogICAgICBjb2xvcjogdmFyKC0tdGV4dC0yKTsKICAgICAgYm9yZGVyLWNvbG9yOiB2YXIoLS1ib3JkZXIpOwogICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1iZy1pbnB1dCk7CiAgICB9CgogICAgLm1ldGEtbGlzdCB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgIGdhcDogOHB4OwogICAgICBmbGV4OiAxOwogICAgfQoKICAgIC5tZXRhLWl0ZW0gewogICAgICBkaXNwbGF5OiBncmlkOwogICAgICBncmlkLXRlbXBsYXRlLWNvbHVtbnM6IDgwcHggMWZyOwogICAgICBnYXA6IDhweDsKICAgICAgbWluLXdpZHRoOiAwOwogICAgfQoKICAgIC5tZXRhLWl0ZW0uYmxvY2sgewogICAgICBncmlkLXRlbXBsYXRlLWNvbHVtbnM6IDFmcjsKICAgIH0KCiAgICAubWV0YS1sYWJlbCB7CiAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLXNtLCAxMnB4KTsKICAgICAgY29sb3I6IHZhcigtLXRleHQtMyk7CiAgICAgIGZvbnQtd2VpZ2h0OiA2MDA7CiAgICB9CgogICAgLm1ldGEtdmFsdWUgewogICAgICBjb2xvcjogdmFyKC0tdGV4dC0xKTsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICBtaW4td2lkdGg6IDA7CiAgICAgIHdvcmQtYnJlYWs6IGJyZWFrLWFsbDsKICAgIH0KCiAgICAubW9ubyB7CiAgICAgIGZvbnQtZmFtaWx5OiB2YXIoLS1uZ2lueC1mb250LWZhbWlseS1tb25vLCB1aS1tb25vc3BhY2UsIFNGTW9uby1SZWd1bGFyLCBNZW5sbywgTW9uYWNvLCBDb25zb2xhcywgJ0xpYmVyYXRpb24gTW9ubycsIG1vbm9zcGFjZSk7CiAgICB9CgogICAgLmNhcmQtYWN0aW9ucyB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGp1c3RpZnktY29udGVudDogZmxleC1lbmQ7CiAgICAgIGdhcDogOHB4OwogICAgfQoKICAgIC5kcmF3ZXItYm9keSB7CiAgICAgIHBhZGRpbmc6IDAgMjRweCAxNnB4OwogICAgfQoKICAgIC5mb3JtLWdyaWQgewogICAgICBkaXNwbGF5OiBncmlkOwogICAgICBncmlkLXRlbXBsYXRlLWNvbHVtbnM6IDFmciAxZnI7CiAgICAgIGdhcDogMTJweDsKICAgIH0KCiAgICAuZHJhd2VyLWZvb3RlciB7CiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgYm90dG9tOiAwOwogICAgICBsZWZ0OiAwOwogICAgICByaWdodDogMDsKICAgICAgcGFkZGluZzogMTJweCAyNHB4OwogICAgICBib3JkZXItdG9wOiAxcHggc29saWQgcmdiYSgwLCAwLCAwLCAwLjA2KTsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAganVzdGlmeS1jb250ZW50OiBmbGV4LWVuZDsKICAgICAgZ2FwOiA4cHg7CiAgICAgIGJhY2tncm91bmQ6ICNmZmY7CiAgICB9CgogICAgLmVtcHR5LXJvdyB7CiAgICAgIGdyaWQtY29sdW1uOiAxIC8gLTE7CiAgICAgIGJvcmRlcjogMXB4IGRhc2hlZCB2YXIoLS1ib3JkZXIpOwogICAgICBib3JkZXItcmFkaXVzOiA2cHg7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTMpOwogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIHRleHQtYWxpZ246IGNlbnRlcjsKICAgICAgcGFkZGluZzogMjBweCAxMnB4OwogICAgICBiYWNrZ3JvdW5kOiAjZmZmOwogICAgfQoKICAgIEBtZWRpYSAobWF4LXdpZHRoOiA5OTJweCkgewogICAgICAucGFuZWwtaGVhZGVyLXJvdyB7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgICAgICBhbGlnbi1pdGVtczogZmxleC1zdGFydDsKICAgICAgfQoKICAgICAgLmhlYWRlci1hY3Rpb25zIHsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgfQoKICAgICAgLmZvcm0tZ3JpZCB7CiAgICAgICAgZ3JpZC10ZW1wbGF0ZS1jb2x1bW5zOiAxZnI7CiAgICAgIH0KICAgIH0KICA=", "src/app/pages/nginx/components/nginx-tabs/nginx-secondary-ssl-tab/nginx-secondary-ssl-tab.component.ts", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-test-tab\\nginx-secondary-test-tab.component.ts;CiAgICA6aG9zdCB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQoKICAgIC5wYW5lbC1oZWFkZXItcm93IHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBnYXA6IDhweDsKICAgICAgbWFyZ2luLWJvdHRvbTogMTJweDsKICAgIH0KCiAgICAucGFuZWwtdGlwIHsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtYmFzZSwgMTRweCk7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTIpOwogICAgfQoKICAgIC5ydW4tdGVzdC1idG4gewogICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1ncmVlbik7CiAgICAgIGNvbG9yOiAjZmZmOwogICAgICBib3JkZXItY29sb3I6IHZhcigtLWdyZWVuKTsKICAgICAgZm9udC13ZWlnaHQ6IDYwMDsKICAgICAgbWluLWhlaWdodDogMjhweDsKICAgICAgcGFkZGluZy1pbmxpbmU6IDEwcHg7CgogICAgICAmOmhvdmVyLAogICAgICAmOmZvY3VzIHsKICAgICAgICBiYWNrZ3JvdW5kOiAjMDBhMTIyOwogICAgICAgIGJvcmRlci1jb2xvcjogIzAwYTEyMjsKICAgICAgICBjb2xvcjogI2ZmZjsKICAgICAgfQogICAgfQoKICAgIEBtZWRpYSAobWF4LXdpZHRoOiA5OTJweCkgewogICAgICAucGFuZWwtaGVhZGVyLXJvdyB7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgICAgICBhbGlnbi1pdGVtczogZmxleC1zdGFydDsKICAgICAgfQogICAgfQogIA==", "src/app/pages/nginx/components/nginx-tabs/nginx-secondary-test-tab/nginx-secondary-test-tab.component.ts", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-traffic-tab\\nginx-secondary-traffic-tab.component.ts;CiAgICA6aG9zdCB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQoKICAgIC5wYW5lbC1oZWFkZXItcm93IHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBnYXA6IDhweDsKICAgICAgbWFyZ2luLWJvdHRvbTogMTJweDsKICAgIH0KCiAgICAucGFuZWwtdGlwIHsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtc20sIDEycHgpOwogICAgICBjb2xvcjogdmFyKC0tdGV4dC0zKTsKICAgIH0KCiAgICAuc2V0dGluZy1yb3cgewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIGdhcDogMTBweDsKICAgICAgcGFkZGluZzogMTBweCAwOwogICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgdmFyKC0tYm9yZGVyLWxpZ2h0KTsKCiAgICAgICYubm8tYm9yZGVyIHsKICAgICAgICBib3JkZXItYm90dG9tOiBub25lOwogICAgICB9CiAgICB9CgogICAgLnNldHRpbmctbGFiZWwgewogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIGZvbnQtd2VpZ2h0OiA2MDA7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTEpOwogICAgfQoKICAgIC5zZXR0aW5nLWRlc2MgewogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTMpOwogICAgICBtYXJnaW4tdG9wOiAycHg7CiAgICB9CgogICAgLnNldHRpbmctY3RybCB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIGdhcDogOHB4OwogICAgfQoKICAgIC5zZXR0aW5nLWlucHV0IHsKICAgICAgd2lkdGg6IDExMHB4OwogICAgICBwYWRkaW5nOiA1cHggOHB4OwogICAgICBib3JkZXItcmFkaXVzOiA0cHg7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkIHZhcigtLWJvcmRlcik7CiAgICAgIGJhY2tncm91bmQ6IHZhcigtLWJnLWlucHV0KTsKICAgICAgY29sb3I6IHZhcigtLXRleHQtMik7CiAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLXNtLCAxMnB4KTsKICAgICAgdGV4dC1hbGlnbjogY2VudGVyOwogICAgICBmb250LWZhbWlseTogdmFyKC0tbmdpbngtZm9udC1mYW1pbHktbW9ubywgdWktbW9ub3NwYWNlLCBTRk1vbm8tUmVndWxhciwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsICdMaWJlcmF0aW9uIE1vbm8nLCBtb25vc3BhY2UpOwogICAgICBvdXRsaW5lOiBub25lOwogICAgfQoKICAgIC5zZXR0aW5nLWlucHV0OmZvY3VzLAogICAgLnNldHRpbmctdGV4dGFyZWE6Zm9jdXMgewogICAgICBib3JkZXItY29sb3I6IHZhcigtLWJsdWUpOwogICAgICBib3gtc2hhZG93OiAwIDAgMCAycHggdmFyKC0tYmx1ZS1ib3JkZXIpOwogICAgfQoKICAgIC5zZXR0aW5nLXRleHRhcmVhIHsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIG1hcmdpbi10b3A6IDhweDsKICAgICAgYm9yZGVyOiAxcHggc29saWQgdmFyKC0tYm9yZGVyKTsKICAgICAgYm9yZGVyLXJhZGl1czogNnB4OwogICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1iZy1pbnB1dCk7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTIpOwogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIHBhZGRpbmc6IDhweCAxMHB4OwogICAgICByZXNpemU6IHZlcnRpY2FsOwogICAgICBvdXRsaW5lOiBub25lOwogICAgfQoKICAgIC5tb25vIHsKICAgICAgZm9udC1mYW1pbHk6IHZhcigtLW5naW54LWZvbnQtZmFtaWx5LW1vbm8sIHVpLW1vbm9zcGFjZSwgU0ZNb25vLVJlZ3VsYXIsIE1lbmxvLCBNb25hY28sIENvbnNvbGFzLCAnTGliZXJhdGlvbiBNb25vJywgbW9ub3NwYWNlKTsKICAgIH0KICA=", "src/app/pages/nginx/components/nginx-tabs/nginx-secondary-traffic-tab/nginx-secondary-traffic-tab.component.ts", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-upstream-tab\\nginx-secondary-upstream-tab.component.ts;CiAgICA6aG9zdCB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQoKICAgIC5saXN0LWhlYWRlciB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsKICAgICAgZ2FwOiAxMHB4OwogICAgICBtYXJnaW4tYm90dG9tOiAxMnB4OwogICAgfQoKICAgIC5wYW5lbC10aXAgewogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTMpOwogICAgfQoKICAgIC5oZWFkZXItYWN0aW9ucyB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGdhcDogOHB4OwogICAgICBmbGV4LXNocmluazogMDsKICAgIH0KCiAgICAudXBzdHJlYW0tZ3JpZC1zaGVsbCB7CiAgICAgIGJvcmRlcjogbm9uZTsKICAgICAgYm9yZGVyLXJhZGl1czogMDsKICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgIH0KCiAgICAudXBzdHJlYW0tZ3JpZC1oZWFkLAogICAgLnVwc3RyZWFtLWdyaWQtcm93IHsKICAgICAgZGlzcGxheTogZ3JpZDsKICAgICAgZ3JpZC10ZW1wbGF0ZS1jb2x1bW5zOiBtaW5tYXgoMTgwcHgsIDFmcikgbWlubWF4KDE1MHB4LCAwLjlmcikgbWlubWF4KDEzMHB4LCAwLjdmcikgbWlubWF4KDIyMHB4LCAxLjZmcikgODhweCAxMTZweDsKICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgY29sdW1uLWdhcDogOHB4OwogICAgICBwYWRkaW5nOiAwIDEycHg7CiAgICB9CgogICAgLnVwc3RyZWFtLWdyaWQtaGVhZCB7CiAgICAgIG1pbi1oZWlnaHQ6IDQycHg7CiAgICAgIGJhY2tncm91bmQ6ICNmYWZhZmE7CiAgICAgIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCByZ2JhKDAsIDAsIDAsIDAuMDYpOwoKICAgICAgLmNlbGwgewogICAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLXNtLCAxMnB4KTsKICAgICAgICBjb2xvcjogcmdiYSgwLCAwLCAwLCAwLjQ1KTsKICAgICAgICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlOwogICAgICAgIGxldHRlci1zcGFjaW5nOiAwLjRweDsKICAgICAgICBmb250LXdlaWdodDogNzAwOwogICAgICB9CiAgICB9CgogICAgLnVwc3RyZWFtLWdyaWQtYm9keSB7CiAgICAgIGJhY2tncm91bmQ6ICNmZmY7CiAgICB9CgogICAgLnVwc3RyZWFtLWdyaWQtcm93IHsKICAgICAgbWluLWhlaWdodDogNThweDsKICAgICAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkIHJnYmEoMCwgMCwgMCwgMC4wNSk7CiAgICAgIHRyYW5zaXRpb246IGJhY2tncm91bmQgMTIwbXMgZWFzZTsKCiAgICAgICY6bGFzdC1jaGlsZCB7CiAgICAgICAgYm9yZGVyLWJvdHRvbTogbm9uZTsKICAgICAgfQoKICAgICAgJjpob3ZlciB7CiAgICAgICAgYmFja2dyb3VuZDogcmdiYSgwLCAwLCAwLCAwLjAyKTsKICAgICAgfQogICAgfQoKICAgIC5yZWFkb25seS1yb3cgewogICAgICBiYWNrZ3JvdW5kOiByZ2JhKDAsIDAsIDAsIDAuMDE1KTsKICAgIH0KCiAgICAuY2VsbCB7CiAgICAgIG1pbi13aWR0aDogMDsKICAgIH0KICAgIC5yb3ctYWN0aW9uc3sKICAgICAgZGlzcGxheTogZmxleDsKICAgICAgZ2FwOiA0cHg7CiAgICAgIGp1c3RpZnktY29udGVudDogZmxleC1lbmQ7CiAgICB9CiAgICAudXBzdHJlYW0tbmFtZSB7CiAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtYmFzZSwgMTRweCk7CiAgICAgIGZvbnQtd2VpZ2h0OiA2MDA7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTEpOwogICAgICB3b3JkLWJyZWFrOiBicmVhay1hbGw7CiAgICB9CgogICAgLnN0cmF0ZWd5LXBpbGwgewogICAgICBkaXNwbGF5OiBpbmxpbmUtZmxleDsKICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgaGVpZ2h0OiAyMnB4OwogICAgICBib3JkZXItcmFkaXVzOiAxMXB4OwogICAgICBwYWRkaW5nOiAwIDEwcHg7CiAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLXNtLCAxMnB4KTsKICAgICAgbGluZS1oZWlnaHQ6IDIycHg7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTIpOwogICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1iZy1pbnB1dCk7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkIHZhcigtLWJvcmRlci1saWdodCk7CiAgICB9CgogICAgLnNvdXJjZS1iYWRnZSB7CiAgICAgIGRpc3BsYXk6IGlubGluZS1mbGV4OwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBtYXgtd2lkdGg6IDEwMCU7CiAgICAgIGhlaWdodDogMjJweDsKICAgICAgcGFkZGluZzogMCA4cHg7CiAgICAgIGJvcmRlci1yYWRpdXM6IDRweDsKICAgICAgYmFja2dyb3VuZDogcmdiYSgwLCAwLCAwLCAwLjA0KTsKICAgICAgY29sb3I6IHZhcigtLXRleHQtMyk7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkIHJnYmEoMCwgMCwgMCwgMC4wOCk7CiAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLXNtLCAxMnB4KTsKICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7CiAgICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7CiAgICB9CgogICAgLnNvdXJjZS1iYWRnZS5tYW5hZ2VkIHsKICAgICAgYmFja2dyb3VuZDogcmdiYSgyMiwgOTMsIDI1NSwgMC4wOCk7CiAgICAgIGNvbG9yOiAjMTY1ZGZmOwogICAgICBib3JkZXItY29sb3I6IHJnYmEoMjIsIDkzLCAyNTUsIDAuMik7CiAgICB9CgogICAgLm5vZGVzLXdyYXAgewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBmbGV4LXdyYXA6IHdyYXA7CiAgICAgIGdhcDogNnB4OwogICAgICBwYWRkaW5nOiA4cHggMDsKICAgIH0KCiAgICAubm9kZS1jaGlwIHsKICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7CiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIGhlaWdodDogMjJweDsKICAgICAgcGFkZGluZzogMCA4cHg7CiAgICAgIGJvcmRlci1yYWRpdXM6IDRweDsKICAgICAgYm9yZGVyOiAxcHggc29saWQgcmdiYSgyMiwgOTMsIDI1NSwgMC4yKTsKICAgICAgYmFja2dyb3VuZDogcmdiYSgyMiwgOTMsIDI1NSwgMC4wOCk7CiAgICAgIGNvbG9yOiAjMTY1ZGZmOwogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIG1heC13aWR0aDogMTAwJTsKICAgICAgd29yZC1icmVhazogYnJlYWstYWxsOwogICAgfQoKICAgIC5ub2RlLWNvdW50IHsKICAgICAgZm9udC1zaXplOiB2YXIoLS1uZ2lueC1mb250LXNpemUtYmFzZSwgMTRweCk7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTIpOwogICAgICBmb250LXdlaWdodDogNjAwOwogICAgfQoKICAgIC5hY3Rpb24tY29sIHsKICAgICAganVzdGlmeS1zZWxmOiBlbmQ7CiAgICB9CgogICAgLmVtcHR5LXN0YXRlIHsKICAgICAgdGV4dC1hbGlnbjogY2VudGVyOwogICAgICBwYWRkaW5nOiA0OHB4IDA7CgogICAgICAuZW1wdHktaWNvbiB7CiAgICAgICAgZm9udC1zaXplOiA0OHB4OwogICAgICAgIGNvbG9yOiByZ2JhKDAsIDAsIDAsIDAuMik7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTZweDsKICAgICAgfQoKICAgICAgcCB7CiAgICAgICAgY29sb3I6IHJnYmEoMCwgMCwgMCwgMC40KTsKICAgICAgICBtYXJnaW46IDA7CiAgICAgIH0KICAgIH0KCiAgICAubW9ubyB7CiAgICAgIGZvbnQtZmFtaWx5OiB2YXIoLS1uZ2lueC1mb250LWZhbWlseS1tb25vLCB1aS1tb25vc3BhY2UsIFNGTW9uby1SZWd1bGFyLCBNZW5sbywgTW9uYWNvLCBDb25zb2xhcywgJ0xpYmVyYXRpb24gTW9ubycsIG1vbm9zcGFjZSk7CiAgICB9CgogICAgLmRyYXdlci1ib2R5IHsKICAgICAgcGFkZGluZzogMCAyNHB4IDE2cHg7CiAgICB9CgogICAgLmRyYXdlci1mb290ZXIgewogICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgIGJvdHRvbTogMDsKICAgICAgbGVmdDogMDsKICAgICAgcmlnaHQ6IDA7CiAgICAgIHBhZGRpbmc6IDEycHggMjRweDsKICAgICAgYm9yZGVyLXRvcDogMXB4IHNvbGlkIHJnYmEoMCwgMCwgMCwgMC4wNik7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGp1c3RpZnktY29udGVudDogZmxleC1lbmQ7CiAgICAgIGdhcDogOHB4OwogICAgICBiYWNrZ3JvdW5kOiAjZmZmOwogICAgfQogIA==", "src/app/pages/nginx/components/nginx-tabs/nginx-secondary-upstream-tab/nginx-secondary-upstream-tab.component.ts", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-section-card\\nginx-section-card.component.ts;CiAgICA6aG9zdCB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQoKICAgIC5jYXJkIHsKICAgICAgYmFja2dyb3VuZDogdmFyKC0tYmctd2hpdGUpOwogICAgICBib3JkZXI6IDFweCBzb2xpZCB2YXIoLS1ib3JkZXIpOwogICAgICBib3JkZXItbGVmdC13aWR0aDogM3B4OwogICAgICBib3JkZXItcmFkaXVzOiA4cHg7CiAgICAgIGJveC1zaGFkb3c6IHZhcigtLXNoYWRvdy1jYXJkKTsKICAgICAgbWFyZ2luLWJvdHRvbTogMTRweDsKICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgYm9yZGVyLWxlZnQtY29sb3I6IHRyYW5zcGFyZW50OwogICAgfQoKICAgIC5jYXJkLmNhcmQtYmx1ZSB7CiAgICAgIGJvcmRlci1sZWZ0LWNvbG9yOiB2YXIoLS1ibHVlKTsKICAgIH0KCiAgICAuY2FyZC5jYXJkLWdyZWVuIHsKICAgICAgYm9yZGVyLWxlZnQtY29sb3I6IHZhcigtLWdyZWVuKTsKICAgIH0KCiAgICAuY2FyZC5jYXJkLW9yYW5nZSB7CiAgICAgIGJvcmRlci1sZWZ0LWNvbG9yOiB2YXIoLS1vcmFuZ2UpOwogICAgfQoKICAgIC5jYXJkLWhlYWRlciB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsKICAgICAgcGFkZGluZzogMTJweCAxNnB4IDEycHggMjBweDsKICAgICAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkIHZhcigtLWJvcmRlci1saWdodCk7CiAgICAgIGdhcDogMTBweDsKICAgIH0KCiAgICAuY2FyZC1oZWFkZXItbGVmdCB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIGdhcDogOHB4OwogICAgICBtaW4td2lkdGg6IDA7CiAgICB9CgogICAgLmNhcmQtdGl0bGUgewogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1iYXNlLCAxNHB4KTsKICAgICAgZm9udC13ZWlnaHQ6IDcwMDsKICAgICAgY29sb3I6IHZhcigtLXRleHQtMSk7CiAgICB9CgogICAgLmNhcmQtc3VidGl0bGUgewogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTMpOwogICAgfQoKICAgIC5jYXJkLWFjdGlvbnMgewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBnYXA6IDhweDsKICAgICAgZmxleC13cmFwOiB3cmFwOwogICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGZsZXgtZW5kOwogICAgfQoKICAgIC5jYXJkLWJvZHkgewogICAgICBwYWRkaW5nOiAxNHB4IDE4cHg7CgogICAgICAmLm5vLXBhZGRpbmcgewogICAgICAgIHBhZGRpbmc6IDA7CiAgICAgIH0KICAgIH0KCiAgICBAbWVkaWEgKG1heC13aWR0aDogNzY4cHgpIHsKICAgICAgLmNhcmQtaGVhZGVyIHsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgIGFsaWduLWl0ZW1zOiBmbGV4LXN0YXJ0OwogICAgICB9CgogICAgICAuY2FyZC1hY3Rpb25zIHsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgfQogICAgfQogIA==", "src/app/pages/nginx/components/nginx-section-card/nginx-section-card.component.ts", "angular:jit:template:src\\app\\pages\\nginx\\components\\nginx-server-list\\nginx-server-list.component.html", "angular:jit:style:src\\app\\pages\\nginx\\components\\nginx-server-list\\nginx-server-list.component.less", "angular:jit:template:src\\app\\pages\\nginx\\components\\nginx-server-drawer\\nginx-server-drawer.component.html", "angular:jit:style:src\\app\\pages\\nginx\\components\\nginx-server-drawer\\nginx-server-drawer.component.less", "src/app/pages/nginx/components/nginx-server-drawer/nginx-server-drawer.component.ts", "src/app/pages/nginx/components/nginx-server-list/nginx-server-list.component.ts", "angular:jit:style:inline:src\\app\\pages\\nginx\\components\\nginx-stat-card\\nginx-stat-card.component.ts;CiAgICA6aG9zdCB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQoKICAgIC5zdGF0LWNhcmQgewogICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1iZy13aGl0ZSk7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkIHZhcigtLWJvcmRlcik7CiAgICAgIGJvcmRlci1yYWRpdXM6IDhweDsKICAgICAgcGFkZGluZzogMTRweDsKICAgICAgYm94LXNoYWRvdzogdmFyKC0tc2hhZG93LWNhcmQpOwogICAgICB0cmFuc2l0aW9uOiBhbGwgMTQwbXMgZWFzZTsKCiAgICAgICY6aG92ZXIgewogICAgICAgIGJveC1zaGFkb3c6IHZhcigtLXNoYWRvdy1ob3Zlcik7CiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC0xcHgpOwogICAgICB9CiAgICB9CgogICAgLnN0YXQtbGFiZWwgewogICAgICBmb250LXNpemU6IHZhcigtLW5naW54LWZvbnQtc2l6ZS1zbSwgMTJweCk7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTMpOwogICAgICBmb250LXdlaWdodDogNjAwOwogICAgICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlOwogICAgICBsZXR0ZXItc3BhY2luZzogMC41cHg7CiAgICAgIG1hcmdpbi1ib3R0b206IDZweDsKICAgIH0KCiAgICAuc3RhdC12YWx1ZSB7CiAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLWtwaSwgMjJweCk7CiAgICAgIGxpbmUtaGVpZ2h0OiAxLjE7CiAgICAgIGZvbnQtd2VpZ2h0OiA4MDA7CiAgICAgIGNvbG9yOiB2YXIoLS10ZXh0LTEpOwogICAgICBmb250LWZhbWlseTogdmFyKC0tbmdpbngtZm9udC1mYW1pbHktbW9ubywgdWktbW9ub3NwYWNlLCBTRk1vbm8tUmVndWxhciwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsICdMaWJlcmF0aW9uIE1vbm8nLCBtb25vc3BhY2UpOwogICAgICBtYXJnaW4tYm90dG9tOiA0cHg7CgogICAgICAmLmdyZWVuIHsKICAgICAgICBjb2xvcjogdmFyKC0tZ3JlZW4pOwogICAgICB9CgogICAgICAmLmJsdWUgewogICAgICAgIGNvbG9yOiB2YXIoLS1ibHVlKTsKICAgICAgfQoKICAgICAgJi5wdXJwbGUgewogICAgICAgIGNvbG9yOiB2YXIoLS1wdXJwbGUpOwogICAgICB9CgogICAgICAmLm9yYW5nZSB7CiAgICAgICAgY29sb3I6IHZhcigtLW9yYW5nZSk7CiAgICAgIH0KCiAgICAgICYucmVkIHsKICAgICAgICBjb2xvcjogdmFyKC0tcmVkKTsKICAgICAgfQogICAgfQoKICAgIC5zdGF0LXN1YiB7CiAgICAgIGZvbnQtc2l6ZTogdmFyKC0tbmdpbngtZm9udC1zaXplLXNtLCAxMnB4KTsKICAgICAgY29sb3I6IHZhcigtLXRleHQtMyk7CiAgICAgIGZvbnQtZmFtaWx5OiB2YXIoLS1uZ2lueC1mb250LWZhbWlseS1tb25vLCB1aS1tb25vc3BhY2UsIFNGTW9uby1SZWd1bGFyLCBNZW5sbywgTW9uYWNvLCBDb25zb2xhcywgJ0xpYmVyYXRpb24gTW9ubycsIG1vbm9zcGFjZSk7CiAgICB9CiAg", "src/app/pages/nginx/components/nginx-stat-card/nginx-stat-card.component.ts", "src/app/pages/nginx/nginx.component.ts"],
4
- "sourcesContent": ["<app-page-layout [title]=\"'Nginx \u7BA1\u7406'\" [loading]=\"loading()\" [isFullscreen]=\"true\" [isOverflowYAuto]=\"false\">\r\n <ng-container ngProjectAs=\"actions\">\r\n @if (instance()) {\r\n <span class=\"top-status\" [class.running]=\"status()?.isRunning\">\r\n <span class=\"status-dot\"></span>\r\n {{ instance()?.version || 'unknown' }} \u00B7 {{ pidText }}\r\n </span>\r\n\r\n <button nz-button nzType=\"default\" (click)=\"refreshAll()\" [nzLoading]=\"configLoading()\">\r\n <nz-icon nzType=\"reload\" nzTheme=\"outline\"></nz-icon>\r\n \u5237\u65B0\r\n </button>\r\n\r\n <button nz-button nzType=\"text\" nz-tooltip nzTooltipTitle=\"\u8BBE\u7F6E\" (click)=\"switchSecondaryTab('settings')\">\r\n <nz-icon nzType=\"setting\" nzTheme=\"outline\"></nz-icon>\r\n </button>\r\n }\r\n </ng-container>\r\n\r\n @if (!instance()) {\r\n <nz-layout class=\"console-shell\">\r\n <nz-content class=\"unbound-content\">\r\n <nz-empty\r\n nzNotFoundImage=\"simple\"\r\n [nzNotFoundContent]=\"'\u8BF7\u5148\u7ED1\u5B9A\u672C\u5730 Nginx \u5B9E\u4F8B'\"\r\n [nzNotFoundFooter]=\"bindFooter\"\r\n >\r\n <ng-template #bindFooter>\r\n <button nz-button nzType=\"primary\" (click)=\"showBindModal()\">\u7ED1\u5B9A Nginx</button>\r\n </ng-template>\r\n </nz-empty>\r\n </nz-content>\r\n </nz-layout>\r\n } @else {\r\n <nz-layout class=\"console-shell\">\r\n <nz-content class=\"console-content\">\r\n <div class=\"stats-row\">\r\n <app-nginx-stat-card\r\n label=\"\u670D\u52A1\u72B6\u6001\"\r\n [value]=\"serviceStatusText\"\r\n [toneClass]=\"status()?.isRunning ? 'green' : 'red'\"\r\n [sub]=\"pidText\"\r\n ></app-nginx-stat-card>\r\n\r\n <app-nginx-stat-card\n label=\"\u8FD0\u884C\u65F6\u957F\"\n [value]=\"uptimeText\"\n toneClass=\"blue\"\n [sub]=\"uptimeSubText\"\n ></app-nginx-stat-card>\n\r\n <app-nginx-stat-card\r\n label=\"\u6D3B\u8DC3\u8FDE\u63A5\"\r\n [value]=\"activeConnectionText\"\r\n toneClass=\"purple\"\r\n [sub]=\"activeConnectionSubText\"\r\n ></app-nginx-stat-card>\r\n\r\n <app-nginx-stat-card\r\n label=\"Server \u5757\"\r\n [value]=\"serverSummary().total\"\r\n toneClass=\"orange\"\r\n [sub]=\"enabledServerText\"\r\n ></app-nginx-stat-card>\r\n </div>\r\n\r\n <div class=\"service-control-row\">\r\n <div class=\"service-control-left\">\r\n <span class=\"service-control-label\">\u670D\u52A1\u63A7\u5236</span>\r\n <button nz-button nzType=\"default\" (click)=\"startNginx()\" [disabled]=\"controlling() || status()?.isRunning\">\r\n <nz-icon nzType=\"caret-right\" nzTheme=\"outline\"></nz-icon>\r\n \u542F\u52A8\r\n </button>\r\n <button nz-button nzType=\"default\" (click)=\"stopNginx()\" [disabled]=\"controlling() || !status()?.isRunning\">\r\n <nz-icon nzType=\"pause\" nzTheme=\"outline\"></nz-icon>\r\n \u505C\u6B62\r\n </button>\r\n <button nz-button nzType=\"default\" (click)=\"reloadNginx()\" [disabled]=\"controlling() || !status()?.isRunning\">\r\n <nz-icon nzType=\"reload\" nzTheme=\"outline\"></nz-icon>\r\n \u91CD\u8F7D\r\n </button>\r\n <button nz-button nzType=\"default\" (click)=\"testConfig()\">\r\n <nz-icon nzType=\"check-circle\" nzTheme=\"outline\"></nz-icon>\r\n \u68C0\u6D4B\r\n </button>\r\n </div>\r\n <span class=\"service-control-path mono\">{{ instance()?.path || '-' }}</span>\r\n </div>\r\n\r\n <app-nginx-section-card\r\n accent=\"blue\"\r\n [showHeader]=\"true\"\r\n title=\"\u57FA\u7840\u914D\u7F6E\"\r\n subtitle=\"Server \u7BA1\u7406\"\r\n [noBodyPadding]=\"true\"\r\n >\r\n <ng-container nginxCardActions>\r\n <button nz-button nzType=\"default\" (click)=\"importServer()\">\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\"></nz-icon>\r\n \u5BFC\u5165\r\n </button>\r\n <button nz-button nzType=\"primary\" (click)=\"requestCreateServer()\">\r\n <nz-icon nzType=\"plus\" nzTheme=\"outline\"></nz-icon>\r\n \u65B0\u589E Server\r\n </button>\r\n </ng-container>\r\n <app-nginx-server-list\r\n [showToolbar]=\"false\"\r\n [openCreateToken]=\"openServerDrawerToken()\"\r\n (summaryChange)=\"onServerSummaryChange($event)\"\r\n (serverListMutated)=\"onServerListMutated()\"\r\n ></app-nginx-server-list>\r\n </app-nginx-section-card>\r\n\r\n <app-nginx-section-card accent=\"green\" [noBodyPadding]=\"true\">\r\n <button type=\"button\" class=\"config-summary\" [class.open]=\"configExpanded()\" (click)=\"toggleConfigExpanded()\">\r\n <div class=\"config-summary-left\">\r\n <span class=\"config-title\">\u914D\u7F6E\u6587\u4EF6</span>\r\n </div>\r\n <nz-icon nzType=\"down\" nzTheme=\"outline\" class=\"expand-icon\"></nz-icon>\r\n </button>\r\n\r\n @if (configExpanded()) {\r\n <div class=\"config-expanded\">\r\n <app-nginx-config-editor [refreshToken]=\"configEditorRefreshToken()\"></app-nginx-config-editor>\r\n </div>\r\n }\r\n </app-nginx-section-card>\r\n\r\n <app-nginx-section-card accent=\"orange\" [noBodyPadding]=\"true\">\r\n <nz-tabs\r\n class=\"secondary-tabset\"\r\n [nzSelectedIndex]=\"secondaryTabIndex()\"\r\n (nzSelectedIndexChange)=\"onSecondaryTabIndexChange($event)\"\r\n >\r\n <nz-tab nzTitle=\"Upstream \u7BA1\u7406\">\r\n @if (secondaryTab() === 'upstream') {\r\n <app-nginx-secondary-upstream-tab></app-nginx-secondary-upstream-tab>\r\n }\r\n </nz-tab>\r\n <nz-tab nzTitle=\"SSL \u8BC1\u4E66\">\r\n @if (secondaryTab() === 'ssl') {\r\n <app-nginx-secondary-ssl-tab></app-nginx-secondary-ssl-tab>\r\n }\r\n </nz-tab>\r\n <nz-tab nzTitle=\"\u6D41\u91CF\u63A7\u5236\">\r\n @if (secondaryTab() === 'traffic') {\r\n <app-nginx-secondary-traffic-tab></app-nginx-secondary-traffic-tab>\r\n }\r\n </nz-tab>\r\n <nz-tab nzTitle=\"\u6027\u80FD\u4F18\u5316\">\r\n @if (secondaryTab() === 'perf') {\r\n <app-nginx-secondary-perf-tab></app-nginx-secondary-perf-tab>\r\n }\r\n </nz-tab>\r\n <nz-tab nzTitle=\"\u65E5\u5FD7\">\r\n @if (secondaryTab() === 'logs') {\r\n <app-nginx-secondary-logs-tab></app-nginx-secondary-logs-tab>\r\n }\r\n </nz-tab>\r\n <nz-tab nzTitle=\"\u914D\u7F6E\u68C0\u6D4B\">\r\n @if (secondaryTab() === 'test') {\r\n <app-nginx-secondary-test-tab\r\n [loading]=\"loading()\"\r\n [logs]=\"recentLogs()\"\r\n (runTest)=\"testConfig()\"\r\n ></app-nginx-secondary-test-tab>\r\n }\r\n </nz-tab>\r\n <nz-tab nzTitle=\"\u8BBE\u7F6E\">\r\n @if (secondaryTab() === 'settings') {\r\n <app-nginx-secondary-settings-tab\r\n [instance]=\"instance()\"\r\n [configFileCount]=\"configFiles().length\"\r\n (unbind)=\"unbind()\"\r\n ></app-nginx-secondary-settings-tab>\r\n }\r\n </nz-tab>\r\n </nz-tabs>\r\n </app-nginx-section-card>\r\n </nz-content>\r\n </nz-layout>\r\n }\r\n\r\n <nz-modal\r\n [(nzVisible)]=\"bindModalVisible\"\r\n (nzOnCancel)=\"bindModalVisible = false\"\r\n [nzFooter]=\"null\"\r\n [nzWidth]=\"560\"\r\n [nzCentered]=\"true\"\r\n [nzBodyStyle]=\"{ padding: '0' }\"\r\n >\r\n <ng-container *nzModalContent>\r\n <div class=\"bind-modal\">\r\n <div class=\"bind-icon\">\r\n <nz-icon nzType=\"link\" nzTheme=\"outline\"></nz-icon>\r\n </div>\r\n\r\n <div class=\"bind-title\">\u7ED1\u5B9A\u672C\u5730 Nginx</div>\r\n <div class=\"bind-desc\">\r\n \u6307\u5B9A\u672C\u673A Nginx \u53EF\u6267\u884C\u6587\u4EF6\u8DEF\u5F84\uFF0C\u5B8C\u6210\u5B9E\u4F8B\u7ED1\u5B9A\u540E\u5373\u53EF\u4F7F\u7528\u53EF\u89C6\u5316\u7BA1\u7406\u529F\u80FD\r\n </div>\r\n\r\n <div class=\"bind-input-group\">\r\n <input\r\n nz-input\r\n class=\"mono bind-path-input\"\r\n [(ngModel)]=\"bindPath\"\r\n placeholder=\"/usr/local/nginx/sbin/nginx\"\r\n (keyup.enter)=\"bindNginx()\"\r\n />\r\n <button nz-button nzType=\"default\" (click)=\"showPathHint()\">\r\n <nz-icon nzType=\"folder-open\" nzTheme=\"outline\"></nz-icon>\r\n \u6D4F\u89C8\r\n </button>\r\n </div>\r\n\r\n <!-- <div class=\"bind-or\">\u2014 \u6216 \u2014</div>\r\n\r\n <button type=\"button\" class=\"bind-auto-detect\" (click)=\"autoDetectBindPath()\">\r\n <nz-icon nzType=\"search\" nzTheme=\"outline\"></nz-icon>\r\n <div class=\"bind-auto-detect-text\">\r\n <div class=\"bind-auto-detect-title\">\u81EA\u52A8\u68C0\u6D4B</div>\r\n <div class=\"bind-auto-detect-hint\">\u586B\u5145\u5E38\u89C1\u5B89\u88C5\u8DEF\u5F84\uFF0C\u786E\u8BA4\u540E\u5373\u53EF\u7ED1\u5B9A</div>\r\n </div>\r\n </button> -->\r\n\r\n <button\r\n nz-button\r\n nzType=\"primary\"\r\n class=\"bind-submit-btn\"\r\n (click)=\"bindNginx()\"\r\n [nzLoading]=\"binding()\"\r\n [disabled]=\"!bindPath.trim()\"\r\n >\r\n <nz-icon nzType=\"link\" nzTheme=\"outline\"></nz-icon>\r\n \u7ED1\u5B9A Nginx\r\n </button>\r\n </div>\r\n </ng-container>\r\n </nz-modal>\r\n</app-page-layout>\r\n", "/* src/app/pages/nginx/nginx.component.less */\n:host {\n --nginx-font-size-sm: 12px;\n --nginx-font-size-base: 14px;\n --nginx-font-size-kpi: 22px;\n --nginx-font-family-mono:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n \"Liberation Mono\",\n monospace;\n --bg-page: #f5f7fa;\n --bg-white: #ffffff;\n --bg-hover: #f8f9fc;\n --bg-input: #f7f8fa;\n --border: #e8ebf0;\n --border-light: #eef1f5;\n --text-1: #1d2129;\n --text-2: #4e5969;\n --text-3: #86909c;\n --text-4: #c9cdd4;\n --blue: #165dff;\n --blue-bg: #f2f6ff;\n --blue-border: rgba(22, 93, 255, 0.15);\n --green: #00b42a;\n --green-bg: #f2fff6;\n --green-border: rgba(0, 180, 42, 0.15);\n --red: #f53f3f;\n --red-bg: #fff2f2;\n --orange: #ff7d00;\n --orange-bg: #fff7e8;\n --orange-border: rgba(255, 125, 0, 0.15);\n --purple: #722ed1;\n --shadow-card: 0 1px 4px rgba(0, 0, 0, 0.04);\n --shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.06);\n}\n.console-shell {\n height: 100%;\n background: var(--bg-page);\n}\n.console-content {\n padding: 18px 20px 30px;\n overflow-y: auto;\n}\n.unbound-content {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n}\n.stats-row {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n gap: 12px;\n margin-bottom: 14px;\n}\n.service-control-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n background: #f2f3f5;\n border: 1px solid #e5e6eb;\n border-radius: 6px;\n padding: 10px 12px;\n margin-bottom: 14px;\n}\n.service-control-left {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.service-control-label {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-2);\n font-weight: 600;\n margin-right: 2px;\n}\n.service-control-path {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n padding: 4px 8px;\n border: 1px solid #dcdfe5;\n border-radius: 4px;\n background: #f7f8fa;\n}\n.config-title {\n font-size: var(--nginx-font-size-base, 14px);\n font-weight: 700;\n color: var(--text-1);\n}\n.config-summary {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 16px 10px 20px;\n border: none;\n background: #fff;\n cursor: pointer;\n text-align: left;\n transition: background 120ms ease;\n}\n.config-summary:hover {\n background: var(--bg-hover);\n}\n.config-summary.open .expand-icon {\n transform: rotate(180deg);\n}\n.config-summary-left {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.expand-icon {\n color: var(--text-3);\n transition: transform 200ms ease;\n}\n.config-expanded {\n border-top: 1px solid var(--border-light);\n}\n.mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n}\n.mono.strong {\n color: var(--text-1);\n font-weight: 600;\n}\n.mono.dim {\n color: var(--text-2);\n font-size: var(--nginx-font-size-sm, 12px);\n}\n.bind-modal {\n padding: 32px;\n text-align: center;\n}\n.bind-icon {\n width: 56px;\n height: 56px;\n margin: 0 auto 18px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--blue);\n background: var(--blue-bg);\n border: 1px solid var(--blue-border);\n font-size: 24px;\n}\n.bind-title {\n font-size: 18px;\n font-weight: 700;\n color: var(--text-1);\n margin-bottom: 8px;\n}\n.bind-desc {\n font-size: var(--nginx-font-size-sm, 12px);\n line-height: 1.7;\n color: var(--text-3);\n margin-bottom: 22px;\n}\n.bind-input-group {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n.bind-path-input {\n flex: 1;\n}\n.bind-or {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n margin-bottom: 12px;\n}\n.bind-auto-detect {\n width: 100%;\n margin-bottom: 16px;\n border: 1px solid var(--border);\n border-radius: 6px;\n background: var(--bg-input);\n cursor: pointer;\n padding: 10px 12px;\n display: flex;\n align-items: center;\n gap: 10px;\n transition: all 120ms ease;\n text-align: left;\n}\n.bind-auto-detect:hover {\n border-color: var(--blue);\n box-shadow: 0 0 0 2px var(--blue-border);\n background: #fff;\n}\n.bind-auto-detect .anticon {\n font-size: 16px;\n color: var(--blue);\n}\n.bind-auto-detect-text {\n min-width: 0;\n}\n.bind-auto-detect-title {\n color: var(--text-1);\n font-size: var(--nginx-font-size-base, 14px);\n font-weight: 600;\n}\n.bind-auto-detect-hint {\n margin-top: 2px;\n color: var(--text-3);\n font-size: var(--nginx-font-size-sm, 12px);\n}\n.bind-submit-btn {\n width: 100%;\n height: 36px;\n font-size: var(--nginx-font-size-base, 14px);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n@media (max-width: 768px) {\n .bind-modal {\n padding: 20px 16px;\n }\n .bind-input-group {\n flex-direction: column;\n align-items: stretch;\n }\n}\n:host ::ng-deep .top-status {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: var(--nginx-font-size-sm, 12px);\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n color: var(--red);\n background: var(--red-bg);\n border: 1px solid rgba(245, 63, 63, 0.2);\n border-radius: 16px;\n padding: 4px 10px;\n}\n:host ::ng-deep .top-status.running {\n color: var(--green);\n background: var(--green-bg);\n border-color: var(--green-border);\n}\n:host ::ng-deep .top-status .status-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: currentColor;\n animation: pulse 2s ease-in-out infinite;\n}\n:host ::ng-deep .secondary-tabset .ant-tabs-nav {\n margin: 0;\n padding: 0 16px;\n border-bottom: 1px solid var(--border-light);\n}\n:host ::ng-deep .secondary-tabset .ant-tabs-content-holder {\n padding: 14px 18px;\n}\n@keyframes pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.35;\n }\n}\n@media (max-width: 1280px) {\n .stats-row {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n}\n@media (max-width: 992px) {\n .service-control-row {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n@media (max-width: 768px) {\n .console-content {\n padding: 12px;\n }\n .stats-row {\n grid-template-columns: 1fr;\n }\n .config-summary-left {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n/*# sourceMappingURL=nginx.component.css.map */\n", "/* angular:styles/component:less;f674c0a165c5f39b31152e289fc86966a5f30c517d213b82b5ab60d88ac38960;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-config-editor\\nginx-config-editor.component.ts */\n.config-editor {\n display: flex;\n flex-direction: column;\n height: 100%;\n border: 1px solid #f0f0f0;\n border-radius: 8px;\n overflow: hidden;\n}\n.editor-toolbar {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 12px;\n padding: 12px 16px;\n background: #fafafa;\n border-bottom: 1px solid #f0f0f0;\n}\n.editor-toolbar .toolbar-left {\n min-width: 0;\n flex: 1;\n display: flex;\n flex-direction: row;\n align-items: center;\n flex-wrap: wrap;\n gap: 10px;\n}\n.editor-toolbar .file-info {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: var(--nginx-font-size-base, 14px);\n min-width: 0;\n}\n.editor-toolbar .file-info .file-path {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n color: rgba(0, 0, 0, 0.65);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.editor-toolbar .file-selector {\n width: min(560px, 100%);\n}\n.editor-toolbar .editor-actions {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n}\n.editor-container {\n flex: 1;\n min-height: 520px;\n position: relative;\n}\n.code-editor {\n display: block;\n min-height: 520px;\n width: 100%;\n border: 1px solid #d9d9d9;\n}\n.validation-result {\n padding: 12px 16px;\n background: #f6ffed;\n border-top: 1px solid #b7eb8f;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.validation-result.error {\n background: #fff2f0;\n border-top-color: #ffccc7;\n}\n.validation-result .success-icon {\n color: #52c41a;\n}\n.validation-result .error-icon {\n color: #ff4d4f;\n}\n.validation-result .error-list,\n.validation-result .warning-list {\n margin-top: 8px;\n width: 100%;\n}\n.validation-result .error-list .error-item,\n.validation-result .warning-list .error-item {\n color: #ff4d4f;\n font-size: var(--nginx-font-size-sm, 12px);\n padding: 4px 0;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n}\n.validation-result .error-list .warning-item,\n.validation-result .warning-list .warning-item {\n color: #faad14;\n font-size: var(--nginx-font-size-sm, 12px);\n padding: 4px 0;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n}\n:host ::ng-deep nz-code-editor.code-editor.ant-code-editor {\n width: 100%;\n min-height: 520px !important;\n display: block;\n}\n:host ::ng-deep nz-code-editor.code-editor .monaco-diff-editor {\n min-height: 520px;\n}\n/*# sourceMappingURL=nginx-config-editor.component.css.map */\n", "import { inject, Injectable } from '@angular/core';\r\nimport { HttpParams } from '@angular/common/http';\r\nimport { firstValueFrom } from 'rxjs';\r\nimport { ApiClient } from '@app/core';\r\nimport type {\r\n NginxStatusResponse,\r\n NginxStatsResponse,\r\n NginxBindResponse,\r\n NginxConfig,\r\n NginxCommandResult,\r\n NginxConfigValidation,\r\n NginxServer,\r\n CreateNginxServerRequest,\r\n UpdateNginxServerRequest,\r\n NginxUpstream,\r\n NginxSslCertificate,\r\n NginxTrafficConfig,\r\n NginxPerformanceConfig,\n NginxModuleSettings,\n} from '../models/nginx.types';\n\r\n/**\r\n * Nginx 管理 API 服务\r\n */\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class NginxService {\r\n private http = inject(ApiClient);\r\n private readonly baseUrl = '/api/nginx';\r\n\r\n // ========== 实例管理 ==========\r\n\r\n /**\r\n * 获取 Nginx 状态和实例信息\r\n */\r\n async getStatus(): Promise<NginxStatusResponse> {\r\n return await firstValueFrom(this.http.get<NginxStatusResponse>(`${this.baseUrl}/status`));\r\n }\r\n\r\n /**\r\n * 获取首页统计信息(状态 + server 汇总)\r\n */\r\n async getStats(): Promise<NginxStatsResponse> {\r\n return await firstValueFrom(this.http.get<NginxStatsResponse>(`${this.baseUrl}/stats`));\r\n }\r\n\r\n /**\r\n * 绑定 Nginx 实例\r\n */\r\n async bind(path: string): Promise<NginxBindResponse> {\r\n return await firstValueFrom(this.http.post<NginxBindResponse>(`${this.baseUrl}/bind`, { path }));\r\n }\r\n\r\n /**\r\n * 解绑 Nginx 实例\r\n */\r\n async unbind(): Promise<{ success: boolean }> {\r\n return await firstValueFrom(this.http.post<{ success: boolean }>(`${this.baseUrl}/unbind`, {}));\r\n }\r\n\r\n // ========== 服务控制 ==========\r\n\r\n /**\r\n * 启动 Nginx\r\n */\r\n async start(): Promise<NginxCommandResult> {\r\n return await firstValueFrom(this.http.post<NginxCommandResult>(`${this.baseUrl}/start`, {}));\r\n }\r\n\r\n /**\r\n * 停止 Nginx\r\n */\r\n async stop(): Promise<NginxCommandResult> {\r\n return await firstValueFrom(this.http.post<NginxCommandResult>(`${this.baseUrl}/stop`, {}));\r\n }\r\n\r\n /**\r\n * 重载配置\r\n */\r\n async reload(): Promise<NginxCommandResult> {\r\n return await firstValueFrom(this.http.post<NginxCommandResult>(`${this.baseUrl}/reload`, {}));\r\n }\r\n\r\n /**\r\n * 测试配置\r\n */\r\n async test(): Promise<NginxConfigValidation> {\r\n return await firstValueFrom(this.http.post<NginxConfigValidation>(`${this.baseUrl}/test`, {}));\r\n }\r\n\r\n // ========== 配置管理 ==========\r\n\r\n /**\r\n * 读取主配置\r\n */\r\n async getConfig(): Promise<{ success: boolean; config?: NginxConfig; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; config?: NginxConfig; error?: string }>(\r\n `${this.baseUrl}/config`\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 更新主配置\r\n */\r\n async updateConfig(content: string): Promise<{ success: boolean; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.put<{ success: boolean; error?: string }>(`${this.baseUrl}/config`, { content })\r\n );\r\n }\r\n\r\n /**\r\n * 验证配置\r\n */\r\n async validateConfig(content?: string): Promise<NginxConfigValidation> {\r\n return await firstValueFrom(\r\n this.http.post<NginxConfigValidation>(`${this.baseUrl}/config/validate`, { content })\r\n );\r\n }\r\n\r\n /**\r\n * 获取包含的配置文件列表\r\n */\r\n async getConfigFiles(): Promise<{ success: boolean; files?: string[]; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; files?: string[]; error?: string }>(\r\n `${this.baseUrl}/config/files`\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 读取指定配置文件\r\n */\r\n async getConfigFile(filePath: string): Promise<{ success: boolean; config?: NginxConfig; error?: string }> {\r\n const params = new HttpParams().set('filePath', filePath);\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; config?: NginxConfig; error?: string }>(\r\n `${this.baseUrl}/config/file`,\r\n params\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 保存指定配置文件\r\n */\r\n async updateConfigFile(filePath: string, content: string): Promise<{ success: boolean; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.put<{ success: boolean; error?: string }>(`${this.baseUrl}/config/file`, {\r\n filePath,\r\n content,\r\n })\r\n );\r\n }\r\n\r\n // ========== Server 管理 ==========\r\n\r\n /**\r\n * 获取所有 server\r\n */\r\n async getServers(): Promise<{ success: boolean; servers?: NginxServer[]; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; servers?: NginxServer[]; error?: string }>(\r\n `${this.baseUrl}/servers`\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 获取单个 server\r\n */\r\n async getServer(id: string): Promise<{ success: boolean; server?: NginxServer; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; server?: NginxServer; error?: string }>(\r\n `${this.baseUrl}/servers/${id}`\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 创建 server\r\n */\r\n async createServer(\r\n request: CreateNginxServerRequest\r\n ): Promise<{ success: boolean; server?: NginxServer; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.post<{ success: boolean; server?: NginxServer; error?: string }>(\r\n `${this.baseUrl}/servers`,\r\n request\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 更新 server\r\n */\r\n async updateServer(\r\n id: string,\r\n request: UpdateNginxServerRequest\r\n ): Promise<{ success: boolean; server?: NginxServer; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.put<{ success: boolean; server?: NginxServer; error?: string }>(\r\n `${this.baseUrl}/servers/${id}`,\r\n request\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 删除 server\r\n */\r\n async deleteServer(id: string): Promise<{ success: boolean; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.delete<{ success: boolean; error?: string }>(`${this.baseUrl}/servers/${id}`)\r\n );\r\n }\r\n\r\n /**\r\n * 启用 server\r\n */\r\n async enableServer(id: string): Promise<{ success: boolean; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.patch<{ success: boolean; error?: string }>(`${this.baseUrl}/servers/${id}/enable`, {})\r\n );\r\n }\r\n\r\n /**\r\n * 禁用 server\r\n */\r\n async disableServer(id: string): Promise<{ success: boolean; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.patch<{ success: boolean; error?: string }>(`${this.baseUrl}/servers/${id}/disable`, {})\r\n );\r\n }\r\n\r\n // ========== Phase2:Upstream / SSL / 流量 / 性能 ==========\r\n\r\n async getUpstreams(): Promise<{ success: boolean; upstreams?: NginxUpstream[]; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; upstreams?: NginxUpstream[]; error?: string }>(\r\n `${this.baseUrl}/upstreams`\r\n )\r\n );\r\n }\r\n\r\n async saveUpstreams(upstreams: NginxUpstream[]): Promise<{ success: boolean; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.put<{ success: boolean; error?: string }>(`${this.baseUrl}/upstreams`, { upstreams })\r\n );\r\n }\r\n\r\n async getSslCertificates(): Promise<{ success: boolean; certificates?: NginxSslCertificate[]; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; certificates?: NginxSslCertificate[]; error?: string }>(\r\n `${this.baseUrl}/ssl/certificates`\r\n )\r\n );\r\n }\r\n\r\n async saveSslCertificates(\r\n certificates: NginxSslCertificate[]\r\n ): Promise<{ success: boolean; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.put<{ success: boolean; error?: string }>(`${this.baseUrl}/ssl/certificates`, {\r\n certificates,\r\n })\r\n );\r\n }\r\n\r\n async getTrafficConfig(): Promise<{ success: boolean; traffic?: NginxTrafficConfig; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; traffic?: NginxTrafficConfig; error?: string }>(\r\n `${this.baseUrl}/traffic`\r\n )\r\n );\r\n }\r\n\r\n async saveTrafficConfig(traffic: NginxTrafficConfig): Promise<{ success: boolean; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.put<{ success: boolean; error?: string }>(`${this.baseUrl}/traffic`, traffic)\r\n );\r\n }\r\n\r\n async getPerformanceConfig(): Promise<{ success: boolean; performance?: NginxPerformanceConfig; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; performance?: NginxPerformanceConfig; error?: string }>(\r\n `${this.baseUrl}/performance`\r\n )\r\n );\r\n }\r\n\r\n async savePerformanceConfig(\n performance: NginxPerformanceConfig\n ): Promise<{ success: boolean; error?: string }> {\n return await firstValueFrom(\n this.http.put<{ success: boolean; error?: string }>(`${this.baseUrl}/performance`, performance)\n );\n }\n\n async getModuleSettings(): Promise<{ success: boolean; settings?: NginxModuleSettings; error?: string }> {\n return await firstValueFrom(\n this.http.get<{ success: boolean; settings?: NginxModuleSettings; error?: string }>(\n `${this.baseUrl}/module/settings`\n )\n );\n }\n\n async saveModuleSettings(\n settings: Partial<NginxModuleSettings>\n ): Promise<{ success: boolean; settings?: NginxModuleSettings; error?: string }> {\n return await firstValueFrom(\n this.http.put<{ success: boolean; settings?: NginxModuleSettings; error?: string }>(\n `${this.baseUrl}/module/settings`,\n settings\n )\n );\n }\n\r\n // ========== 日志管理 ==========\r\n\r\n /**\r\n * 获取错误日志尾部\r\n */\r\n async getErrorLogs(tail: number = 100): Promise<{ success: boolean; lines?: string[]; error?: string }> {\r\n const params = new HttpParams().set('tail', tail.toString());\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; lines?: string[]; error?: string }>(\r\n `${this.baseUrl}/logs/error`,\r\n params\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 获取访问日志尾部\r\n */\r\n async getAccessLogs(tail: number = 100): Promise<{ success: boolean; lines?: string[]; error?: string }> {\r\n const params = new HttpParams().set('tail', tail.toString());\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; lines?: string[]; error?: string }>(\r\n `${this.baseUrl}/logs/access`,\r\n params\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * 获取日志文件路径信息\r\n */\r\n async getLogsInfo(): Promise<{ success: boolean; errorLog?: string; accessLog?: string; error?: string }> {\r\n return await firstValueFrom(\r\n this.http.get<{ success: boolean; errorLog?: string; accessLog?: string; error?: string }>(\r\n `${this.baseUrl}/logs/info`\r\n )\r\n );\r\n }\r\n}\r\n", "import type { languages } from 'monaco-editor';\r\n\r\n/**\r\n * Register nginx language with Monaco editor.\r\n * Must be called after Monaco is loaded (nz-code-editor triggers Monaco lazy load).\r\n */\r\nexport function registerNginxLanguage(): void {\r\n const w = window as any;\r\n if (!w.monaco) return;\r\n\r\n const monaco = w.monaco;\r\n if (monaco.languages.getLanguages().some((l: any) => l.id === 'nginx')) return;\r\n\r\n monaco.languages.register({ id: 'nginx', extensions: ['.conf'], aliases: ['Nginx'] });\r\n\r\n monaco.languages.setMonarchTokensProvider('nginx', {\r\n tokenizer: {\r\n root: [\r\n // Comments\r\n [/#.*$/, 'comment'],\r\n // Section blocks\r\n [/^\\s*(http|server|events|stream|upstream|location|map|geo|types|if|in|limit_except|split_clients|match)\\b/, 'keyword.control'],\r\n // Directive keywords (common nginx directives)\r\n [\r\n /\\b(root|index|try_files|alias|return|rewrite|proxy_pass|fastcgi_pass|scgi_pass|uwsgi_pass|include|set|add_header|expires|proxy_set_header|fastcgi_param|listen|server_name|error_page|default_server|upstream|zone|least_conn|ip_hash|keepalive|keepalive_requests|keepalive_timeout|worker_processes|worker_connections|use|multi_accept|accept_mutex|sendfile|tcp_nopush|tcp_nodelay|reset_timedout|resolver|resolver_timeout|client_max_body_size|client_body_buffer|gzip|gunzip|gzip_types|gzip_comp_level|gzip_vary|open_file_cache|open_file_cache_valid|open_file_cache_min_uses|log_format|access_log|error_log|pid|user|group|daemon|master_process|worker_rlimit_nofile|worker_rlimit_core|worker_shutdown|load_module|types|default_type|server_tokens|aio|directio|sendfile_max_chunk|proxy_buffering|proxy_buffer_size|proxy_buffers|proxy_busy_buffers_size|proxy_temp_path|proxy_temp_file_write_size|fastcgi_buffer_size|fastcgi_buffers|fastcgi_busy_buffers_size|fastcgi_temp_path|fastcgi_temp_file_write_size|fastcgi_cache|fastcgi_cache_key|fastcgi_cache_path|fastcgi_cache_valid|fastcgi_cache_use_stale|fastcgi_no_cache|fastcgi_cache_bypass|fastcgi_ignore_headers|fastcgi_intercept_errors|fastcgi_next_upstream|fastcgi_next_upstream_tries|fastcgi_connect_timeout|fastcgi_send_timeout|fastcgi_read_timeout|proxy_connect_timeout|proxy_send_timeout|proxy_read_timeout|proxy_ignore_client_abort|proxy_hide_header|proxy_pass_header|proxy_redirect|proxy_cookie_domain|proxy_cookie_path|proxy_set_header|proxy_http_version|proxy_force_ranges|proxy_cache|proxy_cache_key|proxy_cache_path|proxy_cache_valid|proxy_cache_use_stale|proxy_cache_background_update|proxy_cache_lock|proxy_cache_lock_timeout|proxy_no_cache|proxy_cache_bypass|proxy_ignore_headers|proxy_intercept_errors|proxy_next_upstream|proxy_next_upstream_tries|proxy_next_upstream_timeout|proxy_store|proxy_store_access|auth_basic|auth_basic_user_file|satisfy|allow|deny|limit_req|limit_req_zone|limit_req_status|limit_conn|limit_conn_zone|limit_conn_status|return|rewrite|break|set|if|set_by_lua|access_by_lua|content_by_lua|header_filter_by_lua|body_filter_by_lua|log_by_lua|rewrite_by_lua|init_by_lua|init_worker_by_lua|ssl_certificate|ssl_certificate_key|ssl_session_cache|ssl_session_timeout|ssl_protocols|ssl_ciphers|ssl_prefer_server_ciphers|ssl_verify_client|ssl_client_certificate|ssl_trusted_certificate|ssl_crl|real_ip_header|set_real_ip_from|geo|geoip_country|geoip_city|memcached_pass|memcached_connect_timeout|memcached_send_timeout|memcached_read_timeout|grpc_pass|grpc_connect_timeout|grpc_send_timeout|grpc_read_timeout|grpc_buffer_size|websocket|websocket_connect_timeout|websocket_send_timeout|websocket_read_timeout|websocket_ping_interval|websocket_ping_timeout|concat|concat_types|secure_link|secure_link_md5|add_before_body|add_after_body|addition_types|image_filter|xslt_stylesheet|xslt_types|random_index|geo|map|perl|perl_modules|perl_require|perl_handler|slice|mp4|mp4_buffer_size|mp4_max_buffer_size|flv|ogmp|status|status_format|check|check_http_send|check_http_expect_alive|check_shm_size|check_status|upstream_conf|js_set|js_content|js_header_filter_by_lua|js_body_filter_by_lua|js_log_by_lua|js_access_by_lua|js_content_by_lua|js_rewrite_by_lua|js_preread_by_lua|influxdb|set_|add_|proxy_|fastcgi_|uwsgi_|scgi_|grpc_|limit_|auth_|secure_|websocket_|ssl_|real_|memcached_|grpc_|proxy_next_upstream_tries|proxy_next_upstream_timeout)\\b/,\r\n 'keyword.directive',\r\n ],\r\n // Boolean/keyword values\r\n [/\\b(on|off|yes|no|true|false|default|none|any|auto|inline|upstream|down|backup|weight|max_fails|fail_timeout)\\b/, 'keyword.value'],\r\n // Numbers (with size units)\r\n [/\\b\\d+[kKmMgG]?\\b/, 'number'],\r\n // Strings\r\n [/\"[^\"]*\"/, 'string'],\r\n [/'[^']*'/, 'string'],\r\n // Variables\r\n [/\\$[a-zA-Z_][a-zA-Z0-9_]*/, 'variable'],\r\n // IP addresses and ports in configs\r\n [/\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d+)?\\b/, 'number'],\r\n // Ports\r\n [/\\b\\d{1,5}\\b/, 'number'],\r\n ],\r\n },\r\n } as languages.LanguageConfiguration);\r\n\r\n // Theme\r\n monaco.editor.defineTheme('nginx-theme', {\r\n base: 'vs',\r\n inherit: true,\r\n rules: [\r\n { token: 'comment', foreground: '6A9955', fontStyle: 'italic' },\r\n { token: 'keyword.control', foreground: '0000FF', fontStyle: 'bold' },\r\n { token: 'keyword.directive', foreground: '795E26' },\r\n { token: 'keyword.value', foreground: '0000FF' },\r\n { token: 'string', foreground: 'A31515' },\r\n { token: 'number', foreground: '098658' },\r\n { token: 'variable', foreground: '001188' },\r\n ],\r\n });\r\n}\r\n", "import {\r\n AfterViewInit,\r\n Component,\r\n ElementRef,\r\n Input,\r\n OnChanges,\r\n OnDestroy,\r\n OnInit,\r\n SimpleChanges,\r\n ViewChild,\r\n inject,\r\n signal,\r\n} from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport { FormsModule } from '@angular/forms';\r\nimport { NzButtonModule } from 'ng-zorro-antd/button';\r\nimport { NzCodeEditorModule } from 'ng-zorro-antd/code-editor';\r\nimport { NzIconModule } from 'ng-zorro-antd/icon';\r\nimport { NzMessageService } from 'ng-zorro-antd/message';\r\nimport { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';\r\nimport { NzSelectModule } from 'ng-zorro-antd/select';\r\nimport { NzToolTipModule } from 'ng-zorro-antd/tooltip';\r\nimport type { editor } from 'monaco-editor';\r\n\r\nimport type { NginxConfig } from '../../models/nginx.types';\r\nimport { NginxService } from '../../services/nginx.service';\r\nimport { registerNginxLanguage } from '../../../../utils/monaco-languages';\r\n\r\n/**\r\n * Nginx 配置编辑器组件\r\n */\r\n@Component({\r\n selector: 'app-nginx-config-editor',\r\n standalone: true,\r\n imports: [\r\n CommonModule,\r\n FormsModule,\r\n NzButtonModule,\r\n NzCodeEditorModule,\r\n NzIconModule,\r\n NzPopconfirmModule,\r\n NzSelectModule,\r\n NzToolTipModule,\r\n ],\r\n template: `\r\n <div class=\"config-editor\">\r\n <div class=\"editor-toolbar\">\r\n <div class=\"toolbar-left\">\r\n <div class=\"file-info\">\r\n <nz-icon nzType=\"file-text\" nzTheme=\"outline\"></nz-icon>\r\n <span class=\"file-path\">{{ config()?.mainConfigPath || '未加载' }}</span>\r\n @if (config()?.isWritable === false) {\r\n <nz-icon\r\n nzType=\"lock\"\r\n nzTheme=\"outline\"\r\n nz-tooltip\r\n nzTooltipTitle=\"配置文件只读,可能需要管理员权限\"\r\n ></nz-icon>\r\n }\r\n </div>\r\n\r\n <nz-select\r\n class=\"file-selector\"\r\n [ngModel]=\"selectedFilePath()\"\r\n (ngModelChange)=\"onSelectConfigFile($event)\"\r\n nzPlaceHolder=\"选择配置文件\"\r\n [nzDisabled]=\"loading() || !configFiles().length\"\r\n >\r\n @for (file of configFiles(); track file) {\r\n <nz-option [nzValue]=\"file\" [nzLabel]=\"getFileLabel(file)\"></nz-option>\r\n }\r\n </nz-select>\r\n <button nz-button nzType=\"default\" (click)=\"loadConfigFiles()\" [nzLoading]=\"filesLoading()\">\r\n <nz-icon nzType=\"reload\" nzTheme=\"outline\"></nz-icon>\r\n 刷新文件列表\r\n </button>\r\n </div>\r\n\r\n <div class=\"editor-actions\">\r\n <button nz-button (click)=\"loadConfig()\" [nzLoading]=\"loading()\">\r\n <nz-icon nzType=\"reload\" nzTheme=\"outline\"></nz-icon>\r\n 刷新\r\n </button>\r\n <button\r\n nz-button\r\n [disabled]=\"!hasUnsavedChanges()\"\r\n nzType=\"default\"\r\n nz-popconfirm\r\n nzPopconfirmTitle=\"确认还原为当前已加载版本?\"\r\n nzPopconfirmPlacement=\"top\"\r\n (nzOnConfirm)=\"restoreFromOriginal()\"\r\n >\r\n <nz-icon nzType=\"rollback\" nzTheme=\"outline\"></nz-icon>\r\n 还原\r\n </button>\r\n <button nz-button (click)=\"validateConfig()\" [nzLoading]=\"validating()\">\r\n <nz-icon nzType=\"check-circle\" nzTheme=\"outline\"></nz-icon>\r\n 验证\r\n </button>\r\n <button\r\n nz-button\r\n nzType=\"primary\"\r\n (click)=\"saveConfig()\"\r\n [nzLoading]=\"saving()\"\r\n [disabled]=\"!config()?.isWritable\"\r\n >\r\n <nz-icon nzType=\"save\" nzTheme=\"outline\"></nz-icon>\r\n 保存\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"editor-container\" #editorShell>\r\n <nz-code-editor\r\n class=\"code-editor\"\r\n [style.height.px]=\"editorHeight()\"\r\n nzEditorMode=\"diff\"\r\n [ngModel]=\"editorContent()\"\r\n (ngModelChange)=\"onEditorContentChange($event)\"\r\n [nzOriginalText]=\"originalContent()\"\r\n [nzEditorOption]=\"editorOptions\"\r\n [nzLoading]=\"loading()\"\r\n (nzEditorInitialized)=\"onCodeEditorInitialized($event)\"\r\n ></nz-code-editor>\r\n </div>\r\n\r\n @if (validationResult()) {\r\n <div class=\"validation-result\" [class.error]=\"!validationResult()?.valid\">\r\n @if (validationResult()?.valid) {\r\n <nz-icon nzType=\"check-circle\" nzTheme=\"outline\" class=\"success-icon\"></nz-icon>\r\n <span>配置验证通过</span>\r\n } @else {\r\n <nz-icon nzType=\"close-circle\" nzTheme=\"outline\" class=\"error-icon\"></nz-icon>\r\n <span>配置验证失败</span>\r\n }\r\n\r\n @if (validationResult()?.errors?.length) {\r\n <div class=\"error-list\">\r\n @for (error of validationResult()?.errors; track $index) {\r\n <div class=\"error-item\">{{ error }}</div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (validationResult()?.warnings?.length) {\r\n <div class=\"warning-list\">\r\n @for (warning of validationResult()?.warnings; track $index) {\r\n <div class=\"warning-item\">{{ warning }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n `,\r\n styles: [`\r\n .config-editor {\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n border: 1px solid #f0f0f0;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n }\r\n\r\n .editor-toolbar {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: flex-start;\r\n gap: 12px;\r\n padding: 12px 16px;\r\n background: #fafafa;\r\n border-bottom: 1px solid #f0f0f0;\r\n\r\n .toolbar-left {\r\n min-width: 0;\r\n flex: 1;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n flex-wrap: wrap;\r\n gap: 10px;\r\n }\r\n\r\n .file-info {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n font-size: var(--nginx-font-size-base, 14px);\r\n min-width: 0;\r\n\r\n .file-path {\r\n font-family: var(\r\n --nginx-font-family-mono,\r\n ui-monospace,\r\n SFMono-Regular,\r\n Menlo,\r\n Monaco,\r\n Consolas,\r\n 'Liberation Mono',\r\n monospace\r\n );\r\n color: rgba(0, 0, 0, 0.65);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n }\r\n\r\n .file-selector {\r\n width: min(560px, 100%);\r\n }\r\n\r\n .editor-actions {\r\n display: flex;\r\n gap: 8px;\r\n flex-shrink: 0;\r\n }\r\n }\r\n\r\n .editor-container {\r\n flex: 1;\r\n min-height: 520px;\r\n position: relative;\r\n }\r\n\r\n .code-editor {\r\n display: block;\r\n min-height: 520px;\r\n width: 100%;\r\n border: 1px solid #d9d9d9;\r\n }\r\n\r\n .validation-result {\r\n padding: 12px 16px;\r\n background: #f6ffed;\r\n border-top: 1px solid #b7eb8f;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n\r\n &.error {\r\n background: #fff2f0;\r\n border-top-color: #ffccc7;\r\n }\r\n\r\n .success-icon {\r\n color: #52c41a;\r\n }\r\n\r\n .error-icon {\r\n color: #ff4d4f;\r\n }\r\n\r\n .error-list,\r\n .warning-list {\r\n margin-top: 8px;\r\n width: 100%;\r\n\r\n .error-item {\r\n color: #ff4d4f;\r\n font-size: var(--nginx-font-size-sm, 12px);\r\n padding: 4px 0;\r\n font-family: var(\r\n --nginx-font-family-mono,\r\n ui-monospace,\r\n SFMono-Regular,\r\n Menlo,\r\n Monaco,\r\n Consolas,\r\n 'Liberation Mono',\r\n monospace\r\n );\r\n }\r\n\r\n .warning-item {\r\n color: #faad14;\r\n font-size: var(--nginx-font-size-sm, 12px);\r\n padding: 4px 0;\r\n font-family: var(\r\n --nginx-font-family-mono,\r\n ui-monospace,\r\n SFMono-Regular,\r\n Menlo,\r\n Monaco,\r\n Consolas,\r\n 'Liberation Mono',\r\n monospace\r\n );\r\n }\r\n }\r\n }\r\n\r\n :host ::ng-deep nz-code-editor.code-editor.ant-code-editor {\r\n width: 100%;\r\n min-height: 520px !important;\r\n display: block;\r\n }\r\n\r\n :host ::ng-deep nz-code-editor.code-editor .monaco-diff-editor {\r\n min-height: 520px;\r\n }\r\n `],\r\n})\r\nexport class NginxConfigEditorComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy {\r\n @Input() refreshToken = 0;\r\n @ViewChild('editorShell', { static: true }) editorShellRef?: ElementRef<HTMLElement>;\r\n\r\n private nginxService = inject(NginxService);\r\n private message = inject(NzMessageService);\r\n\r\n config = signal<NginxConfig | null>(null);\r\n configFiles = signal<string[]>([]);\r\n selectedFilePath = signal('');\r\n originalContent = signal('');\r\n editorContent = signal('');\r\n filesLoading = signal(false);\r\n loading = signal(true);\r\n saving = signal(false);\r\n validating = signal(false);\r\n validationResult = signal<{ valid: boolean; errors?: string[]; warnings?: string[] } | null>(null);\r\n editorHeight = signal(520);\r\n\r\n editorOptions: editor.IStandaloneEditorConstructionOptions & editor.IDiffEditorConstructionOptions = {\r\n language: 'nginx',\r\n // automaticLayout: true,\r\n // renderSideBySide: false,\r\n // ignoreTrimWhitespace: false,\r\n // originalEditable: false,\r\n // enableSplitViewResizing: true,\r\n // lineNumbers: 'on',\r\n // minimap: { enabled: false },\r\n // scrollBeyondLastLine: false,\r\n };\r\n\r\n private mainConfigPath = '';\r\n private diffEditor: editor.IStandaloneDiffEditor | null = null;\r\n private resizeObserver: ResizeObserver | null = null;\r\n\r\n async ngOnInit() {\r\n await this.loadConfig(true);\r\n await this.loadConfigFiles();\r\n }\r\n\r\n ngAfterViewInit(): void {\r\n this.setupEditorHeightObserver();\r\n this.updateEditorHeight();\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges): void {\r\n const tokenChange = changes['refreshToken'];\r\n if (tokenChange && !tokenChange.firstChange) {\r\n void this.loadConfigFiles();\r\n }\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.resizeObserver?.disconnect();\r\n this.resizeObserver = null;\r\n }\r\n\r\n onCodeEditorInitialized(\r\n editorInstance: editor.IStandaloneCodeEditor | editor.IStandaloneDiffEditor,\r\n ): void {\r\n registerNginxLanguage();\r\n // const monacoRef = (globalThis as any).monaco;\r\n // monacoRef?.editor?.setTheme?.('nginx-theme');\r\n if (this.isDiffEditor(editorInstance)) {\r\n this.diffEditor = editorInstance;\r\n }\r\n\r\n this.scheduleLayout();\r\n }\r\n\r\n onEditorContentChange(value: string): void {\r\n this.editorContent.set(String(value ?? ''));\r\n }\r\n\r\n async loadConfig(forceMain = false) {\r\n this.loading.set(true);\r\n try {\r\n const targetPath = this.selectedFilePath();\r\n const useMain = forceMain || !targetPath || this.isSamePath(targetPath, this.mainConfigPath || targetPath);\r\n const res = useMain ? await this.nginxService.getConfig() : await this.nginxService.getConfigFile(targetPath);\r\n if (res.success && res.config) {\r\n const content = res.config.content || '';\r\n this.config.set(res.config);\r\n this.originalContent.set(content);\r\n this.editorContent.set(content);\r\n this.validationResult.set(null);\r\n this.selectedFilePath.set(res.config.mainConfigPath || targetPath);\r\n if (useMain || !this.mainConfigPath) {\r\n this.mainConfigPath = res.config.mainConfigPath;\r\n }\r\n this.mergeConfigFiles([res.config.mainConfigPath]);\r\n this.updateEditorHeight();\r\n this.scheduleLayout();\r\n } else {\r\n this.message.error(res.error || '加载配置失败');\r\n }\r\n } catch (err: any) {\r\n this.message.error('加载配置失败: ' + err.message);\r\n } finally {\r\n this.loading.set(false);\r\n }\r\n }\r\n\r\n async saveConfig() {\r\n const targetPath = this.selectedFilePath() || this.config()?.mainConfigPath || '';\r\n if (!targetPath) {\r\n this.message.error('未选择配置文件');\r\n return;\r\n }\r\n\r\n this.saving.set(true);\r\n try {\r\n const latestContent = this.getCurrentEditorContent();\r\n const useMain = this.isSamePath(targetPath, this.mainConfigPath || targetPath);\r\n const res = useMain\r\n ? await this.nginxService.updateConfig(latestContent)\r\n : await this.nginxService.updateConfigFile(targetPath, latestContent);\r\n if (res.success) {\r\n this.message.success('保存成功');\r\n await this.loadConfig();\r\n await this.loadConfigFiles();\r\n } else {\r\n this.message.error(res.error || '保存失败');\r\n }\r\n } catch (err: any) {\r\n this.message.error('保存失败: ' + err.message);\r\n } finally {\r\n this.saving.set(false);\r\n }\r\n }\r\n\r\n async validateConfig() {\r\n this.validating.set(true);\r\n try {\r\n const res = await this.nginxService.validateConfig(this.getCurrentEditorContent());\r\n this.validationResult.set(res);\r\n if (res.valid) {\r\n this.message.success('配置验证通过');\r\n } else {\r\n this.message.error('配置验证失败');\r\n }\r\n } catch (err: any) {\r\n this.message.error('验证失败: ' + err.message);\r\n } finally {\r\n this.validating.set(false);\r\n }\r\n }\r\n\r\n async loadConfigFiles() {\r\n this.filesLoading.set(true);\r\n try {\r\n const res = await this.nginxService.getConfigFiles();\r\n if (res.success) {\r\n const incoming = res.files || [];\r\n const merged = this.buildFileList(incoming);\r\n this.configFiles.set(merged);\r\n const current = this.selectedFilePath();\r\n if (current && merged.length && !merged.some(item => this.isSamePath(item, current))) {\r\n this.selectedFilePath.set(merged[0]);\r\n await this.loadConfig();\r\n }\r\n } else {\r\n this.message.error(res.error || '加载文件列表失败');\r\n }\r\n } catch (err: any) {\r\n this.message.error('加载文件列表失败: ' + err.message);\r\n } finally {\r\n this.filesLoading.set(false);\r\n }\r\n }\r\n\r\n async onSelectConfigFile(filePath: string): Promise<void> {\r\n const target = String(filePath || '').trim();\r\n if (!target || this.isSamePath(target, this.selectedFilePath())) {\r\n return;\r\n }\r\n this.selectedFilePath.set(target);\r\n await this.loadConfig();\r\n }\r\n\r\n getFileLabel(filePath: string): string {\r\n const normalized = String(filePath || '').replace(/\\\\/g, '/');\r\n const parts = normalized.split('/');\r\n const fileName = parts[parts.length - 1] || filePath;\r\n return fileName;\r\n }\r\n\r\n hasUnsavedChanges(): boolean {\r\n return this.getCurrentEditorContent() !== this.originalContent();\r\n }\r\n\r\n restoreFromOriginal(): void {\r\n const original = this.originalContent();\r\n const modified = this.diffEditor?.getModel()?.modified;\r\n if (modified) {\r\n modified.setValue(original);\r\n }\r\n this.editorContent.set(original);\r\n this.validationResult.set(null);\r\n this.scheduleLayout();\r\n this.message.success('已还原到已加载版本');\r\n }\r\n\r\n private getCurrentEditorContent(): string {\r\n const modified = this.diffEditor?.getModel()?.modified;\r\n if (modified) {\r\n return modified.getValue();\r\n }\r\n return this.editorContent();\r\n }\r\n\r\n private mergeConfigFiles(paths: string[]): void {\r\n const next = this.buildFileList([...this.configFiles(), ...paths]);\r\n this.configFiles.set(next);\r\n }\r\n\r\n private buildFileList(paths: string[]): string[] {\r\n const map = new Map<string, string>();\r\n for (const path of paths) {\r\n const key = this.normalizePath(path);\r\n if (!key) {\r\n continue;\r\n }\r\n if (!map.has(key)) {\r\n map.set(key, path);\r\n }\r\n }\r\n\r\n const list = Array.from(map.values());\r\n list.sort((a, b) => a.localeCompare(b));\r\n\r\n if (this.mainConfigPath) {\r\n const index = list.findIndex(item => this.isSamePath(item, this.mainConfigPath));\r\n if (index > 0) {\r\n const [main] = list.splice(index, 1);\r\n list.unshift(main);\r\n }\r\n if (index < 0) {\r\n list.unshift(this.mainConfigPath);\r\n }\r\n }\r\n\r\n return list;\r\n }\r\n\r\n private isSamePath(a: string, b: string): boolean {\r\n return this.normalizePath(a) === this.normalizePath(b);\r\n }\r\n\r\n private normalizePath(input: string): string {\r\n return String(input || '').trim().replace(/\\\\/g, '/').toLowerCase();\r\n }\r\n\r\n private scheduleLayout(): void {\r\n setTimeout(() => {\r\n try {\r\n this.updateEditorHeight();\r\n this.syncDiffEditorHeight();\r\n this.diffEditor?.layout();\r\n } catch {\r\n // ignore layout error\r\n }\r\n }, 0);\r\n }\r\n\r\n private setupEditorHeightObserver(): void {\r\n const host = this.editorShellRef?.nativeElement;\r\n if (!host || typeof ResizeObserver === 'undefined') {\r\n return;\r\n }\r\n\r\n this.resizeObserver?.disconnect();\r\n this.resizeObserver = new ResizeObserver(() => {\r\n this.updateEditorHeight();\r\n setTimeout(() => {\r\n this.syncDiffEditorHeight();\r\n this.diffEditor?.layout();\r\n }, 0);\r\n });\r\n this.resizeObserver.observe(host);\r\n }\r\n\r\n private updateEditorHeight(): void {\r\n const host = this.editorShellRef?.nativeElement;\r\n if (!host) {\r\n this.editorHeight.set(520);\r\n return;\r\n }\r\n\r\n const measured = host.clientHeight > 0 ? host.clientHeight : 0;\r\n const next = Math.max(520, measured);\r\n if (this.editorHeight() !== next) {\r\n this.editorHeight.set(next);\r\n }\r\n }\r\n\r\n private syncDiffEditorHeight(): void {\r\n const container = this.diffEditor?.getContainerDomNode();\r\n if (!container) {\r\n return;\r\n }\r\n\r\n const heightPx = `${this.editorHeight()}px`;\r\n container.style.height = heightPx;\r\n const diffDom = container.querySelector('.monaco-diff-editor') as HTMLElement | null;\r\n if (!diffDom) {\r\n return;\r\n }\r\n diffDom.style.height = heightPx;\r\n diffDom.style.minHeight = '520px';\r\n }\r\n\r\n private isDiffEditor(\r\n editorInstance: editor.IStandaloneCodeEditor | editor.IStandaloneDiffEditor,\r\n ): editorInstance is editor.IStandaloneDiffEditor {\r\n return typeof (editorInstance as editor.IStandaloneDiffEditor).getOriginalEditor === 'function';\r\n }\r\n}\r\n", "/* angular:styles/component:less;b3096ebdccdca035783796d3b68baf57245add19ad24976e1a6210c236808f2e;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-logs-tab\\nginx-secondary-logs-tab.component.ts */\n:host {\n display: block;\n}\n.panel-header-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n.panel-header-row.compact {\n margin-bottom: 10px;\n}\n.log-tabs {\n display: flex;\n gap: 0;\n}\n.log-tab {\n background: transparent;\n color: var(--text-3);\n font-size: var(--nginx-font-size-sm, 12px);\n font-weight: 500;\n padding: 6px 12px;\n cursor: pointer;\n transition: all 120ms ease;\n border: none;\n border-bottom: 2px solid transparent;\n margin-bottom: -1px;\n}\n.log-tab:hover {\n color: var(--text-2);\n}\n.log-tab.active {\n color: var(--blue);\n border-bottom-color: var(--blue);\n font-weight: 600;\n}\n.log-actions {\n display: flex;\n gap: 4px;\n}\n.log-action-btn nz-icon {\n font-size: 12px;\n}\n/*# sourceMappingURL=nginx-secondary-logs-tab.component.css.map */\n", "/* angular:styles/component:less;2c6128abba9250a43aed06c03b2f2b645b1342fd6ded6acf07230aa9e8a07c7a;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-log-viewer\\nginx-log-viewer.component.ts */\n:host {\n display: block;\n}\n.log-panel {\n background: #1b2332;\n border-radius: 6px;\n overflow: hidden;\n}\n.log-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 14px;\n background: rgba(0, 0, 0, 0.15);\n}\n.log-header-title {\n font-size: var(--nginx-font-size-sm, 12px);\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: rgba(255, 255, 255, 0.4);\n}\n.log-header-status {\n font-size: var(--nginx-font-size-sm, 12px);\n color: rgba(255, 255, 255, 0.5);\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.log-header-status.realtime {\n color: #3dd68c;\n}\n.log-header-status.status-ok {\n color: #3dd68c;\n}\n.log-header-status.status-warn {\n color: #f7ba1e;\n}\n.log-header-status.status-error {\n color: #ef5350;\n}\n.realtime-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #3dd68c;\n animation: pulse 2s ease-in-out infinite;\n}\n@keyframes pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.4;\n }\n}\n.log-body {\n padding: 10px 14px;\n max-height: 180px;\n overflow-y: auto;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n font-size: var(--nginx-font-size-sm, 12px);\n line-height: 1.8;\n}\n.log-body::-webkit-scrollbar {\n width: 6px;\n}\n.log-body::-webkit-scrollbar-track {\n background: transparent;\n}\n.log-body::-webkit-scrollbar-thumb {\n background: #333944;\n border-radius: 3px;\n}\n.log-empty {\n padding: 16px 0;\n font-size: var(--nginx-font-size-sm, 12px);\n color: rgba(255, 255, 255, 0.35);\n text-align: center;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n}\n.log-line {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n cursor: default;\n}\n.log-time {\n color: rgba(255, 255, 255, 0.5);\n flex-shrink: 0;\n min-width: 46px;\n}\n.log-level {\n font-weight: 700;\n flex-shrink: 0;\n width: 32px;\n}\n.log-level.info {\n color: #165dff;\n}\n.log-level.warn {\n color: #ff7d00;\n}\n.log-level.error {\n color: #ef5350;\n}\n.log-level.ok {\n color: #3dd68c;\n}\n.log-msg {\n color: rgba(255, 255, 255, 0.55);\n flex: 1;\n word-break: break-all;\n white-space: pre-wrap;\n}\n/*# sourceMappingURL=nginx-log-viewer.component.css.map */\n", "import { CommonModule } from '@angular/common';\nimport { Component, ElementRef, Input, ViewChild } from '@angular/core';\nimport { NzIconModule } from 'ng-zorro-antd/icon';\n\nexport interface LogEntry {\n time: string;\n level: 'info' | 'warn' | 'error' | 'ok';\n msg: string;\n}\n\n@Component({\n selector: 'app-nginx-log-viewer',\n standalone: true,\n imports: [CommonModule, NzIconModule],\n template: `\n <div class=\"log-panel\">\n <div class=\"log-header\">\n <span class=\"log-header-title\">{{ title }}</span>\n <span\n class=\"log-header-status\"\n [class.realtime]=\"showRealtime\"\n [class.status-ok]=\"statusTone === 'ok'\"\n [class.status-warn]=\"statusTone === 'warn'\"\n [class.status-error]=\"statusTone === 'error'\"\n >\n @if (showRealtime) {\n <span class=\"realtime-dot\"></span>\n 实时\n } @else {\n {{ status }}\n }\n </span>\n </div>\n <div class=\"log-body\" [style.maxHeight.px]=\"maxHeight\" #logBody>\n @if (logs.length === 0) {\n <div class=\"log-empty\">暂无日志</div>\n }\n @for (log of logs; track $index) {\n <div class=\"log-line\">\n <span class=\"log-time\">{{ log.time }}</span>\n <span class=\"log-level\" [class]=\"getLevelClass(log.level)\">\n {{ getLevelLabel(log.level) }}\n </span>\n <span class=\"log-msg\">{{ log.msg }}</span>\n </div>\n }\n </div>\n </div>\n `,\n styles: `\n :host {\n display: block;\n }\n\n .log-panel {\n background: #1b2332;\n border-radius: 6px;\n overflow: hidden;\n }\n\n .log-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 14px;\n background: rgba(0, 0, 0, 0.15);\n }\n\n .log-header-title {\n font-size: var(--nginx-font-size-sm, 12px);\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: rgba(255, 255, 255, 0.4);\n }\n\n .log-header-status {\n font-size: var(--nginx-font-size-sm, 12px);\n color: rgba(255, 255, 255, 0.5);\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 5px;\n\n &.realtime {\n color: #3dd68c;\n }\n\n &.status-ok {\n color: #3dd68c;\n }\n\n &.status-warn {\n color: #f7ba1e;\n }\n\n &.status-error {\n color: #ef5350;\n }\n }\n\n .realtime-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #3dd68c;\n animation: pulse 2s ease-in-out infinite;\n }\n\n @keyframes pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n\n 50% {\n opacity: 0.4;\n }\n }\n\n .log-body {\n padding: 10px 14px;\n max-height: 180px;\n overflow-y: auto;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n font-size: var(--nginx-font-size-sm, 12px);\n line-height: 1.8;\n\n &::-webkit-scrollbar {\n width: 6px;\n }\n\n &::-webkit-scrollbar-track {\n background: transparent;\n }\n\n &::-webkit-scrollbar-thumb {\n background: #333944;\n border-radius: 3px;\n }\n }\n\n .log-empty {\n padding: 16px 0;\n font-size: var(--nginx-font-size-sm, 12px);\n color: rgba(255, 255, 255, 0.35);\n text-align: center;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n }\n\n .log-line {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n cursor: default;\n }\n\n .log-time {\n color: rgba(255, 255, 255, 0.5);\n flex-shrink: 0;\n min-width: 46px;\n }\n\n .log-level {\n font-weight: 700;\n flex-shrink: 0;\n width: 32px;\n\n &.info {\n color: #165dff;\n }\n\n &.warn {\n color: #ff7d00;\n }\n\n &.error {\n color: #ef5350;\n }\n\n &.ok {\n color: #3dd68c;\n }\n }\n\n .log-msg {\n color: rgba(255, 255, 255, 0.55);\n flex: 1;\n word-break: break-all;\n white-space: pre-wrap;\n }\n `,\n})\nexport class NginxLogViewerComponent {\n @Input() title: string = 'error.log';\n @Input() status: string = '';\n @Input() statusTone: 'default' | 'ok' | 'warn' | 'error' = 'default';\n @Input() showRealtime: boolean = false;\n @Input() maxHeight: number = 0;\n @Input() logs: LogEntry[] = [];\n\n @ViewChild('logBody') logBody!: ElementRef<HTMLDivElement>;\n\n scrollToBottom() {\n if (this.logBody) {\n this.logBody.nativeElement.scrollTop = this.logBody.nativeElement.scrollHeight;\n }\n }\n\n getLevelClass(level: string): string {\n return level;\n }\n\n getLevelLabel(level: string): string {\n const map: Record<string, string> = {\n info: 'INFO',\n warn: 'WARN',\n error: 'ERR',\n ok: 'OK',\n };\n return map[level] ?? level.toUpperCase();\n }\n}\n\n\n", "import { CommonModule } from '@angular/common';\nimport { Component, OnDestroy, OnInit, ViewChild, inject, signal } from '@angular/core';\nimport { Subscription, filter } from 'rxjs';\nimport { NzMessageService } from 'ng-zorro-antd/message';\nimport { NzButtonModule } from 'ng-zorro-antd/button';\nimport { NzIconModule } from 'ng-zorro-antd/icon';\n\nimport { LogEntry, NginxLogViewerComponent } from '../../nginx-log-viewer/nginx-log-viewer.component';\nimport { NginxService } from '../../../services/nginx.service';\nimport { WsClientService } from '@app/core/ws';\nimport type { NginxLogAppendMsg, NginxLogTailMsg, NginxLogType } from '@app/core/ws/ws.types';\n\n@Component({\n selector: 'app-nginx-secondary-logs-tab',\n standalone: true,\n imports: [CommonModule, NzButtonModule, NzIconModule, NginxLogViewerComponent],\n template: `\n <div class=\"panel-header-row compact\">\n <div class=\"log-tabs\">\n <button class=\"log-tab\" [class.active]=\"activeLogTab() === 'error'\" (click)=\"switchLogTab('error')\">\n error.log\n </button>\n <button class=\"log-tab\" [class.active]=\"activeLogTab() === 'access'\" (click)=\"switchLogTab('access')\">\n access.log\n </button>\n </div>\n <div class=\"log-actions\">\n <button nz-button nzType=\"default\" class=\"log-action-btn\" (click)=\"scrollToBottom()\">\n <nz-icon nzType=\"vertical-align-bottom\" nzTheme=\"outline\" />\n </button>\n <button nz-button nzType=\"default\" (click)=\"refreshLogs()\">\n <nz-icon nzType=\"reload\" nzTheme=\"outline\" />\n </button>\n <button nz-button nzType=\"default\" class=\"log-action-btn\" (click)=\"clearLogs()\">\n <nz-icon nzType=\"delete\" nzTheme=\"outline\" />\n </button>\n </div>\n </div>\n <app-nginx-log-viewer\n [title]=\"activeLogTab() === 'error' ? 'error.log' : 'access.log'\"\n [showRealtime]=\"isConnected()\"\n [status]=\"statusText()\"\n [statusTone]=\"statusTone()\"\n [logs]=\"currentLogs()\"\n [maxHeight]=\"400\"\n ></app-nginx-log-viewer>\n `,\n styles: [`\n :host {\n display: block;\n }\n\n .panel-header-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n\n &.compact {\n margin-bottom: 10px;\n }\n }\n\n .log-tabs {\n display: flex;\n gap: 0;\n }\n\n .log-tab {\n background: transparent;\n color: var(--text-3);\n font-size: var(--nginx-font-size-sm, 12px);\n font-weight: 500;\n padding: 6px 12px;\n cursor: pointer;\n transition: all 120ms ease;\n border: none;\n border-bottom: 2px solid transparent;\n margin-bottom: -1px;\n\n &:hover {\n color: var(--text-2);\n }\n\n &.active {\n color: var(--blue);\n border-bottom-color: var(--blue);\n font-weight: 600;\n }\n }\n\n .log-actions {\n display: flex;\n gap: 4px;\n }\n\n .log-action-btn {\n nz-icon {\n font-size: 12px;\n }\n }\n `],\n})\nexport class NginxSecondaryLogsTabComponent implements OnInit, OnDestroy {\n private nginxService = inject(NginxService);\n private wsClient = inject(WsClientService);\n private message = inject(NzMessageService);\n private wsSub?: Subscription;\n @ViewChild(NginxLogViewerComponent)\n private logViewerComponent: NginxLogViewerComponent | undefined;\n\n activeLogTab = signal<NginxLogType>('error');\n isConnected = signal(false);\n loading = signal(false);\n\n // 分别存储 error 和 access 日志\n errorLogs = signal<LogEntry[]>([]);\n accessLogs = signal<LogEntry[]>([]);\n\n // 当前显示的日志\n currentLogs = signal<LogEntry[]>([]);\n\n // 最大日志行数限制\n private readonly maxLogLines = 500;\n\n statusText = signal('未连接');\n statusTone = signal<'default' | 'ok' | 'warn' | 'error'>('default');\n\n ngOnInit() {\n this.setupWsSubscription();\n this.loadInitialLogs();\n }\n\n ngOnDestroy() {\n this.unsubscribeWs();\n this.wsSub?.unsubscribe();\n }\n\n switchLogTab(tab: NginxLogType) {\n this.activeLogTab.set(tab);\n this.updateCurrentLogs();\n\n // 切换 tab 时重新订阅\n if (this.isConnected()) {\n this.unsubscribeWs();\n this.subscribeWs(tab);\n }\n }\n\n /**\n * 滚动日志视图到底部\n */\n scrollToBottom() {\n this.logViewerComponent?.scrollToBottom();\n }\n\n async refreshLogs() {\n await this.loadInitialLogs();\n this.message.success('日志已刷新');\n }\n\n clearLogs() {\n const tab = this.activeLogTab();\n if (tab === 'error') {\n this.errorLogs.set([]);\n } else {\n this.accessLogs.set([]);\n }\n this.updateCurrentLogs();\n }\n\n private setupWsSubscription() {\n // 监听 WebSocket 连接状态\n this.wsClient.stateChanges().subscribe(state => {\n const connected = state === 'open';\n this.isConnected.set(connected);\n this.statusText.set(connected ? '已连接' : state === 'connecting' ? '连接中...' : '未连接');\n this.statusTone.set(connected ? 'ok' : 'default');\n\n if (connected) {\n this.subscribeWs(this.activeLogTab());\n }\n });\n\n // 监听 nginx 日志消息\n this.wsSub = this.wsClient.messages()\n .pipe(\n filter((msg): msg is NginxLogAppendMsg | NginxLogTailMsg =>\n msg.op === 'nginx.log.append' || msg.op === 'nginx.log.tail'\n )\n )\n .subscribe(msg => {\n if (msg.op === 'nginx.log.tail') {\n this.handleLogTail(msg);\n setTimeout(() => {\n this.scrollToBottom();\n }, 100);\n } else if (msg.op === 'nginx.log.append') {\n this.handleLogAppend(msg);\n }\n });\n\n // 确保 WebSocket 连接\n this.wsClient.connect();\n }\n\n private subscribeWs(logType: NginxLogType) {\n this.wsClient.send({\n op: 'sub',\n topic: 'nginx',\n logType,\n tail: 50\n });\n }\n\n private unsubscribeWs() {\n this.wsClient.send({\n op: 'unsub',\n topic: 'nginx'\n });\n }\n\n private handleLogTail(msg: NginxLogTailMsg) {\n const entries = msg.lines.map(line => this.parseLogLine(line));\n if (msg.logType === 'error') {\n this.errorLogs.set(entries);\n } else {\n this.accessLogs.set(entries);\n }\n this.updateCurrentLogs();\n }\n\n private handleLogAppend(msg: NginxLogAppendMsg) {\n const entry = this.parseLogLine(msg.line);\n const isCurrentTab = msg.logType === this.activeLogTab();\n\n if (msg.logType === 'error') {\n this.errorLogs.update(logs => {\n const updated = [...logs, entry];\n return updated.length > this.maxLogLines ? updated.slice(-this.maxLogLines) : updated;\n });\n } else {\n this.accessLogs.update(logs => {\n const updated = [...logs, entry];\n return updated.length > this.maxLogLines ? updated.slice(-this.maxLogLines) : updated;\n });\n }\n\n if (isCurrentTab) {\n this.updateCurrentLogs();\n }\n }\n\n private updateCurrentLogs() {\n const tab = this.activeLogTab();\n const logs = tab === 'error' ? this.errorLogs() : this.accessLogs();\n this.currentLogs.set(logs);\n }\n\n private async loadInitialLogs() {\n this.loading.set(true);\n try {\n const [errorRes, accessRes] = await Promise.all([\n this.nginxService.getErrorLogs(100),\n this.nginxService.getAccessLogs(100)\n ]);\n\n if (errorRes.success && errorRes.lines) {\n this.errorLogs.set(errorRes.lines.map((line: string) => this.parseLogLine(line)));\n }\n\n if (accessRes.success && accessRes.lines) {\n this.accessLogs.set(accessRes.lines.map((line: string) => this.parseLogLine(line)));\n }\n\n this.updateCurrentLogs();\n } catch (err: any) {\n this.message.error('加载日志失败: ' + err.message);\n } finally {\n this.loading.set(false);\n }\n }\n\n private parseLogLine(line: string): LogEntry {\n // 解析 nginx 日志行,提取时间和级别\n const timeMatch = line.match(/^(\\d{4}\\/\\d{2}\\/\\d{2}\\s+\\d{2}:\\d{2}:\\d{2})/);\n const levelMatch = line.match(/\\[(error|warn|notice|info|crit|alert|emerg)\\]/i);\n\n let time = '';\n if (timeMatch) {\n time = timeMatch[1].split(/\\s+/)[1] || timeMatch[1];\n } else {\n // 尝试其他格式\n const altTimeMatch = line.match(/(\\d{2}:\\d{2}:\\d{2})/);\n time = altTimeMatch ? altTimeMatch[1] : new Date().toLocaleTimeString('zh-CN', { hour12: false });\n }\n\n let level: LogEntry['level'] = 'info';\n if (levelMatch) {\n const lvl = levelMatch[1].toLowerCase();\n if (lvl === 'error' || lvl === 'crit' || lvl === 'alert' || lvl === 'emerg') {\n level = 'error';\n } else if (lvl === 'warn') {\n level = 'warn';\n }\n }\n\n return { time, level, msg: line };\n }\n}\n", "/* angular:styles/component:less;62bf71d91779f62ccddaefe75011805fcb2e351af3c0f17f5fca16469cc80704;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-perf-tab\\nginx-secondary-perf-tab.component.ts */\n:host {\n display: block;\n}\n.panel-header-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n.panel-tip {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n}\n.setting-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 10px;\n padding: 10px 0;\n border-bottom: 1px solid var(--border-light);\n}\n.setting-row.no-border {\n border-bottom: none;\n}\n.setting-label {\n font-size: var(--nginx-font-size-sm, 12px);\n font-weight: 600;\n color: var(--text-1);\n}\n.setting-desc {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n margin-top: 2px;\n}\n.setting-ctrl {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.setting-input {\n width: 110px;\n padding: 5px 8px;\n border-radius: 4px;\n border: 1px solid var(--border);\n background: var(--bg-input);\n color: var(--text-2);\n font-size: var(--nginx-font-size-sm, 12px);\n text-align: center;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n outline: none;\n}\n.setting-input:focus,\n.setting-textarea:focus {\n border-color: var(--blue);\n box-shadow: 0 0 0 2px var(--blue-border);\n}\n.setting-textarea {\n width: 100%;\n margin-top: 8px;\n border: 1px solid var(--border);\n border-radius: 6px;\n background: var(--bg-input);\n color: var(--text-2);\n font-size: var(--nginx-font-size-sm, 12px);\n padding: 8px 10px;\n resize: vertical;\n outline: none;\n}\n.mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n}\n/*# sourceMappingURL=nginx-secondary-perf-tab.component.css.map */\n", "import { Injectable, signal } from '@angular/core';\nimport type {\n NginxModuleSettings,\n NginxPerformanceConfig,\n NginxSslCertificate,\n NginxTrafficConfig,\n NginxUpstream,\n} from '../models/nginx.types';\nimport { NginxService } from './nginx.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class NginxModuleStore {\n constructor(private nginxService: NginxService) {}\n\n readonly upstreams = signal<NginxUpstream[]>([]);\n readonly sslCertificates = signal<NginxSslCertificate[]>([]);\n readonly trafficConfig = signal<NginxTrafficConfig>({\n rateLimitEnabled: false,\n rateLimit: '',\n connLimitEnabled: false,\n connLimit: 0,\n blacklistIps: [],\n });\n readonly performanceConfig = signal<NginxPerformanceConfig>({\n gzipEnabled: false,\n gzipTypes: '',\n keepaliveTimeout: '',\n workerProcesses: '',\n sendfile: false,\n tcpNopush: false,\n });\n readonly moduleSettings = signal<NginxModuleSettings>({\n backupRetention: 5,\n configBackupRetention: 20,\n });\n\n readonly upstreamsLoading = signal(false);\n readonly sslLoading = signal(false);\n readonly trafficLoading = signal(false);\n readonly performanceLoading = signal(false);\n readonly settingsLoading = signal(false);\n\n async loadUpstreams() {\n this.upstreamsLoading.set(true);\n try {\n const res = await this.nginxService.getUpstreams();\n if (res.success && res.upstreams) {\n this.upstreams.set(res.upstreams);\n }\n return res;\n } finally {\n this.upstreamsLoading.set(false);\n }\n }\n\n async saveUpstreams(upstreams: NginxUpstream[]) {\n const res = await this.nginxService.saveUpstreams(upstreams);\n if (res.success) {\n this.upstreams.set(upstreams.map(item => ({ ...item })));\n }\n return res;\n }\n\n async loadSslCertificates() {\n this.sslLoading.set(true);\n try {\n const res = await this.nginxService.getSslCertificates();\n if (res.success && res.certificates) {\n this.sslCertificates.set(res.certificates.map(item => ({ ...item })));\n }\n return res;\n } finally {\n this.sslLoading.set(false);\n }\n }\n\n async saveSslCertificates(certificates: NginxSslCertificate[]) {\n const res = await this.nginxService.saveSslCertificates(certificates);\n if (res.success) {\n this.sslCertificates.set(certificates.map(item => ({ ...item })));\n }\n return res;\n }\n\n async loadTrafficConfig() {\n this.trafficLoading.set(true);\n try {\n const res = await this.nginxService.getTrafficConfig();\n if (res.success && res.traffic) {\n this.trafficConfig.set({\n ...res.traffic,\n connLimit: Math.max(0, Number(res.traffic.connLimit ?? 0)),\n });\n }\n return res;\n } finally {\n this.trafficLoading.set(false);\n }\n }\n\n async saveTrafficConfig(traffic: NginxTrafficConfig) {\n const res = await this.nginxService.saveTrafficConfig(traffic);\n if (res.success) {\n this.trafficConfig.set({ ...traffic });\n }\n return res;\n }\n\n async loadPerformanceConfig() {\n this.performanceLoading.set(true);\n try {\n const res = await this.nginxService.getPerformanceConfig();\n if (res.success && res.performance) {\n this.performanceConfig.set({\n ...res.performance,\n });\n }\n return res;\n } finally {\n this.performanceLoading.set(false);\n }\n }\n\n async savePerformanceConfig(performance: NginxPerformanceConfig) {\n const res = await this.nginxService.savePerformanceConfig(performance);\n if (res.success) {\n this.performanceConfig.set({ ...performance });\n }\n return res;\n }\n\n async loadModuleSettings() {\n this.settingsLoading.set(true);\n try {\n const res = await this.nginxService.getModuleSettings();\n if (res.success && res.settings) {\n this.moduleSettings.set({\n backupRetention: Math.max(1, Number(res.settings.backupRetention ?? 5)),\n configBackupRetention: Math.max(1, Number(res.settings.configBackupRetention ?? 20)),\n });\n }\n return res;\n } finally {\n this.settingsLoading.set(false);\n }\n }\n\n async saveModuleSettings(settings: Partial<NginxModuleSettings>) {\n const res = await this.nginxService.saveModuleSettings(settings);\n if (res.success && res.settings) {\n this.moduleSettings.set({\n backupRetention: Math.max(1, Number(res.settings.backupRetention ?? 5)),\n configBackupRetention: Math.max(1, Number(res.settings.configBackupRetention ?? 20)),\n });\n }\n return res;\n }\n}\n", "import { CommonModule } from '@angular/common';\nimport { Component, OnInit, inject, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { NzButtonModule } from 'ng-zorro-antd/button';\nimport { NzIconModule } from 'ng-zorro-antd/icon';\nimport { NzMessageService } from 'ng-zorro-antd/message';\n\nimport { NginxModuleStore } from '../../../services/nginx-module.store';\nimport type { NginxPerformanceConfig } from '../../../models/nginx.types';\nimport { NzSwitchModule } from 'ng-zorro-antd/switch';\n\n@Component({\n selector: 'app-nginx-secondary-perf-tab',\n standalone: true,\n imports: [CommonModule, FormsModule, NzButtonModule, NzIconModule, NzSwitchModule],\n template: `\n <div class=\"panel-header-row\">\n <span class=\"panel-tip\">性能优化配置</span>\n <button nz-button nzType=\"primary\" (click)=\"save()\" [nzLoading]=\"saving()\" [disabled]=\"!dirty()\">\n <nz-icon nzType=\"save\" nzTheme=\"outline\"></nz-icon>\n 保存\n </button>\n </div>\n\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">Gzip 压缩</div>\n <div class=\"setting-desc\">是否启用 gzip</div>\n </div>\n <div class=\"setting-ctrl\">\n <nz-switch\n nzSize=\"small\"\n name=\"gzipEnabled\"\n [ngModel]=\"config().gzipEnabled\"\n (ngModelChange)=\"setBoolean('gzipEnabled', $event)\"\n ></nz-switch>\n </div>\n </div>\n\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">Keepalive 超时</div>\n <div class=\"setting-desc\">keepalive_timeout</div>\n </div>\n <div class=\"setting-ctrl\">\n <input\n class=\"setting-input\"\n [(ngModel)]=\"config().keepaliveTimeout\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"65s\"\n />\n </div>\n </div>\n\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">Worker 进程</div>\n <div class=\"setting-desc\">worker_processes</div>\n </div>\n <div class=\"setting-ctrl\">\n <input\n class=\"setting-input\"\n [(ngModel)]=\"config().workerProcesses\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"auto\"\n />\n </div>\n </div>\n\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">sendfile</div>\n <div class=\"setting-desc\">静态文件零拷贝</div>\n </div>\n <div class=\"setting-ctrl\">\n <nz-switch\n nzSize=\"small\"\n name=\"sendfile\"\n [ngModel]=\"config().sendfile\"\n (ngModelChange)=\"setBoolean('sendfile', $event)\"\n ></nz-switch>\n </div>\n </div>\n\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">tcp_nopush</div>\n <div class=\"setting-desc\">优化大文件传输</div>\n </div>\n <div class=\"setting-ctrl\">\n <nz-switch\n nzSize=\"small\"\n name=\"tcpNopush\"\n [ngModel]=\"config().tcpNopush\"\n (ngModelChange)=\"setBoolean('tcpNopush', $event)\"\n ></nz-switch>\n </div>\n </div>\n\n <div class=\"setting-row no-border\">\n <div class=\"full-width\">\n <div class=\"setting-label\">Gzip Types</div>\n <div class=\"setting-desc\">空格分隔 MIME 列表</div>\n <textarea\n class=\"setting-textarea mono\"\n [(ngModel)]=\"config().gzipTypes\"\n (ngModelChange)=\"markDirty()\"\n rows=\"3\"\n ></textarea>\n </div>\n </div>\n `,\n styles: [`\n :host {\n display: block;\n }\n\n .panel-header-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .panel-tip {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n }\n\n .setting-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 10px;\n padding: 10px 0;\n border-bottom: 1px solid var(--border-light);\n\n &.no-border {\n border-bottom: none;\n }\n }\n\n .setting-label {\n font-size: var(--nginx-font-size-sm, 12px);\n font-weight: 600;\n color: var(--text-1);\n }\n\n .setting-desc {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n margin-top: 2px;\n }\n\n .setting-ctrl {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .setting-input {\n width: 110px;\n padding: 5px 8px;\n border-radius: 4px;\n border: 1px solid var(--border);\n background: var(--bg-input);\n color: var(--text-2);\n font-size: var(--nginx-font-size-sm, 12px);\n text-align: center;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n outline: none;\n }\n\n .setting-input:focus,\n .setting-textarea:focus {\n border-color: var(--blue);\n box-shadow: 0 0 0 2px var(--blue-border);\n }\n\n .setting-textarea {\n width: 100%;\n margin-top: 8px;\n border: 1px solid var(--border);\n border-radius: 6px;\n background: var(--bg-input);\n color: var(--text-2);\n font-size: var(--nginx-font-size-sm, 12px);\n padding: 8px 10px;\n resize: vertical;\n outline: none;\n }\n .mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n }\n `],\n})\nexport class NginxSecondaryPerfTabComponent implements OnInit {\n private moduleStore = inject(NginxModuleStore);\n private message = inject(NzMessageService);\n\n saving = signal(false);\n dirty = signal(false);\n config = signal<NginxPerformanceConfig>({\n gzipEnabled: false,\n gzipTypes: '',\n keepaliveTimeout: '',\n workerProcesses: '',\n sendfile: false,\n tcpNopush: false,\n });\n\n async ngOnInit() {\n await this.load();\n }\n\n async load() {\n try {\n const res = await this.moduleStore.loadPerformanceConfig();\n if (res.success && res.performance) {\n this.config.set({\n ...this.moduleStore.performanceConfig(),\n });\n this.dirty.set(false);\n } else {\n this.message.error(res.error || '加载性能配置失败');\n }\n } catch (err: any) {\n this.message.error('加载性能配置失败: ' + err.message);\n }\n }\n\n setBoolean<K extends keyof Pick<NginxPerformanceConfig, 'gzipEnabled' | 'sendfile' | 'tcpNopush'>>(\n key: K,\n value: boolean,\n ) {\n this.config.update(prev => ({\n ...prev,\n [key]: value,\n }));\n this.markDirty();\n }\n\n markDirty() {\n this.dirty.set(true);\n }\n\n async save() {\n const payload: NginxPerformanceConfig = {\n ...this.config(),\n gzipTypes: this.config().gzipTypes.trim(),\n keepaliveTimeout: this.config().keepaliveTimeout.trim(),\n workerProcesses: this.config().workerProcesses.trim(),\n };\n\n this.saving.set(true);\n try {\n const res = await this.moduleStore.savePerformanceConfig(payload);\n if (res.success) {\n this.message.success('性能优化配置已保存');\n this.dirty.set(false);\n await this.load();\n } else {\n this.message.error(res.error || '保存性能配置失败');\n }\n } catch (err: any) {\n this.message.error('保存性能配置失败: ' + err.message);\n } finally {\n this.saving.set(false);\n }\n }\n}\n", "/* angular:styles/component:less;fbfac6a855202f42b3e67e336f2491eb905f2ab84fd62f243dd847b271e5448a;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-settings-tab\\nginx-secondary-settings-tab.component.ts */\n.setting-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 10px;\n padding: 10px 0;\n border-bottom: 1px solid var(--border-light);\n}\n.setting-row:last-child {\n border-bottom: none;\n}\n.setting-row.danger-row {\n padding: 6px 0 0;\n border-bottom: none;\n}\n.setting-label {\n font-size: var(--nginx-font-size-sm, 12px);\n font-weight: 600;\n color: var(--text-1);\n}\n.setting-label.danger {\n color: var(--red);\n margin-bottom: 4px;\n}\n.setting-desc {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n margin-top: 2px;\n}\n.setting-ctrl {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.retention-input {\n width: 92px;\n}\n.mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n}\n.mono.strong {\n color: var(--text-1);\n font-weight: 600;\n}\n.danger-box {\n margin-top: 8px;\n padding: 10px 12px;\n border: 1px solid rgba(245, 63, 63, 0.2);\n background: var(--red-bg);\n border-radius: 6px;\n}\n@media (max-width: 768px) {\n .setting-row {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n/*# sourceMappingURL=nginx-secondary-settings-tab.component.css.map */\n", "import { CommonModule } from '@angular/common';\nimport { Component, EventEmitter, Input, OnInit, Output, inject, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { NzButtonModule } from 'ng-zorro-antd/button';\nimport { NzInputModule } from 'ng-zorro-antd/input';\nimport { NzMessageService } from 'ng-zorro-antd/message';\n\nimport type { NginxInstance } from '../../../models/nginx.types';\nimport { NginxModuleStore } from '../../../services/nginx-module.store';\n\n@Component({\n selector: 'app-nginx-secondary-settings-tab',\n standalone: true,\n imports: [CommonModule, FormsModule, NzButtonModule, NzInputModule],\n template: `\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">Nginx 路径</div>\n <div class=\"setting-desc\">可执行文件位置</div>\n </div>\n <div class=\"setting-ctrl\">\n <span class=\"mono strong\">{{ instance?.path || '-' }}</span>\n </div>\n </div>\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">配置文件</div>\n <div class=\"setting-desc\">主配置路径</div>\n </div>\n <div class=\"setting-ctrl\">\n <span class=\"mono strong\">{{ instance?.configPath || '-' }}</span>\n </div>\n </div>\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">配置文件数</div>\n <div class=\"setting-desc\">根据 include 指令解析</div>\n </div>\n <div class=\"setting-ctrl\">\n <span class=\"mono strong\">{{ configFileCount }}</span>\n </div>\n </div>\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">状态备份保留数</div>\n <div class=\"setting-desc\">.ngm-nginx-module.json.backup-* 最多保留数量</div>\n </div>\n <div class=\"setting-ctrl\">\n <input\n nz-input\n type=\"number\"\n min=\"1\"\n class=\"retention-input mono\"\n [ngModel]=\"backupRetention()\"\n (ngModelChange)=\"setBackupRetention($event)\"\n />\n <button\n nz-button\n nzType=\"default\"\n (click)=\"saveBackupRetention()\"\n [disabled]=\"!retentionDirty() || saving()\"\n >\n 保存\n </button>\n </div>\n </div>\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">配置备份保留数</div>\n <div class=\"setting-desc\">*.conf.backup-* 最多保留数量</div>\n </div>\n <div class=\"setting-ctrl\">\n <input\n nz-input\n type=\"number\"\n min=\"1\"\n class=\"retention-input mono\"\n [ngModel]=\"configBackupRetention()\"\n (ngModelChange)=\"setConfigBackupRetention($event)\"\n />\n <button\n nz-button\n nzType=\"default\"\n (click)=\"saveBackupRetention()\"\n [disabled]=\"!retentionDirty() || saving()\"\n >\n 保存\n </button>\n </div>\n </div>\n <div class=\"danger-box\">\n <div class=\"setting-label danger\">危险操作</div>\n <div class=\"setting-row danger-row\">\n <div>\n <div class=\"setting-label\">解绑实例</div>\n <div class=\"setting-desc\">不影响 Nginx 服务本身</div>\n </div>\n <button nz-button nzDanger nzType=\"default\" (click)=\"unbind.emit()\">\n 解绑\n </button>\n </div>\n </div>\n `,\n styles: [`\n .setting-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 10px;\n padding: 10px 0;\n border-bottom: 1px solid var(--border-light);\n\n &:last-child {\n border-bottom: none;\n }\n\n &.danger-row {\n padding: 6px 0 0;\n border-bottom: none;\n }\n }\n\n .setting-label {\n font-size: var(--nginx-font-size-sm, 12px);\n font-weight: 600;\n color: var(--text-1);\n\n &.danger {\n color: var(--red);\n margin-bottom: 4px;\n }\n }\n\n .setting-desc {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n margin-top: 2px;\n }\n\n .setting-ctrl {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .retention-input {\n width: 92px;\n }\n\n .mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n\n &.strong {\n color: var(--text-1);\n font-weight: 600;\n }\n }\n\n .danger-box {\n margin-top: 8px;\n padding: 10px 12px;\n border: 1px solid rgba(245, 63, 63, 0.2);\n background: var(--red-bg);\n border-radius: 6px;\n }\n\n @media (max-width: 768px) {\n .setting-row {\n flex-direction: column;\n align-items: flex-start;\n }\n }\n `],\n})\nexport class NginxSecondarySettingsTabComponent implements OnInit {\n private moduleStore = inject(NginxModuleStore);\n private message = inject(NzMessageService);\n\n @Input() instance: NginxInstance | null = null;\n @Input() configFileCount = 0;\n @Output() unbind = new EventEmitter<void>();\n\n backupRetention = signal(5);\n configBackupRetention = signal(20);\n retentionDirty = signal(false);\n saving = signal(false);\n\n async ngOnInit(): Promise<void> {\n try {\n const res = await this.moduleStore.loadModuleSettings();\n if (res.success && res.settings) {\n this.backupRetention.set(Math.max(1, Number(res.settings.backupRetention ?? 5)));\n this.configBackupRetention.set(Math.max(1, Number(res.settings.configBackupRetention ?? 20)));\n }\n } catch {\n // 忽略,使用默认值\n }\n }\n\n setBackupRetention(value: number | string): void {\n const parsed = Number(value);\n const normalized = Number.isFinite(parsed) ? Math.max(1, Math.trunc(parsed)) : 1;\n this.backupRetention.set(normalized);\n this.retentionDirty.set(true);\n }\n\n setConfigBackupRetention(value: number | string): void {\n const parsed = Number(value);\n const normalized = Number.isFinite(parsed) ? Math.max(1, Math.trunc(parsed)) : 1;\n this.configBackupRetention.set(normalized);\n this.retentionDirty.set(true);\n }\n\n async saveBackupRetention(): Promise<void> {\n this.saving.set(true);\n try {\n const res = await this.moduleStore.saveModuleSettings({\n backupRetention: this.backupRetention(),\n configBackupRetention: this.configBackupRetention(),\n });\n if (res.success && res.settings) {\n this.backupRetention.set(Math.max(1, Number(res.settings.backupRetention ?? 5)));\n this.configBackupRetention.set(Math.max(1, Number(res.settings.configBackupRetention ?? 20)));\n this.retentionDirty.set(false);\n this.message.success('备份保留数量已保存');\n } else {\n this.message.error(res.error || '保存备份保留数量失败');\n }\n } catch (err: any) {\n this.message.error('保存备份保留数量失败: ' + err.message);\n } finally {\n this.saving.set(false);\n }\n }\n}\n", "/* angular:styles/component:less;e564a99d38a425a93e8df6d9ba8cdf5980494baf3ea6d1f832d04b10cd22736f;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-ssl-tab\\nginx-secondary-ssl-tab.component.ts */\n:host {\n display: block;\n}\n.panel-header-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n.panel-tip {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n}\n.header-actions {\n display: flex;\n gap: 8px;\n}\n.ssl-cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 10px;\n}\n.ssl-card {\n display: flex;\n flex-direction: column;\n gap: 10px;\n border: 1px solid var(--border-light);\n border-radius: 6px;\n padding: 10px;\n background: #fff;\n}\n.card-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n border-bottom: 1px solid var(--border-light);\n padding-bottom: 8px;\n}\n.card-domain {\n font-size: var(--nginx-font-size-base, 14px);\n color: var(--text-1);\n font-weight: 600;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.status-badge {\n display: inline-flex;\n align-items: center;\n height: 24px;\n border-radius: 12px;\n padding: 0 10px;\n font-size: var(--nginx-font-size-sm, 12px);\n line-height: 24px;\n border: 1px solid transparent;\n white-space: nowrap;\n}\n.status-badge.valid {\n color: var(--green);\n border-color: rgba(0, 180, 42, 0.2);\n background: var(--green-bg);\n}\n.status-badge.expiring {\n color: var(--orange);\n border-color: rgba(255, 125, 0, 0.2);\n background: var(--orange-bg);\n}\n.status-badge.expired {\n color: var(--red);\n border-color: rgba(245, 63, 63, 0.2);\n background: var(--red-bg);\n}\n.status-badge.pending {\n color: var(--text-2);\n border-color: var(--border);\n background: var(--bg-input);\n}\n.meta-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1;\n}\n.meta-item {\n display: grid;\n grid-template-columns: 80px 1fr;\n gap: 8px;\n min-width: 0;\n}\n.meta-item.block {\n grid-template-columns: 1fr;\n}\n.meta-label {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n font-weight: 600;\n}\n.meta-value {\n color: var(--text-1);\n font-size: var(--nginx-font-size-sm, 12px);\n min-width: 0;\n word-break: break-all;\n}\n.mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n}\n.card-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.drawer-body {\n padding: 0 24px 16px;\n}\n.form-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n}\n.drawer-footer {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 12px 24px;\n border-top: 1px solid rgba(0, 0, 0, 0.06);\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n background: #fff;\n}\n.empty-row {\n grid-column: 1 / -1;\n border: 1px dashed var(--border);\n border-radius: 6px;\n color: var(--text-3);\n font-size: var(--nginx-font-size-sm, 12px);\n text-align: center;\n padding: 20px 12px;\n background: #fff;\n}\n@media (max-width: 992px) {\n .panel-header-row {\n flex-direction: column;\n align-items: flex-start;\n }\n .header-actions {\n width: 100%;\n }\n .form-grid {\n grid-template-columns: 1fr;\n }\n}\n/*# sourceMappingURL=nginx-secondary-ssl-tab.component.css.map */\n", "import { CommonModule } from '@angular/common';\nimport { Component, OnInit, inject, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { NzButtonModule } from 'ng-zorro-antd/button';\nimport { NzDrawerModule } from 'ng-zorro-antd/drawer';\nimport { NzFormModule } from 'ng-zorro-antd/form';\nimport { NzIconModule } from 'ng-zorro-antd/icon';\nimport { NzInputModule } from 'ng-zorro-antd/input';\nimport { NzMessageService } from 'ng-zorro-antd/message';\nimport { NzSelectModule } from 'ng-zorro-antd/select';\nimport { NzSpinModule } from 'ng-zorro-antd/spin';\nimport { NzSwitchModule } from 'ng-zorro-antd/switch';\n\nimport { NginxModuleStore } from '../../../services/nginx-module.store';\nimport type { NginxSslCertificate, NginxSslStatus } from '../../../models/nginx.types';\n\ninterface SslEditRow extends NginxSslCertificate {}\n\ninterface SslDrawerForm {\n domain: string;\n certPath: string;\n keyPath: string;\n expireAt: string;\n status: NginxSslStatus;\n autoRenew: boolean;\n}\n\n@Component({\n selector: 'app-nginx-secondary-ssl-tab',\n standalone: true,\n imports: [\n CommonModule,\n FormsModule,\n NzButtonModule,\n NzDrawerModule,\n NzFormModule,\n NzIconModule,\n NzInputModule,\n NzSelectModule,\n NzSpinModule,\n NzSwitchModule,\n ],\n template: `\n <div class=\"panel-header-row\">\n <span class=\"panel-tip\">管理 SSL 证书与到期状态</span>\n <div class=\"header-actions\">\n <button nz-button nzType=\"default\" (click)=\"openCreateDrawer()\">\n <nz-icon nzType=\"plus\" nzTheme=\"outline\"></nz-icon>\n 新增证书\n </button>\n <button nz-button nzType=\"primary\" (click)=\"saveAll()\" [nzLoading]=\"saving()\" [disabled]=\"!dirty()\">\n <nz-icon nzType=\"save\" nzTheme=\"outline\"></nz-icon>\n 保存\n </button>\n </div>\n </div>\n\n <nz-spin [nzSpinning]=\"loading()\">\n <div class=\"ssl-cards\">\n @if (!rows().length) {\n <div class=\"empty-row\">暂无 SSL 证书配置,点击“新增证书”开始维护</div>\n } @else {\n @for (row of rows(); track row.id) {\n <div class=\"ssl-card\">\n <div class=\"card-head\">\n <div class=\"card-domain mono\">{{ row.domain }}</div>\n <span class=\"status-badge\" [ngClass]=\"row.status\">{{ statusText(row.status) }}</span>\n </div>\n\n <div class=\"meta-list\">\n <div class=\"meta-item\">\n <span class=\"meta-label\">到期时间</span>\n <span class=\"meta-value mono\">{{ row.expireAt || '-' }}</span>\n </div>\n <div class=\"meta-item\">\n <span class=\"meta-label\">自动续期</span>\n <span class=\"meta-value\">{{ row.autoRenew ? '已启用' : '未启用' }}</span>\n </div>\n <div class=\"meta-item block\">\n <span class=\"meta-label\">证书路径</span>\n <span class=\"meta-value mono\">{{ row.certPath || '-' }}</span>\n </div>\n <div class=\"meta-item block\">\n <span class=\"meta-label\">私钥路径</span>\n <span class=\"meta-value mono\">{{ row.keyPath || '-' }}</span>\n </div>\n </div>\n\n <div class=\"card-actions\">\n <button nz-button nzType=\"default\" nzSize=\"small\" (click)=\"openEditDrawer(row)\">编辑</button>\n <button nz-button nzType=\"default\" nzDanger nzSize=\"small\" (click)=\"removeRow(row.id)\">删除</button>\n </div>\n </div>\n }\n }\n </div>\n </nz-spin>\n\n <nz-drawer\n [nzVisible]=\"drawerVisible()\"\n [nzTitle]=\"editingId() ? '编辑证书' : '新增证书'\"\n [nzWidth]=\"500\"\n [nzPlacement]=\"'right'\"\n (nzOnClose)=\"closeDrawer()\"\n >\n <ng-container *nzDrawerContent>\n <div class=\"drawer-body\">\n <form nz-form nzLayout=\"vertical\">\n <nz-form-item>\n <nz-form-label nzRequired>域名</nz-form-label>\n <nz-form-control>\n <input\n nz-input\n [(ngModel)]=\"drawerForm.domain\"\n name=\"sslDomain\"\n placeholder=\"example.com\"\n class=\"mono\"\n />\n </nz-form-control>\n </nz-form-item>\n\n <div class=\"form-grid\">\n <nz-form-item>\n <nz-form-label>状态</nz-form-label>\n <nz-form-control>\n <nz-select [(ngModel)]=\"drawerForm.status\" name=\"sslStatus\">\n <nz-option nzValue=\"valid\" nzLabel=\"有效\"></nz-option>\n <nz-option nzValue=\"expiring\" nzLabel=\"即将过期\"></nz-option>\n <nz-option nzValue=\"expired\" nzLabel=\"已过期\"></nz-option>\n <nz-option nzValue=\"pending\" nzLabel=\"待接入\"></nz-option>\n </nz-select>\n </nz-form-control>\n </nz-form-item>\n\n <nz-form-item>\n <nz-form-label>到期时间</nz-form-label>\n <nz-form-control>\n <input\n nz-input\n [(ngModel)]=\"drawerForm.expireAt\"\n name=\"sslExpireAt\"\n placeholder=\"YYYY-MM-DD\"\n class=\"mono\"\n />\n </nz-form-control>\n </nz-form-item>\n </div>\n\n <nz-form-item>\n <nz-form-label>自动续期</nz-form-label>\n <nz-form-control>\n <nz-switch [(ngModel)]=\"drawerForm.autoRenew\" name=\"sslAutoRenew\"></nz-switch>\n </nz-form-control>\n </nz-form-item>\n\n <nz-form-item>\n <nz-form-label nzRequired>证书路径</nz-form-label>\n <nz-form-control>\n <input\n nz-input\n [(ngModel)]=\"drawerForm.certPath\"\n name=\"sslCertPath\"\n placeholder=\"/etc/nginx/ssl/fullchain.pem\"\n class=\"mono\"\n />\n </nz-form-control>\n </nz-form-item>\n\n <nz-form-item>\n <nz-form-label nzRequired>私钥路径</nz-form-label>\n <nz-form-control>\n <input\n nz-input\n [(ngModel)]=\"drawerForm.keyPath\"\n name=\"sslKeyPath\"\n placeholder=\"/etc/nginx/ssl/privkey.pem\"\n class=\"mono\"\n />\n </nz-form-control>\n </nz-form-item>\n </form>\n </div>\n\n <div class=\"drawer-footer\">\n <button nz-button nzType=\"default\" (click)=\"closeDrawer()\">取消</button>\n <button nz-button nzType=\"primary\" (click)=\"submitDrawer()\">确定</button>\n </div>\n </ng-container>\n </nz-drawer>\n `,\n styles: [`\n :host {\n display: block;\n }\n\n .panel-header-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .panel-tip {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n }\n\n .header-actions {\n display: flex;\n gap: 8px;\n }\n\n .ssl-cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 10px;\n }\n\n .ssl-card {\n display: flex;\n flex-direction: column;\n gap: 10px;\n border: 1px solid var(--border-light);\n border-radius: 6px;\n padding: 10px;\n background: #fff;\n }\n\n .card-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n border-bottom: 1px solid var(--border-light);\n padding-bottom: 8px;\n }\n\n .card-domain {\n font-size: var(--nginx-font-size-base, 14px);\n color: var(--text-1);\n font-weight: 600;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .status-badge {\n display: inline-flex;\n align-items: center;\n height: 24px;\n border-radius: 12px;\n padding: 0 10px;\n font-size: var(--nginx-font-size-sm, 12px);\n line-height: 24px;\n border: 1px solid transparent;\n white-space: nowrap;\n }\n\n .status-badge.valid {\n color: var(--green);\n border-color: rgba(0, 180, 42, 0.2);\n background: var(--green-bg);\n }\n\n .status-badge.expiring {\n color: var(--orange);\n border-color: rgba(255, 125, 0, 0.2);\n background: var(--orange-bg);\n }\n\n .status-badge.expired {\n color: var(--red);\n border-color: rgba(245, 63, 63, 0.2);\n background: var(--red-bg);\n }\n\n .status-badge.pending {\n color: var(--text-2);\n border-color: var(--border);\n background: var(--bg-input);\n }\n\n .meta-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1;\n }\n\n .meta-item {\n display: grid;\n grid-template-columns: 80px 1fr;\n gap: 8px;\n min-width: 0;\n }\n\n .meta-item.block {\n grid-template-columns: 1fr;\n }\n\n .meta-label {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n font-weight: 600;\n }\n\n .meta-value {\n color: var(--text-1);\n font-size: var(--nginx-font-size-sm, 12px);\n min-width: 0;\n word-break: break-all;\n }\n\n .mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n }\n\n .card-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n }\n\n .drawer-body {\n padding: 0 24px 16px;\n }\n\n .form-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n }\n\n .drawer-footer {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 12px 24px;\n border-top: 1px solid rgba(0, 0, 0, 0.06);\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n background: #fff;\n }\n\n .empty-row {\n grid-column: 1 / -1;\n border: 1px dashed var(--border);\n border-radius: 6px;\n color: var(--text-3);\n font-size: var(--nginx-font-size-sm, 12px);\n text-align: center;\n padding: 20px 12px;\n background: #fff;\n }\n\n @media (max-width: 992px) {\n .panel-header-row {\n flex-direction: column;\n align-items: flex-start;\n }\n\n .header-actions {\n width: 100%;\n }\n\n .form-grid {\n grid-template-columns: 1fr;\n }\n }\n `],\n})\nexport class NginxSecondarySslTabComponent implements OnInit {\n private moduleStore = inject(NginxModuleStore);\n private message = inject(NzMessageService);\n\n loading = signal(false);\n saving = signal(false);\n dirty = signal(false);\n rows = signal<SslEditRow[]>([]);\n drawerVisible = signal(false);\n editingId = signal<string | null>(null);\n\n drawerForm: SslDrawerForm = this.createEmptyForm();\n\n async ngOnInit() {\n await this.loadData();\n }\n\n async loadData() {\n this.loading.set(true);\n try {\n const res = await this.moduleStore.loadSslCertificates();\n if (res.success && res.certificates) {\n this.rows.set(this.moduleStore.sslCertificates().map(item => ({ ...item })));\n this.dirty.set(false);\n } else {\n this.message.error(res.error || '加载 SSL 配置失败');\n }\n } catch (err: any) {\n this.message.error('加载 SSL 配置失败: ' + err.message);\n } finally {\n this.loading.set(false);\n }\n }\n\n openCreateDrawer() {\n this.editingId.set(null);\n this.drawerForm = this.createEmptyForm();\n this.drawerVisible.set(true);\n }\n\n openEditDrawer(row: SslEditRow) {\n this.editingId.set(row.id);\n this.drawerForm = {\n domain: row.domain,\n certPath: row.certPath,\n keyPath: row.keyPath,\n expireAt: row.expireAt,\n status: row.status,\n autoRenew: row.autoRenew,\n };\n this.drawerVisible.set(true);\n }\n\n closeDrawer() {\n this.drawerVisible.set(false);\n }\n\n submitDrawer() {\n const domain = this.drawerForm.domain.trim();\n if (!domain) {\n this.message.warning('证书域名不能为空');\n return;\n }\n\n if (!this.drawerForm.certPath.trim()) {\n this.message.warning('证书路径不能为空');\n return;\n }\n\n if (!this.drawerForm.keyPath.trim()) {\n this.message.warning('私钥路径不能为空');\n return;\n }\n if (this.drawerForm.expireAt.trim() && !this.isValidDateString(this.drawerForm.expireAt.trim())) {\n this.message.warning('到期时间格式无效,请使用 YYYY-MM-DD');\n return;\n }\n\n const normalized: SslEditRow = {\n id: this.editingId() || this.makeId(),\n domain,\n certPath: this.drawerForm.certPath.trim(),\n keyPath: this.drawerForm.keyPath.trim(),\n expireAt: this.drawerForm.expireAt.trim(),\n status: this.normalizeStatus(this.drawerForm.status),\n autoRenew: this.drawerForm.autoRenew,\n };\n\n const duplicate = this.rows().find(item => {\n if (this.editingId() && item.id === this.editingId()) {\n return false;\n }\n return this.buildSslUniqueKey(item) === this.buildSslUniqueKey(normalized);\n });\n if (duplicate) {\n this.message.warning('证书域名 + 证书路径 + 私钥路径重复');\n return;\n }\n\n if (this.editingId()) {\n this.rows.update(rows => rows.map(item => (item.id === normalized.id ? normalized : item)));\n } else {\n this.rows.update(rows => [...rows, normalized]);\n }\n\n this.markDirty();\n this.closeDrawer();\n }\n\n removeRow(id: string) {\n this.rows.update(rows => rows.filter(row => row.id !== id));\n this.markDirty();\n }\n\n markDirty() {\n this.dirty.set(true);\n }\n\n statusText(status: NginxSslStatus): string {\n switch (status) {\n case 'valid':\n return '有效';\n case 'expiring':\n return '即将过期';\n case 'expired':\n return '已过期';\n default:\n return '待接入';\n }\n }\n\n async saveAll() {\n const payload: NginxSslCertificate[] = [];\n const uniqueSet = new Set<string>();\n\n for (const row of this.rows()) {\n const domain = row.domain.trim();\n if (!domain) {\n this.message.warning('证书域名不能为空');\n return;\n }\n const certPath = row.certPath.trim();\n const keyPath = row.keyPath.trim();\n if (!certPath) {\n this.message.warning(`域名 \"${domain}\" 的证书路径不能为空`);\n return;\n }\n if (!keyPath) {\n this.message.warning(`域名 \"${domain}\" 的私钥路径不能为空`);\n return;\n }\n const expireAt = row.expireAt.trim();\n if (expireAt && !this.isValidDateString(expireAt)) {\n this.message.warning(`域名 \"${domain}\" 的到期时间格式无效,请使用 YYYY-MM-DD`);\n return;\n }\n const uniqueKey = this.buildSslUniqueKey({ domain, certPath, keyPath } as SslEditRow);\n if (uniqueSet.has(uniqueKey)) {\n this.message.warning(`检测到重复证书记录: ${domain}`);\n return;\n }\n uniqueSet.add(uniqueKey);\n\n payload.push({\n ...row,\n id: row.id || this.makeId(),\n domain,\n certPath,\n keyPath,\n expireAt,\n status: this.normalizeStatus(row.status),\n });\n }\n\n this.saving.set(true);\n try {\n const res = await this.moduleStore.saveSslCertificates(payload);\n if (res.success) {\n this.message.success('SSL 配置已保存');\n this.dirty.set(false);\n await this.loadData();\n } else {\n this.message.error(res.error || '保存 SSL 配置失败');\n }\n } catch (err: any) {\n this.message.error('保存 SSL 配置失败: ' + err.message);\n } finally {\n this.saving.set(false);\n }\n }\n\n private normalizeStatus(status: NginxSslStatus): NginxSslStatus {\n if (status === 'valid' || status === 'expiring' || status === 'expired' || status === 'pending') {\n return status;\n }\n return 'pending';\n }\n\n private makeId(): string {\n return `ssl-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;\n }\n\n private isValidDateString(value: string): boolean {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n return false;\n }\n const [year, month, day] = value.split('-').map(item => Number(item));\n if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {\n return false;\n }\n const date = new Date(Date.UTC(year, month - 1, day));\n return (\n date.getUTCFullYear() === year &&\n date.getUTCMonth() === month - 1 &&\n date.getUTCDate() === day\n );\n }\n\n private buildSslUniqueKey(input: Pick<SslEditRow, 'domain' | 'certPath' | 'keyPath'>): string {\n return `${input.domain.trim().toLowerCase()}|${input.certPath.trim().toLowerCase()}|${input.keyPath\n .trim()\n .toLowerCase()}`;\n }\n\n private createEmptyForm(): SslDrawerForm {\n return {\n domain: '',\n certPath: '',\n keyPath: '',\n expireAt: '',\n status: 'pending',\n autoRenew: false,\n };\n }\n}\n", "/* angular:styles/component:less;4e4a26faa779f63e11c9d16baaa3bc4dfacf1d8fda3e06af5ec5604e4dfcf2b4;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-test-tab\\nginx-secondary-test-tab.component.ts */\n:host {\n display: block;\n}\n.panel-header-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n.panel-tip {\n font-size: var(--nginx-font-size-base, 14px);\n color: var(--text-2);\n}\n.run-test-btn {\n background: var(--green);\n color: #fff;\n border-color: var(--green);\n font-weight: 600;\n min-height: 28px;\n padding-inline: 10px;\n}\n.run-test-btn:hover,\n.run-test-btn:focus {\n background: #00a122;\n border-color: #00a122;\n color: #fff;\n}\n@media (max-width: 992px) {\n .panel-header-row {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n/*# sourceMappingURL=nginx-secondary-test-tab.component.css.map */\n", "import { CommonModule } from '@angular/common';\nimport { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { NzButtonModule } from 'ng-zorro-antd/button';\nimport { NzIconModule } from 'ng-zorro-antd/icon';\n\nimport { LogEntry, NginxLogViewerComponent } from '../../nginx-log-viewer/nginx-log-viewer.component';\n\n@Component({\n selector: 'app-nginx-secondary-test-tab',\n standalone: true,\n imports: [CommonModule, NzButtonModule, NzIconModule, NginxLogViewerComponent],\n template: `\n <div class=\"panel-header-row\">\n <span class=\"panel-tip\">验证 Nginx 配置文件语法正确性</span>\n <button\n nz-button\n nzType=\"default\"\n nzSize=\"small\"\n class=\"run-test-btn\"\n (click)=\"runTest.emit()\"\n [nzLoading]=\"loading\"\n >\n <nz-icon nzType=\"check-circle\" nzTheme=\"outline\"></nz-icon>\n 执行检测\n </button>\n </div>\n <app-nginx-log-viewer\n [title]=\"'结果'\"\n [status]=\"'✓ syntax is ok'\"\n statusTone=\"ok\"\n [logs]=\"logs\"\n [maxHeight]=\"180\"\n ></app-nginx-log-viewer>\n `,\n styles: [`\n :host {\n display: block;\n }\n\n .panel-header-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .panel-tip {\n font-size: var(--nginx-font-size-base, 14px);\n color: var(--text-2);\n }\n\n .run-test-btn {\n background: var(--green);\n color: #fff;\n border-color: var(--green);\n font-weight: 600;\n min-height: 28px;\n padding-inline: 10px;\n\n &:hover,\n &:focus {\n background: #00a122;\n border-color: #00a122;\n color: #fff;\n }\n }\n\n @media (max-width: 992px) {\n .panel-header-row {\n flex-direction: column;\n align-items: flex-start;\n }\n }\n `],\n})\nexport class NginxSecondaryTestTabComponent {\n @Input() loading = false;\n @Input() logs: LogEntry[] = [];\n @Output() runTest = new EventEmitter<void>();\n}\n\n", "/* angular:styles/component:less;5a71cddcc596825b3daa12a965f8e30bb11a9306650c2cb8ca3f89edaa6ffb1a;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-traffic-tab\\nginx-secondary-traffic-tab.component.ts */\n:host {\n display: block;\n}\n.panel-header-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n.panel-tip {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n}\n.setting-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 10px;\n padding: 10px 0;\n border-bottom: 1px solid var(--border-light);\n}\n.setting-row.no-border {\n border-bottom: none;\n}\n.setting-label {\n font-size: var(--nginx-font-size-sm, 12px);\n font-weight: 600;\n color: var(--text-1);\n}\n.setting-desc {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n margin-top: 2px;\n}\n.setting-ctrl {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.setting-input {\n width: 110px;\n padding: 5px 8px;\n border-radius: 4px;\n border: 1px solid var(--border);\n background: var(--bg-input);\n color: var(--text-2);\n font-size: var(--nginx-font-size-sm, 12px);\n text-align: center;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n outline: none;\n}\n.setting-input:focus,\n.setting-textarea:focus {\n border-color: var(--blue);\n box-shadow: 0 0 0 2px var(--blue-border);\n}\n.setting-textarea {\n width: 100%;\n margin-top: 8px;\n border: 1px solid var(--border);\n border-radius: 6px;\n background: var(--bg-input);\n color: var(--text-2);\n font-size: var(--nginx-font-size-sm, 12px);\n padding: 8px 10px;\n resize: vertical;\n outline: none;\n}\n.mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n}\n/*# sourceMappingURL=nginx-secondary-traffic-tab.component.css.map */\n", "import { CommonModule } from '@angular/common';\nimport { Component, OnInit, inject, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { NzButtonModule } from 'ng-zorro-antd/button';\nimport { NzIconModule } from 'ng-zorro-antd/icon';\nimport { NzMessageService } from 'ng-zorro-antd/message';\n\nimport { NginxModuleStore } from '../../../services/nginx-module.store';\nimport type { NginxTrafficConfig } from '../../../models/nginx.types';\nimport { NzSwitchModule } from 'ng-zorro-antd/switch';\n\n@Component({\n selector: 'app-nginx-secondary-traffic-tab',\n standalone: true,\n imports: [CommonModule, FormsModule, NzButtonModule, NzIconModule, NzSwitchModule],\n template: `\n <div class=\"panel-header-row\">\n <span class=\"panel-tip\">流量控制策略</span>\n <button nz-button nzType=\"primary\" (click)=\"save()\" [nzLoading]=\"saving()\" [disabled]=\"!dirty()\">\n <nz-icon nzType=\"save\" nzTheme=\"outline\"></nz-icon>\n 保存\n </button>\n </div>\n\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">请求限流</div>\n <div class=\"setting-desc\">每个 IP 每秒最大请求数</div>\n </div>\n <div class=\"setting-ctrl\">\n <input\n class=\"setting-input\"\n [(ngModel)]=\"config().rateLimit\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"20r/s\"\n />\n <nz-switch\n name=\"rateLimitEnabled\"\n nzSize=\"small\"\n [ngModel]=\"config().rateLimitEnabled\"\n (ngModelChange)=\"setRateLimitEnabled($event)\"\n ></nz-switch>\n </div>\n </div>\n\n <div class=\"setting-row\">\n <div>\n <div class=\"setting-label\">连接限制</div>\n <div class=\"setting-desc\">单 IP 最大并发连接数</div>\n </div>\n <div class=\"setting-ctrl\">\n <input\n class=\"setting-input\"\n type=\"number\"\n min=\"1\"\n [(ngModel)]=\"config().connLimit\"\n (ngModelChange)=\"markDirty()\"\n />\n <nz-switch\n name=\"connLimitEnabled\"\n nzSize=\"small\"\n [ngModel]=\"config().connLimitEnabled\"\n (ngModelChange)=\"setConnLimitEnabled($event)\"\n ></nz-switch>\n </div>\n </div>\n\n <div class=\"setting-row no-border\">\n <div class=\"full-width\">\n <div class=\"setting-label\">黑名单 IP</div>\n <div class=\"setting-desc\">逗号或换行分隔</div>\n <textarea\n class=\"setting-textarea mono\"\n [ngModel]=\"blacklistText()\"\n (ngModelChange)=\"setBlacklistText($event)\"\n rows=\"4\"\n placeholder=\"192.168.1.10&#10;10.10.10.5\"\n ></textarea>\n </div>\n </div>\n `,\n styles: [`\n :host {\n display: block;\n }\n\n .panel-header-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .panel-tip {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n }\n\n .setting-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 10px;\n padding: 10px 0;\n border-bottom: 1px solid var(--border-light);\n\n &.no-border {\n border-bottom: none;\n }\n }\n\n .setting-label {\n font-size: var(--nginx-font-size-sm, 12px);\n font-weight: 600;\n color: var(--text-1);\n }\n\n .setting-desc {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n margin-top: 2px;\n }\n\n .setting-ctrl {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .setting-input {\n width: 110px;\n padding: 5px 8px;\n border-radius: 4px;\n border: 1px solid var(--border);\n background: var(--bg-input);\n color: var(--text-2);\n font-size: var(--nginx-font-size-sm, 12px);\n text-align: center;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n outline: none;\n }\n\n .setting-input:focus,\n .setting-textarea:focus {\n border-color: var(--blue);\n box-shadow: 0 0 0 2px var(--blue-border);\n }\n\n .setting-textarea {\n width: 100%;\n margin-top: 8px;\n border: 1px solid var(--border);\n border-radius: 6px;\n background: var(--bg-input);\n color: var(--text-2);\n font-size: var(--nginx-font-size-sm, 12px);\n padding: 8px 10px;\n resize: vertical;\n outline: none;\n }\n\n .mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n }\n `],\n})\nexport class NginxSecondaryTrafficTabComponent implements OnInit {\n private moduleStore = inject(NginxModuleStore);\n private message = inject(NzMessageService);\n\n saving = signal(false);\n dirty = signal(false);\n config = signal<NginxTrafficConfig>({\n rateLimitEnabled: false,\n rateLimit: '',\n connLimitEnabled: false,\n connLimit: 0,\n blacklistIps: [],\n });\n blacklistText = signal('');\n\n async ngOnInit() {\n await this.load();\n }\n\n async load() {\n try {\n const res = await this.moduleStore.loadTrafficConfig();\n if (res.success && res.traffic) {\n const traffic = this.moduleStore.trafficConfig();\n this.config.set({\n ...traffic,\n connLimit: Math.max(0, Number(traffic.connLimit ?? 0)),\n });\n this.blacklistText.set((traffic.blacklistIps || []).join('\\n'));\n this.dirty.set(false);\n } else {\n this.message.error(res.error || '加载流量配置失败');\n }\n } catch (err: any) {\n this.message.error('加载流量配置失败: ' + err.message);\n }\n }\n\n setRateLimitEnabled(checked: boolean) {\n this.config.update(prev => ({ ...prev, rateLimitEnabled: checked }));\n this.markDirty();\n }\n\n setConnLimitEnabled(checked: boolean) {\n this.config.update(prev => ({ ...prev, connLimitEnabled: checked }));\n this.markDirty();\n }\n\n setBlacklistText(value: string) {\n this.blacklistText.set(value || '');\n this.markDirty();\n }\n\n markDirty() {\n this.dirty.set(true);\n }\n\n async save() {\n const payload: NginxTrafficConfig = {\n ...this.config(),\n rateLimit: this.config().rateLimit.trim(),\n connLimit: this.config().connLimitEnabled\n ? Math.max(1, Number(this.config().connLimit || 1))\n : Math.max(0, Number(this.config().connLimit || 0)),\n blacklistIps: this.blacklistText()\n .split(/[\\n,]/)\n .map(item => item.trim())\n .filter(Boolean),\n };\n\n this.saving.set(true);\n try {\n const res = await this.moduleStore.saveTrafficConfig(payload);\n if (res.success) {\n this.message.success('流量控制配置已保存');\n this.dirty.set(false);\n await this.load();\n } else {\n this.message.error(res.error || '保存流量控制配置失败');\n }\n } catch (err: any) {\n this.message.error('保存流量控制配置失败: ' + err.message);\n } finally {\n this.saving.set(false);\n }\n }\n}\n", "/* angular:styles/component:less;ec91592b326dafbcee5619c4f0f01ba9546bebad899d8280f72860951520b245;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-tabs\\nginx-secondary-upstream-tab\\nginx-secondary-upstream-tab.component.ts */\n:host {\n display: block;\n}\n.list-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-bottom: 12px;\n}\n.panel-tip {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n}\n.header-actions {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n}\n.upstream-grid-shell {\n border: none;\n border-radius: 0;\n overflow: hidden;\n}\n.upstream-grid-head,\n.upstream-grid-row {\n display: grid;\n grid-template-columns: minmax(180px, 1fr) minmax(150px, 0.9fr) minmax(130px, 0.7fr) minmax(220px, 1.6fr) 88px 116px;\n align-items: center;\n column-gap: 8px;\n padding: 0 12px;\n}\n.upstream-grid-head {\n min-height: 42px;\n background: #fafafa;\n border-bottom: 1px solid rgba(0, 0, 0, 0.06);\n}\n.upstream-grid-head .cell {\n font-size: var(--nginx-font-size-sm, 12px);\n color: rgba(0, 0, 0, 0.45);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n font-weight: 700;\n}\n.upstream-grid-body {\n background: #fff;\n}\n.upstream-grid-row {\n min-height: 58px;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n transition: background 120ms ease;\n}\n.upstream-grid-row:last-child {\n border-bottom: none;\n}\n.upstream-grid-row:hover {\n background: rgba(0, 0, 0, 0.02);\n}\n.readonly-row {\n background: rgba(0, 0, 0, 0.015);\n}\n.cell {\n min-width: 0;\n}\n.row-actions {\n display: flex;\n gap: 4px;\n justify-content: flex-end;\n}\n.upstream-name {\n display: inline-block;\n font-size: var(--nginx-font-size-base, 14px);\n font-weight: 600;\n color: var(--text-1);\n word-break: break-all;\n}\n.strategy-pill {\n display: inline-flex;\n align-items: center;\n height: 22px;\n border-radius: 11px;\n padding: 0 10px;\n font-size: var(--nginx-font-size-sm, 12px);\n line-height: 22px;\n color: var(--text-2);\n background: var(--bg-input);\n border: 1px solid var(--border-light);\n}\n.source-badge {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n height: 22px;\n padding: 0 8px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.04);\n color: var(--text-3);\n border: 1px solid rgba(0, 0, 0, 0.08);\n font-size: var(--nginx-font-size-sm, 12px);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.source-badge.managed {\n background: rgba(22, 93, 255, 0.08);\n color: #165dff;\n border-color: rgba(22, 93, 255, 0.2);\n}\n.nodes-wrap {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 0;\n}\n.node-chip {\n display: inline-flex;\n align-items: center;\n height: 22px;\n padding: 0 8px;\n border-radius: 4px;\n border: 1px solid rgba(22, 93, 255, 0.2);\n background: rgba(22, 93, 255, 0.08);\n color: #165dff;\n font-size: var(--nginx-font-size-sm, 12px);\n max-width: 100%;\n word-break: break-all;\n}\n.node-count {\n font-size: var(--nginx-font-size-base, 14px);\n color: var(--text-2);\n font-weight: 600;\n}\n.action-col {\n justify-self: end;\n}\n.empty-state {\n text-align: center;\n padding: 48px 0;\n}\n.empty-state .empty-icon {\n font-size: 48px;\n color: rgba(0, 0, 0, 0.2);\n margin-bottom: 16px;\n}\n.empty-state p {\n color: rgba(0, 0, 0, 0.4);\n margin: 0;\n}\n.mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n}\n.drawer-body {\n padding: 0 24px 16px;\n}\n.drawer-footer {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 12px 24px;\n border-top: 1px solid rgba(0, 0, 0, 0.06);\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n background: #fff;\n}\n/*# sourceMappingURL=nginx-secondary-upstream-tab.component.css.map */\n", "import { CommonModule } from '@angular/common';\nimport { Component, OnInit, inject, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { NzButtonModule } from 'ng-zorro-antd/button';\nimport { NzDrawerModule } from 'ng-zorro-antd/drawer';\nimport { NzFormModule } from 'ng-zorro-antd/form';\nimport { NzIconModule } from 'ng-zorro-antd/icon';\nimport { NzInputModule } from 'ng-zorro-antd/input';\nimport { NzMessageService } from 'ng-zorro-antd/message';\nimport { NzSelectModule } from 'ng-zorro-antd/select';\nimport { NzSpinModule } from 'ng-zorro-antd/spin';\n\nimport type { NginxUpstream, NginxUpstreamStrategy } from '../../../models/nginx.types';\nimport { NginxModuleStore } from '../../../services/nginx-module.store';\n\ninterface UpstreamEditRow {\n id: string;\n name: string;\n strategy: NginxUpstreamStrategy;\n nodes: string[];\n sourceFile: string;\n managed: boolean;\n readonly: boolean;\n}\n\ninterface UpstreamDrawerForm {\n name: string;\n strategy: NginxUpstreamStrategy;\n nodesText: string;\n}\n\n@Component({\n selector: 'app-nginx-secondary-upstream-tab',\n standalone: true,\n imports: [\n CommonModule,\n FormsModule,\n NzButtonModule,\n NzDrawerModule,\n NzFormModule,\n NzIconModule,\n NzInputModule,\n NzSelectModule,\n NzSpinModule,\n ],\n template: `\n <div class=\"list-header\">\n <span class=\"panel-tip\">管理后端服务集群的负载均衡配置</span>\n <div class=\"header-actions\">\n <button nz-button nzType=\"default\" (click)=\"openCreateDrawer()\">\n <nz-icon nzType=\"plus\" nzTheme=\"outline\"></nz-icon>\n 新增 Upstream\n </button>\n <button nz-button nzType=\"primary\" (click)=\"saveAll()\" [nzLoading]=\"saving()\" [disabled]=\"!dirty()\">\n <nz-icon nzType=\"save\" nzTheme=\"outline\"></nz-icon>\n 保存\n </button>\n </div>\n </div>\n\n <div class=\"upstream-grid-shell\">\n <div class=\"upstream-grid-head\">\n <div class=\"cell name-col\">UPSTREAM 名称</div>\n <div class=\"cell source-col\">来源</div>\n <div class=\"cell strategy-col\">策略</div>\n <div class=\"cell nodes-col\">节点</div>\n <div class=\"cell count-col\">节点数</div>\n <div class=\"cell action-col\">操作</div>\n </div>\n\n <nz-spin [nzSpinning]=\"loading()\">\n <div class=\"upstream-grid-body\">\n @if (!loading() && !rows().length) {\n <div class=\"empty-state\">\n <nz-icon nzType=\"cluster\" nzTheme=\"outline\" class=\"empty-icon\"></nz-icon>\n <p>暂无 Upstream 配置</p>\n </div>\n } @else {\n @for (row of rows(); track row.id) {\n <div class=\"upstream-grid-row\" [class.readonly-row]=\"row.readonly\">\n <div class=\"cell name-col\">\n <span class=\"upstream-name mono\">{{ row.name }}</span>\n </div>\n\n <div class=\"cell source-col\">\n <span class=\"source-badge\" [class.managed]=\"row.managed\" [title]=\"sourceHint(row)\">\n {{ sourceLabel(row) }}\n </span>\n </div>\n\n <div class=\"cell strategy-col\">\n <span class=\"strategy-pill mono\">{{ row.strategy }}</span>\n </div>\n\n <div class=\"cell nodes-col\">\n <div class=\"nodes-wrap\">\n @for (node of row.nodes; track node + '-' + $index) {\n <span class=\"node-chip mono\">{{ node }}</span>\n }\n </div>\n </div>\n\n <div class=\"cell count-col\">\n <span class=\"node-count\">{{ row.nodes.length }}</span>\n </div>\n\n <div class=\"cell action-col\">\n <div class=\"row-actions\">\n <button nz-button nzSize=\"small\" nzType=\"link\" (click)=\"openEditDrawer(row)\" [disabled]=\"row.readonly\">\n <nz-icon nzType=\"edit\" nzTheme=\"outline\"></nz-icon>\n </button>\n <button nz-button nzSize=\"small\" nzType=\"link\" nzDanger (click)=\"removeRow(row.id)\" [disabled]=\"row.readonly\">\n <nz-icon nzType=\"delete\" nzTheme=\"outline\"></nz-icon>\n </button>\n </div>\n </div>\n </div>\n }\n }\n </div>\n </nz-spin>\n </div>\n\n <nz-drawer\n [nzVisible]=\"drawerVisible()\"\n [nzTitle]=\"editingId() ? '编辑 Upstream' : '新增 Upstream'\"\n [nzWidth]=\"460\"\n [nzPlacement]=\"'right'\"\n (nzOnClose)=\"closeDrawer()\"\n >\n <ng-container *nzDrawerContent>\n <div class=\"drawer-body\">\n <form nz-form nzLayout=\"vertical\">\n <nz-form-item>\n <nz-form-label nzRequired>名称</nz-form-label>\n <nz-form-control>\n <input\n nz-input\n [(ngModel)]=\"drawerForm.name\"\n name=\"upstreamName\"\n placeholder=\"例如 backend_cluster\"\n class=\"mono\"\n />\n </nz-form-control>\n </nz-form-item>\n\n <nz-form-item>\n <nz-form-label nzRequired>策略</nz-form-label>\n <nz-form-control>\n <nz-select [(ngModel)]=\"drawerForm.strategy\" name=\"upstreamStrategy\">\n <nz-option nzValue=\"round-robin\" nzLabel=\"round-robin\"></nz-option>\n <nz-option nzValue=\"least_conn\" nzLabel=\"least_conn\"></nz-option>\n <nz-option nzValue=\"ip_hash\" nzLabel=\"ip_hash\"></nz-option>\n <nz-option nzValue=\"hash\" nzLabel=\"hash\"></nz-option>\n </nz-select>\n </nz-form-control>\n </nz-form-item>\n\n <nz-form-item>\n <nz-form-label nzRequired>节点(逗号或换行分隔)</nz-form-label>\n <nz-form-control>\n <textarea\n nz-input\n rows=\"5\"\n [(ngModel)]=\"drawerForm.nodesText\"\n name=\"upstreamNodes\"\n placeholder=\"127.0.0.1:3001, 127.0.0.1:3002\"\n class=\"mono\"\n ></textarea>\n </nz-form-control>\n </nz-form-item>\n </form>\n </div>\n\n <div class=\"drawer-footer\">\n <button nz-button nzType=\"default\" (click)=\"closeDrawer()\">取消</button>\n <button nz-button nzType=\"primary\" (click)=\"submitDrawer()\">确定</button>\n </div>\n </ng-container>\n </nz-drawer>\n `,\n styles: [`\n :host {\n display: block;\n }\n\n .list-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-bottom: 12px;\n }\n\n .panel-tip {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n }\n\n .header-actions {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n }\n\n .upstream-grid-shell {\n border: none;\n border-radius: 0;\n overflow: hidden;\n }\n\n .upstream-grid-head,\n .upstream-grid-row {\n display: grid;\n grid-template-columns: minmax(180px, 1fr) minmax(150px, 0.9fr) minmax(130px, 0.7fr) minmax(220px, 1.6fr) 88px 116px;\n align-items: center;\n column-gap: 8px;\n padding: 0 12px;\n }\n\n .upstream-grid-head {\n min-height: 42px;\n background: #fafafa;\n border-bottom: 1px solid rgba(0, 0, 0, 0.06);\n\n .cell {\n font-size: var(--nginx-font-size-sm, 12px);\n color: rgba(0, 0, 0, 0.45);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n font-weight: 700;\n }\n }\n\n .upstream-grid-body {\n background: #fff;\n }\n\n .upstream-grid-row {\n min-height: 58px;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n transition: background 120ms ease;\n\n &:last-child {\n border-bottom: none;\n }\n\n &:hover {\n background: rgba(0, 0, 0, 0.02);\n }\n }\n\n .readonly-row {\n background: rgba(0, 0, 0, 0.015);\n }\n\n .cell {\n min-width: 0;\n }\n .row-actions{\n display: flex;\n gap: 4px;\n justify-content: flex-end;\n }\n .upstream-name {\n display: inline-block;\n font-size: var(--nginx-font-size-base, 14px);\n font-weight: 600;\n color: var(--text-1);\n word-break: break-all;\n }\n\n .strategy-pill {\n display: inline-flex;\n align-items: center;\n height: 22px;\n border-radius: 11px;\n padding: 0 10px;\n font-size: var(--nginx-font-size-sm, 12px);\n line-height: 22px;\n color: var(--text-2);\n background: var(--bg-input);\n border: 1px solid var(--border-light);\n }\n\n .source-badge {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n height: 22px;\n padding: 0 8px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.04);\n color: var(--text-3);\n border: 1px solid rgba(0, 0, 0, 0.08);\n font-size: var(--nginx-font-size-sm, 12px);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .source-badge.managed {\n background: rgba(22, 93, 255, 0.08);\n color: #165dff;\n border-color: rgba(22, 93, 255, 0.2);\n }\n\n .nodes-wrap {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 0;\n }\n\n .node-chip {\n display: inline-flex;\n align-items: center;\n height: 22px;\n padding: 0 8px;\n border-radius: 4px;\n border: 1px solid rgba(22, 93, 255, 0.2);\n background: rgba(22, 93, 255, 0.08);\n color: #165dff;\n font-size: var(--nginx-font-size-sm, 12px);\n max-width: 100%;\n word-break: break-all;\n }\n\n .node-count {\n font-size: var(--nginx-font-size-base, 14px);\n color: var(--text-2);\n font-weight: 600;\n }\n\n .action-col {\n justify-self: end;\n }\n\n .empty-state {\n text-align: center;\n padding: 48px 0;\n\n .empty-icon {\n font-size: 48px;\n color: rgba(0, 0, 0, 0.2);\n margin-bottom: 16px;\n }\n\n p {\n color: rgba(0, 0, 0, 0.4);\n margin: 0;\n }\n }\n\n .mono {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n }\n\n .drawer-body {\n padding: 0 24px 16px;\n }\n\n .drawer-footer {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 12px 24px;\n border-top: 1px solid rgba(0, 0, 0, 0.06);\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n background: #fff;\n }\n `],\n})\nexport class NginxSecondaryUpstreamTabComponent implements OnInit {\n private moduleStore = inject(NginxModuleStore);\n private message = inject(NzMessageService);\n\n loading = signal(false);\n saving = signal(false);\n dirty = signal(false);\n rows = signal<UpstreamEditRow[]>([]);\n drawerVisible = signal(false);\n editingId = signal<string | null>(null);\n\n drawerForm: UpstreamDrawerForm = this.createEmptyForm();\n\n async ngOnInit() {\n await this.loadData();\n }\n\n async loadData() {\n this.loading.set(true);\n try {\n const res = await this.moduleStore.loadUpstreams();\n if (res.success && res.upstreams) {\n this.rows.set(\n this.moduleStore.upstreams().map(item => ({\n id: item.id,\n name: item.name,\n strategy: item.strategy,\n nodes: (item.nodes || []).map(node => node.trim()).filter(Boolean),\n sourceFile: item.sourceFile || '',\n managed: item.managed !== false,\n readonly: item.readonly === true || item.managed === false,\n })),\n );\n this.dirty.set(false);\n } else {\n this.message.error(res.error || '加载 Upstream 失败');\n }\n } catch (err: any) {\n this.message.error('加载 Upstream 失败: ' + err.message);\n } finally {\n this.loading.set(false);\n }\n }\n\n openCreateDrawer() {\n this.editingId.set(null);\n this.drawerForm = this.createEmptyForm();\n this.drawerVisible.set(true);\n }\n\n openEditDrawer(row: UpstreamEditRow) {\n if (row.readonly) {\n this.message.info('该 Upstream 来自外部配置文件,当前为只读展示');\n return;\n }\n this.editingId.set(row.id);\n this.drawerForm = {\n name: row.name,\n strategy: row.strategy,\n nodesText: row.nodes.join(', '),\n };\n this.drawerVisible.set(true);\n }\n\n closeDrawer() {\n this.drawerVisible.set(false);\n }\n\n submitDrawer() {\n const name = this.drawerForm.name.trim();\n if (!name) {\n this.message.warning('Upstream 名称不能为空');\n return;\n }\n\n const nodes = this.parseNodes(this.drawerForm.nodesText);\n if (!nodes.length) {\n this.message.warning(`Upstream \"${name}\" 至少需要一个节点`);\n return;\n }\n\n const normalizedRow: UpstreamEditRow = {\n id: this.editingId() || this.makeId(),\n name,\n strategy: this.drawerForm.strategy,\n nodes,\n sourceFile: '',\n managed: true,\n readonly: false,\n };\n\n if (this.editingId()) {\n this.rows.update(rows =>\n rows.map(item =>\n item.id === normalizedRow.id\n ? {\n ...normalizedRow,\n sourceFile: item.sourceFile,\n managed: item.managed,\n readonly: item.readonly,\n }\n : item,\n ),\n );\n } else {\n this.rows.update(rows => [...rows, normalizedRow]);\n }\n\n this.markDirty();\n this.closeDrawer();\n }\n\n removeRow(id: string) {\n const target = this.rows().find(row => row.id === id);\n if (target?.readonly) {\n this.message.info('该 Upstream 来自外部配置文件,当前不可删除');\n return;\n }\n this.rows.update(rows => rows.filter(row => row.id !== id));\n this.markDirty();\n }\n\n markDirty() {\n this.dirty.set(true);\n }\n\n async saveAll() {\n const payload: NginxUpstream[] = [];\n\n for (const row of this.rows()) {\n if (row.readonly) {\n continue;\n }\n\n const name = row.name.trim();\n if (!name) {\n this.message.warning('Upstream 名称不能为空');\n return;\n }\n if (!/^[a-zA-Z0-9._-]+$/.test(name)) {\n this.message.warning(`Upstream 名称不合法: ${name}`);\n return;\n }\n\n const nodes = (row.nodes || []).map(item => item.trim()).filter(Boolean);\n if (!nodes.length) {\n this.message.warning(`Upstream \"${name}\" 至少需要一个节点`);\n return;\n }\n\n payload.push({\n id: row.id,\n name,\n strategy: row.strategy,\n nodes,\n sourceFile: row.sourceFile,\n managed: true,\n readonly: false,\n });\n }\n\n this.saving.set(true);\n try {\n const res = await this.moduleStore.saveUpstreams(payload);\n if (res.success) {\n this.message.success('Upstream 配置已保存');\n this.dirty.set(false);\n await this.loadData();\n } else {\n this.message.error(res.error || '保存 Upstream 失败');\n }\n } catch (err: any) {\n this.message.error('保存 Upstream 失败: ' + err.message);\n } finally {\n this.saving.set(false);\n }\n }\n\n private makeId(): string {\n return `upstream-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;\n }\n\n private parseNodes(nodesText: string): string[] {\n return nodesText\n .split(/[,;\\n]/)\n .map(item => item.trim())\n .filter(Boolean);\n }\n\n private createEmptyForm(): UpstreamDrawerForm {\n return {\n name: '',\n strategy: 'round-robin',\n nodesText: '',\n };\n }\n\n sourceLabel(row: UpstreamEditRow): string {\n const raw = row.sourceFile || '';\n const normalized = raw.replace(/\\\\/g, '/');\n const name = normalized.split('/').pop() || normalized;\n return name || (row.managed ? '托管文件' : '外部文件');\n }\n\n sourceHint(row: UpstreamEditRow): string {\n if (!row.sourceFile) {\n return row.managed ? '托管 upstream(来源未解析)' : '外部 upstream(来源未解析)';\n }\n return `${row.managed ? '托管' : '外部'}: ${row.sourceFile}`;\n }\n}\n", "/* angular:styles/component:less;be0d28def7a058758ca93c978518ebddcea4fe4be40c09bbcf97a80d5213437c;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-section-card\\nginx-section-card.component.ts */\n:host {\n display: block;\n}\n.card {\n background: var(--bg-white);\n border: 1px solid var(--border);\n border-left-width: 3px;\n border-radius: 8px;\n box-shadow: var(--shadow-card);\n margin-bottom: 14px;\n overflow: hidden;\n border-left-color: transparent;\n}\n.card.card-blue {\n border-left-color: var(--blue);\n}\n.card.card-green {\n border-left-color: var(--green);\n}\n.card.card-orange {\n border-left-color: var(--orange);\n}\n.card-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px 12px 20px;\n border-bottom: 1px solid var(--border-light);\n gap: 10px;\n}\n.card-header-left {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.card-title {\n font-size: var(--nginx-font-size-base, 14px);\n font-weight: 700;\n color: var(--text-1);\n}\n.card-subtitle {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n}\n.card-actions {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n justify-content: flex-end;\n}\n.card-body {\n padding: 14px 18px;\n}\n.card-body.no-padding {\n padding: 0;\n}\n@media (max-width: 768px) {\n .card-header {\n flex-direction: column;\n align-items: flex-start;\n }\n .card-actions {\n width: 100%;\n }\n}\n/*# sourceMappingURL=nginx-section-card.component.css.map */\n", "import { Component, Input } from '@angular/core';\n\n@Component({\n selector: 'app-nginx-section-card',\n standalone: true,\n template: `\n <section class=\"card\" [class.card-blue]=\"accent === 'blue'\" [class.card-green]=\"accent === 'green'\" [class.card-orange]=\"accent === 'orange'\">\n @if (showHeader) {\n <div class=\"card-header\">\n <div class=\"card-header-left\">\n <span class=\"card-title\">{{ title }}</span>\n @if (subtitle) {\n <span class=\"card-subtitle\">{{ subtitle }}</span>\n }\n </div>\n <div class=\"card-actions\">\n <ng-content select=\"[nginxCardActions]\"></ng-content>\n </div>\n </div>\n }\n\n <div class=\"card-body\" [class.no-padding]=\"noBodyPadding\">\n <ng-content></ng-content>\n </div>\n </section>\n `,\n styles: `\n :host {\n display: block;\n }\n\n .card {\n background: var(--bg-white);\n border: 1px solid var(--border);\n border-left-width: 3px;\n border-radius: 8px;\n box-shadow: var(--shadow-card);\n margin-bottom: 14px;\n overflow: hidden;\n border-left-color: transparent;\n }\n\n .card.card-blue {\n border-left-color: var(--blue);\n }\n\n .card.card-green {\n border-left-color: var(--green);\n }\n\n .card.card-orange {\n border-left-color: var(--orange);\n }\n\n .card-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px 12px 20px;\n border-bottom: 1px solid var(--border-light);\n gap: 10px;\n }\n\n .card-header-left {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n }\n\n .card-title {\n font-size: var(--nginx-font-size-base, 14px);\n font-weight: 700;\n color: var(--text-1);\n }\n\n .card-subtitle {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n }\n\n .card-actions {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n justify-content: flex-end;\n }\n\n .card-body {\n padding: 14px 18px;\n\n &.no-padding {\n padding: 0;\n }\n }\n\n @media (max-width: 768px) {\n .card-header {\n flex-direction: column;\n align-items: flex-start;\n }\n\n .card-actions {\n width: 100%;\n }\n }\n `,\n})\nexport class NginxSectionCardComponent {\n @Input() accent: 'blue' | 'green' | 'orange' = 'blue';\n @Input() showHeader = false;\n @Input() title = '';\n @Input() subtitle = '';\n @Input() noBodyPadding = false;\n}\n\r\n", "<div class=\"server-list\">\r\n <!-- \u9876\u90E8\u64CD\u4F5C\u680F -->\r\n @if (showToolbar) {\r\n <div class=\"list-header\">\r\n <div class=\"header-left\">\r\n <button nz-button nzType=\"default\" (click)=\"importServer()\">\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\"></nz-icon>\r\n \u5BFC\u5165\r\n </button>\r\n <button nz-button nzType=\"primary\" (click)=\"openDrawer(null)\">\r\n <nz-icon nzType=\"plus\" nzTheme=\"outline\"></nz-icon>\r\n \u65B0\u589E Server\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"server-grid-shell\">\r\n <div class=\"server-grid-head\">\r\n <div class=\"cell status-col\">\u72B6\u6001</div>\r\n <div class=\"cell\">Server \u540D\u79F0</div>\n <div class=\"cell listen-col\">\u76D1\u542C\u7AEF\u53E3</div>\n <div class=\"cell domain-col\">\u57DF\u540D</div>\n <div class=\"cell access-col\">\u8BBF\u95EE\u5730\u5740</div>\n <div class=\"cell root-col\">\u6839\u76EE\u5F55</div>\n <div class=\"cell action-col\">\u64CD\u4F5C</div>\n </div>\n\r\n <nz-spin [nzSpinning]=\"loading()\">\r\n <div class=\"server-grid-body\">\r\n @if (!loading() && !servers().length) {\r\n <div class=\"empty-state\">\r\n <nz-icon nzType=\"inbox\" nzTheme=\"outline\" class=\"empty-icon\"></nz-icon>\r\n <p>\u6682\u65E0 Server \u914D\u7F6E</p>\r\n </div>\r\n } @else {\r\n @for (server of servers(); track server.id) {\r\n <div class=\"server-grid-row\" [class.disabled-row]=\"!server.enabled\">\r\n <div class=\"cell status-col\">\r\n <nz-switch\r\n nzSize=\"small\"\r\n [ngModel]=\"server.enabled\"\r\n (ngModelChange)=\"toggleServer(server.id, $event)\"\r\n ></nz-switch>\r\n </div>\r\n\r\n <div class=\"cell\">\r\n <div class=\"server-name\">\r\n <nz-icon nzType=\"file\" nzTheme=\"outline\" class=\"conf-icon\"></nz-icon>\r\n {{ server.name }}\r\n </div>\r\n </div>\r\n\r\n <div class=\"cell listen-col\">\r\n @for (port of server.listen; track $index) {\r\n @if (port === '443' || server.ssl) {\r\n <span class=\"server-listen ssl\">:{{ port }} ssl</span>\r\n } @else {\r\n <span class=\"server-listen\">:{{ port }}</span>\r\n }\r\n }\r\n </div>\r\n\r\n <div class=\"cell domain-col\">\n <span class=\"server-domain\">{{ (server.domains || []).join(', ') || '\u2014' }}</span>\n </div>\n\n <div class=\"cell access-col\">\n @if (getAccessUrls(server).length) {\n <div class=\"access-links\">\n @for (url of getAccessUrls(server); track url) {\n <a\n class=\"access-link\"\n [href]=\"url\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {{ url }}\n </a>\n }\n </div>\n } @else {\n <span class=\"server-domain\">\u2014</span>\n }\n </div>\n\n <div class=\"cell root-col\">\n <span class=\"server-root\" [title]=\"server.root || ''\">{{\n server.root || '\u2014'\n }}</span>\n </div>\r\n\r\n <div class=\"cell action-col\">\r\n <div class=\"row-actions\">\r\n <button nz-button nzType=\"link\" nzSize=\"small\" (click)=\"openDrawer(server)\">\r\n <nz-icon nzType=\"edit\" nzTheme=\"outline\"></nz-icon>\r\n </button>\r\n <button nz-button nzType=\"link\" nzSize=\"small\" (click)=\"copyServer(server)\">\r\n <nz-icon nzType=\"copy\" nzTheme=\"outline\"></nz-icon>\r\n </button>\r\n <button\r\n nz-button\r\n nzDanger\r\n nzType=\"link\"\r\n nzSize=\"small\"\r\n nz-popconfirm\r\n [nzPopconfirmTitle]=\"'\u5220\u9664\u540E\u65E0\u6CD5\u6062\u590D\uFF0C\u786E\u8BA4\u5220\u9664' + server.name + '\u5417\uFF1F'\"\r\n [nzOkButtonProps]=\"{ nzDanger: true }\"\r\n nzPopconfirmOkText=\"\u5220\u9664\"\r\n nzPopconfirmCancelText=\"\u53D6\u6D88\"\r\n (nzOnConfirm)=\"deleteServer(server)\"\r\n >\r\n <nz-icon nzType=\"delete\" nzTheme=\"outline\"></nz-icon>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </nz-spin>\r\n </div>\r\n</div>\r\n\r\n<!-- Server \u65B0\u589E/\u7F16\u8F91 Drawer -->\r\n<app-nginx-server-drawer\n [visible]=\"drawerVisible\"\n (visibleChange)=\"onDrawerVisibleChange($event)\"\n [editingServer]=\"editingServer()\"\n (saved)=\"onSaved()\"\n></app-nginx-server-drawer>\n\r\n<!-- \u67E5\u770B\u914D\u7F6E\u6A21\u6001\u6846 -->\r\n<nz-modal\r\n [(nzVisible)]=\"configModalVisible\"\r\n nzTitle=\"Server \u914D\u7F6E\"\r\n (nzOnCancel)=\"configModalVisible = false\"\r\n [nzFooter]=\"null\"\r\n nzWidth=\"800px\"\r\n>\r\n <ng-container *nzModalContent>\r\n <pre class=\"config-preview\">{{ viewingConfig() }}</pre>\r\n </ng-container>\r\n</nz-modal>\r\n", "/* src/app/pages/nginx/components/nginx-server-list/nginx-server-list.component.less */\n.server-list .list-header {\n display: flex;\n align-items: center;\n margin-bottom: 16px;\n}\n.server-list .list-header .header-left {\n display: flex;\n gap: 8px;\n}\n.server-grid-shell {\n border: none;\n border-radius: 0;\n overflow: hidden;\n}\n.server-grid-head,\n.server-grid-row {\n display: grid;\n grid-template-columns: 72px minmax(160px, 1.1fr) minmax(130px, 0.8fr) minmax(150px, 1fr) minmax(220px, 1.2fr) minmax(180px, 1fr) 120px;\n align-items: center;\n column-gap: 8px;\n padding: 0 12px;\n}\n.server-grid-head {\n min-height: 42px;\n background: #fafafa;\n border-bottom: 1px solid rgba(0, 0, 0, 0.06);\n}\n.server-grid-head .cell {\n font-size: var(--nginx-font-size-sm, 12px);\n color: rgba(0, 0, 0, 0.45);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n font-weight: 700;\n}\n.server-grid-body {\n background: #fff;\n}\n.server-grid-row {\n min-height: 56px;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n transition: background 120ms ease;\n}\n.server-grid-row:last-child {\n border-bottom: none;\n}\n.server-grid-row:hover {\n background: rgba(0, 0, 0, 0.02);\n}\n.cell {\n min-width: 0;\n}\n.action-col {\n justify-self: end;\n}\n.server-name {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 500;\n font-size: var(--nginx-font-size-base, 14px);\n}\n.conf-icon {\n color: rgba(0, 0, 0, 0.35);\n flex-shrink: 0;\n}\n.server-listen {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n font-size: var(--nginx-font-size-sm, 12px);\n color: #e5a832;\n background: rgba(229, 168, 50, 0.12);\n padding: 2px 8px;\n border-radius: 4px;\n margin-right: 4px;\n}\n.server-listen.ssl {\n color: #5ea6f7;\n background: rgba(94, 166, 247, 0.12);\n}\n.server-domain {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n font-size: var(--nginx-font-size-sm, 12px);\n color: rgba(0, 0, 0, 0.55);\n}\n.server-root {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n font-size: var(--nginx-font-size-sm, 12px);\n color: rgba(0, 0, 0, 0.4);\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n display: block;\n}\n.access-link {\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--blue);\n text-decoration: none;\n display: inline-block;\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.access-link:hover {\n text-decoration: underline;\n}\n.access-links {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.row-actions {\n display: flex;\n gap: 4px;\n justify-content: flex-end;\n}\n.empty-state {\n text-align: center;\n padding: 48px 0;\n}\n.empty-state .empty-icon {\n font-size: 48px;\n color: rgba(0, 0, 0, 0.2);\n margin-bottom: 16px;\n}\n.empty-state p {\n color: rgba(0, 0, 0, 0.4);\n margin: 0;\n}\n@media (max-width: 1100px) {\n .server-grid-head {\n display: none;\n }\n .server-grid-row {\n grid-template-columns: 1fr;\n gap: 8px;\n padding: 12px;\n align-items: stretch;\n }\n .status-col,\n .listen-col,\n .domain-col,\n .access-col,\n .root-col,\n .action-col {\n justify-self: start;\n }\n .action-col {\n width: 100%;\n }\n .row-actions {\n opacity: 1;\n }\n}\n.config-preview {\n background: #f6ffed;\n border: 1px solid #b7eb8f;\n border-radius: 4px;\n padding: 16px;\n margin: 0;\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n font-size: var(--nginx-font-size-base, 14px);\n line-height: 1.6;\n overflow-x: auto;\n white-space: pre-wrap;\n}\n/*# sourceMappingURL=nginx-server-list.component.css.map */\n", "<nz-drawer\n [nzVisible]=\"visible\"\n [nzPlacement]=\"'right'\"\n [nzWidth]=\"520\"\n [nzTitle]=\"drawerTitle\"\n (nzOnClose)=\"onClose()\"\n [nzClosable]=\"true\"\n [nzMaskClosable]=\"true\"\n>\n <ng-container *nzDrawerContent>\n <div class=\"drawer-body\">\n <form nz-form nzLayout=\"vertical\">\n <!-- Server \u540D\u79F0 -->\n <nz-form-item>\n <nz-form-label nzRequired>Server \u540D\u79F0</nz-form-label>\n <nz-form-control nzHasFeedback nzErrorTip=\"\u8BF7\u8F93\u5165 Server \u540D\u79F0\">\n <input\n nz-input\n [(ngModel)]=\"formData.name\"\n name=\"serverName\"\n placeholder=\"\u4F8B\u5982\uFF1Amain-site\"\n />\n </nz-form-control>\n </nz-form-item>\n\n <!-- \u534F\u8BAE + \u57DF\u540D + \u76D1\u542C\u7AEF\u53E3 -->\n <nz-form-item>\n <nz-form-label nzRequired>\u57DF\u540D</nz-form-label>\n <nz-form-control>\n <div nz-input-group nzCompact class=\"domain-row-wrapper\">\n <nz-select\n class=\"domain-row-protocol\"\n [(ngModel)]=\"formData.protocol\"\n name=\"protocol\"\n nzPlaceHolder=\"HTTP\"\n >\n <nz-option nzValue=\"http\" nzLabel=\"HTTP\"></nz-option>\n <nz-option nzValue=\"https\" nzLabel=\"HTTPS (SSL)\"></nz-option>\n </nz-select>\n\n <nz-select\n class=\"domain-row-domain\"\n nzMode=\"tags\"\n [(ngModel)]=\"domainValues\"\n name=\"domains\"\n nzPlaceHolder=\"\u53EF\u8F93\u5165\u6216\u9009\u62E9\u591A\u4E2A\u57DF\u540D\"\n [nzTokenSeparators]=\"[',', ' ']\"\n (ngModelChange)=\"syncDomains()\"\n >\n @for (domain of commonDomainOptions; track domain) {\n <nz-option [nzValue]=\"domain\" [nzLabel]=\"domain\"></nz-option>\n }\n </nz-select>\n\n <nz-select\n class=\"domain-row-listen\"\n nzMode=\"tags\"\n [(ngModel)]=\"listenValues\"\n name=\"listenPort\"\n nzPlaceHolder=\"\u7AEF\u53E3\"\n [nzTokenSeparators]=\"[',', ' ']\"\n >\n @for (port of commonListenOptions; track port) {\n <nz-option [nzValue]=\"port\" [nzLabel]=\"port\"></nz-option>\n }\n </nz-select>\n </div>\n </nz-form-control>\n <div class=\"field-hint\">\u57DF\u540D\u548C\u7AEF\u53E3\u5747\u652F\u6301\u591A\u4E2A\u503C\uFF0C\u4F8B\u5982\uFF1Alocalhost, 127.0.0.1 / 80, 443</div>\n </nz-form-item>\n\n <!-- \u6839\u76EE\u5F55 + \u9ED8\u8BA4\u9996\u9875 -->\n <nz-form-item>\n <nz-form-label nzTooltipTitle=\"\u9759\u6001\u8D44\u6E90\u6839\u76EE\u5F55\uFF0C\u901A\u5E38\u7528\u4E8E\u63D0\u4F9B\u9759\u6001\u6587\u4EF6\u670D\u52A1\uFF0C\u5982index.html\u6240\u5728\u76EE\u5F55\"\n [nzTooltipIcon]=\"'question-circle'\">\u6839\u76EE\u5F55</nz-form-label>\n <nz-form-control>\n <div nz-input-group nzCompact class=\"root-row-wrapper\">\n <input\n nz-input\n class=\"mono-input root-row-root\"\n [(ngModel)]=\"formData.root\"\n name=\"root\"\n placeholder=\"\u5982\uFF1AD:\\\\dist\\\\www\"\n />\n <nz-select\n class=\"root-row-index\"\n nzMode=\"tags\"\n [(ngModel)]=\"indexValues\"\n name=\"serverIndex\"\n nzPlaceHolder=\"\u9ED8\u8BA4\u9996\u9875\"\n [nzTokenSeparators]=\"[',', ' ']\"\n (ngModelChange)=\"syncIndex()\"\n >\n @for (item of commonIndexOptions; track item) {\n <nz-option [nzValue]=\"item\" [nzLabel]=\"item\"></nz-option>\n }\n </nz-select>\n </div>\n </nz-form-control>\n <div class=\"field-hint\">\u6839\u76EE\u5F55\u793A\u4F8B\uFF1AD:\\dist\\www\uFF1B\u9ED8\u8BA4\u9996\u9875\u793A\u4F8B\uFF1Aindex.html, index.htm</div>\n </nz-form-item>\n\n <!-- Location \u89C4\u5219 -->\n <nz-form-item>\n <nz-form-label nzTooltipTitle=\"\u7528\u4E8E\u5B9A\u4E49\u8BF7\u6C42\u8DEF\u5F84\u7684\u5904\u7406\u89C4\u5219\uFF0C\u5982 API \u4EE3\u7406\u670D\u52A1\u6216\u9759\u6001\u8D44\u6E90\u8DEF\u5F84\"\n [nzTooltipIcon]=\"'question-circle'\">Location \u89C4\u5219</nz-form-label>\n <nz-form-control>\n <div class=\"location-toolbar\">\n <button nz-button nzType=\"default\" type=\"button\" (click)=\"addLocation('empty')\">\n <span nz-icon nzType=\"plus\"></span>\n \u65B0\u589E\u89C4\u5219\n </button>\n <!-- <button nz-button nzType=\"default\" type=\"button\" (click)=\"addLocation('api')\">\n \u5FEB\u901F\u65B0\u589E API \u4EE3\u7406\n </button> -->\n </div>\n\n @for (loc of formData.locations; track $index) {\n <div class=\"location-item\">\n <div class=\"location-item-head\">\n <span>Location {{ $index + 1 }}</span>\n <button\n nz-button\n nzType=\"link\"\n nzDanger\n type=\"button\"\n (click)=\"removeLocation($index)\"\n >\n \u5220\u9664\n </button>\n </div>\n\n <div class=\"location-grid\">\n <input\n nz-input\n [(ngModel)]=\"loc.path\"\n [name]=\"'locationPath' + $index\"\n placeholder=\"/api/\"\n />\n <input\n nz-input\n class=\"mono-input\"\n [(ngModel)]=\"loc.proxyPass\"\n [name]=\"'locationProxy' + $index\"\n placeholder=\"http://127.0.0.1:6808\"\n />\n </div>\n\n <textarea\n nz-input\n class=\"mono-textarea location-extra-textarea\"\n rows=\"3\"\n [(ngModel)]=\"loc.rawConfig\"\n [name]=\"'locationExtra' + $index\"\n placeholder=\"\u989D\u5916 location \u6307\u4EE4\uFF0C\u5982\uFF1A&#10;proxy_http_version 1.1;&#10;proxy_set_header Upgrade $http_upgrade;&#10;proxy_set_header Connection &quot;upgrade&quot;;\"\n ></textarea>\n </div>\n }\n </nz-form-control>\n <div class=\"field-hint\">\u5E38\u89C1\u573A\u666F\uFF1A`location /api/` + `proxy_pass http://127.0.0.1:6808;`</div>\n </nz-form-item>\n\n <!-- SSL \u914D\u7F6E\uFF08\u534F\u8BAE\u4E3A HTTPS \u65F6\u663E\u793A\uFF09 -->\n @if (formData.protocol === 'https') {\n <nz-form-item>\n <nz-form-label nzRequired>SSL \u8BC1\u4E66\u8DEF\u5F84</nz-form-label>\n <nz-form-control nzHasFeedback nzErrorTip=\"\u8BF7\u586B\u5199 SSL \u8BC1\u4E66\u8DEF\u5F84\">\n <input\n nz-input\n [(ngModel)]=\"formData.sslCert\"\n name=\"sslCert\"\n placeholder=\"/etc/nginx/ssl/cert.pem\"\n class=\"mono-input\"\n />\n </nz-form-control>\n </nz-form-item>\n\n <nz-form-item>\n <nz-form-label nzRequired>SSL \u79C1\u94A5\u8DEF\u5F84</nz-form-label>\n <nz-form-control nzHasFeedback nzErrorTip=\"\u8BF7\u586B\u5199 SSL \u79C1\u94A5\u8DEF\u5F84\">\n <input\n nz-input\n [(ngModel)]=\"formData.sslKey\"\n name=\"sslKey\"\n placeholder=\"/etc/nginx/ssl/key.pem\"\n class=\"mono-input\"\n />\n </nz-form-control>\n </nz-form-item>\n }\n\n <!-- \u81EA\u5B9A\u4E49\u914D\u7F6E -->\n <nz-form-item>\n <nz-form-label>\u81EA\u5B9A\u4E49\u914D\u7F6E\u7247\u6BB5</nz-form-label>\n <nz-form-control>\n <textarea\n nz-input\n [(ngModel)]=\"formData.extraConfig\"\n name=\"extraConfig\"\n class=\"mono-textarea\"\n rows=\"4\"\n placeholder=\"# \u5728\u6B64\u6DFB\u52A0\u81EA\u5B9A\u4E49 nginx \u914D\u7F6E&#10;location /api {&#10; proxy_pass http://backend_cluster;&#10;}\"\n ></textarea>\n </nz-form-control>\n <div class=\"field-hint\">\u7528\u4E8E\u8865\u5145\u9AD8\u7EA7\u6307\u4EE4\uFF08`location` \u5E38\u89C4\u573A\u666F\u5EFA\u8BAE\u5728\u4E0A\u9762\u7684 Location \u89C4\u5219\u4E2D\u914D\u7F6E\uFF09</div>\n </nz-form-item>\n </form>\n </div>\n\n <div class=\"drawer-footer\">\n <button nz-button nzType=\"default\" (click)=\"onClose()\">\u53D6\u6D88</button>\n <!-- <button nz-button nzType=\"default\" (click)=\"previewConfig()\">\u9884\u89C8\u914D\u7F6E</button> -->\n <button nz-button nzType=\"primary\" (click)=\"save()\" [nzLoading]=\"saving()\">\u4FDD\u5B58</button>\n </div>\n </ng-container>\n</nz-drawer>\n", "/* src/app/pages/nginx/components/nginx-server-drawer/nginx-server-drawer.component.less */\n:host {\n display: block;\n}\n.drawer-body {\n padding: 0 24px 16px;\n}\n.mono-textarea {\n resize: none;\n}\n.domain-row-wrapper,\n.root-row-wrapper {\n display: flex;\n width: 100%;\n}\n.domain-row-protocol {\n width: 130px;\n flex: 0 0 130px;\n}\n.domain-row-domain {\n flex: 1 1 auto;\n min-width: 0;\n margin-left: -1px;\n}\n.domain-row-listen {\n width: 180px;\n flex: 0 0 180px;\n margin-left: -1px;\n}\n.root-row-root {\n flex: 1 1 auto;\n min-width: 0;\n}\n.root-row-index {\n width: 220px;\n flex: 0 0 220px;\n margin-left: -1px;\n}\n.field-hint {\n margin-top: 4px;\n font-size: 12px;\n opacity: 0.7;\n}\n.location-toolbar {\n display: flex;\n gap: 8px;\n margin-bottom: 10px;\n}\n.location-item {\n border: 1px solid rgba(0, 0, 0, 0.08);\n border-radius: 8px;\n padding: 10px;\n background: #fafafa;\n margin-bottom: 10px;\n}\n.location-item-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 8px;\n}\n.location-grid {\n display: grid;\n grid-template-columns: 160px 1fr;\n gap: 8px;\n}\n.location-extra-textarea {\n margin-top: 8px;\n}\n.drawer-footer {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 12px 24px;\n border-top: 1px solid rgba(0, 0, 0, 0.06);\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n background: #fff;\n}\n@media (max-width: 640px) {\n .domain-row-wrapper,\n .root-row-wrapper {\n flex-wrap: wrap;\n gap: 8px;\n }\n .domain-row-protocol,\n .domain-row-domain,\n .domain-row-listen,\n .root-row-root,\n .root-row-index {\n width: 100%;\n flex: 1 1 100%;\n margin-left: 0;\n }\n}\n/*# sourceMappingURL=nginx-server-drawer.component.css.map */\n", "import {\n ChangeDetectorRef,\n Component,\n EventEmitter,\n Input,\n OnChanges,\n OnDestroy,\n Output,\n SimpleChanges,\n inject,\n signal,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { NzButtonModule } from 'ng-zorro-antd/button';\nimport { NzDrawerModule } from 'ng-zorro-antd/drawer';\nimport { NzFormModule } from 'ng-zorro-antd/form';\nimport { NzIconModule } from 'ng-zorro-antd/icon';\nimport { NzInputModule } from 'ng-zorro-antd/input';\nimport { NzMessageService } from 'ng-zorro-antd/message';\nimport { NzSelectModule } from 'ng-zorro-antd/select';\n\nimport { NginxService } from '../../services/nginx.service';\nimport type { NginxLocation, NginxServer, CreateNginxServerRequest } from '../../models/nginx.types';\n\n/**\n * Nginx Server 新增/编辑 Drawer\n * 使用 nz-form 布局\n */\n@Component({\n selector: 'app-nginx-server-drawer',\n standalone: true,\n imports: [\n CommonModule,\n FormsModule,\n NzButtonModule,\n NzDrawerModule,\n NzFormModule,\n NzIconModule,\n NzInputModule,\n NzSelectModule,\n ],\n templateUrl:'./nginx-server-drawer.component.html',\n styleUrls: ['./nginx-server-drawer.component.less'],\n})\nexport class NginxServerDrawerComponent implements OnChanges, OnDestroy {\n @Input() visible = false;\n @Input() editingServer: NginxServer | null = null;\n @Output() visibleChange = new EventEmitter<boolean>();\n @Output() saved = new EventEmitter<void>();\n\n private nginxService = inject(NginxService);\n private message = inject(NzMessageService);\n private cdr = inject(ChangeDetectorRef);\n private resetTimer: ReturnType<typeof setTimeout> | null = null;\n private visibleEmitTimer: ReturnType<typeof setTimeout> | null = null;\n\n saving = signal(false);\n\n readonly commonListenOptions = ['80', '443', '8080', '8443'];\n readonly commonDomainOptions = [ '127.0.0.1','localhost','example.com'];\n readonly commonIndexOptions = ['index.html'];\n\n listenValues: string[] = [];\n domainValues: string[] = ['127.0.0.1'];\n indexValues: string[] = ['index.html'];\n\n formData: CreateNginxServerRequest = {\n name: '',\n listen: [],\n domains: ['127.0.0.1'],\n root: '',\n index: ['index.html'],\n locations: [],\n ssl: false,\n protocol: 'http',\n enabled: true,\n sslCert: '',\n sslKey: '',\n extraConfig: '',\n };\n\n get drawerTitle() {\n return this.editingServer ? '编辑 Server ' : '新增 Server ';\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n const visibleChanged = 'visible' in changes;\n const serverChanged = 'editingServer' in changes;\n const shouldReset =\n (visibleChanged && this.visible) ||\n (serverChanged && this.visible && !changes['editingServer']?.firstChange);\n\n if (shouldReset) {\n this.scheduleResetForm();\n }\n }\n\n ngOnDestroy(): void {\n if (this.resetTimer) {\n clearTimeout(this.resetTimer);\n this.resetTimer = null;\n }\n if (this.visibleEmitTimer) {\n clearTimeout(this.visibleEmitTimer);\n this.visibleEmitTimer = null;\n }\n }\n\n private scheduleResetForm(): void {\n if (this.resetTimer) {\n clearTimeout(this.resetTimer);\n }\n this.resetTimer = setTimeout(() => {\n this.resetTimer = null;\n this.resetForm();\n this.cdr.detectChanges();\n }, 0);\n }\n\n private emitVisibleChange(nextVisible: boolean): void {\n if (this.visibleEmitTimer) {\n clearTimeout(this.visibleEmitTimer);\n }\n this.visibleEmitTimer = setTimeout(() => {\n this.visibleEmitTimer = null;\n this.visibleChange.emit(nextVisible);\n }, 0);\n }\n\n private resetForm(): void {\n if (this.editingServer) {\n const parsedListen = this.parseListenValues(this.editingServer.listen);\n this.formData = {\n name: this.editingServer.name,\n listen: parsedListen.length ? parsedListen : ['80'],\n domains: [...(this.editingServer.domains || [])],\n root: this.editingServer.root || '',\n index: [...(this.editingServer.index?.length ? this.editingServer.index : ['index.html'])],\n locations: this.editingServer.locations.map((l) => ({ ...l })),\n ssl: this.editingServer.ssl,\n protocol: this.editingServer.ssl ? 'https' : 'http',\n enabled: this.editingServer.enabled,\n sslCert: this.editingServer.sslCert || '',\n sslKey: this.editingServer.sslKey || '',\n extraConfig: this.editingServer.extraConfig || '',\n };\n this.listenValues = [...this.formData.listen];\n this.domainValues = [...(this.formData.domains || [])];\n this.indexValues = [...(this.formData.index || ['index.html'])];\n } else {\n this.formData = {\n name: '',\n listen: [],\n domains: ['127.0.0.1'],\n root: '',\n index: ['index.html'],\n locations: [],//{ path: '/', proxyPass: '' }\n ssl: false,\n protocol: 'http',\n enabled: true,\n sslCert: '',\n sslKey: '',\n extraConfig: '',\n };\n this.listenValues = [];\n this.domainValues = ['127.0.0.1'];\n this.indexValues = ['index.html'];\n }\n }\n\n syncDomains(): void {\n this.formData.domains = (this.domainValues || [])\n .flatMap(item => item.split(/[,\\s]+/))\n .map(item => item.trim())\n .filter(item => item.length > 0)\n .filter((item, index, arr) => arr.indexOf(item) === index);\n }\n\n onClose(): void {\n this.emitVisibleChange(false);\n }\n\n syncIndex(): void {\n const normalized = (this.indexValues || [])\n .flatMap(item => String(item || '').split(/[,\\s]+/))\n .map(item => item.trim())\n .filter(Boolean)\n .filter((item, index, arr) => arr.indexOf(item) === index);\n\n this.indexValues = normalized.length ? normalized : ['index.html'];\n this.formData.index = [...this.indexValues];\n }\n\n addLocation(template: 'empty' | 'api' = 'empty'): void {\n const list = [...(this.formData.locations || [])];\n if (template === 'api') {\n list.push({\n path: '/api/',\n proxyPass: 'http://127.0.0.1:6808',\n });\n } else {\n list.push({\n path: '',\n proxyPass: '',\n });\n }\n this.formData.locations = list;\n }\n\n removeLocation(index: number): void {\n const list = [...(this.formData.locations || [])];\n list.splice(index, 1);\n this.formData.locations = list;\n }\n\n previewConfig(): void {\n this.syncListen();\n this.syncDomains();\n this.syncIndex();\n this.normalizeLocations();\n this.message.info('配置预览功能开发中');\n }\n\n async save(): Promise<void> {\n if (!this.formData.name.trim()) {\n this.message.warning('请输入 Server 名称');\n return;\n }\n\n this.syncDomains();\n if (!this.formData.domains?.length) {\n this.message.warning('请至少填写一个域名');\n return;\n }\n\n this.syncListen();\n if (!this.formData.listen.length) {\n this.message.warning('请至少填写一个监听端口');\n return;\n }\n\n if (this.formData.protocol === 'https') {\n if (!this.formData.sslCert?.trim()) {\n this.message.warning('请填写 SSL 证书路径');\n return;\n }\n if (!this.formData.sslKey?.trim()) {\n this.message.warning('请填写 SSL 私钥路径');\n return;\n }\n }\n\n this.syncIndex();\n this.normalizeLocations();\n\n this.saving.set(true);\n try {\n let res: any;\n if (this.editingServer) {\n res = await this.nginxService.updateServer(this.editingServer.id, this.formData);\n } else {\n res = await this.nginxService.createServer(this.formData);\n }\n\n if (res.success) {\n this.message.success(this.editingServer ? 'Server 已更新' : 'Server 已创建');\n this.saved.emit();\n this.emitVisibleChange(false);\n } else {\n this.message.error(res.error || '操作失败');\n }\n } catch (err: any) {\n this.message.error('操作失败: ' + this.extractErrorMessage(err));\n } finally {\n this.saving.set(false);\n }\n }\n\n private syncListen(): void {\n const normalized: string[] = [];\n for (const item of this.listenValues || []) {\n const raw = String(item || '').trim();\n if (!raw) {\n continue;\n }\n const parsed = Number(raw);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {\n this.message.warning(`监听端口 \"${raw}\" 无效,请输入 1-65535 的整数`);\n continue;\n }\n const port = String(parsed);\n if (!normalized.includes(port)) {\n normalized.push(port);\n }\n }\n this.listenValues = normalized;\n this.formData.listen = [...normalized];\n }\n\n private parseListenValues(listenValues?: string[]): string[] {\n const normalized: string[] = [];\n for (const item of listenValues || []) {\n const match = String(item || '').match(/\\d+/);\n if (!match) {\n continue;\n }\n const parsed = Number(match[0]);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {\n continue;\n }\n const port = String(parsed);\n if (!normalized.includes(port)) {\n normalized.push(port);\n }\n }\n return normalized;\n }\n\n private normalizeLocations(): void {\n const normalizeText = (value?: string): string | undefined => {\n const text = String(value || '').trim();\n return text ? text : undefined;\n };\n\n const parseList = (value?: string | string[]): string[] | undefined => {\n if (Array.isArray(value)) {\n const arr = value.map(item => String(item || '').trim()).filter(Boolean);\n return arr.length ? arr : undefined;\n }\n const text = String(value || '').trim();\n if (!text) {\n return undefined;\n }\n const arr = text.split(/\\s+/).map(item => item.trim()).filter(Boolean);\n return arr.length ? arr : undefined;\n };\n\n const normalized: NginxLocation[] = (this.formData.locations || [])\n .map(location => {\n const path = String(location.path || '').trim() || '/';\n const proxyPass = normalizeText(location.proxyPass);\n const root = normalizeText(location.root);\n const index = parseList(location.index as unknown as string | string[]);\n const tryFiles = parseList(location.tryFiles as unknown as string | string[]);\n const rawConfig = normalizeText(location.rawConfig);\n return {\n path,\n proxyPass,\n root,\n index,\n tryFiles,\n rawConfig,\n };\n })\n .filter(location => Boolean(\n location.proxyPass ||\n location.root ||\n location.index?.length ||\n location.tryFiles?.length ||\n location.rawConfig\n ));\n\n this.formData.locations = normalized;\n }\n\n private extractErrorMessage(err: unknown): string {\n const error = err as any;\n return (\n error?.error?.error?.message ||\n error?.error?.message ||\n error?.message ||\n '请求失败'\n );\n }\n}\n", "import {\r\n Component,\r\n EventEmitter,\r\n Input,\r\n OnChanges,\r\n OnInit,\r\n Output,\r\n SimpleChanges,\r\n inject,\r\n signal,\r\n} from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport { FormsModule } from '@angular/forms';\r\nimport { NzButtonModule } from 'ng-zorro-antd/button';\r\nimport { NzIconModule } from 'ng-zorro-antd/icon';\r\nimport { NzMessageService } from 'ng-zorro-antd/message';\r\nimport { NzModalModule, NzModalService } from 'ng-zorro-antd/modal';\r\nimport { NzSpinModule } from 'ng-zorro-antd/spin';\r\nimport { NzTooltipModule } from 'ng-zorro-antd/tooltip';\r\n\r\nimport { NginxService } from '../../services/nginx.service';\r\nimport { NginxServerDrawerComponent } from '../nginx-server-drawer/nginx-server-drawer.component';\r\nimport type { NginxServer } from '../../models/nginx.types';\r\nimport { NzSwitchModule } from 'ng-zorro-antd/switch';\r\nimport { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';\r\n\r\n/**\r\n * Nginx Server 列表组件\r\n * 对齐设计稿 nginx.html 中 server-block-list 样式\r\n */\r\n@Component({\r\n selector: 'app-nginx-server-list',\r\n standalone: true,\r\n imports: [\r\n CommonModule,\r\n FormsModule,\r\n NzButtonModule,\r\n NzIconModule,\r\n NzModalModule,\r\n NzSpinModule,\r\n NzTooltipModule,\r\n NzSwitchModule,\r\n NzPopconfirmModule,\r\n NginxServerDrawerComponent,\r\n ],\r\n templateUrl: './nginx-server-list.component.html',\r\n styleUrls: ['./nginx-server-list.component.less'],\r\n})\r\nexport class NginxServerListComponent implements OnInit, OnChanges {\r\n @Input() showToolbar = true;\r\n @Input() openCreateToken = 0;\r\n @Output() summaryChange = new EventEmitter<{ total: number; enabled: number }>();\r\n @Output() serverListMutated = new EventEmitter<void>();\r\n\r\n private nginxService = inject(NginxService);\r\n private message = inject(NzMessageService);\r\n private modal = inject(NzModalService);\r\n\r\n servers = signal<NginxServer[]>([]);\r\n loading = signal(false);\r\n\r\n drawerVisible = false;\r\n editingServer = signal<NginxServer | null>(null);\r\n\r\n configModalVisible = false;\r\n viewingConfig = signal('');\r\n\r\n ngOnInit() {\r\n this.loadServers();\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges): void {\r\n const openCreate = changes['openCreateToken'];\r\n if (openCreate && !openCreate.firstChange) {\r\n this.openDrawer(null);\r\n }\r\n }\r\n\r\n async loadServers(): Promise<void> {\r\n this.loading.set(true);\r\n try {\r\n const res = await this.nginxService.getServers();\r\n if (res.success && res.servers) {\r\n this.servers.set(res.servers);\r\n this.summaryChange.emit({\r\n total: res.servers.length,\r\n enabled: res.servers.filter(server => server.enabled).length,\r\n });\r\n }\r\n } catch (err: any) {\r\n this.message.error('加载失败: ' + err.message);\r\n } finally {\r\n this.loading.set(false);\r\n }\r\n }\r\n\r\n openDrawer(server: NginxServer | null): void {\n this.editingServer.set(server);\n this.drawerVisible = true;\n }\n\n onDrawerVisibleChange(visible: boolean): void {\n this.drawerVisible = visible;\n if (!visible) {\n this.editingServer.set(null);\n }\n }\n\r\n onSaved(): void {\r\n this.loadServers();\r\n this.serverListMutated.emit();\r\n }\r\n\r\n importServer(): void {\r\n this.message.info('导入功能开发中');\r\n }\r\n\r\n async toggleServer(id: string, enabled: boolean): Promise<void> {\r\n try {\r\n const res = enabled\r\n ? await this.nginxService.enableServer(id)\r\n : await this.nginxService.disableServer(id);\r\n\r\n if (res.success) {\r\n this.message.success(enabled ? '已启用' : '已禁用');\r\n await this.loadServers();\r\n this.serverListMutated.emit();\r\n } else {\r\n this.message.error(res.error || '操作失败');\r\n }\r\n } catch (err: any) {\r\n this.message.error('操作失败: ' + err.message);\r\n }\r\n }\r\n\r\n copyServer(server: NginxServer): void {\r\n this.editingServer.set(server);\r\n this.drawerVisible = true;\r\n this.message.info('复制 Server - 请修改名称后保存');\r\n }\r\n\r\n async deleteServer(server: NginxServer): Promise<void> {\n try {\r\n const res = await this.nginxService.deleteServer(server.id);\r\n if (res.success) {\r\n this.message.success('已删除');\r\n this.loadServers();\r\n this.serverListMutated.emit();\r\n } else {\r\n this.message.error(res.error || '删除失败');\r\n }\r\n } catch (err: any) {\r\n this.message.error('删除失败: ' + err.message);\n }\n }\n\n getAccessUrls(server: NginxServer): string[] {\n return this.buildAccessUrls(server);\n }\n\n private buildAccessUrls(server: NginxServer): string[] {\n const scheme = server.ssl ? 'https' : 'http';\n const ports = this.extractPorts(server.listen);\n const hosts = this.extractHosts(server);\n const selectedPort = ports[0] ?? (server.ssl ? 443 : 80);\n\n if (!hosts.length) {\n return [];\n }\n\n return hosts\n .map(host => this.buildUrl(scheme, host, selectedPort))\n .filter(Boolean)\n .slice(0, 6);\n }\n\n private extractPorts(listen: string[]): number[] {\n const ports = new Set<number>();\n for (const item of listen || []) {\n const port = this.parseListenPort(item);\n if (port !== null) {\n ports.add(port);\n }\n }\n return Array.from(ports.values()).sort((a, b) => a - b);\n }\n\n private parseListenPort(rawListen: string): number | null {\n const text = String(rawListen || '').trim();\n if (!text || /^unix:/i.test(text)) {\n return null;\n }\n const token = text.split(/\\s+/)[0] || '';\n let portToken = token;\n if (/^\\[[^\\]]+\\]:\\d+$/.test(token)) {\n portToken = token.replace(/^.*\\]:/, '');\n } else if (token.includes(':')) {\n portToken = token.slice(token.lastIndexOf(':') + 1);\n }\n const port = Number(portToken);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n return null;\n }\n return port;\n }\n\n private extractHosts(server: NginxServer): string[] {\n const hosts = new Set<string>();\n for (const domain of server.domains || []) {\n const item = String(domain || '').trim();\n if (!item || item === '_' || item === '*') {\n continue;\n }\n hosts.add(item);\n }\n\n if (!hosts.size) {\n const listenHost = this.parseListenHost(server.listen?.[0] || '');\n if (listenHost) {\n hosts.add(listenHost);\n }\n }\n\n if (!hosts.size) {\n hosts.add('127.0.0.1');\n }\n\n return Array.from(hosts.values());\n }\n\n private parseListenHost(rawListen: string): string | null {\n const text = String(rawListen || '').trim();\n if (!text || /^unix:/i.test(text)) {\n return null;\n }\n const token = text.split(/\\s+/)[0] || '';\n if (/^\\[[^\\]]+\\]:\\d+$/.test(token)) {\n const host = token.slice(1, token.indexOf(']')).trim();\n return this.normalizeHost(host);\n }\n if (token.includes(':')) {\n const host = token.slice(0, token.lastIndexOf(':')).trim();\n return this.normalizeHost(host);\n }\n return null;\n }\n\n private normalizeHost(host: string): string | null {\n if (!host || host === '*' || host === '0.0.0.0' || host === '::' || host === '[::]') {\n return null;\n }\n return host;\n }\n\n private buildUrl(scheme: 'http' | 'https', host: string, port: number): string {\n const normalizedHost = this.normalizeHostForUrl(host);\n if (!normalizedHost) {\n return '';\n }\n const hidePort = (scheme === 'http' && port === 80) || (scheme === 'https' && port === 443);\n return `${scheme}://${normalizedHost}${hidePort ? '' : `:${port}`}`;\n }\n\n private normalizeHostForUrl(host: string): string {\n const text = String(host || '').trim();\n if (!text) {\n return '';\n }\n if (text.includes(':') && !text.startsWith('[') && !text.endsWith(']')) {\n return `[${text}]`;\n }\n return text;\n }\n}\n\r\n", "/* angular:styles/component:less;1d06b6323828ffaa434e23281d02e9600bef0e7eacce61bf5884196c0db708c4;D:\\ng-manager\\webapp\\src\\app\\pages\\nginx\\components\\nginx-stat-card\\nginx-stat-card.component.ts */\n:host {\n display: block;\n}\n.stat-card {\n background: var(--bg-white);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 14px;\n box-shadow: var(--shadow-card);\n transition: all 140ms ease;\n}\n.stat-card:hover {\n box-shadow: var(--shadow-hover);\n transform: translateY(-1px);\n}\n.stat-label {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 6px;\n}\n.stat-value {\n font-size: var(--nginx-font-size-kpi, 22px);\n line-height: 1.1;\n font-weight: 800;\n color: var(--text-1);\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n margin-bottom: 4px;\n}\n.stat-value.green {\n color: var(--green);\n}\n.stat-value.blue {\n color: var(--blue);\n}\n.stat-value.purple {\n color: var(--purple);\n}\n.stat-value.orange {\n color: var(--orange);\n}\n.stat-value.red {\n color: var(--red);\n}\n.stat-sub {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace);\n}\n/*# sourceMappingURL=nginx-stat-card.component.css.map */\n", "import { CommonModule } from '@angular/common';\nimport { Component, Input } from '@angular/core';\n\n@Component({\n selector: 'app-nginx-stat-card',\n standalone: true,\n imports: [CommonModule],\n template: `\n <div class=\"stat-card\">\n <div class=\"stat-label\">{{ label }}</div>\n <div class=\"stat-value\" [ngClass]=\"toneClass\">{{ value }}</div>\n <div class=\"stat-sub\">{{ sub }}</div>\n </div>\n `,\n styles: `\n :host {\n display: block;\n }\n\n .stat-card {\n background: var(--bg-white);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 14px;\n box-shadow: var(--shadow-card);\n transition: all 140ms ease;\n\n &:hover {\n box-shadow: var(--shadow-hover);\n transform: translateY(-1px);\n }\n }\n\n .stat-label {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 6px;\n }\n\n .stat-value {\n font-size: var(--nginx-font-size-kpi, 22px);\n line-height: 1.1;\n font-weight: 800;\n color: var(--text-1);\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n margin-bottom: 4px;\n\n &.green {\n color: var(--green);\n }\n\n &.blue {\n color: var(--blue);\n }\n\n &.purple {\n color: var(--purple);\n }\n\n &.orange {\n color: var(--orange);\n }\n\n &.red {\n color: var(--red);\n }\n }\n\n .stat-sub {\n font-size: var(--nginx-font-size-sm, 12px);\n color: var(--text-3);\n font-family: var(--nginx-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace);\n }\n `,\n})\nexport class NginxStatCardComponent {\n @Input() label = '';\n @Input() value: string | number = '-';\n @Input() sub = '';\n @Input() toneClass: 'green' | 'blue' | 'purple' | 'orange' | 'red' | '' = '';\n}\n\r\n", "import { CommonModule } from '@angular/common';\r\nimport { Component, OnDestroy, OnInit, inject, signal } from '@angular/core';\r\nimport { FormsModule } from '@angular/forms';\r\nimport { PageLayoutComponent } from '@app/shared';\r\nimport { NzButtonModule } from 'ng-zorro-antd/button';\r\nimport { NzEmptyModule } from 'ng-zorro-antd/empty';\r\nimport { NzIconModule } from 'ng-zorro-antd/icon';\r\nimport { NzInputModule } from 'ng-zorro-antd/input';\r\nimport { NzLayoutModule } from 'ng-zorro-antd/layout';\r\nimport { NzMessageService } from 'ng-zorro-antd/message';\r\nimport { NzModalModule, NzModalService } from 'ng-zorro-antd/modal';\r\nimport { NzSpinModule } from 'ng-zorro-antd/spin';\r\nimport { NzTabsModule } from 'ng-zorro-antd/tabs';\r\nimport { NzTooltipModule } from 'ng-zorro-antd/tooltip';\r\n\r\nimport { NginxConfigEditorComponent } from './components/nginx-config-editor/nginx-config-editor.component';\r\nimport { LogEntry } from './components/nginx-log-viewer/nginx-log-viewer.component';\r\nimport { NginxSecondaryLogsTabComponent } from './components/nginx-tabs/nginx-secondary-logs-tab/nginx-secondary-logs-tab.component';\r\nimport { NginxSecondaryPerfTabComponent } from './components/nginx-tabs/nginx-secondary-perf-tab/nginx-secondary-perf-tab.component';\r\nimport { NginxSecondarySettingsTabComponent } from './components/nginx-tabs/nginx-secondary-settings-tab/nginx-secondary-settings-tab.component';\r\nimport { NginxSecondarySslTabComponent } from './components/nginx-tabs/nginx-secondary-ssl-tab/nginx-secondary-ssl-tab.component';\r\nimport { NginxSecondaryTestTabComponent } from './components/nginx-tabs/nginx-secondary-test-tab/nginx-secondary-test-tab.component';\r\nimport { NginxSecondaryTrafficTabComponent } from './components/nginx-tabs/nginx-secondary-traffic-tab/nginx-secondary-traffic-tab.component';\r\nimport { NginxSecondaryUpstreamTabComponent } from './components/nginx-tabs/nginx-secondary-upstream-tab/nginx-secondary-upstream-tab.component';\r\nimport { NginxSectionCardComponent } from './components/nginx-section-card/nginx-section-card.component';\r\nimport { NginxServerListComponent } from './components/nginx-server-list/nginx-server-list.component';\r\nimport { NginxStatCardComponent } from './components/nginx-stat-card/nginx-stat-card.component';\r\nimport type { NginxInstance, NginxStatus } from './models/nginx.types';\r\nimport { NginxService } from './services/nginx.service';\r\n\r\ntype SecondaryTab =\r\n | 'upstream'\r\n | 'ssl'\r\n | 'traffic'\r\n | 'perf'\r\n | 'logs'\r\n | 'test'\r\n | 'settings';\r\n\r\ninterface ServerSummary {\r\n total: number;\r\n enabled: number;\r\n}\r\n\r\n@Component({\r\n selector: 'app-nginx',\r\n standalone: true,\r\n imports: [\r\n CommonModule,\r\n FormsModule,\r\n NzButtonModule,\r\n NzIconModule,\r\n NzInputModule,\r\n NzLayoutModule,\r\n NzModalModule,\r\n NzSpinModule,\r\n NzTabsModule,\r\n NzEmptyModule,\r\n NzTooltipModule,\r\n PageLayoutComponent,\r\n NginxConfigEditorComponent,\r\n NginxSecondaryUpstreamTabComponent,\r\n NginxSecondarySslTabComponent,\r\n NginxSecondaryTrafficTabComponent,\r\n NginxSecondaryPerfTabComponent,\r\n NginxSecondaryLogsTabComponent,\r\n NginxSecondaryTestTabComponent,\r\n NginxSecondarySettingsTabComponent,\r\n NginxSectionCardComponent,\r\n NginxServerListComponent,\r\n NginxStatCardComponent,\r\n ],\r\n templateUrl: './nginx.component.html',\r\n styleUrls: ['./nginx.component.less'],\r\n})\r\nexport class NginxComponent implements OnInit, OnDestroy {\r\n private nginxService = inject(NginxService);\r\n private message = inject(NzMessageService);\r\n private modal = inject(NzModalService);\r\n\r\n // Core state\r\n instance = signal<NginxInstance | null>(null);\r\n status = signal<NginxStatus | null>(null);\r\n loading = signal(false);\r\n binding = signal(false);\r\n\r\n // 操作中状态,防止重复点击\r\n controlling = signal(false);\r\n\r\n // UI state\r\n bindModalVisible = false;\r\n bindPath = '';\r\n secondaryTab = signal<SecondaryTab>('upstream');\r\n configExpanded = signal(false);\r\n openServerDrawerToken = signal(0);\r\n configEditorRefreshToken = signal(0);\r\n configLoading = signal(false);\r\n\r\n // Data state\r\n configFiles = signal<string[]>([]);\r\n serverSummary = signal<ServerSummary>({ total: 0, enabled: 0 });\r\n recentLogs = signal<LogEntry[]>([]);\r\n runtimeDisplay = signal('-');\r\n runtimeStartedAtLabel = signal('-');\r\n\r\n private runtimeBaseSeconds: number | null = null;\r\n private runtimeBaseTimestamp = 0;\r\n private runtimeRafId: number | null = null;\r\n private runtimeRenderBucket: number | null = null;\r\n\r\n readonly secondaryTabs: Array<{ id: SecondaryTab; label: string }> = [\r\n { id: 'upstream', label: 'Upstream 管理' },\r\n { id: 'ssl', label: 'SSL 证书' },\r\n { id: 'traffic', label: '流量控制' },\r\n { id: 'perf', label: '性能优化' },\r\n { id: 'logs', label: '日志' },\r\n { id: 'test', label: '配置检测' },\r\n { id: 'settings', label: '设置' },\r\n ];\r\n\r\n async ngOnInit() {\r\n await this.loadStatus();\r\n await this.loadConfigFiles();\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.stopRuntimeTicker();\r\n }\r\n\r\n switchSecondaryTab(tab: SecondaryTab) {\r\n this.secondaryTab.set(tab);\r\n }\r\n\r\n onSecondaryTabIndexChange(index: number) {\r\n const target = this.secondaryTabs[index];\r\n if (target) {\r\n this.secondaryTab.set(target.id);\r\n }\r\n }\r\n\r\n secondaryTabIndex(): number {\r\n const index = this.secondaryTabs.findIndex(tab => tab.id === this.secondaryTab());\r\n return index >= 0 ? index : 0;\r\n }\r\n\r\n toggleConfigExpanded() {\r\n this.configExpanded.update(open => !open);\r\n }\r\n\r\n requestCreateServer() {\r\n this.openServerDrawerToken.update(token => token + 1);\r\n }\r\n\r\n importServer() {\r\n this.message.info('导入功能开发中');\r\n }\r\n\r\n onServerSummaryChange(summary: ServerSummary) {\r\n this.serverSummary.set(summary);\r\n }\r\n\r\n onServerListMutated() {\r\n void this.loadConfigFiles();\r\n if (this.configExpanded()) {\r\n this.configEditorRefreshToken.update(token => token + 1);\r\n }\r\n }\r\n\r\n get serviceStatusText(): string {\r\n return this.status()?.isRunning ? '运行中' : '已停止';\r\n }\r\n\r\n get uptimeText(): string {\r\n if (!this.status()?.isRunning) {\r\n return '-';\r\n }\r\n return this.runtimeDisplay() || this.status()?.uptime || '-';\r\n }\r\n\r\n get uptimeSubText(): string {\r\n if (!this.status()?.isRunning) {\r\n return '服务未运行';\r\n }\r\n const startedAt = this.runtimeStartedAtLabel();\r\n if (startedAt && startedAt !== '-') {\r\n return `开始时间 ${startedAt}`;\r\n }\r\n return '开始时间未知';\r\n }\r\n\r\n get pidText(): string {\r\n return this.status()?.pid ? `PID ${this.status()?.pid}` : '未检测到 PID';\r\n }\r\n\r\n get activeConnectionText(): string {\r\n const activeConnections = this.status()?.activeConnections;\r\n if (Number.isFinite(activeConnections)) {\r\n return String(activeConnections);\r\n }\r\n return this.status()?.isRunning ? 'N/A' : '-';\r\n }\r\n\r\n get activeConnectionSubText(): string {\r\n const activeConnections = this.status()?.activeConnections;\r\n if (!this.status()?.isRunning) {\r\n return '服务未运行';\r\n }\r\n if (Number.isFinite(activeConnections)) {\r\n return 'ESTABLISHED TCP 连接数';\r\n }\r\n return '当前环境暂不可用';\r\n }\r\n\r\n get enabledServerText(): string {\r\n const summary = this.serverSummary();\r\n return `${summary.enabled} 启用 / ${Math.max(summary.total - summary.enabled, 0)} 禁用`;\r\n }\r\n\r\n async refreshAll() {\r\n await Promise.all([this.loadStatus(), this.loadConfigFiles()]);\r\n this.message.success('状态已刷新');\r\n }\r\n\r\n async loadStatus() {\r\n this.loading.set(true);\r\n try {\r\n const stats = await this.nginxService.getStats();\r\n if (stats.success && stats.status) {\r\n this.instance.set(stats.instance || null);\r\n this.status.set(stats.status);\r\n this.syncRuntimeState(stats.status);\r\n if (stats.serverSummary) {\r\n this.serverSummary.set({\r\n total: stats.serverSummary.total,\r\n enabled: stats.serverSummary.enabled,\r\n });\r\n }\r\n return;\r\n }\r\n\r\n const res = await this.nginxService.getStatus();\r\n this.instance.set(res.instance);\r\n this.status.set(res.status);\r\n this.syncRuntimeState(res.status);\r\n } catch {\r\n // 静默失败,等待用户手动绑定\r\n } finally {\r\n this.loading.set(false);\r\n }\r\n }\r\n\r\n async loadConfigFiles() {\r\n this.configLoading.set(true);\r\n try {\r\n const res = await this.nginxService.getConfigFiles();\r\n this.configFiles.set(res.success ? res.files || [] : []);\r\n } catch {\r\n this.configFiles.set([]);\r\n } finally {\r\n this.configLoading.set(false);\r\n }\r\n }\r\n\r\n showBindModal() {\r\n this.bindPath = this.getBindPathCandidates()[0] || '';\r\n this.bindModalVisible = true;\r\n }\r\n\r\n showPathHint() {\r\n this.message.info('当前版本请手动输入路径,文件浏览选择能力后续接入');\r\n }\r\n\r\n autoDetectBindPath() {\r\n const candidates = this.getBindPathCandidates();\r\n if (!candidates.length) {\r\n this.message.warning('未识别到可用的默认路径,请手动输入');\r\n return;\r\n }\r\n\r\n this.bindPath = candidates[0];\r\n this.message.success(`已填充路径:${this.bindPath}`);\r\n }\r\n\r\n async bindNginx() {\r\n if (!this.bindPath.trim()) {\r\n this.message.warning('请输入 Nginx 路径');\r\n return;\r\n }\r\n\r\n this.binding.set(true);\r\n try {\r\n const res = await this.nginxService.bind(this.bindPath.trim());\r\n if (res.success && res.instance) {\r\n this.instance.set(res.instance);\r\n this.bindModalVisible = false;\r\n this.message.success('绑定成功');\r\n await this.refreshAll();\r\n } else {\r\n this.message.error(res.error || '绑定失败');\r\n }\r\n } catch (err: any) {\r\n this.message.error('绑定失败: ' + err.message);\r\n } finally {\r\n this.binding.set(false);\r\n }\r\n }\r\n\r\n unbind() {\r\n this.modal.confirm({\r\n nzTitle: '确认解绑',\r\n nzContent: '解绑后将无法管理 Nginx,是否继续?',\r\n nzOkText: '解绑',\r\n nzOkType: 'primary',\r\n nzOnOk: async () => {\r\n try {\r\n await this.nginxService.unbind();\r\n this.instance.set(null);\r\n this.status.set(null);\r\n this.syncRuntimeState(null);\r\n this.serverSummary.set({ total: 0, enabled: 0 });\r\n this.configFiles.set([]);\r\n this.message.success('解绑成功');\r\n } catch (err: any) {\r\n this.message.error('解绑失败: ' + err.message);\r\n }\r\n },\r\n });\r\n }\r\n\r\n async startNginx() {\r\n if (this.controlling()) return;\r\n this.controlling.set(true);\r\n this.loading.set(true);\r\n try {\r\n const res = await this.nginxService.start();\r\n if (res.success) {\r\n this.appendLog('ok', 'nginx start executed');\r\n this.message.success('启动成功');\r\n await this.loadStatus();\r\n } else {\r\n this.message.error(res.error || '启动失败');\r\n }\r\n } catch (err: any) {\r\n this.message.error('启动失败: ' + err.message);\r\n } finally {\r\n this.loading.set(false);\r\n this.controlling.set(false);\r\n }\r\n }\r\n\r\n async stopNginx() {\r\n if (this.controlling()) return;\r\n this.controlling.set(true);\r\n this.loading.set(true);\r\n try {\r\n const res = await this.nginxService.stop();\r\n if (res.success) {\r\n this.appendLog('warn', 'nginx stop executed');\r\n this.message.success('停止成功');\r\n await this.loadStatus();\r\n } else {\r\n this.message.error(res.error || '停止失败');\r\n }\r\n } catch (err: any) {\r\n this.message.error('停止失败: ' + err.message);\r\n } finally {\r\n this.loading.set(false);\r\n this.controlling.set(false);\r\n }\r\n }\r\n\r\n async reloadNginx() {\r\n if (this.controlling()) return;\r\n this.controlling.set(true);\r\n this.loading.set(true);\r\n try {\r\n const res = await this.nginxService.reload();\r\n if (res.success) {\r\n this.appendLog('ok', 'nginx reload executed');\r\n this.message.success('重载成功');\r\n await this.loadStatus();\r\n } else {\r\n this.message.error(res.error || '重载失败');\r\n }\r\n } catch (err: any) {\r\n this.message.error('重载失败: ' + err.message);\r\n } finally {\r\n this.loading.set(false);\r\n this.controlling.set(false);\r\n }\r\n }\r\n\r\n async restartNginx() {\r\n if (this.controlling()) return;\r\n this.controlling.set(true);\r\n this.loading.set(true);\r\n try {\r\n // 先停止\r\n const stopRes = await this.nginxService.stop();\r\n if (!stopRes.success) {\r\n this.message.error('重启失败: ' + (stopRes.error || '停止失败'));\r\n return;\r\n }\r\n // 再启动\r\n const startRes = await this.nginxService.start();\r\n if (startRes.success) {\r\n this.appendLog('info', 'nginx restart executed');\r\n this.message.success('重启成功');\r\n await this.loadStatus();\r\n } else {\r\n this.message.error('重启失败: ' + (startRes.error || '启动失败'));\r\n }\r\n } catch (err: any) {\r\n this.message.error('重启失败: ' + err.message);\r\n } finally {\r\n this.loading.set(false);\r\n this.controlling.set(false);\r\n }\r\n }\r\n\r\n async testConfig() {\r\n this.loading.set(true);\r\n try {\r\n const res = await this.nginxService.test();\r\n if (res.valid) {\r\n this.appendLog('ok', 'configuration test passed');\r\n this.message.success('配置验证通过');\r\n if (res.warnings?.length) {\r\n res.warnings.forEach((w: string) => this.message.warning(w));\r\n }\r\n } else {\r\n this.appendLog('error', 'configuration test failed');\r\n this.message.error('配置验证失败');\r\n res.errors?.forEach((e: string) => this.message.error(e));\r\n }\r\n } catch (err: any) {\r\n this.message.error('测试失败: ' + err.message);\r\n } finally {\r\n this.loading.set(false);\r\n }\r\n }\r\n\r\n private appendLog(level: LogEntry['level'], msg: string) {\r\n const time = new Date().toLocaleTimeString('zh-CN', {\r\n hour12: false,\r\n hour: '2-digit',\r\n minute: '2-digit',\r\n second: '2-digit',\r\n });\r\n\r\n this.recentLogs.update(logs => [{ time, level, msg }, ...logs].slice(0, 120));\r\n }\r\n\r\n private getBindPathCandidates(): string[] {\r\n const ua = typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase() : '';\r\n\r\n if (ua.includes('windows')) {\r\n return ['C:\\\\nginx\\\\nginx.exe', 'D:\\\\nginx\\\\nginx.exe'];\r\n }\r\n\r\n if (ua.includes('mac')) {\r\n return ['/opt/homebrew/bin/nginx', '/usr/local/bin/nginx'];\r\n }\r\n\r\n return ['/usr/sbin/nginx', '/usr/local/nginx/sbin/nginx', '/usr/local/bin/nginx'];\r\n }\r\n\r\n private syncRuntimeState(status: NginxStatus | null): void {\r\n if (!status?.isRunning) {\r\n this.stopRuntimeTicker();\r\n this.runtimeBaseSeconds = null;\r\n this.runtimeBaseTimestamp = 0;\r\n this.runtimeDisplay.set('-');\r\n this.runtimeStartedAtLabel.set('-');\r\n return;\r\n }\r\n\r\n const seconds = this.parseUptimeSeconds(status.uptime);\r\n if (seconds === null) {\r\n this.stopRuntimeTicker();\r\n this.runtimeBaseSeconds = null;\r\n this.runtimeBaseTimestamp = 0;\r\n this.runtimeDisplay.set(status.uptime || '-');\r\n this.runtimeStartedAtLabel.set('-');\r\n return;\r\n }\r\n\r\n this.runtimeBaseSeconds = seconds;\r\n this.runtimeBaseTimestamp = Date.now();\r\n this.runtimeRenderBucket = null;\r\n const startedAt = new Date(this.runtimeBaseTimestamp - seconds * 1000);\r\n this.runtimeStartedAtLabel.set(this.formatDateTime(startedAt));\r\n this.updateRuntimeDisplay();\r\n this.startRuntimeTicker();\r\n }\r\n\r\n private startRuntimeTicker(): void {\r\n if (typeof window === 'undefined') {\r\n return;\r\n }\r\n if (this.runtimeRafId !== null) {\r\n return;\r\n }\r\n\r\n const tick = () => {\r\n if (this.runtimeBaseSeconds === null || !this.status()?.isRunning) {\r\n this.stopRuntimeTicker();\r\n return;\r\n }\r\n this.updateRuntimeDisplay();\r\n this.runtimeRafId = window.requestAnimationFrame(tick);\r\n };\r\n\r\n this.runtimeRafId = window.requestAnimationFrame(tick);\r\n }\r\n\r\n private stopRuntimeTicker(): void {\r\n if (typeof window === 'undefined') {\r\n return;\r\n }\r\n if (this.runtimeRafId !== null) {\r\n window.cancelAnimationFrame(this.runtimeRafId);\r\n this.runtimeRafId = null;\r\n }\r\n this.runtimeRenderBucket = null;\r\n }\r\n\r\n private updateRuntimeDisplay(): void {\r\n if (this.runtimeBaseSeconds === null) {\r\n return;\r\n }\r\n\r\n const elapsed = this.runtimeBaseSeconds + Math.max(0, Math.floor((Date.now() - this.runtimeBaseTimestamp) / 1000));\r\n const useMinuteBucket = elapsed >= 86400;\r\n const bucket = useMinuteBucket ? Math.floor(elapsed / 60) : elapsed;\r\n if (this.runtimeRenderBucket === bucket) {\r\n return;\r\n }\r\n this.runtimeRenderBucket = bucket;\r\n this.runtimeDisplay.set(this.formatElapsed(elapsed));\r\n }\r\n\r\n private parseUptimeSeconds(value?: string): number | null {\r\n const text = String(value || '').trim();\r\n if (!text) {\r\n return null;\r\n }\r\n\r\n let match = text.match(/^(\\d+)\\s*d\\s*(\\d{1,2}):(\\d{1,2}):(\\d{1,2})$/i);\r\n if (match) {\r\n const days = Number(match[1]);\r\n const hours = Number(match[2]);\r\n const minutes = Number(match[3]);\r\n const seconds = Number(match[4]);\r\n return days * 86400 + hours * 3600 + minutes * 60 + seconds;\r\n }\r\n\r\n match = text.match(/^(\\d+)-(\\d{1,2}):(\\d{1,2}):(\\d{1,2})$/);\r\n if (match) {\r\n const days = Number(match[1]);\r\n const hours = Number(match[2]);\r\n const minutes = Number(match[3]);\r\n const seconds = Number(match[4]);\r\n return days * 86400 + hours * 3600 + minutes * 60 + seconds;\r\n }\r\n\r\n match = text.match(/^(\\d{1,2}):(\\d{1,2}):(\\d{1,2})$/);\r\n if (match) {\r\n const hours = Number(match[1]);\r\n const minutes = Number(match[2]);\r\n const seconds = Number(match[3]);\r\n return hours * 3600 + minutes * 60 + seconds;\r\n }\r\n\r\n match = text.match(/^(\\d{1,2}):(\\d{1,2})$/);\r\n if (match) {\r\n const minutes = Number(match[1]);\r\n const seconds = Number(match[2]);\r\n return minutes * 60 + seconds;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n private formatElapsed(seconds: number): string {\r\n const total = Math.max(0, Math.floor(seconds));\r\n if (total >= 86400) {\r\n const days = Math.floor(total / 86400);\r\n const hours = Math.floor((total % 86400) / 3600);\r\n const minutes = Math.floor((total % 3600) / 60);\r\n return `${days}天 ${hours}小时 ${minutes}分钟`;\r\n }\r\n\r\n const hours = Math.floor(total / 3600);\r\n const minutes = Math.floor((total % 3600) / 60);\r\n const remainSeconds = total % 60;\r\n const hh = String(hours).padStart(2, '0');\r\n const mm = String(minutes).padStart(2, '0');\r\n const ss = String(remainSeconds).padStart(2, '0');\r\n return `${hh}:${mm}:${ss}`;\r\n }\r\n\r\n private formatDateTime(date: Date): string {\r\n const year = date.getFullYear();\r\n const month = String(date.getMonth() + 1).padStart(2, '0');\r\n const day = String(date.getDate()).padStart(2, '0');\r\n const hours = String(date.getHours()).padStart(2, '0');\r\n const minutes = String(date.getMinutes()).padStart(2, '0');\r\n const seconds = String(date.getSeconds()).padStart(2, '0');\r\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;\r\n }\r\n}\r\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAC,uCAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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",
6
- "names": ["nginx_component_default", "NginxService", "NginxConfigEditorComponent", "NginxLogViewerComponent", "NginxSecondaryLogsTabComponent", "NginxModuleStore", "NginxSecondaryPerfTabComponent", "NginxSecondarySettingsTabComponent", "NginxSecondarySslTabComponent", "NginxSecondaryTestTabComponent", "NginxSecondaryTrafficTabComponent", "NginxSecondaryUpstreamTabComponent", "NginxSectionCardComponent", "nginx_server_list_component_default", "nginx_server_drawer_component_default", "NginxServerDrawerComponent", "arr", "NginxServerListComponent", "NginxStatCardComponent", "NginxComponent", "hours", "minutes"]
7
- }