and_cesbo icon

xProxy Web-UI mod

and_cesbo | PRO | 07/04/14 08:28:41 PM UTC | 0 ⭐ | 1623 👁️ | Never ⏰ | []
Lua |

22.98 KB

|

None

|

0 👍

/

0 👎

-- xProxy. Web-UI mod
-- http://cesbo.com/astra
--
-- Copyright (C) 2014, Andrey Dyldin <[email protected]>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- Usage:
-- Save into the /etc/astra/xproxy-mod.lua
-- Execute: astra --xproxy /etc/astra/xproxy-mod.lua
 
xproxy_config_path = "/etc/astra/xproxy-db.json"
 
function auth_request(client_id, request, auth_callback)
    if not request then
        return nil
    end
 
    function check_auth()
        if not request.query or not request.query.user then return false end
        for _,a in ipairs(xproxy_config.accounts) do
            if a.user == request.query.user then
                return (a.pass == request.query.pass)
            end
        end
        return false
    end
 
    if check_auth() then
        auth_callback(true)
    else
        auth_callback(false)
    end
end
 
--
 
channels = {}
xproxy_config = nil
function xproxy_config_load()
    if utils.stat(xproxy_config_path).type == "file" then
        xproxy_config = json.load(xproxy_config_path)
    end
    if not xproxy_config then
        json.save(xproxy_config_path, {
            options = {
                no_udp = true,
                no_rtp = true,
                no_http = true,
            },
            accounts = {},
            channels = {},
        })
        xproxy_config = json.load(xproxy_config_path)
        if xproxy_config == nil then
            log.error("[xProxy] failed to load " .. xproxy_config_path)
            astra.exit()
        end
    end
 
    for _,c in pairs(xproxy_config.channels) do channels[c.path] = c.source end
    if xproxy_config.options.auth and #xproxy_config.options.auth > 0 then
        xproxy_pass = "Basic " .. base64.encode(xproxy_config.options.auth)
    end
    if xproxy_config.options.no_udp == true then xproxy_allow_udp = false end
    if xproxy_config.options.no_rtp == true then xproxy_allow_rtp = false end
    if xproxy_config.options.no_http == true then xproxy_allow_http = false end
end
xproxy_config_load()
 
function render_stat_html()
    return [[<!DOCTYPE html>
 
<html lang="en" ng-app="App">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>xProxy : Web-UI</title>
 
    <link lazy-href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
    <link lazy-href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" />
 
    <style type="text/css">
* { box-sizing: border-box; }
html { height: 100%; }
body { height: 100%; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #333333; }
a:not([href]) { cursor: pointer; }
 
.xproxy-nav { padding: 10px 15px; }
.wrap { min-height: 100%; }
.content { overflow: visible; padding-top: 5px; padding-bottom: 25px; }
.footer { position: relative; height: 20px; margin-top: -20px; width: 100%; padding: 0 15px; }
.footer span { font-size: 0.8em; color: #bbb; display: inline-block; width: 50%; padding: 0 15px; }
.footer .version { text-align: right; }
 
.app-status { position: absolute; z-index: 10000; top: 0; right: 0; bottom: 0; left: 0; padding: 20px; background: #ffffff; }
    </style>
</head>
 
<body>
<div class="app-status" ng-hide="version">Loading...</div>
 
<div class="wrap">
<nav class="xproxy-nav container-fluid"><div class="row">
    <div class="col-sm-3">
    </div>
    <div class="col-sm-offset-3 col-sm-6" style="white-space:nowrap;text-align:right;">
        <a href="#/stat" class="btn btn-default" style="width:120px;" ng-class="{'active':controller==='StatController'}">Connections</a>
        <a href="#/options" class="btn btn-default" style="width:120px;" ng-class="{'active':controller==='OptionsController'}">Options</a>
        <a href="#/accounts" class="btn btn-default" style="width:120px;" ng-class="{'active':controller==='AccountsController'}">Accounts</a>
        <a href="#/channels" class="btn btn-default" style="width:120px;" ng-class="{'active':controller==='ChannelsController'}">Channels</a>
    </div>
</div></nav>
<div class="container-fluid content" ng-view></div>
</div> <!-- wrap -->
 
<footer class="footer">
    <span class="copyright">&copy; 2014 Cesbo Ltd. All rights reserved.</span><span class="version" ng-bind="version"></span>
</footer>
</body>
 
<script type="text/ng-template" id="stat.html">
<div class="row"><div class="col-sm-offset-2 col-sm-8">
<table class="table"><thead><tr>
    <th width="100px">Client ID</th>
    <th width="150px">IP</th>
    <th>Source</th>
    <th width="100px">Uptime</th>
    <th width="100px"></th>
</tr></thead><tbody>
    <tr ng-repeat="c in stat">
        <td ng-bind="c.id"></td>
        <td ng-bind="c.addr"></td>
        <td ng-bind="c.path"></td>
        <td ng-bind="c.uptime"></td>
        <td align="right"><a ng-click="disconnect(c.id)">Disconnect</a></td>
    </tr>
    <tr><td colspan="5"><a ng-click="refresh()">Refresh Connections List</a></td></tr>
</tbody></table>
</div></div>
</script>
 
<script type="text/ng-template" id="options.html">
<div class="row"><div class="col-sm-offset-3 col-sm-6">
<div class="form-horizontal">
    <div class="form-group">
        <label class="col-sm-3 control-label">Basic Authorization</label>
        <div class="col-sm-6"><input type="text" class="form-control" ng-model="xproxy.options.auth" placeholder="Login:Password" /></div>
    </div>
 
    <div class="form-group">
        <label class="col-sm-3 control-label">Disable UDP proxy</label>
        <div class="col-sm-6"><label class="checkbox"><input type="checkbox" ng-model="xproxy.options.no_udp" />&nbsp;<span style="color:#737373;font-weight:normal;">http://server/udp/*</span></label></div>
    </div>
    <div class="form-group">
        <label class="col-sm-3 control-label">Disable RTP proxy</label>
        <div class="col-sm-6"><label class="checkbox"><input type="checkbox" ng-model="xproxy.options.no_rtp" />&nbsp;<span style="color:#737373;font-weight:normal;">http://server/rtp/*</span></label></div>
    </div>
    <div class="form-group">
        <label class="col-sm-3 control-label">Disable HTTP proxy</label>
        <div class="col-sm-6"><label class="checkbox"><input type="checkbox" ng-model="xproxy.options.no_http" />&nbsp;<span style="color:#737373;font-weight:normal;">http://server/http/*</span></label></div>
    </div>
 
    <hr />
    <div class="form-group">
        <div class="col-sm-offset-3 col-sm-6">
            <button class="btn btn-primary" style="width:100px;" ng-click="options_save()">Save</button>
            <button class="btn btn-danger" style="width:100px;" ng-click="restart()">Restart</button>
        </div>
    </div>
</div>
</div></div>
</script>
 
<script type="text/ng-template" id="accounts.html">
<div class="row"><div class="col-sm-offset-2 col-sm-8"><table class="table">
    <thead>
        <tr>
            <th>User Name</th>
            <th>Password</th>
            <th width="120px"></th>
        </tr>
    </thead><tbody>
        <tr>
            <td><input type="text" class="form-control" ng-model="account_new.user" /></td>
            <td><input type="text" class="form-control" ng-model="account_new.pass" /></td>
            <td><button class="btn btn-success" ng-click="account_save(0)"><i class="fa fa-plus fa-lg fa-fw"></i></button></td>
        </tr>
        <tr ng-repeat="(i,a) in xproxy.accounts">
            <td><input type="text" class="form-control" ng-model="a.user" /></td>
            <td><input type="text" class="form-control" ng-model="a.pass" /></td>
            <td>
                <button class="btn btn-success" ng-click="account_save(i + 1)"><i class="fa fa-check fa-lg fa-fw"></i></button>
                <button class="btn btn-danger" ng-click="account_delete(i + 1)"><i class="fa fa-times fa-lg fa-fw"></i></button>
            </td>
        </tr>
    </tbody>
</table></div></div>
</script>
 
<script type="text/ng-template" id="channels.html">
<div class="row"><div class="col-sm-offset-2 col-sm-8"><table class="table">
    <thead>
        <tr>
            <th>Source</th>
            <th>Channel ID</th>
            <th width="120px"></th>
        </tr>
    </thead><tbody>
        <tr>
            <td><input type="text" class="form-control" ng-model="channel_new.source" /></td>
            <td><input type="text" class="form-control" ng-model="channel_new.path" /></td>
            <td><button class="btn btn-success" ng-click="channel_save(0)"><i class="fa fa-plus fa-lg fa-fw"></i></button></td>
        </tr>
        <tr ng-repeat="(i,c) in xproxy.channels">
            <td><input type="text" class="form-control" ng-model="c.source" /></td>
            <td><input type="text" class="form-control" ng-model="c.path" /></td>
            <td>
                <button class="btn btn-success" ng-click="channel_save(i + 1)"><i class="fa fa-check fa-lg fa-fw"></i></button>
                <button class="btn btn-danger" ng-click="channel_delete(i + 1)"><i class="fa fa-times fa-lg fa-fw"></i></button>
            </td>
        </tr>
    </tbody>
</table></div></div>
</script>
 
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular-route.js"></script>
 
<script type="text/javascript">
var App = angular.module("App", ["ngRoute"]);
 
App.directive("lazyHref", function() {
    return {
        restrict: "A",
        transclude: true,
        link: function (scope, element, attrs) {
            element.attr("type", "text/css");
            element.attr("rel", "stylesheet");
            element.attr("href", attrs.lazyHref);
            element.removeAttr("lazy-href");
        }
    };
});
 
App.config(["$routeProvider", function($routeProvider) {
    $routeProvider
        .when("/stat", {
            templateUrl: "stat.html",
            controller: "StatController"
        })
        .when("/options", {
            templateUrl: "options.html",
            controller: "OptionsController"
        })
        .when("/accounts", {
            templateUrl: "accounts.html",
            controller: "AccountsController"
        })
        .when("/channels", {
            templateUrl: "channels.html",
            controller: "ChannelsController"
        })
        .otherwise({
            redirectTo: '/stat'
        });;
}]);
 
App.run(["$rootScope",
         "$http",
         function($rootScope, $http)
{
    angular.element(document).ready(function() {
        $http
            .post('/stat/', { 'cmd': 'load' })
            .success(function(data) {
                $rootScope.version = "Astra v." + data.version;
                $rootScope.xproxy = data.xproxy;
                if($rootScope.xproxy.accounts === undefined) $rootScope.xproxy.accounts = [];
                if($rootScope.xproxy.channels === undefined) $rootScope.xproxy.channels = [];
            });
    });
}]);
 
App.controller('AccountsController', ['$rootScope',
                                      '$scope',
                                      '$http',
                                      function($rootScope, $scope, $http)
{
    $rootScope.controller = 'AccountsController';
    $scope.account_new = {};
 
    $scope.account_save = function(i) {
        var a = (i === 0) ? $scope.account_new : $rootScope.xproxy.accounts[i - 1];
        var e = false;
        if(a.user.length === 0) {
            a.user = "ERROR";
            e = true;
        }
        if(a.pass.length === 0) {
            a.pass = "ERROR";
            e = true;
        }
        if(e === true) return;
        $http
            .post('/stat/', {
                'cmd': 'save',
                'scope': 'account',
                'id': i,
                'account': a
            })
            .success(function(data) {
                if(i === 0 && data.success) $rootScope.xproxy.accounts.push(data.success);
            });
 
        $scope.account_new = {};
    };
 
    $scope.account_delete = function(i) {
        $http
            .post('/stat/', {
                'cmd': 'delete',
                'scope': 'account',
                'id': i
            })
            .success(function(data) {
                if(data.success) $rootScope.xproxy.accounts.splice(i - 1, 1);
            });
    };
}]);
 
App.controller('ChannelsController', ['$rootScope',
                                      '$scope',
                                      '$http',
                                      function($rootScope, $scope, $http)
{
    $rootScope.controller = 'ChannelsController';
    $scope.channel_new = {};
 
    $scope.channel_save = function(i) {
        var c = (i === 0) ? $scope.channel_new : $rootScope.xproxy.channels[i - 1];
        var e = false;
        if(c.source.length === 0) {
            c.source = "ERROR";
            e = true;
        }
        if(c.path.length === 0) {
            c.path = "ERROR";
            e = true;
        }
        if(e === true) return;
        $http
            .post('/stat/', {
                'cmd': 'save',
                'scope': 'channel',
                'id': i,
                'channel': c
            })
            .success(function(data) {
                if(i === 0 && data.success) $rootScope.xproxy.channels.push(data.success);
            });
 
        $scope.channel_new = {};
    };
 
    $scope.channel_delete = function(i) {
        $http
            .post('/stat/', {
                'cmd': 'delete',
                'scope': 'channel',
                'id': i
            })
            .success(function(data) {
                if(data.success) $rootScope.xproxy.channels.splice(i - 1, 1);
            });
    };
}]);
 
App.controller('OptionsController', ['$rootScope',
                                     '$scope',
                                     '$http',
                                     function($rootScope, $scope, $http)
{
    $rootScope.controller = 'OptionsController';
 
    $scope.options_save = function() {
        $http
            .post('/stat/', {
                'cmd': 'save',
                'scope': 'options',
                'options': $rootScope.xproxy.options
            })
            .success(function(data) {
                //
            });
    };
 
    $scope.restart = function() {
        $http
            .post('/stat/', {
                'cmd': 'restart',
                'scope': 'options',
            })
            .success(function(data) {
                //
            });
    };
}]);
 
App.controller('StatController', ['$rootScope',
                                  '$scope',
                                  '$http',
                                  function($rootScope, $scope, $http)
{
    $rootScope.controller = 'StatController';
 
    $scope.refresh = function() {
        $http
            .post('/stat/', { 'cmd': 'stat' })
            .success(function(data) {
                $scope.stat = (data.stat === undefined) ? [] : data.stat;
            });
    };
 
    $scope.disconnect = function(id) {
        $http
            .post('/stat/', { 'cmd': 'disconnect', 'id': id })
            .success(function(data) {
                for(var i = 0; i < $scope.stat.length; ++i) {
                    if($scope.stat[i].id === id) {
                        $scope.stat.splice(i, 1);
                        return;
                    }
                }
            });
    };
 
    $scope.refresh();
}]);
</script>
</html>
]]
end
 
function on_http_stat(server, client, request)
    if not request then return nil end
 
    if not xproxy_config then
        server:abort(client, 404, "Access List is not defined")
        return nil
    end
 
    if xproxy_pass then
        if request.headers["authorization"] ~= xproxy_pass then
            server:send(client, {
                code = 401,
                headers = {
                    "WWW-Authenticate: Basic realm=\"xProxy\"",
                    "Content-Length: 0",
                    "Connection: close",
                }
            })
            return
        end
    end
 
    if request.method == "POST" then
        local data = json.decode(request.content)
        if not data then
            server:abort(client, 400)
        elseif data.cmd == "load" then
            local r = { version = astra.version, xproxy = {}, }
            r.xproxy.options = xproxy_config.options
            if #xproxy_config.accounts > 0 then r.xproxy.accounts = xproxy_config.accounts end
            if #xproxy_config.channels > 0 then r.xproxy.channels = xproxy_config.channels end
            server:send(client, {
                code = 200,
                headers = { "Content-Type: application/json", "Connection: close", },
                content = json.encode(r),
            })
        elseif data.cmd == "stat" then
            local ct = os.time()
            local l = {}
            for i,c in pairs(client_list) do
                local dt = ct - c.st
                local uptime = string.format("%02d:%02d", (dt / 3600), (dt / 60) % 60)
                table.insert(l, {
                    id = i,
                    addr = c.addr,
                    path = c.path,
                    uptime = uptime,
                })
            end
            local r = {}
            if #l > 0 then r.stat = l end
            server:send(client, {
                code = 200,
                headers = { "Content-Type: application/json", "Connection: close", },
                content = json.encode(r),
            })
        elseif data.cmd == "disconnect" then
            local client_id = tonumber(data.id)
            if client_list[client_id] then
                server:close(client_list[client_id].client)
            end
            server:send(client, {
                code = 200,
                headers = { "Content-Type: application/json", "Connection: close", },
                content = json.encode({ success = true })
            })
        elseif data.cmd == "restart" then
            timer({
                interval = 1,
                callback = function(self)
                    self:close()
                    astra.reload()
                end,
            })
            server:send(client, {
                code = 200,
                headers = { "Content-Type: application/json", "Connection: close", },
                content = json.encode({ success = true })
            })
        elseif data.cmd == "save" then
            if data.scope == "options" then
                xproxy_config.options = data.options
                xproxy_pass = "Basic " .. base64.encode(xproxy_config.options.auth)
                json.save(xproxy_config_path, xproxy_config)
                server:send(client, {
                    code = 200,
                    headers = { "Content-Type: application/json", "Connection: close", },
                    content = json.encode({ success = true })
                })
            elseif data.scope == "account" then
                local id = tonumber(data.id)
                if id == nil then
                    server:abort(client, 400)
                    return nil
                end
                if id == 0 then
                    table.insert(xproxy_config.accounts, data.account)
                else
                    xproxy_config.accounts[id] = data.account
                end
                json.save(xproxy_config_path, xproxy_config)
                server:send(client, {
                    code = 200,
                    headers = { "Content-Type: application/json", "Connection: close", },
                    content = json.encode({ success = data.account })
                })
            elseif data.scope == "channel" then
                local id = tonumber(data.id)
                if id == nil then
                    server:abort(client, 400)
                    return nil
                end
                if id == 0 then
                    table.insert(xproxy_config.channels, data.channel)
                    channels[data.channel.path] = data.channel.source
                else
                    local c = xproxy_config.channels[id]
                    channels[c.path] = nil
                    channels[data.channel.path] = data.channel.source
                    xproxy_config.channels[id] = data.channel
                end
                json.save(xproxy_config_path, xproxy_config)
                server:send(client, {
                    code = 200,
                    headers = { "Content-Type: application/json", "Connection: close", },
                    content = json.encode({ success = data.channel })
                })
            else
                server:abort(client, 400)
            end
        elseif data.cmd == "delete" then
            if data.scope == "account" then
                local id = tonumber(data.id)
                if id == nil then
                    server:abort(client, 400)
                    return nil
                end
                table.remove(xproxy_config.accounts, id)
                json.save(xproxy_config_path, xproxy_config)
                server:send(client, {
                    code = 200,
                    headers = { "Content-Type: application/json", "Connection: close", },
                    content = json.encode({ success = true })
                })
            elseif data.scope == "channel" then
                local id = tonumber(data.id)
                if id == nil then
                    server:abort(client, 400)
                    return nil
                end
                local c = xproxy_config.channels[id]
                channels[c.path] = nil
                table.remove(xproxy_config.channels, id)
                json.save(xproxy_config_path, xproxy_config)
                server:send(client, {
                    code = 200,
                    headers = { "Content-Type: application/json", "Connection: close", },
                    content = json.encode({ success = true })
                })
            else
                server:abort(client, 400)
            end
        else
            server:abort(client, 400)
        end
        return nil
    end
 
    server:send(client, {
        code = 200,
        headers = { "Content-Type: text/html; charset=utf-8", "Connection: close", },
        content = render_stat_html(),
    })
end

Comments