mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-23 01:29:12 +01:00
v0.26.0
This commit is contained in:
@@ -19,22 +19,23 @@ import (
|
||||
"github.com/yusing/godoxy/internal/auth"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
// NewHandler creates a new Gin engine for the API.
|
||||
//
|
||||
// @title GoDoxy API
|
||||
// @version 1.0
|
||||
// @description GoDoxy API
|
||||
// @termsOfService https://github.com/yusing/godoxy/blob/main/LICENSE
|
||||
|
||||
//
|
||||
// @contact.name Yusing
|
||||
// @contact.url https://github.com/yusing/godoxy/issues
|
||||
|
||||
//
|
||||
// @license.name MIT
|
||||
// @license.url https://github.com/yusing/godoxy/blob/main/LICENSE
|
||||
|
||||
//
|
||||
// @BasePath /api/v1
|
||||
|
||||
//
|
||||
// @externalDocs.description GoDoxy Docs
|
||||
// @externalDocs.url https://docs.godoxy.dev
|
||||
func NewHandler(requireAuth bool) *gin.Engine {
|
||||
@@ -72,8 +73,8 @@ func NewHandler(requireAuth bool) *gin.Engine {
|
||||
v1.GET("/favicon", apiV1.FavIcon)
|
||||
v1.GET("/health", apiV1.Health)
|
||||
v1.GET("/icons", apiV1.Icons)
|
||||
v1.POST("/reload", apiV1.Reload)
|
||||
v1.GET("/stats", apiV1.Stats)
|
||||
v1.GET("/events", apiV1.Events)
|
||||
|
||||
route := v1.Group("/route")
|
||||
{
|
||||
@@ -200,9 +201,8 @@ func ErrorHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Next()
|
||||
if len(c.Errors) > 0 {
|
||||
logger := log.With().Str("uri", c.Request.RequestURI).Logger()
|
||||
for _, err := range c.Errors {
|
||||
gperr.LogError("Internal error", err.Err, &logger)
|
||||
log.Err(err.Err).Str("uri", c.Request.RequestURI).Msg("Internal error")
|
||||
}
|
||||
if !c.IsWebsocket() {
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("Internal server error"))
|
||||
|
||||
@@ -2,6 +2,7 @@ package agentapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/route/provider"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type VerifyNewAgentRequest struct {
|
||||
@@ -84,9 +84,9 @@ func Verify(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, apitypes.Success(fmt.Sprintf("Added %d routes", nRoutesAdded)))
|
||||
}
|
||||
|
||||
var errAgentAlreadyExists = gperr.New("agent already exists")
|
||||
var errAgentAlreadyExists = errors.New("agent already exists")
|
||||
|
||||
func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, gperr.Error) {
|
||||
func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, error) {
|
||||
var agentCfg agent.AgentConfig
|
||||
agentCfg.Addr = host
|
||||
agentCfg.Runtime = containerRuntime
|
||||
@@ -105,12 +105,12 @@ func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client a
|
||||
|
||||
err := agentCfg.InitWithCerts(ctx, ca.Cert, client.Cert, client.Key)
|
||||
if err != nil {
|
||||
return 0, gperr.Wrap(err, "failed to initialize agent config")
|
||||
return 0, fmt.Errorf("failed to initialize agent config: %w", err)
|
||||
}
|
||||
|
||||
provider := provider.NewAgentProvider(&agentCfg)
|
||||
if _, loaded := cfgState.LoadOrStoreProvider(provider.String(), provider); loaded {
|
||||
return 0, gperr.Errorf("provider %s already exists", provider.String())
|
||||
return 0, fmt.Errorf("provider %s already exists", provider.String())
|
||||
}
|
||||
|
||||
// agent must be added before loading routes
|
||||
@@ -122,7 +122,7 @@ func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client a
|
||||
if err != nil {
|
||||
cfgState.DeleteProvider(provider.String())
|
||||
agentpool.Remove(&agentCfg)
|
||||
return 0, gperr.Wrap(err, "failed to load routes")
|
||||
return 0, fmt.Errorf("failed to load routes: %w", err)
|
||||
}
|
||||
|
||||
return provider.NumRoutes(), nil
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yusing/godoxy/internal/autocert"
|
||||
autocertctx "github.com/yusing/godoxy/internal/autocert/types"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
@@ -21,7 +22,7 @@ import (
|
||||
// @Failure 500 {object} apitypes.ErrorResponse "Internal server error"
|
||||
// @Router /cert/info [get]
|
||||
func Info(c *gin.Context) {
|
||||
provider := autocert.ActiveProvider.Load()
|
||||
provider := autocertctx.FromCtx(c.Request.Context())
|
||||
if provider == nil {
|
||||
c.JSON(http.StatusNotFound, apitypes.Error("autocert is not enabled"))
|
||||
return
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/autocert"
|
||||
autocertctx "github.com/yusing/godoxy/internal/autocert/types"
|
||||
"github.com/yusing/godoxy/internal/logging/memlogger"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
@@ -23,8 +23,8 @@ import (
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /cert/renew [get]
|
||||
func Renew(c *gin.Context) {
|
||||
autocert := autocert.ActiveProvider.Load()
|
||||
if autocert == nil {
|
||||
provider := autocertctx.FromCtx(c.Request.Context())
|
||||
if provider == nil {
|
||||
c.JSON(http.StatusNotFound, apitypes.Error("autocert is not enabled"))
|
||||
return
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func Renew(c *gin.Context) {
|
||||
}()
|
||||
|
||||
// renewal happens in background
|
||||
ok := autocert.ForceExpiryAll()
|
||||
ok := provider.ForceExpiryAll()
|
||||
if !ok {
|
||||
log.Error().Msg("cert renewal already in progress")
|
||||
time.Sleep(1 * time.Second) // wait for the log above to be sent
|
||||
@@ -67,5 +67,5 @@ func Renew(c *gin.Context) {
|
||||
}
|
||||
log.Info().Msg("cert force renewal requested")
|
||||
|
||||
autocert.WaitRenewalDone(manager.Context())
|
||||
provider.WaitRenewalDone(manager.Context())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
|
||||
_ "github.com/yusing/goutils/apitypes"
|
||||
@@ -35,18 +37,18 @@ func Containers(c *gin.Context) {
|
||||
serveHTTP[Container](c, GetContainers)
|
||||
}
|
||||
|
||||
func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Container, gperr.Error) {
|
||||
func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Container, error) {
|
||||
errs := gperr.NewBuilder("failed to get containers")
|
||||
containers := make([]Container, 0)
|
||||
for server, dockerClient := range dockerClients {
|
||||
conts, err := dockerClient.ContainerList(ctx, container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
errs.Add(err)
|
||||
errs.AddSubject(err, name)
|
||||
continue
|
||||
}
|
||||
for _, cont := range conts {
|
||||
containers = append(containers, Container{
|
||||
Server: server,
|
||||
Server: name,
|
||||
Name: cont.Names[0],
|
||||
ID: cont.ID,
|
||||
Image: cont.Image,
|
||||
@@ -58,11 +60,10 @@ func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Containe
|
||||
return containers[i].Name < containers[j].Name
|
||||
})
|
||||
if err := errs.Error(); err != nil {
|
||||
gperr.LogError("failed to get containers", err)
|
||||
if len(containers) == 0 {
|
||||
return nil, err
|
||||
if len(containers) > 0 {
|
||||
log.Err(err).Msg("failed to get containers from some servers")
|
||||
return containers, nil
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
return containers, nil
|
||||
return containers, errs.Error()
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func Info(c *gin.Context) {
|
||||
serveHTTP[dockerInfo](c, GetDockerInfo)
|
||||
}
|
||||
|
||||
func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerInfo, gperr.Error) {
|
||||
func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerInfo, error) {
|
||||
errs := gperr.NewBuilder("failed to get docker info")
|
||||
dockerInfos := make([]dockerInfo, len(dockerClients))
|
||||
|
||||
@@ -66,7 +66,7 @@ func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerIn
|
||||
for name, dockerClient := range dockerClients {
|
||||
info, err := dockerClient.Info(ctx)
|
||||
if err != nil {
|
||||
errs.Add(err)
|
||||
errs.AddSubject(err, name)
|
||||
continue
|
||||
}
|
||||
info.Name = name
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
@@ -43,7 +43,7 @@ func Stats(c *gin.Context) {
|
||||
dockerCfg, ok := docker.GetDockerCfgByContainerID(id)
|
||||
if !ok {
|
||||
var route types.Route
|
||||
route, ok = routes.GetIncludeExcluded(id)
|
||||
route, ok = entrypoint.FromCtx(c.Request.Context()).GetRoute(id)
|
||||
if ok {
|
||||
cont := route.ContainerInfo()
|
||||
if cont == nil {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
)
|
||||
@@ -39,7 +38,7 @@ func handleResult[V any, T ResultType[V]](c *gin.Context, errs error, result T)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func serveHTTP[V any, T ResultType[V]](c *gin.Context, getResult func(ctx context.Context, dockerClients DockerClients) (T, gperr.Error)) {
|
||||
func serveHTTP[V any, T ResultType[V]](c *gin.Context, getResult func(ctx context.Context, dockerClients DockerClients) (T, error)) {
|
||||
dockerClients := docker.Clients()
|
||||
defer closeAllClients(dockerClients)
|
||||
|
||||
|
||||
@@ -837,6 +837,45 @@
|
||||
"operationId": "stop"
|
||||
}
|
||||
},
|
||||
"/events": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v1"
|
||||
],
|
||||
"summary": "Get events history",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Event"
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden: unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error: internal error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-id": "events",
|
||||
"operationId": "events"
|
||||
}
|
||||
},
|
||||
"/favicon": {
|
||||
"get": {
|
||||
"description": "Get favicon",
|
||||
@@ -1219,6 +1258,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-id": "categories",
|
||||
@@ -1337,6 +1382,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-id": "items",
|
||||
@@ -2820,43 +2871,6 @@
|
||||
"operationId": "tail"
|
||||
}
|
||||
},
|
||||
"/reload": {
|
||||
"post": {
|
||||
"description": "Reload config",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"v1"
|
||||
],
|
||||
"summary": "Reload config",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/SuccessResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-id": "reload",
|
||||
"operationId": "reload"
|
||||
}
|
||||
},
|
||||
"/route/by_provider": {
|
||||
"get": {
|
||||
"description": "List routes by provider",
|
||||
@@ -3810,6 +3824,42 @@
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"Event": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"data": {
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"level": {
|
||||
"$ref": "#/definitions/events.Level",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
}
|
||||
},
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"FileType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -3979,7 +4029,6 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"latency": {
|
||||
"description": "latency in microseconds",
|
||||
"type": "number",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
@@ -3998,7 +4047,6 @@
|
||||
"x-omitempty": false
|
||||
},
|
||||
"uptime": {
|
||||
"description": "uptime in milliseconds",
|
||||
"type": "number",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
@@ -4074,7 +4122,7 @@
|
||||
"HealthMap": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/HealthStatusString"
|
||||
"$ref": "#/definitions/HealthInfoWithoutDetail"
|
||||
},
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
@@ -5059,6 +5107,7 @@
|
||||
"x-omitempty": false
|
||||
},
|
||||
"validationError": {
|
||||
"description": "we need the structured error, not the plain string",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
}
|
||||
@@ -5094,6 +5143,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"executionError": {
|
||||
"description": "we need the structured error, not the plain string",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
@@ -5329,7 +5379,6 @@
|
||||
"x-omitempty": false
|
||||
},
|
||||
"bind": {
|
||||
"description": "for TCP and UDP routes, bind address to listen on",
|
||||
"type": "string",
|
||||
"x-nullable": true
|
||||
},
|
||||
@@ -6691,6 +6740,23 @@
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"events.Level": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"debug",
|
||||
"info",
|
||||
"warn",
|
||||
"error"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"LevelDebug",
|
||||
"LevelInfo",
|
||||
"LevelWarn",
|
||||
"LevelError"
|
||||
],
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"icons.Source": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
||||
@@ -295,6 +295,20 @@ definitions:
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
Event:
|
||||
properties:
|
||||
action:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
data: {}
|
||||
level:
|
||||
$ref: '#/definitions/events.Level'
|
||||
timestamp:
|
||||
type: string
|
||||
uuid:
|
||||
type: string
|
||||
type: object
|
||||
FileType:
|
||||
enum:
|
||||
- config
|
||||
@@ -375,7 +389,6 @@ definitions:
|
||||
HealthInfoWithoutDetail:
|
||||
properties:
|
||||
latency:
|
||||
description: latency in microseconds
|
||||
type: number
|
||||
status:
|
||||
enum:
|
||||
@@ -387,7 +400,6 @@ definitions:
|
||||
- unknown
|
||||
type: string
|
||||
uptime:
|
||||
description: uptime in milliseconds
|
||||
type: number
|
||||
type: object
|
||||
HealthJSON:
|
||||
@@ -421,7 +433,7 @@ definitions:
|
||||
type: object
|
||||
HealthMap:
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/HealthStatusString'
|
||||
$ref: '#/definitions/HealthInfoWithoutDetail'
|
||||
type: object
|
||||
HealthStatusString:
|
||||
enum:
|
||||
@@ -882,7 +894,8 @@ definitions:
|
||||
type: string
|
||||
"on":
|
||||
type: string
|
||||
validationError: {}
|
||||
validationError:
|
||||
description: we need the structured error, not the plain string
|
||||
type: object
|
||||
PlaygroundRequest:
|
||||
properties:
|
||||
@@ -899,7 +912,8 @@ definitions:
|
||||
type: object
|
||||
PlaygroundResponse:
|
||||
properties:
|
||||
executionError: {}
|
||||
executionError:
|
||||
description: we need the structured error, not the plain string
|
||||
finalRequest:
|
||||
$ref: '#/definitions/FinalRequest'
|
||||
finalResponse:
|
||||
@@ -1007,7 +1021,6 @@ definitions:
|
||||
alias:
|
||||
type: string
|
||||
bind:
|
||||
description: for TCP and UDP routes, bind address to listen on
|
||||
type: string
|
||||
x-nullable: true
|
||||
container:
|
||||
@@ -1746,6 +1759,18 @@ definitions:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
events.Level:
|
||||
enum:
|
||||
- debug
|
||||
- info
|
||||
- warn
|
||||
- error
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- LevelDebug
|
||||
- LevelInfo
|
||||
- LevelWarn
|
||||
- LevelError
|
||||
icons.Source:
|
||||
enum:
|
||||
- https://
|
||||
@@ -2452,6 +2477,31 @@ paths:
|
||||
tags:
|
||||
- docker
|
||||
x-id: stop
|
||||
/events:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/Event'
|
||||
type: array
|
||||
"403":
|
||||
description: 'Forbidden: unauthorized'
|
||||
schema:
|
||||
$ref: '#/definitions/ErrorResponse'
|
||||
"500":
|
||||
description: 'Internal Server Error: internal error'
|
||||
schema:
|
||||
$ref: '#/definitions/ErrorResponse'
|
||||
summary: Get events history
|
||||
tags:
|
||||
- v1
|
||||
x-id: events
|
||||
/favicon:
|
||||
get:
|
||||
consumes:
|
||||
@@ -2707,6 +2757,10 @@ paths:
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/ErrorResponse'
|
||||
summary: List homepage categories
|
||||
tags:
|
||||
- homepage
|
||||
@@ -2784,6 +2838,10 @@ paths:
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/ErrorResponse'
|
||||
summary: Homepage items
|
||||
tags:
|
||||
- homepage
|
||||
@@ -3790,30 +3848,6 @@ paths:
|
||||
- proxmox
|
||||
- websocket
|
||||
x-id: tail
|
||||
/reload:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Reload config
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/SuccessResponse'
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
$ref: '#/definitions/ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/ErrorResponse'
|
||||
summary: Reload config
|
||||
tags:
|
||||
- v1
|
||||
x-id: reload
|
||||
/route/{which}:
|
||||
get:
|
||||
consumes:
|
||||
|
||||
44
internal/api/v1/events.go
Normal file
44
internal/api/v1/events.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
"github.com/yusing/goutils/events"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
)
|
||||
|
||||
// @x-id "events"
|
||||
// @BasePath /api/v1
|
||||
// @Summary Get events history
|
||||
// @Tags v1
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} events.Event
|
||||
// @Failure 403 {object} apitypes.ErrorResponse "Forbidden: unauthorized"
|
||||
// @Failure 500 {object} apitypes.ErrorResponse "Internal Server Error: internal error"
|
||||
// @Router /events [get]
|
||||
func Events(c *gin.Context) {
|
||||
if !httpheaders.IsWebsocket(c.Request.Header) {
|
||||
c.JSON(http.StatusOK, events.Global.Get())
|
||||
return
|
||||
}
|
||||
|
||||
manager, err := websocket.NewManagerWithUpgrade(c)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to upgrade to websocket"))
|
||||
return
|
||||
}
|
||||
defer manager.Close()
|
||||
|
||||
writer := manager.NewWriter(websocket.TextMessage)
|
||||
err = events.Global.ListenJSON(c.Request.Context(), writer)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to listen to events"))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/homepage/icons"
|
||||
iconfetch "github.com/yusing/godoxy/internal/homepage/icons/fetch"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
|
||||
_ "unsafe"
|
||||
@@ -73,7 +73,11 @@ func FavIcon(c *gin.Context) {
|
||||
//go:linkname GetFavIconFromAlias v1.GetFavIconFromAlias
|
||||
func GetFavIconFromAlias(ctx context.Context, alias string, variant icons.Variant) (iconfetch.Result, error) {
|
||||
// try with route.Icon
|
||||
r, ok := routes.HTTP.Get(alias)
|
||||
ep := entrypoint.FromCtx(ctx)
|
||||
if ep == nil { // impossible, but just in case
|
||||
return iconfetch.FetchResultWithErrorf(http.StatusInternalServerError, "entrypoint not initialized")
|
||||
}
|
||||
r, ok := ep.HTTPRoutes().Get(alias)
|
||||
if !ok {
|
||||
return iconfetch.FetchResultWithErrorf(http.StatusNotFound, "route not found")
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func Validate(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, apitypes.Success("file validated"))
|
||||
}
|
||||
|
||||
func validateFile(fileType FileType, content []byte) gperr.Error {
|
||||
func validateFile(fileType FileType, content []byte) error {
|
||||
switch fileType {
|
||||
case FileTypeConfig:
|
||||
return config.Validate(content)
|
||||
|
||||
@@ -5,13 +5,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/goutils/apitypes"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
|
||||
_ "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
type HealthMap = map[string]types.HealthInfoWithoutDetail // @name HealthMap
|
||||
|
||||
// @x-id "health"
|
||||
// @BasePath /api/v1
|
||||
// @Summary Get routes health info
|
||||
@@ -19,16 +21,21 @@ import (
|
||||
// @Tags v1,websocket
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} routes.HealthMap "Health info by route name"
|
||||
// @Success 200 {object} HealthMap "Health info by route name"
|
||||
// @Failure 403 {object} apitypes.ErrorResponse
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /health [get]
|
||||
func Health(c *gin.Context) {
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
if ep == nil { // impossible, but just in case
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized"))
|
||||
return
|
||||
}
|
||||
if httpheaders.IsWebsocket(c.Request.Header) {
|
||||
websocket.PeriodicWrite(c, 1*time.Second, func() (any, error) {
|
||||
return routes.GetHealthInfoSimple(), nil
|
||||
return ep.GetHealthInfoWithoutDetail(), nil
|
||||
})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, routes.GetHealthInfoSimple())
|
||||
c.JSON(http.StatusOK, ep.GetHealthInfoWithoutDetail())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/homepage"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
|
||||
_ "github.com/yusing/goutils/apitypes"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
// @x-id "categories"
|
||||
@@ -19,17 +19,23 @@ import (
|
||||
// @Produce json
|
||||
// @Success 200 {array} string
|
||||
// @Failure 403 {object} apitypes.ErrorResponse
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /homepage/categories [get]
|
||||
func Categories(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, HomepageCategories())
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
if ep == nil { // impossible, but just in case
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HomepageCategories(ep))
|
||||
}
|
||||
|
||||
func HomepageCategories() []string {
|
||||
func HomepageCategories(ep entrypoint.Entrypoint) []string {
|
||||
check := make(map[string]struct{})
|
||||
categories := make([]string, 0)
|
||||
categories = append(categories, homepage.CategoryAll)
|
||||
categories = append(categories, homepage.CategoryFavorites)
|
||||
for _, r := range routes.HTTP.Iter {
|
||||
for _, r := range ep.HTTPRoutes().Iter {
|
||||
item := r.HomepageItem()
|
||||
if item.Category == "" {
|
||||
continue
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/homepage"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
@@ -36,6 +36,7 @@ type HomepageItemsRequest struct {
|
||||
// @Success 200 {object} homepage.Homepage
|
||||
// @Failure 400 {object} apitypes.ErrorResponse
|
||||
// @Failure 403 {object} apitypes.ErrorResponse
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /homepage/items [get]
|
||||
func Items(c *gin.Context) {
|
||||
var request HomepageItemsRequest
|
||||
@@ -53,29 +54,35 @@ func Items(c *gin.Context) {
|
||||
hostname = host
|
||||
}
|
||||
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
if ep == nil {
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not found in context", nil))
|
||||
return
|
||||
}
|
||||
|
||||
if httpheaders.IsWebsocket(c.Request.Header) {
|
||||
websocket.PeriodicWrite(c, 2*time.Second, func() (any, error) {
|
||||
return HomepageItems(proto, hostname, &request), nil
|
||||
return HomepageItems(ep, proto, hostname, &request), nil
|
||||
})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, HomepageItems(proto, hostname, &request))
|
||||
c.JSON(http.StatusOK, HomepageItems(ep, proto, hostname, &request))
|
||||
}
|
||||
}
|
||||
|
||||
func HomepageItems(proto, hostname string, request *HomepageItemsRequest) homepage.Homepage {
|
||||
func HomepageItems(ep entrypoint.Entrypoint, proto, hostname string, request *HomepageItemsRequest) homepage.Homepage {
|
||||
switch proto {
|
||||
case "http", "https":
|
||||
default:
|
||||
proto = "http"
|
||||
}
|
||||
|
||||
hp := homepage.NewHomepageMap(routes.HTTP.Size())
|
||||
hp := homepage.NewHomepageMap(ep.HTTPRoutes().Size())
|
||||
|
||||
if strings.Count(hostname, ".") > 1 {
|
||||
_, hostname, _ = strings.Cut(hostname, ".") // remove the subdomain
|
||||
}
|
||||
|
||||
for _, r := range routes.HTTP.Iter {
|
||||
for _, r := range ep.HTTPRoutes().Iter {
|
||||
if request.Provider != "" && r.ProviderName() != request.Provider {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func AllSystemInfo(c *gin.Context) {
|
||||
data, err := systeminfo.Poller.GetRespData(req.Period, query)
|
||||
if err != nil {
|
||||
numErrs.Add(1)
|
||||
return gperr.PrependSubject("Main server", err)
|
||||
return gperr.PrependSubject(err, "Main server")
|
||||
}
|
||||
select {
|
||||
case <-manager.Done():
|
||||
@@ -132,7 +132,7 @@ func AllSystemInfo(c *gin.Context) {
|
||||
data, err := getAgentSystemInfoWithRetry(manager.Context(), a, queryEncoded)
|
||||
if err != nil {
|
||||
numErrs.Add(1)
|
||||
return gperr.PrependSubject("Agent "+a.Name, err)
|
||||
return gperr.PrependSubject(err, "Agent "+a.Name)
|
||||
}
|
||||
select {
|
||||
case <-manager.Done():
|
||||
@@ -169,7 +169,7 @@ func AllSystemInfo(c *gin.Context) {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to get all system info"))
|
||||
return
|
||||
}
|
||||
gperr.LogWarn("failed to get some system info", err)
|
||||
log.Warn().Err(err).Msg("failed to get some system info")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@ package proxmoxapi
|
||||
|
||||
type ActionRequest struct {
|
||||
Node string `uri:"node" binding:"required"`
|
||||
VMID int `uri:"vmid" binding:"required"`
|
||||
VMID uint64 `uri:"vmid" binding:"required"`
|
||||
} // @name ProxmoxVMActionRequest
|
||||
|
||||
@@ -11,10 +11,7 @@ import (
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
)
|
||||
|
||||
type StatsRequest struct {
|
||||
Node string `uri:"node" binding:"required"`
|
||||
VMID int `uri:"vmid" binding:"required"`
|
||||
}
|
||||
type StatsRequest ActionRequest
|
||||
|
||||
// @x-id "nodeStats"
|
||||
// @BasePath /api/v1
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yusing/godoxy/internal/config"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
// @x-id "reload"
|
||||
// @BasePath /api/v1
|
||||
// @Summary Reload config
|
||||
// @Description Reload config
|
||||
// @Tags v1
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} apitypes.SuccessResponse
|
||||
// @Failure 403 {object} apitypes.ErrorResponse
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /reload [post]
|
||||
func Reload(c *gin.Context) {
|
||||
if err := config.Reload(); err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to reload config"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, apitypes.Success("config reloaded"))
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
|
||||
_ "github.com/yusing/goutils/apitypes"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
type RoutesByProvider map[string][]route.Route
|
||||
@@ -24,5 +24,10 @@ type RoutesByProvider map[string][]route.Route
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /route/by_provider [get]
|
||||
func ByProvider(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, routes.ByProvider())
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
if ep == nil { // impossible, but just in case
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, ep.RoutesByProvider())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package routeApi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -54,16 +55,16 @@ type PlaygroundResponse struct {
|
||||
MatchedRules []string `json:"matchedRules"`
|
||||
FinalRequest FinalRequest `json:"finalRequest"`
|
||||
FinalResponse FinalResponse `json:"finalResponse"`
|
||||
ExecutionError gperr.Error `json:"executionError,omitempty"`
|
||||
ExecutionError error `json:"executionError,omitempty"` // we need the structured error, not the plain string
|
||||
UpstreamCalled bool `json:"upstreamCalled"`
|
||||
} // @name PlaygroundResponse
|
||||
|
||||
type ParsedRule struct {
|
||||
Name string `json:"name"`
|
||||
On string `json:"on"`
|
||||
Do string `json:"do"`
|
||||
ValidationError gperr.Error `json:"validationError,omitempty"`
|
||||
IsResponseRule bool `json:"isResponseRule"`
|
||||
Name string `json:"name"`
|
||||
On string `json:"on"`
|
||||
Do string `json:"do"`
|
||||
ValidationError error `json:"validationError,omitempty"` // we need the structured error, not the plain string
|
||||
IsResponseRule bool `json:"isResponseRule"`
|
||||
} // @name ParsedRule
|
||||
|
||||
type FinalRequest struct {
|
||||
@@ -138,7 +139,7 @@ func Playground(c *gin.Context) {
|
||||
// Execute rules
|
||||
matchedRules := []string{}
|
||||
upstreamCalled := false
|
||||
var executionError gperr.Error
|
||||
var executionError error
|
||||
|
||||
// Variables to capture modified request state
|
||||
var finalReqMethod, finalReqPath, finalReqHost string
|
||||
@@ -244,20 +245,22 @@ func Playground(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFunc, outErr *gperr.Error) {
|
||||
func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFunc, outErr *error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if outErr != nil {
|
||||
*outErr = gperr.Errorf("panic during rule execution: %v", r)
|
||||
*outErr = fmt.Errorf("panic during rule execution: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
h(w, r)
|
||||
}
|
||||
|
||||
func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) {
|
||||
var parsedRules []ParsedRule
|
||||
var rulesList rules.Rules
|
||||
func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, error) {
|
||||
parsedRules := make([]ParsedRule, 0, len(rawRules))
|
||||
rulesList := make(rules.Rules, 0, len(rawRules))
|
||||
|
||||
var valErrs gperr.Builder
|
||||
|
||||
// Parse each rule individually to capture per-rule errors
|
||||
for _, rawRule := range rawRules {
|
||||
@@ -284,7 +287,11 @@ func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) {
|
||||
|
||||
// Determine if valid
|
||||
isValid := onErr == nil && doErr == nil
|
||||
validationErr := gperr.Join(gperr.PrependSubject("on", onErr), gperr.PrependSubject("do", doErr))
|
||||
var validationErr error
|
||||
if !isValid {
|
||||
validationErr = gperr.Join(gperr.PrependSubject(onErr, "on"), gperr.PrependSubject(doErr, "do"))
|
||||
valErrs.Add(validationErr)
|
||||
}
|
||||
|
||||
parsedRules = append(parsedRules, ParsedRule{
|
||||
Name: name,
|
||||
@@ -300,7 +307,7 @@ func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) {
|
||||
}
|
||||
}
|
||||
|
||||
return parsedRules, rulesList, nil
|
||||
return parsedRules, rulesList, valErrs.Error()
|
||||
}
|
||||
|
||||
func createMockRequest(mock MockRequest) *http.Request {
|
||||
|
||||
@@ -79,7 +79,7 @@ func TestPlayground(t *testing.T) {
|
||||
if len(resp.MatchedRules) != 1 {
|
||||
t.Errorf("expected 1 matched rule, got %d", len(resp.MatchedRules))
|
||||
}
|
||||
if resp.FinalResponse.StatusCode != 403 {
|
||||
if resp.FinalResponse.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("expected status 403, got %d", resp.FinalResponse.StatusCode)
|
||||
}
|
||||
if resp.UpstreamCalled {
|
||||
@@ -168,7 +168,7 @@ func TestPlayground(t *testing.T) {
|
||||
if len(resp.MatchedRules) != 1 {
|
||||
t.Errorf("expected 1 matched rule, got %d", len(resp.MatchedRules))
|
||||
}
|
||||
if resp.FinalResponse.StatusCode != 405 {
|
||||
if resp.FinalResponse.StatusCode != http.StatusMethodNotAllowed {
|
||||
t.Errorf("expected status 405, got %d", resp.FinalResponse.StatusCode)
|
||||
}
|
||||
},
|
||||
@@ -179,7 +179,7 @@ func TestPlayground(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create request
|
||||
body, _ := json.Marshal(tt.request)
|
||||
req := httptest.NewRequest("POST", "/api/v1/route/playground", bytes.NewReader(body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/route/playground", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Create response recorder
|
||||
@@ -214,7 +214,7 @@ func TestPlayground(t *testing.T) {
|
||||
func TestPlaygroundInvalidRequest(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/v1/route/playground", bytes.NewReader([]byte(`{}`)))
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/route/playground", bytes.NewReader([]byte(`{}`)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,13 @@ func Route(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
route, ok := routes.GetIncludeExcluded(request.Which)
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
if ep == nil { // impossible, but just in case
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized"))
|
||||
return
|
||||
}
|
||||
|
||||
route, ok := ep.GetRoute(request.Which)
|
||||
if ok {
|
||||
c.JSON(http.StatusOK, route)
|
||||
return
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
@@ -32,14 +32,16 @@ func Routes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
|
||||
provider := c.Query("provider")
|
||||
if provider == "" {
|
||||
c.JSON(http.StatusOK, slices.Collect(routes.IterAll))
|
||||
c.JSON(http.StatusOK, slices.Collect(ep.IterRoutes))
|
||||
return
|
||||
}
|
||||
|
||||
rts := make([]types.Route, 0, routes.NumAllRoutes())
|
||||
for r := range routes.IterAll {
|
||||
rts := make([]types.Route, 0, ep.NumRoutes())
|
||||
for r := range ep.IterRoutes {
|
||||
if r.ProviderName() == provider {
|
||||
rts = append(rts, r)
|
||||
}
|
||||
@@ -48,17 +50,19 @@ func Routes(c *gin.Context) {
|
||||
}
|
||||
|
||||
func RoutesWS(c *gin.Context) {
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
|
||||
provider := c.Query("provider")
|
||||
if provider == "" {
|
||||
websocket.PeriodicWrite(c, 3*time.Second, func() (any, error) {
|
||||
return slices.Collect(routes.IterAll), nil
|
||||
return slices.Collect(ep.IterRoutes), nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
websocket.PeriodicWrite(c, 3*time.Second, func() (any, error) {
|
||||
rts := make([]types.Route, 0, routes.NumAllRoutes())
|
||||
for r := range routes.IterAll {
|
||||
rts := make([]types.Route, 0, ep.NumRoutes())
|
||||
for r := range ep.IterRoutes {
|
||||
if r.ProviderName() == provider {
|
||||
rts = append(rts, r)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user