diff --git a/apps/controller/log.go b/apps/controller/log.go
index efa6ca7..bd9f9f5 100644
--- a/apps/controller/log.go
+++ b/apps/controller/log.go
@@ -7,6 +7,7 @@ import (
"hy2xs-admin/model/constant"
"hy2xs-admin/model/dto"
"hy2xs-admin/model/vo"
+ "hy2xs-admin/service"
"hy2xs-admin/util"
"time"
)
@@ -55,34 +56,17 @@ func LogHysteria2(c *gin.Context) {
if err != nil {
return
}
- exists := util.Exists(constant.Hysteria2LogPath)
logHysteria2Vos := make([]vo.LogHysteria2Vo, 0)
- if !exists {
- vo.Success(logHysteria2Vos, c)
- return
- }
numLine := 0
if logSystemDto.NumLine != nil && *logSystemDto.NumLine > 0 {
numLine = *logSystemDto.NumLine
}
- logLines, total, err := util.ReadLinesFromBottom(constant.Hysteria2LogPath, numLine)
+ logHysteria2Vos, total, err := service.ReadHysteriaJournalLogs(numLine)
if err != nil {
- vo.Fail("Unable to read log file", c)
+ vo.Fail("Unable to read hysteria journal logs", c)
return
}
- for _, line := range logLines {
- if line == "" {
- continue
- }
- logHysteria2Vo := vo.LogHysteria2Vo{}
- err := json.Unmarshal([]byte(line), &logHysteria2Vo)
- if err != nil {
- vo.Fail("Unable to unmarshal log data", c)
- continue
- }
- logHysteria2Vos = append(logHysteria2Vos, logHysteria2Vo)
- }
vo.Success(vo.LogSystemPage[vo.LogHysteria2Vo]{
LogSystemVos: logHysteria2Vos,
Total: int64(total),
@@ -102,7 +86,16 @@ func ExportLog(c *gin.Context) {
filePath = constant.SystemLogPath
} else if *logExportDto.Option == 1 {
fileName = fmt.Sprintf("hysteria2-%s.log", time.Now().Format("20060102150405"))
- filePath = constant.Hysteria2LogPath
+ output, exportErr := service.ExportHysteriaJournalLogs(5000)
+ if exportErr != nil {
+ vo.Fail("failed to export hysteria journal logs", c)
+ return
+ }
+ c.Header("Content-Type", "text/plain; charset=utf-8")
+ c.Header("Content-Transfer-Encoding", "binary")
+ c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
+ c.Data(200, "text/plain; charset=utf-8", []byte(output))
+ return
}
if !util.Exists(filePath) {
diff --git a/apps/frontend/src/App.vue b/apps/frontend/src/App.vue
index f88dc24..c8c86b9 100644
--- a/apps/frontend/src/App.vue
+++ b/apps/frontend/src/App.vue
@@ -5,7 +5,7 @@ const appStore = useAppStore();
-
+
diff --git a/apps/frontend/src/assets/icons/size.svg b/apps/frontend/src/assets/icons/size.svg
deleted file mode 100644
index ddb25b8..0000000
--- a/apps/frontend/src/assets/icons/size.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/frontend/src/components/SizeSelect/index.vue b/apps/frontend/src/components/SizeSelect/index.vue
deleted file mode 100644
index 2e0cf28..0000000
--- a/apps/frontend/src/components/SizeSelect/index.vue
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- {{ item.label }}
-
-
-
-
-
-
-
diff --git a/apps/frontend/src/lang/package/en.ts b/apps/frontend/src/lang/package/en.ts
index 96d0d45..7aa5a56 100644
--- a/apps/frontend/src/lang/package/en.ts
+++ b/apps/frontend/src/lang/package/en.ts
@@ -112,10 +112,6 @@ export default {
resetTrafficConfirm: "Are you sure to reset peer traffic?",
invalid: "Invalid value",
switchLanguageSuccess: "Language switched successfully",
- sizeChanged: "Interface size changed",
- sizeDefault: "Default",
- sizeLarge: "Large",
- sizeSmall: "Small",
logoutConfirm: "Are you sure you want to log out?",
sessionExpired: "Current session has expired, please log in again",
},
diff --git a/apps/frontend/src/lang/package/ru.ts b/apps/frontend/src/lang/package/ru.ts
index 9564735..dee9f3a 100644
--- a/apps/frontend/src/lang/package/ru.ts
+++ b/apps/frontend/src/lang/package/ru.ts
@@ -108,10 +108,6 @@ export default {
resetTrafficConfirm: "Сбросить трафик для пира?",
invalid: "Некорректное значение",
switchLanguageSuccess: "Язык переключён",
- sizeChanged: "Размер интерфейса изменён",
- sizeDefault: "Обычный",
- sizeLarge: "Крупный",
- sizeSmall: "Компактный",
logoutConfirm: "Выйти из системы?",
sessionExpired: "Текущая сессия истекла, войдите снова",
},
diff --git a/apps/frontend/src/layout/components/Navbar.vue b/apps/frontend/src/layout/components/Navbar.vue
index e6eeb49..3962776 100644
--- a/apps/frontend/src/layout/components/Navbar.vue
+++ b/apps/frontend/src/layout/components/Navbar.vue
@@ -53,7 +53,7 @@ function logout() {
-
+
-
+
@@ -71,30 +71,28 @@ function logout() {
:icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t("navbar.logout") }}
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("navbar.logout") }}
+
+
+
+
+
@@ -108,17 +106,24 @@ function logout() {
background-color: #fff;
box-shadow: 0 0 1px #0003;
- .setting-container {
+ .navbar-left,
+ .navbar-actions,
+ .setting-container,
+ .avatar-dropdown {
display: flex;
align-items: center;
+ height: 100%;
+ }
+ .setting-container {
.setting-item {
- display: inline-block;
+ display: flex;
+ align-items: center;
+ justify-content: center;
width: 30px;
height: 50px;
- line-height: 50px;
+ line-height: normal;
color: #5a5e66;
- text-align: center;
cursor: pointer;
&:hover {
@@ -132,8 +137,8 @@ function logout() {
align-items: center;
justify-content: center;
gap: 6px;
- height: 36px;
- margin: 0 8px;
+ height: 50px;
+ margin: 0;
padding: 0 8px;
border-radius: 8px;
cursor: pointer;
diff --git a/apps/frontend/src/layout/index.vue b/apps/frontend/src/layout/index.vue
index 1695a19..2bae3dd 100644
--- a/apps/frontend/src/layout/index.vue
+++ b/apps/frontend/src/layout/index.vue
@@ -110,7 +110,7 @@ function handleOutsideClick() {
}
.hideSidebar .fixed-header {
- width: calc(100% - 54px);
+ width: calc(100% - #{$sideBarCollapsedWidth});
}
.mobile .fixed-header {
diff --git a/apps/frontend/src/settings.ts b/apps/frontend/src/settings.ts
index 234f53d..2bd7258 100644
--- a/apps/frontend/src/settings.ts
+++ b/apps/frontend/src/settings.ts
@@ -34,11 +34,6 @@ interface DefaultSettings {
*/
theme: string;
- /**
- * Размер интерфейса
- */
- size: string;
-
/**
* Язык
*/
@@ -60,7 +55,6 @@ const defaultSettings: DefaultSettings = {
* light: Светлая тема
*/
theme: "dark",
- size: "default", // default |large |small
language: "ru", // ru | en
};
diff --git a/apps/frontend/src/store/modules/app.ts b/apps/frontend/src/store/modules/app.ts
index 0d28057..da77f6f 100644
--- a/apps/frontend/src/store/modules/app.ts
+++ b/apps/frontend/src/store/modules/app.ts
@@ -9,7 +9,6 @@ import ru from "element-plus/es/locale/lang/ru";
export const useAppStore = defineStore("app", () => {
// state
const device = useStorage("device", "desktop");
- const size = useStorage
("size", defaultSettings.size);
const language = useStorage("language", defaultSettings.language);
const sidebarStatus = useStorage("sidebarStatus", "closed");
@@ -52,9 +51,6 @@ export const useAppStore = defineStore("app", () => {
device.value = val;
}
- function changeSize(val: string) {
- size.value = val;
- }
/**
* Переключение языка
*
@@ -69,9 +65,7 @@ export const useAppStore = defineStore("app", () => {
sidebar,
language,
locale,
- size,
toggleDevice,
- changeSize,
changeLanguage,
toggleSidebar,
closeSideBar,
diff --git a/apps/frontend/src/styles/sidebar.scss b/apps/frontend/src/styles/sidebar.scss
index 1cb1b46..1e24cf2 100644
--- a/apps/frontend/src/styles/sidebar.scss
+++ b/apps/frontend/src/styles/sidebar.scss
@@ -109,7 +109,7 @@
.hideSidebar {
.sidebar-container {
- width: 54px !important;
+ width: $sideBarCollapsedWidth !important;
.svg-icon {
margin-right: 0;
@@ -117,7 +117,26 @@
}
.main-container {
- margin-left: 54px;
+ margin-left: $sideBarCollapsedWidth;
+ }
+
+ .sidebar-container .el-menu--collapse {
+ width: $sideBarCollapsedWidth !important;
+ }
+
+ .sidebar-container .el-menu--collapse > .el-menu-item,
+ .sidebar-container .el-menu--collapse > .el-sub-menu > .el-sub-menu__title {
+ justify-content: center;
+ gap: 0;
+ padding: 0 !important;
+ }
+
+ .sidebar-container .el-menu--collapse .svg-icon {
+ display: block;
+ flex: 0 0 18px;
+ width: 18px;
+ height: 18px;
+ margin: 0;
}
.el-sub-menu {
@@ -146,7 +165,7 @@
}
.el-menu--collapse .el-menu .el-sub-menu {
- min-width: $sideBarWidth !important;
+ min-width: $sideBarCollapsedWidth !important;
}
// mobile responsive
@@ -218,4 +237,3 @@
}
}
-
diff --git a/apps/frontend/src/styles/variables.scss b/apps/frontend/src/styles/variables.scss
index d2a0e6a..b2fbd48 100644
--- a/apps/frontend/src/styles/variables.scss
+++ b/apps/frontend/src/styles/variables.scss
@@ -20,5 +20,6 @@ $subMenuActiveText: var(--subMenuActiveText);
$subMenuHover: var(--subMenuHover);
$sideBarWidth: 210px;
+$sideBarCollapsedWidth: 54px;
diff --git a/apps/frontend/src/types/components.d.ts b/apps/frontend/src/types/components.d.ts
index 52b9c2b..8c3c32c 100644
--- a/apps/frontend/src/types/components.d.ts
+++ b/apps/frontend/src/types/components.d.ts
@@ -60,7 +60,6 @@ declare module '@vue/runtime-core' {
RightPanel: typeof import('./../components/RightPanel/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
- SizeSelect: typeof import('./../components/SizeSelect/index.vue')['default']
SvgIcon: typeof import('./../components/SvgIcon/index.vue')['default']
UnitSelect: typeof import('./../components/UnitSelect/index.vue')['default']
}
diff --git a/apps/middleware/log.go b/apps/middleware/log.go
index a4b385a..e485de6 100644
--- a/apps/middleware/log.go
+++ b/apps/middleware/log.go
@@ -18,26 +18,32 @@ func InitLog() {
LocalTime: true,
})
logrus.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"})
- logrus.SetLevel(logrus.WarnLevel)
+ logrus.SetLevel(logrus.InfoLevel)
}
func LogHandler() gin.HandlerFunc {
return func(c *gin.Context) {
startTime := time.Now()
- endTime := time.Now()
- statusCode := c.Writer.Status()
- latencyTime := endTime.Sub(startTime)
- clientIP := c.ClientIP()
- reqMethod := c.Request.Method
- reqUri := c.Request.RequestURI
- logrus.WithFields(logrus.Fields{
- "statusCode": statusCode,
- "latencyTime": latencyTime,
- "clientIP": clientIP,
- "reqMethod": reqMethod,
- "reqUri": reqUri,
- }).Info()
c.Next()
+
+ statusCode := c.Writer.Status()
+ latencyTime := time.Since(startTime)
+
+ entry := logrus.WithFields(logrus.Fields{
+ "statusCode": statusCode,
+ "latencyTime": latencyTime.Milliseconds(),
+ "clientIP": c.ClientIP(),
+ "reqMethod": c.Request.Method,
+ "reqUri": c.Request.RequestURI,
+ })
+
+ if statusCode >= 500 {
+ entry.Error()
+ } else if statusCode >= 400 {
+ entry.Warn()
+ } else {
+ entry.Info()
+ }
}
}
diff --git a/apps/model/constant/system.go b/apps/model/constant/system.go
index 79e4c7f..4d3ad82 100644
--- a/apps/model/constant/system.go
+++ b/apps/model/constant/system.go
@@ -25,8 +25,7 @@ var (
Hysteria2ConfigPath = "/etc/hysteria/config.yaml"
Hysteria2BinPath = "/usr/local/bin/hysteria"
- SystemLogPath = filepath.Join(LogDir, "hy2xs-admin.log")
- Hysteria2LogPath = filepath.Join(LogDir, "hysteria2.log")
+ SystemLogPath = filepath.Join(LogDir, "hy2xs-admin.log")
)
const (
diff --git a/apps/service/journal.go b/apps/service/journal.go
new file mode 100644
index 0000000..32ddbfa
--- /dev/null
+++ b/apps/service/journal.go
@@ -0,0 +1,120 @@
+package service
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "hy2xs-admin/model/vo"
+ "hy2xs-admin/util"
+ "strconv"
+ "strings"
+ "time"
+)
+
+type journalctlLogLine struct {
+ Message string `json:"MESSAGE"`
+ Priority string `json:"PRIORITY"`
+ RealtimeTimestamp string `json:"__REALTIME_TIMESTAMP"`
+}
+
+func ReadHysteriaJournalLogs(numLine int) ([]vo.LogHysteria2Vo, int, error) {
+ lines := 100
+ if numLine > 0 {
+ lines = numLine
+ }
+
+ output, err := util.Exec(fmt.Sprintf("journalctl --no-pager -u hysteria-server.service -n %d -o json", lines))
+ if err != nil {
+ return nil, 0, err
+ }
+
+ result := make([]vo.LogHysteria2Vo, 0, lines)
+ scanner := bufio.NewScanner(strings.NewReader(output))
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" {
+ continue
+ }
+
+ item := journalctlLogLine{}
+ if err = json.Unmarshal([]byte(line), &item); err != nil {
+ continue
+ }
+
+ result = append(result, parseHysteriaJournalRecord(item))
+ }
+
+ if scanErr := scanner.Err(); scanErr != nil {
+ return nil, 0, scanErr
+ }
+
+ return result, len(result), nil
+}
+
+func ExportHysteriaJournalLogs(numLine int) (string, error) {
+ lines := 5000
+ if numLine > 0 {
+ lines = numLine
+ }
+
+ return util.Exec(fmt.Sprintf("journalctl --no-pager -u hysteria-server.service -n %d -o short-iso", lines))
+}
+
+func parseHysteriaJournalRecord(item journalctlLogLine) vo.LogHysteria2Vo {
+ fallbackTime := convertJournalTimestamp(item.RealtimeTimestamp)
+ fallbackLevel := mapJournalPriorityToLevel(item.Priority)
+
+ if strings.TrimSpace(item.Message) == "" {
+ return vo.LogHysteria2Vo{
+ Level: fallbackLevel,
+ Msg: "",
+ Time: fallbackTime,
+ }
+ }
+
+ parsed := vo.LogHysteria2Vo{}
+ if err := json.Unmarshal([]byte(item.Message), &parsed); err == nil {
+ if parsed.Level == "" {
+ parsed.Level = fallbackLevel
+ }
+ if parsed.Time == "" {
+ parsed.Time = fallbackTime
+ }
+ if parsed.Msg == "" {
+ parsed.Msg = item.Message
+ }
+ return parsed
+ }
+
+ return vo.LogHysteria2Vo{
+ Level: fallbackLevel,
+ Msg: item.Message,
+ Time: fallbackTime,
+ }
+}
+
+func convertJournalTimestamp(raw string) string {
+ if raw == "" {
+ return ""
+ }
+
+ us, err := strconv.ParseInt(raw, 10, 64)
+ if err != nil {
+ return ""
+ }
+
+ t := time.UnixMicro(us)
+ return t.Format("2006-01-02 15:04:05")
+}
+
+func mapJournalPriorityToLevel(priority string) string {
+ switch strings.TrimSpace(priority) {
+ case "0", "1", "2", "3":
+ return "error"
+ case "4":
+ return "warn"
+ default:
+ return "info"
+ }
+}
+
diff --git a/docs/12-operations-and-troubleshooting.md b/docs/12-operations-and-troubleshooting.md
index 57b4eea..60440c3 100644
--- a/docs/12-operations-and-troubleshooting.md
+++ b/docs/12-operations-and-troubleshooting.md
@@ -73,6 +73,10 @@ journalctl -u hysteria-server -n 100 --no-pager
journalctl -u hy2xs-admin -n 100 --no-pager
```
+Источник логов в UI:
+- Страница «Логи Hysteria» читает записи из journald unit `hysteria-server.service`.
+- Экспорт «Логи Hysteria» также формируется из journald (`journalctl`), а не из отдельного файла `hysteria2.log`.
+
Проверка install-state marker:
```bash
cat /var/lib/hy2xs/install-state.json
diff --git a/package/systemd/hy2xs-admin.service b/package/systemd/hy2xs-admin.service
index 1384fb8..3f88b55 100644
--- a/package/systemd/hy2xs-admin.service
+++ b/package/systemd/hy2xs-admin.service
@@ -7,6 +7,7 @@ Wants=network-online.target
Type=simple
User=hy2xs-admin
Group=hy2xs-admin
+SupplementaryGroups=systemd-journal
WorkingDirectory={{INSTALL_DIR}}
EnvironmentFile=/etc/hy2xs/hy2xs.env
Environment=GIN_MODE=release
diff --git a/package/systemd/hysteria-server.service b/package/systemd/hysteria-server.service
index 2c6bb2a..b624df2 100644
--- a/package/systemd/hysteria-server.service
+++ b/package/systemd/hysteria-server.service
@@ -7,6 +7,8 @@ Wants=network-online.target
Type=simple
User=hysteria
Group=hysteria
+Environment=HYSTERIA_LOG_LEVEL=info
+Environment=HYSTERIA_LOG_FORMAT=json
ExecStart=/usr/local/bin/hysteria server -c /etc/hysteria/config.yaml
Restart=on-failure
RestartSec=5s