feat(streams): Add multiple upstreams for basic load balancing

This commit is contained in:
Teagan glenn 2024-06-29 21:03:19 +00:00
parent 51414ced3a
commit f368985a60
9 changed files with 135 additions and 60 deletions

View File

@ -1,55 +1,65 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class Stream extends Model { class Stream extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for forwarding_hosts
if (typeof this.meta === 'undefined') { if (typeof this.forwarding_hosts === 'undefined') {
this.meta = {}; this.forwarding_hosts = [];
} }
}
$beforeUpdate () { // Default for meta
this.modified_on = now(); if (typeof this.meta === 'undefined') {
} this.meta = {};
}
}
static get name () { $beforeUpdate() {
return 'Stream'; this.modified_on = now();
}
static get tableName () { // Sort domain_names
return 'stream'; if (typeof this.forwarding_hosts !== 'undefined') {
} this.forwarding_hosts.sort();
}
}
static get jsonAttributes () { static get name() {
return ['meta']; return 'Stream';
} }
static get relationMappings () { static get tableName() {
return { return 'stream';
owner: { }
relation: Model.HasOneRelation,
modelClass: User, static get jsonAttributes() {
join: { return ['forwarding_hosts', 'meta'];
from: 'stream.owner_user_id', }
to: 'user.id'
}, static get relationMappings() {
modify: function (qb) { return {
qb.where('user.is_deleted', 0); owner: {
} relation: Model.HasOneRelation,
} modelClass: User,
}; join: {
} from: 'stream.owner_user_id',
to: 'user.id'
},
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
}
};
}
} }
module.exports = Stream; module.exports = Stream;

View File

@ -20,7 +20,7 @@
"minimum": 1, "minimum": 1,
"maximum": 65535 "maximum": 65535
}, },
"forwarding_host": { "host": {
"anyOf": [ "anyOf": [
{ {
"$ref": "../definitions.json#/definitions/domain_name" "$ref": "../definitions.json#/definitions/domain_name"
@ -35,6 +35,22 @@
} }
] ]
}, },
"forwarding_hosts": {
"anyOf": [
{
"$ref": "#/definitions/host"
},
{
"type": "array",
"minItems": 1,
"maxItems": 15,
"uniqueItems": true,
"items": {
"$ref": "#/definitions/host"
}
}
]
},
"forwarding_port": { "forwarding_port": {
"type": "integer", "type": "integer",
"minimum": 1, "minimum": 1,
@ -66,8 +82,8 @@
"incoming_port": { "incoming_port": {
"$ref": "#/definitions/incoming_port" "$ref": "#/definitions/incoming_port"
}, },
"forwarding_host": { "forwarding_hosts": {
"$ref": "#/definitions/forwarding_host" "$ref": "#/definitions/forwarding_hosts"
}, },
"forwarding_port": { "forwarding_port": {
"$ref": "#/definitions/forwarding_port" "$ref": "#/definitions/forwarding_port"
@ -118,15 +134,15 @@
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"incoming_port", "incoming_port",
"forwarding_host", "forwarding_hosts",
"forwarding_port" "forwarding_port"
], ],
"properties": { "properties": {
"incoming_port": { "incoming_port": {
"$ref": "#/definitions/incoming_port" "$ref": "#/definitions/incoming_port"
}, },
"forwarding_host": { "forwarding_hosts": {
"$ref": "#/definitions/forwarding_host" "$ref": "#/definitions/forwarding_hosts"
}, },
"forwarding_port": { "forwarding_port": {
"$ref": "#/definitions/forwarding_port" "$ref": "#/definitions/forwarding_port"
@ -165,8 +181,8 @@
"incoming_port": { "incoming_port": {
"$ref": "#/definitions/incoming_port" "$ref": "#/definitions/incoming_port"
}, },
"forwarding_host": { "forwarding_hosts": {
"$ref": "#/definitions/forwarding_host" "$ref": "#/definitions/forwarding_hosts"
}, },
"forwarding_port": { "forwarding_port": {
"$ref": "#/definitions/forwarding_port" "$ref": "#/definitions/forwarding_port"

View File

@ -3,6 +3,13 @@
# ------------------------------------------------------------ # ------------------------------------------------------------
{% if enabled %} {% if enabled %}
upstream stream_{{ incoming_port }}_tcp {
{% for forwarding_host in forwarding_hosts %}
server {{ forwarding_host }}:{{ forwarding_port }};
{%- endfor %}
}
{% if tcp_forwarding == 1 or tcp_forwarding == true -%} {% if tcp_forwarding == 1 or tcp_forwarding == true -%}
server { server {
listen {{ incoming_port }}; listen {{ incoming_port }};
@ -12,7 +19,7 @@ server {
#listen [::]:{{ incoming_port }}; #listen [::]:{{ incoming_port }};
{% endif %} {% endif %}
proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; proxy_pass stream_{{ incoming_port }}_tcp;
# Custom # Custom
include /data/nginx/custom/server_stream[.]conf; include /data/nginx/custom/server_stream[.]conf;
@ -20,14 +27,22 @@ server {
} }
{% endif %} {% endif %}
{% if udp_forwarding == 1 or udp_forwarding == true %} {% if udp_forwarding == 1 or udp_forwarding == true %}
upstream stream_{{ incoming_port }}_udp {
{% for forwarding_host in forwarding_hosts %}
server {{ forwarding_host }}:{{ forwarding_port }};
{%- endfor %}
}
server { server {
listen {{ incoming_port }} udp; listen {{ incoming_port }} udp;
{% if ipv6 -%} {% if ipv6 -%}
listen [::]:{{ incoming_port }} udp; listen [::]:{{ incoming_port }} udp;
{% else -%} {% else -%}
#listen [::]:{{ incoming_port }} udp; #listen [::]:{{ incoming_port }} udp;
{% endif %} {% endif %}
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
proxy_pass stream_{{ incoming_port }}_udp;
# Custom # Custom
include /data/nginx/custom/server_stream[.]conf; include /data/nginx/custom/server_stream[.]conf;

View File

@ -14,8 +14,8 @@
</div> </div>
<div class="col-sm-8 col-md-8"> <div class="col-sm-8 col-md-8">
<div class="form-group"> <div class="form-group">
<label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label> <label class="form-label"><%- i18n('streams', 'forwarding-hosts') %><span class="form-required">*</span></label>
<input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required> <input type="text" name="forwarding_hosts" class="form-control text-monospace" placeholder="10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" id="input-forwarding-hosts" value="<%- forwarding_hosts.join(',') %>" autocomplete="off" maxlength="255" required>
</div> </div>
</div> </div>
<div class="col-sm-4 col-md-4"> <div class="col-sm-4 col-md-4">

View File

@ -6,6 +6,8 @@ const template = require('./form.ejs');
require('jquery-serializejson'); require('jquery-serializejson');
require('jquery-mask-plugin'); require('jquery-mask-plugin');
require('selectize'); require('selectize');
const Helpers = require("../../../lib/helpers");
const certListItemTemplate = require("../certificates-list-item.ejs");
module.exports = Mn.View.extend({ module.exports = Mn.View.extend({
template: template, template: template,
@ -13,7 +15,7 @@ module.exports = Mn.View.extend({
ui: { ui: {
form: 'form', form: 'form',
forwarding_host: 'input[name="forwarding_host"]', forwarding_hosts: 'input[name="forwarding_hosts"]',
type_error: '.forward-type-error', type_error: '.forward-type-error',
buttons: '.modal-footer button', buttons: '.modal-footer button',
switches: '.custom-switch-input', switches: '.custom-switch-input',
@ -48,6 +50,10 @@ module.exports = Mn.View.extend({
data.tcp_forwarding = !!data.tcp_forwarding; data.tcp_forwarding = !!data.tcp_forwarding;
data.udp_forwarding = !!data.udp_forwarding; data.udp_forwarding = !!data.udp_forwarding;
if (typeof data.forwarding_hosts === 'string' && data.forwarding_hosts) {
data.forwarding_hosts = data.forwarding_hosts.split(',');
}
let method = App.Api.Nginx.Streams.create; let method = App.Api.Nginx.Streams.create;
let is_new = true; let is_new = true;
@ -76,6 +82,24 @@ module.exports = Mn.View.extend({
} }
}, },
onRender: function () {
let view = this;
// Domain names
this.ui.forwarding_hosts.selectize({
delimiter: ',',
persist: false,
maxOptions: 15,
create: function (input) {
return {
value: input,
text: input
};
},
createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/
});
},
initialize: function (options) { initialize: function (options) {
if (typeof options.model === 'undefined' || !options.model) { if (typeof options.model === 'undefined' || !options.model) {
this.model = new StreamModel.Model(); this.model = new StreamModel.Model();

View File

@ -12,7 +12,11 @@
</div> </div>
</td> </td>
<td> <td>
<div class="text-monospace"><%- forwarding_host %>:<%- forwarding_port %></div> <div class="wrap">
<% forwarding_hosts.map(function(host) { %>
<span class="tag host-link hover-green" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span>
<% }); %>
</div>
</td> </td>
<td> <td>
<div> <div>
@ -24,6 +28,11 @@
<% } %> <% } %>
</div> </div>
</td> </td>
<td>
<div class="text-monospace">
<%- forwarding_port %>
</div>
</td>
<td> <td>
<% <%
var o = isOnline(); var o = isOnline();

View File

@ -3,6 +3,7 @@
<th><%- i18n('streams', 'incoming-port') %></th> <th><%- i18n('streams', 'incoming-port') %></th>
<th><%- i18n('str', 'destination') %></th> <th><%- i18n('str', 'destination') %></th>
<th><%- i18n('streams', 'protocol') %></th> <th><%- i18n('streams', 'protocol') %></th>
<th><%- i18n('streams', 'forwarding-port') %></th>
<th><%- i18n('str', 'status') %></th> <th><%- i18n('str', 'status') %></th>
<% if (canManage) { %> <% if (canManage) { %>
<th>&nbsp;</th> <th>&nbsp;</th>

View File

@ -167,7 +167,7 @@
"add": "Add Stream", "add": "Add Stream",
"form-title": "{id, select, undefined{New} other{Edit}} Stream", "form-title": "{id, select, undefined{New} other{Edit}} Stream",
"incoming-port": "Incoming Port", "incoming-port": "Incoming Port",
"forwarding-host": "Forward Host", "forwarding-hosts": "Forward Hoss",
"forwarding-port": "Forward Port", "forwarding-port": "Forward Port",
"tcp-forwarding": "TCP Forwarding", "tcp-forwarding": "TCP Forwarding",
"udp-forwarding": "UDP Forwarding", "udp-forwarding": "UDP Forwarding",

View File

@ -9,7 +9,7 @@ const model = Backbone.Model.extend({
created_on: null, created_on: null,
modified_on: null, modified_on: null,
incoming_port: null, incoming_port: null,
forwarding_host: null, forwarding_hosts: [],
forwarding_port: null, forwarding_port: null,
tcp_forwarding: true, tcp_forwarding: true,
udp_forwarding: false, udp_forwarding: false,