diff --git a/backend/embed/api_docs/api.swagger.json b/backend/embed/api_docs/api.swagger.json
index c22f945..90ef98b 100644
--- a/backend/embed/api_docs/api.swagger.json
+++ b/backend/embed/api_docs/api.swagger.json
@@ -91,6 +91,25 @@
"$ref": "file://./paths/hosts/hostID/delete.json"
}
},
+ "/nginx-templates": {
+ "get": {
+ "$ref": "file://./paths/nginx-templates/get.json"
+ },
+ "post": {
+ "$ref": "file://./paths/nginx-templates/post.json"
+ }
+ },
+ "/nginx-templates/{templateID}": {
+ "get": {
+ "$ref": "file://./paths/nginx-templates/templateID/get.json"
+ },
+ "put": {
+ "$ref": "file://./paths/nginx-templates/templateID/put.json"
+ },
+ "delete": {
+ "$ref": "file://./paths/nginx-templates/templateID/delete.json"
+ }
+ },
"/schema": {
"get": {
"$ref": "file://./paths/schema/get.json"
@@ -139,6 +158,22 @@
"$ref": "file://./paths/tokens/post.json"
}
},
+ "/upstreams": {
+ "get": {
+ "$ref": "file://./paths/upstreams/get.json"
+ },
+ "post": {
+ "$ref": "file://./paths/upstreams/post.json"
+ }
+ },
+ "/upstreams/{upstreamID}": {
+ "get": {
+ "$ref": "file://./paths/upstreams/upstreamID/get.json"
+ },
+ "delete": {
+ "$ref": "file://./paths/upstreams/upstreamID/delete.json"
+ }
+ },
"/users": {
"get": {
"$ref": "file://./paths/users/get.json"
@@ -205,11 +240,11 @@
"HostObject": {
"$ref": "file://./components/HostObject.json"
},
- "HostTemplateList": {
- "$ref": "file://./components/HostTemplateList.json"
+ "NginxTemplateList": {
+ "$ref": "file://./components/NginxTemplateList.json"
},
- "HostTemplateObject": {
- "$ref": "file://./components/HostTemplateObject.json"
+ "NginxTemplateObject": {
+ "$ref": "file://./components/NginxTemplateObject.json"
},
"SettingList": {
"$ref": "file://./components/SettingList.json"
@@ -229,6 +264,12 @@
"TokenObject": {
"$ref": "file://./components/TokenObject.json"
},
+ "UpstreamList": {
+ "$ref": "file://./components/UpstreamList.json"
+ },
+ "UpstreamObject": {
+ "$ref": "file://./components/UpstreamObject.json"
+ },
"UserAuthObject": {
"$ref": "file://./components/UserAuthObject.json"
},
diff --git a/backend/embed/api_docs/components/NginxTemplateList.json b/backend/embed/api_docs/components/NginxTemplateList.json
new file mode 100644
index 0000000..c0c11be
--- /dev/null
+++ b/backend/embed/api_docs/components/NginxTemplateList.json
@@ -0,0 +1,40 @@
+{
+ "type": "object",
+ "description": "NginxTemplateList",
+ "additionalProperties": false,
+ "required": ["total", "offset", "limit", "sort"],
+ "properties": {
+ "total": {
+ "type": "integer",
+ "description": "Total number of rows"
+ },
+ "offset": {
+ "type": "integer",
+ "description": "Pagination Offset"
+ },
+ "limit": {
+ "type": "integer",
+ "description": "Pagination Limit"
+ },
+ "sort": {
+ "type": "array",
+ "description": "Sorting",
+ "items": {
+ "$ref": "#/components/schemas/SortObject"
+ }
+ },
+ "filter": {
+ "type": "array",
+ "description": "Filters",
+ "items": {
+ "$ref": "#/components/schemas/FilterObject"
+ }
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NginxTemplateObject"
+ }
+ }
+ }
+}
diff --git a/backend/embed/api_docs/components/HostTemplateObject.json b/backend/embed/api_docs/components/NginxTemplateObject.json
similarity index 82%
rename from backend/embed/api_docs/components/HostTemplateObject.json
rename to backend/embed/api_docs/components/NginxTemplateObject.json
index ac4937a..5668660 100644
--- a/backend/embed/api_docs/components/HostTemplateObject.json
+++ b/backend/embed/api_docs/components/NginxTemplateObject.json
@@ -1,6 +1,6 @@
{
"type": "object",
- "description": "HostTemplateObject",
+ "description": "NginxTemplateObject",
"additionalProperties": false,
"required": [
"id",
@@ -8,7 +8,7 @@
"modified_on",
"user_id",
"name",
- "host_type",
+ "type",
"template"
],
"properties": {
@@ -32,9 +32,9 @@
"type": "string",
"minLength": 1
},
- "host_type": {
+ "type": {
"type": "string",
- "pattern": "^proxy|redirect|dead|stream$"
+ "pattern": "^proxy|redirect|dead|stream|upstream$"
},
"template": {
"type": "string",
diff --git a/backend/embed/api_docs/components/HostTemplateList.json b/backend/embed/api_docs/components/UpstreamList.json
similarity index 88%
rename from backend/embed/api_docs/components/HostTemplateList.json
rename to backend/embed/api_docs/components/UpstreamList.json
index a4ad36e..316725a 100644
--- a/backend/embed/api_docs/components/HostTemplateList.json
+++ b/backend/embed/api_docs/components/UpstreamList.json
@@ -1,6 +1,6 @@
{
"type": "object",
- "description": "HostTemplateList",
+ "description": "UpstreamList",
"additionalProperties": false,
"required": ["total", "offset", "limit", "sort"],
"properties": {
@@ -33,7 +33,7 @@
"items": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/HostTemplateObject"
+ "$ref": "#/components/schemas/UpstreamObject"
}
}
}
diff --git a/backend/embed/api_docs/components/UpstreamObject.json b/backend/embed/api_docs/components/UpstreamObject.json
new file mode 100644
index 0000000..0ad044a
--- /dev/null
+++ b/backend/embed/api_docs/components/UpstreamObject.json
@@ -0,0 +1,133 @@
+{
+ "type": "object",
+ "description": "UpstreamObject",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "created_on",
+ "modified_on",
+ "user_id",
+ "name",
+ "nginx_template_id",
+ "ip_hash",
+ "ntlm",
+ "keepalive",
+ "keepalive_requests",
+ "keepalive_time",
+ "keepalive_timeout",
+ "advanced_config",
+ "status",
+ "error_message",
+ "servers"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "created_on": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "modified_on": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "user_id": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "nginx_template_id": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "ip_hash": {
+ "type": "boolean"
+ },
+ "ntlm": {
+ "type": "boolean"
+ },
+ "keepalive": {
+ "type": "integer"
+ },
+ "keepalive_requests": {
+ "type": "integer"
+ },
+ "keepalive_time": {
+ "type": "string"
+ },
+ "keepalive_timeout": {
+ "type": "string"
+ },
+ "advanced_config": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string"
+ },
+ "error_message": {
+ "type": "string"
+ },
+ "servers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "created_on",
+ "modified_on",
+ "upstream_id",
+ "server",
+ "weight",
+ "max_conns",
+ "max_fails",
+ "fail_timeout",
+ "backup"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "created_on": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "modified_on": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "upstream_id": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "server": {
+ "type": "string",
+ "minLength": 2
+ },
+ "weight": {
+ "type": "integer"
+ },
+ "max_conns": {
+ "type": "integer"
+ },
+ "max_fails": {
+ "type": "integer"
+ },
+ "fail_timeout": {
+ "type": "integer"
+ },
+ "backup": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/backend/embed/api_docs/paths/hosts/get.json b/backend/embed/api_docs/paths/hosts/get.json
index 160980e..a8683c7 100644
--- a/backend/embed/api_docs/paths/hosts/get.json
+++ b/backend/embed/api_docs/paths/hosts/get.json
@@ -64,7 +64,7 @@
"modified_on": 1646279455,
"user_id": 2,
"type": "proxy",
- "host_template_id": 1,
+ "nginx_template_id": 1,
"listen_interface": "",
"domain_names": ["jc21.com"],
"upstream_id": 0,
@@ -78,7 +78,6 @@
"hsts_enabled": false,
"hsts_subdomains": false,
"paths": "",
- "upstream_options": "",
"advanced_config": "",
"is_disabled": false
}
diff --git a/backend/embed/api_docs/paths/hosts/hostID/get.json b/backend/embed/api_docs/paths/hosts/hostID/get.json
index 654f7df..090fdce 100644
--- a/backend/embed/api_docs/paths/hosts/hostID/get.json
+++ b/backend/embed/api_docs/paths/hosts/hostID/get.json
@@ -37,7 +37,7 @@
"modified_on": 1646279455,
"user_id": 2,
"type": "proxy",
- "host_template_id": 1,
+ "nginx_template_id": 1,
"listen_interface": "",
"domain_names": ["jc21.com"],
"upstream_id": 0,
@@ -51,7 +51,6 @@
"hsts_enabled": false,
"hsts_subdomains": false,
"paths": "",
- "upstream_options": "",
"advanced_config": "",
"is_disabled": false
}
diff --git a/backend/embed/api_docs/paths/hosts/hostID/put.json b/backend/embed/api_docs/paths/hosts/hostID/put.json
index ec9675c..f1d38f9 100644
--- a/backend/embed/api_docs/paths/hosts/hostID/put.json
+++ b/backend/embed/api_docs/paths/hosts/hostID/put.json
@@ -46,7 +46,7 @@
"modified_on": 1646279455,
"user_id": 2,
"type": "proxy",
- "host_template_id": 1,
+ "nginx_template_id": 1,
"listen_interface": "",
"domain_names": ["jc21.com"],
"upstream_id": 0,
@@ -60,7 +60,6 @@
"hsts_enabled": false,
"hsts_subdomains": false,
"paths": "",
- "upstream_options": "",
"advanced_config": "",
"is_disabled": false
}
diff --git a/backend/embed/api_docs/paths/hosts/post.json b/backend/embed/api_docs/paths/hosts/post.json
index e0362c6..da5dcd9 100644
--- a/backend/embed/api_docs/paths/hosts/post.json
+++ b/backend/embed/api_docs/paths/hosts/post.json
@@ -33,7 +33,7 @@
"modified_on": 1645700556,
"user_id": 2,
"type": "proxy",
- "host_template_id": 1,
+ "nginx_template_id": 1,
"listen_interface": "",
"domain_names": ["jc21.com"],
"upstream_id": 0,
@@ -47,7 +47,6 @@
"hsts_enabled": false,
"hsts_subdomains": false,
"paths": "",
- "upstream_options": "",
"advanced_config": "",
"is_disabled": false
}
diff --git a/backend/embed/api_docs/paths/host-templates/get.json b/backend/embed/api_docs/paths/nginx-templates/get.json
similarity index 87%
rename from backend/embed/api_docs/paths/host-templates/get.json
rename to backend/embed/api_docs/paths/nginx-templates/get.json
index 2c3ca70..c242024 100644
--- a/backend/embed/api_docs/paths/host-templates/get.json
+++ b/backend/embed/api_docs/paths/nginx-templates/get.json
@@ -1,7 +1,7 @@
{
- "operationId": "getHostTemplates",
- "summary": "Get a list of Host Templates",
- "tags": ["Hosts"],
+ "operationId": "getNginxTemplates",
+ "summary": "Get a list of Nginx Templates",
+ "tags": ["Nginx Templates"],
"parameters": [
{
"in": "query",
@@ -40,7 +40,7 @@
"required": ["result"],
"properties": {
"result": {
- "$ref": "#/components/schemas/HostTemplateList"
+ "$ref": "#/components/schemas/NginxTemplateList"
}
}
},
@@ -64,7 +64,7 @@
"modified_on": 1646218093,
"user_id": 1,
"name": "Default Proxy Template",
- "host_type": "proxy",
+ "type": "proxy",
"template": "# this is a proxy template"
}
]
diff --git a/backend/embed/api_docs/paths/host-templates/post.json b/backend/embed/api_docs/paths/nginx-templates/post.json
similarity index 69%
rename from backend/embed/api_docs/paths/host-templates/post.json
rename to backend/embed/api_docs/paths/nginx-templates/post.json
index dd834c8..470ad39 100644
--- a/backend/embed/api_docs/paths/host-templates/post.json
+++ b/backend/embed/api_docs/paths/nginx-templates/post.json
@@ -1,13 +1,13 @@
{
- "operationId": "createHost",
- "summary": "Create a new Host",
- "tags": ["Hosts"],
+ "operationId": "createNginxTemplate",
+ "summary": "Create a new Nginx Template",
+ "tags": ["Nginx Templates"],
"requestBody": {
- "description": "Host to Create",
+ "description": "Template to Create",
"required": true,
"content": {
"application/json": {
- "schema": "{{schema.CreateHostTemplate}}"
+ "schema": "{{schema.CreateNginxTemplate}}"
}
}
},
@@ -20,7 +20,7 @@
"required": ["result"],
"properties": {
"result": {
- "$ref": "#/components/schemas/HostTemplateObject"
+ "$ref": "#/components/schemas/NginxTemplateObject"
}
}
},
@@ -33,7 +33,7 @@
"modified_on": 1646218093,
"user_id": 1,
"name": "My proxy template",
- "host_type": "proxy",
+ "type": "proxy",
"template": "# this is a proxy template"
}
}
diff --git a/backend/embed/api_docs/paths/host-templates/hostTemplateID/delete.json b/backend/embed/api_docs/paths/nginx-templates/templateID/delete.json
similarity index 75%
rename from backend/embed/api_docs/paths/host-templates/hostTemplateID/delete.json
rename to backend/embed/api_docs/paths/nginx-templates/templateID/delete.json
index f14fe9d..81e7ff5 100644
--- a/backend/embed/api_docs/paths/host-templates/hostTemplateID/delete.json
+++ b/backend/embed/api_docs/paths/nginx-templates/templateID/delete.json
@@ -1,17 +1,17 @@
{
- "operationId": "deleteHostTemplate",
- "summary": "Delete a Host Template",
- "tags": ["Host Templates"],
+ "operationId": "deleteNginxTemplate",
+ "summary": "Delete a Nginx Template",
+ "tags": ["Nginx Templates"],
"parameters": [
{
"in": "path",
- "name": "hostTemplateID",
+ "name": "templateID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
- "description": "Numeric ID of the Host Template",
+ "description": "Numeric ID of the Template",
"example": 1
}
],
@@ -46,7 +46,7 @@
"result": null,
"error": {
"code": 400,
- "message": "You cannot delete a host template that is in use!"
+ "message": "You cannot delete a template that is in use!"
}
}
}
diff --git a/backend/embed/api_docs/paths/host-templates/hostTemplateID/get.json b/backend/embed/api_docs/paths/nginx-templates/templateID/get.json
similarity index 72%
rename from backend/embed/api_docs/paths/host-templates/hostTemplateID/get.json
rename to backend/embed/api_docs/paths/nginx-templates/templateID/get.json
index 6f16a95..01808aa 100644
--- a/backend/embed/api_docs/paths/host-templates/hostTemplateID/get.json
+++ b/backend/embed/api_docs/paths/nginx-templates/templateID/get.json
@@ -1,11 +1,11 @@
{
- "operationId": "getHostTemplate",
- "summary": "Get a Host Template object by ID",
- "tags": ["Hosts"],
+ "operationId": "getNginxTemplate",
+ "summary": "Get a Nginx Template object by ID",
+ "tags": ["Nginx Templates"],
"parameters": [
{
"in": "path",
- "name": "hostTemplateID",
+ "name": "templateID",
"schema": {
"type": "integer",
"minimum": 1
@@ -24,7 +24,7 @@
"required": ["result"],
"properties": {
"result": {
- "$ref": "#/components/schemas/HostTemplateObject"
+ "$ref": "#/components/schemas/NginxTemplateObject"
}
}
},
@@ -36,8 +36,8 @@
"created_on": 1646218093,
"modified_on": 1646218093,
"user_id": 1,
- "name": "Default Host Template",
- "host_type": "proxy",
+ "name": "Default Proxy Template",
+ "type": "proxy",
"template": "# this is a proxy template"
}
}
diff --git a/backend/embed/api_docs/paths/host-templates/hostTemplateID/put.json b/backend/embed/api_docs/paths/nginx-templates/templateID/put.json
similarity index 68%
rename from backend/embed/api_docs/paths/host-templates/hostTemplateID/put.json
rename to backend/embed/api_docs/paths/nginx-templates/templateID/put.json
index 2993e5a..31d6647 100644
--- a/backend/embed/api_docs/paths/host-templates/hostTemplateID/put.json
+++ b/backend/embed/api_docs/paths/nginx-templates/templateID/put.json
@@ -1,26 +1,26 @@
{
- "operationId": "updateHostTemplate",
- "summary": "Update an existing Host Template",
- "tags": ["Hosts"],
+ "operationId": "updateNginxTemplate",
+ "summary": "Update an existing Nginx Template",
+ "tags": ["Nginx Templates"],
"parameters": [
{
"in": "path",
- "name": "hostTemplateID",
+ "name": "templateID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
- "description": "ID of the Host Template",
+ "description": "ID of the Template",
"example": 1
}
],
"requestBody": {
- "description": "Host Template details to update",
+ "description": "Template details to update",
"required": true,
"content": {
"application/json": {
- "schema": "{{schema.UpdateHostTemplate}}"
+ "schema": "{{schema.UpdateNginxTemplate}}"
}
}
},
@@ -33,7 +33,7 @@
"required": ["result"],
"properties": {
"result": {
- "$ref": "#/components/schemas/HostTemplateObject"
+ "$ref": "#/components/schemas/NginxTemplateObject"
}
}
},
@@ -46,7 +46,7 @@
"modified_on": 1646218093,
"user_id": 1,
"name": "My renamed proxy template",
- "host_type": "proxy",
+ "type": "proxy",
"template": "# this is a proxy template"
}
}
diff --git a/backend/embed/api_docs/paths/streams/post.json b/backend/embed/api_docs/paths/streams/post.json
index d194983..d660eed 100644
--- a/backend/embed/api_docs/paths/streams/post.json
+++ b/backend/embed/api_docs/paths/streams/post.json
@@ -1,11 +1,9 @@
{
"operationId": "createStream",
"summary": "Create a new Stream",
- "tags": [
- "Streams"
- ],
+ "tags": ["Streams"],
"requestBody": {
- "description": "Host to Create",
+ "description": "Stream to Create",
"required": true,
"content": {
"application/json": {
@@ -19,9 +17,7 @@
"content": {
"application/json": {
"schema": {
- "required": [
- "result"
- ],
+ "required": ["result"],
"properties": {
"result": {
"$ref": "#/components/schemas/StreamObject"
@@ -39,4 +35,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/backend/embed/api_docs/paths/upstreams/get.json b/backend/embed/api_docs/paths/upstreams/get.json
new file mode 100644
index 0000000..e52d7a9
--- /dev/null
+++ b/backend/embed/api_docs/paths/upstreams/get.json
@@ -0,0 +1,285 @@
+{
+ "operationId": "getUpstreams",
+ "summary": "Get a list of Upstreams",
+ "tags": ["Upstreams"],
+ "parameters": [
+ {
+ "in": "query",
+ "name": "offset",
+ "schema": {
+ "type": "number"
+ },
+ "description": "The pagination row offset, default 0",
+ "example": 0
+ },
+ {
+ "in": "query",
+ "name": "limit",
+ "schema": {
+ "type": "number"
+ },
+ "description": "The pagination row limit, default 10",
+ "example": 10
+ },
+ {
+ "in": "query",
+ "name": "sort",
+ "schema": {
+ "type": "string"
+ },
+ "description": "The sorting of the list",
+ "example": "id,name.asc,value.desc"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "200 response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "required": ["result"],
+ "properties": {
+ "result": {
+ "$ref": "#/components/schemas/UpstreamList"
+ }
+ }
+ },
+ "examples": {
+ "default": {
+ "value": {
+ "result": {
+ "total": 5,
+ "offset": 0,
+ "limit": 10,
+ "sort": [
+ {
+ "field": "name",
+ "direction": "ASC"
+ }
+ ],
+ "items": [
+ {
+ "id": 1,
+ "created_on": 1672804124,
+ "modified_on": 1672804124,
+ "user_id": 2,
+ "name": "API servers",
+ "nginx_template_id": 5,
+ "ip_hash": true,
+ "ntlm": false,
+ "keepalive": 10,
+ "keepalive_requests": 10,
+ "keepalive_time": "60s",
+ "keepalive_timeout": "3s",
+ "advanced_config": "",
+ "status": "ok",
+ "error_message": "",
+ "servers": [
+ {
+ "id": 1,
+ "created_on": 1672804124,
+ "modified_on": 1672804124,
+ "upstream_group_id": 1,
+ "server": "192.168.0.10:80",
+ "weight": 100,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ },
+ {
+ "id": 2,
+ "created_on": 1672804124,
+ "modified_on": 1672804124,
+ "upstream_group_id": 1,
+ "server": "192.168.0.11:80",
+ "weight": 50,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "created_on": 1672804197,
+ "modified_on": 1672804197,
+ "user_id": 2,
+ "name": "API servers 2",
+ "nginx_template_id": 5,
+ "ip_hash": false,
+ "ntlm": false,
+ "keepalive": 0,
+ "keepalive_requests": 0,
+ "keepalive_time": "",
+ "keepalive_timeout": "",
+ "advanced_config": "",
+ "status": "ok",
+ "error_message": "",
+ "servers": [
+ {
+ "id": 3,
+ "created_on": 1672804197,
+ "modified_on": 1672804197,
+ "upstream_group_id": 2,
+ "server": "192.168.0.10:80",
+ "weight": 100,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ },
+ {
+ "id": 4,
+ "created_on": 1672804197,
+ "modified_on": 1672804197,
+ "upstream_group_id": 2,
+ "server": "192.168.0.11:80",
+ "weight": 50,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ }
+ ]
+ },
+ {
+ "id": 3,
+ "created_on": 1672804200,
+ "modified_on": 1672804200,
+ "user_id": 2,
+ "name": "API servers 2",
+ "nginx_template_id": 5,
+ "ip_hash": false,
+ "ntlm": false,
+ "keepalive": 0,
+ "keepalive_requests": 0,
+ "keepalive_time": "",
+ "keepalive_timeout": "",
+ "advanced_config": "",
+ "status": "ok",
+ "error_message": "",
+ "servers": [
+ {
+ "id": 5,
+ "created_on": 1672804200,
+ "modified_on": 1672804200,
+ "upstream_group_id": 3,
+ "server": "192.168.0.10:80",
+ "weight": 100,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ },
+ {
+ "id": 6,
+ "created_on": 1672804200,
+ "modified_on": 1672804200,
+ "upstream_group_id": 3,
+ "server": "192.168.0.11:80",
+ "weight": 50,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "created_on": 1672804201,
+ "modified_on": 1672804201,
+ "user_id": 2,
+ "name": "API servers 2",
+ "nginx_template_id": 5,
+ "ip_hash": false,
+ "ntlm": false,
+ "keepalive": 0,
+ "keepalive_requests": 0,
+ "keepalive_time": "",
+ "keepalive_timeout": "",
+ "advanced_config": "",
+ "status": "ok",
+ "error_message": "",
+ "servers": [
+ {
+ "id": 7,
+ "created_on": 1672804201,
+ "modified_on": 1672804201,
+ "upstream_group_id": 4,
+ "server": "192.168.0.10:80",
+ "weight": 100,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ },
+ {
+ "id": 8,
+ "created_on": 1672804201,
+ "modified_on": 1672804201,
+ "upstream_group_id": 4,
+ "server": "192.168.0.11:80",
+ "weight": 50,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ }
+ ]
+ },
+ {
+ "id": 5,
+ "created_on": 1672804201,
+ "modified_on": 1672804201,
+ "user_id": 2,
+ "name": "API servers 2",
+ "nginx_template_id": 5,
+ "ip_hash": false,
+ "ntlm": false,
+ "keepalive": 0,
+ "keepalive_requests": 0,
+ "keepalive_time": "",
+ "keepalive_timeout": "",
+ "advanced_config": "",
+ "status": "ok",
+ "error_message": "",
+ "servers": [
+ {
+ "id": 9,
+ "created_on": 1672804201,
+ "modified_on": 1672804201,
+ "upstream_group_id": 5,
+ "server": "192.168.0.10:80",
+ "weight": 100,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ },
+ {
+ "id": 10,
+ "created_on": 1672804201,
+ "modified_on": 1672804201,
+ "upstream_group_id": 5,
+ "server": "192.168.0.11:80",
+ "weight": 50,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/backend/embed/api_docs/paths/upstreams/post.json b/backend/embed/api_docs/paths/upstreams/post.json
new file mode 100644
index 0000000..8bab6d3
--- /dev/null
+++ b/backend/embed/api_docs/paths/upstreams/post.json
@@ -0,0 +1,80 @@
+{
+ "operationId": "createUpstream",
+ "summary": "Create a new Upstream",
+ "tags": ["Upstreams"],
+ "requestBody": {
+ "description": "Upstream to Create",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": "{{schema.CreateUpstream}}"
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "201 response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "required": ["result"],
+ "properties": {
+ "result": {
+ "$ref": "#/components/schemas/UpstreamObject"
+ }
+ }
+ },
+ "examples": {
+ "default": {
+ "value": {
+ "result": {
+ "id": 6,
+ "created_on": 1672806857,
+ "modified_on": 1672806857,
+ "user_id": 2,
+ "name": "API servers 2",
+ "nginx_template_id": 5,
+ "ip_hash": false,
+ "ntlm": false,
+ "keepalive": 0,
+ "keepalive_requests": 0,
+ "keepalive_time": "",
+ "keepalive_timeout": "",
+ "advanced_config": "",
+ "status": "ready",
+ "error_message": "",
+ "servers": [
+ {
+ "id": 11,
+ "created_on": 1672806857,
+ "modified_on": 1672806857,
+ "upstream_id": 6,
+ "server": "192.168.0.10:80",
+ "weight": 100,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ },
+ {
+ "id": 12,
+ "created_on": 1672806857,
+ "modified_on": 1672806857,
+ "upstream_id": 6,
+ "server": "192.168.0.11:80",
+ "weight": 50,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/backend/embed/api_docs/paths/upstreams/upstreamID/delete.json b/backend/embed/api_docs/paths/upstreams/upstreamID/delete.json
new file mode 100644
index 0000000..0c54708
--- /dev/null
+++ b/backend/embed/api_docs/paths/upstreams/upstreamID/delete.json
@@ -0,0 +1,58 @@
+{
+ "operationId": "deleteUpstream",
+ "summary": "Delete a Upstream",
+ "tags": ["Upstreams"],
+ "parameters": [
+ {
+ "in": "path",
+ "name": "upstreamID",
+ "schema": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "required": true,
+ "description": "Numeric ID of the Upstream",
+ "example": 1
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "200 response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeletedItemResponse"
+ },
+ "examples": {
+ "default": {
+ "value": {
+ "result": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "400 response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeletedItemResponse"
+ },
+ "examples": {
+ "default": {
+ "value": {
+ "result": null,
+ "error": {
+ "code": 400,
+ "message": "You cannot delete a Upstream that is in use!"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/backend/embed/api_docs/paths/upstreams/upstreamID/get.json b/backend/embed/api_docs/paths/upstreams/upstreamID/get.json
new file mode 100644
index 0000000..8303b2c
--- /dev/null
+++ b/backend/embed/api_docs/paths/upstreams/upstreamID/get.json
@@ -0,0 +1,81 @@
+{
+ "operationId": "getUpstream",
+ "summary": "Get a Upstream object by ID",
+ "tags": ["Upstreams"],
+ "parameters": [
+ {
+ "in": "path",
+ "name": "upstreamID",
+ "schema": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "required": true,
+ "description": "ID of the Upstream",
+ "example": 1
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "200 response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "required": ["result"],
+ "properties": {
+ "result": {
+ "$ref": "#/components/schemas/UpstreamObject"
+ }
+ }
+ },
+ "examples": {
+ "default": {
+ "value": {
+ "result": {
+ "id": 1,
+ "created_on": 1672786008,
+ "modified_on": 1672786008,
+ "user_id": 2,
+ "name": "API servers 3",
+ "ip_hash": true,
+ "ntlm": false,
+ "keepalive": 10,
+ "keepalive_requests": 10,
+ "keepalive_time": "60s",
+ "keepalive_timeout": "3s",
+ "advanced_config": "",
+ "servers": [
+ {
+ "id": 1,
+ "created_on": 1672786009,
+ "modified_on": 1672786009,
+ "upstream_id": 1,
+ "server": "api1.localhost:1234",
+ "weight": 100,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": false
+ },
+ {
+ "id": 2,
+ "created_on": 1672786009,
+ "modified_on": 1672786009,
+ "upstream_id": 1,
+ "server": "api2.localhost:1234",
+ "weight": 50,
+ "max_conns": 0,
+ "max_fails": 0,
+ "fail_timeout": 0,
+ "backup": true
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/backend/embed/migrations/20201013035318_initial_schema.sql b/backend/embed/migrations/20201013035318_initial_schema.sql
index 3b95c50..4d5cb1e 100644
--- a/backend/embed/migrations/20201013035318_initial_schema.sql
+++ b/backend/embed/migrations/20201013035318_initial_schema.sql
@@ -123,7 +123,6 @@ CREATE TABLE IF NOT EXISTS `stream`
user_id INTEGER NOT NULL,
listen_interface TEXT NOT NULL,
incoming_port INTEGER NOT NULL,
- upstream_options TEXT NOT NULL,
tcp_forwarding INTEGER NOT NULL DEFAULT 0,
udp_forwarding INTEGER NOT NULL DEFAULT 0,
advanced_config TEXT NOT NULL,
@@ -138,13 +137,36 @@ CREATE TABLE IF NOT EXISTS `upstream`
created_on INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL,
- hosts TEXT NOT NULL,
- balance_method TEXT NOT NULL,
- max_fails INTEGER NOT NULL DEFAULT 1,
- fail_timeout INTEGER NOT NULL DEFAULT 10,
+ name TEXT NOT NULL,
+ nginx_template_id INTEGER NOT NULL,
+ ip_hash INTEGER NOT NULL DEFAULT 0,
+ ntlm INTEGER NOT NULL DEFAULT 0,
+ keepalive INTEGER NOT NULL DEFAULT 0,
+ keepalive_requests INTEGER NOT NULL DEFAULT 0,
+ keepalive_time TEXT NOT NULL DEFAULT "",
+ keepalive_timeout TEXT NOT NULL DEFAULT "",
advanced_config TEXT NOT NULL,
+ status TEXT NOT NULL DEFAULT "",
+ error_message TEXT NOT NULL DEFAULT "",
is_deleted INTEGER NOT NULL DEFAULT 0,
- FOREIGN KEY (user_id) REFERENCES user (id)
+ FOREIGN KEY (user_id) REFERENCES user (id),
+ FOREIGN KEY (nginx_template_id) REFERENCES nginx_template (id)
+);
+
+CREATE TABLE IF NOT EXISTS `upstream_server`
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ created_on INTEGER NOT NULL DEFAULT 0,
+ modified_on INTEGER NOT NULL DEFAULT 0,
+ upstream_id INTEGER NOT NULL,
+ server TEXT NOT NULL,
+ weight INTEGER NOT NULL DEFAULT 0,
+ max_conns INTEGER NOT NULL DEFAULT 0,
+ max_fails INTEGER NOT NULL DEFAULT 0,
+ fail_timeout INTEGER NOT NULL DEFAULT 0,
+ backup INTEGER NOT NULL DEFAULT 0,
+ is_deleted INTEGER NOT NULL DEFAULT 0,
+ FOREIGN KEY (upstream_id) REFERENCES upstream (id)
);
CREATE TABLE IF NOT EXISTS `access_list`
@@ -159,14 +181,14 @@ CREATE TABLE IF NOT EXISTS `access_list`
FOREIGN KEY (user_id) REFERENCES user (id)
);
-CREATE TABLE IF NOT EXISTS `host_template`
+CREATE TABLE IF NOT EXISTS `nginx_template`
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL,
name TEXT NOT NULL,
- host_type TEXT NOT NULL,
+ type TEXT NOT NULL,
template TEXT NOT NULL,
is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id)
@@ -179,7 +201,7 @@ CREATE TABLE IF NOT EXISTS `host`
modified_on INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL,
type TEXT NOT NULL,
- host_template_id INTEGER NOT NULL,
+ nginx_template_id INTEGER NOT NULL,
listen_interface TEXT NOT NULL DEFAULT "",
domain_names TEXT NOT NULL,
upstream_id INTEGER NOT NULL DEFAULT 0,
@@ -193,14 +215,13 @@ CREATE TABLE IF NOT EXISTS `host`
hsts_enabled INTEGER NOT NULL DEFAULT 0,
hsts_subdomains INTEGER NOT NULL DEFAULT 0,
paths TEXT NOT NULL DEFAULT "",
- upstream_options TEXT NOT NULL DEFAULT "",
advanced_config TEXT NOT NULL DEFAULT "",
status TEXT NOT NULL DEFAULT "",
error_message TEXT NOT NULL DEFAULT "",
is_disabled INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id),
- FOREIGN KEY (host_template_id) REFERENCES host_template (id),
+ FOREIGN KEY (nginx_template_id) REFERENCES nginx_template (id),
FOREIGN KEY (upstream_id) REFERENCES upstream (id),
FOREIGN KEY (certificate_id) REFERENCES certificate (id),
FOREIGN KEY (access_list_id) REFERENCES access_list (id)
diff --git a/backend/embed/migrations/20201013035839_initial_data.sql b/backend/embed/migrations/20201013035839_initial_data.sql
index fa71871..d7a9112 100644
--- a/backend/embed/migrations/20201013035839_initial_data.sql
+++ b/backend/embed/migrations/20201013035839_initial_data.sql
@@ -16,8 +16,8 @@ INSERT INTO `capability` (
("dns-providers.manage"),
("hosts.view"),
("hosts.manage"),
- ("host-templates.view"),
- ("host-templates.manage"),
+ ("nginx-templates.view"),
+ ("nginx-templates.manage"),
("settings.manage"),
("streams.view"),
("streams.manage"),
@@ -131,12 +131,12 @@ INSERT INTO `user` (
);
-- Host Templates
-INSERT INTO `host_template` (
+INSERT INTO `nginx_template` (
created_on,
modified_on,
user_id,
name,
- host_type,
+ type,
template
) VALUES (
strftime('%s', 'now'),
@@ -144,7 +144,119 @@ INSERT INTO `host_template` (
(SELECT id FROM user WHERE is_system = 1 LIMIT 1),
"Default Proxy Template",
"proxy",
- "# this is a proxy template"
+ "# ------------------------------------------------------------
+{{#each Host.DomainNames}}
+# {{this}}
+{{/each}}
+# ------------------------------------------------------------
+
+{{#if Host.IsDisabled}}
+# This Proxy Host is disabled and will not generate functional config
+{{/if}}
+
+{{#unless Host.IsDisabled}}
+server {
+ set $forward_scheme {{Host.ForwardScheme}};
+ set $server ""{{Host.ForwardHost}}"";
+ set $port {{Host.ForwardPort}};
+
+ {{#if Config.Ipv4}}
+ listen 80;
+ {{/if}}
+ {{#if Config.Ipv6}}
+ listen [::]:80;
+ {{/if}}
+
+ {{#if Certificate.ID}}
+ listen 443 ssl {{#if Host.HTTP2Support}}http2{{/if}};
+ {{/if}}
+ {{#if Config.Ipv6}}
+ listen [::]:443 ssl {{#if Host.HTTP2Support}}http2{{/if}};
+ {{/if}}
+
+ server_name {{#each Host.DomainNames}}{{this}} {{/each}};
+
+ {{#if Certificate.ID}}
+ include conf.d/include/ssl-ciphers.conf;
+ {{#if Certificate.IsAcme}}
+ ssl_certificate {{Certificate.Folder}}/fullchain.pem;
+ ssl_certificate_key {{Certificate.Folder}}/privkey.pem;
+ {{else}}
+ # Custom SSL
+ ssl_certificate /data/custom_ssl/npm-{{Certicicate.ID}}/fullchain.pem;
+ ssl_certificate_key /data/custom_ssl/npm-{{Certificate.ID}}/privkey.pem;
+ {{/if}}
+ {{/if}}
+
+ {{#if Host.CachingEnabled}}
+ include conf.d/include/assets.conf;
+ {{/if}}
+
+ {{#if Host.BlockExploits}}
+ include conf.d/include/block-exploits.conf;
+ {{/if}}
+
+ {{#if Certificate.ID}}
+ {{#if Host.SSLForced}}
+ {{#if Host.HSTSEnabled}}
+ # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
+ add_header Strict-Transport-Security ""max-age=63072000;{{#if Host.HSTSSubdomains}} includeSubDomains;{{/if}} preload"" always;
+ {{/if}}
+ # Force SSL
+ include conf.d/include/force-ssl.conf;
+ {{/if}}
+ {{/if}}
+
+ {{#if Host.AllowWebsocketUpgrade}}
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $http_connection;
+ proxy_http_version 1.1;
+ {{/if}}
+
+ access_log /data/logs/host-{{Host.ID}}_access.log proxy;
+ error_log /data/logs/host-{{Host.ID}}_error.log warn;
+
+ {{Host.AdvancedConfig}}
+
+ # locations ?
+
+ # default location:
+ location / {
+ {{#if Host.AccessListID}}
+ # Authorization
+ auth_basic ""Authorization required"";
+ auth_basic_user_file /data/access/{{Host.AccessListID}};
+ # access_list.passauth ? todo
+ {{/if}}
+
+ # Access Rules ? todo
+
+ # Access checks must...? todo
+
+ {{#if Certificate.ID}}
+ {{#if Host.SSLForced}}
+ {{#if Host.HSTSEnabled}}
+ # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
+ add_header Strict-Transport-Security ""max-age=63072000;{{#if Host.HSTSSubdomains}} includeSubDomains;{{/if}} preload"" always;
+ {{/if}}
+ {{/if}}
+ {{/if}}
+
+ {{#if Host.AllowWebsocketUpgrade}}
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $http_connection;
+ proxy_http_version 1.1;
+ {{/if}}
+
+ # Proxy!
+ include conf.d/include/proxy.conf;
+ }
+
+ # Legacy Custom Configuration
+ include /data/nginx/custom/server_proxy[.]conf;
+}
+{{/unless}}
+"
), (
strftime('%s', 'now'),
strftime('%s', 'now'),
@@ -166,6 +278,55 @@ INSERT INTO `host_template` (
"Default Stream Template",
"stream",
"# this is a stream template"
+), (
+ strftime('%s', 'now'),
+ strftime('%s', 'now'),
+ (SELECT id FROM user WHERE is_system = 1 LIMIT 1),
+ "Default Upstream Template",
+ "upstream",
+ "# ------------------------------------------------------------
+# Upstream {{Upstream.ID}}: {{Upstream.Name}}
+# ------------------------------------------------------------
+
+{{#unless Upstream.IsDeleted~}}
+
+upstream npm_upstream_{{Upstream.ID}} {
+
+ {{#if Upstream.IPHash~}}
+ ip_hash;
+ {{~/if}}
+
+ {{#if Upstream.NTLM~}}
+ ntlm;
+ {{~/if}}
+
+ {{#if Upstream.Keepalive~}}
+ keepalive {{Upstream.Keepalive}};
+ {{~/if}}
+
+ {{#if Upstream.KeepaliveRequests~}}
+ keepalive_requests {{Upstream.KeepaliveRequests}};
+ {{~/if}}
+
+ {{#if Upstream.KeepaliveTime~}}
+ keepalive_time {{Upstream.KeepaliveTime}};
+ {{~/if}}
+
+ {{#if Upstream.KeepaliveTimeout~}}
+ keepalive_timeout {{Upstream.KeepaliveTimeout}};
+ {{~/if}}
+
+ {{Upstream.AdvancedConfig}}
+
+ {{#each Upstream.Servers~}}
+ {{#unless IsDeleted~}}
+ server {{Server}} {{#if Weight}}weight={{Weight}} {{/if}}{{#if MaxConns}}max_conns={{MaxConns}} {{/if}}{{#if MaxFails}}max_fails={{MaxFails}} {{/if}}{{#if FailTimeout}}fail_timeout={{FailTimeout}} {{/if}}{{#if Backup}}backup{{/if}};
+ {{/unless}}
+ {{/each}}
+}
+
+{{~/unless~}}
+"
);
-- migrate:down
diff --git a/backend/internal/acme/acmesh.go b/backend/internal/acme/acmesh.go
index 462eba3..7d95b1d 100644
--- a/backend/internal/acme/acmesh.go
+++ b/backend/internal/acme/acmesh.go
@@ -104,7 +104,7 @@ func shExec(args []string, envs []string) (string, error) {
c := exec.Command(acmeSh, args...)
c.Env = append(getCommonEnvVars(), envs...)
- b, e := c.Output()
+ b, e := c.CombinedOutput()
if e != nil {
logger.Error("AcmeShError", fmt.Errorf("Command error: %s -- %v\n%+v", acmeSh, args, e))
diff --git a/backend/internal/acme/acmesh_test.go b/backend/internal/acme/acmesh_test.go
index 837aa36..08969b8 100644
--- a/backend/internal/acme/acmesh_test.go
+++ b/backend/internal/acme/acmesh_test.go
@@ -11,15 +11,6 @@ import (
"github.com/stretchr/testify/assert"
)
-// Tear up/down
-/*
-func TestMain(m *testing.M) {
- config.Init(&version, &commit, &sentryDSN)
- code := m.Run()
- os.Exit(code)
-}
-*/
-
// TODO configurable
const acmeLogFile = "/data/logs/acme.sh.log"
diff --git a/backend/internal/api/handler/hosts.go b/backend/internal/api/handler/hosts.go
index e4c6e11..3a77f10 100644
--- a/backend/internal/api/handler/hosts.go
+++ b/backend/internal/api/handler/hosts.go
@@ -1,6 +1,7 @@
package handler
import (
+ "database/sql"
"encoding/json"
"fmt"
"net/http"
@@ -45,13 +46,16 @@ func GetHost() func(http.ResponseWriter, *http.Request) {
return
}
- hostObject, err := host.GetByID(hostID)
- if err != nil {
- h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
- } else {
+ item, err := host.GetByID(hostID)
+ switch err {
+ case sql.ErrNoRows:
+ h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
+ case nil:
// nolint: errcheck,gosec
- hostObject.Expand(getExpandFromContext(r))
- h.ResultResponseJSON(w, r, http.StatusOK, hostObject)
+ item.Expand(getExpandFromContext(r))
+ h.ResultResponseJSON(w, r, http.StatusOK, item)
+ default:
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
}
@@ -111,6 +115,11 @@ func UpdateHost() func(http.ResponseWriter, *http.Request) {
return
}
+ if err = validator.ValidateHost(hostObject); err != nil {
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
+ return
+ }
+
if err = hostObject.Save(false); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
@@ -137,11 +146,14 @@ func DeleteHost() func(http.ResponseWriter, *http.Request) {
return
}
- host, err := host.GetByID(hostID)
- if err != nil {
+ item, err := host.GetByID(hostID)
+ switch err {
+ case sql.ErrNoRows:
+ h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
+ case nil:
+ h.ResultResponseJSON(w, r, http.StatusOK, item.Delete())
+ default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
- } else {
- h.ResultResponseJSON(w, r, http.StatusOK, host.Delete())
}
}
}
diff --git a/backend/internal/api/handler/host_templates.go b/backend/internal/api/handler/nginx_templates.go
similarity index 52%
rename from backend/internal/api/handler/host_templates.go
rename to backend/internal/api/handler/nginx_templates.go
index 3ce1ca3..a459691 100644
--- a/backend/internal/api/handler/host_templates.go
+++ b/backend/internal/api/handler/nginx_templates.go
@@ -1,6 +1,7 @@
package handler
import (
+ "database/sql"
"encoding/json"
"fmt"
"net/http"
@@ -8,13 +9,12 @@ import (
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
- "npm/internal/entity/host"
- "npm/internal/entity/hosttemplate"
+ "npm/internal/entity/nginxtemplate"
)
-// GetHostTemplates will return a list of Host Templates
-// Route: GET /host-templates
-func GetHostTemplates() func(http.ResponseWriter, *http.Request) {
+// GetNginxTemplates will return a list of Nginx Templates
+// Route: GET /nginx-templates
+func GetNginxTemplates() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
@@ -22,18 +22,18 @@ func GetHostTemplates() func(http.ResponseWriter, *http.Request) {
return
}
- hosts, err := hosttemplate.List(pageInfo, middleware.GetFiltersFromContext(r))
+ items, err := nginxtemplate.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
- h.ResultResponseJSON(w, r, http.StatusOK, hosts)
+ h.ResultResponseJSON(w, r, http.StatusOK, items)
}
}
}
-// GetHostTemplate will return a single Host Template
-// Route: GET /host-templates/{templateID}
-func GetHostTemplate() func(http.ResponseWriter, *http.Request) {
+// GetNginxTemplate will return a single Nginx Template
+// Route: GET /nginx-templates/{templateID}
+func GetNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var templateID int
@@ -42,23 +42,26 @@ func GetHostTemplate() func(http.ResponseWriter, *http.Request) {
return
}
- host, err := hosttemplate.GetByID(templateID)
- if err != nil {
+ item, err := nginxtemplate.GetByID(templateID)
+ switch err {
+ case sql.ErrNoRows:
+ h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
+ case nil:
+ h.ResultResponseJSON(w, r, http.StatusOK, item)
+ default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
- } else {
- h.ResultResponseJSON(w, r, http.StatusOK, host)
}
}
}
-// CreateHostTemplate will create a Host Template
-// Route: POST /host-templates
-func CreateHostTemplate() func(http.ResponseWriter, *http.Request) {
+// CreateNginxTemplate will create a Nginx Template
+// Route: POST /nginx-templates
+func CreateNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
- var newHostTemplate hosttemplate.Model
- err := json.Unmarshal(bodyBytes, &newHostTemplate)
+ var newNginxTemplate nginxtemplate.Model
+ err := json.Unmarshal(bodyBytes, &newNginxTemplate)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
@@ -66,20 +69,20 @@ func CreateHostTemplate() func(http.ResponseWriter, *http.Request) {
// Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(int)
- newHostTemplate.UserID = userID
+ newNginxTemplate.UserID = userID
- if err = newHostTemplate.Save(); err != nil {
- h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Host Template: %s", err.Error()), nil)
+ if err = newNginxTemplate.Save(); err != nil {
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Nginx Template: %s", err.Error()), nil)
return
}
- h.ResultResponseJSON(w, r, http.StatusOK, newHostTemplate)
+ h.ResultResponseJSON(w, r, http.StatusOK, newNginxTemplate)
}
}
-// UpdateHostTemplate updates a host template
-// Route: PUT /host-templates/{templateID}
-func UpdateHostTemplate() func(http.ResponseWriter, *http.Request) {
+// UpdateNginxTemplate updates a nginx template
+// Route: PUT /nginx-templates/{templateID}
+func UpdateNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var templateID int
@@ -90,30 +93,30 @@ func UpdateHostTemplate() func(http.ResponseWriter, *http.Request) {
// reconfigure, _ := getQueryVarBool(r, "reconfigure", false, false)
- hostTemplate, err := hosttemplate.GetByID(templateID)
+ nginxTemplate, err := nginxtemplate.GetByID(templateID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
- err := json.Unmarshal(bodyBytes, &hostTemplate)
+ err := json.Unmarshal(bodyBytes, &nginxTemplate)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
- if err = hostTemplate.Save(); err != nil {
+ if err = nginxTemplate.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
- h.ResultResponseJSON(w, r, http.StatusOK, hostTemplate)
+ h.ResultResponseJSON(w, r, http.StatusOK, nginxTemplate)
}
}
}
-// DeleteHostTemplate removes a host template
-// Route: DELETE /host-templates/{templateID}
-func DeleteHostTemplate() func(http.ResponseWriter, *http.Request) {
+// DeleteNginxTemplate removes a nginx template
+// Route: DELETE /nginx-templates/{templateID}
+func DeleteNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var templateID int
@@ -122,11 +125,14 @@ func DeleteHostTemplate() func(http.ResponseWriter, *http.Request) {
return
}
- hostTemplate, err := host.GetByID(templateID)
- if err != nil {
+ item, err := nginxtemplate.GetByID(templateID)
+ switch err {
+ case sql.ErrNoRows:
+ h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
+ case nil:
+ h.ResultResponseJSON(w, r, http.StatusOK, item.Delete())
+ default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
- } else {
- h.ResultResponseJSON(w, r, http.StatusOK, hostTemplate.Delete())
}
}
}
diff --git a/backend/internal/api/handler/schema.go b/backend/internal/api/handler/schema.go
index 2fb37b5..b0fdad7 100644
--- a/backend/internal/api/handler/schema.go
+++ b/backend/internal/api/handler/schema.go
@@ -95,8 +95,8 @@ func replaceIncomingSchemas(swaggerSchema []byte) []byte {
str = strings.ReplaceAll(str, `"{{schema.CreateHost}}"`, schema.CreateHost())
str = strings.ReplaceAll(str, `"{{schema.UpdateHost}}"`, schema.UpdateHost())
- str = strings.ReplaceAll(str, `"{{schema.CreateHostTemplate}}"`, schema.CreateHostTemplate())
- str = strings.ReplaceAll(str, `"{{schema.UpdateHostTemplate}}"`, schema.UpdateHostTemplate())
+ str = strings.ReplaceAll(str, `"{{schema.CreateNginxTemplate}}"`, schema.CreateNginxTemplate())
+ str = strings.ReplaceAll(str, `"{{schema.UpdateNginxTemplate}}"`, schema.UpdateNginxTemplate())
str = strings.ReplaceAll(str, `"{{schema.CreateStream}}"`, schema.CreateStream())
str = strings.ReplaceAll(str, `"{{schema.UpdateStream}}"`, schema.UpdateStream())
@@ -104,5 +104,7 @@ func replaceIncomingSchemas(swaggerSchema []byte) []byte {
str = strings.ReplaceAll(str, `"{{schema.CreateDNSProvider}}"`, schema.CreateDNSProvider())
str = strings.ReplaceAll(str, `"{{schema.UpdateDNSProvider}}"`, schema.UpdateDNSProvider())
+ str = strings.ReplaceAll(str, `"{{schema.CreateUpstream}}"`, schema.CreateUpstream())
+
return []byte(str)
}
diff --git a/backend/internal/api/handler/upstreams.go b/backend/internal/api/handler/upstreams.go
new file mode 100644
index 0000000..e505d1e
--- /dev/null
+++ b/backend/internal/api/handler/upstreams.go
@@ -0,0 +1,129 @@
+package handler
+
+import (
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ c "npm/internal/api/context"
+ h "npm/internal/api/http"
+ "npm/internal/api/middleware"
+ "npm/internal/entity/upstream"
+ "npm/internal/jobqueue"
+ "npm/internal/logger"
+ "npm/internal/nginx"
+ "npm/internal/validator"
+)
+
+// GetUpstreams will return a list of Upstreams
+// Route: GET /upstreams
+func GetUpstreams() func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ pageInfo, err := getPageInfoFromRequest(r)
+ if err != nil {
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
+ return
+ }
+
+ items, err := upstream.List(pageInfo, middleware.GetFiltersFromContext(r), getExpandFromContext(r))
+ if err != nil {
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
+ } else {
+ h.ResultResponseJSON(w, r, http.StatusOK, items)
+ }
+ }
+}
+
+// GetUpstream will return a single Upstream
+// Route: GET /upstreams/{upstreamID}
+func GetUpstream() func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ var err error
+ var upstreamID int
+ if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil {
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
+ return
+ }
+
+ item, err := upstream.GetByID(upstreamID)
+ switch err {
+ case sql.ErrNoRows:
+ h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
+ case nil:
+ // nolint: errcheck,gosec
+ item.Expand(getExpandFromContext(r))
+ h.ResultResponseJSON(w, r, http.StatusOK, item)
+ default:
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
+ }
+ }
+}
+
+// CreateUpstream will create a Upstream
+// Route: POST /upstreams
+func CreateUpstream() func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
+
+ var newUpstream upstream.Model
+ err := json.Unmarshal(bodyBytes, &newUpstream)
+ if err != nil {
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
+ return
+ }
+
+ // Get userID from token
+ userID, _ := r.Context().Value(c.UserIDCtxKey).(int)
+ newUpstream.UserID = userID
+
+ if err = validator.ValidateUpstream(newUpstream); err != nil {
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
+ return
+ }
+
+ if err = newUpstream.Save(false); err != nil {
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Upstream: %s", err.Error()), nil)
+ return
+ }
+
+ configureUpstream(newUpstream)
+
+ h.ResultResponseJSON(w, r, http.StatusOK, newUpstream)
+ }
+}
+
+// DeleteUpstream removes a upstream
+// Route: DELETE /upstreams/{upstreamID}
+func DeleteUpstream() func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ var err error
+ var upstreamID int
+ if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil {
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
+ return
+ }
+
+ item, err := upstream.GetByID(upstreamID)
+ switch err {
+ case sql.ErrNoRows:
+ h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
+ case nil:
+ h.ResultResponseJSON(w, r, http.StatusOK, item.Delete())
+ default:
+ h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
+ }
+ }
+}
+
+func configureUpstream(u upstream.Model) {
+ err := jobqueue.AddJob(jobqueue.Job{
+ Name: "NginxConfigureUpstream",
+ Action: func() error {
+ return nginx.ConfigureUpstream(u)
+ },
+ })
+ if err != nil {
+ logger.Error("ConfigureUpstreamError", err)
+ }
+}
diff --git a/backend/internal/api/router.go b/backend/internal/api/router.go
index 27bc32b..e73c9e5 100644
--- a/backend/internal/api/router.go
+++ b/backend/internal/api/router.go
@@ -12,9 +12,10 @@ import (
"npm/internal/entity/certificateauthority"
"npm/internal/entity/dnsprovider"
"npm/internal/entity/host"
- "npm/internal/entity/hosttemplate"
+ "npm/internal/entity/nginxtemplate"
"npm/internal/entity/setting"
"npm/internal/entity/stream"
+ "npm/internal/entity/upstream"
"npm/internal/entity/user"
"npm/internal/logger"
@@ -169,16 +170,16 @@ func applyRoutes(r chi.Router) chi.Router {
Put("/{hostID:[0-9]+}", handler.UpdateHost())
})
- // Host Templates
- r.With(middleware.EnforceSetup(true)).Route("/host-templates", func(r chi.Router) {
- r.With(middleware.Enforce(user.CapabilityHostTemplatesView), middleware.Filters(hosttemplate.GetFilterSchema())).
- Get("/", handler.GetHostTemplates())
- r.With(middleware.Enforce(user.CapabilityHostTemplatesView)).Get("/{templateID:[0-9]+}", handler.GetHostTemplates())
- r.With(middleware.Enforce(user.CapabilityHostTemplatesManage)).Delete("/{templateID:[0-9]+}", handler.DeleteHostTemplate())
- r.With(middleware.Enforce(user.CapabilityHostTemplatesManage)).With(middleware.EnforceRequestSchema(schema.CreateHostTemplate())).
- Post("/", handler.CreateHostTemplate())
- r.With(middleware.Enforce(user.CapabilityHostTemplatesManage)).With(middleware.EnforceRequestSchema(schema.UpdateHostTemplate())).
- Put("/{templateID:[0-9]+}", handler.UpdateHostTemplate())
+ // Nginx Templates
+ r.With(middleware.EnforceSetup(true)).Route("/nginx-templates", func(r chi.Router) {
+ r.With(middleware.Enforce(user.CapabilityNginxTemplatesView), middleware.Filters(nginxtemplate.GetFilterSchema())).
+ Get("/", handler.GetNginxTemplates())
+ r.With(middleware.Enforce(user.CapabilityNginxTemplatesView)).Get("/{templateID:[0-9]+}", handler.GetNginxTemplates())
+ r.With(middleware.Enforce(user.CapabilityNginxTemplatesManage)).Delete("/{templateID:[0-9]+}", handler.DeleteNginxTemplate())
+ r.With(middleware.Enforce(user.CapabilityNginxTemplatesManage)).With(middleware.EnforceRequestSchema(schema.CreateNginxTemplate())).
+ Post("/", handler.CreateNginxTemplate())
+ r.With(middleware.Enforce(user.CapabilityNginxTemplatesManage)).With(middleware.EnforceRequestSchema(schema.UpdateNginxTemplate())).
+ Put("/{templateID:[0-9]+}", handler.UpdateNginxTemplate())
})
// Streams
@@ -192,6 +193,16 @@ func applyRoutes(r chi.Router) chi.Router {
r.With(middleware.Enforce(user.CapabilityStreamsManage)).With(middleware.EnforceRequestSchema(schema.UpdateStream())).
Put("/{hostID:[0-9]+}", handler.UpdateStream())
})
+
+ // Upstreams
+ r.With(middleware.EnforceSetup(true)).Route("/upstreams", func(r chi.Router) {
+ r.With(middleware.Enforce(user.CapabilityHostsView), middleware.Filters(upstream.GetFilterSchema())).
+ Get("/", handler.GetUpstreams())
+ r.With(middleware.Enforce(user.CapabilityHostsView)).Get("/{upstreamID:[0-9]+}", handler.GetUpstream())
+ r.With(middleware.Enforce(user.CapabilityHostsManage)).Delete("/{upstreamID:[0-9]+}", handler.DeleteUpstream())
+ r.With(middleware.Enforce(user.CapabilityHostsManage)).With(middleware.EnforceRequestSchema(schema.CreateUpstream())).
+ Post("/", handler.CreateUpstream())
+ })
})
return r
diff --git a/backend/internal/api/schema/create_host.go b/backend/internal/api/schema/create_host.go
index 0c44842..18347bb 100644
--- a/backend/internal/api/schema/create_host.go
+++ b/backend/internal/api/schema/create_host.go
@@ -17,14 +17,14 @@ func CreateHost() string {
"required": [
"type",
"domain_names",
- "host_template_id"
+ "nginx_template_id"
],
"properties": {
"type": {
"type": "string",
"pattern": "^proxy$"
},
- "host_template_id": {
+ "nginx_template_id": {
"type": "integer",
"minimum": 1
},
@@ -63,9 +63,6 @@ func CreateHost() string {
"paths": {
"type": "string"
},
- "upstream_options": {
- "type": "string"
- },
"advanced_config": {
"type": "string"
},
diff --git a/backend/internal/api/schema/create_host_template.go b/backend/internal/api/schema/create_nginx_template.go
similarity index 64%
rename from backend/internal/api/schema/create_host_template.go
rename to backend/internal/api/schema/create_nginx_template.go
index ebb0cf4..7fe3fc6 100644
--- a/backend/internal/api/schema/create_host_template.go
+++ b/backend/internal/api/schema/create_nginx_template.go
@@ -1,14 +1,14 @@
package schema
-// CreateHostTemplate is the schema for incoming data validation
-func CreateHostTemplate() string {
+// CreateNginxTemplate is the schema for incoming data validation
+func CreateNginxTemplate() string {
return `
{
"type": "object",
"additionalProperties": false,
"required": [
"name",
- "host_type",
+ "type",
"template"
],
"properties": {
@@ -16,9 +16,9 @@ func CreateHostTemplate() string {
"type": "string",
"minLength": 1
},
- "host_type": {
+ "type": {
"type": "string",
- "pattern": "^proxy|redirect|dead|stream$"
+ "pattern": "^proxy|redirect|dead|stream|upstream$"
},
"template": {
"type": "string",
diff --git a/backend/internal/api/schema/create_upstream.go b/backend/internal/api/schema/create_upstream.go
new file mode 100644
index 0000000..3f2be46
--- /dev/null
+++ b/backend/internal/api/schema/create_upstream.go
@@ -0,0 +1,73 @@
+package schema
+
+import "fmt"
+
+// CreateUpstream is the schema for incoming data validation
+func CreateUpstream() string {
+ return fmt.Sprintf(`
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "servers",
+ "nginx_template_id"
+ ],
+ "properties": {
+ "name": %s,
+ "nginx_template_id": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "advanced_config": %s,
+ "ip_hash": {
+ "type": "boolean"
+ },
+ "ntlm": {
+ "type": "boolean"
+ },
+ "keepalive": {
+ "type": "integer"
+ },
+ "keepalive_requests": {
+ "type": "integer"
+ },
+ "keepalive_time": {
+ "type": "string"
+ },
+ "keepalive_timeout": {
+ "type": "string"
+ },
+ "servers": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "server"
+ ],
+ "properties": {
+ "server": %s,
+ "weight": {
+ "type": "integer"
+ },
+ "max_conns": {
+ "type": "integer"
+ },
+ "max_fails": {
+ "type": "integer"
+ },
+ "fail_timeout": {
+ "type": "integer"
+ },
+ "backup": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+ }
+`, stringMinMax(1, 100), stringMinMax(0, 1024), stringMinMax(2, 255))
+}
diff --git a/backend/internal/api/schema/update_host.go b/backend/internal/api/schema/update_host.go
index 78d1322..21643bb 100644
--- a/backend/internal/api/schema/update_host.go
+++ b/backend/internal/api/schema/update_host.go
@@ -10,7 +10,7 @@ func UpdateHost() string {
"additionalProperties": false,
"minProperties": 1,
"properties": {
- "host_template_id": {
+ "nginx_template_id": {
"type": "integer",
"minimum": 1
},
diff --git a/backend/internal/api/schema/update_host_template.go b/backend/internal/api/schema/update_nginx_template.go
similarity index 90%
rename from backend/internal/api/schema/update_host_template.go
rename to backend/internal/api/schema/update_nginx_template.go
index d51cf34..4ba59cd 100644
--- a/backend/internal/api/schema/update_host_template.go
+++ b/backend/internal/api/schema/update_nginx_template.go
@@ -1,7 +1,7 @@
package schema
// UpdateHostTemplate is the schema for incoming data validation
-func UpdateHostTemplate() string {
+func UpdateNginxTemplate() string {
return `
{
"type": "object",
diff --git a/backend/internal/config/folders.go b/backend/internal/config/folders.go
index bf3d43c..e82570c 100644
--- a/backend/internal/config/folders.go
+++ b/backend/internal/config/folders.go
@@ -19,6 +19,7 @@ func createDataFolders() {
"nginx/hosts",
"nginx/streams",
"nginx/temp",
+ "nginx/upstreams",
}
for _, folder := range folders {
diff --git a/backend/internal/entity/certificate/methods.go b/backend/internal/entity/certificate/methods.go
index f08cc5c..b020469 100644
--- a/backend/internal/entity/certificate/methods.go
+++ b/backend/internal/entity/certificate/methods.go
@@ -127,7 +127,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error)
}
// Get rows
- var items []Model
+ items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
diff --git a/backend/internal/entity/certificate/model.go b/backend/internal/entity/certificate/model.go
index d3b4f18..ac276fc 100644
--- a/backend/internal/entity/certificate/model.go
+++ b/backend/internal/entity/certificate/model.go
@@ -267,9 +267,14 @@ func (m *Model) Request() error {
// GetTemplate will convert the Model to a Template
func (m *Model) GetTemplate() Template {
+ if m.ID == 0 {
+ // No or empty certificate object, happens when the host has no cert
+ return Template{}
+ }
+
domainNames, _ := m.DomainNames.AsStringArray()
- t := Template{
+ return Template{
ID: m.ID,
CreatedOn: m.CreatedOn.Time.String(),
ModifiedOn: m.ModifiedOn.Time.String(),
@@ -288,8 +293,6 @@ func (m *Model) GetTemplate() Template {
IsProvided: m.ID > 0 && m.Status == StatusProvided,
Folder: m.GetFolder(),
}
-
- return t
}
// GetFolder returns the folder where these certs should exist
diff --git a/backend/internal/entity/certificateauthority/methods.go b/backend/internal/entity/certificateauthority/methods.go
index 28941e2..9d0a14e 100644
--- a/backend/internal/entity/certificateauthority/methods.go
+++ b/backend/internal/entity/certificateauthority/methods.go
@@ -112,7 +112,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error)
}
// Get rows
- var items []Model
+ items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
diff --git a/backend/internal/entity/dnsprovider/methods.go b/backend/internal/entity/dnsprovider/methods.go
index c1a1e0e..c31c742 100644
--- a/backend/internal/entity/dnsprovider/methods.go
+++ b/backend/internal/entity/dnsprovider/methods.go
@@ -112,7 +112,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error)
}
// Get rows
- var items []Model
+ items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
diff --git a/backend/internal/entity/host/methods.go b/backend/internal/entity/host/methods.go
index 1de2a7d..68f00fd 100644
--- a/backend/internal/entity/host/methods.go
+++ b/backend/internal/entity/host/methods.go
@@ -34,7 +34,7 @@ func create(host *Model) (int, error) {
modified_on,
user_id,
type,
- host_template_id,
+ nginx_template_id,
listen_interface,
domain_names,
upstream_id,
@@ -48,7 +48,6 @@ func create(host *Model) (int, error) {
hsts_enabled,
hsts_subdomains,
paths,
- upstream_options,
advanced_config,
status,
error_message,
@@ -59,7 +58,7 @@ func create(host *Model) (int, error) {
:modified_on,
:user_id,
:type,
- :host_template_id,
+ :nginx_template_id,
:listen_interface,
:domain_names,
:upstream_id,
@@ -73,7 +72,6 @@ func create(host *Model) (int, error) {
:hsts_enabled,
:hsts_subdomains,
:paths,
- :upstream_options,
:advanced_config,
:status,
:error_message,
@@ -110,7 +108,7 @@ func update(host *Model) error {
modified_on = :modified_on,
user_id = :user_id,
type = :type,
- host_template_id = :host_template_id,
+ nginx_template_id = :nginx_template_id,
listen_interface = :listen_interface,
domain_names = :domain_names,
upstream_id = :upstream_id,
@@ -124,7 +122,6 @@ func update(host *Model) error {
hsts_enabled = :hsts_enabled,
hsts_subdomains = :hsts_subdomains,
paths = :paths,
- upstream_options = :upstream_options,
advanced_config = :advanced_config,
status = :status,
error_message = :error_message,
@@ -163,7 +160,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis
}
// Get rows
- var items []Model
+ items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
diff --git a/backend/internal/entity/host/model.go b/backend/internal/entity/host/model.go
index 8b76836..31cf405 100644
--- a/backend/internal/entity/host/model.go
+++ b/backend/internal/entity/host/model.go
@@ -6,8 +6,10 @@ import (
"npm/internal/database"
"npm/internal/entity/certificate"
- "npm/internal/entity/hosttemplate"
+ "npm/internal/entity/nginxtemplate"
+ "npm/internal/entity/upstream"
"npm/internal/entity/user"
+ "npm/internal/status"
"npm/internal/types"
"npm/internal/util"
)
@@ -21,12 +23,6 @@ const (
RedirectionHostType = "redirection"
// DeadHostType is self explanatory
DeadHostType = "dead"
- // StatusReady means a host is ready to configure
- StatusReady = "ready"
- // StatusOK means a host is configured within Nginx
- StatusOK = "ok"
- // StatusError is self explanatory
- StatusError = "error"
)
// Model is the user model
@@ -36,7 +32,7 @@ type Model struct {
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"`
Type string `json:"type" db:"type" filter:"type,string"`
- HostTemplateID int `json:"host_template_id" db:"host_template_id" filter:"host_template_id,integer"`
+ NginxTemplateID int `json:"nginx_template_id" db:"nginx_template_id" filter:"nginx_template_id,integer"`
ListenInterface string `json:"listen_interface" db:"listen_interface" filter:"listen_interface,string"`
DomainNames types.JSONB `json:"domain_names" db:"domain_names" filter:"domain_names,string"`
UpstreamID int `json:"upstream_id" db:"upstream_id" filter:"upstream_id,integer"`
@@ -50,16 +46,16 @@ type Model struct {
HSTSEnabled bool `json:"hsts_enabled" db:"hsts_enabled" filter:"hsts_enabled,boolean"`
HSTSSubdomains bool `json:"hsts_subdomains" db:"hsts_subdomains" filter:"hsts_subdomains,boolean"`
Paths string `json:"paths" db:"paths" filter:"paths,string"`
- UpstreamOptions string `json:"upstream_options" db:"upstream_options" filter:"upstream_options,string"`
AdvancedConfig string `json:"advanced_config" db:"advanced_config" filter:"advanced_config,string"`
Status string `json:"status" db:"status" filter:"status,string"`
ErrorMessage string `json:"error_message" db:"error_message" filter:"error_message,string"`
IsDisabled bool `json:"is_disabled" db:"is_disabled" filter:"is_disabled,boolean"`
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
// Expansions
- Certificate *certificate.Model `json:"certificate,omitempty"`
- HostTemplate *hosttemplate.Model `json:"host_template,omitempty"`
- User *user.Model `json:"user,omitempty"`
+ Certificate *certificate.Model `json:"certificate,omitempty"`
+ NginxTemplate *nginxtemplate.Model `json:"nginx_template,omitempty"`
+ User *user.Model `json:"user,omitempty"`
+ Upstream *upstream.Model `json:"upstream,omitempty"`
}
func (m *Model) getByQuery(query string, params []interface{}) error {
@@ -93,7 +89,7 @@ func (m *Model) Save(skipConfiguration bool) error {
if !skipConfiguration {
// Set this host as requiring reconfiguration
- m.Status = StatusReady
+ m.Status = status.StatusReady
}
if m.ID == 0 {
@@ -119,6 +115,13 @@ func (m *Model) Delete() bool {
func (m *Model) Expand(items []string) error {
var err error
+ // Always expand the upstream
+ if m.UpstreamID > 0 {
+ var u upstream.Model
+ u, err = upstream.GetByID(m.UpstreamID)
+ m.Upstream = &u
+ }
+
if util.SliceContainsItem(items, "user") && m.ID > 0 {
var usr user.Model
usr, err = user.GetByID(m.UserID)
@@ -131,10 +134,10 @@ func (m *Model) Expand(items []string) error {
m.Certificate = &cert
}
- if util.SliceContainsItem(items, "hosttemplate") && m.HostTemplateID > 0 {
- var templ hosttemplate.Model
- templ, err = hosttemplate.GetByID(m.HostTemplateID)
- m.HostTemplate = &templ
+ if util.SliceContainsItem(items, "nginxtemplate") && m.NginxTemplateID > 0 {
+ var templ nginxtemplate.Model
+ templ, err = nginxtemplate.GetByID(m.NginxTemplateID)
+ m.NginxTemplate = &templ
}
return err
@@ -150,7 +153,7 @@ func (m *Model) GetTemplate() Template {
ModifiedOn: m.ModifiedOn.Time.String(),
UserID: m.UserID,
Type: m.Type,
- HostTemplateID: m.HostTemplateID,
+ NginxTemplateID: m.NginxTemplateID,
ListenInterface: m.ListenInterface,
DomainNames: domainNames,
UpstreamID: m.UpstreamID,
@@ -164,7 +167,6 @@ func (m *Model) GetTemplate() Template {
HSTSEnabled: m.HSTSEnabled,
HSTSSubdomains: m.HSTSSubdomains,
Paths: m.Paths,
- UpstreamOptions: m.UpstreamOptions,
AdvancedConfig: m.AdvancedConfig,
Status: m.Status,
ErrorMessage: m.ErrorMessage,
diff --git a/backend/internal/entity/host/template.go b/backend/internal/entity/host/template.go
index 06fe26d..957b977 100644
--- a/backend/internal/entity/host/template.go
+++ b/backend/internal/entity/host/template.go
@@ -1,5 +1,14 @@
package host
+type TemplateUpstream struct {
+ Hostname string
+ Port int
+ BalanceMethod string
+ MaxFails int
+ FailTimeout int
+ AdvancedConfig string
+}
+
// Template is the model given to the template parser, converted from the Model
type Template struct {
ID int
@@ -7,7 +16,7 @@ type Template struct {
ModifiedOn string
UserID int
Type string
- HostTemplateID int
+ NginxTemplateID int
ListenInterface string
DomainNames []string
UpstreamID int
@@ -22,8 +31,8 @@ type Template struct {
HSTSSubdomains bool
IsDisabled bool
Paths string
- UpstreamOptions string
AdvancedConfig string
Status string
ErrorMessage string
+ Upstreams []TemplateUpstream
}
diff --git a/backend/internal/entity/nginxtemplate/filters.go b/backend/internal/entity/nginxtemplate/filters.go
new file mode 100644
index 0000000..91fd52a
--- /dev/null
+++ b/backend/internal/entity/nginxtemplate/filters.go
@@ -0,0 +1,25 @@
+package nginxtemplate
+
+import (
+ "npm/internal/entity"
+)
+
+var filterMapFunctions = make(map[string]entity.FilterMapFunction)
+
+// getFilterMapFunctions is a map of functions that should be executed
+// during the filtering process, if a field is defined here then the value in
+// the filter will be given to the defined function and it will return a new
+// value for use in the sql query.
+func getFilterMapFunctions() map[string]entity.FilterMapFunction {
+ // if len(filterMapFunctions) == 0 {
+ // TODO: See internal/model/file_item.go:620 for an example
+ // }
+
+ return filterMapFunctions
+}
+
+// GetFilterSchema returns filter schema
+func GetFilterSchema() string {
+ var m Model
+ return entity.GetFilterSchema(m)
+}
diff --git a/backend/internal/entity/hosttemplate/methods.go b/backend/internal/entity/nginxtemplate/methods.go
similarity index 98%
rename from backend/internal/entity/hosttemplate/methods.go
rename to backend/internal/entity/nginxtemplate/methods.go
index e75fcfd..70faef7 100644
--- a/backend/internal/entity/hosttemplate/methods.go
+++ b/backend/internal/entity/nginxtemplate/methods.go
@@ -1,4 +1,4 @@
-package hosttemplate
+package nginxtemplate
import (
"database/sql"
@@ -108,7 +108,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error)
}
// Get rows
- var items []Model
+ items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
diff --git a/backend/internal/entity/hosttemplate/model.go b/backend/internal/entity/nginxtemplate/model.go
similarity index 90%
rename from backend/internal/entity/hosttemplate/model.go
rename to backend/internal/entity/nginxtemplate/model.go
index c791537..ecaac00 100644
--- a/backend/internal/entity/hosttemplate/model.go
+++ b/backend/internal/entity/nginxtemplate/model.go
@@ -1,4 +1,4 @@
-package hosttemplate
+package nginxtemplate
import (
"fmt"
@@ -9,7 +9,7 @@ import (
)
const (
- tableName = "host_template"
+ tableName = "nginx_template"
)
// Model is the user model
@@ -19,7 +19,7 @@ type Model struct {
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"`
Name string `json:"name" db:"name" filter:"name,string"`
- Type string `json:"host_type" db:"host_type" filter:"host_type,string"`
+ Type string `json:"type" db:"type" filter:"type,string"`
Template string `json:"template" db:"template" filter:"template,string"`
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
}
@@ -62,7 +62,7 @@ func (m *Model) Save() error {
return err
}
-// Delete will mark a host as deleted
+// Delete will mark a template as deleted
func (m *Model) Delete() bool {
m.Touch(false)
m.IsDeleted = true
diff --git a/backend/internal/entity/nginxtemplate/structs.go b/backend/internal/entity/nginxtemplate/structs.go
new file mode 100644
index 0000000..09999d1
--- /dev/null
+++ b/backend/internal/entity/nginxtemplate/structs.go
@@ -0,0 +1,15 @@
+package nginxtemplate
+
+import (
+ "npm/internal/model"
+)
+
+// ListResponse is the JSON response for this list
+type ListResponse struct {
+ Total int `json:"total"`
+ Offset int `json:"offset"`
+ Limit int `json:"limit"`
+ Sort []model.Sort `json:"sort"`
+ Filter []model.Filter `json:"filter,omitempty"`
+ Items []Model `json:"items,omitempty"`
+}
diff --git a/backend/internal/entity/setting/methods.go b/backend/internal/entity/setting/methods.go
index 51b7517..1d9eef9 100644
--- a/backend/internal/entity/setting/methods.go
+++ b/backend/internal/entity/setting/methods.go
@@ -106,7 +106,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error)
}
// Get rows
- var items []Model
+ items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
diff --git a/backend/internal/entity/stream/methods.go b/backend/internal/entity/stream/methods.go
index 02ddd4d..0accd50 100644
--- a/backend/internal/entity/stream/methods.go
+++ b/backend/internal/entity/stream/methods.go
@@ -114,7 +114,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error)
}
// Get rows
- var items []Model
+ items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
diff --git a/backend/internal/entity/hosttemplate/filters.go b/backend/internal/entity/upstream/filters.go
similarity index 97%
rename from backend/internal/entity/hosttemplate/filters.go
rename to backend/internal/entity/upstream/filters.go
index 3b4d512..82ace11 100644
--- a/backend/internal/entity/hosttemplate/filters.go
+++ b/backend/internal/entity/upstream/filters.go
@@ -1,4 +1,4 @@
-package hosttemplate
+package upstream
import (
"npm/internal/entity"
diff --git a/backend/internal/entity/upstream/methods.go b/backend/internal/entity/upstream/methods.go
new file mode 100644
index 0000000..4418db7
--- /dev/null
+++ b/backend/internal/entity/upstream/methods.go
@@ -0,0 +1,162 @@
+package upstream
+
+import (
+ "database/sql"
+ goerrors "errors"
+ "fmt"
+
+ "npm/internal/database"
+ "npm/internal/entity"
+ "npm/internal/errors"
+ "npm/internal/logger"
+ "npm/internal/model"
+)
+
+// GetByID finds a Upstream by ID
+func GetByID(id int) (Model, error) {
+ var m Model
+ err := m.LoadByID(id)
+ return m, err
+}
+
+// create will create a Upstream from this model
+func create(u *Model) (int, error) {
+ if u.ID != 0 {
+ return 0, goerrors.New("Cannot create upstream when model already has an ID")
+ }
+
+ u.Touch(true)
+
+ db := database.GetInstance()
+ // nolint: gosec
+ result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
+ created_on,
+ modified_on,
+ user_id,
+ name,
+ nginx_template_id,
+ ip_hash,
+ ntlm,
+ keepalive,
+ keepalive_requests,
+ keepalive_time,
+ keepalive_timeout,
+ advanced_config,
+ status,
+ error_message,
+ is_deleted
+ ) VALUES (
+ :created_on,
+ :modified_on,
+ :user_id,
+ :name,
+ :nginx_template_id,
+ :ip_hash,
+ :ntlm,
+ :keepalive,
+ :keepalive_requests,
+ :keepalive_time,
+ :keepalive_timeout,
+ :advanced_config,
+ :status,
+ :error_message,
+ :is_deleted
+ )`, u)
+
+ if err != nil {
+ return 0, err
+ }
+
+ last, lastErr := result.LastInsertId()
+ if lastErr != nil {
+ return 0, lastErr
+ }
+
+ logger.Debug("Created Upstream: %+v", u)
+
+ return int(last), nil
+}
+
+// update will Update a Upstream from this model
+func update(u *Model) error {
+ if u.ID == 0 {
+ return goerrors.New("Cannot update upstream when model doesn't have an ID")
+ }
+
+ u.Touch(false)
+
+ db := database.GetInstance()
+ // nolint: gosec
+ _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
+ created_on = :created_on,
+ modified_on = :modified_on,
+ user_id = :user_id,
+ name = :name,
+ nginx_template_id = :nginx_template_id,
+ ip_hash = :ip_hash,
+ ntlm = :ntlm,
+ keepalive = :keepalive,
+ keepalive_requests = :keepalive_requests,
+ keepalive_time = :keepalive_time,
+ advanced_config = :advanced_config,
+ status = :status,
+ error_message = :error_message,
+ is_deleted = :is_deleted
+ WHERE id = :id`, u)
+
+ logger.Debug("Updated Upstream: %+v", u)
+
+ return err
+}
+
+// List will return a list of Upstreams
+func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) {
+ var result ListResponse
+ var exampleModel Model
+
+ defaultSort := model.Sort{
+ Field: "name",
+ Direction: "ASC",
+ }
+
+ db := database.GetInstance()
+ if db == nil {
+ return result, errors.ErrDatabaseUnavailable
+ }
+
+ // Get count of items in this search
+ query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
+ countRow := db.QueryRowx(query, params...)
+ var totalRows int
+ queryErr := countRow.Scan(&totalRows)
+ if queryErr != nil && queryErr != sql.ErrNoRows {
+ logger.Debug("%s -- %+v", query, params)
+ return result, queryErr
+ }
+
+ // Get rows
+ items := make([]Model, 0)
+ query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
+ err := db.Select(&items, query, params...)
+ if err != nil {
+ logger.Debug("%s -- %+v", query, params)
+ return result, err
+ }
+
+ // Expand to get servers, at a minimum
+ for idx := range items {
+ // nolint: errcheck, gosec
+ items[idx].Expand(expand)
+ }
+
+ result = ListResponse{
+ Items: items,
+ Total: totalRows,
+ Limit: pageInfo.Limit,
+ Offset: pageInfo.Offset,
+ Sort: pageInfo.Sort,
+ Filter: filters,
+ }
+
+ return result, nil
+}
diff --git a/backend/internal/entity/upstream/model.go b/backend/internal/entity/upstream/model.go
new file mode 100644
index 0000000..408a878
--- /dev/null
+++ b/backend/internal/entity/upstream/model.go
@@ -0,0 +1,127 @@
+package upstream
+
+import (
+ "fmt"
+ "time"
+
+ "npm/internal/database"
+ "npm/internal/entity/nginxtemplate"
+ "npm/internal/entity/upstreamserver"
+ "npm/internal/status"
+ "npm/internal/types"
+ "npm/internal/util"
+)
+
+const (
+ tableName = "upstream"
+)
+
+// Model is the Upstream model
+// See: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream
+type Model struct {
+ ID int `json:"id" db:"id" filter:"id,integer"`
+ CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"`
+ ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
+ UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"`
+ Name string `json:"name" db:"name" filter:"name,string"`
+ NginxTemplateID int `json:"nginx_template_id" db:"nginx_template_id" filter:"nginx_template_id,integer"`
+ IPHash bool `json:"ip_hash" db:"ip_hash" filter:"ip_hash,boolean"`
+ NTLM bool `json:"ntlm" db:"ntlm" filter:"ntlm,boolean"`
+ Keepalive int `json:"keepalive" db:"keepalive" filter:"keepalive,integer"`
+ KeepaliveRequests int `json:"keepalive_requests" db:"keepalive_requests" filter:"keepalive_requests,integer"`
+ KeepaliveTime string `json:"keepalive_time" db:"keepalive_time" filter:"keepalive_time,string"`
+ KeepaliveTimeout string `json:"keepalive_timeout" db:"keepalive_timeout" filter:"keepalive_timeout,string"`
+ AdvancedConfig string `json:"advanced_config" db:"advanced_config" filter:"advanced_config,string"`
+ Status string `json:"status" db:"status" filter:"status,string"`
+ ErrorMessage string `json:"error_message" db:"error_message" filter:"error_message,string"`
+ IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
+ // Expansions
+ Servers []upstreamserver.Model `json:"servers"`
+ NginxTemplate *nginxtemplate.Model `json:"nginx_template,omitempty"`
+}
+
+func (m *Model) getByQuery(query string, params []interface{}) error {
+ return database.GetByQuery(m, query, params)
+}
+
+// LoadByID will load from an ID
+func (m *Model) LoadByID(id int) error {
+ query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName)
+ params := []interface{}{id, 0}
+ err := m.getByQuery(query, params)
+ if err == nil {
+ err = m.Expand(nil)
+ }
+ return err
+}
+
+// Touch will update model's timestamp(s)
+func (m *Model) Touch(created bool) {
+ var d types.DBDate
+ d.Time = time.Now()
+ if created {
+ m.CreatedOn = d
+ }
+ m.ModifiedOn = d
+}
+
+// Save will save this model to the DB
+func (m *Model) Save(skipConfiguration bool) error {
+ var err error
+
+ if m.UserID == 0 {
+ return fmt.Errorf("User ID must be specified")
+ }
+
+ if !skipConfiguration {
+ // Set this upstream as requiring reconfiguration
+ m.Status = status.StatusReady
+ }
+
+ if m.ID == 0 {
+ m.ID, err = create(m)
+ } else {
+ err = update(m)
+ }
+
+ // Save Servers
+ if err == nil {
+ for idx := range m.Servers {
+ // Continue if previous iteration didn't cause an error
+ if err == nil {
+ m.Servers[idx].UpstreamID = m.ID
+ err = m.Servers[idx].Save()
+ }
+ }
+ }
+
+ return err
+}
+
+// Delete will mark a upstream as deleted
+func (m *Model) Delete() bool {
+ m.Touch(false)
+ m.IsDeleted = true
+ if err := m.Save(false); err != nil {
+ return false
+ }
+ return true
+}
+
+// Expand will fill in more properties
+func (m *Model) Expand(items []string) error {
+ var err error
+
+ // Always expand servers, if not done already
+ if len(m.Servers) == 0 {
+ m.Servers, err = upstreamserver.GetByUpstreamID(m.ID)
+ }
+
+ if util.SliceContainsItem(items, "nginxtemplate") && m.NginxTemplateID > 0 {
+ var templ nginxtemplate.Model
+ templ, err = nginxtemplate.GetByID(m.NginxTemplateID)
+ m.NginxTemplate = &templ
+ }
+
+ return err
+}
diff --git a/backend/internal/entity/hosttemplate/structs.go b/backend/internal/entity/upstream/structs.go
similarity index 94%
rename from backend/internal/entity/hosttemplate/structs.go
rename to backend/internal/entity/upstream/structs.go
index 4cc54b9..b56dd06 100644
--- a/backend/internal/entity/hosttemplate/structs.go
+++ b/backend/internal/entity/upstream/structs.go
@@ -1,4 +1,4 @@
-package hosttemplate
+package upstream
import (
"npm/internal/model"
diff --git a/backend/internal/entity/upstreamserver/filters.go b/backend/internal/entity/upstreamserver/filters.go
new file mode 100644
index 0000000..e61197e
--- /dev/null
+++ b/backend/internal/entity/upstreamserver/filters.go
@@ -0,0 +1,25 @@
+package upstreamserver
+
+import (
+ "npm/internal/entity"
+)
+
+var filterMapFunctions = make(map[string]entity.FilterMapFunction)
+
+// getFilterMapFunctions is a map of functions that should be executed
+// during the filtering process, if a field is defined here then the value in
+// the filter will be given to the defined function and it will return a new
+// value for use in the sql query.
+func getFilterMapFunctions() map[string]entity.FilterMapFunction {
+ // if len(filterMapFunctions) == 0 {
+ // TODO: See internal/model/file_item.go:620 for an example
+ // }
+
+ return filterMapFunctions
+}
+
+// GetFilterSchema returns filter schema
+func GetFilterSchema() string {
+ var m Model
+ return entity.GetFilterSchema(m)
+}
diff --git a/backend/internal/entity/upstreamserver/methods.go b/backend/internal/entity/upstreamserver/methods.go
new file mode 100644
index 0000000..69a12c5
--- /dev/null
+++ b/backend/internal/entity/upstreamserver/methods.go
@@ -0,0 +1,154 @@
+package upstreamserver
+
+import (
+ "database/sql"
+ goerrors "errors"
+ "fmt"
+
+ "npm/internal/database"
+ "npm/internal/entity"
+ "npm/internal/errors"
+ "npm/internal/logger"
+ "npm/internal/model"
+)
+
+// GetByID finds a Upstream Server by ID
+func GetByID(id int) (Model, error) {
+ var m Model
+ err := m.LoadByID(id)
+ return m, err
+}
+
+// GetByUpstreamID finds all servers in the upstream
+func GetByUpstreamID(upstreamID int) ([]Model, error) {
+ items := make([]Model, 0)
+ query := `SELECT * FROM ` + fmt.Sprintf("`%s`", tableName) + ` WHERE upstream_id = ? ORDER BY server`
+ db := database.GetInstance()
+ err := db.Select(&items, query, upstreamID)
+ if err != nil {
+ logger.Debug("%s -- %d", query, upstreamID)
+ }
+ return items, err
+}
+
+// create will create a Upstream Server from this model
+func create(u *Model) (int, error) {
+ if u.ID != 0 {
+ return 0, goerrors.New("Cannot create upstream server when model already has an ID")
+ }
+
+ u.Touch(true)
+
+ db := database.GetInstance()
+ // nolint: gosec
+ result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
+ created_on,
+ modified_on,
+ upstream_id,
+ server,
+ weight,
+ max_conns,
+ max_fails,
+ fail_timeout,
+ backup,
+ is_deleted
+ ) VALUES (
+ :created_on,
+ :modified_on,
+ :upstream_id,
+ :server,
+ :weight,
+ :max_conns,
+ :max_fails,
+ :fail_timeout,
+ :backup,
+ :is_deleted
+ )`, u)
+
+ if err != nil {
+ return 0, err
+ }
+
+ last, lastErr := result.LastInsertId()
+ if lastErr != nil {
+ return 0, lastErr
+ }
+
+ logger.Debug("Created Upstream Server: %+v", u)
+
+ return int(last), nil
+}
+
+// update will Update a Upstream from this model
+func update(u *Model) error {
+ if u.ID == 0 {
+ return goerrors.New("Cannot update upstream server when model doesn't have an ID")
+ }
+
+ u.Touch(false)
+
+ db := database.GetInstance()
+ // nolint: gosec
+ _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
+ created_on = :created_on,
+ modified_on = :modified_on,
+ upstream_id = :upstream_id,
+ server = :server,
+ weight = :weight,
+ max_conns = :max_conns,
+ max_fails = :max_fails,
+ fail_timeout = :fail_timeout,
+ backup = :backup,
+ is_deleted = :is_deleted
+ WHERE id = :id`, u)
+
+ logger.Debug("Updated Upstream Server: %+v", u)
+
+ return err
+}
+
+// List will return a list of Upstreams
+func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
+ var result ListResponse
+ var exampleModel Model
+
+ defaultSort := model.Sort{
+ Field: "server",
+ Direction: "ASC",
+ }
+
+ db := database.GetInstance()
+ if db == nil {
+ return result, errors.ErrDatabaseUnavailable
+ }
+
+ // Get count of items in this search
+ query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
+ countRow := db.QueryRowx(query, params...)
+ var totalRows int
+ queryErr := countRow.Scan(&totalRows)
+ if queryErr != nil && queryErr != sql.ErrNoRows {
+ logger.Debug("%s -- %+v", query, params)
+ return result, queryErr
+ }
+
+ // Get rows
+ items := make([]Model, 0)
+ query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
+ err := db.Select(&items, query, params...)
+ if err != nil {
+ logger.Debug("%s -- %+v", query, params)
+ return result, err
+ }
+
+ result = ListResponse{
+ Items: items,
+ Total: totalRows,
+ Limit: pageInfo.Limit,
+ Offset: pageInfo.Offset,
+ Sort: pageInfo.Sort,
+ Filter: filters,
+ }
+
+ return result, nil
+}
diff --git a/backend/internal/entity/upstreamserver/model.go b/backend/internal/entity/upstreamserver/model.go
new file mode 100644
index 0000000..50f7f23
--- /dev/null
+++ b/backend/internal/entity/upstreamserver/model.go
@@ -0,0 +1,76 @@
+package upstreamserver
+
+import (
+ "fmt"
+ "time"
+
+ "npm/internal/database"
+ "npm/internal/types"
+)
+
+const (
+ tableName = "upstream_server"
+)
+
+// Model is the upstream model
+type Model struct {
+ ID int `json:"id" db:"id" filter:"id,integer"`
+ CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"`
+ ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
+ UpstreamID int `json:"upstream_id" db:"upstream_id" filter:"upstream_id,integer"`
+ Server string `json:"server" db:"server" filter:"server,string"`
+ Weight int `json:"weight" db:"weight" filter:"weight,integer"`
+ MaxConns int `json:"max_conns" db:"max_conns" filter:"max_conns,integer"`
+ MaxFails int `json:"max_fails" db:"max_fails" filter:"max_fails,integer"`
+ FailTimeout int `json:"fail_timeout" db:"fail_timeout" filter:"fail_timeout,integer"`
+ Backup bool `json:"backup" db:"backup" filter:"backup,boolean"`
+ IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
+}
+
+func (m *Model) getByQuery(query string, params []interface{}) error {
+ return database.GetByQuery(m, query, params)
+}
+
+// LoadByID will load from an ID
+func (m *Model) LoadByID(id int) error {
+ query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName)
+ params := []interface{}{id, 0}
+ return m.getByQuery(query, params)
+}
+
+// Touch will update model's timestamp(s)
+func (m *Model) Touch(created bool) {
+ var d types.DBDate
+ d.Time = time.Now()
+ if created {
+ m.CreatedOn = d
+ }
+ m.ModifiedOn = d
+}
+
+// Save will save this model to the DB
+func (m *Model) Save() error {
+ var err error
+
+ if m.UpstreamID == 0 {
+ return fmt.Errorf("Upstream ID must be specified")
+ }
+
+ if m.ID == 0 {
+ m.ID, err = create(m)
+ } else {
+ err = update(m)
+ }
+
+ return err
+}
+
+// Delete will mark a upstream as deleted
+func (m *Model) Delete() bool {
+ m.Touch(false)
+ m.IsDeleted = true
+ if err := m.Save(); err != nil {
+ return false
+ }
+ return true
+}
diff --git a/backend/internal/entity/upstreamserver/structs.go b/backend/internal/entity/upstreamserver/structs.go
new file mode 100644
index 0000000..df6a40e
--- /dev/null
+++ b/backend/internal/entity/upstreamserver/structs.go
@@ -0,0 +1,15 @@
+package upstreamserver
+
+import (
+ "npm/internal/model"
+)
+
+// ListResponse is the JSON response for this list
+type ListResponse struct {
+ Total int `json:"total"`
+ Offset int `json:"offset"`
+ Limit int `json:"limit"`
+ Sort []model.Sort `json:"sort"`
+ Filter []model.Filter `json:"filter,omitempty"`
+ Items []Model `json:"items,omitempty"`
+}
diff --git a/backend/internal/entity/user/capabilities.go b/backend/internal/entity/user/capabilities.go
index 704e7b6..a4c74b8 100644
--- a/backend/internal/entity/user/capabilities.go
+++ b/backend/internal/entity/user/capabilities.go
@@ -25,10 +25,10 @@ const (
CapabilityHostsView = "hosts.view"
// CapabilityHostsManage hosts manage
CapabilityHostsManage = "hosts.manage"
- // CapabilityHostTemplatesView host-templates view
- CapabilityHostTemplatesView = "host-templates.view"
- // CapabilityHostTemplatesManage host-templates manage
- CapabilityHostTemplatesManage = "host-templates.manage"
+ // CapabilityNginxTemplatesView nginx-templates view
+ CapabilityNginxTemplatesView = "nginx-templates.view"
+ // CapabilityNginxTemplatesManage nginx-templates manage
+ CapabilityNginxTemplatesManage = "nginx-templates.manage"
// CapabilitySettingsManage settings manage
CapabilitySettingsManage = "settings.manage"
// CapabilityStreamsView streams view
diff --git a/backend/internal/entity/user/methods.go b/backend/internal/entity/user/methods.go
index 42b5247..c80890e 100644
--- a/backend/internal/entity/user/methods.go
+++ b/backend/internal/entity/user/methods.go
@@ -154,7 +154,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis
}
// Get rows
- var items []Model
+ items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
diff --git a/backend/internal/nginx/control.go b/backend/internal/nginx/control.go
index a41c8e0..9d34ad2 100644
--- a/backend/internal/nginx/control.go
+++ b/backend/internal/nginx/control.go
@@ -4,47 +4,97 @@ import (
"fmt"
"npm/internal/config"
+ "npm/internal/entity/certificate"
"npm/internal/entity/host"
+ "npm/internal/entity/upstream"
"npm/internal/logger"
+ "npm/internal/status"
)
// ConfigureHost will attempt to write nginx conf and reload nginx
func ConfigureHost(h host.Model) error {
// nolint: errcheck, gosec
- h.Expand([]string{"certificate", "hosttemplate"})
+ h.Expand([]string{"certificate", "nginxtemplate"})
+
+ var certificateTemplate certificate.Template
+ if h.Certificate != nil {
+ certificateTemplate = h.Certificate.GetTemplate()
+ }
data := TemplateData{
ConfDir: fmt.Sprintf("%s/nginx/hosts", config.Configuration.DataFolder),
DataDir: config.Configuration.DataFolder,
Host: h.GetTemplate(),
- Certificate: h.Certificate.GetTemplate(),
+ Certificate: certificateTemplate,
}
filename := fmt.Sprintf("%s/host_%d.conf", data.ConfDir, h.ID)
// Write the config to disk
- err := writeTemplate(filename, h.HostTemplate.Template, data)
+ err := writeTemplate(filename, h.NginxTemplate.Template, data)
if err != nil {
// this configuration failed somehow
- h.Status = host.StatusError
+ h.Status = status.StatusError
h.ErrorMessage = fmt.Sprintf("Template generation failed: %s", err.Error())
logger.Debug(h.ErrorMessage)
return h.Save(true)
}
// nolint: errcheck, gosec
- if err := reloadNginx(); err != nil {
+ if output, err := reloadNginx(); err != nil {
// reloading nginx failed, likely due to this host having a problem
- h.Status = host.StatusError
- h.ErrorMessage = fmt.Sprintf("Nginx configuation error: %s", err.Error())
+ h.Status = status.StatusError
+ h.ErrorMessage = fmt.Sprintf("Nginx configuation error: %s - %s", err.Error(), output)
writeConfigFile(filename, fmt.Sprintf("# %s", h.ErrorMessage))
logger.Debug(h.ErrorMessage)
} else {
// All good
- h.Status = host.StatusOK
+ h.Status = status.StatusOK
h.ErrorMessage = ""
logger.Debug("ConfigureHost OK: %+v", h)
}
return h.Save(true)
}
+
+// ConfigureUpstream will attempt to write nginx conf and reload nginx
+func ConfigureUpstream(u upstream.Model) error {
+ logger.Debug("ConfigureUpstream: %+v)", u)
+
+ // nolint: errcheck, gosec
+ u.Expand([]string{"nginxtemplate"})
+
+ data := TemplateData{
+ ConfDir: fmt.Sprintf("%s/nginx/upstreams", config.Configuration.DataFolder),
+ DataDir: config.Configuration.DataFolder,
+ Upstream: u,
+ }
+
+ filename := fmt.Sprintf("%s/upstream_%d.conf", data.ConfDir, u.ID)
+
+ // Write the config to disk
+ err := writeTemplate(filename, u.NginxTemplate.Template, data)
+ if err != nil {
+ // this configuration failed somehow
+ u.Status = status.StatusError
+ u.ErrorMessage = fmt.Sprintf("Template generation failed: %s", err.Error())
+ logger.Debug(u.ErrorMessage)
+ return u.Save(true)
+ }
+
+ // nolint: errcheck, gosec
+ if output, err := reloadNginx(); err != nil {
+ // reloading nginx failed, likely due to this host having a problem
+ u.Status = status.StatusError
+ u.ErrorMessage = fmt.Sprintf("Nginx configuation error: %s - %s", err.Error(), output)
+ writeConfigFile(filename, fmt.Sprintf("# %s", u.ErrorMessage))
+ logger.Debug(u.ErrorMessage)
+ } else {
+ // All good
+ u.Status = status.StatusOK
+ u.ErrorMessage = ""
+ logger.Debug("ConfigureUpstream OK: %+v", u)
+ }
+
+ return u.Save(true)
+}
diff --git a/backend/internal/nginx/exec.go b/backend/internal/nginx/exec.go
index 7801751..727aad3 100644
--- a/backend/internal/nginx/exec.go
+++ b/backend/internal/nginx/exec.go
@@ -7,9 +7,8 @@ import (
"npm/internal/logger"
)
-func reloadNginx() error {
- _, err := shExec([]string{"-s", "reload"})
- return err
+func reloadNginx() (string, error) {
+ return shExec([]string{"-s", "reload"})
}
func getNginxFilePath() (string, error) {
@@ -32,7 +31,7 @@ func shExec(args []string) (string, error) {
// nolint: gosec
c := exec.Command(ng, args...)
- b, e := c.Output()
+ b, e := c.CombinedOutput()
if e != nil {
logger.Error("NginxError", fmt.Errorf("Command error: %s -- %v\n%+v", ng, args, e))
diff --git a/backend/internal/nginx/templates.go b/backend/internal/nginx/templates.go
index 09285fd..951cc42 100644
--- a/backend/internal/nginx/templates.go
+++ b/backend/internal/nginx/templates.go
@@ -6,7 +6,9 @@ import (
"npm/internal/entity/certificate"
"npm/internal/entity/host"
+ "npm/internal/entity/upstream"
"npm/internal/logger"
+ "npm/internal/util"
"github.com/aymerick/raymond"
)
@@ -17,10 +19,15 @@ type TemplateData struct {
DataDir string
Host host.Template
Certificate certificate.Template
+ Upstream upstream.Model
}
func generateHostConfig(template string, data TemplateData) (string, error) {
+ logger.Debug("Rendering Template - Template: %s", template)
+ logger.Debug("Rendering Template - Data: %+v", data)
return raymond.Render(template, data)
+
+ // todo: apply some post processing to this config, stripe trailing whitespace from lines and then remove groups of 2+ \n's so the config looks nicer
}
func writeTemplate(filename, template string, data TemplateData) error {
@@ -31,7 +38,7 @@ func writeTemplate(filename, template string, data TemplateData) error {
// Write it. This will also write an error comment if generation failed
// nolint: gosec
- writeErr := writeConfigFile(filename, output)
+ writeErr := writeConfigFile(filename, util.CleanupWhitespace(output))
if err != nil {
return err
}
diff --git a/backend/internal/status/status.go b/backend/internal/status/status.go
new file mode 100644
index 0000000..84f8dc6
--- /dev/null
+++ b/backend/internal/status/status.go
@@ -0,0 +1,10 @@
+package status
+
+const (
+ // StatusReady means a host is ready to configure
+ StatusReady = "ready"
+ // StatusOK means a host is configured within Nginx
+ StatusOK = "ok"
+ // StatusError is self explanatory
+ StatusError = "error"
+)
diff --git a/backend/internal/util/strings.go b/backend/internal/util/strings.go
new file mode 100644
index 0000000..085602b
--- /dev/null
+++ b/backend/internal/util/strings.go
@@ -0,0 +1,24 @@
+package util
+
+import (
+ "regexp"
+ "strings"
+ "unicode"
+)
+
+// CleanupWhitespace will trim up and remove extra lines and stuff
+func CleanupWhitespace(s string) string {
+ // Remove trailing whitespace from all lines
+ slices := strings.Split(s, "\n")
+ for idx := range slices {
+ slices[idx] = strings.TrimRightFunc(slices[idx], unicode.IsSpace)
+ }
+ // Output: [a b c]
+ result := strings.Join(slices, "\n")
+
+ // Remove empty lines
+ r1 := regexp.MustCompile("\n+")
+ result = r1.ReplaceAllString(result, "\n")
+
+ return result
+}
diff --git a/backend/internal/util/strings_test.go b/backend/internal/util/strings_test.go
new file mode 100644
index 0000000..d9bd24b
--- /dev/null
+++ b/backend/internal/util/strings_test.go
@@ -0,0 +1,51 @@
+package util
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestCleanupWhitespace(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ want string
+ }{
+ {
+ name: "test a",
+ input: `# ------------------------------------------------------------
+# Upstream 5: API servers 2
+# ------------------------------------------------------------
+
+upstream npm_upstream_5 {` + ` ` + /* this adds whitespace to the end without the ide trimming it */ `
+
+
+
+
+
+
+
+
+
+ server 192.168.0.10:80 weight=100 ;
+ server 192.168.0.11:80 weight=50 ;
+
+}`,
+ want: `# ------------------------------------------------------------
+# Upstream 5: API servers 2
+# ------------------------------------------------------------
+upstream npm_upstream_5 {
+ server 192.168.0.10:80 weight=100 ;
+ server 192.168.0.11:80 weight=50 ;
+}`,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ output := CleanupWhitespace(tt.input)
+ assert.Equal(t, tt.want, output)
+ })
+ }
+}
diff --git a/backend/internal/validator/hosts.go b/backend/internal/validator/hosts.go
index f830d2d..7a7ca2e 100644
--- a/backend/internal/validator/hosts.go
+++ b/backend/internal/validator/hosts.go
@@ -5,7 +5,7 @@ import (
"npm/internal/entity/certificate"
"npm/internal/entity/host"
- "npm/internal/entity/hosttemplate"
+ "npm/internal/entity/nginxtemplate"
)
// ValidateHost will check if associated objects exist and other checks
@@ -20,13 +20,13 @@ func ValidateHost(h host.Model) error {
}
}
- // Check the host template exists and has the same type.
- hostTemplate, tErr := hosttemplate.GetByID(h.HostTemplateID)
+ // Check the nginx template exists and has the same type.
+ nginxTemplate, tErr := nginxtemplate.GetByID(h.NginxTemplateID)
if tErr != nil {
- return fmt.Errorf("Host Template #%d does not exist", h.HostTemplateID)
+ return fmt.Errorf("Host Template #%d does not exist", h.NginxTemplateID)
}
- if hostTemplate.Type != h.Type {
- return fmt.Errorf("Host Template #%d is not valid for this host type", h.HostTemplateID)
+ if nginxTemplate.Type != h.Type {
+ return fmt.Errorf("Host Template #%d is not valid for this host type", h.NginxTemplateID)
}
return nil
diff --git a/backend/internal/validator/upstreams.go b/backend/internal/validator/upstreams.go
new file mode 100644
index 0000000..c8c9648
--- /dev/null
+++ b/backend/internal/validator/upstreams.go
@@ -0,0 +1,39 @@
+package validator
+
+import (
+ "errors"
+ "fmt"
+
+ "npm/internal/entity/nginxtemplate"
+ "npm/internal/entity/upstream"
+)
+
+// ValidateUpstream will check if associated objects exist and other checks
+// will return a nil error if things are OK
+func ValidateUpstream(u upstream.Model) error {
+ // Needs to have more than 1 server
+ if len(u.Servers) < 2 {
+ return errors.New("Upstreams require at least 2 servers")
+ }
+
+ // Backup servers aren't permitted with hash balancing
+ if u.IPHash {
+ // check all servers for a backup param
+ for _, server := range u.Servers {
+ if server.Backup {
+ return errors.New("Backup servers cannot be used with hash balancing")
+ }
+ }
+ }
+
+ // Check the nginx template exists and has the same type.
+ nginxTemplate, err := nginxtemplate.GetByID(u.NginxTemplateID)
+ if err != nil {
+ return fmt.Errorf("Nginx Template #%d does not exist", u.NginxTemplateID)
+ }
+ if nginxTemplate.Type != "upstream" {
+ return fmt.Errorf("Host Template #%d is not valid for this upstream", u.NginxTemplateID)
+ }
+
+ return nil
+}
diff --git a/backend/scripts/test.sh b/backend/scripts/test.sh
index 92a0a02..dcfb5c5 100755
--- a/backend/scripts/test.sh
+++ b/backend/scripts/test.sh
@@ -1,5 +1,3 @@
#!/bin/bash -e
-export RICHGO_FORCE_COLOR=1
-
-richgo test -bench=. -cover -v ./internal/...
+go test -json -cover ./internal/... | tparse
diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf
index f815c1b..87118ad 100644
--- a/docker/rootfs/etc/nginx/nginx.conf
+++ b/docker/rootfs/etc/nginx/nginx.conf
@@ -1,6 +1,6 @@
# run nginx in foreground
daemon off;
-user npmuser;
+#user npmuser;
pid /run/nginx/nginx.pid;
# Set number of worker processes automatically based on number of CPU cores.
@@ -60,7 +60,7 @@ http {
# Files generated by NPM
include /etc/nginx/conf.d/*.conf;
include /data/nginx/default_host/*.conf;
- include /data/nginx/proxy_host/*.conf;
- include /data/nginx/redirection_host/*.conf;
- include /data/nginx/dead_host/*.conf;
+ include /data/nginx/upstreams/*.conf;
+ include /data/nginx/hosts/*.conf;
+ include /data/nginx/streams/*.conf;
}
diff --git a/frontend/src/Router.tsx b/frontend/src/Router.tsx
index 755579a..d4796e5 100644
--- a/frontend/src/Router.tsx
+++ b/frontend/src/Router.tsx
@@ -14,7 +14,7 @@ const CertificateAuthorities = lazy(
const Dashboard = lazy(() => import("pages/Dashboard"));
const DNSProviders = lazy(() => import("pages/DNSProviders"));
const Hosts = lazy(() => import("pages/Hosts"));
-const HostTemplates = lazy(() => import("pages/HostTemplates"));
+const NginxTemplates = lazy(() => import("pages/NginxTemplates"));
const Login = lazy(() => import("pages/Login"));
const GeneralSettings = lazy(() => import("pages/Settings"));
const Setup = lazy(() => import("pages/Setup"));
@@ -66,8 +66,8 @@ function Router() {
} />
} />
}
+ path="/settings/nginx-templates"
+ element={}
/>
} />
} />
diff --git a/frontend/src/api/npm/getHostTemplates.ts b/frontend/src/api/npm/getNginxTemplates.ts
similarity index 63%
rename from frontend/src/api/npm/getHostTemplates.ts
rename to frontend/src/api/npm/getNginxTemplates.ts
index dc91d66..15d8e28 100644
--- a/frontend/src/api/npm/getHostTemplates.ts
+++ b/frontend/src/api/npm/getNginxTemplates.ts
@@ -1,16 +1,16 @@
import * as api from "./base";
-import { HostTemplatesResponse } from "./responseTypes";
+import { NginxTemplatesResponse } from "./responseTypes";
-export async function getHostTemplates(
+export async function getNginxTemplates(
offset = 0,
limit = 10,
sort?: string,
filters?: { [key: string]: string },
abortController?: AbortController,
-): Promise {
+): Promise {
const { result } = await api.get(
{
- url: "host-templates",
+ url: "nginx-templates",
params: { limit, offset, sort, ...filters },
},
abortController,
diff --git a/frontend/src/api/npm/index.ts b/frontend/src/api/npm/index.ts
index faf6fe8..d03fa7a 100644
--- a/frontend/src/api/npm/index.ts
+++ b/frontend/src/api/npm/index.ts
@@ -9,7 +9,7 @@ export * from "./getDNSProviders";
export * from "./getDNSProvidersAcmesh";
export * from "./getHealth";
export * from "./getHosts";
-export * from "./getHostTemplates";
+export * from "./getNginxTemplates";
export * from "./getSettings";
export * from "./getToken";
export * from "./getUser";
diff --git a/frontend/src/api/npm/models.ts b/frontend/src/api/npm/models.ts
index b739ecd..4f043a1 100644
--- a/frontend/src/api/npm/models.ts
+++ b/frontend/src/api/npm/models.ts
@@ -94,7 +94,7 @@ export interface Host {
modifiedOn: number;
userId: number;
type: string;
- hostTemplateId: number;
+ nginxTemplateId: number;
listenInterface: number;
domainNames: string[];
upstreamId: number;
@@ -108,16 +108,15 @@ export interface Host {
hstsEnabled: boolean;
hstsSubdomains: boolean;
paths: string;
- upstreamOptions: string;
advancedConfig: string;
isDisabled: boolean;
}
-export interface HostTemplate {
+export interface NginxTemplate {
id: number;
createdOn: number;
modifiedOn: number;
userId: number;
- hostType: string;
+ type: string;
template: string;
}
diff --git a/frontend/src/api/npm/responseTypes.ts b/frontend/src/api/npm/responseTypes.ts
index a15276a..7d66204 100644
--- a/frontend/src/api/npm/responseTypes.ts
+++ b/frontend/src/api/npm/responseTypes.ts
@@ -3,7 +3,7 @@ import {
CertificateAuthority,
DNSProvider,
Host,
- HostTemplate,
+ NginxTemplate,
Setting,
Sort,
User,
@@ -53,6 +53,6 @@ export interface HostsResponse extends BaseResponse {
items: Host[];
}
-export interface HostTemplatesResponse extends BaseResponse {
- items: HostTemplate[];
+export interface NginxTemplatesResponse extends BaseResponse {
+ items: NginxTemplate[];
}
diff --git a/frontend/src/components/Navigation/NavigationMenu.tsx b/frontend/src/components/Navigation/NavigationMenu.tsx
index bc7575e..62edb3b 100644
--- a/frontend/src/components/Navigation/NavigationMenu.tsx
+++ b/frontend/src/components/Navigation/NavigationMenu.tsx
@@ -51,7 +51,16 @@ const navItems: NavItem[] = [
{
label: intl.formatMessage({ id: "hosts.title" }),
icon: ,
- to: "/hosts",
+ subItems: [
+ {
+ label: intl.formatMessage({ id: "hosts.title" }),
+ to: "/hosts",
+ },
+ {
+ label: intl.formatMessage({ id: "upstreams.title" }),
+ to: "/upstreams",
+ },
+ ],
},
{
label: intl.formatMessage({ id: "access-lists.title" }),
@@ -95,8 +104,8 @@ const navItems: NavItem[] = [
to: "/settings/general",
},
{
- label: intl.formatMessage({ id: "host-templates.title" }),
- to: "/settings/host-templates",
+ label: intl.formatMessage({ id: "nginx-templates.title" }),
+ to: "/settings/nginx-templates",
},
],
},
diff --git a/frontend/src/components/Permissions/PermissionSelector.tsx b/frontend/src/components/Permissions/PermissionSelector.tsx
index 146c782..45e7556 100644
--- a/frontend/src/components/Permissions/PermissionSelector.tsx
+++ b/frontend/src/components/Permissions/PermissionSelector.tsx
@@ -212,13 +212,13 @@ function PermissionSelector({
- {intl.formatMessage({ id: "host-templates.title" })}
+ {intl.formatMessage({ id: "nginx-templates.title" })}