@@ -21,6 +21,14 @@ components:
|
||||
error:
|
||||
type: string
|
||||
|
||||
Info:
|
||||
type: object
|
||||
properties:
|
||||
version:
|
||||
type: string
|
||||
started:
|
||||
type: string
|
||||
|
||||
AuthInternalUser:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1134,6 +1142,25 @@ components:
|
||||
|
||||
paths:
|
||||
|
||||
/v3/info:
|
||||
get:
|
||||
operationId: info
|
||||
tags: [General]
|
||||
summary: returns informations about the instance.
|
||||
responses:
|
||||
'200':
|
||||
description: the request was successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Info'
|
||||
'500':
|
||||
description: server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/v3/auth/jwks/refresh:
|
||||
post:
|
||||
operationId: authJwksRefresh
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Control API
|
||||
|
||||
The server can be queried and controlled with an API, that can be enabled by setting the `api` parameter in the configuration:
|
||||
The server can be queried and controlled with an API, that can be enabled by toggling the `api` parameter in the configuration:
|
||||
|
||||
```yml
|
||||
api: yes
|
||||
|
||||
@@ -88,6 +88,8 @@ type apiParent interface {
|
||||
|
||||
// API is an API server.
|
||||
type API struct {
|
||||
Version string
|
||||
Started time.Time
|
||||
Address string
|
||||
Encryption bool
|
||||
ServerKey string
|
||||
@@ -121,6 +123,8 @@ func (a *API) Initialize() error {
|
||||
|
||||
group := router.Group("/v3")
|
||||
|
||||
group.GET("/info", a.onInfo)
|
||||
|
||||
group.POST("/auth/jwks/refresh", a.onAuthJwksRefresh)
|
||||
|
||||
group.GET("/config/global/get", a.onConfigGlobalGet)
|
||||
@@ -538,6 +542,13 @@ func (a *API) onConfigPathsDelete(ctx *gin.Context) {
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func (a *API) onInfo(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusOK, &defs.APIInfo{
|
||||
Version: a.Version,
|
||||
Started: a.Started,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *API) onAuthJwksRefresh(ctx *gin.Context) {
|
||||
a.AuthManager.RefreshJWTJWKS()
|
||||
ctx.Status(http.StatusOK)
|
||||
|
||||
@@ -117,6 +117,34 @@ func TestPreflightRequest(t *testing.T) {
|
||||
require.Equal(t, byts, []byte{})
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
cnf := tempConf(t, "api: yes\n")
|
||||
|
||||
api := API{
|
||||
Version: "v1.2.3",
|
||||
Started: time.Date(2008, 11, 7, 11, 22, 0, 0, time.Local),
|
||||
Address: "localhost:9997",
|
||||
ReadTimeout: conf.Duration(10 * time.Second),
|
||||
Conf: cnf,
|
||||
AuthManager: test.NilAuthManager,
|
||||
Parent: &testParent{},
|
||||
}
|
||||
err := api.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer api.Close()
|
||||
|
||||
tr := &http.Transport{}
|
||||
defer tr.CloseIdleConnections()
|
||||
hc := &http.Client{Transport: tr}
|
||||
|
||||
var out map[string]interface{}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/info", nil, &out)
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"started": time.Date(2008, 11, 7, 11, 22, 0, 0, time.Local).Format(time.RFC3339),
|
||||
"version": "v1.2.3",
|
||||
}, out)
|
||||
}
|
||||
|
||||
func TestConfigGlobalGet(t *testing.T) {
|
||||
cnf := tempConf(t, "api: yes\n")
|
||||
checked := false
|
||||
@@ -621,10 +649,10 @@ func TestRecordingsList(t *testing.T) {
|
||||
"name": "mypath1",
|
||||
"segments": []interface{}{
|
||||
map[string]interface{}{
|
||||
"start": time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
},
|
||||
map[string]interface{}{
|
||||
"start": time.Date(2009, 11, 0o7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2009, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -632,7 +660,7 @@ func TestRecordingsList(t *testing.T) {
|
||||
"name": "mypath2",
|
||||
"segments": []interface{}{
|
||||
map[string]interface{}{
|
||||
"start": time.Date(2009, 11, 0o7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2009, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -680,10 +708,10 @@ func TestRecordingsGet(t *testing.T) {
|
||||
"name": "mypath1",
|
||||
"segments": []interface{}{
|
||||
map[string]interface{}{
|
||||
"start": time.Date(2008, 11, 0o7, 11, 22, 0, 0, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2008, 11, 7, 11, 22, 0, 0, time.Local).Format(time.RFC3339Nano),
|
||||
},
|
||||
map[string]interface{}{
|
||||
"start": time.Date(2009, 11, 0o7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2009, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
|
||||
},
|
||||
},
|
||||
}, out)
|
||||
@@ -725,7 +753,7 @@ func TestRecordingsDeleteSegment(t *testing.T) {
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("path", "mypath1")
|
||||
v.Set("start", time.Date(2008, 11, 0o7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("start", time.Date(2008, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano))
|
||||
u.RawQuery = v.Encode()
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, u.String(), nil)
|
||||
|
||||
@@ -41,6 +41,8 @@ import (
|
||||
//go:embed VERSION
|
||||
var version []byte
|
||||
|
||||
var started = time.Now()
|
||||
|
||||
var defaultConfPaths = []string{
|
||||
"rtsp-simple-server.yml",
|
||||
"mediamtx.yml",
|
||||
@@ -615,6 +617,8 @@ func (p *Core) createResources(initial bool) error {
|
||||
if p.conf.API &&
|
||||
p.api == nil {
|
||||
i := &api.API{
|
||||
Version: string(version),
|
||||
Started: started,
|
||||
Address: p.conf.APIAddress,
|
||||
Encryption: p.conf.APIEncryption,
|
||||
ServerKey: p.conf.APIServerKey,
|
||||
|
||||
@@ -55,6 +55,12 @@ type APIError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// APIInfo is a info response.
|
||||
type APIInfo struct {
|
||||
Version string `json:"version"`
|
||||
Started time.Time `json:"started"`
|
||||
}
|
||||
|
||||
// APIPathConfList is a list of path configurations.
|
||||
type APIPathConfList struct {
|
||||
ItemCount int `json:"itemCount"`
|
||||
|
||||
@@ -264,7 +264,7 @@ func TestOnGet(t *testing.T) {
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("path", "mypath")
|
||||
v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("start", time.Date(2008, 11, 7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("duration", "3")
|
||||
v.Set("format", format)
|
||||
u.RawQuery = v.Encode()
|
||||
@@ -488,7 +488,7 @@ func TestOnGetDifferentInit(t *testing.T) {
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("path", "mypath")
|
||||
v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("start", time.Date(2008, 11, 7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("duration", "2")
|
||||
v.Set("format", "fmp4")
|
||||
u.RawQuery = v.Encode()
|
||||
@@ -566,7 +566,7 @@ func TestOnGetNTPCompensation(t *testing.T) {
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("path", "mypath")
|
||||
v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("start", time.Date(2008, 11, 7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("duration", "3")
|
||||
v.Set("format", "fmp4")
|
||||
u.RawQuery = v.Encode()
|
||||
|
||||
@@ -91,18 +91,18 @@ func TestOnList(t *testing.T) {
|
||||
|
||||
switch ca {
|
||||
case "filtered":
|
||||
v.Set("start", time.Date(2008, 11, 0o7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("end", time.Date(2009, 11, 0o7, 11, 23, 4, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("start", time.Date(2008, 11, 7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("end", time.Date(2009, 11, 7, 11, 23, 4, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
|
||||
case "filtered and gap":
|
||||
v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("end", time.Date(2009, 11, 0o7, 11, 23, 4, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("start", time.Date(2008, 11, 7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("end", time.Date(2009, 11, 7, 11, 23, 4, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
|
||||
case "start after duration":
|
||||
v.Set("start", time.Date(2010, 11, 0o7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("start", time.Date(2010, 11, 7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
|
||||
case "start before first":
|
||||
v.Set("start", time.Date(2007, 11, 0o7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
v.Set("start", time.Date(2007, 11, 7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
|
||||
}
|
||||
|
||||
u.RawQuery = v.Encode()
|
||||
@@ -130,15 +130,15 @@ func TestOnList(t *testing.T) {
|
||||
require.Equal(t, []interface{}{
|
||||
map[string]interface{}{
|
||||
"duration": float64(66),
|
||||
"start": time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"url": "http://localhost:9996/get?duration=66&path=mypath&start=" +
|
||||
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
},
|
||||
map[string]interface{}{
|
||||
"duration": float64(4),
|
||||
"start": time.Date(2009, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2009, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"url": "http://localhost:9996/get?duration=4&path=mypath&start=" +
|
||||
url.QueryEscape(time.Date(2009, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
url.QueryEscape(time.Date(2009, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
},
|
||||
}, out)
|
||||
|
||||
@@ -146,15 +146,15 @@ func TestOnList(t *testing.T) {
|
||||
require.Equal(t, []interface{}{
|
||||
map[string]interface{}{
|
||||
"duration": float64(65),
|
||||
"start": time.Date(2008, 11, 0o7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2008, 11, 7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"url": "http://localhost:9996/get?duration=65&path=mypath&start=" +
|
||||
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
},
|
||||
map[string]interface{}{
|
||||
"duration": float64(2),
|
||||
"start": time.Date(2009, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2009, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"url": "http://localhost:9996/get?duration=2&path=mypath&start=" +
|
||||
url.QueryEscape(time.Date(2009, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
url.QueryEscape(time.Date(2009, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
},
|
||||
}, out)
|
||||
|
||||
@@ -162,9 +162,9 @@ func TestOnList(t *testing.T) {
|
||||
require.Equal(t, []interface{}{
|
||||
map[string]interface{}{
|
||||
"duration": float64(4),
|
||||
"start": time.Date(2008, 11, 0o7, 11, 24, 2, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2008, 11, 7, 11, 24, 2, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"url": "http://localhost:9996/get?duration=4&path=mypath&start=" +
|
||||
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 24, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
url.QueryEscape(time.Date(2008, 11, 7, 11, 24, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
},
|
||||
}, out)
|
||||
|
||||
@@ -172,15 +172,15 @@ func TestOnList(t *testing.T) {
|
||||
require.Equal(t, []interface{}{
|
||||
map[string]interface{}{
|
||||
"duration": float64(62),
|
||||
"start": time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"url": "http://localhost:9996/get?duration=62&path=mypath&start=" +
|
||||
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
},
|
||||
map[string]interface{}{
|
||||
"duration": float64(1),
|
||||
"start": time.Date(2008, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2008, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"url": "http://localhost:9996/get?duration=1&path=mypath&start=" +
|
||||
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
url.QueryEscape(time.Date(2008, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
},
|
||||
}, out)
|
||||
}
|
||||
@@ -327,9 +327,9 @@ func TestOnListCachedDuration(t *testing.T) {
|
||||
require.Equal(t, []interface{}{
|
||||
map[string]interface{}{
|
||||
"duration": float64(50),
|
||||
"start": time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"start": time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
|
||||
"url": "http://localhost:9996/get?duration=50&path=mypath&start=" +
|
||||
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
|
||||
},
|
||||
}, out)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ var pathCases = []struct {
|
||||
"standard",
|
||||
"%path/%Y-%m-%d_%H-%M-%S-%f.mp4",
|
||||
Path{
|
||||
Start: time.Date(2008, 11, 0o7, 11, 22, 4, 123456000, time.Local),
|
||||
Start: time.Date(2008, 11, 7, 11, 22, 4, 123456000, time.Local),
|
||||
Path: "mypath",
|
||||
},
|
||||
"mypath/2008-11-07_11-22-04-123456.mp4",
|
||||
|
||||
@@ -44,6 +44,10 @@ func TestAPIDocs(t *testing.T) {
|
||||
openAPIKey string
|
||||
goStruct any
|
||||
}{
|
||||
{
|
||||
"Info",
|
||||
defs.APIInfo{},
|
||||
},
|
||||
{
|
||||
"AuthInternalUser",
|
||||
conf.AuthInternalUser{},
|
||||
|
||||
Reference in New Issue
Block a user