Guest User

Untitled

a guest
Nov 7th, 2016
34
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 43.90 KB | None | 0 0
  1. -- xProxy. Web-UI mod
  2. -- http://cesbo.com/astra
  3. --
  4. -- Copyright (C) 2014-2015, Andrey Dyldin <and@cesbo.com>
  5. --
  6. -- This program is free software: you can redistribute it and/or modify
  7. -- it under the terms of the GNU General Public License as published by
  8. -- the Free Software Foundation, either version 3 of the License, or
  9. -- (at your option) any later version.
  10. --
  11. -- This program is distributed in the hope that it will be useful,
  12. -- but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. -- GNU General Public License for more details.
  15. --
  16. -- You should have received a copy of the GNU General Public License
  17. -- along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. --
  19. -- Usage:
  20. -- Save into the /etc/astra/xproxy-ui.lua
  21. -- Execute: astra --relay /etc/astra/xproxy-ui.lua
  22.  
  23. xproxy_config_path = "/etc/astra/xproxy-db.json"
  24.  
  25. function xproxy_init_client(server, client, request, path)
  26. local client_data = server:data(client)
  27.  
  28. local client_id = client_data.client_id
  29. if client_id then return nil end
  30.  
  31. repeat
  32. client_id = math.random(10000000, 99000000)
  33. until not client_list[client_id]
  34.  
  35. local p = path:sub(2)
  36.  
  37. client_list[client_id] = {
  38. server = server,
  39. client = client,
  40. addr = request.addr,
  41. path = p,
  42. name = channel_names[p],
  43. st = os.time(),
  44. }
  45.  
  46. client_data.client_id = client_id
  47. end
  48.  
  49. client_addr_list = {}
  50. client_name_list = {}
  51.  
  52. function check_account(user, pass, callback)
  53. if not user or not pass then
  54. callback(false)
  55. return nil
  56. end
  57.  
  58. local as = xproxy_config.options.authserver
  59. if as ~= nil and as ~= "" then
  60. local opts = parse_url(as)
  61. opts.content = json.encode({ cmd = "auth", user = user, pass = pass })
  62. opts.method = "POST"
  63. opts.headers = {
  64. "User-Agent: Astra [xproxy-ui]",
  65. "Host: " .. opts.host .. ":" .. opts.port,
  66. "Content-Type: application/json",
  67. "Content-Length: " .. #opts.content,
  68. "Connection: close"
  69. }
  70. if opts.login and opts.password then
  71. local token = (opts.login .. ":" .. opts.password):b64e()
  72. table.insert(opts.headers, "Authorization: Basic " .. token)
  73. end
  74. opts.callback = function(request, response)
  75. callback(response.code == 200)
  76. end
  77. http_request(opts)
  78. return nil
  79. end
  80.  
  81. local c = os.time()
  82.  
  83. for _,a in ipairs(xproxy_config.accounts) do
  84. if a.user == user then
  85. if a.pass ~= pass then
  86. break
  87. end
  88. if a.enable == false then
  89. break
  90. end
  91. if not a.exp then
  92. break
  93. end
  94. local y, m, d = a.exp:match("(%d+)-(%d+)-(%d+)")
  95. local e = os.time({ year = tonumber(y), month = tonumber(m), day = tonumber(d) })
  96. if e <= c then
  97. break
  98. end
  99.  
  100. callback(true)
  101. return nil
  102. end
  103. end
  104.  
  105. callback(false)
  106. end
  107.  
  108. function auth_request(client_id, request, auth_callback)
  109. if not request then
  110. if xproxy_config.options.check_ip then
  111. local a = client_list[client_id].addr
  112. if a then client_addr_list[a] = nil end
  113. end
  114. if xproxy_config.options.check_name then
  115. local a = client_list[client_id].user
  116. if a then client_name_list[a] = nil end
  117. end
  118. return nil
  119. end
  120.  
  121. local path = request.path:sub(2) -- skip '/'
  122. if path == xproxy_config.options.access_denied_id then
  123. auth_callback(true)
  124. return nil
  125. end
  126.  
  127. local user = nil
  128. local pass = nil
  129.  
  130. if request.query then
  131. if request.query.auth then
  132. local b = request.query.auth:find(" ")
  133. if b then
  134. user = request.query.auth:sub(1, b - 1)
  135. pass = request.query.auth:sub(b + 1)
  136. end
  137. else
  138. user = request.query.user
  139. if request.query.login then user = request.query.login end
  140. pass = request.query.pass
  141. end
  142. end
  143.  
  144. local function check_ip()
  145. if xproxy_config.options.check_ip then
  146. local cc = client_addr_list[request.addr]
  147. if cc then
  148. local c = client_list[cc]
  149. c.server:close(c.client)
  150. end
  151. client_addr_list[request.addr] = client_id
  152. end
  153. end
  154.  
  155. local function check_name()
  156. if xproxy_config.options.check_name then
  157. local cc = client_name_list[user]
  158. if cc then
  159. local c = client_list[cc]
  160. c.server:close(c.client)
  161. end
  162. client_name_list[user] = client_id
  163. end
  164.  
  165. client_list[client_id].user = user
  166. end
  167.  
  168. local function check_result(result)
  169. if not result then
  170. if xproxy_config.options.access_denied_id then
  171. request.redirect = xproxy_config.options.access_denied_id
  172. auth_callback(true)
  173. else
  174. auth_callback(false)
  175. end
  176. else
  177. check_ip()
  178. check_name()
  179. auth_callback(true)
  180. end
  181. end
  182.  
  183. check_account(user, pass, check_result)
  184. end
  185.  
  186. function on_request_channel(server, client, request)
  187. local client_data = server:data(client)
  188.  
  189. if not request then -- on_close
  190. kill_input(client_data.input)
  191. xproxy_kill_client(server, client)
  192. collectgarbage()
  193. return nil
  194. end
  195.  
  196. local path = request.path:sub(2) -- skip '/'
  197. local channel = channels[path]
  198. if not channel then channel = channels["*"] end
  199. if not channel then
  200. server:abort(client, 404)
  201. return nil
  202. end
  203. local conf = parse_url(channel)
  204. if not conf then
  205. server:abort(client, 404)
  206. return nil
  207. end
  208.  
  209. xproxy_init_client(server, client, request, request.path)
  210.  
  211. local allow_channel = function()
  212. if request.redirect then
  213. channel = channels[request.redirect]
  214. if not channel then
  215. server:abort(client, 404)
  216. return nil
  217. end
  218. conf = parse_url(channel)
  219. if not conf then
  220. server:abort(client, 404)
  221. return nil
  222. end
  223. end
  224. conf.name = "Relay " .. client_data.client_id
  225. client_data.input = init_input(conf)
  226. server:send(client, {
  227. upstream = client_data.input.tail:stream(),
  228. buffer_size = relay_buffer_size,
  229. buffer_fill = relay_buffer_fill,
  230. })
  231. end
  232.  
  233. do_auth_request(server, client, request, allow_channel)
  234. end
  235.  
  236. --
  237.  
  238. channels = {}
  239. channel_names = {}
  240. xproxy_config = nil
  241. function xproxy_config_load()
  242. if utils.stat(xproxy_config_path).type == "file" then
  243. xproxy_config = json.load(xproxy_config_path)
  244. end
  245. if not xproxy_config then
  246. json.save(xproxy_config_path, {
  247. options = {
  248. no_udp = true,
  249. no_http = true,
  250. },
  251. accounts = {},
  252. channels = {},
  253. })
  254. xproxy_config = json.load(xproxy_config_path)
  255. if xproxy_config == nil then
  256. log.error("[xProxy] failed to load " .. xproxy_config_path)
  257. astra.exit()
  258. end
  259. end
  260.  
  261. for _,a in pairs(xproxy_config.accounts) do
  262. if a.enable == nil then
  263. a.enable = true
  264. end
  265. end
  266.  
  267. for _,c in pairs(xproxy_config.channels) do
  268. if c.status then
  269. c.enable = (c.status == "active")
  270. c.status = nil
  271. end
  272. if c.enable then
  273. channels[c.path] = c.source
  274. channel_names[c.path] = c.name
  275. end
  276. end
  277. if xproxy_config.options.auth then
  278. local a = split(xproxy_config.options.auth, ":")
  279. xproxy_config.options.login = a[1]
  280. xproxy_config.options.pass = a[2]
  281. xproxy_config.options.auth = nil
  282. end
  283. if xproxy_config.options.login and #xproxy_config.options.login > 0 then
  284. xproxy_pass = "Basic " .. base64.encode(xproxy_config.options.login .. ":" .. xproxy_config.options.pass)
  285. end
  286. if xproxy_config.options.no_udp == true then
  287. xproxy_allow_udp = false
  288. relay_allow_udp = false
  289. end
  290. if xproxy_config.options.no_http == true then
  291. xproxy_allow_http = false
  292. relay_allow_http = false
  293. end
  294. end
  295. xproxy_config_load()
  296.  
  297. function render_stat_html()
  298. return [[<!DOCTYPE html>
  299.  
  300. <html lang="en" ng-app="App">
  301. <head>
  302. <meta charset="utf-8" />
  303. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  304. <title>xProxy : Web-UI</title>
  305.  
  306. <link lazy-href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
  307. <link lazy-href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" />
  308.  
  309. <style type="text/css">
  310. * { box-sizing: border-box; }
  311. html { height: 100%; }
  312. body { height: 100%; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #333333; }
  313. a:not([href]) { cursor: pointer; }
  314.  
  315. .xproxy-nav { padding: 10px 15px; }
  316. .wrap { min-height: 100%; }
  317. .content { overflow: visible; padding-top: 5px; padding-bottom: 25px; }
  318. .footer { position: relative; height: 20px; margin-top: -20px; width: 100%; padding: 0 15px; }
  319. .footer span { font-size: 0.8em; color: #bbb; display: inline-block; width: 50%; padding: 0 15px; }
  320. .footer .version { text-align: right; }
  321.  
  322. .app-status { position: fixed; z-index: 10000; top: 0; right: 0; bottom: 0; left: 0; padding: 20px; background: #ffffff; }
  323.  
  324. .options-group>div { padding-left: 5px; padding-right: 5px; }
  325. .options-group>div:first-child { padding-left: 15px; }
  326. .options-group>div:last-child { padding-right: 15px; }
  327.  
  328. .quickdate { display: block; position: relative; vertical-align: bottom; font-size: 15px; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; }
  329. .quickdate-button { background: #ffffff; color: #333333; font-size: 14px; border: solid 1px #cccccc; box-shadow: outset 0 1px 1px rgba(0, 0, 0, 0.075); border-radius: 4px; padding: 6px 12px; display: block; text-decoration: none; width: 100%; height: 34px; }
  330. .quickdate-button:hover i { text-decoration: none; }
  331. .quickdate-button i { padding-right: 4px; }
  332. .quickdate-button div, .quickdate-action-link div { display: inline; }
  333. .quickdate-popup { z-index: 10; border: solid 1px #000; text-align: center; width: 250px; display: none; position: absolute; padding: 5px; color: #333333; font-size: 15px; background-color: #fafafa; border: solid 1px #dddddd; border-radius: 3px; -webkit-box-shadow: 0px 10px 30px rgba(25, 25, 25, 0.92); -moz-box-shadow: 0px 10px 30px rgba(25, 25, 25, 0.92); box-shadow: 0px 10px 30px rgba(25, 25, 25, 0.92); }
  334. .quickdate-popup.open { display: block; }
  335. .quickdate-close { display: none; }
  336. .quickdate-calendar-header { display: block; padding: 2px 0; margin-bottom: 5px; text-align: center; }
  337. .quickdate-month { display: inline-block; }
  338. a.quickdate-prev-month { float: left; }
  339. a.quickdate-next-month { float: right; }
  340. .quickdate-text-inputs { display: none; }
  341. input.quickdate-date-input, input.quickdate-time-input { width: 100px; margin: 0; height: auto; padding: 2px 3px; }
  342. table.quickdate-calendar { border-collapse: collapse; border-spacing: 0; width: 100%; margin-top: 5px; }
  343. table.quickdate-calendar th, table.quickdate-calendar td { padding: 5px; }
  344. table.quickdate-calendar td:hover { cursor: pointer; }
  345. .quickdate input.ng-invalid { border: 1px solid #dd3b30; }
  346. .quickdate input.ng-invalid:focus { outline-color: #dd3b30; }
  347.  
  348.  
  349. .quickdate-action-link:visited, .quickdate-action-link:hover { color: #333333; }
  350. .quickdate-next-month i { padding-left: 10px; }
  351. .quickdate-prev-month i { padding-right: 10px; }
  352. table.quickdate-calendar { border: solid 1px #ccc; background-color: #ffffff; }
  353. table.quickdate-calendar th, table.quickdate-calendar td { border-right: 1px solid #ccc; border-bottom: 1px solid #ccc; }
  354. table.quickdate-calendar th { font-size: 10px; }
  355. table.quickdate-calendar td:hover { background-color: #e6e6e6; }
  356. table.quickdate-calendar td.other-month { background-color: #dbdbdb; color: #808080; }
  357. table.quickdate-calendar td.other-month:hover { background-color: #c7c7c7; }
  358. table.quickdate-calendar td.disabled-date { background-color: inherit; color: #ffffff; }
  359. table.quickdate-calendar td.disabled-date:hover { background-color: inherit; cursor: default; }
  360. table.quickdate-calendar td.selected { background-color: #b0ccde; font-weight: bold; }
  361. table.quickdate-calendar td.is-today { color: #b58922; font-weight: bold; }
  362. table.quickdate-calendar td.is-today.disabled-date { color: #929292; font-weight: normal; }
  363.  
  364. table.inline-form input[type=checkbox] { margin: 0; height: 34px; width: 100%; }
  365. .expired input, .expired .quickdate-button { border: 1px solid #d9534f; }
  366. </style>
  367. </head>
  368.  
  369. <body>
  370. <div class="app-status" ng-hide="version">Loading...</div>
  371.  
  372. <div class="wrap">
  373. <nav class="xproxy-nav container-fluid"><div class="row">
  374. <div class="col-sm-3">
  375. </div>
  376. <div class="col-sm-offset-3 col-sm-6" style="white-space:nowrap;text-align:right;">
  377. <a href="#/stat" class="btn btn-default" style="width:120px;" ng-class="{'active':controller==='StatController'}">Connections</a>
  378. <a href="#/options" class="btn btn-default" style="width:120px;" ng-class="{'active':controller==='OptionsController'}">Options</a>
  379. <a href="#/accounts" class="btn btn-default" style="width:120px;" ng-class="{'active':controller==='AccountsController'}">Accounts</a>
  380. <a href="#/channels" class="btn btn-default" style="width:120px;" ng-class="{'active':controller==='ChannelsController'}">Channels</a>
  381. </div>
  382. </div></nav>
  383. <div class="container-fluid content" ng-view></div>
  384. </div> <!-- wrap -->
  385.  
  386. <footer class="footer">
  387. <span class="copyright">&copy; 2014 Cesbo Ltd. All rights reserved.</span><span class="version" ng-bind="version"></span>
  388. </footer>
  389. </body>
  390.  
  391. <script type="text/ng-template" id="stat.html">
  392. <div class="row"><div class="col-sm-offset-2 col-sm-8">
  393. <table class="table"><thead><tr>
  394. <th width="100px">Session ID</th>
  395. <th width="100px">User Name</th>
  396. <th width="150px">IP</th>
  397. <th>Channel</th>
  398. <th width="100px">Uptime</th>
  399. <th width="100px"></th>
  400. </tr></thead><tbody>
  401. <tr ng-repeat="c in stat">
  402. <td ng-bind="c.id"></td>
  403. <td ng-bind="c.user"></td>
  404. <td ng-bind="c.addr"></td>
  405. <td ng-bind="c.name"></td>
  406. <td ng-bind="c.uptime"></td>
  407. <td align="right"><a ng-click="disconnect(c.id)">Disconnect</a></td>
  408. </tr>
  409. <tr><td colspan="6"><a ng-click="refresh()">Refresh Connections List</a></td></tr>
  410. </tbody></table>
  411. </div></div>
  412. </script>
  413.  
  414. <script type="text/ng-template" id="options.html">
  415. <div class="row"><div class="col-sm-offset-2 col-sm-8">
  416. <div class="form-horizontal">
  417. <div class="form-group">
  418. <label class="col-sm-3 control-label">Basic Authorization</label>
  419. <div class="col-sm-6"><div class="row options-group">
  420. <div class="col-sm-6"><input type="text" class="form-control" ng-model="xproxy.options.login" placeholder="Login" /></div>
  421. <div class="col-sm-6"><input type="password" class="form-control" ng-model="xproxy.options.pass" placeholder="Password" /></div>
  422. </div></div>
  423. </div>
  424.  
  425. <div class="form-group">
  426. <label class="col-sm-3 control-label">Disable UDP proxy</label>
  427. <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>
  428. </div>
  429. <div class="form-group">
  430. <label class="col-sm-3 control-label">Disable HTTP proxy</label>
  431. <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>
  432. </div>
  433. <div class="form-group">
  434. <label class="col-sm-3 control-label">Check client IP</label>
  435. <div class="col-sm-6"><label class="checkbox"><input type="checkbox" ng-model="xproxy.options.check_ip" />&nbsp;<span style="color:#737373;font-weight:normal;">Prevent to use one account from different IP</span></label></div>
  436. </div>
  437. <div class="form-group">
  438. <label class="col-sm-3 control-label">Check client Name</label>
  439. <div class="col-sm-6"><label class="checkbox"><input type="checkbox" ng-model="xproxy.options.check_name" />&nbsp;<span style="color:#737373;font-weight:normal;">Prevent to use one account twice</span></label></div>
  440. </div>
  441. <div class="form-group">
  442. <label class="col-sm-3 control-label">Auth Server</label>
  443. <div class="col-sm-6">
  444. <input type="text" class="form-control" ng-model="xproxy.options.authserver" placeholder="http://..." />
  445. <p class="help-block">Send authentication reuqest to the external server</p>
  446. </div>
  447. </div>
  448. <div class="form-group">
  449. <label class="col-sm-3 control-label">Access Denied Channel ID</label>
  450. <div class="col-sm-6">
  451. <input type="text" class="form-control" ng-model="xproxy.options.access_denied_id" placeholder="" />
  452. <p class="help-block">Show this channel if authentication has failed</p>
  453. </div>
  454. </div>
  455.  
  456. <hr />
  457. <div class="form-group">
  458. <div class="col-sm-offset-3 col-sm-6">
  459. <button class="btn btn-primary" style="width:100px;" ng-click="options_save()">Save</button>
  460. <button class="btn btn-danger" style="width:100px;" ng-click="restart()">Restart</button>
  461. </div>
  462. </div>
  463. </div>
  464. </div></div>
  465. </script>
  466.  
  467. <script type="text/ng-template" id="accounts.html">
  468. <div class="row"><div class="col-sm-offset-2 col-sm-8"><table class="table inline-form">
  469. <thead>
  470. <tr>
  471. <th width="35px">&nbsp;</th>
  472. <th>User Name</th>
  473. <th>Password</th>
  474. <th width="200px">Expired Date</th>
  475. <th width="120px"></th>
  476. </tr>
  477. </thead><tbody>
  478. <tr>
  479. <td><input type="checkbox" class="checkbox" ng-model="account_new.enable" /></td>
  480. <td><input type="text" class="form-control" ng-model="account_new.user" /></td>
  481. <td><input type="text" class="form-control" ng-model="account_new.pass" /></td>
  482. <td><quick-datepicker ng-model="account_new.exp"></quick-datepicker></td>
  483. <td><button class="btn btn-success" ng-click="account_save(0)"><i class="fa fa-plus fa-lg fa-fw"></i></button></td>
  484. </tr>
  485. <tr ng-repeat="(i,a) in xproxy.accounts" ng-class="{'expired':is_expired(a)}">
  486. <td><input type="checkbox" class="checkbox" ng-model="a.enable" /></td>
  487. <td><input type="text" class="form-control" ng-model="a.user" /></td>
  488. <td><input type="text" class="form-control" ng-model="a.pass" /></td>
  489. <td><quick-datepicker ng-model="a.exp"></quick-datepicker></td>
  490. <td>
  491. <button class="btn btn-success" ng-click="account_save(i + 1)"><i class="fa fa-check fa-lg fa-fw"></i></button>
  492. <button class="btn btn-danger" ng-click="account_delete(i + 1)"><i class="fa fa-times fa-lg fa-fw"></i></button>
  493. </td>
  494. </tr>
  495. </tbody>
  496. </table></div></div>
  497. </script>
  498.  
  499. <script type="text/ng-template" id="channels.html">
  500. <div class="row"><div class="col-sm-offset-2 col-sm-8"><table class="table inline-form">
  501. <thead>
  502. <tr>
  503. <th width="35px">&nbsp;</th>
  504. <th>Name</th>
  505. <th>Source</th>
  506. <th>Channel ID</th>
  507. <th width="120px"></th>
  508. </tr>
  509. </thead><tbody>
  510. <tr>
  511. <td><input type="checkbox" class="checkbox" ng-model="channel_new.enable" /></td>
  512. <td><input type="text" class="form-control" ng-model="channel_new.name" /></td>
  513. <td><input type="text" class="form-control" ng-model="channel_new.source" /></td>
  514. <td><input type="text" class="form-control" ng-model="channel_new.path" /></td>
  515. <td><button class="btn btn-success" ng-click="channel_save(0)"><i class="fa fa-plus fa-lg fa-fw"></i></button></td>
  516. </tr>
  517. <tr ng-repeat="(i,c) in xproxy.channels">
  518. <td><input type="checkbox" class="checkbox" ng-model="c.enable" /></td>
  519. <td><input type="text" class="form-control" ng-model="c.name" /></td>
  520. <td><input type="text" class="form-control" ng-model="c.source" /></td>
  521. <td><input type="text" class="form-control" ng-model="c.path" /></td>
  522. <td>
  523. <button class="btn btn-success" ng-click="channel_save(i + 1)"><i class="fa fa-check fa-lg fa-fw"></i></button>
  524. <button class="btn btn-danger" ng-click="channel_delete(i + 1)"><i class="fa fa-times fa-lg fa-fw"></i></button>
  525. </td>
  526. </tr>
  527. </tbody>
  528. </table></div></div>
  529. </script>
  530.  
  531. <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
  532. <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular-route.js"></script>
  533.  
  534. <script type="text/javascript">
  535. (function(){var a;a=angular.module("ngQuickDate",[]),a.provider("ngQuickDateDefaults",function(){return{options:{dateFormat:"dd/MM/yyyy",timeFormat:"h:mm a",labelFormat:null,placeholder:"Click to Set Date",hoverText:null,buttonIconHtml:null,closeButtonHtml:"&times;",nextLinkHtml:"Next &rarr;",prevLinkHtml:"&larr; Prev",disableTimepicker:1,disableClearButton:1,defaultTime:null,dayAbbreviations:["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],dateFilter:null,parseDateFunction:function(a){var b;return b=Date.parse(a),isNaN(b)?null:new Date(b)}},$get:function(){return this.options},set:function(a,b){var c,d,e;if("object"==typeof a){e=[];for(c in a)d=a[c],e.push(this.options[c]=d);return e}return this.options[a]=b}}}),a.directive("quickDatepicker",["ngQuickDateDefaults","$filter","$sce",function(a,b,c){return{restrict:"E",require:"?ngModel",scope:{dateFilter:"=?",onChange:"&",required:"@"},replace:!0,link:function(d,e,f,g){var h,i,j,k,l,m,n,o,p,q,r,s,t;return m=function(){return q(),d.toggleCalendar(!1),d.weeks=[],d.inputDate=null,d.inputTime=null,d.invalid=!0,"string"==typeof f.initValue&&g.$setViewValue(f.initValue),p(),o()},q=function(){var b,e;for(b in a)e=a[b],b.match(/[Hh]tml/)?d[b]=c.trustAsHtml(a[b]||""):!d[b]&&f[b]?d[b]=f[b]:d[b]||(d[b]=a[b]);return d.labelFormat||(d.labelFormat=d.dateFormat,d.disableTimepicker||(d.labelFormat+=" "+d.timeFormat)),f.iconClass&&f.iconClass.length?d.buttonIconHtml=c.trustAsHtml("<i ng-show='iconClass' class='"+f.iconClass+"'></i>"):void 0},i=!1,window.document.addEventListener("click",function(){return d.calendarShown&&!i&&(d.toggleCalendar(!1),d.$apply()),i=!1}),angular.element(e[0])[0].addEventListener("click",function(){return i=!0}),o=function(){var a;return a=g.$modelValue?new Date(g.$modelValue):null,s(),r(a),d.mainButtonStr=a?b("date")(a,d.labelFormat):d.placeholder,d.invalid=g.$invalid},r=function(a){return null!=a?(d.inputDate=b("date")(a,d.dateFormat),d.inputTime=b("date")(a,d.timeFormat)):(d.inputDate=null,d.inputTime=null)},p=function(a){var b;return null==a&&(a=null),b=null!=a?new Date(a):new Date,"Invalid Date"===b.toString()&&(b=new Date),b.setDate(1),d.calendarDate=new Date(b)},s=function(){var a,b,c,e,f,h,i,k,m,n,o,p,q,r;for(h=d.calendarDate.getDay()-1,e=l(d.calendarDate.getFullYear(),d.calendarDate.getMonth()),f=Math.ceil((h+e)/7),o=[],a=new Date(d.calendarDate),a.setDate(a.getDate()+-1*h),i=p=0,r=f-1;r>=0?r>=p:p>=r;i=r>=0?++p:--p)for(o.push([]),c=q=0;6>=q;c=++q)b=new Date(a),d.defaultTime&&(m=d.defaultTime.split(":"),b.setHours(m[0]||0),b.setMinutes(m[1]||0),b.setSeconds(m[2]||0)),k=g.$modelValue&&b&&j(b,g.$modelValue),n=j(b,new Date),o[i].push({date:b,selected:k,disabled:"function"==typeof d.dateFilter?!d.dateFilter(b):!1,other:b.getMonth()!==d.calendarDate.getMonth(),today:n}),a.setDate(a.getDate()+1);return d.weeks=o},g.$parsers.push(function(a){return d.required&&null==a?(g.$setValidity("required",!1),null):angular.isDate(a)?(g.$setValidity("required",!0),a):angular.isString(a)?(g.$setValidity("required",!0),d.parseDateFunction(a)):null}),g.$formatters.push(function(a){return angular.isDate(a)?a:angular.isString(a)?d.parseDateFunction(a):void 0}),h=function(a,c){return b("date")(a,c)},t=function(a){return"string"==typeof a?n(a):a},n=a.parseDateFunction,j=function(a,b,c){return null==c&&(c=!1),c?a-b===0:(a=t(a),b=t(b),a&&b&&a.getYear()===b.getYear()&&a.getMonth()===b.getMonth()&&a.getDate()===b.getDate())},k=function(a,b){return a&&b?parseInt(a.getTime()/6e4)===parseInt(b.getTime()/6e4):!1},l=function(a,b){return[31,a%4===0&&a%100!==0||a%400===0?29:28,31,30,31,30,31,31,30,31,30,31][b]},g.$render=function(){return p(g.$viewValue),o()},g.$viewChangeListeners.unshift(function(){return p(g.$viewValue),o(),d.onChange?d.onChange():void 0}),d.$watch("calendarShown",function(a){var b;return a?(b=angular.element(e[0].querySelector(".quickdate-date-input"))[0],b.select()):void 0}),d.toggleCalendar=function(a){return d.calendarShown=isFinite(a)?a:!d.calendarShown},d.selectDate=function(a,b){var c;return null==b&&(b=!0),c=!g.$viewValue&&a||g.$viewValue&&!a||a&&g.$viewValue&&a.getTime()!==g.$viewValue.getTime(),"function"!=typeof d.dateFilter||d.dateFilter(a)?(g.$setViewValue(a),b&&d.toggleCalendar(!1),!0):!1},d.selectDateFromInput=function(a){var b,c,e,f;null==a&&(a=!1);try{if(c=n(d.inputDate),!c)throw"Invalid Date";if(!d.disableTimepicker&&d.inputTime&&d.inputTime.length&&c){if(f=d.disableTimepicker?"00:00:00":d.inputTime,e=n(""+d.inputDate+" "+f),!e)throw"Invalid Time";c=e}if(!k(g.$viewValue,c)&&!d.selectDate(c,!1))throw"Invalid Date";return a&&d.toggleCalendar(!1),d.inputDateErr=!1,d.inputTimeErr=!1}catch(h){if(b=h,"Invalid Date"===b)return d.inputDateErr=!0;if("Invalid Time"===b)return d.inputTimeErr=!0}},d.onDateInputTab=function(){return d.disableTimepicker&&d.toggleCalendar(!1),!0},d.onTimeInputTab=function(){return d.toggleCalendar(!1),!0},d.nextMonth=function(){return p(new Date(new Date(d.calendarDate).setMonth(d.calendarDate.getMonth()+1))),o()},d.prevMonth=function(){return p(new Date(new Date(d.calendarDate).setMonth(d.calendarDate.getMonth()-1))),o()},d.clear=function(){return d.selectDate(null,!0)},m()},template:"<div class='quickdate'>\n <a href='' ng-focus='toggleCalendar()' ng-click='toggleCalendar()' class='quickdate-button' title='{{hoverText}}'><div ng-hide='iconClass' ng-bind-html='buttonIconHtml'></div>{{mainButtonStr}}</a>\n <div class='quickdate-popup' ng-class='{open: calendarShown}'>\n <a href='' tabindex='-1' class='quickdate-close' ng-click='toggleCalendar()'><div ng-bind-html='closeButtonHtml'></div></a>\n <div class='quickdate-text-inputs'>\n <div class='quickdate-input-wrapper'>\n <label>Date</label>\n <input class='quickdate-date-input' ng-class=\"{'ng-invalid': inputDateErr}\" name='inputDate' type='text' ng-model='inputDate' placeholder='01/01/2014' ng-enter=\"selectDateFromInput(true)\" ng-blur=\"selectDateFromInput(false)\" on-tab='onDateInputTab()' />\n </div>\n <div class='quickdate-input-wrapper' ng-hide='disableTimepicker'>\n <label>Time</label>\n <input class='quickdate-time-input' ng-class=\"{'ng-invalid': inputTimeErr}\" name='inputTime' type='text' ng-model='inputTime' placeholder='12:00 PM' ng-enter=\"selectDateFromInput(true)\" ng-blur=\"selectDateFromInput(false)\" on-tab='onTimeInputTab()'>\n </div>\n </div>\n <div class='quickdate-calendar-header'>\n <a href='' class='quickdate-prev-month quickdate-action-link' tabindex='-1' ng-click='prevMonth()'><div ng-bind-html='prevLinkHtml'></div></a>\n <span class='quickdate-month'>{{calendarDate | date:'MMMM yyyy'}}</span>\n <a href='' class='quickdate-next-month quickdate-action-link' ng-click='nextMonth()' tabindex='-1' ><div ng-bind-html='nextLinkHtml'></div></a>\n </div>\n <table class='quickdate-calendar'>\n <thead>\n <tr>\n <th ng-repeat='day in dayAbbreviations'>{{day}}</th>\n </tr>\n </thead>\n <tbody>\n <tr ng-repeat='week in weeks'>\n <td ng-mousedown='selectDate(day.date, true, true)' ng-click='$event.preventDefault()' ng-class='{\"other-month\": day.other, \"disabled-date\": day.disabled, \"selected\": day.selected, \"is-today\": day.today}' ng-repeat='day in week'>{{day.date | date:'d'}}</td>\n </tr>\n </tbody>\n </table>\n <div class='quickdate-popup-footer'>\n <a href='' class='quickdate-clear' tabindex='-1' ng-hide='disableClearButton' ng-click='clear()'>Clear</a>\n </div>\n </div>\n</div>"}}]),a.directive("ngEnter",function(){return function(a,b,c){return b.bind("keydown keypress",function(b){return 13===b.which?(a.$apply(c.ngEnter),b.preventDefault()):void 0})}}),a.directive("onTab",function(){return{restrict:"A",link:function(a,b,c){return b.bind("keydown keypress",function(b){return 9!==b.which||b.shiftKey?void 0:a.$apply(c.onTab)})}}})}).call(this);
  536. </script>
  537.  
  538. <script type="text/javascript">
  539. var App = angular.module("App", ["ngRoute", "ngQuickDate"]);
  540.  
  541. App.directive("lazyHref", function() {
  542. return {
  543. restrict: "A",
  544. transclude: true,
  545. link: function (scope, element, attrs) {
  546. element.attr("type", "text/css");
  547. element.attr("rel", "stylesheet");
  548. element.attr("href", attrs.lazyHref);
  549. element.removeAttr("lazy-href");
  550. }
  551. };
  552. });
  553.  
  554. App.config(["$routeProvider", function($routeProvider) {
  555. $routeProvider
  556. .when("/stat", {
  557. templateUrl: "stat.html",
  558. controller: "StatController"
  559. })
  560. .when("/options", {
  561. templateUrl: "options.html",
  562. controller: "OptionsController"
  563. })
  564. .when("/accounts", {
  565. templateUrl: "accounts.html",
  566. controller: "AccountsController"
  567. })
  568. .when("/channels", {
  569. templateUrl: "channels.html",
  570. controller: "ChannelsController"
  571. })
  572. .otherwise({
  573. redirectTo: '/stat'
  574. });;
  575. }]);
  576.  
  577. App.run(["$rootScope",
  578. "$http",
  579. function($rootScope, $http)
  580. {
  581. angular.element(document).ready(function() {
  582. $http
  583. .post('/stat/', { 'cmd': 'load' })
  584. .success(function(data) {
  585. $rootScope.version = "Astra v." + data.version;
  586. $rootScope.xproxy = data.xproxy;
  587. if($rootScope.xproxy.accounts === undefined) $rootScope.xproxy.accounts = [];
  588. if($rootScope.xproxy.channels === undefined) $rootScope.xproxy.channels = [];
  589. });
  590. });
  591. }]);
  592.  
  593. App.controller('AccountsController', ['$rootScope',
  594. '$scope',
  595. '$http',
  596. function($rootScope, $scope, $http)
  597. {
  598. $rootScope.controller = 'AccountsController';
  599. $scope.account_new = {};
  600.  
  601. $scope.account_save = function(i) {
  602. var a = (i === 0) ? $scope.account_new : $rootScope.xproxy.accounts[i - 1];
  603. var e = false;
  604. if(a.user.length === 0) {
  605. a.user = "ERROR";
  606. e = true;
  607. }
  608. if(a.pass.length === 0) {
  609. a.pass = "ERROR";
  610. e = true;
  611. }
  612. if(e === true) return;
  613. $http
  614. .post('/stat/', {
  615. 'cmd': 'save',
  616. 'scope': 'account',
  617. 'id': i,
  618. 'account': a
  619. })
  620. .success(function(data) {
  621. if(i === 0 && data.success) $rootScope.xproxy.accounts.push(data.success);
  622. });
  623.  
  624. $scope.account_new = {};
  625. };
  626.  
  627. $scope.is_expired = function(a) {
  628. var cdate = new Date();
  629. cdate.setHours(0);
  630. cdate.setMinutes(0);
  631. cdate.setSeconds(0);
  632. var edate = new Date(a.exp);
  633. return (edate <= cdate);
  634. };
  635.  
  636. $scope.account_delete = function(i) {
  637. $http
  638. .post('/stat/', {
  639. 'cmd': 'delete',
  640. 'scope': 'account',
  641. 'id': i
  642. })
  643. .success(function(data) {
  644. if(data.success) $rootScope.xproxy.accounts.splice(i - 1, 1);
  645. });
  646. };
  647. }]);
  648.  
  649. App.controller('ChannelsController', ['$rootScope',
  650. '$scope',
  651. '$http',
  652. function($rootScope, $scope, $http)
  653. {
  654. $rootScope.controller = 'ChannelsController';
  655. $scope.channel_new = { 'enable': true };
  656.  
  657. $scope.channel_save = function(i) {
  658. var c = (i === 0) ? $scope.channel_new : $rootScope.xproxy.channels[i - 1];
  659. var e = false;
  660. if(c.source.length === 0) {
  661. c.source = "ERROR";
  662. e = true;
  663. }
  664. if(c.path.length === 0) {
  665. c.path = "ERROR";
  666. e = true;
  667. }
  668. if(e === true) return;
  669. $http
  670. .post('/stat/', {
  671. 'cmd': 'save',
  672. 'scope': 'channel',
  673. 'id': i,
  674. 'channel': c
  675. })
  676. .success(function(data) {
  677. if(i === 0 && data.success) $rootScope.xproxy.channels.push(data.success);
  678. });
  679.  
  680. $scope.channel_new = { 'enable': true };
  681. };
  682.  
  683. $scope.channel_delete = function(i) {
  684. $http
  685. .post('/stat/', {
  686. 'cmd': 'delete',
  687. 'scope': 'channel',
  688. 'id': i
  689. })
  690. .success(function(data) {
  691. if(data.success) $rootScope.xproxy.channels.splice(i - 1, 1);
  692. });
  693. };
  694. }]);
  695.  
  696. App.controller('OptionsController', ['$rootScope',
  697. '$scope',
  698. '$http',
  699. function($rootScope, $scope, $http)
  700. {
  701. $rootScope.controller = 'OptionsController';
  702.  
  703. $scope.options_save = function() {
  704. var opts = $rootScope.xproxy.options;
  705. for(var k in opts) {
  706. if(!opts[k]) delete(opts[k]);
  707. }
  708. $http
  709. .post('/stat/', {
  710. 'cmd': 'save',
  711. 'scope': 'options',
  712. 'options': opts
  713. })
  714. .success(function(data) {
  715. //
  716. });
  717. };
  718.  
  719. $scope.restart = function() {
  720. $http
  721. .post('/stat/', {
  722. 'cmd': 'restart',
  723. 'scope': 'options',
  724. })
  725. .success(function(data) {
  726. //
  727. });
  728. };
  729. }]);
  730.  
  731. App.controller('StatController', ['$rootScope',
  732. '$scope',
  733. '$http',
  734. function($rootScope, $scope, $http)
  735. {
  736. $rootScope.controller = 'StatController';
  737.  
  738. $scope.refresh = function() {
  739. $http
  740. .post('/stat/', { 'cmd': 'stat' })
  741. .success(function(data) {
  742. $scope.stat = (data.stat === undefined) ? [] : data.stat;
  743. });
  744. };
  745.  
  746. $scope.disconnect = function(id) {
  747. $http
  748. .post('/stat/', { 'cmd': 'disconnect', 'id': id })
  749. .success(function(data) {
  750. for(var i = 0; i < $scope.stat.length; ++i) {
  751. if($scope.stat[i].id === id) {
  752. $scope.stat.splice(i, 1);
  753. return;
  754. }
  755. }
  756. });
  757. };
  758.  
  759. $scope.refresh();
  760. }]);
  761. </script>
  762. </html>
  763. ]]
  764. end
  765.  
  766. function on_request_stat(server, client, request)
  767. if not request then return nil end
  768.  
  769. if not xproxy_config then
  770. server:abort(client, 404, "Access List is not defined")
  771. return nil
  772. end
  773.  
  774. if xproxy_pass then
  775. if request.headers["authorization"] ~= xproxy_pass then
  776. server:send(client, {
  777. code = 401,
  778. headers = {
  779. "WWW-Authenticate: Basic realm=\"xProxy\"",
  780. "Content-Length: 0",
  781. "Connection: close",
  782. }
  783. })
  784. return
  785. end
  786. end
  787.  
  788. if request.method == "POST" then
  789. local data = json.decode(request.content)
  790. if not data then
  791. server:abort(client, 400)
  792. elseif data.cmd == "load" then
  793. local r = { version = astra.version, xproxy = {}, }
  794. r.xproxy.options = xproxy_config.options
  795. if #xproxy_config.accounts > 0 then r.xproxy.accounts = xproxy_config.accounts end
  796. if #xproxy_config.channels > 0 then r.xproxy.channels = xproxy_config.channels end
  797. server:send(client, {
  798. code = 200,
  799. headers = { "Content-Type: application/json", "Connection: close", },
  800. content = json.encode(r),
  801. })
  802. elseif data.cmd == "stat" then
  803. local ct = os.time()
  804. local l = {}
  805. for i,c in pairs(client_list) do
  806. local dt = ct - c.st
  807. local uptime = string.format("%02d:%02d", (dt / 3600), (dt / 60) % 60)
  808. table.insert(l, {
  809. id = i,
  810. user = c.user,
  811. addr = c.addr,
  812. path = c.path,
  813. name = c.name,
  814. uptime = uptime,
  815. })
  816. end
  817. local r = {}
  818. if #l > 0 then r.stat = l end
  819. server:send(client, {
  820. code = 200,
  821. headers = { "Content-Type: application/json", "Connection: close", },
  822. content = json.encode(r),
  823. })
  824. elseif data.cmd == "disconnect" then
  825. local client_id = tonumber(data.id)
  826. if client_list[client_id] then
  827. server:close(client_list[client_id].client)
  828. end
  829. server:send(client, {
  830. code = 200,
  831. headers = { "Content-Type: application/json", "Connection: close", },
  832. content = json.encode({ success = true })
  833. })
  834. elseif data.cmd == "restart" then
  835. timer({
  836. interval = 1,
  837. callback = function(self)
  838. self:close()
  839. astra.reload()
  840. end,
  841. })
  842. server:send(client, {
  843. code = 200,
  844. headers = { "Content-Type: application/json", "Connection: close", },
  845. content = json.encode({ success = true })
  846. })
  847. elseif data.cmd == "save" then
  848. if data.scope == "options" then
  849. xproxy_config.options = data.options
  850. if xproxy_config.options.login and #xproxy_config.options.login > 0 then
  851. xproxy_pass = "Basic " .. base64.encode(xproxy_config.options.login .. ":" .. xproxy_config.options.pass)
  852. else
  853. xproxy_config.options.login = nil
  854. xproxy_config.options.pass = nil
  855. end
  856. json.save(xproxy_config_path, xproxy_config)
  857. server:send(client, {
  858. code = 200,
  859. headers = { "Content-Type: application/json", "Connection: close", },
  860. content = json.encode({ success = true })
  861. })
  862. elseif data.scope == "account" then
  863. local id = tonumber(data.id)
  864. if id == nil then
  865. server:abort(client, 400)
  866. return nil
  867. end
  868. if id == 0 then
  869. table.insert(xproxy_config.accounts, data.account)
  870. else
  871. xproxy_config.accounts[id] = data.account
  872. end
  873. json.save(xproxy_config_path, xproxy_config)
  874. server:send(client, {
  875. code = 200,
  876. headers = { "Content-Type: application/json", "Connection: close", },
  877. content = json.encode({ success = data.account })
  878. })
  879. elseif data.scope == "channel" then
  880. local id = tonumber(data.id)
  881. if id == nil then
  882. server:abort(client, 400)
  883. return nil
  884. end
  885. if id == 0 then
  886. table.insert(xproxy_config.channels, data.channel)
  887. if data.channel.enable then
  888. channels[data.channel.path] = data.channel.source
  889. channel_names[data.channel.path] = data.channel.name
  890. end
  891. else
  892. local c = xproxy_config.channels[id]
  893. channels[c.path] = nil
  894. channel_names[c.path] = nil
  895. if data.channel.enable then
  896. channels[data.channel.path] = data.channel.source
  897. channel_names[data.channel.path] = data.channel.name
  898. end
  899. xproxy_config.channels[id] = data.channel
  900. end
  901. json.save(xproxy_config_path, xproxy_config)
  902. server:send(client, {
  903. code = 200,
  904. headers = { "Content-Type: application/json", "Connection: close", },
  905. content = json.encode({ success = data.channel })
  906. })
  907. else
  908. server:abort(client, 400)
  909. end
  910. elseif data.cmd == "delete" then
  911. if data.scope == "account" then
  912. local id = tonumber(data.id)
  913. if id == nil then
  914. server:abort(client, 400)
  915. return nil
  916. end
  917. table.remove(xproxy_config.accounts, id)
  918. json.save(xproxy_config_path, xproxy_config)
  919. server:send(client, {
  920. code = 200,
  921. headers = { "Content-Type: application/json", "Connection: close", },
  922. content = json.encode({ success = true })
  923. })
  924. elseif data.scope == "channel" then
  925. local id = tonumber(data.id)
  926. if id == nil then
  927. server:abort(client, 400)
  928. return nil
  929. end
  930. local c = xproxy_config.channels[id]
  931. channels[c.path] = nil
  932. channel_names[c.path] = nil
  933. table.remove(xproxy_config.channels, id)
  934. json.save(xproxy_config_path, xproxy_config)
  935. server:send(client, {
  936. code = 200,
  937. headers = { "Content-Type: application/json", "Connection: close", },
  938. content = json.encode({ success = true })
  939. })
  940. else
  941. server:abort(client, 400)
  942. end
  943. elseif data.cmd == "auth" then
  944. check_account(data.user, data.pass, function(ok)
  945. if ok then
  946. server:send(client, {
  947. code = 200,
  948. headers = { "Content-Type: text/plain", "Connection: close", },
  949. content = "Ok"
  950. })
  951. else
  952. server:abort(client, 403)
  953. end
  954. end)
  955. else
  956. server:abort(client, 400)
  957. end
  958. return nil
  959. end
  960.  
  961. server:send(client, {
  962. code = 200,
  963. headers = { "Content-Type: text/html; charset=utf-8", "Connection: close", },
  964. content = render_stat_html(),
  965. })
  966. end
  967.  
  968. xproxy_route = {
  969. { "/", on_request_stat },
  970. }
Add Comment
Please, Sign In to add comment