Merge pull request #830 from 0xJacky/enhance/error-handle

enhance: error handle
This commit is contained in:
Jacky 2025-01-24 17:01:06 +08:00 committed by GitHub
commit bbae67939f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
94 changed files with 5230 additions and 3740 deletions

View file

@ -1,9 +1,12 @@
package api
import (
"errors"
"github.com/0xJacky/Nginx-UI/model"
"github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger"
"gorm.io/gorm"
"net/http"
)
@ -13,9 +16,21 @@ func CurrentUser(c *gin.Context) *model.User {
func ErrHandler(c *gin.Context, err error) {
logger.GetLogger().Errorln(err)
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
var cErr *cosy.Error
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
c.JSON(http.StatusNotFound, &cosy.Error{
Code: http.StatusNotFound,
Message: gorm.ErrRecordNotFound.Error(),
})
case errors.As(err, &cErr):
c.JSON(http.StatusInternalServerError, cErr)
default:
c.JSON(http.StatusInternalServerError, &cosy.Error{
Code: http.StatusInternalServerError,
Message: err.Error(),
})
}
}
func SetSSEHeaders(c *gin.Context) {

View file

@ -1,6 +1,9 @@
package nginx
import "github.com/gin-gonic/gin"
import (
"github.com/0xJacky/Nginx-UI/api/nginx_log"
"github.com/gin-gonic/gin"
)
func InitRouter(r *gin.RouterGroup) {
r.POST("ngx/build_config", BuildNginxConfig)
@ -10,10 +13,6 @@ func InitRouter(r *gin.RouterGroup) {
r.POST("nginx/restart", Restart)
r.POST("nginx/test", Test)
r.GET("nginx/status", Status)
r.POST("nginx_log", GetNginxLogPage)
r.POST("nginx_log", nginx_log.GetNginxLogPage)
r.GET("nginx/directives", GetDirectives)
}
func InitNginxLogRouter(r *gin.RouterGroup) {
r.GET("nginx_log", Log)
}

View file

@ -1,12 +1,9 @@
package nginx
package nginx_log
import (
"encoding/json"
"fmt"
"github.com/0xJacky/Nginx-UI/internal/cache"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/0xJacky/Nginx-UI/internal/nginx_log"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/hpcloud/tail"
@ -70,7 +67,7 @@ func GetNginxLogPage(c *gin.Context) {
c.JSON(http.StatusInternalServerError, nginxLogPageResp{
Error: "log file is not regular file",
})
logger.Error("log file is not regular file:", logPath)
logger.Errorf("log file is not regular file: %s", logPath)
return
}
@ -132,30 +129,7 @@ func GetNginxLogPage(c *gin.Context) {
})
}
// isLogPathUnderWhiteList checks if the log path is under one of the paths in LogDirWhiteList
func isLogPathUnderWhiteList(path string) bool {
cacheKey := fmt.Sprintf("isLogPathUnderWhiteList:%s", path)
res, ok := cache.Get(cacheKey)
// no cache, check it
if !ok {
for _, whitePath := range settings.NginxSettings.LogDirWhiteList {
if helper.IsUnderDirectory(path, whitePath) {
cache.Set(cacheKey, true, 0)
return true
}
}
return false
}
return res.(bool)
}
func getLogPath(control *controlStruct) (logPath string, err error) {
if len(settings.NginxSettings.LogDirWhiteList) == 0 {
err = errors.New("The settings.NginxSettings.LogDirWhiteList has not been configured. " +
"For security reasons, please configure a whitelist of log directories. " +
"Please visit https://nginxui.com/guide/config-nginx.html for more information.")
return
}
switch control.Type {
case "site":
var config *nginx.NgxConfig
@ -167,12 +141,12 @@ func getLogPath(control *controlStruct) (logPath string, err error) {
}
if control.ServerIdx >= len(config.Servers) {
err = errors.New("serverIdx out of range")
err = nginx_log.ErrServerIdxOutOfRange
return
}
if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {
err = errors.New("DirectiveIdx out of range")
err = nginx_log.ErrDirectiveIdxOutOfRange
return
}
@ -181,12 +155,12 @@ func getLogPath(control *controlStruct) (logPath string, err error) {
case "access_log", "error_log":
// ok
default:
err = errors.New("directive.Params neither access_log nor error_log")
err = nginx_log.ErrLogDirective
return
}
if directive.Params == "" {
err = errors.New("directive.Params is empty")
err = nginx_log.ErrDirectiveParamsIsEmpty
return
}
@ -200,8 +174,7 @@ func getLogPath(control *controlStruct) (logPath string, err error) {
path := nginx.GetErrorLogPath()
if path == "" {
err = errors.New("settings.NginxLogSettings.ErrorLogPath is empty," +
" refer to https://nginxui.com/guide/config-nginx.html for more information")
err = nginx_log.ErrErrorLogPathIsEmpty
return
}
@ -210,8 +183,7 @@ func getLogPath(control *controlStruct) (logPath string, err error) {
path := nginx.GetAccessLogPath()
if path == "" {
err = errors.New("settings.NginxLogSettings.AccessLogPath is empty," +
" refer to https://nginxui.com/guide/config-nginx.html for more information")
err = nginx_log.ErrAccessLogPathIsEmpty
return
}
@ -219,9 +191,8 @@ func getLogPath(control *controlStruct) (logPath string, err error) {
}
// check if logPath is under one of the paths in LogDirWhiteList
if !isLogPathUnderWhiteList(logPath) {
err = errors.New("The log path is not under the paths in LogDirWhiteList.")
return "", err
if !nginx_log.IsLogPathUnderWhiteList(logPath) {
return "", nginx_log.ErrLogPathIsNotUnderTheLogDirWhiteList
}
return
}

7
api/nginx_log/router.go Normal file
View file

@ -0,0 +1,7 @@
package nginx_log
import "github.com/gin-gonic/gin"
func InitRouter(r *gin.RouterGroup) {
r.GET("nginx_log", Log)
}

View file

@ -6,7 +6,7 @@ import (
"github.com/0xJacky/Nginx-UI/internal/chatbot"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"errors"
"github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger"

View file

@ -2,7 +2,6 @@ package user
import (
"encoding/base64"
"fmt"
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/cache"
"github.com/0xJacky/Nginx-UI/internal/passkey"
@ -77,23 +76,17 @@ func Start2FASecureSessionByOTP(c *gin.Context) {
}
u := api.CurrentUser(c)
if !u.EnabledOTP() {
c.JSON(http.StatusBadRequest, gin.H{
"message": "User has not configured OTP as 2FA",
})
api.ErrHandler(c, user.ErrUserNotEnabledOTPAs2FA)
return
}
if json.OTP == "" && json.RecoveryCode == "" {
c.JSON(http.StatusBadRequest, LoginResponse{
Message: "The user has enabled OTP as 2FA",
})
api.ErrHandler(c, user.ErrOTPOrRecoveryCodeEmpty)
return
}
if err := user.VerifyOTP(u, json.OTP, json.RecoveryCode); err != nil {
c.JSON(http.StatusBadRequest, LoginResponse{
Message: "Invalid OTP or recovery code",
})
api.ErrHandler(c, err)
return
}
@ -106,7 +99,7 @@ func Start2FASecureSessionByOTP(c *gin.Context) {
func BeginStart2FASecureSessionByPasskey(c *gin.Context) {
if !passkey.Enabled() {
api.ErrHandler(c, fmt.Errorf("WebAuthn settings are not configured"))
api.ErrHandler(c, user.ErrWebAuthnNotConfigured)
return
}
webauthnInstance := passkey.GetInstance()
@ -126,13 +119,13 @@ func BeginStart2FASecureSessionByPasskey(c *gin.Context) {
func FinishStart2FASecureSessionByPasskey(c *gin.Context) {
if !passkey.Enabled() {
api.ErrHandler(c, fmt.Errorf("WebAuthn settings are not configured"))
api.ErrHandler(c, user.ErrWebAuthnNotConfigured)
return
}
passkeySessionID := c.GetHeader("X-Passkey-Session-ID")
sessionDataBytes, ok := cache.Get(passkeySessionID)
if !ok {
api.ErrHandler(c, fmt.Errorf("session not found"))
api.ErrHandler(c, user.ErrSessionNotFound)
return
}
sessionData := sessionDataBytes.(*webauthn.SessionData)

View file

@ -6,7 +6,7 @@ import (
"github.com/0xJacky/Nginx-UI/query"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"errors"
"github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger"
"math/rand/v2"
@ -25,12 +25,10 @@ type LoginUser struct {
}
const (
ErrPasswordIncorrect = 4031
ErrMaxAttempts = 4291
ErrUserBanned = 4033
Enabled2FA = 199
Error2FACode = 4034
LoginSuccess = 200
ErrMaxAttempts = 4291
Enabled2FA = 199
Error2FACode = 4034
LoginSuccess = 200
)
type LoginResponse struct {
@ -73,15 +71,9 @@ func Login(c *gin.Context) {
time.Sleep(random * time.Second)
switch {
case errors.Is(err, user.ErrPasswordIncorrect):
c.JSON(http.StatusForbidden, LoginResponse{
Message: "Password incorrect",
Code: ErrPasswordIncorrect,
})
c.JSON(http.StatusForbidden, user.ErrPasswordIncorrect)
case errors.Is(err, user.ErrUserBanned):
c.JSON(http.StatusForbidden, LoginResponse{
Message: "The user is banned",
Code: ErrUserBanned,
})
c.JSON(http.StatusForbidden, user.ErrUserBanned)
default:
api.ErrHandler(c, err)
}

View file

@ -7,7 +7,7 @@ import (
"github.com/0xJacky/Nginx-UI/settings"
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"errors"
"github.com/uozi-tech/cosy"
"gorm.io/gorm"
"net/http"

View file

@ -53,7 +53,7 @@ func FinishPasskeyRegistration(c *gin.Context) {
webauthnInstance := passkey.GetInstance()
sessionDataBytes, ok := cache.Get(buildCachePasskeyRegKey(cUser.ID))
if !ok {
api.ErrHandler(c, fmt.Errorf("session not found"))
api.ErrHandler(c, user.ErrSessionNotFound)
return
}
@ -87,7 +87,7 @@ func FinishPasskeyRegistration(c *gin.Context) {
func BeginPasskeyLogin(c *gin.Context) {
if !passkey.Enabled() {
api.ErrHandler(c, fmt.Errorf("WebAuthn settings are not configured"))
api.ErrHandler(c, user.ErrWebAuthnNotConfigured)
return
}
webauthnInstance := passkey.GetInstance()
@ -107,13 +107,13 @@ func BeginPasskeyLogin(c *gin.Context) {
func FinishPasskeyLogin(c *gin.Context) {
if !passkey.Enabled() {
api.ErrHandler(c, fmt.Errorf("WebAuthn settings are not configured"))
api.ErrHandler(c, user.ErrWebAuthnNotConfigured)
return
}
sessionId := c.GetHeader("X-Passkey-Session-ID")
sessionDataBytes, ok := cache.Get(sessionId)
if !ok {
api.ErrHandler(c, fmt.Errorf("session not found"))
api.ErrHandler(c, user.ErrSessionNotFound)
return
}
webauthnInstance := passkey.GetInstance()

View file

@ -28,8 +28,6 @@ function reloadNginx() {
message.warn(r.message)
else
message.error(r.message)
}).catch(e => {
message.error(`${$gettext('Server error')} ${e?.message}`)
}).finally(() => getStatus())
}
@ -44,8 +42,6 @@ async function restartNginx() {
message.warn(r.message)
else
message.error(r.message)
}).catch(e => {
message.error(`${$gettext('Server error')} ${e?.message}`)
})
}

View file

@ -24,7 +24,7 @@ function reconnect() {
}
function newSSE() {
const s = new SSE('/api/environments/enabled', {
const s = new SSE('api/environments/enabled', {
headers: {
Authorization: token.value,
},

View file

@ -30,7 +30,7 @@ function reconnect() {
}
function newSSE() {
const s = new SSE('/api/notifications/live', {
const s = new SSE('api/notifications/live', {
headers: {
Authorization: token.value,
},
@ -67,8 +67,6 @@ function init() {
notificationApi.get_list().then(r => {
data.value = r.data
unreadCount.value = r.pagination?.total || 0
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
}).finally(() => {
loading.value = false
})
@ -90,8 +88,7 @@ function clear() {
message.success($gettext('Cleared successfully'))
data.value = []
unreadCount.value = 0
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
open.value = false
})
}
@ -99,8 +96,6 @@ function remove(id: number) {
notificationApi.destroy(id).then(() => {
message.success($gettext('Removed successfully'))
init()
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
})
}

View file

@ -48,10 +48,6 @@ async function ok() {
emit('save')
visible.value = false
})
.catch(e => {
error.value = e.errors
message.error($gettext(e?.message) ?? $gettext('Server error'))
})
.finally(() => {
loading.value = false
})

View file

@ -66,8 +66,6 @@ function onClickApply() {
computedActions.value[actionValue.value]?.action(props.selectedRowKeys).then(async () => {
message.success($gettext('Apply bulk action successfully'))
emit('onSuccess')
}).catch(e => {
message.error($gettext(e?.message) ?? $gettext('Server error'))
}),
)
})

View file

@ -99,7 +99,6 @@ async function ok() {
get_list()
visible.value = false
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'), 5)
Object.assign(error, e.errors)
})
}
@ -126,8 +125,6 @@ function edit(id: number | string) {
visible.value = true
modifyMode.value = true
editMode.value = 'modify'
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'), 5)
})
}
@ -135,8 +132,6 @@ function view(id: number | string) {
get(id).then(() => {
visible.value = true
modifyMode.value = false
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'), 5)
})
}

View file

@ -181,8 +181,6 @@ function destroy(id: number | string) {
props.api!.destroy(id, { permanent: props.inTrash }).then(() => {
get_list()
message.success($gettext('Deleted successfully'))
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
})
}
@ -190,8 +188,6 @@ function recover(id: number | string) {
props.api.recover(id).then(() => {
message.success($gettext('Recovered Successfully'))
get_list()
}).catch(e => {
message.error(e?.message ?? $gettext('Server error'))
})
}
@ -224,8 +220,6 @@ async function _get_list() {
if (r.pagination)
Object.assign(pagination, r.pagination)
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
})
loading.value = false

View file

@ -2,7 +2,6 @@ import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/types'
import type { Column, StdTableResponse } from '@/components/StdDesign/types'
import type { ComputedRef } from 'vue'
import { downloadCsv } from '@/lib/helper'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import _ from 'lodash'
@ -40,8 +39,7 @@ async function exportCsv(props: StdTableProps, pithyColumns: ComputedRef<Column[
return
}
dataSource.push(...r.data)
}).catch((e: { message?: string }) => {
message.error(e.message ?? $gettext('Server error'))
}).catch(() => {
hasMore = false
})
page += 1

View file

@ -119,9 +119,6 @@ function useSortable(props: StdTableProps, randomId: Ref<string>, dataSource: Re
affected_ids: changeIds,
}).then(() => {
message.success($gettext('Updated successfully'))
// eslint-disable-next-line ts/no-explicit-any
}).catch((e: any) => {
message.error(e?.message ?? $gettext('Server error'))
})
},
})

View file

@ -2,6 +2,7 @@
import type Curd from '@/api/curd'
import type { Column } from '@/components/StdDesign/types'
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
import { CloseCircleFilled } from '@ant-design/icons-vue'
import { watchOnce } from '@vueuse/core'
import _ from 'lodash'
@ -134,10 +135,13 @@ async function ok() {
M_values.value = _.clone(records.value)
}
// function clear() {
// M_values.value = []
// emit('update:selectedKey', '')
// }
function clear() {
M_values.value = []
if (props.selectionType === 'radio')
selectedKey.value = null
else
selectedKey.value = []
}
defineExpose({ show })
</script>
@ -150,9 +154,8 @@ defineExpose({ show })
>
<div
class="std-selector"
@click="show"
>
<div class="chips-container">
<div class="chips-container w-full" @click="show">
<div v-if="props.recordValueIndex">
<ATag
v-for="(chipText, index) in ComputedMValue"
@ -172,6 +175,10 @@ defineExpose({ show })
{{ placeholder }}
</div>
</div>
<div class="close-btn flex text-trueGray-3" @click="clear">
<CloseCircleFilled />
</div>
</div>
</div>
<AModal
@ -211,6 +218,8 @@ defineExpose({ show })
align-items: self-start;
.std-selector {
display: flex;
justify-content: space-between;
overflow-y: auto;
box-sizing: border-box;
font-variant: tabular-nums;
@ -228,6 +237,16 @@ defineExpose({ show })
//margin: 0 10px 0 0;
cursor: pointer;
min-width: 180px;
.close-btn {
opacity: 0;
transition: opacity 0.3s;
}
&:hover {
.close-btn {
opacity: 1;
}
}
}
}

View file

@ -5,7 +5,6 @@ import OTPInput from '@/components/OTPInput/OTPInput.vue'
import { useUserStore } from '@/pinia'
import { KeyOutlined } from '@ant-design/icons-vue'
import { startAuthentication } from '@simplewebauthn/browser'
import { message } from 'ant-design-vue'
defineProps<{
twoFAStatus: TwoFAStatusResponse
@ -44,21 +43,17 @@ defineExpose({
async function passkeyAuthenticate() {
passkeyLoading.value = true
try {
const begin = await twoFA.begin_start_secure_session_by_passkey()
const asseResp = await startAuthentication({ optionsJSON: begin.options.publicKey })
const r = await twoFA.finish_start_secure_session_by_passkey({
session_id: begin.session_id,
options: asseResp,
})
const begin = await twoFA.begin_start_secure_session_by_passkey()
const asseResp = await startAuthentication({ optionsJSON: begin.options.publicKey })
const r = await twoFA.finish_start_secure_session_by_passkey({
session_id: begin.session_id,
options: asseResp,
})
emit('submitSecureSessionID', r.session_id)
emit('submitSecureSessionID', r.session_id)
}
// eslint-disable-next-line ts/no-explicit-any
catch (e: any) {
message.error($gettext(e.message ?? 'Server error'))
}
passkeyLoading.value = false
}

View file

@ -0,0 +1,8 @@
export default {
50001: () => $gettext('Filename is empty'),
50002: () => $gettext('Cert path is not under the nginx conf dir'),
50003: () => $gettext('Certificate decode error'),
50004: () => $gettext('Certificate parse error'),
50005: () => $gettext('Payload resource is nil'),
50006: () => $gettext('Path: {0} is not under the nginx conf dir: {1}'),
}

View file

@ -0,0 +1,3 @@
export default {
50006: () => $gettext('Path: {0} is not under the nginx conf dir: {1}'),
}

View file

@ -0,0 +1,4 @@
export default {
50001: () => $gettext('Plain text is empty'),
50002: () => $gettext('Cipher text is too short'),
}

View file

@ -0,0 +1,3 @@
export default {
50001: () => $gettext('Block is nil'),
}

View file

@ -0,0 +1,9 @@
export default {
50001: () => $gettext('The log path is not under the paths in settings.NginxSettings.LogDirWhiteList'),
50002: () => $gettext('ServerIdx out of range'),
50003: () => $gettext('DirectiveIdx out of range'),
50004: () => $gettext('Directive.Params neither access_log nor error_log'),
50005: () => $gettext('Directive params is empty'),
50006: () => $gettext('Settings.NginxLogSettings.ErrorLogPath is empty, refer to https://nginxui.com/guide/config-nginx.html for more information'),
50007: () => $gettext('Settings.NginxLogSettings.AccessLogPath is empty, refer to https://nginxui.com/guide/config-nginx.html for more information'),
}

View file

@ -0,0 +1,14 @@
export default {
4040: () => $gettext('Task not found'),
4041: () => $gettext('Failed to read nginx.conf'),
5001: () => $gettext('Failed to parse nginx.conf'),
4042: () => $gettext('Nginx conf no http block'),
4043: () => $gettext('Nginx conf not include sites-enabled'),
4044: () => $gettext('Nginx conf no stream block'),
4045: () => $gettext('Nginx conf not include stream-enabled'),
5002: () => $gettext('Failed to create backup'),
4046: () => $gettext('Sites-available directory not exist'),
4047: () => $gettext('Sites-enabled directory not exist'),
4048: () => $gettext('Streams-available directory not exist'),
4049: () => $gettext('Streams-enabled directory not exist'),
}

View file

@ -0,0 +1,5 @@
export default {
40401: () => $gettext('Site not found'),
50001: () => $gettext('Destination file already exists'),
50002: () => $gettext('Site is enabled'),
}

View file

@ -0,0 +1,10 @@
export default {
40301: () => $gettext('Password incorrect'),
40303: () => $gettext('User banned'),
40304: () => $gettext('Invalid otp code'),
40305: () => $gettext('Invalid recovery code'),
50000: () => $gettext('WebAuthn settings are not configured'),
50001: () => $gettext('User not enabled otp as 2fa'),
50002: () => $gettext('Otp or recovery code empty'),
40401: () => $gettext('Session not found'),
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -52,7 +52,6 @@ async function save() {
// eslint-disable-next-line ts/no-explicit-any
catch (e: any) {
errors.value = e.errors
message.error($gettext(e?.message ?? 'Server error'))
throw e
}
}

View file

@ -107,8 +107,6 @@ async function init() {
translatedName: () => origName.value,
hasChildren: false,
}]
}).catch(r => {
message.error(r.message ?? $gettext('Server error'))
})
}
else {

View file

@ -31,8 +31,6 @@ function save() {
router.push({
path: `/config/${r.path}/edit`,
})
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
}).finally(() => {
loading.value = false
})

View file

@ -34,8 +34,6 @@ function ok() {
message.success($gettext('Created successfully'))
emit('created')
}).catch(e => {
message.error(`${$gettext('Server error')} ${e?.message}`)
})
})
})

View file

@ -41,8 +41,6 @@ function ok() {
visible.value = false
message.success($gettext('Rename successfully'))
emit('renamed')
}).catch(e => {
message.error(`${$gettext('Server error')} ${e?.message}`)
})
})
})

View file

@ -3,7 +3,6 @@ import type { Environment } from '@/api/environment'
import type { Ref } from 'vue'
import upgrade, { type RuntimeInfo } from '@/api/upgrade'
import websocket from '@/lib/websocket'
import { message } from 'ant-design-vue'
import _ from 'lodash'
import { marked } from 'marked'
import { useRoute } from 'vue-router'
@ -56,7 +55,6 @@ function getLatestRelease() {
data.value = r
}).catch(e => {
getReleaseError.value = e?.message
message.error(e?.message ?? $gettext('Server error'))
}).finally(() => {
loading.value = false
})

View file

@ -11,8 +11,6 @@ function loadFromSettings() {
environment.load_from_settings().then(() => {
curd.value.get_list()
message.success($gettext('Load successfully'))
}).catch(e => {
message.error(`${$gettext('Server error')} ${e?.message}`)
})
}
const selectedNodeIds = ref([])

View file

@ -13,8 +13,6 @@ function clear() {
message.success($gettext('Cleared successfully'))
curd.value?.get_list()
unreadCount.value = 0
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
})
}

View file

@ -66,8 +66,6 @@ function onSubmit() {
install.install_nginx_ui(modelRef).then(async () => {
message.success($gettext('Install successfully'))
await router.push('/login')
}).catch(e => {
message.error(e.message ?? $gettext('Server error'))
}).finally(() => {
loading.value = false
})

View file

@ -73,23 +73,8 @@ function onSubmit() {
break
}
}).catch(e => {
switch (e.code) {
case 4031:
message.error($gettext('Incorrect username or password'))
break
case 4291:
message.error($gettext('Too many login failed attempts, please try again later'))
break
case 4033:
message.error($gettext('User is banned'))
break
case 4034:
refOTP.value?.clearInput()
message.error($gettext('Invalid 2FA or recovery code'))
break
default:
message.error($gettext(e.message ?? 'Server error'))
break
if (e.code === 4043) {
refOTP.value?.clearInput()
}
})
loading.value = false
@ -118,9 +103,6 @@ auth.get_casdoor_uri()
casdoor_uri.value = r.uri
}
})
.catch(e => {
message.error($gettext(e.message ?? 'Server error'))
})
function loginWithCasdoor() {
window.location.href = casdoor_uri.value
@ -134,8 +116,6 @@ if (route.query?.code !== undefined && route.query?.state !== undefined) {
const next = (route.query?.next || '').toString() || '/'
await router.push(next)
}).catch(e => {
message.error($gettext(e.message ?? 'Server error'))
})
loading.value = false
}
@ -156,27 +136,23 @@ passkey.get_config_status().then(r => {
const passkeyLoginLoading = ref(false)
async function handlePasskeyLogin() {
passkeyLoginLoading.value = true
try {
const begin = await auth.begin_passkey_login()
const asseResp = await startAuthentication({ optionsJSON: begin.options.publicKey })
const r = await auth.finish_passkey_login({
session_id: begin.session_id,
options: asseResp,
})
const begin = await auth.begin_passkey_login()
const asseResp = await startAuthentication({ optionsJSON: begin.options.publicKey })
if (r.token) {
const next = (route.query?.next || '').toString() || '/'
const r = await auth.finish_passkey_login({
session_id: begin.session_id,
options: asseResp,
})
passkeyLogin(asseResp.rawId, r.token)
secureSessionId.value = r.secure_session_id
await router.push(next)
}
}
// eslint-disable-next-line ts/no-explicit-any
catch (e: any) {
message.error($gettext(e.message ?? 'Server error'))
if (r.token) {
const next = (route.query?.next || '').toString() || '/'
passkeyLogin(asseResp.rawId, r.token)
secureSessionId.value = r.secure_session_id
await router.push(next)
}
passkeyLoginLoading.value = false
}
</script>

View file

@ -45,8 +45,6 @@ function removeBannedIP(ip: string) {
setting.remove_banned_ip(ip).then(() => {
bannedIPs.value = bannedIPs.value.filter(v => v.ip !== ip)
message.success($gettext('Remove successfully'))
}).catch((e: { message?: string }) => {
message.error(e?.message ?? $gettext('Server error'))
})
}
</script>

View file

@ -112,9 +112,6 @@ async function save() {
refAuthSettings.value?.getBannedIPs?.()
message.success($gettext('Save successfully'))
errors.value = {}
}).catch(e => {
errors.value = e.errors
message.error(e?.message ?? $gettext('Server error'))
})
})
}

View file

@ -14,24 +14,18 @@ const passkeyEnabled = ref(false)
const regLoading = ref(false)
async function registerPasskey() {
regLoading.value = true
try {
const optionsJSON = await passkey.begin_registration()
const optionsJSON = await passkey.begin_registration()
const attestationResponse = await startRegistration({ optionsJSON })
const attestationResponse = await startRegistration({ optionsJSON })
await passkey.finish_registration(attestationResponse, passkeyName.value)
await passkey.finish_registration(attestationResponse, passkeyName.value)
emit('created')
emit('created')
message.success($gettext('Register passkey successfully'))
addPasskeyModelOpen.value = false
message.success($gettext('Register passkey successfully'))
addPasskeyModelOpen.value = false
user.passkeyRawId = attestationResponse.rawId
}
// eslint-disable-next-line ts/no-explicit-any
catch (e: any) {
message.error($gettext(e.message ?? 'Server error'))
}
user.passkeyRawId = attestationResponse.rawId
regLoading.value = false
}

View file

@ -22,8 +22,6 @@ function getList() {
getListLoading.value = true
passkey.get_list().then(r => {
data.value = r
}).catch((e: { message?: string }) => {
message.error(e?.message ?? $gettext('Server error'))
}).finally(() => {
getListLoading.value = false
})
@ -39,8 +37,6 @@ function update(id: number, record: Passkey) {
getList()
modifyIdx.value = -1
message.success($gettext('Update successfully'))
}).catch((e: { message?: string }) => {
message.error(e?.message ?? $gettext('Server error'))
})
}
@ -52,8 +48,6 @@ function remove(item: Passkey) {
// if current passkey is removed, clear it from user store
if (user.passkeyLoginAvailable && user.passkeyRawId === item.raw_id)
user.passkeyRawId = ''
}).catch((e: { message?: string }) => {
message.error(e?.message ?? $gettext('Server error'))
})
}
</script>

View file

@ -40,8 +40,6 @@ function generateSecret() {
secret.value = r.secret
qrCode.value = r.qr_code
refOtp.value?.clearInput()
}).catch((e: { message?: string }) => {
message.error(e.message ?? $gettext('Server error'))
})
}
@ -52,9 +50,8 @@ function enroll(code: string) {
clearGenerateSecretInterval()
get2FAStatus()
message.success($gettext('Enable 2FA successfully'))
}).catch((e: { message?: string }) => {
}).catch(() => {
refOtp.value?.clearInput()
message.error(e.message ?? $gettext('Server error'))
})
}
@ -79,8 +76,6 @@ function reset2FA() {
recoveryCode.value = ''
get2FAStatus()
clickEnable2FA()
}).catch((e: { message?: string }) => {
message.error($gettext(e.message ?? 'Server error'))
})
}
</script>

View file

@ -28,8 +28,6 @@ function save() {
router.push({
path: `/sites/${buffer.value}`,
})
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
}).finally(() => {
loading.value = false
})

View file

@ -48,8 +48,6 @@ function onSubmit() {
message.success($gettext('Duplicate to local successfully'))
show.value = false
emit('duplicated')
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
})
loading.value = false

View file

@ -35,9 +35,7 @@ onMounted(async () => {
return
}
}
// eslint-disable-next-line ts/no-explicit-any
catch (e: any) {
message.error(e?.message ?? $gettext('Server error'))
catch {
return
}
}
@ -68,8 +66,6 @@ function destroy(site_name: string) {
table.value.get_list()
message.success($gettext('Delete site: %{site_name}', { site_name }))
inspect_config.value?.test()
}).catch(e => {
message.error(e?.message ?? $gettext('Server error'))
})
}

View file

@ -77,8 +77,6 @@ function destroy(stream_name: string) {
table.value.get_list()
message.success($gettext('Delete stream: %{stream_name}', { stream_name }))
inspect_config.value?.test()
}).catch(e => {
message.error(e?.message ?? $gettext('Server error'))
})
}
@ -109,8 +107,6 @@ function handleAddStream() {
showAddStream.value = false
table.value?.get_list()
message.success($gettext('Added successfully'))
}).catch(e => {
message.error(e?.message ?? $gettext('Server error'))
})
}
</script>

View file

@ -5,7 +5,6 @@ import upgrade from '@/api/upgrade'
import websocket from '@/lib/websocket'
import version from '@/version.json'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import { marked } from 'marked'
import { useRoute } from 'vue-router'
@ -39,7 +38,6 @@ function getLatestRelease() {
lastCheck.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
}).catch(e => {
getReleaseError.value = e?.message
message.error(e?.message ?? $gettext('Server error'))
}).finally(() => {
loading.value = false
})

7
cmd/errdef/generate.go Normal file
View file

@ -0,0 +1,7 @@
package main
import "github.com/uozi-tech/cosy/errdef"
func main() {
errdef.Generate()
}

View file

@ -2,7 +2,7 @@ package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
@ -15,10 +15,14 @@ type Directive struct {
}
func main() {
if len(os.Args) < 2 {
log.Println("Usage: go run . <output_file>")
}
outputPath := os.Args[1]
// Fetch page content
resp, err := http.Get("https://nginx.org/en/docs/dirindex.html")
if err != nil {
fmt.Println("Error fetching page:", err)
log.Println("[Error] fetching page:", err)
return
}
defer resp.Body.Close()
@ -26,7 +30,7 @@ func main() {
// Parse HTML
doc, err := html.Parse(resp.Body)
if err != nil {
fmt.Println("Error parsing HTML:", err)
log.Println("[Error] parsing HTML:", err)
return
}
@ -99,15 +103,15 @@ func main() {
// Write results to JSON file
jsonData, err := json.MarshalIndent(directives, "", " ")
if err != nil {
fmt.Println("Error marshaling JSON:", err)
log.Println("[Error] marshaling JSON:", err)
return
}
err = os.WriteFile("../../internal/nginx/nginx_directives.json", jsonData, 0644)
err = os.WriteFile(outputPath, jsonData, 0644)
if err != nil {
fmt.Println("Error writing file:", err)
log.Println("[Error] writing file:", err)
return
}
fmt.Printf("Successfully parsed %d directives and saved to nginx_directives.json\n", len(directives))
log.Printf("[OK] Successfully parsed %d directives and saved to %s\n", len(directives), outputPath)
}

7
gen.sh
View file

@ -1,3 +1,10 @@
# generate gen code
pushd ./cmd/gen || exit
go run generate.go -config ../../app.ini
popd || exit
# generate error definitions
go run cmd/errdef/generate.go . ts ./app/src/constants/errors
# parse nginx directive indexs
go run cmd/ngx_dir_index/ngx_dir_index.go ./internal/nginx/nginx_directives.json

4
go.mod
View file

@ -15,7 +15,7 @@ require (
github.com/gin-contrib/static v1.1.3
github.com/gin-gonic/gin v1.10.0
github.com/go-acme/lego/v4 v4.21.0
github.com/go-co-op/gocron/v2 v2.14.2
github.com/go-co-op/gocron/v2 v2.15.0
github.com/go-playground/validator/v10 v10.24.0
github.com/go-resty/resty/v2 v2.16.4
github.com/go-webauthn/webauthn v0.11.2
@ -35,7 +35,7 @@ require (
github.com/spf13/cast v1.7.1
github.com/stretchr/testify v1.10.0
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a
github.com/uozi-tech/cosy v1.14.1
github.com/uozi-tech/cosy v1.14.2
github.com/uozi-tech/cosy-driver-sqlite v0.2.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.32.0

4
go.sum
View file

@ -1027,6 +1027,8 @@ github.com/go-co-op/gocron/v2 v2.14.1 h1:bwWMkX2rNfS6RqBmUAfkDuOPKl/BRCRCrmuAv8f
github.com/go-co-op/gocron/v2 v2.14.1/go.mod h1:ZF70ZwEqz0OO4RBXE1sNxnANy/zvwLcattWEFsqpKig=
github.com/go-co-op/gocron/v2 v2.14.2 h1:S6CbI7MVfD3S/aPJNLoSg2YcGyEqzEMwUopDejuT4Oc=
github.com/go-co-op/gocron/v2 v2.14.2/go.mod h1:ZF70ZwEqz0OO4RBXE1sNxnANy/zvwLcattWEFsqpKig=
github.com/go-co-op/gocron/v2 v2.15.0 h1:Kpvo71VSihE+RImmpA+3ta5CcMhoRzMGw4dJawrj4zo=
github.com/go-co-op/gocron/v2 v2.15.0/go.mod h1:ZF70ZwEqz0OO4RBXE1sNxnANy/zvwLcattWEFsqpKig=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
@ -1941,6 +1943,8 @@ github.com/uozi-tech/cosy v1.13.0 h1:dDnU8f3z3AA2KamqEcpa0ObqY4YVXXsNbdxR9Hv7f0A
github.com/uozi-tech/cosy v1.13.0/go.mod h1:DSKLtoVaGLUlJ8KiQ1vWEsnv85epRrAAMXSijuq+asM=
github.com/uozi-tech/cosy v1.14.1 h1:Qat6Av9XhYMypeBCcagl3Pfp6LWgDRK0+lHBN/1jFY0=
github.com/uozi-tech/cosy v1.14.1/go.mod h1:DSKLtoVaGLUlJ8KiQ1vWEsnv85epRrAAMXSijuq+asM=
github.com/uozi-tech/cosy v1.14.2 h1:nWTiBSAYn1yTtQeTZluK/G2OjERGs/SRaK4yVjq1IQ8=
github.com/uozi-tech/cosy v1.14.2/go.mod h1:DSKLtoVaGLUlJ8KiQ1vWEsnv85epRrAAMXSijuq+asM=
github.com/uozi-tech/cosy-driver-mysql v0.2.2 h1:22S/XNIvuaKGqxQPsYPXN8TZ8hHjCQdcJKVQ83Vzxoo=
github.com/uozi-tech/cosy-driver-mysql v0.2.2/go.mod h1:EZnRIbSj1V5U0gEeTobrXai/d1SV11lkl4zP9NFEmyE=
github.com/uozi-tech/cosy-driver-postgres v0.2.1 h1:OICakGuT+omva6QOJCxTJ5Lfr7CGXLmk/zD+aS51Z2o=

View file

@ -35,7 +35,7 @@ func autoCert(certModel *model.Cert) {
defer log.Exit()
if len(certModel.Filename) == 0 {
log.Error(errors.New("filename is empty"))
log.Error(ErrCertModelFilenameEmpty)
return
}

View file

@ -5,7 +5,6 @@ import (
"encoding/pem"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/pkg/errors"
"os"
"time"
)
@ -19,24 +18,24 @@ type Info struct {
func GetCertInfo(sslCertificatePath string) (info *Info, err error) {
if !helper.IsUnderDirectory(sslCertificatePath, nginx.GetConfPath()) {
err = errors.New("ssl certificate path is not under the nginx conf path")
err = ErrCertPathIsNotUnderTheNginxConfDir
return
}
certData, err := os.ReadFile(sslCertificatePath)
if err != nil {
err = errors.Wrap(err, "error read certificate")
return
}
block, _ := pem.Decode(certData)
if block == nil || block.Type != "CERTIFICATE" {
err = errors.New("certificate decoding error")
err = ErrCertDecode
return
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
err = errors.Wrap(err, "certificate parsing error")
err = ErrCertParse
return
}

13
internal/cert/errors.go Normal file
View file

@ -0,0 +1,13 @@
package cert
import "github.com/uozi-tech/cosy"
var (
e = cosy.NewErrorScope("cert")
ErrCertModelFilenameEmpty = e.New(50001, "filename is empty")
ErrCertPathIsNotUnderTheNginxConfDir = e.New(50002, "cert path is not under the nginx conf dir")
ErrCertDecode = e.New(50003, "certificate decode error")
ErrCertParse = e.New(50004, "certificate parse error")
ErrPayloadResourceIsNil = e.New(50005, "payload resource is nil")
ErrPathIsNotUnderTheNginxConfDir = e.New(50006, "path: {0} is not under the nginx conf dir: {1}")
)

View file

@ -10,7 +10,7 @@ import (
func renew(payload *ConfigPayload, client *lego.Client, l *log.Logger, errChan chan error) {
if payload.Resource == nil {
errChan <- errors.New("resource is nil")
errChan <- ErrPayloadResourceIsNil
return
}

View file

@ -3,7 +3,6 @@ package cert
import (
"bytes"
"encoding/json"
"fmt"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/internal/notification"
@ -33,13 +32,11 @@ func SyncToRemoteServer(c *model.Cert) (err error) {
nginxConfPath := nginx.GetConfPath()
if !helper.IsUnderDirectory(c.SSLCertificatePath, nginxConfPath) {
return fmt.Errorf("ssl_certificate_path: %s is not under the nginx conf path: %s",
c.SSLCertificatePath, nginxConfPath)
return e.NewWithParams(50006, ErrPathIsNotUnderTheNginxConfDir.Error(), c.SSLCertificatePath, nginxConfPath)
}
if !helper.IsUnderDirectory(c.SSLCertificateKeyPath, nginxConfPath) {
return fmt.Errorf("ssl_certificate_key_path: %s is not under the nginx conf path: %s",
c.SSLCertificateKeyPath, nginxConfPath)
return e.NewWithParams(50006, ErrPathIsNotUnderTheNginxConfDir.Error(), c.SSLCertificateKeyPath, nginxConfPath)
}
certBytes, err := os.ReadFile(c.SSLCertificatePath)

View file

@ -1,7 +1,6 @@
package cert
import (
"fmt"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"os"
@ -22,13 +21,11 @@ func (c *Content) WriteFile() (err error) {
nginxConfPath := nginx.GetConfPath()
if !helper.IsUnderDirectory(c.SSLCertificatePath, nginxConfPath) {
return fmt.Errorf("ssl_certificate_path: %s is not under the nginx conf path: %s",
c.SSLCertificatePath, nginxConfPath)
return e.NewWithParams(50006, ErrPathIsNotUnderTheNginxConfDir.Error(), c.SSLCertificatePath, nginxConfPath)
}
if !helper.IsUnderDirectory(c.SSLCertificateKeyPath, nginxConfPath) {
return fmt.Errorf("ssl_certificate_key_path: %s is not under the nginx conf path: %s",
c.SSLCertificateKeyPath, nginxConfPath)
return e.NewWithParams(50006, ErrPathIsNotUnderTheNginxConfDir.Error(), c.SSLCertificateKeyPath, nginxConfPath)
}
// MkdirAll creates a directory named path, along with any necessary parents,

View file

@ -0,0 +1,8 @@
package config
import "github.com/uozi-tech/cosy"
var (
e = cosy.NewErrorScope("config")
ErrPathIsNotUnderTheNginxConfDir = e.New(50006, "path: {0} is not under the nginx conf dir: {1}")
)

View file

@ -4,7 +4,6 @@ import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/internal/notification"
@ -35,8 +34,7 @@ func SyncToRemoteServer(c *model.Config) (err error) {
nginxConfPath := nginx.GetConfPath()
if !helper.IsUnderDirectory(c.Filepath, nginxConfPath) {
return fmt.Errorf("config: %s is not under the nginx conf path: %s",
c.Filepath, nginxConfPath)
return e.NewWithParams(50006, ErrPathIsNotUnderTheNginxConfDir.Error(), c.Filepath, nginxConfPath)
}
configBytes, err := os.ReadFile(c.Filepath)
@ -76,13 +74,11 @@ func SyncRenameOnRemoteServer(origPath, newPath string, syncNodeIds []uint64) (e
nginxConfPath := nginx.GetConfPath()
if !helper.IsUnderDirectory(origPath, nginxConfPath) {
return fmt.Errorf("config: %s is not under the nginx conf path: %s",
origPath, nginxConfPath)
return e.NewWithParams(50006, ErrPathIsNotUnderTheNginxConfDir.Error(), origPath, nginxConfPath)
}
if !helper.IsUnderDirectory(newPath, nginxConfPath) {
return fmt.Errorf("config: %s is not under the nginx conf path: %s",
newPath, nginxConfPath)
return e.NewWithParams(50006, ErrPathIsNotUnderTheNginxConfDir.Error(), newPath, nginxConfPath)
}
payload := &RenameConfigPayload{

View file

@ -5,27 +5,25 @@ import (
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/pkg/errors"
"io"
)
// AesEncrypt encrypts text and given key with AES.
func AesEncrypt(text []byte) ([]byte, error) {
if len(text) == 0 {
return nil, errors.New("AesEncrypt text is empty")
return nil, ErrPlainTextEmpty
}
block, err := aes.NewCipher(settings.CryptoSettings.GetSecretMd5())
if err != nil {
return nil, fmt.Errorf("AesEncrypt invalid key: %v", err)
return nil, err
}
b := base64.StdEncoding.EncodeToString(text)
ciphertext := make([]byte, aes.BlockSize+len(b))
iv := ciphertext[:aes.BlockSize]
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
return nil, fmt.Errorf("AesEncrypt unable to read IV: %w", err)
return nil, err
}
cfb := cipher.NewCFBEncrypter(block, iv)
@ -42,7 +40,7 @@ func AesDecrypt(text []byte) ([]byte, error) {
}
if len(text) < aes.BlockSize {
return nil, errors.New("AesDecrypt ciphertext too short")
return nil, ErrCipherTextTooShort
}
iv := text[:aes.BlockSize]
@ -52,7 +50,7 @@ func AesDecrypt(text []byte) ([]byte, error) {
data, err := base64.StdEncoding.DecodeString(string(text))
if err != nil {
return nil, fmt.Errorf("AesDecrypt invalid decrypted base64 string: %w", err)
return nil, err
}
return data, nil

View file

@ -0,0 +1,9 @@
package crypto
import "github.com/uozi-tech/cosy"
var (
e = cosy.NewErrorScope("crypto")
ErrPlainTextEmpty = e.New(50001, "plain text is empty")
ErrCipherTextTooShort = e.New(50002, "cipher text is too short")
)

View file

@ -3,7 +3,7 @@ package helper
import (
"strings"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
"errors"
"syscall"
)

View file

@ -6,7 +6,7 @@ import (
"github.com/0xJacky/Nginx-UI/settings"
"github.com/caarlos0/env/v11"
"github.com/google/uuid"
"github.com/pkg/errors"
"errors"
"github.com/uozi-tech/cosy/logger"
cSettings "github.com/uozi-tech/cosy/settings"
"golang.org/x/crypto/bcrypt"

8
internal/nginx/errors.go Normal file
View file

@ -0,0 +1,8 @@
package nginx
import "github.com/uozi-tech/cosy"
var (
e = cosy.NewErrorScope("nginx")
ErrBlockIsNil = e.New(50001, "block is nil")
)

View file

@ -155,7 +155,7 @@ func buildComment(c []string) string {
func parse(block config.IBlock, ngxConfig *NgxConfig) (err error) {
if block == nil {
err = errors.New("block is nil")
err = ErrBlockIsNil
return
}
for _, v := range block.GetDirectives() {

View file

@ -0,0 +1,14 @@
package nginx_log
import "github.com/uozi-tech/cosy"
var (
e = cosy.NewErrorScope("nginx_log")
ErrLogPathIsNotUnderTheLogDirWhiteList = e.New(50001, "the log path is not under the paths in settings.NginxSettings.LogDirWhiteList")
ErrServerIdxOutOfRange = e.New(50002, "serverIdx out of range")
ErrDirectiveIdxOutOfRange = e.New(50003, "directiveIdx out of range")
ErrLogDirective = e.New(50004, "directive.Params neither access_log nor error_log")
ErrDirectiveParamsIsEmpty = e.New(50005, "directive params is empty")
ErrErrorLogPathIsEmpty = e.New(50006, "settings.NginxLogSettings.ErrorLogPath is empty, refer to https://nginxui.com/guide/config-nginx.html for more information")
ErrAccessLogPathIsEmpty = e.New(50007, "settings.NginxLogSettings.AccessLogPath is empty, refer to https://nginxui.com/guide/config-nginx.html for more information")
)

View file

@ -0,0 +1,41 @@
package nginx_log
import (
"fmt"
"github.com/0xJacky/Nginx-UI/internal/cache"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/settings"
"path/filepath"
)
// IsLogPathUnderWhiteList checks if the log path is under one of the paths in LogDirWhiteList
func IsLogPathUnderWhiteList(path string) bool {
cacheKey := fmt.Sprintf("isLogPathUnderWhiteList:%s", path)
res, ok := cache.Get(cacheKey)
// deep copy
logDirWhiteList := append([]string{}, settings.NginxSettings.LogDirWhiteList...)
accessLogPath := nginx.GetAccessLogPath()
errorLogPath := nginx.GetErrorLogPath()
if accessLogPath != "" {
logDirWhiteList = append(logDirWhiteList, filepath.Dir(accessLogPath))
}
if errorLogPath != "" {
logDirWhiteList = append(logDirWhiteList, filepath.Dir(errorLogPath))
}
// no cache, check it
if !ok {
for _, whitePath := range logDirWhiteList {
if helper.IsUnderDirectory(path, whitePath) {
cache.Set(cacheKey, true, 0)
return true
}
}
return false
}
return res.(bool)
}

View file

@ -11,9 +11,9 @@ var (
ErrNginxConfNotIncludeSitesEnabled = e.New(4043, "Nginx conf not include sites-enabled")
ErrorNginxConfNoStreamBlock = e.New(4044, "Nginx conf no stream block")
ErrNginxConfNotIncludeStreamEnabled = e.New(4045, "Nginx conf not include stream-enabled")
ErrFailedToCreateBackup = e.New(5001, "Failed to create backup")
ErrFailedToCreateBackup = e.New(5002, "Failed to create backup")
ErrSitesAvailableNotExist = e.New(4046, "Sites-available directory not exist")
ErrSitesEnabledNotExist = e.New(4047, "Sites-enabled directory not exist")
ErrStreamAvailableNotExist = e.New(4048, "Stream-available directory not exist")
ErrStreamEnabledNotExist = e.New(4049, "Stream-enabled directory not exist")
ErrStreamAvailableNotExist = e.New(4048, "Streams-available directory not exist")
ErrStreamEnabledNotExist = e.New(4049, "Streams-enabled directory not exist")
)

View file

@ -6,7 +6,6 @@ import (
"time"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/spf13/cast"
"github.com/tufanbarisyildirim/gonginx/config"
"github.com/tufanbarisyildirim/gonginx/dumper"
"github.com/tufanbarisyildirim/gonginx/parser"
@ -88,7 +87,7 @@ func FixNginxConfIncludeSites() error {
}
// create a backup file (+.bak.timestamp)
backupPath := path + ".bak." + cast.ToString(time.Now().Unix())
backupPath := fmt.Sprintf("%s.bak.%d", path, time.Now().Unix())
err = os.WriteFile(backupPath, content, 0644)
if err != nil {
return ErrFailedToCreateBackup
@ -133,7 +132,7 @@ func FixNginxConfIncludeStreams() error {
}
// create a backup file (+.bak.timestamp)
backupPath := path + ".bak." + cast.ToString(time.Now().Unix())
backupPath := fmt.Sprintf("%s.bak.%d", path, time.Now().Unix())
err = os.WriteFile(backupPath, content, 0644)
if err != nil {
return ErrFailedToCreateBackup

View file

@ -29,11 +29,11 @@ func Delete(name string) (err error) {
enabledPath := nginx.GetConfPath("sites-enabled", name)
if !helper.FileExists(availablePath) {
return fmt.Errorf("site not found")
return ErrSiteNotFound
}
if helper.FileExists(enabledPath) {
return fmt.Errorf("site is enabled")
return ErrSiteIsEnabled
}
certModel := model.Cert{Filename: name}

View file

@ -3,7 +3,6 @@ package site
import (
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/pkg/errors"
)
// Duplicate duplicates a site by copying the file
@ -12,7 +11,7 @@ func Duplicate(src, dst string) (err error) {
dst = nginx.GetConfPath("sites-available", dst)
if helper.FileExists(dst) {
return errors.New("file exists")
return ErrDstFileExists
}
_, err = helper.CopyFile(src, dst)

10
internal/site/errors.go Normal file
View file

@ -0,0 +1,10 @@
package site
import "github.com/uozi-tech/cosy"
var (
e = cosy.NewErrorScope("site")
ErrSiteNotFound = e.New(40401, "site not found")
ErrDstFileExists = e.New(50001, "destination file already exists")
ErrSiteIsEnabled = e.New(50002, "site is enabled")
)

View file

@ -24,7 +24,7 @@ func Rename(oldName string, newName string) (err error) {
// check if dst file exists, do not rename
if helper.FileExists(newPath) {
return fmt.Errorf("file exists")
return ErrDstFileExists
}
s := query.Site
@ -84,9 +84,9 @@ func syncRename(oldName, newName string) {
client.SetBaseURL(node.URL)
resp, err := client.R().
SetHeader("X-Node-Secret", node.Token).
SetBody(map[string]string{
"new_name": newName,
}).
SetBody(map[string]string{
"new_name": newName,
}).
Post(fmt.Sprintf("/api/sites/%s/rename", oldName))
if err != nil {
notification.Error("Rename Remote Site Error", err.Error())

View file

@ -19,7 +19,7 @@ import (
func Save(name string, content string, overwrite bool, siteCategoryId uint64, syncNodeIds []uint64) (err error) {
path := nginx.GetConfPath("sites-available", name)
if !overwrite && helper.FileExists(path) {
return fmt.Errorf("file exists")
return ErrDstFileExists
}
err = os.WriteFile(path, []byte(content), 0644)

15
internal/user/errors.go Normal file
View file

@ -0,0 +1,15 @@
package user
import "github.com/uozi-tech/cosy"
var (
e = cosy.NewErrorScope("user")
ErrPasswordIncorrect = e.New(40301, "password incorrect")
ErrUserBanned = e.New(40303, "user banned")
ErrOTPCode = e.New(40304, "invalid otp code")
ErrRecoveryCode = e.New(40305, "invalid recovery code")
ErrWebAuthnNotConfigured = e.New(50000, "WebAuthn settings are not configured")
ErrUserNotEnabledOTPAs2FA = e.New(50001, "user not enabled otp as 2fa")
ErrOTPOrRecoveryCodeEmpty = e.New(50002, "otp or recovery code empty")
ErrSessionNotFound = e.New(40401, "session not found")
)

View file

@ -1,7 +1,6 @@
package user
import (
"errors"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/0xJacky/Nginx-UI/settings"
@ -9,11 +8,6 @@ import (
"time"
)
var (
ErrPasswordIncorrect = errors.New("password incorrect")
ErrUserBanned = errors.New("user banned")
)
func Login(name string, password string) (user *model.User, err error) {
u := query.User

View file

@ -9,16 +9,10 @@ import (
"github.com/0xJacky/Nginx-UI/internal/crypto"
"github.com/0xJacky/Nginx-UI/model"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/pquerna/otp/totp"
"time"
)
var (
ErrOTPCode = errors.New("invalid otp code")
ErrRecoveryCode = errors.New("invalid recovery code")
)
func VerifyOTP(user *model.User, otp, recoveryCode string) (err error) {
if otp != "" {
decrypted, err := crypto.AesDecrypt(user.OTPSecret)

View file

@ -9,7 +9,7 @@ import (
"github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin"
"github.com/jpillora/overseer"
"github.com/pkg/errors"
"errors"
"github.com/uozi-tech/cosy"
cKernel "github.com/uozi-tech/cosy/kernel"
"github.com/uozi-tech/cosy/logger"

View file

@ -4,7 +4,7 @@ import (
"database/sql/driver"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"errors"
"github.com/sashabaranov/go-openai"
)

View file

@ -6,6 +6,7 @@ import (
"github.com/0xJacky/Nginx-UI/api/cluster"
"github.com/0xJacky/Nginx-UI/api/config"
"github.com/0xJacky/Nginx-UI/api/nginx"
nginxLog "github.com/0xJacky/Nginx-UI/api/nginx_log"
"github.com/0xJacky/Nginx-UI/api/notification"
"github.com/0xJacky/Nginx-UI/api/openai"
"github.com/0xJacky/Nginx-UI/api/public"
@ -75,7 +76,7 @@ func InitRouter() {
{
terminal.InitRouter(o)
}
nginx.InitNginxLogRouter(w)
nginxLog.InitRouter(w)
upstream.InitRouter(w)
system.InitWebSocketRouter(w)
}

View file

@ -4,7 +4,7 @@ import (
"context"
"fmt"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/pkg/errors"
"errors"
"github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy/sandbox"
"io"