[
  {
    "path": ".gitmodules",
    "content": "[submodule \"luci/applications/luci-app-gpoint\"]\n\tpath = luci/applications/luci-app-gpoint\n\turl = https://github.com/Kodo-kakaku/luci-app-gpoint.git\n\tbranch = main\n\n[submodule \"luci/applications/luci-app-cpu-perf\"]\n\tpath = luci/applications/luci-app-cpu-perf\n\turl = https://github.com/koshev-msk/luci-app-cpu-perf.git\n\tbranch = master\n"
  },
  {
    "path": "README.md",
    "content": "# modemfeed\n\nIs a repository for OpenWrt firmware worked by with LTE cellular modems.\n\nIncluded next packages:\n\n|Package       |        Dependies      |       Description        |\n|:-------------|:----------------------|:-------------------------|\n| luci-app-modeminfo|modeminfo|Dashboard for LTE modemds.|\n|luci-app-smstools3|smstools3|web UI smstools3 package.|\n|luci-app-mmconfig|modemmanager|band manipulation modem via mmcli utility.|\n|luci-app-atinout|atinout|AT commands tool.|\n|luci-app-cellled|cellled|LED cellular signal signal strength.|\n|luci-app-ttl|luci-app-firewall|TTL modification utility with support for both iptables and nftables.|\n|qminfo|libqmi|simple info from Qualcomm modem chipsets|\n|qtools|libc|tools manipulation Qualcomm chipset cellualr modems.|\n|asterisk-chan-quectel|asterisk|asterisk plugin for SimCom and Quectel modems.|\n|xmm-modem|kmod-usb-net-ncm, kmod-usb-acm|Intel XMM modem connect scripts|\n* and more packages not included in official OpenWrt Repo.\n\n# How-to add repo and compile packages\n\nAdd next line to feeds.conf.default in OpenWrt SDK/Buildroot\n\n```\nsrc-git modemfeed https://github.com/koshev-msk/modemfeed.git\n```\n\nUpdate feeds and compile singe package\n\n```\n./scripts/feeds update -a; ./scripts/feeds install -a\nmake -j$((`nproc` + 1)) package/feeds/modemfeed/<package_name>/compile\n```\n\nor `make menuconfig` menu to include package(s) firmware in Buildroot\n\n# Precompiled packages 21,23,24,25 branch\n\nhttp://openwrt.132lan.ru/packages/\n"
  },
  {
    "path": "luci/applications/luci-app-3proxy/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=3proxy simple webUI\nLUCI_DEPENDS:=+3proxy\nPKG_LICENSE:=GPLv3\nPKG_VERSION:=0.1.1\nPKG_RELEASE:=2\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-3proxy/README.md",
    "content": "# luci-app-3proxy\nSimple webUI OpenWrt Luci for 3proxy\n"
  },
  {
    "path": "luci/applications/luci-app-3proxy/htdocs/luci-static/resources/view/proxy/3proxy.js",
    "content": "'use strict';\n'require fs';\n'require view';\n'require ui';\n'require uci';\n\n\nreturn view.extend({\n\tload: function() {\n\t\treturn uci.load('3proxy');\n\t},\n\n\trender: function() {\n\t\tvar configPath = '/etc/3proxy.cfg';\n\t\t\n\t\tuci.sections('3proxy', '3proxy').forEach(function(s) {\n\t\t\tif (s['.type'] === '3proxy' && s.config) {\n\t\t\t\tconfigPath = s.config;\n\t\t\t}\n\t\t});\n\t\t\n\t\tthis.configPath = configPath;\n\t\t\n\t\tvar container = E('div', { 'class': 'cbi-map' }, [\n\t\t\tE('h2', {}, _('3proxy Configuration')),\n\t\t\tE('div', { 'class': 'cbi-section' }, [\n\t\t\t\tE('div', { 'class': 'cbi-section-descr' }, [\n\t\t\t\t\t_('Edit 3proxy configuration file.'),\n\t\t\t\t\tE('br'),\n\t\t\t\t\t_('Config file: '),\n\t\t\t\t\tE('code', {}, configPath)\n\t\t\t\t]),\n\t\t\t\tE('div', { 'class': 'cbi-value' }, [\n\t\t\t\t\tE('label', { \n\t\t\t\t\t\t'class': 'cbi-value-title',\n\t\t\t\t\t\t'style': 'display: block; font-weight: bold;' \n\t\t\t\t\t}, _('Configuration')),\n\t\t\t\t\tE('br'), E('br'),\n\t\t\t\t\tE('textarea', {\n\t\t\t\t\t\t'class': 'cbi-input-textarea',\n\t\t\t\t\t\t'rows': 20,\n\t\t\t\t\t\t'style': 'width: 100%; box-sizing: border-box;',\n\t\t\t\t\t\t'name': 'config',\n\t\t\t\t\t\t'id': 'config-textarea'\n\t\t\t\t\t}, '')\n\t\t\t\t]),\n\t\t\t\tE('div', { \n\t\t\t\t\t'class': 'cbi-value',\n\t\t\t\t\t'style': 'display: flex; justify-content: flex-end; margin-top: 20px; gap: 10px;' \n\t\t\t\t}, [\n\t\t\t\t\tE('div', { 'class': 'cbi-value-field right' }, [\n\t\t\t\t\t\tE('button', {\n\t\t\t\t\t\t\t'class': 'cbi-button cbi-button-save',\n\t\t\t\t\t\t\t'click': ui.createHandlerFn(this, 'saveConfig')\n\t\t\t\t\t\t}, _('Save')),\n\t\t\t\t\t\t' ',\n\t\t\t\t\t\tE('button', {\n\t\t\t\t\t\t\t'class': 'cbi-button cbi-button-apply',\n\t\t\t\t\t\t\t'click': ui.createHandlerFn(this, 'restartService')\n\t\t\t\t\t\t}, _('Restart 3proxy'))\n\t\t\t\t\t])\n\t\t\t\t])\n\t\t\t])\n\t\t]);\n\t\t\n\t\tvar self = this;\n\t\tfs.read(configPath).catch(function(err) {\n\t\t\treturn '';\n\t\t}).then(function(content) {\n\t\t\tvar textarea = document.getElementById('config-textarea');\n\t\t\tif (textarea) {\n\t\t\t\ttextarea.value = content || '';\n\t\t\t}\n\t\t});\n\t\t\n\t\treturn container;\n\t},\n\n\tsaveConfig: function() {\n\t\tvar textarea = document.querySelector('textarea[name=\"config\"]');\n\t\tif (!textarea) {\n\t\t\ttextarea = document.getElementById('config-textarea');\n\t\t}\n\t\t\n\t\tvar config = textarea.value.trim().replace(/\\r\\n/g, '\\n') + '\\n';\n\t\t\n\t\tvar configPath = this.configPath || '/etc/3proxy.cfg';\n\n\t\treturn fs.write(configPath, config).then(function() {\n\t\t\tui.addNotification(null, E('p', _('Configuration saved successfully')), 'info');\n\t\t}).catch(function(err) {\n\t\t\tui.addNotification(null, E('p', _('Error saving configuration: ') + err.message), 'error');\n\t\t});\n\t},\n\n\trestartService: function() {\n\t\treturn fs.exec('/etc/init.d/3proxy', ['restart']).then(function(res) {\n\t\t\tif (res.code === 0) {\n\t\t\t\tui.addNotification(null, E('p', _('3proxy restarted successfully')), 'info');\n\t\t\t} else {\n\t\t\t\tui.addNotification(null, E('p', _('Error restarting 3proxy: ') + res.stderr), 'error');\n\t\t\t}\n\t\t}).catch(function(err) {\n\t\t\tui.addNotification(null, E('p', _('Error restarting 3proxy: ') + err.message), 'error');\n\t\t});\n\t},\n\n\thandleSaveApply: null,\n\thandleSave: null,\n\thandleReset: null\n});\n"
  },
  {
    "path": "luci/applications/luci-app-3proxy/po/ru/3proxy.po",
    "content": "\"Language: ru\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Last-Translator: Konstantine Shevlakov <shevlakov@132lan.ru>\\n\"\n\nmsgid \"Configuration\"\nmsgstr \"Настройки\"\n\nmsgid \"3proxy Configuration\"\nmsgstr \"Файл настройки 3proxy.\"\n\nmsgid \"Config file: \"\nmsgstr \"Файл настройки: \"\n\nmsgid \"Edit 3proxy configuration file.\"\nmsgstr \"Редактируйте файл настройки.\"\n\nmsgid \"Save\"\nmsgstr \"Сохранить\"\n\nmsgid \"Restart 3proxy\"\nmsgstr \"Перезапустить 3proxy\"\n\nmsgid \"Configuration saved successfully\"\nmsgstr \"Конфигурация сохранена\"\n\nmsgid \"Error saving configuration: \"\nmsgstr \"Ошибка сохранения: \"\n\nmsgid \"3proxy restarted successfully\"\nmsgstr \"3proxy успешно перезапущен\"\n\nmsgid \"Error restarting 3proxy: \"\nmsgstr \"Ошибка запуска 3proxy: \"\n\n"
  },
  {
    "path": "luci/applications/luci-app-3proxy/root/usr/share/luci/menu.d/luci-app-3proxy.json",
    "content": "{\n\t\"admin/services/3proxy\": {\n\t\t\"title\": \"3proxy\",\n\t\t\"action\": {\n\t\t\"type\": \"alias\",\n\t\t\t\"path\": \"admin/services/3proxy/config\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"acl\": [ \"luci-app-3proxy\" ],\n\t\t\t\"uci\": { \"3proxy\": true }\n\t\t}\n\t},\n\t\"admin/services/3proxy/config\": {\n\t\t\"title\": \"Config\",\n\t\t\"order\": 41,\n\t\t\"action\": {\n\t\t\"type\": \"view\",\n\t\t\t\"path\": \"proxy/3proxy\"\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "luci/applications/luci-app-3proxy/root/usr/share/rpcd/acl.d/luci-app-3proxy.json",
    "content": "{\n\t\"luci-app-3proxy\": {\n\t\t\"description\": \"Grant UCI access for luci-app-3proxy\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/3proxy.cfg\": [ \"read\" ],\n\t\t\t\t\"/etc/init.d/3proxy\": [ \"exec\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"3proxy\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/3proxy.cfg\": [ \"write\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"3proxy\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/Makefile",
    "content": "#\n# Copyright 2022-2023 Rafał Wabik - IceG - From eko.one.pl forum\n#\n# Licensed to the GNU General Public License v3.0.\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=luci-app-atinout\nLUCI_TITLE:=LuCI JS Support for AT commands\nMAINTAINER:=Rafał Wabik <4Rafal@gmail.com>\nLUCI_DESCRIPTION:=LuCI JS interface for the atinout.\nLUCI_DEPENDS:=+atinout\nLUCI_PKGARCH:=all\nPKG_VERSION:=0.1.1\nPKG_RELEASE:=5\n\ndefine Package/$(PKG_NAME)/conffiles\n/etc/atcommands.user\nendef\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/README.md",
    "content": "# luci-app-atinout\n\nWeb UI AT-commands using atinout for OpenWrt.\n\n![](https://raw.githubusercontent.com/4IceG/Personal_data/master/atcommands.gif)\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/htdocs/luci-static/resources/view/modem/atcommands.js",
    "content": "'use strict';\n'require dom';\n'require form';\n'require fs';\n'require ui';\n'require view';\n\n/*\n\tCopyright 2022-2023 Rafał Wabik - IceG - From eko.one.pl forum\n\n\tModified for atinout by Konstantine Shevlakov <shevlakov@132lan.ru> 2025\n\n\tLicensed to the GNU General Public License v3.0.\n*/\n\nreturn view.extend({\n\thandleCommand: function(exec, args) {\n\t\tvar buttons = document.querySelectorAll('.cbi-button');\n\n\t\tfor (var i = 0; i < buttons.length; i++)\n\t\t\tbuttons[i].setAttribute('disabled', 'true');\n\n\t\treturn fs.exec(exec, args).then(function(res) {\n\t\t\tvar out = document.querySelector('.atcommand-output');\n\t\t\tout.style.display = '';\n\n\t\t\tres.stdout = res.stdout?.trim().replace(/\\n\\s*\\n\\s*\\n+/g, '\\n\\n') || '';\n\t\t\tres.stderr = res.stderr?.trim().replace(/\\n\\s*\\n\\s*\\n+/g, '\\n\\n') || '';\n\n\t\t\tdom.content(out, [ res.stdout || '', res.stderr || '' ]);\n\n\t\t}).catch(function(err) {\n\t\t\tui.addNotification(null, E('p', [ err ]))\n\t\t}).finally(function() {\n\t\t\tfor (var i = 0; i < buttons.length; i++)\n\t\t\tbuttons[i].removeAttribute('disabled');\n\n\t\t});\n\t},\n\n\thandleGo: function(ev) {\n\t\tvar atcmd = document.getElementById('cmdvalue').value;\n\t\tvar portSelect = document.getElementById('portselect');\n\t\tvar port = portSelect ? portSelect.value : '';\n\n\t\tif (atcmd.length < 2) {\n\t\t\tui.addNotification(null, E('p', _('Please specify the AT command to send')), 'info');\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!port) {\n\t\t\tui.addNotification(null, E('p', _('Please select a port for communication with the modem')), 'info');\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.handleCommand('luci-app-atinout', [atcmd, port]);\n\t},\n\n\thandleClear: function(ev) {\n\t\tvar out = document.querySelector('.atcommand-output');\n\t\tout.style.display = 'none';\n\n\t\tvar ov = document.getElementById('cmdvalue');\n\t\tov.value = '';\n\n\t\tdocument.getElementById('cmdvalue').focus();\n\t},\n\n\thandleCopy: function(ev) {\n\t\tvar ov = document.getElementById('cmdvalue');\n\t\tov.value = '';\n\t\tvar x = document.getElementById('tk').value;\n\t\tov.value = x;\n\t},\n\n\t// search port in /dev/ dir\n\tscanPorts: function() {\n\t\treturn fs.list('/dev').then(function(devices) {\n\t\t\tvar ports = [];\n\n\t\t\tif (devices) {\n\t\t\t\tdevices.forEach(function(device) {\n\t\t\t\t\tvar name = device.name;\n\t\t\t\t\tif (name) {\n\t\t\t\t\t\tif (name.startsWith('ttyUSB') ||\n\t\t\t\t\t\t\tname.startsWith('ttyACM') ||\n\t\t\t\t\t\t\t/^wwan\\d+at\\d+/.test(name)) {\n\t\t\t\t\t\t\tports.push('/dev/' + name);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tports.sort();\n\t\t\treturn ports;\n\t\t}).catch(function(err) {\n\t\t\tconsole.error('Error scanning ports:', err);\n\t\t\treturn [];\n\t\t});\n\t},\n\n\tupdatePortList: function() {\n\t\tvar self = this;\n\t\tvar portSelect = document.getElementById('portselect');\n\n\t\tif (!portSelect) return;\n\n\t\tportSelect.disabled = true;\n\t\tvar currentValue = portSelect.value;\n\n\t\tthis.scanPorts().then(function(ports) {\n\t\t\tvar currentPort = currentValue;\n\n\t\t\twhile (portSelect.firstChild) {\n\t\t\t\tportSelect.removeChild(portSelect.firstChild);\n\t\t\t}\n\n\t\t\tvar emptyOption = E('option', { value: '' }, _('-- Select port --'));\n\t\t\tportSelect.appendChild(emptyOption);\n\n\t\t\tports.forEach(function(port) {\n\t\t\t\tvar option = E('option', { value: port }, port);\n\t\t\t\tif (port === currentPort) {\n\t\t\t\t\toption.setAttribute('selected', 'selected');\n\t\t\t\t}\n\t\t\t\tportSelect.appendChild(option);\n\t\t\t});\n\n\t\t\tportSelect.disabled = false;\n\n\t\t\t// if ports not found\n\t\t\tif (ports.length === 0) {\n\t\t\t\tui.addNotification(null, E('p', _('No ttyUSB or ttyACM ports found')), 'info');\n\t\t\t}\n\t\t});\n\t},\n\n\tload: function() {\n\t\treturn Promise.all([\n\t\t\tL.resolveDefault(fs.read_direct('/etc/atcommands.user'), null)\n\t\t]);\n\t},\n\n\trender: function (loadResults) {\n\t\tvar info = _('User interface for handling AT commands using atinout utility.');\n\t\tvar self = this;\n\n\t\tvar container = E('div', { 'class': 'cbi-map', 'id': 'map' }, [\n\t\t\tE('h2', {}, [ _('AT Commands') ]),\n\t\t\tE('div', { 'class': 'cbi-map-descr'}, info),\n\t\t\tE('hr'),\n\t\t\tE('div', { 'class': 'cbi-section' }, [\n\t\t\t\tE('div', { 'class': 'cbi-section-node' }, [\n\t\t\t\t\tE('div', { 'class': 'cbi-value' }, [\n\t\t\t\t\t\tE('label', { 'class': 'cbi-value-title' }, [ _('User AT commands') ]),\n\t\t\t\t\t\tE('div', { 'class': 'cbi-value-field' }, [\n\t\t\t\t\t\t\tE('select', { \n\t\t\t\t\t\t\t\t'class': 'cbi-input-select', \n\t\t\t\t\t\t\t\t'id': 'tk', \n\t\t\t\t\t\t\t\t'style': 'margin:5px 0; width:100%;', \n\t\t\t\t\t\t\t\t'change': ui.createHandlerFn(this, 'handleCopy')\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t(loadResults[0] || \"\").trim().split(\"\\n\").map(function(cmd) {\n\t\t\t\t\t\t\t\t\tvar fields = cmd.split(/;/);\n\t\t\t\t\t\t\t\t\tvar name = fields[0];\n\t\t\t\t\t\t\t\t\tvar code = fields[1];\n\t\t\t\t\t\t\t\t\treturn E('option', { 'value': code }, name );\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t]) \n\t\t\t\t\t]),\n\n\t\t\t\t\tE('div', { 'class': 'cbi-value' }, [\n\t\t\t\t\t\tE('label', { 'class': 'cbi-value-title' }, [ _('Modem Port') ]),\n\t\t\t\t\t\tE('div', { 'class': 'cbi-value-field' }, [\n\t\t\t\t\t\t\tE('div', { 'style': 'display: flex; align-items: center; gap: 10px;' }, [\n\t\t\t\t\t\t\t\tE('select', {\n\t\t\t\t\t\t\t\t\t'class': 'cbi-input-select',\n\t\t\t\t\t\t\t\t\t'id': 'portselect',\n\t\t\t\t\t\t\t\t\t'style': 'margin:5px 0; width: 100%;',\n\t\t\t\t\t\t\t\t\t'name': 'port'\n\t\t\t\t\t\t\t\t}, [\n\t\t\t\t\t\t\t\t\tE('option', { value: '' }, _('-- Scanning ports --'))\n\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\tE('button', {\n\t\t\t\t\t\t\t\t\t'class': 'cbi-button cbi-button-action',\n\t\t\t\t\t\t\t\t\t'style': 'white-space: nowrap;',\n\t\t\t\t\t\t\t\t\t'click': ui.createHandlerFn(this, function() {\n\t\t\t\t\t\t\t\t\t\tthis.updatePortList();\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t}, [ _('Refresh') ])\n\t\t\t\t\t\t\t])\n\t\t\t\t\t\t])\n\t\t\t\t\t]),\n\n\t\t\t\t\tE('div', { 'class': 'cbi-value' }, [\n\t\t\t\t\t\tE('label', { 'class': 'cbi-value-title' }, [ _('Command to send') ]),\n\t\t\t\t\t\tE('div', { 'class': 'cbi-value-field' }, [\n\t\t\t\t\t\t\tE('input', {\n\t\t\t\t\t\t\t\t'style': 'margin:5px 0; width:100%;',\n\t\t\t\t\t\t\t\t'type': 'text',\n\t\t\t\t\t\t\t\t'id': 'cmdvalue',\n\t\t\t\t\t\t\t\t'data-tooltip': _('Press [Enter] to send the command, press [Delete] to delete the command'),\n\t\t\t\t\t\t\t\t'keydown': function(ev) {\n\t\t\t\t\t\t\t\t\tif (ev.keyCode === 13) {\n\t\t\t\t\t\t\t\t\t\tvar execBtn = document.getElementById('execute');\n\t\t\t\t\t\t\t\t\t\tif (execBtn)\n\t\t\t\t\t\t\t\t\t\t\texecBtn.click();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (ev.keyCode === 46) {\n\t\t\t\t\t\t\t\t\t\tvar del = document.getElementById('cmdvalue');\n\t\t\t\t\t\t\t\t\t\tif (del) {\n\t\t\t\t\t\t\t\t\t\t\tvar ov = document.getElementById('cmdvalue');\n\t\t\t\t\t\t\t\t\t\t\tov.value = '';\n\t\t\t\t\t\t\t\t\t\t\tdocument.getElementById('cmdvalue').focus();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t])\n\t\t\t\t\t]),\n\t\t\t\t])\n\t\t\t]),\n\t\t\tE('hr'),\n\t\t\tE('div', { 'class': 'right' }, [\n\t\t\t\tE('button', {\n\t\t\t\t\t'class': 'cbi-button cbi-button-remove',\n\t\t\t\t\t'id': 'clr',\n\t\t\t\t\t'click': ui.createHandlerFn(this, 'handleClear')\n\t\t\t\t}, [ _('Clear form') ]),\n\t\t\t\t'\\xa0\\xa0\\xa0',\n\t\t\t\tE('button', {\n\t\t\t\t\t'class': 'cbi-button cbi-button-action important',\n\t\t\t\t\t'id': 'execute',\n\t\t\t\t\t'click': ui.createHandlerFn(this, 'handleGo')\n\t\t\t\t}, [ _('Send command') ]),\n\t\t\t]),\n\t\t\tE('p', _('Reply')),\n\t\t\tE('pre', { \n\t\t\t\t'class': 'atcommand-output', \n\t\t\t\t'id': 'preout', \n\t\t\t\t'style': 'display:none; border: 1px solid var(--border-color-medium); border-radius: 5px; font-family: monospace' \n\t\t\t}),\n\t\t]);\n\n\t\tsetTimeout(function() {\n\t\t\tself.updatePortList();\n\t\t}, 100);\n\n\t\treturn container;\n\t},\n\n\thandleSaveApply: null,\n\thandleSave: null,\n\thandleReset: null\n});\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/htdocs/luci-static/resources/view/modem/atconfig.js",
    "content": "'use strict';\n'require fs';\n'require view';\n'require ui';\n'require rpc';\n\n/*\n        Copyright 2022-2023 Rafał Wabik - IceG - From eko.one.pl forum\n\n        Modified for atinout by Konstantine Shevlakov <shevlakov@132lan.ru> 2023\n\n        Licensed to the GNU General Public License v3.0.\n*/\n\nvar cmddesc = _(\"Each line must have the following format: <code>AT command description;AT+COMMAND</code>.<br />For user convenience, the file is saved to the location <code>/etc/atcommands.user</code>.\");\n\nreturn view.extend({\n        load: function() {\n                return fs.read('/etc/atcommands.user').catch(function(err) {\n                        return '';\n                });\n        },\n\n        render: function(content) {\n                var content = content || '';\n\n                return E('div', { 'class': 'cbi-map' }, [\n                        E('div', { 'class': 'cbi-map-descr' }, _('AT Commands Configuration')),\n                        E('div', { 'class': 'cbi-section' }, [\n                                E('div', { 'class': 'cbi-section-descr' }, cmddesc),\n                                E('div', { 'class': 'cbi-value' }, [\n                                        E('label', { 'class': 'cbi-value-title' }, _('User AT commands')),\n                                        E('div', { 'class': 'cbi-value-field' }, [\n                                                E('textarea', {\n                                                        'class': 'cbi-input-textarea',\n                                                        'rows': 20,\n                                                        'style': 'width: 100%',\n                                                        'name': 'atcommands'\n                                                }, content)\n                                        ])\n                                ]),\n                                E('div', { \n                                        'class': 'cbi-value',\n                                        'style': 'display: flex; justify-content: flex-end; margin-top: 20px;' \n                                }, [\n                                        E('div', { 'class': 'cbi-value-field right' }, [\n                                                E('button', {\n                                                        'class': 'cbi-button cbi-button-save',\n                                                        'click': ui.createHandlerFn(this, 'saveCommands')\n                                                }, _('Save'))\n                                        ])\n                                ])\n                        ])\n                ]);\n        },\n\n        saveCommands: function(ev) {\n                var textarea = document.querySelector('textarea[name=\"atcommands\"]');\n                var commands = textarea.value.trim().replace(/\\r\\n/g, '\\n') + '\\n';\n\n                return fs.write('/etc/atcommands.user', commands).then(function() {\n                        ui.addNotification(null, E('p', _('AT commands list saved successfully')), 'info');\n                }).catch(function(err) {\n                        ui.addNotification(null, E('p', _('Error saving list: ') + err.message), 'error');\n                });\n        },\n\n        handleSaveApply: null,\n        handleSave: null,\n        handleReset: null\n});\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/po/ru/atinout.po",
    "content": "msgid \"\"\nmsgstr \"Content-Type: text/plain; charset=UTF-8\"\n\nmsgid \"AT Commands\"\nmsgstr \"Команды AT\"\n\nmsgid \"Configuration\"\nmsgstr \"Настройки\"\n\nmsgid \"Please specify the AT command to send\"\nmsgstr \"Выберите команду AT для отправки\"\n\nmsgid \"Please set the port for communication with the modem\"\nmsgstr \"Выберите порт модема.\"\n\nmsgid \"Please select a port for communication with the modem\"\nmsgstr \"Выберите порт модема.\"\n\nmsgid \"User AT commands\"\nmsgstr \"Пользовательские команды AT\"\n\nmsgid \"Command to send\"\nmsgstr \"Команда к отправке\"\n\nmsgid \"Clear form\"\nmsgstr \"Очистить\"\n\nmsgid \"Send command\"\nmsgstr \"Отправить\"\n\nmsgid \"Reply\"\nmsgstr \"Ответ\" \n\nmsgid \"User interface for handling AT commands using atinout utility.\"\nmsgstr \"Интерфейс для отсылки команд AT с использованием atinout.\"\n\nmsgid \"Configuration atinout\"\nmsgstr \"Настройки atinout\"\n\nmsgid \"Configuration panel for atinout.\"\nmsgstr \"Панель конфигурации atinout\"\n\nmsgid \"Modem Port\"\nmsgstr \"Порт модема\"\n\nmsgid \"-- Select port --\"\nmsgstr \"-- Выберите порт --\"\n\nmsgid \"Each line must have the following format: <code>AT command description;AT+COMMAND</code>.<br />For user convenience, the file is saved to the location <code>/etc/atcommands.user</code>.\"\nmsgstr \"Формат записи команд: <code>Описание команды;Команда AT</code>.<br /> Пользовательские команды содержатся в файле <code>/etc/atcommands.user</code>.\"\n\nmsgid \"Press [Enter] to send the command, press [Delete] to delete the command\"\nmsgstr \"Нажмите [Enter] для отправки или [Delete] для удаления\"\n\nmsgid \"AT commands list saved successfully\"\nmsgstr \"Список команд успешно сохранён\"\n\nmsgid \"Error saving list: \"\nmsgstr \"Ошибка сохраниения: \"\n\nmsgid \"No ttyUSB or ttyACM ports found\"\nmsgstr \"Последовательные порты модема не обнаружены\"\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/po/template/atinout.pot",
    "content": "msgid \"\"\nmsgstr \"Content-Type: text/plain; charset=UTF-8\"\n\nmsgid \"AT Commands\"\nmsgstr \"\"\n\nmsgid \"Configuration\"\nmsgstr \"\"\n\nmsgid \"Please specify the AT command to send\"\nmsgstr \"\"\n\nmsgid \"Please set the port for communication with the modem\"\nmsgstr \"\"\n\nmsgid \"User AT commands\"\nmsgstr \"\"\n\nmsgid \"Command to send\"\nmsgstr \"\"\n\nmsgid \"Clear form\"\nmsgstr \"\"\n\nmsgid \"Send command\"\nmsgstr \"\"\n\nmsgid \"Reply\"\nmsgstr \"\" \n\nmsgid \"User interface for handling AT commands using sms-tool. More information about the sms-tool on the %seko.one.pl forum%s.\"\nmsgstr \"\"\n\nmsgid \"Configuration sms-tool\"\nmsgstr \"\"\n\nmsgid \"Configuration panel for sms-tool and gui application.\"\nmsgstr \"\"\n\nmsgid \"Port for communication with the modem\"\nmsgstr \"\"\n\nmsgid \"Select one of the available ttyUSBX ports.\"\nmsgstr \"\"\n\nmsgid \"Please select a port\"\nmsgstr \"\"\n\nmsgid \"Each line must have the following format: 'At command description;AT command'. For user convenience, the file is saved to the location '/etc/modem/atcommands.user'.\"\nmsgstr \"\"\n\nmsgid \"Press [Enter] to send the command, press [Delete] to delete the command.\"\nmsgstr \"\"\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/po/zh_Hans/atinout.po",
    "content": "msgid \"\"\nmsgstr \"Content-Type: text/plain; charset=UTF-8\"\n\nmsgid \"AT Commands\"\nmsgstr \"AT命令\"\n\nmsgid \"Configuration\"\nmsgstr \"配置\"\n\nmsgid \"Please specify the AT command to send\"\nmsgstr \"请选择要发送的 AT命令\"\n\nmsgid \"Please set the port for communication with the modem\"\nmsgstr \"请选择与调制解调器通信的端口\"\n\nmsgid \"User AT commands\"\nmsgstr \"用户自定义 AT 命令\"\n\nmsgid \"Command to send\"\nmsgstr \"待发送命令\"\n\nmsgid \"Clear form\"\nmsgstr \"清除表单\"\n\nmsgid \"Send command\"\nmsgstr \"发送命令\"\n\nmsgid \"Reply\"\nmsgstr \"回复\"\n\nmsgid \"User interface for handling AT commands using atinout utility.\"\nmsgstr \"使用 atinout工具处理 AT命令的用户界面\"\n\nmsgid \"Configuration atinout\"\nmsgstr \"atinout 的配置\"\n\nmsgid \"Configuration panel for atinout.\"\nmsgstr \"atinout 的配置面板\"\n\nmsgid \"Port for communication with the modem\"\nmsgstr \"调制解调器通信端口\"\n\nmsgid \"Select serial modem port.\"\nmsgstr \"选择串行调制解调器端口\"\n\nmsgid \"Please select a port\"\nmsgstr \"请选择一个端口\"\n\nmsgid \"Each line must have the following format: 'At command description;AT command'. For user convenience, the file is saved to the location <code>/etc/atcommands.user</code>.\"\nmsgstr \"每行应遵循以下格式：'AT命令描述;AT命令'。为了方便用户，文件会被保存在<code>/etc/atcommands.user</code>位置。\"\n\nmsgid \"Press [Enter] to send the command, press [Delete] to delete the command\"\nmsgstr \"按[Enter]键发送命令，按[Delete]键删除命令\"\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/root/etc/uci-defaults/65-luci-app-atinout",
    "content": "#!/bin/sh\n\nif [ ! -f /etc/atcommands.user  ]; then\n\techo \"Demo command \\\"ATI\\\";ATI\" > /etc/atcommands.user\nfi\n\nrm -rf /tmp/luci-*\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/root/usr/bin/luci-app-atinout",
    "content": "#!/bin/sh\n\necho $1 | atinout - $2 -\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/root/usr/share/luci/menu.d/luci-app-atinout.json",
    "content": "{\n\t\"admin/modem\": {\n\t\t\"title\": \"Modem\",\n\t\t\"order\": 45,\n\t\t\"action\": {\n\t\t\t\"type\": \"firstchild\",\n\t\t\t\"recurse\": true\n\t\t}\n\t},\n\t\n\t\"admin/modem/atinout\": {\n\t\t\"title\": \"AT Commands\",\n\t\t\"order\": 41,\n\t\t\"action\": {\n\t\t\t\"type\": \"alias\",\n\t\t\t\"path\": \"admin/modem/atinout/atcommands\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"acl\": [ \"luci-app-atinout\" ]\n\t\t}\n\t},\n\t\n\t\"admin/modem/atinout/atcommands\": {\n\t\t\"title\": \"AT Commands\",\n\t\t\"order\": 41,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"modem/atcommands\"\n\t\t}\n\t},\n\n\t\"admin/modem/atinout/atconfig\": {\n\t\t\"title\": \"Configuration\",\n\t\t\"order\": 42,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"modem/atconfig\"\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "luci/applications/luci-app-atinout/root/usr/share/rpcd/acl.d/luci-app-atinout.json",
    "content": "{\n\t\"luci-app-atinout\": {\n\t\t\"description\": \"Grant access to atcommands executables\",\n\t\t\"read\": {\n   \t\t\t\"cgi-io\": [ \"exec\" ],\n    \t\t\t\"file\": {\n\t\t\t\t\"/bin/echo\": [ \"exec\" ],\n\t\t\t\t\"/usr/bin/atinout\": [ \"exec\" ],\n\t\t\t\t\"/usr/bin/luci-app-atinout\": [ \"exec\" ],\n\t\t\t\t\"/etc/atcommands.user\": [ \"read\" ]\n\t\t\t}\n\t\t},\n\t\t\"write\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/atcommands.user\": [ \"write\" ]\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-cellled/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=luci-app-cellled\nLUCI_DEPENDS:=+cellled\nPKG_VERSION:=0.1.0\nPKG_RELEASE:=2\nLUCI_TITLE:=LuCI application for showing cellular RSSI LED\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-cellled/htdocs/luci-static/resources/view/cellled.js",
    "content": "'use strict';\n'require form';\n'require view';\n'require uci';\n'require rpc';\n'require fs';\n'require tools.widgets as widgets';\n\n\nfunction getModemList() {\n\treturn fs.exec_direct('/usr/bin/mmcli', [ '-L' ]).then(function(res) {\n\t\tvar lines = (res || '').split(/\\n/),\n\t\t    tasks = [];\n\n\t\tfor (var i = 0; i < lines.length; i++) {\n\t\t\tvar m = lines[i].match(/\\/Modem\\/(\\d+)/);\n\t\t\tif (m)\n\t\t\t\ttasks.push(fs.exec_direct('/usr/bin/mmcli', [ '-m', m[1] ]));\n\t\t}\n\n\t\treturn Promise.all(tasks).then(function(res) {\n\t\t\tvar modems = [];\n\n\t\t\tfor (var i = 0; i < res.length; i++) {\n\t\t\t\tvar man = res[i].match(/manufacturer: ([^\\n]+)/),\n\t\t\t\t    mod = res[i].match(/model: ([^\\n]+)/),\n\t\t\t\t    dev = res[i].match(/device: ([^\\n]+)/);\n\n\t\t\t\tif (dev) {\n\t\t\t\t\tmodems.push({\n\t\t\t\t\t\tdevice:       dev[1].trim(),\n\t\t\t\t\t\tmanufacturer: (man ? man[1].trim() : '') || '?',\n\t\t\t\t\t\tmodel:        (mod ? mod[1].trim() : '') || dev[1].trim()\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn modems;\n\t\t});\n\t});\n}\n\nvar callSerialPort = rpc.declare({\n\tobject: 'file',\n\tmethod: 'list',\n\tparams: [ 'path' ],\n\texpect: { entries: [] },\n\tfilter: function(list, params) {\n\t\tvar rv = [];\n\t\tfor (var i = 0; i < list.length; i++)\n\t\t\tif (list[i].name.match(/^ttyACM/) || list[i].name.match(/^ttyUSB/))\n\t\t\t\trv.push(params.path + list[i].name);\n\t\treturn rv.sort();\n\t}\n});\n\nvar callQMIPort = rpc.declare({\n        object: 'file',\n        method: 'list',\n        params: [ 'path' ],\n        expect: { entries: [] },\n        filter: function(list, params) {\n                var rv = [];\n                for (var i = 0; i < list.length; i++)\n                        if (list[i].name.match(/^cdc-wdm/))\n                                rv.push(params.path + list[i].name);\n                return rv.sort();\n        }\n});\n\nvar callLEDs = rpc.declare({\n\tobject: 'luci',\n\tmethod: 'getLEDs',\n\texpect: { '': {} }\n});\n\n\nreturn view.extend({\n\n\tload: function() {\n\t\treturn Promise.all([\n\t\t\tcallLEDs(),\n\t\t\tL.resolveDefault(fs.list('/www' + L.resource('view/system/led-trigger')), []),\n\t\t]).then(function(data) {\n\t\t\tvar plugins = data[1];\n\t\t\tvar tasks = [];\n\n\t\t\tfor (var i = 0; i < plugins.length; i++) {\n\t\t\t\tvar m = plugins[i].name.match(/^(.+)\\.js$/);\n\n\t\t\t\tif (plugins[i].type != 'file' || m == null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\ttasks.push(L.require('view.system.led-trigger.' + m[1]).then(L.bind(function(name){\n\t\t\t\t\treturn L.resolveDefault(L.require('view.system.led-trigger.' + name)).then(function(form) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: name,\n\t\t\t\t\t\t\tform: form,\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t}, this, m[1])));\n\t\t\t}\n\n\t\t\treturn Promise.all(tasks).then(function(plugins) {\n\t\t\t\tvar value = {};\n\t\t\t\tvalue[0] = data[0];\n\t\t\t\tvalue[1] = plugins;\n\t\t\t\t\n\t\t\t\treturn value;\n\t\t\t});\n\t\t});\n\t},\n\n\trender: function(data) {\n\t\t\n\t\tvar m, s, o, triggers = [];\n\t\tvar leds = data[0];\n\t\tvar plugins = data[1];\n\n\t\tfor (var k in leds)\n\t\t\tfor (var i = 0; i < leds[k].triggers.length; i++)\n\t\t\t\ttriggers[i] = leds[k].triggers[i];\n\n\t\tm = new form.Map('cellled', _('CellLED'));\n\t\tm.description = _('Application for showing cellular RSSI LEDs');\n\n\t\ts = m.section(form.TypedSection, 'device', _('General setup'));\n\t\ts.anonymous = true;\n\t\ts.rmempty = true;\n\t\t\t\t\n\t\to = s.option(form.ListValue, 'data_type', _('Select Data service'));\n\t\to.value('qmi', 'libQMI');\n\t\to.value('uqmi', 'uQMI');\n\t\to.value('mm', 'Modem Manager');\n\t\to.value('serial', 'Serial Port');\n\n\t\to = s.option(form.ListValue, 'device', _('Select Data port'));\n\t\to.load = function(section_id) {\n\t\t\treturn callSerialPort('/dev/').then(L.bind(function(devices) {\n\t\t\t\tfor (var i = 0; i < devices.length; i++)\n\t\t\t\t\tthis.value(devices[i]);\n\t\t\t\treturn form.Value.prototype.load.apply(this, [section_id]);\n\t\t\t}, this));\n\t\t};\n\t\to.rmempty = true;\n\t\to.depends('data_type', 'serial');\n\n\t\to = s.option(form.ListValue, 'device_qmi', _('Select Data port'));\n\t\to.load = function(section_id) {\n\t\t\treturn callQMIPort('/dev/').then(L.bind(function(devices) {\n\t\t\t\tfor (var i = 0; i < devices.length; i++)\n\t\t\t\t\tthis.value(devices[i]);\n\t\t\t\treturn form.Value.prototype.load.apply(this, [section_id]);\n\t\t\t}, this));\n\t\t};\n\t\to.rmempty = true;\n\t\to.depends('data_type', 'qmi');\n\t\to.depends('data_type', 'uqmi');\n\n\t\to = s.option(form.ListValue, 'device_mm', _('Select Data port'));\n\t\to.load = function(section_id) {\n\t\t\treturn getModemList().then(L.bind(function(devices) {\n\t\t\t\tfor (var i = 0; i < devices.length; i++)\n\t\t\t\t\tthis.value(devices[i].device,\n\t\t\t\t\t\t'%s - %s'.format(devices[i].manufacturer, devices[i].model));\n\t\t\t\treturn form.Value.prototype.load.apply(this, [section_id]);\n\t\t\t}, this));\n\t\t};\n\t\to.rmempty = true;\n\t\to.depends('data_type', 'mm');\n\t\t\n\t\to = s.option(form.Value, 'timeout', _('Timeout interval data(sec)'));\n\t\to.datatype = 'and(uinteger,min(5))';\n\n\t\to = s.option(form.Flag, 'rgb_led', _('RGB LED'), _('Use RGB Led'));\n\n\t\to = s.option(form.Flag, 'use_pwm', _('Use PWM'), _('Enable if Support PWM LED'));\n\t\to.depends('rgb_led', '1');\n\n\t\to = s.option(form.ListValue, 'red_led', _('Red LED'));\n\t\tObject.keys(leds).sort().forEach(function(name) {\n                        o.value(name);\n                });\n\t\to.rmempty = true;\n\t\to.depends('rgb_led', '1');\n\n\t\to = s.option(form.ListValue, 'green_led', _('Greed LED'));\n\t\tObject.keys(leds).sort().forEach(function(name) {\n\t\t\to.value(name);\n\t\t});\n\t\to.rmempty = true;\n\t\to.depends('rgb_led', '1');\n\t\t\n\t\to = s.option(form.ListValue, 'blue_led', _('Blue LED'));\t\t\n\t\tObject.keys(leds).sort().forEach(function(name) {\n\t\t\to.value(name);\n\t\t});\n\t\to.rmempty = true;\n\t\to.depends('rgb_led', '1');\n\n\t\ts = m.section(form.GridSection, 'rssi_led', _('Signal strength values'));\n\t\ts.addremove = true;\n\t\ts.anonymous = true;\n\t\ts.nodescriptions = true;\n\n\t\to = s.option(form.Flag, 'rgb', _('RGB Led'));\n\n\t\to = s.option(form.ListValue, 'led', _('LED'));\n\t\tObject.keys(leds).sort().forEach(function(name) {\n\t\t\to.value(name);\n\t\t});\n\t\to.rmempty = true;\n\t\to.depends('rgb', '0');\n\n\t\to = s.option(form.ListValue, 'type', _('Quality'));\n\t\to.value('poor',_('Poor'));\n\t\to.value('bad',_('Bad'));\n\t\to.value('fair',_('Fair'));\n\t\to.value('good',_('Good'));\n\t\to.depends('rgb', '1');\n\n\t\to = s.option(form.Value, 'rssi_min', _('Min.value %'));\n\t\to.datatype = 'and(uinteger,min(0),max(100))';\n\t\to.rmempty = true;\n\n\t\to = s.option(form.Value, 'rssi_max', _('Max.value %'));\n\t\to.datatype = 'and(uinteger,min(0),max(100))';\n\t\to.rmempty = true;\n\n\t\treturn m.render();\n\t}\n});\n"
  },
  {
    "path": "luci/applications/luci-app-cellled/po/ru/cellled.po",
    "content": "\"Language: ru\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Last-Translator: Konstantine Shevlyakov <shevlakov@132lan.ru>\\n\"\n\nmsgid \"Application for showing cellular RSSI LEDs\"\nmsgstr \"Отображение сигнала сотовой сети RSSI\"\n\nmsgid \"Select Data service'\"\nmsgstr \"Выберите тип данных\"\n\nmsgid \"Select Data port\"\nmsgstr \"Выберите порт данных\"\n\nmsgid \"Timeout interval data(sec)\"\nmsgstr \"Интервал обновления данных (сек)\"\n\nmsgid \"Use RGB Led\"\nmsgstr \"Используется RGB Led\"\n\nmsgid \"Enable if Support PWM LED\"\nmsgstr \"Включить ШИМ, если доступно\"\n\nmsgid \"Red LED\"\nmsgstr \"Красный\"\n\nmsgid \"Greed LED\"\nmsgstr \"Зеленый\"\n\nmsgid \"Blue LED\"\nmsgstr \"Синий\"\n\nmsgid \"Signal strength values\"\nmsgstr \"Значения силы сигнала\"\n\nmsgid \"Min.value %\"\nmsgstr \"Мин.значение %\"\n\nmsgid \"Max.value %\"\nmsgstr \"Макс.значение %\"\n\nmsgid \"Quality\"\nmsgstr \"Качество\"\n\nmsgid \"Poor\"\nmsgstr \"Ничтожное\"\n\nmsgid \"Bad\"\nmsgstr \"Плохое\"\n\nmsgid \"Fair\"\nmsgstr \"Среднее\"\n\nmsgid \"Good\"\nmsgstr \"Хорошее\"\n\n"
  },
  {
    "path": "luci/applications/luci-app-cellled/root/usr/share/luci/menu.d/luci-app-cellled.json",
    "content": "{\n        \"admin/modem\": {\n                \"title\": \"Modem\",\n\t\t\"order\": 45,\n                \"action\": {\n                        \"type\": \"firstchild\",\n                        \"rescue\": \"true\"\n                }\n         \n        },\n\n\t\"admin/modem/cellled\": {\n\t\t\"title\": \"CellLED\",\n\t\t\"order\": \"61\",\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"cellled\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"uci\": { \"cellled\": true }\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "luci/applications/luci-app-cellled/root/usr/share/rpcd/acl.d/luci-app-cellled.json",
    "content": "{\n\t\"luci-app-cellled\": {\n\t\t\"description\": \"Grant UCI access for luci-app-cellled\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/usr/bin/mmcli\": [ \"exec\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"cellled\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"uci\": [ \"cellled\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published\n    by the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Configrure modem bands via mmcli utility\nLUCI_DEPENDS:=+luci-proto-modemmanager +luci-app-modeminfo\nPKG_LICENSE:=GPLv3\nPKG_VERSION:=0.1.1\nPKG_RELEASE:=5\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/README.md",
    "content": "# luci-app-mmconfig\nThe OpenWrt Luci app configure modem cellular bands via mmcli utility\n\n![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/applications/luci-app-mmconfig/screenshot.png)\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/htdocs/luci-static/resources/view/modem/bands.js",
    "content": "'use strict';\n'require form';\n'require uci';\n'require view';\n'require dom';\n'require modemmanager_helper as helper';\n\nreturn view.extend({\n    load: function() {\n        return Promise.all([\n            uci.load('mmconfig'),\n            helper.getModems()\n        ]);\n    },\n\n    render: function(data) {\n        var modemsData = data[1];\n        var m = new form.Map('mmconfig', _('Modem Configuration'), _('List supported bands.<br />If deselect all bands, then used default band modem config.'));\n        \n        // add styles\n        var style = document.createElement('style');\n        style.textContent = this.getCSS();\n        document.head.appendChild(style);\n        \n        var configSections = [];\n        uci.sections('mmconfig', 'modem', function(s) {\n            configSections.push(s);\n        });\n        \n        configSections.forEach(function(section, index) {\n            // search modems\n            var modemObj = null;\n            if (modemsData && modemsData.length > 0) {\n                for (var i = 0; i < modemsData.length; i++) {\n                    if (modemsData[i] && \n                        modemsData[i].modem && \n                        modemsData[i].modem.generic && \n                        modemsData[i].modem.generic.device === section.device) {\n                        modemObj = modemsData[i].modem;\n                        break;\n                    }\n                }\n            }\n            \n            // create sections\n            var s = m.section(form.NamedSection, section['.name'], _('Modem ') + (index + 1));\n            s.addremove = false;\n            s.anonymous = false;\n            \n            // hide device option\n            var o = s.option(form.HiddenValue, 'device', '');\n            o.default = section.device || '';\n            \n            // container\n            if (modemObj && modemObj.generic) {\n                var infoPanel = s.option(form.DummyValue, '_info_panel', '');\n                infoPanel.rawhtml = true;\n                \n                var html = '<div class=\"modem-info-compact\">';\n                \n                // Operator name modem and access tech \n                var modelText = '';\n                if (modemObj.generic.manufacturer || modemObj.generic.model) {\n                    if (modemObj.generic.manufacturer) {\n                        modelText += modemObj.generic.manufacturer + ' ';\n                    }\n                    if (modemObj.generic.model) {\n                        modelText += modemObj.generic.model;\n                    }\n                } else {\n                    modelText = _('Unknown modem');\n                }\n                \n                var operatorText = '';\n                if (modemObj['3gpp'] && modemObj['3gpp']['operator-name']) {\n                    operatorText = modemObj['3gpp']['operator-name'];\n                }\n                \n                html += '<div class=\"compact-line\">';\n                html += '<span class=\"modem-model\">' + modelText + '</span>';\n                \n                if (operatorText) {\n                    html += '<span class=\"separator\">•</span>';\n                    html += '<span class=\"modem-operator\">' + operatorText + '</span>';\n                }\n\n\n               var currentModeText = '';\n               if (modemObj.generic['current-modes']) {\n\n                    currentModeText = modemObj.generic['current-modes'];\n               } \n               // if current-modes not aviaible, use access-technologies\n               else if (modemObj.generic['access-technologies'] && modemObj.generic['access-technologies'].length > 0) {\n              // Берем первую технологию из access-technologies\n                   currentModeText = modemObj.generic['access-technologies'];\n              }\n\n              if (currentModeText) {\n                 html += '<span class=\"separator\">•</span>';\n\t      html += '<span class=\"current-mode\">' + _('Access Tech:') + ' ' + currentModeText + '</span>';\n            }\n\n                html += '</div>';\n                \n                html += '</div>'; // close container\n                \n                infoPanel.default = html;\n            }\n            \n            // network preffered\n            o = s.option(form.ListValue, 'preffer', _('Network Mode'));\n            o.rmempty = false;\n            \n            if (modemObj && modemObj.generic && modemObj.generic['supported-modes']) {\n                // get from modem supported-modes \n                modemObj.generic['supported-modes'].forEach(function(mode) {\n                    o.value(mode, mode);\n                });\n\n                // Set current\n              if (section.preffer) {\n                   o.default = section.preffer;\n              } else if (currentModeText && modemObj.generic['supported-modes'].includes(currentModeText)) {\n                   o.default = currentModeText;\n\t      }\n            } else {\n                // If not aviable\n                o.value('', _('Not Available'));\n\t\to.default = '';\n\t\to.readonly = true;\n            }\n\n            // bands select\n            if (modemObj && modemObj.generic && modemObj.generic['supported-bands']) {\n                o = s.option(form.MultiValue, 'bands', _('Bands'));\n\n                // get from modem supported-bands\n                modemObj.generic['supported-bands'].forEach(function(band) {\n                    o.value(band, band);\n                });\n\n                // Set current\n                if (section.bands) {\n                    o.default = section.bands;\n\t\t}\n            } else {\n\t\to = s.option(form.Value, 'bands', _('Bands'));\n\t\to.value('', _('Not Available'));\n\t\to.default = '';\n\t\to.readonly = true;\n\t    }\n            \n            // small separator\n            if (index < configSections.length - 1) {\n                var spacer = s.option(form.DummyValue, '_divider', '');\n                spacer.default = '<div class=\"light-divider\"></div>';\n                spacer.rawhtml = true;\n            }\n        });\n        \n        if (configSections.length === 0) {\n            var s = m.section(form.NamedSection, 'info', _('WARNING'));\n            s.anonymous = true;\n            \n            var o = s.option(form.DummyValue, '_message', _('Status'));\n            o.default = _('No modem configuration found. Run <code>/etc/init.d/mmconfig start<code>');\n            o.rawhtml = false;\n        }\n        \n        return m.render();\n    },\n    \n    getCSS: function() {\n        return [\n            '.modem-info-compact {',\n            '  background: #f8fafc;',\n            '  border: 1px solid #e2e8f0;',\n            '  border-radius: 6px;',\n            '  padding: 12px 16px;',\n            '  margin: 15px 0;',\n            '}',\n            '',\n            '.compact-line {',\n            '  display: flex;',\n            '  align-items: center;',\n            '  gap: 10px;',\n            '}',\n            '',\n            '.modem-model {',\n            '  font-weight: 600;',\n            '  color: #2d3748;',\n            '  font-size: 1em;',\n            '}',\n            '',\n            '.separator {',\n            '  color: #a0aec0;',\n            '  font-weight: 300;',\n            '}',\n            '',\n            '.modem-operator {',\n            '  color: #4a5568;',\n            '  font-size: 0.95em;',\n            '}',\n            '',\n            '.light-divider {',\n            '  height: 1px;',\n            '  background: #edf2f7;',\n            '  margin: 20px 0;',\n            '}',\n            '',\n            '/* Улучшаем отступы в секциях */',\n            '.cbi-section .cbi-section-node {',\n            '  margin-bottom: 10px;',\n            '}',\n            '',\n            '.cbi-section .cbi-section-descr {',\n            '  padding: 5px 0;',\n            '}'\n        ].join('\\n');\n    }\n});\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/po/ru/mmconfig.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Language: ru\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Last-Translator: Konstantine Shevlakov <shevlakov@132lan.ru>\\n\"\n\nmsgid \"Modem \"\nmsgstr \"Модем \"\n\nmsgid \"Bands\"\nmsgstr \"Диапазоны\"\n\nmsgid \"Network Mode\"\nmsgstr \"Режим сети\"\n\nmsgid \"Bands\"\nmsgstr \"Диапазоны\"\n\nmsgid \"Access Tech:\"\nmsgstr \"Текущий:\"\n\nmsgid \"Status\"\nmsgstr \"Состояние\"\n\nmsgid \"WARNING\"\nmsgstr \"ПРЕДУПРЕЖДЕНИЕ\"\n\nmsgid \"List supported bands.<br />If deselect all bands, then used default band modem config.\"\nmsgstr \"Список поддерживаемых диапазонов.<br />Если не выбрано ни одного диапазона, используется конфигурация модема по умолчанию.\"\n\nmsgid \"No modem configuration found. Run <code>/etc/init.d/mmconfig start<code>\"\nmsgstr \"Не найдена конфигурация. Перезапустите скрипт <code>/etc/init.d/mmconfig start</code>\"\n\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/po/zh_Hans/mmconfig.po",
    "content": "msgid \"\"\nmsgstr \"Content-Type: text/plain; charset=UTF-8\"\n\n#: applications/luci-app-mmconfig/luasrc/controller/modemconfig.lua:8\nmsgid \"Modem\"\nmsgstr \"移动网络\"\n\n#: applications/luci-app-mmconfig/luasrc/controller/modemconfig.lua:9\nmsgid \"Band config\"\nmsgstr \"频段配置\"\n\n#: applications/luci-app-mmconfig/luasrc/model/cbi/modem/modemconfig.lua:12\nmsgid \"Configure modem bands\"\nmsgstr \"配置模块频段\"\n\n#: applications/luci-app-mmconfig/luasrc/model/cbi/modem/modemconfig.lua:13\nmsgid \"Configuration 2G/3G/4G modem frequency bands.\"\nmsgstr \"配置 2G/3G/4G 模块频段.\"\n\n#: applications/luci-app-mmconfig/luasrc/model/cbi/modem/modemconfig.lua:13\nmsgid \"Choose bands cellular modem\"\nmsgstr \"选择模块频段\"\n\n#: applications/luci-app-mmconfig/luasrc/model/cbi/modem/modemconfig.lua:19\nmsgid \"Net Mode\"\nmsgstr \"网络模式\"\n\n#: applications/luci-app-mmconfig/luasrc/model/cbi/modem/modemconfig.lua:19\nmsgid \"Preffered Network mode select.\"\nmsgstr \"首选网络\"\n\n#: applications/luci-app-mmconfig/luasrc/model/cbi/modem/modemconfig.lua:61\nmsgid \"Select modem\"\nmsgstr \"选择模块\"\n\n#: applications/luci-app-mmconfig/luasrc/model/cbi/modem/modemconfig.lua:72\nmsgid \"Maybe must reconnect cellular interface. <br /> If deselect all bands, then used default band modem config.\"\nmsgstr \"可能需要重连移动网络接口。 <br /> 如果不选择频段则使用模块默认频段。\"\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/root/etc/config/mmconfig",
    "content": ""
  },
  {
    "path": "luci/applications/luci-app-mmconfig/root/etc/init.d/mmconfig",
    "content": "#!/bin/sh /etc/rc.common\n\nSTART=99\nUSE_PROCD=1\n\n\ncheck_modems(){\n\tfor m in $(mmcli -L | awk '{print $1}'); do\n\t        devm=$(mmcli -m ${m} -J | jsonfilter -e '@[\"modem\"][\"generic\"][\"device\"]')\n        \tuci show mmconfig | grep \"$devm\" || {\n\t                echo \"uci section not found\"\n                \tsecname=$(uci show mmconfig | tail -1 | awk -F [.] '{num = $2; gsub(/[^0-9]/, \"\", num); if(num != \"\") printf \"%s%d\\n\", substr($2, 1, length($2)-length(num)), num+1; else print $0 \"1\"}')\n        \t        [ $secname ] || secname=modem1\n\t                uci set mmconfig.${secname}=modem\n                \tuci set mmconfig.${secname}.device=$devm\n        \t        uci commit\n \t       }\n\tdone\n}\n\n\nstart_service(){\n\tlogger -t \"MMconfig\" \"MMConfig started\"\n\t# generate config uci\n\tcheck_modems\n}\n\nreload_service(){\n\t/usr/share/modeminfo/mmconfig\n}\n\nservice_triggers() {\n\tprocd_add_reload_trigger mmconfig\n}\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/root/etc/uci-defaults/70_luci-app-mmconfig",
    "content": "#!/bin/sh\n\n# disable mode setting to any in modemnanager proto\n[ -f /lib/netifd/proto/modemmanager.sh ] && {\n\tsed -i '/^[[:space:]]*modemmanager_set_allowed_mode \"$device\" \"$interface\" \"any\"$/s/.*/\\t\\t:/' /lib/netifd/proto/modemmanager.sh\n}\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/root/usr/share/luci/menu.d/luci-app-mmconfig.json",
    "content": "{\n\t\"admin/modem/main\": {\n\t\t\"title\": \"Modeminfo\",\n\t\t\"order\": 10,\n\t\t\"action\": {\n\t\t\t\"type\": \"alias\",\n\t\t\t\"path\": \"admin/modem/main/main\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"acl\": [ \"luci-app-mmconfig\" ],\n\t\t\t\"uci\": { \"mmconfig\": true }\n\t\t}\n\t},\n\t\n\t\"admin/modem/main/bands\": {\n\t\t\"title\": \"Bands\",\n\t\t\"order\": 63,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"modem/bands\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/root/usr/share/modeminfo/mmconfig",
    "content": "#!/bin/sh\n\n. /lib/functions.sh\n\n# get vars\n\nget_mm_config(){\n\tlocal device bands preffer\n\tconfig_get device $1 device\n\tconfig_get bands $1 bands\n\tconfig_get preffer $1 preffer\n\n\tmmcli -m $device > /dev/null 2>&1 && {\n\t\t\n\t\tsetpref=$(echo \"$preffer\" | awk -F'[:;]' '{ gsub(/[ \\t]+/, \"\", $0); gsub(\",\", \"|\", $0); print \"--set-\"$1\"-modes=\"$2\"  --set-\"$3\"-mode=\"$NF}')\n\t\tsetbands=$(echo $bands | awk -v OFS='|' '{$1=$1}1')\n\t\tdefbands=$(mmcli -J -m $device | jsonfilter -e '@[\"modem\"][\"generic\"][\"supported-bands\"].*' | awk '{bands = (NR==1 ? $1 : bands \"|\"$1)} END {print bands}')\n\t\tcurbands=$(mmcli -J -m $device | jsonfilter -e '@[\"modem\"][\"generic\"][\"current-bands\"].*' | awk '{bands = (NR==1 ? $1 : bands \"|\"$1)} END {print bands}')\n\t\tcurpref=$(mmcli -J -m $device | jsonfilter -e '@[\"modem\"][\"generic\"][\"current-modes\"]')\n\t\t[ -n \"$bands\" ] || {\n\t\t\tsetbands=$defbands\n\t\t}\n\t\t[ \"$curbands\" = \"$setbands\" ] && [ \"$preffer\" = \"$curpref\" ] || {\n\t\t\tmodemif=$(uci show network | grep \"$device\" | awk -F [.] '{print $2}')\n\t\t\t[ \"$preffer\" = \"$curpref\" ] ||\tmmcli -m $device $setpref\n\t\t\t[ \"$curbands\" = \"$setbands\" ] || mmcli -m $device --set-current-bands=\"$setbands\"\n\t\t\tsleep 3\n\t\t\tifup $modemif\n\t\t}\n\t}\n}\n\nconfig_load mmconfig\nconfig_foreach get_mm_config modem\n"
  },
  {
    "path": "luci/applications/luci-app-mmconfig/root/usr/share/rpcd/acl.d/luci-app-mmconfig.json",
    "content": "{\n\t\"luci-app-mmconfig\": {\n\t\t\"description\": \"Grant access to mmconfig configuration\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/init.d/mmconfig\": [ \"exec\" ]\n\t\t\t},\n\t\t\t\"cgi-io\": [ \"exec\" ], \n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"exec\" ],\n\t\t\t\t\"uci\": [ \"changes\", \"get\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"mmconfig\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"cgi-io\": [ \"exec\" ],\n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"exec\" ],\n\t\t\t\t\"uci\": [ \"add\", \"apply\", \"confirm\", \"delete\", \"order\", \"rename\", \"set\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"mmconfig\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Information dashboard for 3G/LTE dongle\nLUCI_DEPENDS:=+modeminfo\nPKG_LICENSE:=GPLv3\nPKG_VERSION:=0.4.7\nPKG_RELEASE:=3\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/README.md",
    "content": "# luci-app-modeminfo\n3G/LTE dongle information for OpenWrt LuCi\n\n\nluci-app-modeminfo is fork from https://github.com/IceG2020/luci-app-3ginfo\n\nSupported devices:\n\n - Quectel EC200T/EC21/EC25/EP06/EM12/EM160R-GL\n\n - SimCom SIM7600E-H/SIM7906/A7600/A7906-M2\n   \n - STYX MG8224\n\n - Huawei E3372 (LTE)/MU709/ME909\n\n - Sierra Wireless EM7455/EM9191\n\n - HP LT4220\n\n - Dell DW5821e/DW5829e\n\n - MEIGLink SLM750-R2/SLM820/SLM828\n \n - MikroTik R11e-LTE/R11e-LTE6\n\n - Fibocom NL668/NL678/L850/L860/NL952/FM150/FM190/FM350\n\n - Gosuncnwelink GM510\n\n - ThinkWill ML7820+\n\n - Yuge CLM920\n\n - ZTE MF823/MF823D\n\n<details>\n<summary>Package contents:</summary>\n\n|Package |Description |\n|:-------|:-----------|\n|luci-app-modeminfo |LuCI web interface |\n|modeminfo |common files |\n|modeminfo-qmi |Qualcomm MSM Interface support |\n|modeminfo-serial-quectel |Quectel modems support |\n|modeminfo-serial-telit |Telit LN940 (HP LT4220) modem support |\n|modeminfo-serial-huawei |Huawei MU709/ME909/E3372(stick mode, LTE only) modems support|\n|modeminfo-serial-sierra |Sierra EM7455/EM9191 modem support |\n|modeminfo-serial-simcom |SimCOM modems support |\n|modeminfo-serial-dell |Dell DW5821e/DW5829e modem support |\n|modeminfo-serial-fibocom |Fibocom LN668/NL678/NL952/FM150/FM190 modems support |\n|modeminfo-serial-xmm |Fibocom L850/L860/FM350 modems support |\n|modeminfo-serial-mikrotik |MikroTik R11e-LTE/R11e-LTE6 modems support |\n|modeminfo-serial-gosun |Gosuncnwelink GM510 support |\n|modeminfo-serial-tw |ThinkWill ML7820+ support |\n|modeminfo-serial-yuge |Yuge CLM920 support |\n|modeminfo-serial-meig |MEIGLink SLM750-R2/SLM820/SLM828 support |\n|modeminfo-serial-zte |ZTE MF823/MF823D support |\n|modeminfo-serial-styx |STYX MG8224 support |\n</details>\n\n<details>\n   <summary>Screenshots</summary>\n   \n   \n* Overview page. Short network info.\n\n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/applications/luci-app-modeminfo/screenshots/modeminfo-overview.png)\n   \n* Modeminfo index page. Verbose network info.\n\n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/applications/luci-app-modeminfo/screenshots/modeminfo-network.png)\n   \n* Modeminfo hardware page.\n\n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/applications/luci-app-modeminfo/screenshots/modeminfo-hardware.png)\n\n* Modeminfo setup page.\n\n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/applications/luci-app-modeminfo/screenshots/modeminfo-setup.png)\n\n</details>\n\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/htdocs/luci-static/resources/modem-indicator.js",
    "content": "'use strict';\n'require ui';\n'require poll';\n'require fs';\n'require uci';\n\nfunction getSignalColor(percent) {\n    if (percent === null || percent === undefined || percent < 5) {\n        return '#000000';\n    }\n    \n    let p = percent;\n    \n    if (p >= 80) return '#00cc00';\n    \n    if (p >= 67) {\n        const ratio = (p - 50) / 30;\n        const r = Math.floor(255 * (1 - ratio));\n        const g = 255;\n        return `rgb(${r}, ${g}, 0)`;\n    }\n    \n    if (p >= 35) {\n        const ratio = (p - 25) / 25;\n        const r = 255;\n        const g = Math.floor(165 + (90 * ratio));\n        return `rgb(${r}, ${g}, 0)`;\n    }\n    \n    const ratio = (p - 5) / 20;\n    const r = 255;\n    const g = Math.floor(165 * ratio);\n    return `rgb(${r}, ${g}, 0)`;\n}\n\n\nfunction getSignalState(percent) {\n    const p = percent || 0;\n    if (p < 10) return 'error';\n    if (p < 25) return 'warning';\n    if (p < 50) return 'warning';\n    return 'active';\n}\n\nfunction isUciIndexTwo() {\n    return L.resolveDefault(fs.exec_direct('/sbin/uci', ['get', 'modeminfo.@general[0].index']), '')\n        .then(function(value) {\n            return value.trim() === '2';\n        })\n        .catch(function() {\n            return false;\n        });\n}\n\nfunction updateIndicator() {\n\n\n\n    isUciIndexTwo().then(function(ok) {\n        if (!ok) {\n            ui.hideIndicator('modem-status');\n            return;\n        }\n\n    if (window.location.pathname.includes('/cgi-bin/luci/admin/modem/main')) {\n        ui.hideIndicator('modem-status');\n        return;\n    }\n    L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{\"modem\":[]}')\n    .then(function(res) {\n        var data = JSON.parse(res);\n        if (!data.modem || !data.modem.length) {\n            ui.hideIndicator('modem-status');\n            return;\n        }\n\n        var parts = data.modem.map(function(modem) {\n            var percent = parseInt(modem.csq_per);\n            var cops = modem.cops || '';\n            var mode = modem.mode + (parseInt(modem.lteca) > 0 ? '+' : '');\n            return { mode: mode, cops: cops, percent: percent };\n        });\n\n        var minPercent = Math.min.apply(null, parts.map(function(p) { return p.percent; }));\n        var state = getSignalState(minPercent);\n\n        var status = parts.map(function(p) {\n            return p.mode + ' ' + p.cops;\n        }).join(' | ');\n\n        ui.showIndicator('modem-status', status, null, state);\n\n        var indicator = document.querySelector('[data-indicator=\"modem-status\"]');\n        if (indicator) {\n            var html = parts.map(function(p) {\n                var color = getSignalColor(p.percent);\n\t\treturn '<span style=\"color:' + color + '; -webkit-text-stroke: 0.5px black; text-stroke: 0.5px black;\">●</span> ' + p.mode + ' ' + p.cops;\n            }).join(' | ');\n            indicator.innerHTML = html;\n        }\n    })\n    .catch(function() {\n        ui.hideIndicator('modem-status');\n    });\n    });\n}\n\nsetTimeout(function() {\n    updateIndicator();\n    poll.add(updateIndicator, 10);\n}, 500);\n\nreturn L.Class.extend({\n    __name__: 'ModemIndicator'\n});\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/htdocs/luci-static/resources/preload/modem-indicator.js",
    "content": "'use strict';\n'require modem-indicator';\n\nreturn L.Class.extend({\n    __name__: 'preload.modem-indicator'\n});\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/htdocs/luci-static/resources/view/modem/hw.js",
    "content": "'use strict';\n'require baseclass';\n'require form';\n'require fs';\n'require view';\n'require ui';\n'require uci';\n'require poll';\n'require dom';\n'require tools.widgets as widgets';\n\n/*\n\tCopyright Konstantine Shevlakov <shevlakov@132lan.ru> 2023-2026\n\n\tLicensed to the GNU General Public License v3.0.\n\n\tRefactored: bug fixes, XSS prevention, deduplication.\n*/\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/**\n * Safe getElementById.\n * @param {string} id\n * @returns {HTMLElement|null}\n */\nfunction getEl(id) {\n\treturn document.getElementById(id);\n}\n\n/**\n * Write a value into a cell by id.\n * Falls back to '--' when value is missing/placeholder.\n * Uses textContent to prevent XSS.\n *\n * @param {string} id    - Element id\n * @param {string} value - Raw value from modem JSON\n * @param {string} [suffix] - Optional suffix appended to a real value (e.g. ' °C')\n */\nfunction setCell(id, value, suffix) {\n\tconst el = getEl(id);\n\tif (!el) return;\n\tconst missing = !value || value === '--';\n\tel.textContent = missing ? '--' : value + (suffix || '');\n}\n\n// ─── Field definitions ────────────────────────────────────────────────────────\n// Each entry: { key: modem JSON field, suffix: optional unit string }\nconst MODEM_FIELDS = [\n\t{ key: 'device',   suffix: ''    },\n\t{ key: 'firmware', suffix: ''    },\n\t{ key: 'imsi',     suffix: ''    },\n\t{ key: 'iccid',    suffix: ''    },\n\t{ key: 'imei',     suffix: ''    },\n\t{ key: 'chiptemp', suffix: ' °C' },\n];\n\n// ─── Main view ────────────────────────────────────────────────────────────────\n\nreturn view.extend({\n\n\t// FIX: removed unused `data` parameter\n\tload: function() {\n\t\treturn L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{\"modem\": []}');\n\t},\n\n\tpolldata: poll.add(function() {\n\t\treturn L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{\"modem\": []}')\n\t\t\t.then(function(res) {\n\t\t\t\t// FIX: wrapped in try/catch — was crashing on invalid JSON\n\t\t\t\tlet json;\n\t\t\t\ttry {\n\t\t\t\t\tjson = JSON.parse(res);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.error('modeminfo hw: JSON parse error', e);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!json || !Array.isArray(json.modem)) return;\n\n\t\t\t\tfor (let i = 0; i < json.modem.length; i++) {\n\t\t\t\t\tconst modem = json.modem[i];\n\n\t\t\t\t\t// FIX: replaced 6 copy-pasted blocks with a single loop.\n\t\t\t\t\t// FIX: `var view` renamed — was shadowing the LuCI `view` module.\n\t\t\t\t\t// FIX: value '--' now explicitly written to cell (was silently ignored).\n\t\t\t\t\t// FIX: innerHTML → textContent to prevent XSS.\n\t\t\t\t\t// FIX: String.format(x) with no extra args replaced by direct assignment.\n\t\t\t\t\tfor (const { key, suffix } of MODEM_FIELDS) {\n\t\t\t\t\t\tsetCell(key + i, modem[key], suffix);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t}),\n\n\trender: function(data) {\n\t\t// FIX: wrapped in try/catch — was crashing on invalid JSON\n\t\tlet json;\n\t\ttry {\n\t\t\tjson = JSON.parse(data);\n\t\t} catch (e) {\n\t\t\tjson = { modem: [] };\n\t\t}\n\n\t\tconst m = new form.Map('modeminfo', _('Modeminfo: Hardware'), _('Hardware and sim-card info.'));\n\t\tconst s = m.section(form.TypedSection, 'general', null);\n\t\ts.anonymous = true;\n\n\t\tfor (let i = 0; i < json.modem.length; i++) {\n\t\t\tconst idx = i + 1;\n\n\t\t\t// Pre-build all element ids for this modem slot\n\t\t\tconst ids = {};\n\t\t\tfor (const { key } of MODEM_FIELDS) {\n\t\t\t\tids[key] = key + i;\n\t\t\t}\n\n\t\t\tlet o;\n\t\t\tif (json.modem.length > 1) {\n\t\t\t\ts.tab('modem' + i, _('Modem') + ' ' + idx);\n\t\t\t\to = s.taboption('modem' + i, form.HiddenValue, 'generic');\n\t\t\t} else {\n\t\t\t\to = s.option(form.HiddenValue, 'generic');\n\t\t\t}\n\n\t\t\to.render = L.bind(function() {\n\t\t\t\treturn E('div', {}, [\n\t\t\t\t\tE('h3', { 'class': 'data-tab' }),\n\t\t\t\t\tE('div', { 'class': 'cbi-section' }, [\n\t\t\t\t\t\tE('table', { 'class': 'table' }, [\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-2' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%' }, [_('Device')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': ids.device }, ['--']),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-1' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%' }, [_('Firmware')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': ids.firmware }, ['--']),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-2' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%' }, [_('IMSI')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': ids.imsi }, ['--']),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-1' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%' }, [_('ICCID')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': ids.iccid }, ['--']),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-2' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%' }, [_('IMEI')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': ids.imei }, ['--']),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-1' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%' }, [_('Chiptemp')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': ids.chiptemp }, ['--']),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t])\n\t\t\t\t\t])\n\t\t\t\t]);\n\t\t\t}, this.polldata);\n\n\t\t\to.anonymous = true;\n\t\t\to.rmempty   = true;\n\t\t}\n\n\t\treturn m.render();\n\t},\n\n\thandleSaveApply: null,\n\thandleSave:      null,\n\thandleReset:     null,\n});\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/htdocs/luci-static/resources/view/modem/main.js",
    "content": "'use strict';\n'require baseclass';\n'require form';\n'require fs';\n'require view';\n'require ui';\n'require uci';\n'require poll';\n'require dom';\n'require tools.widgets as widgets';\n\n/*\n\tCopyright Konstantine Shevlakov <shevlakov@132lan.ru> 2023\n\n\tLicensed to the GNU General Public License v3.0.\n\n\tRefactored: bug fixes, performance improvements, code deduplication.\n*/\n\nconst REGISTERED_STATUSES = [1, 6, 9];\nconst ROAMING_STATUSES    = [3, 5, 7, 10];\n\n// ─── Signal Icons ────────────────────────────────────────────────────────────\nconst SIGNAL_ICONS = [\n\t{ max: 10,       icn: 'signal-000-000.svg' },\n\t{ max: 25,       icn: 'signal-000-025.svg' },\n\t{ max: 50,       icn: 'signal-025-050.svg' },\n\t{ max: 75,       icn: 'signal-050-075.svg' },\n\t{ max: Infinity, icn: 'signal-075-100.svg' },\n];\n\n// ─── Registration Status Labels ───────────────────────────────────────────────\nconst REG_STATUSES = new Map([\n\t[0, _('No Registration')],\n\t[2, _('Searching')],  [8, _('Searching')],\n\t[3, _('Denied')],\n\t[4, _('Unknown')],\n\t[5, _('Roaming')],    [7, _('Roaming')], [10, _('Roaming')],\n]);\n\n// ─── Progress bar configuration ───────────────────────────────────────────────\n// calc(vn, mn): vn = clamped signal value, mn = min boundary\n// All formulas normalise the value to 0–100% range for the progress bar.\nconst PROGRESS_CONFIG = {\n\trssi: {\n\t\tselector: '#rssi',\n\t\tmin: -110, max: -50,\n\t\t// Linear map from [-110..-50] → [0..100]\n\t\tcalc: (vn, mn) => Math.floor(100 * (1 - (-50 - vn) / (-50 - mn))),\n\t},\n\trsrp: {\n\t\tselector: '#rsrp',\n\t\tmin: -140, max: -50,\n\t\t// Slightly expanded scale (×1.2) to better use bar width\n\t\tcalc: (vn, mn) => Math.floor(120 * (1 - (-50 - vn) / (-70 - mn))),\n\t},\n\tsinr: {\n\t\tselector: '#sinr',\n\t\tmin: -20, max: 30,\n\t\t// Linear map from [-20..30] → [0..100]\n\t\tcalc: (vn, mn) => Math.floor(100 - (100 * (1 - ((mn - vn) / (mn - 30))))),\n\t},\n\trsrq: {\n\t\tselector: '#rsrq',\n\t\tmin: -20, max: 0,\n\t\t// Proportional map from [-20..0] → [0..100]\n\t\tcalc: (vn, mn) => Math.floor(115 - (100 / mn) * vn),\n\t},\n\tecio: {\n\t\tselector: '#sinr',\n\t\tmin: -24, max: 0,\n\t\t// Same shape as rsrq but for EC/IO range [-24..0]\n\t\tcalc: (vn, mn) => Math.floor(100 - (100 / mn) * vn),\n\t},\n};\n\n// ─── LTE EARFCN → band/frequency table ───────────────────────────────────────\nconst LTE_BANDS = [\n\t{ min: 0,     max: 599,   frdl: 2110,  frul: 1920,  offset: 0,     band: '1'  },\n\t{ min: 600,   max: 1199,  frdl: 1930,  frul: 1850,  offset: 600,   band: '2'  },\n\t{ min: 1200,  max: 1949,  frdl: 1805,  frul: 1710,  offset: 1200,  band: '3'  },\n\t{ min: 1950,  max: 2399,  frdl: 2110,  frul: 1710,  offset: 1950,  band: '4'  },\n\t{ min: 2400,  max: 2469,  frdl: 869,   frul: 824,   offset: 2400,  band: '5'  },\n\t{ min: 2750,  max: 3449,  frdl: 2620,  frul: 2500,  offset: 2750,  band: '7'  },\n\t{ min: 3450,  max: 3799,  frdl: 925,   frul: 880,   offset: 3450,  band: '8'  },\n\t{ min: 6150,  max: 6449,  frdl: 791,   frul: 832,   offset: 6150,  band: '20' },\n\t{ min: 9210,  max: 9659,  frdl: 758,   frul: 703,   offset: 9210,  band: '28' },\n\t{ min: 9870,  max: 9919,  frdl: 452.5, frul: 462.5, offset: 9870,  band: '31' },\n\t{ min: 37750, max: 38249, frdl: 2570,  frul: 2570,  offset: 37750, band: '38' },\n\t{ min: 38650, max: 39649, frdl: 2300,  frul: 2300,  offset: 38650, band: '40' },\n\t{ min: 39650, max: 41589, frdl: 2496,  frul: 2496,  offset: 39650, band: '41' },\n];\n\n// ─── Non-LTE (UMTS/GSM) ARFCN → band/frequency table ─────────────────────────\nconst NON_LTE_BANDS = [\n\t{\n\t\tcondition: rfcn => rfcn >= 10562 && rfcn <= 10838,\n\t\tcalc:      rfcn => ({ offset: 950, dlfreq: rfcn / 5,                        ulfreq: (rfcn - 950) / 5,          band: 'IMT2100' }),\n\t},\n\t{\n\t\tcondition: rfcn => rfcn >= 2937  && rfcn <= 3088,\n\t\tcalc:      rfcn => ({ frul: 925,  ulfreq: 340 + (rfcn / 5),                 dlfreq: (340 + (rfcn / 5)) - 45,   band: 'UMTS900' }),\n\t},\n\t{\n\t\tcondition: rfcn => rfcn >= 955   && rfcn <= 1023,\n\t\tcalc:      rfcn => ({ frul: 890,  ulfreq: 890 + ((rfcn - 1024) / 5),        dlfreq: (890 + ((rfcn - 1024) / 5)) + 45, band: 'DSC900' }),\n\t},\n\t{\n\t\tcondition: rfcn => rfcn >= 512   && rfcn <= 885,\n\t\tcalc:      rfcn => ({ frul: 1710, ulfreq: 1710 + ((rfcn - 512) / 5),        dlfreq: (1710 + ((rfcn - 512) / 5)) + 95, band: 'DCS1800' }),\n\t},\n\t{\n\t\tcondition: rfcn => rfcn >= 1     && rfcn <= 124,\n\t\tcalc:      rfcn => ({ frul: 890,  ulfreq: 890 + (rfcn / 5),                 dlfreq: (890 + (rfcn / 5)) + 45,   band: 'GSM900' }),\n\t},\n];\n\nconst UMTS_MODES = /(HS|3G|UMTS|WCDMA)/i;\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/**\n * Safe getElementById wrapper.\n * @param {string} id\n * @returns {HTMLElement|null}\n */\nfunction getEl(id) {\n\treturn document.getElementById(id);\n}\n\n/**\n * Show or hide the grandparent row of a signal element.\n * @param {HTMLElement} el\n * @param {boolean} show\n */\nfunction setRowVisible(el, show) {\n\tconst row = el && el.parentElement && el.parentElement.parentElement;\n\tif (row) row.style.display = show ? '' : 'none';\n}\n\n/**\n * Update a cbi-progressbar element.\n * @param {string}  type   - Key in PROGRESS_CONFIG\n * @param {string}  value  - Raw signal value string (e.g. \"-85 dBm\")\n * @param {number}  max    - Boundary value (passed as second calc argument)\n * @param {number}  idx    - Modem index suffix for the element id\n */\nfunction updateProgressBar(type, value, max, idx) {\n\tconst config = PROGRESS_CONFIG[type];\n\tif (!config) return;\n\n\tconst pg = document.querySelector(`${config.selector}${idx}`);\n\tif (!pg) return;\n\n\tconst vn = Math.max(config.min, Math.min(config.max, parseInt(value) || 0));\n\tconst mn = parseInt(max) || 100;\n\tconst pc = Math.min(100, Math.max(0, config.calc(vn, mn)));\n\n\tpg.firstElementChild.style.width              = `${pc}%`;\n\tpg.firstElementChild.style.animationDirection = 'reverse';\n\tpg.setAttribute('title', String(value));\n}\n\n/**\n * Format distance string, returns empty string when unavailable.\n * @param {string|number} dist\n * @returns {string}\n */\nfunction formatDistance(dist) {\n\tif (!dist || dist === '--' || dist === '' || dist === '0.00') return '';\n\treturn ' ~' + dist + ' km';\n}\n\n/**\n * Build the operator status badge HTML safely using LuCI E() helper.\n * @param {Object} modem  - Single modem entry from JSON\n * @param {string} icon   - URL to signal icon\n * @param {string} reg    - Human-readable registration status\n * @returns {string}      - Safe innerHTML string via dom.content\n */\nfunction formatModemStatus(modem, icon, reg) {\n\tconst rg           = parseInt(modem.reg) || 0;\n\tconst p            = modem.csq_per || 0;\n\tconst cops         = modem.cops    || '--';\n\tconst color        = modem.csq_col || '#000000';\n\tconst distanceText = formatDistance(modem.distance);\n\n\tconst iconEl  = icon ? E('img', { 'class': 'modem-signal-icon', 'src': icon }) : null;\n\tconst boldEl  = E('b', { 'style': `color:${color}` }, [`${p}%`]);\n\n\tconst children = iconEl\n\t\t? [cops + ' ', iconEl, ' ', boldEl, distanceText]\n\t\t: [cops + ' ', boldEl, distanceText];\n\n\tif (REGISTERED_STATUSES.includes(rg)) {\n\t\treturn E('span', { 'class': 'ifacebadge' }, children).outerHTML;\n\t} else if (ROAMING_STATUSES.includes(rg)) {\n\t\treturn E('span', { 'class': 'ifacebadge' }, [`${cops} (${reg}) `, ...(iconEl ? [iconEl, ' '] : []), boldEl, distanceText]).outerHTML;\n\t} else {\n\t\treturn E('span', { 'class': 'ifacebadge' }, [reg || '--']).outerHTML;\n\t}\n}\n\n/**\n * Derive signal icon URL from signal percentage.\n * @param {number} pct\n * @returns {string}\n */\nfunction resolveSignalIcon(pct) {\n\tconst { icn } = SIGNAL_ICONS.find(({ max }) => pct <= max) || SIGNAL_ICONS[SIGNAL_ICONS.length - 1];\n\treturn L.resource(`view/modem/icons/${icn}`);\n}\n\n/**\n * Calculate DL/UL frequencies and band name for LTE mode.\n * @param {number} rfcn\n * @returns {{ dlfreq: number, ulfreq: number, band: string, frdl: number, frul: number, offset: number }}\n */\nfunction calcLteBand(rfcn) {\n\tconst b = LTE_BANDS.find(b => rfcn >= b.min && rfcn <= b.max);\n\tif (!b) return { frdl: 0, frul: 0, offset: 0, band: String(rfcn), dlfreq: 0, ulfreq: 0 };\n\tconst dlfreq = b.frdl + (rfcn - b.offset) / 10;\n\tconst ulfreq = b.frul + (rfcn - b.offset) / 10;\n\treturn { ...b, dlfreq, ulfreq };\n}\n\n/**\n * Calculate DL/UL frequencies and band name for non-LTE modes.\n * @param {number} rfcn\n * @returns {{ dlfreq: number, ulfreq: number, band: string }}\n */\nfunction calcNonLteBand(rfcn) {\n\tconst match = NON_LTE_BANDS.find(b => b.condition(rfcn));\n\treturn match ? match.calc(rfcn) : { ulfreq: 0, dlfreq: 0, band: String(rfcn) };\n}\n\n/**\n * Build a LAC/CID/eNB/Cell/PCI label and value string.\n * @param {Object} modem - Single modem entry\n * @returns {{ namecid: string, lactac: string }}\n */\nfunction buildCellId(modem) {\n\tconst { enbid, cell, pci, lac, cid } = modem;\n\tconst parts   = [lac, cid];\n\tlet namecid   = 'LAC/CID';\n\n\tif (enbid) {\n\t\tparts.push(enbid);\n\t\tnamecid += '/eNB ID';\n\n\t\tif (cell) {\n\t\t\tparts.push(cell);\n\t\t\tnamecid += '/Cell';\n\n\t\t\tif (pci) {\n\t\t\t\tparts.push(pci);\n\t\t\t\tnamecid += '/PCI';\n\t\t\t}\n\t\t}\n\t}\n\n\tconst lactac = parts.join(' / ');\n\treturn { namecid, lactac };\n}\n\n/**\n * Derive mode-specific labels and carrier aggregation info.\n * @param {Object} modem   - Single modem entry\n * @param {string} netmode\n * @param {string} band\n * @param {number} bw\n * @returns {{ carrier, bcc, bca, bwDisplay, namech, namesnr, namecid, lactac, namebnd }}\n */\nfunction buildModeInfo(modem, netmode, band, bw) {\n\tlet carrier  = '';\n\tlet bcc, bca, bwDisplay, namech, namesnr, namecid, lactac;\n\n\tif (netmode === 'LTE') {\n\t\tconst calte = modem.lteca;\n\t\tnamech  = 'EARFCN';\n\t\tnamesnr = 'SINR';\n\n\t\tif (calte > 0) {\n\t\t\tcarrier   = '+';\n\t\t\tbwDisplay = modem.bwca;\n\t\t\tbcc       = ` B${band}${modem.scc}`;\n\t\t\tbca       = bwDisplay ? ` / ${bwDisplay} MHz` : '';\n\t\t} else {\n\t\t\tbwDisplay = bw;\n\t\t\tbcc       = ` B${band}`;\n\t\t\tbca       = bw ? ` / ${bw} MHz` : '';\n\t\t}\n\n\t\tconst cellInfo = buildCellId(modem);\n\t\tnamecid = cellInfo.namecid;\n\t\tlactac  = cellInfo.lactac;\n\n\t} else if (UMTS_MODES.test(netmode)) {\n\t\tnamech  = 'UARFCN';\n\t\tnamesnr = 'ECIO';\n\t\tnamecid = 'LAC/CID';\n\t\tlactac  = `${modem.lac} / ${modem.cid}`;\n\t\tbcc     = ` ${band}`;\n\n\t} else {\n\t\tnamech  = 'ARFCN';\n\t\tnamesnr = 'SINR/ECIO';\n\t\tnamecid = 'LAC/CID';\n\t\tlactac  = `${modem.lac} / ${modem.cid}`;\n\t\tbcc     = ` ${band}`;\n\t}\n\n\tconst namebnd = bw\n\t\t? _('Network/Band/Bandwidth')\n\t\t: _('Network/Band');\n\n\treturn { carrier, bcc, bca: bca || '', bwDisplay, namech, namesnr, namecid, lactac, namebnd };\n}\n\n/**\n * Set textContent of element by id (if it exists).\n * @param {string} id\n * @param {string} text\n */\nfunction setText(id, text) {\n\tconst el = getEl(id);\n\tif (el) el.textContent = text;\n}\n\n/**\n * Set innerHTML of element by id (if it exists).\n * @param {string} id\n * @param {string} html\n */\nfunction setHtml(id, html) {\n\tconst el = getEl(id);\n\tif (el) el.innerHTML = html;\n}\n\n/**\n * Update a signal progress bar, hiding its parent row if value is missing.\n * @param {string} elId    - Element id (without modem index)\n * @param {number} idx     - Modem index\n * @param {*}      rawVal  - Raw value from modem JSON\n * @param {string} unit    - Unit suffix, e.g. ' dBm'\n * @param {string} type    - Key in PROGRESS_CONFIG\n * @param {number} boundary\n */\nfunction updateSignalBar(elId, idx, rawVal, unit, type, boundary) {\n\tconst el = getEl(elId + idx);\n\tif (!el) return;\n\tconst missing = !rawVal || rawVal === '--' || rawVal === '';\n\tsetRowVisible(el, !missing);\n\tif (!missing) {\n\t\tupdateProgressBar(type, rawVal + unit, boundary, idx);\n\t}\n}\n\n// ─── Main view ────────────────────────────────────────────────────────────────\n\nreturn view.extend({\n\n\tload: function() {\n\t\treturn L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{\"modem\": []}');\n\t},\n\n\tpolldata: poll.add(function() {\n\t\treturn L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{\"modem\": []}')\n\t\t\t.then(function(res) {\n\t\t\t\tlet json;\n\t\t\t\ttry {\n\t\t\t\t\tjson = JSON.parse(res);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.error('modeminfo: JSON parse error', e);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!json || !Array.isArray(json.modem)) return;\n\n\t\t\t\tfor (let i = 0; i < json.modem.length; i++) {\n\t\t\t\t\tconst modem   = json.modem[i];\n\t\t\t\t\tconst netmode = modem.mode  || '';\n\t\t\t\t\tconst rfcn    = modem.arfcn || 0;\n\n\t\t\t\t\t// ── Band / Frequency ────────────────────────────────────\n\t\t\t\t\tlet dlfreq, ulfreq, band, bw;\n\n\t\t\t\t\tif (netmode === 'LTE') {\n\t\t\t\t\t\t({ dlfreq, ulfreq, band } = calcLteBand(rfcn));\n\t\t\t\t\t\tconst bandwidths = [1.4, 3, 5, 10, 15, 20];\n\t\t\t\t\t\tbw = bandwidths[modem.bwdl] || '';\n\t\t\t\t\t} else {\n\t\t\t\t\t\t({ dlfreq, ulfreq, band } = calcNonLteBand(rfcn));\n\t\t\t\t\t\tbw = '';\n\t\t\t\t\t}\n\n\t\t\t\t\tconst arfcnStr = `${rfcn} (${dlfreq} / ${ulfreq} MHz)`;\n\n\t\t\t\t\t// ── Registration ────────────────────────────────────────\n\t\t\t\t\tconst rg  = Number(modem.reg);\n\t\t\t\t\tconst reg = REG_STATUSES.get(rg) || _('No Data');\n\n\t\t\t\t\t// ── Signal icon ─────────────────────────────────────────\n\t\t\t\t\tconst icon = resolveSignalIcon(modem.csq_per || 0);\n\n\t\t\t\t\t// ── Mode-specific labels ────────────────────────────────\n\t\t\t\t\tconst { carrier, bcc, bca, namech, namesnr, namecid, lactac, namebnd } =\n\t\t\t\t\t\tbuildModeInfo(modem, netmode, band, bw);\n\n\t\t\t\t\t// ── DOM updates ─────────────────────────────────────────\n\t\t\t\t\tsetHtml('status' + i,  formatModemStatus(modem, icon, reg));\n\n\t\t\t\t\tconst modeEl = getEl('mode' + i);\n\t\t\t\t\tif (modeEl) {\n\t\t\t\t\t\t// FIX: was `signal = 0` (assignment), must be `=== 0`\n\t\t\t\t\t\tif (modem.signal === 0 || modem.signal === '' || !netmode) {\n\t\t\t\t\t\t\tmodeEl.textContent = '--';\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmodeEl.textContent = `${netmode}${carrier} /${bcc}${bca}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tsetText('namebnd' + i, namebnd);\n\t\t\t\t\tsetText('chname'  + i, namech);\n\t\t\t\t\tsetText('namecid' + i, namecid);\n\t\t\t\t\tsetText('snrname' + i, namesnr);\n\t\t\t\t\tsetHtml('arfcn'   + i, arfcnStr);\n\t\t\t\t\tsetHtml('lac'     + i, lactac);\n\n\t\t\t\t\t// ── Progress bars ───────────────────────────────────────\n\t\t\t\t\t// RSSI always visible if element exists\n\t\t\t\t\tif (getEl('rssi' + i)) {\n\t\t\t\t\t\tif (!modem.rssi || modem.rssi === '') {\n\t\t\t\t\t\t\tsetText('rssi' + i, '--');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tupdateProgressBar('rssi', modem.rssi + ' dBm', -110, i);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// SINR / ECIO (shared element, hidden when unavailable)\n\t\t\t\t\tupdateSignalBar(\n\t\t\t\t\t\t'sinr', i, modem.sinr, ' dB',\n\t\t\t\t\t\tnetmode === 'LTE' ? 'sinr' : 'ecio',\n\t\t\t\t\t\tnetmode === 'LTE' ? -20 : -24\n\t\t\t\t\t);\n\n\t\t\t\t\t// RSRP (hidden when unavailable)\n\t\t\t\t\tupdateSignalBar('rsrp', i, modem.rsrp, ' dBm', 'rsrp', -140);\n\n\t\t\t\t\t// RSRQ (hidden when unavailable)\n\t\t\t\t\tupdateSignalBar('rsrq', i, modem.rsrq, ' dB',  'rsrq', -20);\n\t\t\t\t}\n\t\t\t});\n\t}),\n\n\trender: function(data) {\n\t\tlet json;\n\t\ttry {\n\t\t\tjson = JSON.parse(data);\n\t\t} catch (e) {\n\t\t\tjson = { modem: [] };\n\t\t}\n\n\t\tconst m = new form.Map('modeminfo', _('Modeminfo: Network'), _('Cellular network'));\n\t\tconst s = m.section(form.TypedSection, 'general', null);\n\t\ts.anonymous = true;\n\n\t\tfor (let i = 0; i < json.modem.length; i++) {\n\t\t\tconst idx      = i + 1;\n\t\t\tconst statusId = 'status' + i;\n\t\t\tconst modeId   = 'mode'   + i;\n\t\t\tconst namebndId= 'namebnd'+ i;\n\t\t\tconst chnameId = 'chname' + i;\n\t\t\tconst namecidId= 'namecid'+ i;\n\t\t\tconst arfcnId  = 'arfcn'  + i;\n\t\t\tconst lacId    = 'lac'    + i;\n\t\t\tconst rssiId   = 'rssi'   + i;\n\t\t\tconst sinrId   = 'sinr'   + i;\n\t\t\tconst snrnameId= 'snrname'+ i;\n\t\t\tconst rsrpId   = 'rsrp'   + i;\n\t\t\tconst rsrqId   = 'rsrq'   + i;\n\n\t\t\tlet o;\n\t\t\tif (json.modem.length > 1) {\n\t\t\t\ts.tab('modem' + i, _('Modem') + ' ' + idx);\n\t\t\t\to = s.taboption('modem' + i, form.HiddenValue, 'generic');\n\t\t\t} else {\n\t\t\t\to = s.option(form.HiddenValue, 'generic');\n\t\t\t}\n\n\t\t\to.render = L.bind(function() {\n\t\t\t\treturn E('div', {}, [\n\t\t\t\t\tE('h3', { 'class': 'data-tab' }),\n\t\t\t\t\tE('div', { 'class': 'cbi-section' }, [\n\t\t\t\t\t\tE('table', { 'class': 'table' }, [\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-2' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%' }, [_('Operator')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': statusId }, ['--']),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-1' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%', 'id': namebndId }, [_('Network/Band')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': modeId }, ['--']),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-2' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%', 'id': chnameId }, [_('E/U/ARFCN')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': arfcnId }, ['--']),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-1' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%', 'id': namecidId }, [_('LAC/CID')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': lacId }, ['--']),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-2' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%' }, [_('RSSI')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left' },\n\t\t\t\t\t\t\t\t\tE('div', { 'id': rssiId, 'class': 'cbi-progressbar', 'title': '--' }, E('div'))\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-1' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%', 'id': snrnameId }, [_('SINR/EcIO')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left' },\n\t\t\t\t\t\t\t\t\tE('div', { 'id': sinrId, 'class': 'cbi-progressbar', 'title': '--' }, E('div'))\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-2' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%' }, [_('RSRP')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left' },\n\t\t\t\t\t\t\t\t\tE('div', { 'id': rsrpId, 'class': 'cbi-progressbar', 'title': '--' }, E('div'))\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr cbi-rowstyle-1' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '50%' }, [_('RSRQ')]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left' },\n\t\t\t\t\t\t\t\t\tE('div', { 'id': rsrqId, 'class': 'cbi-progressbar', 'title': '--' }, E('div'))\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t])\n\t\t\t\t\t])\n\t\t\t\t]);\n\t\t\t}, this.polldata);\n\n\t\t\to.anonymous = true;\n\t\t\to.rmempty   = true;\n\t\t}\n\n\t\treturn m.render();\n\t},\n\n\thandleSaveApply: null,\n\thandleSave:      null,\n\thandleReset:     null,\n});\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/htdocs/luci-static/resources/view/modem/setup.js",
    "content": "'use strict';\n'require form';\n'require rpc';\n'require fs';\n'require view';\n'require uci';\n'require ui';\n'require tools.widgets as widgets'\n\n\n/*\n\tWritten by Konstantine Shevlakov at <shevlakov@132lan.ru> 2023 - 2026\n\n\tLicensed to the GNU General Public License v3.0.\n\n*/\n\nvar callSerialPort = rpc.declare({\n\tobject: 'file',\n\tmethod: 'list',\n\tparams: [ 'path' ],\n\texpect: { entries: [] },\n\tfilter: function(list, params) {\n\t\tvar rv = [];\n\t\tfor (var i = 0; i < list.length; i++)\n\t\t\tif (list[i].name.match(/^ttyACM/) ||\n\t\t\t\tlist[i].name.match(/^ttyUSB/) ||\n\t\t\t\tlist[i].name.match(/^wwan\\d+at\\d+/))\n\t\t\t\trv.push(params.path + list[i].name);\n\t\treturn rv.sort((a, b) => a.name > b.name);\n\t}\n});\n\nvar callQMIPort = rpc.declare({\n        object: 'file',\n        method: 'list',\n        params: [ 'path' ],\n        expect: { entries: [] },\n        filter: function(list, params) {\n                var rv = [];\n                for (var i = 0; i < list.length; i++)\n                        if (list[i].name.match(/^cdc-wdm/))\n                                rv.push(params.path + list[i].name);\n                return rv.sort();\n        }\n});\n\nvar callCheckQmiinfo = rpc.declare({\n        object: 'file',\n        method: 'stat',\n        params: [ 'path' ],\n        expect: { '': {} }\n});\n\nvar maindesc = _('Modeminfo: Configuration');\nvar mdesc = _('Configuration panel of Modeminfo.');\nvar qfdesc = _('Get modem data via qmi. Require install qminfo.');\nvar sdesc = _('Select serial port.');\nvar qdesc = _('Select qmi port.');\nvar lacdec = _('Show LAC and CID in decimal.');\nvar mmdesc = _('Get device hardware name via mmcli utility if aviable.');\nvar qmidesc = _('Set qmi mode.');\nvar idesc = _('Show short info.<br />Overview: show info on main page \\\"Cellular Network\\\" section<br />MenuBar: show info on menubar all pages<br />NOTICE: Don\\'t add too many modems in menubar, it may break the theme display.');\nvar portplace = _('Please select a port');\n\nreturn view.extend({\n\n\tload: function() {\n\t\treturn callCheckQmiinfo('/usr/bin/qminfo')\n\t\t\t.then(function(stat) {\n\t\t\t\treturn (stat && stat.size != null);\n\t\t\t})\n\t\t\t.catch(function() {\n\t\t\t\treturn false;\n\t\t\t});\n\t},\n\n\trender: function(qmiinfoInstalled){\n\n\t\tvar qfdesc = qmiinfoInstalled\n\t\t\t? _('Get modem data via qmi.')\n\t\t\t: _('Please install qminfo package.');\n\n\t\tvar m, s, o;\n\n\t\tm = new form.Map('modeminfo', maindesc, mdesc);\n\n\t\ts = m.section(form.TypedSection, 'general', _('General option'), null);\n\t\ts.anonymous = true;\n\n\t\to = s.option(form.ListValue, 'index', _('Short info'), idesc);\n\t\to.widget=\"radio\";\n\t\to.value(0,_('none'));\n\t\to.value(1,_('overview'));\n\t\to.value(2,_('menubar'));\n\t\to.default = '0';\n\t\to.rmempty = true;\n\n\t\to = s.option(form.Flag, 'decimail', _('Show decimal'), lacdec);\n\t\to.rmempty = true;\n\n\t\to = s.option(form.ListValue, 'delay', _('Interval'), _('Poll interval data'));\n\t\to.value('', _('none'));\n\t\to.value('1', '1 '+_('sec'));\n\t\to.value('2', '2 '+_('sec'));\n\t\to.value('5', '5 '+_('sec'));\n\t\to.value('10', '10 '+_('sec'));\n\t\to.value('30', '30 '+_('sec'));\n\n\t\ts = m.section(form.TypedSection, 'modeminfo', _('Devices setup'), null);\n\t\ts.anonymous = true;\n\t\ts.addremove = true;\n\n\t\to = s.option(form.Flag, 'qmi_mode', _('Use QMI'), qfdesc);\n\t\to.rmempty = true;\n\t\tif (!qmiinfoInstalled) {\n\t\t\to.readonly = true;\n\t\t\to.write = function() {};\n\t\t\to.cfgvalue = function() { return '0'; };\n\t\t}\n\n\t\to = s.option(form.ListValue, 'device', _('Data port'), sdesc);\n\t\to.load = function(section_id) {\n\t\t\treturn callSerialPort('/dev/').then(L.bind(function(devices) {\n\t\t\t\tthis.keylist = [];\n\t\t\t\tthis.vallist = [];\n\t\t\t\tdevices.reverse().forEach(device => this.value(device));\n\t\t\t\treturn form.Value.prototype.load.apply(this, [section_id]);\n\t\t\t}, this));\n\t\t};\n\t\to.placeholder = portplace;\n\t\to.rmempty = true;\n\t\to.depends('qmi_mode', '0');\n\n\t\to = s.option(form.ListValue, 'device_qmi', _('Data port'), qdesc);\n\t\to.load = function(section_id) {\n\t\t\treturn callQMIPort('/dev/').then(L.bind(function(devices) {\n\t\t\t\tthis.keylist = []; \n\t\t\t\tthis.vallist = [];\n\t\t\t\tfor (var i = 0; i < devices.length; i++)\n\t\t\t\t\tthis.value(devices[i]);\n\t\t\t\treturn form.Value.prototype.load.apply(this, [section_id]);\n\t\t\t}, this));\n\t\t};\n\t\to.placeholder = portplace;\n\t\to.rmempty = true;\n\t\to.depends('qmi_mode', '1');\n\n\t\to = s.option(form.Flag, 'mmcli_name', _('Name via mmcli'), mmdesc);\n\t\to.rmempty = true;\n\t\to.depends('qmi_mode', '0');\n\n\t\to = s.option(form.ListValue, 'qmi_trap', _('QMI mode'), qmidesc);\n\t\to.value('',_('Auto'));\n\t\to.value('qmi',_('Direct QMI'));\n\t\to.value('mbim',_('QMI over MBIM'));\n\t\to.rmempty = true;\n\t\to.depends('qmi_mode', '1');\n\n\t\treturn m.render();\n\t}\t\n});\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/htdocs/luci-static/resources/view/status/include/26_modeminfo.js",
    "content": "'use strict';\n'require baseclass';\n'require form';\n'require fs';\n'require ui';\n'require uci';\n\n/*\n\tWritten by Konstantine Shevlakov at <shevlakov@132lan.ru> 2023\n\n\tLicensed to the GNU General Public License v3.0.\n\n\tSpecial Thanks to Vladislav Kadulin aka @Kodo-kakaku\n\thttps://github.com/Kodo-kakaku/ to fix update overview page\n\n*/\n\nreturn baseclass.extend({\n\ttitle: _('Cellular network'),\n\n\tload: async function() {\n\t\tawait uci.load('modeminfo');\n\t\tvar index = uci.sections('modeminfo', 'general');\n\t\tif (!index || !index[0] || index[0].index !== \"1\") {\n\t\t\treturn null;\n\t\t};\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\treject(new Error('Timeout: modeminfo execution took too long'));\n\t\t\t}, 30000); // 30 sec timeout\n\n\t\t\tfs.exec_direct('/usr/bin/modeminfo')\n\t\t\t\t.then(result => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve(result);\n\t\t\t\t})\n\t\t\t\t.catch(error => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\treject(error);\n\t\t\t\t});\n\t\t}).catch(error => {\n\t\t\tconsole.error('Error loading modeminfo:', error);\n\t\t\treturn '';\n\t\t});\n\t},\n\n\trender: function(data){\n\t\tif (!data || data.trim() === '') {\n\t\t\tvar index = uci.sections('modeminfo', 'general');\n\t\t\tif (!index || !index[0] || index[0].index !== \"1\") {\n\t\t\t\treturn null;\n\t\t\t} else {\n\t\t\t\treturn E('div', { \n\t\t\t\t\t'class': 'cbi-section',\n\t\t\t\t\t'style': 'color: #999; text-align: center;'\n\t\t\t\t}, _('Wait devices information'));\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tvar json = JSON.parse(data);\n\t\t\tvar index = uci.sections('modeminfo', 'general');\n\t\t\tlet modemTBL = E('div', { 'class': 'cbi-section' });\n\n\t\t\tif (json.modem && index[0].index == \"1\"){\n\t\t\t\tfor (var i = 0; i < json.modem.length; i++) {\n\t\t\t\t\tvar signal = document.createElement('div');\n\t\t\t\t\tvar icn;\n\t\t\t\t\tvar signalIcons = [\n\t\t\t\t\t\t{ max: -1, icn: 'signal-000-000.svg' },\n\t\t\t\t\t\t{ max: 0, icn: 'signal-000-000.svg' },\n\t\t\t\t\t\t{ max: 10, icn: 'signal-000-000.svg' },\n\t\t\t\t\t\t{ max: 25, icn: 'signal-000-025.svg' },\n\t\t\t\t\t\t{ max: 50, icn: 'signal-025-050.svg' },\n\t\t\t\t\t\t{ max: 75, icn: 'signal-050-075.svg' },\n\t\t\t\t\t\t{ max: Infinity, icn: 'signal-075-100.svg' }\n\t\t\t\t\t];\n\n\t\t\t\t\tvar p = json.modem[i].csq_per || 0;\n\t\t\t\t\tvar { icn } = signalIcons.find(({ max }) => p <= max);\n\t\t\t\t\tvar icon = L.resource(`view/modem/icons/${icn}`);\n\n\t\t\t\t\tvar per = p+'%';\n\n\t\t\t\t\tvar ca;\n\t\t\t\t\tif (json.modem[i].lteca > 0) {\n\t\t\t\t\t\tca = \"+\";\n\t\t\t\t\t} else {\n\t\t\t\t\t\tca = \"\";\n\t\t\t\t\t}\n\n\t\t\t\t\tsignal.innerHTML = String.format(\n\t\t\t\t\t\t'<span class=\"ifacebadge\">' + json.modem[i].cops  + \" \" +  \n\t\t\t\t\t\t'<img src=\"%s\"/><b>'+per.fontcolor(json.modem[i].csq_col) + \"</b> \" + \n\t\t\t\t\t\tjson.modem[i].mode+ca + '</span>', icon, p );\n\n\t\t\t\t\tmodemTBL.append( \n\t\t\t\t\t\tE('table', { 'class': 'table' }, [\n\t\t\t\t\t\t\tE('tr', { 'class': 'tr' }, [\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'width': '33%' }, [ json.modem[i].device ]),\n\t\t\t\t\t\t\t\tE('td', { 'class': 'td left', 'id': 'device' }, [ signal ])\n\t\t\t\t\t\t\t])\n\t\t\t\t\t\t])\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t\treturn modemTBL;\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tconsole.error('Error parsing modeminfo data:', error);\n\t\t\treturn E('div', { \n\t\t\t\t'class': 'cbi-section',\n\t\t\t\t'style': 'color: #999; text-align: center;'\n\t\t\t}, _('Error loading information'));\n\t\t}\n\t},\n});\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/po/ru/modeminfo.po",
    "content": "\"Language: ru\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Last-Translator: Konstantine Shevlyakov <shevlakov@132lan.ru>\\n\"\n\nmsgid \"Modem\"\nmsgstr \"Модем\"\n\nmsgid \"Modeminfo\"\nmsgstr \"Модеминфо\"\n\nmsgid \"Network\"\nmsgstr \"Сеть\"\n\nmsgid \"Hardware\"\nmsgstr \"Оборудование\"\n\nmsgid \"Setup\"\nmsgstr \"Настройка\"\n\nmsgid \"Modeminfo: Network\"\nmsgstr \"Модеминфо: Сеть\"\n\nmsgid \"No Registration\"\nmsgstr \"Нет регистрации\"\n\nmsgid \"Registered\"\nmsgstr \"Зарегистрирован\"\n\nmsgid \"Searching\"\nmsgstr \"Поиск сети\"\n\nmsgid \"Denied\"\nmsgstr \"Запрет регистрации\"\n\nmsgid \"Unknown\"\nmsgstr \"Неизвестно\"\n\nmsgid \"Roaming\"\nmsgstr \"Гостевая сеть\"\n\nmsgid \"No Data\"\nmsgstr \"Нет данных\"\n\nmsgid \"Network/Band/Bandwidth\"\nmsgstr \"Сеть/Диапазон/Полоса\"\n\nmsgid \"Network/Band\"\nmsgstr \"Сеть/Диапазон\"\n\nmsgid \"Cellular network\"\nmsgstr \"Сотовая сеть\"\n\nmsgid \"Operator\"\nmsgstr \"Оператор\"\n\nmsgid \"Status\"\nmsgstr \"Состояние\"\n\nmsgid \"Modeminfo: Hardware\"\nmsgstr \"Модеминфо: Устройство\"\n\nmsgid \"Hardware and sim-card info.\"\nmsgstr \"Информация о модеме и сим-карте\"\n\nmsgid \"Device\"\nmsgstr \"Устройство\"\n\nmsgid \"Firmware\"\nmsgstr \"Встроенное ПО\"\n\nmsgid \"Chiptemp\"\nmsgstr \"Температура\"\n\nmsgid \"In automatic mode detect first answered DATA port.\"\nmsgstr \"В автоматическом режиме находит первый отвечающий порт данных.\"\n\nmsgid \"Please select a port\"\nmsgstr \"Пожалуйста выберите порт\"\n\nmsgid \"Modeminfo: Configuration\"\nmsgstr \"Модеминфо: Конфигурация\"\n\nmsgid \"Configuration panel of Modeminfo.\"\nmsgstr \"Страница конфигурации Modeminfo.\"\n\nmsgid \"Use QMI\"\nmsgstr \"Использовать QMI\"\n\nmsgid \"Get modem data via qmi. Require install qminfo.\"\nmsgstr \"Получать данные модема через qmi. Требуется установленный qminfo.\"\n\nmsgid \"Get modem data via qmi.\"\nmsgstr \"Получать данные модема через qmi.\"\n\nmsgid \"Please install qminfo package.\"\nmsgstr \"Установите пакет qminfo.\"\n\nmsgid \"Data port\"\nmsgstr \"Порт данных\"\n\nmsgid \"Select serial port.\"\nmsgstr \"Выберите последовательный порт.\"\n\nmsgid \"Select qmi port.\"\nmsgstr \"Выберите порт qmi.\"\n\nmsgid \"Show decimal\"\nmsgstr \"Показать в десятичных\"\n\nmsgid \"Show LAC and CID in decimal.\"\nmsgstr \"Показать LAC и CID соты в десятичных.\"\n\nmsgid \"Get device hardware name via mmcli utility if aviable.\"\nmsgstr \"Если доступно, использовать утилиту mmcli для имени модема.\"\n\nmsgid \"Name via mmcli\"\nmsgstr \"Имя модема через mmcli\"\n\nmsgid \"QMI mode\"\nmsgstr \"Режим QMI\"\n\nmsgid \"Set qmi mode.\"\nmsgstr \"Задействовать режим QMI.\"\n\nmsgid \"Auto\"\nmsgstr \"Автоматически\"\n\nmsgid \"Direct QMI\"\nmsgstr \"Только QMI\"\n\nmsgid \"QMI over MBIM\"\nmsgstr \"QMI через MBIM\"\n\nmsgid \"Short info\"\nmsgstr \"Краткая информация\"\n\nmsgid \"Show short info.<br />Overview: show info on main page \\\"Cellular Network\\\" section<br />MenuBar: show info on menubar all pages<br />NOTICE: Don\\'t add too many modems in menubar, it may break the theme display.\"\nmsgstr \"Показвать краткую информацию<br />На главной: показывать главной странице раздел \\\"Сотовая сеть\\\"<br />Меню: показывать в строке меню на всех страницах<br />ВНИМАНИЕ: не добавляйте слишком много устройств в строку меню, это может сломать отображение.\"\n\nmsgid \"overview\"\nmsgstr \"на главной\"\n\nmsgid \"menubar\"\nmsgstr \"в строке меню\"\n\nmsgid \"sec\"\nmsgstr \"сек\"\n\nmsgid \"none\"\nmsgstr \"нет\"\n\nmsgid \"Interval\"\nmsgstr \"Интервал\"\n\nmsgid \"Poll interval data\"\nmsgstr \"Интервал обновления данных\"\n\nmsgid \"General option\"\nmsgstr \"Общие настройки\"\n\nmsgid \"Devices setup\"\nmsgstr \"Настройки устройств\"\n\nmsgid \"Wait devices information\"\nmsgstr \"Ожидание ответа от устройств\"\n\nmsgid \"Error loading information\"\nmsgstr \"Получение данных завершилось ошибкой\"\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/po/zh_Hans/modeminfo.po",
    "content": "msgid \"\"\nmsgstr \"Content-Type: text/plain; charset=UTF-8\"\n\nmsgid \"Modem\"\nmsgstr \"移动网络\"\n\nmsgid \"Modeminfo\"\nmsgstr \"调制解调器信息\"\n\nmsgid \"Network\"\nmsgstr \"网络\"\n\nmsgid \"China Mobile\"\nmsgstr \"中国移动\"\n\nmsgid \"China Unicom\"\nmsgstr \"中国联通\"\n\nmsgid \"China Telecom\"\nmsgstr \"中国电信\"\n\nmsgid \"Hardware\"\nmsgstr \"硬件\"\n\nmsgid \"Setup\"\nmsgstr \"设置\"\n\nmsgid \"Modeminfo: Network\"\nmsgstr \"调制解调器信息: 网络\"\n\nmsgid \"No Registration\"\nmsgstr \"未注册\"\n\nmsgid \"Registered\"\nmsgstr \"已注册\"\n\nmsgid \"Searching\"\nmsgstr \"搜索中\"\n\nmsgid \"Denied\"\nmsgstr \"禁止注册\"\n\nmsgid \"Unknown\"\nmsgstr \"未知\"\n\nmsgid \"Roaming\"\nmsgstr \"漫游\"\n\nmsgid \"No Data\"\nmsgstr \"无数据\"\n\nmsgid \"Network/Band/Bandwidth\"\nmsgstr \"网络/频段/带宽\"\n\nmsgid \"Network/Band\"\nmsgstr \"网络/频段\"\n\nmsgid \"Cellular network\"\nmsgstr \"蜂窝网络\"\n\nmsgid \"Operator\"\nmsgstr \"运营商\"\n\nmsgid \"Status\"\nmsgstr \"状态\"\n\n\nmsgid \"Modeminfo: Hardware\"\nmsgstr \"调制解调器信息: 硬件\"\n\nmsgid \"Hardware and sim card info.\"\nmsgstr \"硬件和sim卡信息。\"\n\nmsgid \"Device\"\nmsgstr \"设备\"\n\nmsgid \"Firmware\"\nmsgstr \"固件\"\n\nmsgid \"Chiptemp\"\nmsgstr \"芯片温度\"\n\nmsgid \"In automatic mode detect first answered DATA port.\"\nmsgstr \"在自动模式下，检测第一个应答的DATA端口。\"\n\nmsgid \"Please select a port\"\nmsgstr \"请选择端口\"\n\nmsgid \"Modeminfo: Configuration\"\nmsgstr \"调制解调器信息：配置\"\n\nmsgid \"Configuration panel of Modeminfo.\"\nmsgstr \"调制解调器信息配置面板。\"\n\nmsgid \"Use QMI\"\nmsgstr \"使用 QMI\"\n\nmsgid \"Get modem data via qmicli (experimental). Require install qmi-utils.\"\nmsgstr \"通过qmicli获取调制解调器数据（实验）。需要安装qmi utils。\"\n\nmsgid \"Data port\"\nmsgstr \"数据端口\"\n\nmsgid \"Select serial port.\"\nmsgstr \"选择串口。\"\n\nmsgid \"Select qmi port.\"\nmsgstr \"选择qmi端口。\"\n\nmsgid \"Show decimal\"\nmsgstr \"十进制显示\"\n\nmsgid \"Show LAC and CID in decimal.\"\nmsgstr \"以十进制显示LAC和CID。\"\n\nmsgid \"Get device hardware name via mmcli utility if aviable.\"\nmsgstr \"如果可用，请通过mmcli 获取设备硬件名称。\"\n\nmsgid \"Name via mmcli\"\nmsgstr \"通过mmcli命名\"\n\nmsgid \"QMI proxy\"\nmsgstr \"QMI 代理\"\n\nmsgid \"Enable qmi-proxy mode.\"\nmsgstr \"启用qmi代理模式。\"\n\nmsgid \"Index page\"\nmsgstr \"主页显示\"\n\nmsgid \"Short info on Overview page\"\nmsgstr \"在概览页面显示简单的信息。\"\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/root/usr/share/luci/menu.d/luci-app-modeminfo.json",
    "content": "{\n\t\"admin/modem\": {\n\t\t\"title\": \"Modem\",\n\t\t\"order\": 45,\n\t\t\"action\": {\n\t\t\t\"type\": \"firstchild\",\n\t\t\t\"recurse\": true\n\t\t}\n\t},\n\n\t\"admin/modem/main\": {\n\t\t\"title\": \"Modeminfo\",\n\t\t\"order\": 10,\n\t\t\"action\": {\n\t\t\t\"type\": \"alias\",\n\t\t\t\"path\": \"admin/modem/main/main\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"acl\": [ \"luci-app-modeminfo\" ],\n\t\t\t\"uci\": { \"modeminfo\": true }\n\t\t}\n\t},\n\n\t\"admin/modem/main/main\": {\n\t\t\"title\": \"Network\",\n\t\t\"order\": 51,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"modem/main\"\n\t\t}\n\t},\n\t\n\t\"admin/modem/main/hw\": {\n\t\t\"title\": \"Hardware\",\n\t\t\"order\": 52,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"modem/hw\"\n\t\t}\n\t},\n\t\n\t\"admin/modem/main/config\": {\n\t\t\"title\": \"Setup\",\n\t\t\"order\": 53,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"modem/setup\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-modeminfo/root/usr/share/rpcd/acl.d/luci-app-modeminfo.json",
    "content": "{\n\t\"luci-app-modeminfo\": {\n\t\t\"description\": \"Grant access to modeminfo configuration\",\n\t\t\"read\": {\n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"read\" ],\n\t\t\t\t\"luci\": [ \"getConntrackHelpers\" ]\n\t\t\t},\n\t\t\t\"file\": {\n\t\t\t\t\"/usr/bin/modeminfo\": [ \"exec\" ],\n\t\t\t\t\"/usr/share/modeminfo/scripts/rmtmp\": [ \"exec\" ],\n\t\t\t\t\"/sbin/uci\": [ \"exec\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"modeminfo\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"write\" ]\n\t\t\t},\n\t\t\n\t\t\t\"uci\": [ \"modeminfo\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-mwan3-ledhelper/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=MWAN3 Ledhelper simple webUI\nLUCI_DEPENDS:=+luci-app-mwan3 +mwan3-ledhelper\nPKG_LICENSE:=GPLv3\nPKG_VERSION:=0.0.1\nPKG_RELEASE:=3\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-mwan3-ledhelper/htdocs/luci-static/resources/view/mwan3/network/led.js",
    "content": "'use strict';\n'require form';\n'require fs';\n'require view';\n'require uci';\n'require rpc';\n'require ui';\n'require network';\n'require tools.widgets as widgets'\n\nvar callLEDs = rpc.declare({\n    object: 'luci',\n    method: 'getLEDs',\n    expect: { '': {} }\n});\n\nreturn view.extend({\n    load: function() {\n        return Promise.all([\n            callLEDs(),\n            L.resolveDefault(fs.list('/www' + L.resource('view/system/led-trigger')), []),\n            // load config mwan3\n            fs.read('/etc/config/mwan3').catch(function(err) { return ''; })\n        ]).then(function(data) {\n            var plugins = data[1];\n            var mwan3Config = data[2];\n            var tasks = [];\n\n            // parce ifaces mwan3 config\n            var mwan3Interfaces = [];\n            if (mwan3Config) {\n                var lines = mwan3Config.split('\\n');\n                for (var i = 0; i < lines.length; i++) {\n                    var line = lines[i].trim();\n                    var match = line.match(/^config interface '([^']+)'/);\n                    if (match) {\n                        mwan3Interfaces.push(match[1]);\n                    }\n                }\n            }\n\n            for (var i = 0; i < plugins.length; i++) {\n                var m = plugins[i].name.match(/^(.+)\\.js$/);\n\n                if (plugins[i].type != 'file' || m == null)\n                    continue;\n\n                tasks.push(L.require('view.system.led-trigger.' + m[1]).then(L.bind(function(name){\n                    return L.resolveDefault(L.require('view.system.led-trigger.' + name)).then(function(form) {\n                        return {\n                            name: name,\n                            form: form,\n                        };\n                    });\n                }, this, m[1])));\n            }\n\n            return Promise.all(tasks).then(function(plugins) {\n                var value = {};\n                value[0] = data[0];\n                value[1] = plugins;\n                value[2] = mwan3Interfaces; //  add mwan3 iface \n                return value;\n            });\n        });\n    },\n\n    render: function(data) {\n        var m, s, o, triggers = [];\n        var leds = data[0];\n        var mwan3Interfaces = data[2]; // get ifaces from mwan3 config\n\n        for (var k in leds)\n            for (var i = 0; i < leds[k].triggers.length; i++)\n                triggers[i] = leds[k].triggers[i];\n\n        m = new form.Map('mwan3_led', _('MWAN3 Ledhelper'), _('Flash led on link state.'));\n        s = m.section(form.GridSection, 'led', null);\n        s.anonymous = false;\n        s.addremove = true;\n\n        // filter NetworkSelect\n        o = s.option(widgets.NetworkSelect, 'iface', _('Set interface'));\n        o.exclude = s.section;\n        o.nocreate = true;\n        o.optional = true;\n        //  filter interfaces\n        o.filter = function(section_id, value) {\n            return mwan3Interfaces.indexOf(value) !== -1;\n        };\n\n        o = s.option(form.ListValue, 'led_on', _('Select LED online'));\n        Object.keys(leds).sort().forEach(function(name) {\n            o.value(name);\n        });\n        o.value('', _('Exclude'));\n        o.default = '';\n        o.rmempty = true;\n\n        o = s.option(form.ListValue, 'led_off', _('Select LED offline'));\n        Object.keys(leds).sort().forEach(function(name) {\n            o.value(name);\n        });\n\to.value('', _('Exclude'));\n\to.default = '';\n\to.rmempty = true;\n\n        return m.render();\n    }\n});\n"
  },
  {
    "path": "luci/applications/luci-app-mwan3-ledhelper/po/ru/mwan3-ledhelper.po",
    "content": "\"Language: ru\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Last-Translator: Konstantine Shevlakov <shevlakov@132lan.ru>\\n\"\n\nmsgid \"Flash led on link state.\"\nmsgstr \"Индикация светодиодов\"\n\nmsgid \"Name\"\nmsgstr \"Имя\"\n\nmsgid \"Set interface\"\nmsgstr \"Интерфейс\"\n\nmsgid \"Exclude\"\nmsgstr \"Не использовать\"\n\nmsgid \"Select LED online\"\nmsgstr \"LED \\\"в сети\\\"\"\n\nmsgid \"Select LED offline\"\nmsgstr \"LED \\\"вне сети\\\"\"\n"
  },
  {
    "path": "luci/applications/luci-app-mwan3-ledhelper/root/usr/share/luci/menu.d/luci-app-mwan3-ledhelper.json",
    "content": "{\n\t\"admin/network/mwan3/led\": {\n\t\t\"title\": \"LED\",\n\t\t\"order\": 55,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"mwan3/network/led\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-mwan3-ledhelper/root/usr/share/rpcd/acl.d/luci-app-mwan3-ledhelper.json",
    "content": "{\n\t\"luci-app-mwan3-ledhelper\": {\n\t\t\"description\": \"Grant access to MWAN3 LED configuration\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/init.d/mwan3\": [ \"exec\" ],\n\t\t\t\t\"/etc/config/mwan3\": [ \"read\" ]\n\t\t\t},\n\t\t\t\"cgi-io\": [ \"exec\" ],\n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"exec\" ],\n\t\t\t\t\"uci\": [ \"changes\", \"get\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"mwan3_led\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"cgi-io\": [ \"exec\" ],\n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"exec\" ],\n\t\t\t\t\"uci\": [ \"add\", \"apply\", \"confirm\", \"delete\", \"order\", \"rename\", \"set\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"mwan3_led\" ]\n\t\t}\n\t}\n}"
  },
  {
    "path": "luci/applications/luci-app-ota/Makefile",
    "content": "#\n# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Simple OTA Update for OpenWrt\n#LUCI_DEPENDS:= +luci-app-firewall\n\nPKG_LICENSE:=Apache-2.0\nPKG_VERSION:=0.0.1\nPKG_RELEASE:=3\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-ota/htdocs/luci-static/resources/view/system/ota.js",
    "content": "'use strict';\n'require dom';\n'require fs';\n'require ui';\n'require view';\n\n/*\n * OTA Update Application\n */\n\nreturn view.extend({\n    load: function() {\n        return Promise.resolve();\n    },\n\n    checkInitialState: function() {\n        var self = this;\n        this.verifyCheckResult().then(function(success) {\n            if (success) {\n                // If files already exist, disable Check button\n                self.checkButton.disabled = true;\n                // And show information about available update\n                self.statusDiv.innerHTML = '';\n                self.statusDiv.appendChild(E('div', { \n                    'class': 'alert-message success'\n                }, [\n                    E('h3', {}, _('Update Available')),\n                    E('pre', { \n                        'style': 'white-space: pre-wrap; background: #f5f5f5; padding: 10px; border-radius: 3px; color: black;' \n                    }, self.changelog || '')\n                ]));\n                // Activate Upgrade button\n                self.upgradeButton.disabled = false;\n            }\n        }).catch(function(err) {\n            // No files, leave Check button active\n        });\n    },\n\n    render: function() {\n        var self = this;\n\n        // Create control elements\n        this.checkButton = E('button', {\n            'class': 'btn cbi-button cbi-button-positive important',\n            'click': ui.createHandlerFn(this, 'handleCheck')\n        }, _('Check for Updates'));\n\n        this.upgradeButton = E('button', {\n            'class': 'btn cbi-button cbi-button-negative important',\n            'disabled': true,\n            'click': ui.createHandlerFn(this, 'handleUpgrade')\n        }, _('Upgrade'));\n\n        this.statusDiv = E('div', { 'class': 'cbi-section' });\n        this.progressBar = E('div', {\n            'style': 'display: none; margin: 10px 0;'\n        });\n\n        // Main layout\n        var container = E('div', { 'class': 'cbi-map' }, [\n            E('h2', {}, _('OTA System Update')),\n            E('div', { 'class': 'cbi-section' }, [\n                this.checkButton,\n                ' ',\n                this.upgradeButton\n            ]),\n            this.progressBar,\n            this.statusDiv\n        ]);\n\n        // Check initial state when page loads\n        this.checkInitialState();\n\n        return container;\n    },\n\n    handleCheck: function() {\n        var self = this;\n        \n        this.checkButton.disabled = true;\n        this.statusDiv.innerHTML = '';\n        this.statusDiv.appendChild(E('div', { \n            'class': 'spinner' \n        }, _('Checking for updates...')));\n\n        // Execute update check\n        return fs.exec('/usr/share/ota.sh', ['check'])\n            .then(function() {\n                return self.verifyCheckResult();\n            })\n            .then(function(success) {\n                if (success) {\n                    self.upgradeButton.disabled = false;\n                    self.statusDiv.innerHTML = '';\n                    self.statusDiv.appendChild(E('div', { \n                        'class': 'alert-message success'\n                    }, [\n                        E('h3', {}, _('Update Available')),\n                        E('pre', { \n                            'style': 'white-space: pre-wrap; background: #f5f5f5; padding: 10px; border-radius: 3px; color: black;' \n                        }, self.changelog || '')\n                    ]));\n                } else {\n                    self.statusDiv.innerHTML = '';\n                    self.statusDiv.appendChild(E('div', { \n                        'class': 'alert-message warning'\n                    }, _('No updates available or check failed')));\n                }\n                self.checkButton.disabled = false;\n            })\n            .catch(function(err) {\n                self.statusDiv.innerHTML = '';\n                self.statusDiv.appendChild(E('div', { \n                    'class': 'alert-message error'\n                }, _('Check failed: ') + (err.message || err)));\n                self.checkButton.disabled = false;\n            });\n    },\n\n    verifyCheckResult: function() {\n        var self = this;\n        \n        return Promise.all([\n            fs.stat('/tmp/profiles.json').catch(function() { return null; }),\n            fs.stat('/tmp/update.lock').catch(function() { return null; }),\n            fs.read('/tmp/changelog.txt')\n                .then(function(content) {\n                    self.changelog = content;\n                    return content && content.length > 0;\n                })\n                .catch(function() { return false; })\n        ]).then(function(results) {\n            // Check that all files exist and changelog is not empty\n            return results[0] !== null && \n                   results[1] !== null && \n                   results[2] === true;\n        });\n    },\n\n    handleUpgrade: function() {\n        var self = this;\n        \n        this.upgradeButton.disabled = true;\n        this.checkButton.disabled = true;\n        this.progressBar.style.display = 'block';\n        \n        // Initialize progress bar once\n        this.progressBar.innerHTML = '';\n        \n        // Create progress bar title\n        this.progressTitle = E('div', { \n            'class': 'cbi-progressbar-title' \n        }, _('Starting upgrade...'));\n        this.progressBar.appendChild(this.progressTitle);\n        \n        // Create progress bar container\n        this.currentProgressBar = E('div', {\n            'class': 'cbi-progressbar',\n            'style': 'margin: 10px 0;'\n        }, E('div', { 'style': 'width: 0%' }));\n        this.progressBar.appendChild(this.currentProgressBar);\n\n        // First start progress simulation\n        this.simulateProgressBar();\n\n        // Then start update process\n        return fs.exec('/usr/share/ota.sh', ['upgrade'])\n            .then(function(result) {\n                // Wait for progress simulation to complete to 100%\n                return new Promise(function(resolve) {\n                    var checkCompletion = function() {\n                        if (self.progressInterval) {\n                            setTimeout(checkCompletion, 100);\n                        } else {\n                            resolve(result);\n                        }\n                    };\n                    checkCompletion();\n                });\n            })\n            .then(function(result) {\n                // Show final message\n                self.progressBar.style.display = 'none';\n                self.statusDiv.innerHTML = '';\n                self.statusDiv.appendChild(E('div', { \n                    'class': 'alert-message warning'\n                }, _('Download completed successfully! Start upgrade.<br />DO NOT POWER OFF THIS DEVICE!<br />System will be upgrade completed after reboot!')));\n                \n                self.upgradeButton.disabled = false;\n                self.checkButton.disabled = false;\n            })\n            .catch(function(err) {\n                // Stop progress simulation on error\n                if (self.progressInterval) {\n                    clearInterval(self.progressInterval);\n                    self.progressInterval = null;\n                }\n                \n                // Check if error is XHR timeout\n                var errorMessage = err.message || err.toString();\n                if (errorMessage.includes('XHR request timed out')) {\n                    // For timeout show success message\n                    self.progressBar.style.display = 'none';\n                    self.statusDiv.innerHTML = '';\n                    self.statusDiv.appendChild(E('div', { \n                        'class': 'alert-message warning'\n                    }, _('Download completed successfully! Start upgrade.<br />DO NOT POWER OFF THIS DEVICE!<br />System will be upgrade completed after reboot!')));\n                } else {\n                    // For other errors show error message\n                    self.progressTitle.textContent = _('Upgrade failed: ') + errorMessage;\n                    self.progressTitle.className = 'cbi-progressbar-title error';\n                }\n                \n                self.upgradeButton.disabled = false;\n                self.checkButton.disabled = false;\n            });\n    },\n\n    simulateProgressBar: function() {\n        var self = this;\n        \n        if (this.progressInterval) {\n            clearInterval(this.progressInterval);\n        }\n        \n        let progress = 0;\n        this.progressInterval = setInterval(function() {\n            progress += Math.random() * 10 + 5; // Slowed down simulation\n            if (progress >= 100) {\n                progress = 100;\n                clearInterval(self.progressInterval);\n                self.progressInterval = null;\n                self.updateProgressBar(progress, _('Download completed! Prepare upgrade...'));\n            } else {\n                self.updateProgressBar(progress, _('Downloading: ') + Math.round(progress) + '%');\n            }\n        }, 800); // Increased interval\n    },\n\n    updateProgressBar: function(percent, text) {\n        if (!this.currentProgressBar || !this.progressTitle) return;\n        \n        // Update only text and progress, without recreating elements\n        this.progressTitle.textContent = text;\n        \n        // Update progress bar as in modem example\n        var percentValue = Math.min(100, Math.max(0, percent));\n        this.currentProgressBar.firstElementChild.style.width = percentValue + '%';\n        \n        // Add animation as in example\n        this.currentProgressBar.firstElementChild.style.animationDirection = \"reverse\";\n    },\n\n    handleSaveApply: null,\n    handleSave: null,\n    handleReset: null\n});\n"
  },
  {
    "path": "luci/applications/luci-app-ota/po/ru/ota.po",
    "content": "\"Language: ru\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Last-Translator: Konstantine Shevlakov <shevlakov@132lan.ru>\\n\"\n\nmsgid \"OTA Update\"\nmsgstr \"Обновление OTA\"\n\nmsgid \"Update Available\"\nmsgstr \"Доступно обновление\"\n\nmsgid \"OTA System Update\"\nmsgstr \"Обновление через OTA\"\n\nmsgid \"Check for Updates\"\nmsgstr \"Проверить\"\n\nmsgid \"Upgrade\"\nmsgstr \"Обновить\"\n\nmsgid \"Checking for updates...\"\nmsgstr \"Проверка обновлений...\"\n\nmsgid \"No updates available or check failed\"\nmsgstr \"Нет доступных обновлений\"\n\nmsgid \"Check failed: \"\nmsgstr \"Неудачно: \"\n\nmsgid \"Download completed successfully! Start upgrade.<br />DO NOT POWER OFF THIS DEVICE!<br />System will be upgrade completed after reboot!\"\nmsgstr \"Загрузка обновления завершена. Запущен процесс обновления.<br />НЕ ВЫКЛЮЧАЙТЕ УСТРОЙСТВО!<br />Система будет обновлена после перезагрузки!\"\n\nmsgid \"Upgrade failed: \"\nmsgstr \"Обновление прервано: \"\n\nmsgid \"Download completed! Prepare upgrade...\"\nmsgstr \"Загрузка выполнена. Подготовка...\"\n"
  },
  {
    "path": "luci/applications/luci-app-ota/root/etc/config/ota",
    "content": "config ota\n\toption url 'https://openwrt.132lan.ru/releases_cell/latest'\n"
  },
  {
    "path": "luci/applications/luci-app-ota/root/usr/share/luci/menu.d/luci-app-ota.json",
    "content": "{\n        \"admin/system/ota\": {\n                \"title\": \"OTA Update\",\n                \"order\": 70,\n                \"action\": {\n                        \"type\": \"view\",\n                        \"path\": \"system/ota\"\n                },\n\t\t\"depends\": {\n\t\t\t\"acl\": [ \"luci-app-ota\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-ota/root/usr/share/ota.sh",
    "content": "#!/bin/sh\n\n# simple update firmware script for 132lan.ru site\n# by Konstantine Shevlakov (c) 2025\n# for correct OTA upgrade remote dir must have next iherarchy:\n# www_root_dir/\n#   latest/\n#     changelog.txt - small note for update.\n#     targets/\n#       SoC_family/\n#         SoC/\n#           openwrt-${DISTRIB_RELEASE}-${PREFIX1}-${PREFIX2}-${YEAR}${MONTH}-rev${REVNUMBER}*.bin\n#\t    (e.g openwrt-24.10.2-4g-lte-202507-rev02-ramips-mt7621-beeline_smartbox-pro-squashfs-sysupgrade.bin)\n#           profiles.json - list images generated by imagebuilder or buildroot\n#\n# In local firmware must be included local version in uci-defaults:\n# /rom/etc/uci_defaults/fw_rev next body\n#\n#  FW_REV=\"${YEAR}${MONTH}-rev${REVNUMBER}\"\n#  e.g\n#  FW_REV=\"202507-rev01\"\n#\n# config URL Update server stored in\n# /etc/config/ota\n# config ota\n# \toption url 'https://example.com/latest'\n#\n#\n#       example\n#\n# config ota\n# \toption url 'https://openwrt.132lan.ru/releases_cell/latest'\n\n. /lib/functions.sh\n\n\nload_config(){\n        config_get url $1 url\n}\n\nconfig_load ota\nconfig_foreach load_config\n\nURL_BASE=$url\n\nif [ \"x${URL_BASE}\" = \"x\" ]; then\n\texit 0\nfi\n\n\n# Get OpenWrt Release info\n[ -f /etc/openwrt_release ] && {\n\t. /etc/openwrt_release\n\tcase ${DISTRIB_TARGET} in\n\t\t*x86*|*armsr*) echo \"Current platform ${DISTRIB_TARGET} not supported.\" && exit 0 ;;\n\tesac\n} || {\n\techo \"Unknown OpenWrt release!\"\n\texit 0\n}\n\n# Get board info\nBOARD=$(jsonfilter -s \"$(cat /etc/board.json)\" -e '@[\"model\"][\"id\"]')\n\n# Remove update files\nremove_files(){\n\tfor f in firmware.bin changelog.txt; do\n\t\t[ -f /tmp/${f} ] && {\n\t\t\trm -rf /tmp/${f}\n\t\t}\n\tdone\n}\n\ncase $1 in\n\tcheck)\n\n\t\tif [ ! -f /tmp/update.lock ]; then\n\t\t\techo -e \"Check updates from ${URL_BASE}\\n\"\n\t\tfi\n\n\t\t# Get aviable profiles\n\t\t! [ -f /tmp/profiles.json ] && {\n\t\t\twget ${URL_BASE}/targets/${DISTRIB_TARGET}/profiles.json -O /tmp/profiles.json > /dev/null 2>&1\n\t\t}\n\n\t\t! [ -f /tmp/profiles.json ] && {\n\t\t\techo \"Updates not available from this server.\"\n\t\t\texit 0\n\t\t}\n\n\t\t# Get changelog\n\t\t[ -f /tmp/profiles.json ] && {\n\t\t\t[ ! -f /tmp/changelog.txt ] && {\n\t\t\t\twget ${URL_BASE}/changelog.txt -O /tmp/changelog.txt > /dev/null 2>&1\n\t\t\t} || {\n\t\t\t\tbreak\n\t\t\t}\n\t\t} || {\n\t\t\techo \"No updates for this board: $BOARD\" && remove_files && exit 0\n\t\t}\n\t;;\nesac\n\n# Get available boards\nBASE_BOARD=$(jsonfilter -s \"$(cat /tmp/profiles.json)\" -e '@[\"profiles\"][*][\"supported_devices\"].*')\n\n# Get available board info\n\nboard_stuff(){\n        IMAGES=$(jsonfilter -s \"$(cat /tmp/profiles.json)\" -e \"@['profiles']['$FW_BOARD']['images'][*]\" | grep sysupgrade)\n        echo $IMAGES | jsonfilter \\\n                -e FILE=\"$['name']\" \\\n                -e SHA256=\"$['sha256']\"\n}\n\n#sha256check(){\n#}\n\n# stuff\nfor b in $BASE_BOARD; do\n\tif [ \"${b}\" = \"${BOARD}\" ]; then\n\t\t# modify board name for profiles.json\n\t\tFW_BOARD=$(echo $BOARD | sed -e 's/\\,/_/')\n\t\t[ -f /tmp/profiles.json ] && {\n\t\t\teval $(board_stuff)\n\t\t} || {\n\t\t\techo \"Failed! Abort update\"\n\t\t\tremove_files\n\t\t}\n\t\t# Get remote revision firmware\n\t\tFW_REV_EXT=$(echo $FILE | awk -F [-] '{gsub(\"rev\",\"\"); gsub(/\\./,\"\",$2);  print $2$5$6}')\n\t\t# Get local revision firmware\n\t\tif [ -f /rom/etc/uci-defaults/fw_rev ]; then\n\t\t\t. /rom/etc/uci-defaults/fw_rev\n\t\t\tVER_LOCAL=$(echo $FW_REV | awk -F [-] '{gsub(\"rev\",\"\"); print $1$2}')\n\t\t\tDIGIT_RELEASE=$(echo ${DISTRIB_RELEASE} | awk '{gsub(/\\./,\"\"); print $0}')\n\t\t\tFW_VER_LOCAL=${DIGIT_RELEASE}${VER_LOCAL}\n\t\telse\n\t\t\techo \"Failed! Abort update\"\n\t\t\texit 0\n\t\tfi\n\t\t# Firmware upgrade\n\t\tcase $1 in\n\t\t\tupgrade)\n\t\t\tif [ -f /tmp/update.lock ]; then\n\t\t\t\techo \"Download firmware $FILE\"\n\t\t\t\techo \"from $URL_BASE\"\n\t\t\t\twget $URL_BASE/targets/${DISTRIB_TARGET}/$FILE -O /tmp/firmware.bin > /dev/null 2>&1\n\t\t\t\tcase $? in\n\t\t\t\t\t0) echo \"Download complete.\" ;;\n\t\t\t\t\t*) echo \"No updates for this board: $BOARD\" && remove_files && exit 0 ;;\n\t\t\t\tesac\n\t\t\t\t# Get local SHA256\n\t\t\t\tSHA256_DL=$(sha256sum /tmp/firmware.bin | awk '{print $1}')\n\t\t\t\techo -n \"Check sha256 sum: \"\n\t\t\t\t# Compare remote and download file SHA256\n\t\t\t\tif [ \"$SHA256\" = \"$SHA256_DL\" ]; then\n\t\t\t\t\techo \"OK\"\n\t\t\t\t\techo \"Update process start!\"\n\t\t\t\t\techo \"Device will be rebooted.\"\n\t\t\t\t\techo \"DO NOT TURN OFF DEVICE!\"\n\t\t\t\t\t# Test firmware image before flashing\n\t\t\t\t\tsysupgrade -T /tmp/firmware.bin\n\t\t\t\t\tcase $? in\n\t\t\t\t\t\t0) echo \"Flashing firmware\" ;;\n\t\t\t\t\t\t*) echo \"Failed! Abort update\" && remove_files && exit 0 ;;\n\t\t\t\t\tesac\n\t\t\t\t\trm -rf /tmp/update.lock /tmp/profiles.json /tmp/changelog.txt\n\t\t\t\t\t# Updrade firmware\n\t\t\t\t\tsleep 25 && sysupgrade /tmp/firmware.bin &\n\t\t\t\telse\n\t\t\t\t\techo \"Failed! Abort update.\"\n\t\t\t\t\tremove_files && rm -rf /tmp/update.lock /tmp/profiles.json\n\t\t\t\tfi\n\t\t\tfi\n\t\t\t;;\n\t\t\tcheck)\n\t\t\t# Compare firmware versions\n\t\t\tif [ $FW_REV_EXT -gt $FW_VER_LOCAL ]; then\n\t\t\t\techo \"New firmware upgrade release!\"\n\t\t\t\techo -e \"*** $FILE ***\\n\"\n\t\t\t\t[ -r /tmp/changelog.txt ] && {\n\t\t\t\t\techo -e \"RELEASE NOTES:\\n\"\n\t\t\t\t\techo \"$(cat /tmp/changelog.txt)\"\n\t\t\t\t}\n\t\t\t\techo \"\"\n\t\t\t\techo \"Please run script again for download and install update!\"\n\t\t\t\ttouch /tmp/update.lock\n\t\t\telse\n\t\t\t\techo \"Update not found!\"\n\t\t\tfi\n\t\t\t;;\n\t\tesac\n\tfi\ndone\n\n#rm -f /tmp/profiles.json /tmp/update.lock /tmp/changelog.txt\n"
  },
  {
    "path": "luci/applications/luci-app-ota/root/usr/share/rpcd/acl.d/luci-app-ota.json",
    "content": "{\n\t\"luci-app-ota\": {\n\t\"description\": \"OTA Update access\",\n\t\"read\": {\n\t\"file\": {\n\t\t\t\"/usr/share/ota.sh\": [\"exec\"],\n\t\t\t\"/tmp/changelog.txt\": [\"read\"],\n\t\t\t\"/tmp/profiles.json\": [\"read\"],\n\t\t\t\"/tmp/update.lock\": [\"read\"]\n\t\t}\n\t},\n\t\"write\": {\n\t\t\"file\": {\n\t\t\t\"/usr/share/ota.sh\": [\"exec\"]\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-pingcontrol/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=luci-app-pingcontrol\nLUCI_DEPENDS:=+pingcontrol\nPKG_VERSION:=1\nPKG_RELEASE:=3\n\nLUCI_TITLE:=LuCI network interface control\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-pingcontrol/htdocs/luci-static/resources/view/pingcontrol/pingcontrol.js",
    "content": "'use strict';\n'require form';\n'require view';\n'require uci';\n'require rpc';\n'require tools.widgets as widgets';\n\nreturn view.extend({\n\thandleEnableService: rpc.declare({\n\t\tobject: 'luci',\n\t\tmethod: 'setInitAction',\n\t\tparams: [ 'pingcontrol', 'enable' ],\n\t\texpect: { result: false }\n\t}),\n\n\trender: function() {\n\t\tvar m, s, o;\n\t\t\n\t\tm = new form.Map('pingcontrol', _('PingControl'));\n\t\tm.description = _('Server availability check');\n\n\t\ts = m.section(form.GridSection, 'pingcontrol', _('Settings'));\n\t\ts.tab('general', _('General Settings'));\n\t\ts.tab('commands', _('Commands'));\n\t\ts.addremove = true;\n\t\ts.nodescriptions = true;\n\n\t\to = s.taboption('general',form.Flag, 'enabled', _('Enabled'));\n\t\to.rmempty = false;\n\t\to.write = L.bind(function(section, value) {\n\t\t\tif (value == '1') {\n\t\t\t\tthis.handleEnableService();\n\t\t\t}\n\t\t\treturn uci.set('pingcontrol', section, 'enabled', value);\n\t\t}, this);\n\n\t\to = s.taboption('general',widgets.NetworkSelect, 'iface', _('Ping interface'));\n\t\to.rmempty = false;\n\t\to.textvalue = function(section_id) {\n\t\t\treturn uci.get('pingcontrol', section_id, 'iface');\n\t\t}\n\n\t\to = s.taboption('general',form.DynamicList, 'testip', _('IP address or hostname of test servers'));\n\t\to.datatype = 'or(hostname,ipaddr(\"nomask\"))';\n\n\t\to = s.taboption('general',form.Flag, 'ping_silent', _('Silent mode'), _('Do not log success ping'));\n\n\n\t\to = s.taboption('general',form.Value, 'check_period', _('Period of check, sec'));\n\t\to.rmempty = false;\n\t\to.datatype = 'and(uinteger,min(20))';\n\t\to.default = '60';\n\n\t\to = s.taboption('general',form.Value, 'sw_before_modres', _('Failed attempts before iface up/down'), _('0 - not used'));\n\t\to.rmempty = false;\n\t\to.datatype = 'and(uinteger,min(0),max(100))';\n\t\to.default = '3';\n\n\t\to = s.taboption('general',form.Value, 'sw_before_sysres', _('Failed attempts before reboot'), _('0 - not used'));\n\t\to.rmempty = false;\n\t\to.datatype = 'and(uinteger,min(0),max(100))';\n\t\to.default = '0';\n\n\t\to = s.taboption('commands',form.Value, 'ping_ok', _('Successful ping'));\n\t\to.modalonly = true;\n\n\t\to = s.taboption('commands',form.Value, 'ping_lost', _('Ping lost'));\n\t\to.modalonly = true;\n\n\t\to = s.taboption('commands',form.Value, 'ping_restored', _('Ping restored'));\n\t\to.modalonly = true;\n\n\t\to = s.taboption('commands',form.Value, 'before_iface_down', _('Before interface down'));\n\t\to.modalonly = true;\n\n\t\to = s.taboption('commands',form.Value, 'after_iface_up', _('After interface up'));\n\t\to.modalonly = true;\n\n\t\to = s.taboption('commands',form.Value, 'before_reboot', _('Before reboot'));\n\t\to.modalonly = true;\n\n\t\treturn m.render();\n\t}\n});\n"
  },
  {
    "path": "luci/applications/luci-app-pingcontrol/po/en/pingcontrol.po",
    "content": "msgid \"Server availability check\"\nmsgstr \"Server availability check\"\n\nmsgid \"Period of check, sec\"\nmsgstr \"Period of check, sec\"\n\nmsgid \"Ping interface\"\nmsgstr \"Ping interface\"\n\nmsgid \"IP address or hostname of test servers\"\nmsgstr \"IP address or hostname of test servers\"\n\nmsgid \"Failed attempts before iface up/down\"\nmsgstr \"Failed attempts before iface up/down\"\n\nmsgid \"Failed attempts before reboot\"\nmsgstr \"Failed attempts before reboot\"\n\nmsgid \"Configuration name\"\nmsgstr \"Configuration name\"\n"
  },
  {
    "path": "luci/applications/luci-app-pingcontrol/po/ru/pingcontrol.po",
    "content": "msgid \"Server availability check\"\nmsgstr \"Проверка доступности сервера\"\n\nmsgid \"Period of check, sec\"\nmsgstr \"Период проверки, сек\"\n\nmsgid \"Ping interface\"\nmsgstr \"Интерфейс для ping-запросов\"\n\nmsgid \"IP address or hostname of test servers\"\nmsgstr \"IP-адрес или имя хоста тестовых серверов\"\n\nmsgid \"Silent mode\"\nmsgstr \"Тихий режим\"\n\nmsgid \"Do not log success ping\"\nmsgstr \"Не журналировать успешные попытки\"\n\nmsgid \"Failed attempts before iface up/down\"\nmsgstr \"Количество неудачных попыток до переподключения интерфейса\"\n\nmsgid \"Failed attempts before reboot\"\nmsgstr \"Количество неудачных попыток до перезагрузки устройства\"\n\nmsgid \"Configuration name\"\nmsgstr \"Имя конфигурации\"\n\nmsgid \"Commands\"\nmsgstr \"Команды\"\n\nmsgid \"Successful ping\"\nmsgstr \"Пинг успешен\"\n\nmsgid \"Ping lost\"\nmsgstr \"Пинг потерян\"\n\nmsgid \"Ping restored\"\nmsgstr \"Пинг восстановлен\"\n\nmsgid \"Before interface down\"\nmsgstr \"Перед отключением интерфейса\"\n\nmsgid \"After interface up\"\nmsgstr \"После включения интерфейса\"\n\nmsgid \"Before reboot\"\nmsgstr \"Перед перезагрузкой\"\n"
  },
  {
    "path": "luci/applications/luci-app-pingcontrol/root/usr/share/luci/menu.d/luci-app-pingcontrol.json",
    "content": "{\n        \"admin/services/pingcontrol\": {\n                \"title\": \"Pingcontrol\",\n                \"action\": {\n                        \"type\": \"view\",\n                        \"path\": \"pingcontrol/pingcontrol\"\n                },\n                \"depends\": {\n                        \"acl\": [ \"luci-app-pingcontrol\" ],\n                        \"uci\": { \"pingcontrol\": true }\n                }\n        }\n}\n"
  },
  {
    "path": "luci/applications/luci-app-pingcontrol/root/usr/share/rpcd/acl.d/luci-app-pingcontrol.json",
    "content": "{\n\t\"luci-app-pingcontrol\": {\n\t\t\"description\": \"Grant UCI access for luci-app-pingcontrol\",\n\t\t\"read\": {\n\t\t\t\"uci\": [ \"pingcontrol\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"uci\": [ \"pingcontrol\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/Makefile",
    "content": "#\n# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=rTorrent LuCI web interface\nLUCI_DEPENDS:=+rtorrent-rpc +luaexpat +luasocket +luasec +screen\n\nPKG_VERSION:=0.1.7\nPKG_RELEASE:=1\nPKG_LICENSE:=GPLv3\n\ndefine Package//luci-app-rtorrent/conffiles\n\t/etc/config/rtorrent\n\t/etc/rtorrent.conf\nendef\n\ndefine Package/luci-app-rtorrent/postinst\n\trm -rf /tmp/luci-indexcache /tmp/luci-modulecache\nendef\n\ndefine Package/luci-app-rtorrent/postrm\n\trm -rf /tmp/luci-indexcache /tmp/luci-modulecache\nendef\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/README.md",
    "content": "# luci-app-rtorrent\nrTorrent client for OpenWrt's LuCI web interface\n\n## Features\n- List all torrent downloads\n- Add new torrent by url/magnet uri/file\n- Stop/start/delete torrents\n- Mark torrents with tags\n- Set priority per file\n- Enable/disable and add trackers to torrent\n- Detailed peer listing\n- Completely LuCI based interface\n- OpenWrt device independent (written in lua)\n- Opkg package manager support\n- RSS feed downloader (automatically download torrents that match the specified criteria)\n- Support for [Transdroid](https://www.transdroid.org/) ([Transdrone](https://play.google.com/store/apps/details?id=org.transdroid.lite))\n\n## Screenshots\n[luci-app-rtorrent 0.1.3](https://github.com/wolandmaster/luci-app-rtorrent/wiki/Screenshots)\n\n## Install instructions\n(for Openwrt 15.05.1 Chaos Calmer)\n\n### Install rtorrent-rpc\n```\nopkg update\nopkg install rtorrent-rpc\n```\n(Note: If you going to install rtorrent-rpc to an own [opkg destination](https://wiki.openwrt.org/doc/techref/opkg#installation_destinations) then you have to install libopenssl to the root destination before)\n\n### Create rTorrent config file\n\n#### Minimal _/root/.rtorrent.rc_ file:\n```\ndirectory = /path/to/downloads/\nsession = /path/to/session/\n\nscgi_port = 127.0.0.1:5000\n\nschedule = rss_downloader,300,300,\"execute=/usr/lib/lua/rss_downloader.lua\"\n```\n#### Sample _/root/.rtorrent.rc_ file:\nhttp://pissedoffadmins.com/os/linux/sample-rtorrent-rc-file.html\n\n#### Recommended kernel parameters to avoid low memory issues:\n```\ncat /etc/sysctl.conf\n...\n# handle rtorrent related low memory issues\nvm.swappiness=95\nvm.vfs_cache_pressure=200\nvm.min_free_kbytes=4096\nvm.overcommit_memory=2\nvm.overcommit_ratio=60\n```\n\n### Create init.d script (optional)\n\n#### Install screen\n```\nopkg install screen\n```\n\n#### Create _/etc/init.d/rtorrent_ script\n```\n#!/bin/sh /etc/rc.common\n\nSTART=99\nSTOP=99\n\nSCREEN=/usr/sbin/screen\nPROG=/usr/bin/rtorrent\n\nstart() {\n  sleep 3\n  $SCREEN -dm -t rtorrent nice -19 $PROG\n}\n\nstop() {\n  killall rtorrent\n}\n```\n\n#### Start rtorrent\n```\nchmod +x /etc/init.d/rtorrent\n/etc/init.d/rtorrent enable\n/etc/init.d/rtorrent start\n```\n\n### Install wget\n(the wget in busybox does not support https)\n```\nopkg install ca-certificates\nopkg install wget\nln -sf $(which wget-ssl) /usr/bin/wget\n```\n\n### Install luci-app-rtorrent\n```\nwget -nv https://github.com/wolandmaster/luci-app-rtorrent/releases/download/latest/e1a1ba8004c4220f -O /etc/opkg/keys/e1a1ba8004c4220f\necho 'src/gz luci_app_rtorrent https://github.com/wolandmaster/luci-app-rtorrent/releases/download/latest' >> /etc/opkg.conf\nopkg update\nopkg install luci-app-rtorrent\n```\n\n### Upgrade already installed version\n```\nopkg update\nopkg upgrade luci-app-rtorrent\n```\n\n### References\n<https://www.pcsuggest.com/openwrt-torrent-download-box-luci/>\n\n<https://medium.com/openwrt-iot/lede-openwrt-setting-up-torrent-downloading-a06fe37a1ea2>\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/htdocs/cgi-bin/rtorrent",
    "content": "#!/usr/bin/lua\nlocal rtorrent = require \"rtorrent\"\nio.write(rtorrent.rpc(io.read(\"*all\")))\nio.flush()\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal nixio = require \"nixio\"\nlocal dm = require \"luci.model.cbi.rtorrent.download\"\n\nmodule(\"luci.controller.rtorrent\", package.seeall)\n\nfunction index()\n\tentry({\"admin\", \"rtorrent\"},  firstchild(), translate(\"Torrent\"), 45).dependent = false\n\tentry({\"admin\", \"rtorrent\", \"main\"}, form(\"rtorrent/main\"), translate(\"Torrent List\"), 10).leaf = true\n\tentry({\"admin\", \"rtorrent\", \"add\"}, form(\"rtorrent/add\", {autoapply=true}), translate(\"Add Torrent\"), 20)\n\tentry({\"admin\", \"rtorrent\", \"rss\"}, arcombine(cbi(\"rtorrent/rss\"), cbi(\"rtorrent/rss-rule\")), translate(\"RSS Downloader\"), 30).leaf = true\n\tentry({\"admin\", \"rtorrent\", \"admin\"}, form(\"rtorrent/admin/rtorrent\"), translate(\"Torrent Settings\"), 40)\n\n\tentry({\"admin\", \"rtorrent\", \"info\"}, form(\"rtorrent/torrent/info\"), nil).leaf = true\n\tentry({\"admin\", \"rtorrent\", \"files\"}, form(\"rtorrent/torrent/files\"), nil).leaf = true\n\tentry({\"admin\", \"rtorrent\", \"trackers\"}, form(\"rtorrent/torrent/trackers\"), nil).leaf = true\n\tentry({\"admin\", \"rtorrent\", \"peers\"}, form(\"rtorrent/torrent/peers\"), nil).leaf = true\n\n\tentry({\"admin\", \"rtorrent\", \"download\"}, call(\"download\"), nil).leaf = true\n\tentry({\"admin\", \"rtorrent\", \"downloadall\"}, call(\"downloadall\"), nil).leaf = true\n\n\tentry({\"admin\", \"rtorrent\", \"admin\", \"rtorrent\"}, form(\"rtorrent/admin/rtorrent\"), nil).leaf = true\n\tentry({\"admin\", \"rtorrent\", \"admin\", \"rss\"}, cbi(\"rtorrent/admin/rss\"), nil).leaf = true\nend\n\nfunction download()\n\tdm.download_file(nixio.bin.b64decode(luci.dispatcher.context.requestpath[4]))\nend\n\nfunction downloadall()\n\tdm.download_all(nixio.bin.b64decode(luci.dispatcher.context.requestpath[4]))\nend\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal bencode = require \"bencode\"\nlocal nixio = require \"nixio\"\nlocal rtorrent = require \"rtorrent\"\nlocal xmlrpc = require \"xmlrpc\"\nlocal common = require \"luci.model.cbi.rtorrent.common\"\nrequire \"luci.model.cbi.rtorrent.string\"\n\nf = SimpleForm(\"rtorrent\", translate(\"Add Torrent\"))\nf.submit = \"Add\"\n\nlocal torrent\n\nuri = f:field(TextValue, \"uri\", translate(\"Torrent<br />or magnet URI\"))\nuri.rows = 1\n\nfunction uri.validate(self, value, section)\n\tif \"magnet:\" == string.sub(value:trim(), 1, 7) then\n\t\ttorrent = bencode.encode({ [\"magnet-uri\"] = value:trim() })\n\telse\n\t\tlocal ok, res = common.get(value)\n\t\tif not ok then return nil, \"Not able to download torrent: \" .. res end\n\t\tlocal tab, err = bencode.decode(res)\n\t\tif not tab then return nil, \"Not able to parse torrent file: \" .. err end\n\t\ttorrent = res\n\tend\n\treturn value\nend\n\nfile = f:field(FileUpload, \"file\", translate(\"Upload torrent file\"))\nfile.root_directory = \"/etc/luci-uploads\"\n\nfunction file.validate(self, value, section)\n\ttorrent = nixio.fs.readfile(value)\n\tself:remove(section)\n\tlocal tab, err = bencode.decode(torrent)\n\tif not tab then return nil, \"Not able to parse torrent file: \" .. err end\n\treturn value\nend\n\ndir = f:field(Value, \"dir\", translate(\"Download directory\"))\ndir.default = rtorrent.call(\"directory.default\")\ndir.datatype = \"directory\"\ndir.rmempty = false\n\ntags = f:field(Value, \"tags\", translate(\"Tags\"))\nlocal user =  luci.dispatcher.context.authuser\ntags.default = \"all\" .. (user ~= \"root\" and \" \" .. user or \"\")\ntags.rmempty = false\n\nstart = f:field(Flag, \"start\", translate(\"Start now\"))\nstart.default  = \"1\"\nstart.rmempty  = false\n\nfunction f.handle(self, state, data)\n\tif state == FORM_VALID and torrent and #torrent > 0 then\n\t\tlocal params = {}\n\t\ttable.insert(params, data.start == \"1\" and \"load.raw_start\" or \"load.raw\")\n\t\ttable.insert(params, \"\") -- target\n\t\ttable.insert(params, xmlrpc.newTypedValue((nixio.bin.b64encode(torrent)), \"base64\"))\n\t\ttable.insert(params, \"d.directory.set=\\\"\" .. data.dir .. \"\\\"\")\n\t\ttable.insert(params, \"d.custom1.set=\\\"\" .. data.tags .. \"\\\"\")\n\t\tif data.uri then\n\t\t\ttable.insert(params, \"d.custom3.set=\" .. nixio.bin.b64encode(data.uri))\n\t\tend\n\t\trtorrent.call(unpack(params))\n\t\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/add\"))\n\tend\n\treturn true\nend\n\nreturn f\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rss.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal common = require \"luci.model.cbi.rtorrent.common\"\nlocal nixio = require \"nixio\"\n\nm = Map(\"rtorrent\", \"Admin - RSS Downloader\")\n\ns = m:section(TypedSection, \"rss-feed\")\ns.addremove = true\ns.anonymous = true\ns.sortable = true\ns.template = \"cbi/tblsection\"\ns.render = function(self, section, scope)\n\tluci.template.render(\"rtorrent/tabmenu\", { self = {\n\t\tpages = common.get_admin_pages(),\n\t\tpage = \"RSS\"\n\t}})\n\tTypedSection.render(self, section, scope)\nend\n\nname = s:option(Value, \"name\", \"Name\")\nname.rmempty = false\n\nurl = s:option(Value, \"url\", \"RSS Feed URL\")\nurl.size = \"65\"\nurl.rmempty = false\n\nenabled = s:option(Flag, \"enabled\", \"Enabled\")\nenabled.rmempty = false\n\nt = m:section(NamedSection, \"logging\", \"rss\", \"Logging\")\n\nfeed_logging = t:option(Flag, \"feed_logging\", \"Enable RSS feed logging\")\n\nfeed_logfile = t:option(Value, \"feed_logfile\", \"RSS feed logfile\")\nfeed_logfile:depends(\"feed_logging\", 1)\n\nfunction feed_logfile.validate(self, value, section)\n\tlocal parent_folder = nixio.fs.dirname(value)\n\tif parent_folder == \".\" or nixio.fs.stat(parent_folder, \"type\") ~= \"dir\" then\n\t\treturn nil, \"Wrong filename, please use absolute path!\"\n\tend\n\treturn value\nend\n\nreturn m\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal rtorrent = require \"rtorrent\"\nlocal common = require \"luci.model.cbi.rtorrent.common\"\n\nlocal config = rtorrent.batchcall({\n\t\"throttle.global_up.max_rate\", \"throttle.global_down.max_rate\",\n\t\"throttle.max_downloads.global\", \"throttle.max_uploads.global\",\n\t\"throttle.max_uploads\",\n\t\"throttle.min_peers.normal\", \"throttle.max_peers.normal\",\n\t\"throttle.min_peers.seed\", \"throttle.max_peers.seed\"\n})\n\nlocal function set_config(key, value)\n\tif tonumber(value) ~= config[key] then\n\t\trtorrent.call(key .. \".set\", \"\", value)\n\t\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/admin/rtorrent\"))\n\tend\nend\n\nf = SimpleForm(\"rtorrent\", translate(\"Admin - rTorrent\"))\n\nspeed = f:section(SimpleSection)\nspeed.title = translate(\"Bandwidth limits\")\nspeed.render = function(self, ...)\n\tluci.template.render(\"rtorrent/tabmenu\", { self = {\n\t\tpages = common.get_admin_pages(),\n\t\tpage = \"rTorrent\"\n\t}})\n\tSimpleSection.render(self, ...)\nend\n\nupload_rate = speed:option(Value, \"upload_rate\", translate(\"Upload limit (KiB/sec)\"),\n\ttranslate(\"Global upload rate (0: unlimited)\"))\nupload_rate.rmempty = false\nupload_rate.default = config[\"throttle.global_up.max_rate\"] / 1024\nupload_rate.datatype = \"uinteger\"\nfunction upload_rate.write(self, section, value)\n\tset_config(\"throttle.global_up.max_rate\", value .. \"k\")\nend\n\ndownload_rate = speed:option(Value, \"download_rate\", translate(\"Download limit (KiB/sec)\"),\n\ttranslate(\"Global downlaod rate (0: unlimited)\"))\ndownload_rate.rmempty = false\ndownload_rate.default = config[\"throttle.global_down.max_rate\"] / 1024\ndownload_rate.datatype = \"uinteger\"\nfunction download_rate.write(self, section, value)\n\tset_config(\"throttle.global_down.max_rate\", value .. \"k\")\nend\n\n\nglobal_limits = f:section(SimpleSection)\nglobal_limits.title = translate(\"Global limits\")\n\nmax_downloads_global = global_limits:option(Value, \"max_downloads_global\", translate(\"Download slots\"),\n\ttranslate(\"Maximum number of simultaneous downloads\"))\nmax_downloads_global.rmempty = false\nmax_downloads_global.default = config[\"throttle.max_downloads.global\"]\nmax_downloads_global.datatype = \"uinteger\"\nfunction max_downloads_global.write(self, section, value)\n\tset_config(\"throttle.max_downloads.global\", value)\nend\n\nmax_uploads_global = global_limits:option(Value, \"max_uploads_global\", translate(\"Upload slots\"),\n\ttranslate(\"Maximum number of simultaneous uploads\"))\nmax_uploads_global.rmempty = false\nmax_uploads_global.default = config[\"throttle.max_uploads.global\"]\nmax_uploads_global.datatype = \"uinteger\"\nfunction max_uploads_global.write(self, section, value)\n\tset_config(\"throttle.max_uploads.global\", value)\nend\n\n\ntorrent_limits = f:section(SimpleSection)\ntorrent_limits.title = translate(\"Torrent limits\")\n\nmax_uploads = torrent_limits:option(Value, \"max_uploads\", translate(\"Maximum uploads\"),\n\ttranslate(\"Maximum number of simultanious uploads per torrent\"))\nmax_uploads.rmempty = false\nmax_uploads.default = config[\"throttle.max_uploads\"]\nmax_uploads.datatype = \"uinteger\"\nfunction max_uploads.write(self, section, value)\n\tset_config(\"throttle.max_uploads\", value)\nend\n\nmin_peers = torrent_limits:option(Value, \"min_peers\", translate(\"Minimum peers\"),\n\ttranslate(\"Minimum number of peers to connect to per torrent\"))\nmin_peers.rmempty = false\nmin_peers.default = config[\"throttle.min_peers.normal\"]\nmin_peers.datatype = \"uinteger\"\nfunction min_peers.write(self, section, value)\n\tset_config(\"throttle.min_peers.normal\", value)\nend\n\nmax_peers = torrent_limits:option(Value, \"max_peers\", translate(\"Maximum peers\"),\n\ttranslate(\"Maximum number of peers to connect to per torrent\"))\nmax_peers.rmempty = false\nmax_peers.default = config[\"throttle.max_peers.normal\"]\nmax_peers.datatype = \"uinteger\"\nfunction max_peers.write(self, section, value)\n\tset_config(\"throttle.max_peers.normal\", value)\nend\n\nmin_peers_seed = torrent_limits:option(Value, \"min_peers_seed\", translate(\"Minimum seeds\"),\n\ttranslate(\"Minimum number of seeds for completed torrents (-1 = same as peers)\"))\nmin_peers_seed.rmempty = false\nmin_peers_seed.default = config[\"throttle.min_peers.seed\"]\nmin_peers_seed.datatype = \"integer\"\nfunction min_peers_seed.write(self, section, value)\n\tset_config(\"throttle.min_peers.seed\", value)\nend\n\nmax_peers_seed = torrent_limits:option(Value, \"max_peers_seed\", translate(\"Maximum seeds\"),\n\ttranslate(\"Maximum number of seeds for completed torrents (-1 = same as peers)\"))\nmax_peers_seed.rmempty = false\nmax_peers_seed.default = config[\"throttle.max_peers.seed\"]\nmax_peers_seed.datatype = \"integer\"\nfunction max_peers_seed.write(self, section, value)\n\tset_config(\"throttle.max_peers.seed\", value)\nend\n\n-- dir = f:field(DummyValue, \"dummy\", luci.dispatcher.context.authuser)\n\nreturn f\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/common.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal http = require \"socket.http\"\nlocal https = require \"ssl.https\"\nlocal ssl = require \"ssl\"\nlocal ltn12 = require \"ltn12\"\nlocal fs = require \"nixio.fs\"\nlocal dispatcher = require \"luci.dispatcher\"\nrequire \"luci.model.cbi.rtorrent.string\"\n\nlocal string, os, io, math, assert = string, os, io, math, assert\nlocal ipairs, table, unpack, tonumber = ipairs, table, unpack, tonumber\n\nlocal COOKIES_FILE = \"/etc/cookies.txt\"\n\nmodule \"luci.model.cbi.rtorrent.common\"\n\nfunction get_torrent_pages(hash)\n\treturn {\n\t\t{ name = \"Info\", link = dispatcher.build_url(\"admin/rtorrent/info/\") .. hash },\n\t\t{ name = \"File List\", link = dispatcher.build_url(\"admin/rtorrent/files/\") .. hash },\n\t\t{ name = \"Tracker List\", link = dispatcher.build_url(\"admin/rtorrent/trackers/\") .. hash },\n\t\t{ name = \"Peer List\", link = dispatcher.build_url(\"admin/rtorrent/peers/\") .. hash }\n\t}\nend\n\nfunction get_admin_pages()\n\treturn {\n\t\t{ name = \"rTorrent\", link = dispatcher.build_url(\"admin/rtorrent/admin/rtorrent\") },\n\t\t{ name = \"RSS\", link = dispatcher.build_url(\"admin/rtorrent/admin/rss\") }\n\t}\nend\n\nfunction human_size(bytes)\n\tlocal symbol = {[0]=\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\", \"ZiB\", \"YiB\"}\n\tlocal exp = bytes > 0 and math.floor(math.log(bytes) / math.log(1024)) or 0\n\tlocal value = bytes / math.pow(1024, exp)\n\tlocal acc = bytes > 0 and 2 - math.floor(math.log10(value)) or 2\n\tif acc < 0 then acc = 0 end\n\treturn string.format(\"%.\" .. acc .. \"f \" .. symbol[exp], value)\nend\n\nfunction human_time(sec)\n\tlocal t = os.date(\"*t\", sec)\n\tif     t[\"day\"]  > 25 then return \"&#8734;\"\n\telseif t[\"day\"]  > 1 then return string.format(\"%dd<br />%dh %dm\", t[\"day\"] - 1, t[\"hour\"], t[\"min\"])\n\telseif t[\"hour\"] > 1 then return string.format(\"%dh<br />%dm %ds\", t[\"hour\"] - 1, t[\"min\"], t[\"sec\"])\n\telseif t[\"min\"]  > 0 then return string.format(\"%dm %ds\", t[\"min\"], t[\"sec\"])\n\telse   return string.format(\"%ds\", t[\"sec\"]) end\nend\n\nfunction div(body, ...)\n\tlocal class = {}\n\tfor _, c in ipairs({...}) do\n\t\tif c then table.insert(class, c) end\n\tend\n\tif #class > 0 then\n\t\treturn \"<div class=\\\"%s\\\">%s</div>\" % {table.concat(class, \" \"), body}\n\telse\n\t\treturn body\n\tend\nend\n\nfunction wget(url)\n\tlocal file = assert(io.popen(\"/usr/bin/wget \"\n\t\t.. \"--quiet \"\n\t\t.. \"--user-agent=unknown \"\n\t\t.. \"--referer=http://www.google.com \"\n\t\t.. \"--load-cookies=\" .. COOKIES_FILE .. \" \"\n\t\t.. \"--output-document=- \"\n\t\t.. url, \"r\"))\n\tlocal response = file:read(\"*all\")\n\tfile:close()\n\treturn response\nend\n\nfunction get(url)\n\tlocal response = {}\n\tlocal proto = url:starts(\"https://\") and https or http\n\tproto.TIMEOUT = 5\n\tlocal body, code, headers, status = proto.request {\n\t\tmethod = \"GET\",\n\t\theaders = {\n\t\t\t[\"Referer\"] = \"http://www.google.com\",\n\t\t\t[\"User-Agent\"] = \"unknown\",\n\t\t\t[\"Cookie\"] = get_cookies(url)\n\t\t},\n\t\turl = url,\n\t\tredirect = (proto.PORT == 80) and true or nil,\n\t\tsink = ltn12.sink.table(response)\n\t}\n\tif code == 301 then return get(headers[\"location\"]) end\n\tif code == 200 then return true, table.concat(response)\n\telse\n\t\tlocal body = wget(url)\n\t\tif body:len() > 0 then return true, body\n\t\telse return false, status end\n\tend\nend\n\nfunction get_cookies(url)\n\tlocal cookies = {}\n\tfor _, line in ipairs(fs.readfile(COOKIES_FILE):split(\"\\n\\r\")) do\n\t\tif not line:match(\"^\\s*#.*\") then\n\t\t\tlocal domain, tailmatch, path, secure, expiration, name, value = unpack(line:split())\n\t\t\tlocal url_match = (secure == \"TRUE\") and \"^https://\" or \"^https?://\"\n\t\t\turl_match = url_match .. (tailmatch == \"TRUE\" and \".*\" or \"\") .. domain .. path\n\t\t\tif url:match(url_match) and tonumber(expiration) >= os.time() then\n\t\t\t\ttable.insert(cookies, name .. \"=\" .. value)\n\t\t\tend\n\t\tend\n\tend\n\treturn table.concat(cookies, \"; \")\nend\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/download.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal nixio = require \"nixio\"\nlocal http = require \"luci.http\"\nlocal ltn12 = require \"luci.ltn12\"\nlocal sys = require \"luci.sys\"\nlocal common = require \"luci.model.cbi.rtorrent.common\"\nrequire \"luci.model.cbi.rtorrent.string\"\n\nlocal ipairs, string = ipairs, string\n\nlocal PROTECTED_PATH = {\"/bin\", \"/dev\", \"/etc\", \"/lib\", \"/overlay\", \"/root\", \"/sbin\", \"/tmp\", \"/usr\", \"/var\", \"/www\"}\n\nmodule(\"luci.model.cbi.rtorrent.download\", package.seeall)\n\nfunction security_check(file)\n\tfor _, path_prefix in ipairs(PROTECTED_PATH) do\n\t\tif file:starts(path_prefix) then\n\t\t\thttp.write(\"<h1>Access Denied</h1>\")\n\t\t\treturn false\n\t\tend\n\tend\n\treturn true\nend\n\nfunction download_file(file)\n\tfile = nixio.fs.realpath(file)\n\tif security_check(file) then\n\t\tlocal f = nixio.open(file, \"r\")\n\t\thttp.header('Content-Disposition', 'attachment; filename=\"%s\"' % nixio.fs.basename(file))\n\t\thttp.header('Content-Length', nixio.fs.stat(file, \"size\"))\n\t\thttp.prepare_content(\"application/octet-stream\")\n\t\trepeat\n\t\t\tlocal buf = f:read(2^13)\t-- 8k\n\t\t\thttp.write(buf)\n\t\tuntil (buf == \"\")\n\t\tf:close()\n\tend\nend\n\nfunction download_all(path)\n\tpath = nixio.fs.realpath(path)\n\tif security_check(path) then\n\t\tif string.find(string.lower(http.getenv(\"HTTP_USER_AGENT\")), \"linux\") then\n\t\t\tdownload_all_as_tar(path)\n\t\telse\n\t\t\tdownload_all_as_zip(path)\n\t\tend\n\tend\nend\n\nfunction download_all_as_zip(path)\n\tlocal reader = sys.ltn12_popen(\"zip -0 -j -r - \\\"%s\\\"\" % path)\n\thttp.header('Content-Disposition', 'attachment; filename=\"%s.zip\"' % nixio.fs.basename(path))\n\thttp.prepare_content(\"application/zip\")\n\tltn12.pump.all(reader, http.write)\nend\n\nfunction download_all_as_tar(path)\n\tlocal reader = sys.ltn12_popen(\"tar -cf - -C \\\"%s\\\" .\" % path)\n\thttp.header('Content-Disposition', 'attachment; filename=\"%s.tar\"' % nixio.fs.basename(path))\n\thttp.prepare_content(\"application/x-tar\")\n\tltn12.pump.all(reader, http.write)\nend\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\n-- Custom fields:\n-- d.custom1: tags (space delimited) \n-- d.custom2: tracker favicon \n-- d.custom3: url of torrent file  \n-- d.custom4: not used\n-- d.custom5: when \"1\": delete files from disk on erase\n\nlocal common = require \"luci.model.cbi.rtorrent.common\"\nlocal rtorrent = require \"rtorrent\"\nrequire \"luci.model.cbi.rtorrent.string\"\n\nlocal selected, format, total = {}, {}, {}\n\nlocal methods = { \"hash\", \"name\", \"size_bytes\", \"bytes_done\", \"hashing\", \"state\", \"complete\",\n\t\"peers_accounted\", \"peers_complete\", \"down.rate\", \"up.rate\", \"ratio\", \"up.total\", \n\t\"timestamp.started\", \"timestamp.finished\", \"custom1\", \"custom2\" }\n\nfunction status(d)\n\tif     d[\"hashing\"] > 0 then return \"hash\"\n\telseif d[\"state\"] == 0 then return \"stop\"\n\telseif d[\"state\"] > 0 then\n\t\tif d[\"complete\"] == 0 then return \"down\"\n\t\telse return \"seed\" end\n\telse return \"unknown\" end\nend\n\nfunction eta(d)\n\tif d[\"bytes_done\"] < d[\"size_bytes\"] then\n\t\tif d[\"down_rate\"] > 0 then\n\t\t\treturn common.human_time((d[\"size_bytes\"] - d[\"bytes_done\"]) / d[\"down_rate\"])\n\t\telse return \"&#8734;\" end\n\telse return \"--\" end\nend\n\nfunction favicon(d)\n\tif not d[\"custom2\"] or d[\"custom2\"] == \"\" then\n\t\td[\"custom2\"] = \"/luci-static/resources/icons/unknown_tracker.png\"\n\t\tfor _, t in pairs(rtorrent.multicall(\"t.\", d[\"hash\"], 0, \"url\", \"is_enabled\")) do\n\t\t\tif t[\"is_enabled\"] then\n\t\t\t\tlocal domain = t[\"url\"]:match(\"[%w%.:/]*[%./](%w+%.%w+)\")\n\t\t\t\tif domain then\n\t\t\t\t\tlocal icon = \"http://\" .. domain .. \"/favicon.ico\"\n\t\t\t\t\tif common.get(icon) then\n\t\t\t\t\t\td[\"custom2\"] = icon\n\t\t\t\t\t\tbreak\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\t\trtorrent.call(\"d.custom2.set\", d[\"hash\"], d[\"custom2\"])\n\tend\n \treturn d[\"custom2\"]\nend\n\nfunction has_tag(tags, tag)\n\tfor _, t in ipairs(tags) do\n\t\tif t.name:lower() == tag:lower() then return true end\n\tend\n\treturn false\nend\n\nfunction get_tags(details)\n\tlocal l = {}\n\tlocal has_incomplete = false\n\tfor _, d in ipairs(details) do\n\t\tfor _, p in ipairs(d[\"custom1\"]:split()) do\n\t\t\tif not has_tag(l, p) then\n\t\t\t\ttable.insert(l, {name = p:ucfirst(), link = luci.dispatcher.build_url(\"admin/rtorrent/main/%s\" % p)})\n\t\t\tend\n\t\tend\n\t\tif d[\"complete\"] == 0 then\n\t\t\thas_incomplete = true\n\t\tend\n\tend\n\tif has_incomplete then\n\t\ttable.insert(l, {name = translate(\"Incomplete\"), link = luci.dispatcher.build_url(\"admin/rtorrent/main/incomplete\")})\n\tend\n\treturn l\nend\n\nfunction filter(details, page)\n\tlocal filtered = {}\n\tfor _, d in ipairs(details) do\n\t\tif string.find(\" \" .. d[\"custom1\"] .. \" \", \" \" .. page .. \" \") then\n\t\t\ttable.insert(filtered, d)\n\t\tend\n\t\tif page == \"incomplete\" and d[\"complete\"] == 0 then\n\t\t\ttable.insert(filtered, d)\n\t\tend\n\tend\n\treturn filtered\nend\n\nfunction format.icon(d, v)\n\treturn \"<img src=\\\"\" .. v .. \"\\\" />\"\nend\n\nfunction format.name(d, v)\n\ttotal[\"name\"] = (total[\"name\"] or 0) + 1\n\tlocal url = luci.dispatcher.build_url(\"admin/rtorrent/files/\" .. d[\"hash\"])\n\treturn \"<a href=\\\"%s\\\">%s</a>\" % {url, v}\nend\n\nfunction format.size_bytes(d, v)\n\ttotal[\"size_bytes\"] = (total[\"size_bytes\"] or 0) + v\n\treturn \"<div title=\\\"%s B\\\">%s</div>\" % {v, common.human_size(v)}\nend\n\nfunction format.done_percent(d, v)\n\treturn string.format(\"%.1f%%\", v)\nend\n\nfunction format.status(d, v)\n\treturn common.div(v, v == \"stop\" and \"red\", v == \"seed\" and \"blue\",\n\t\tv == \"down\" and \"green\", v == \"hash\" and \"green\")\nend\n\nfunction format.down_rate(d, v)\n\ttotal[\"down_rate\"] = (total[\"down_rate\"] or 0) + v\n\treturn string.format(\"%.2f\", v / 1000)\nend\n\nfunction format.up_rate(d, v)\n\ttotal[\"up_rate\"] = (total[\"up_rate\"] or 0) + v\n\treturn string.format(\"%.2f\", v / 1000)\nend\n\nfunction format.ratio(d, v)\n\treturn common.div(string.format(\"%.2f\", v / 1000), v < 1000 and \"red\" or \"green\")\n\t--\t\"title: Total uploaded: \" .. common.human_size(d[\"up_total\"]))\nend\n\nfunction format.eta(d, v)\n\tlocal download_started = d[\"timestamp_started\"] == 0\n\t\tand translate(\"not yet started\") or os.date(\"!%Y-%m-%d %H:%M:%S\", d[\"timestamp_started\"])\n\tlocal download_finished = d[\"timestamp_finished\"] == 0\n\t\tand translate(\"not yet finished\") or os.date(\"!%Y-%m-%d %H:%M:%S\", d[\"timestamp_finished\"])\n\treturn \"<div title=\\\"Download started: %s&#13;Download finished: %s\\\">%s</div>\" % \n\t\t{download_started, download_finished, v }\nend\n\nfunction add_custom(details)\n\tfor _, d in ipairs(details) do\n\t\t-- refactor: swap favicon (custom1) and tags (custom2)\n\t\tif d[\"custom1\"]:ends(\".ico\") or d[\"custom1\"]:ends(\".png\") then\n\t\t\tlocal temp = d[\"custom1\"]\n\t\t\td[\"custom1\"] = d[\"custom2\"]\n\t\t\td[\"custom2\"] = temp\n\t\t\trtorrent.call(\"d.custom1.set\", d[\"hash\"], d[\"custom1\"])\n\t\t\trtorrent.call(\"d.custom2.set\", d[\"hash\"], d[\"custom2\"])\n\t\tend\n\t\td[\"status\"] = status(d)\n\t\td[\"done_percent\"] = 100.0 * d[\"bytes_done\"] / d[\"size_bytes\"]\n\t\td[\"eta\"] = eta(d)\n\t\td[\"icon\"] = favicon(d)\n\t\td[\"custom1\"] = (d[\"custom1\"] == \"\") and \"all\" or d[\"custom1\"]\n\tend\nend\n\nfunction add_summary(details)\n \ttable.insert(details, {\n \t\t[\"name\"] = \"TOTAL: \" .. total[\"name\"] .. \" pcs.\",\n \t\t[\"size_bytes\"] = common.human_size(total[\"size_bytes\"]),\n \t\t[\"down_rate\"] = string.format(\"%.2f\", total[\"down_rate\"] / 1000),\n \t\t[\"up_rate\"] =  string.format(\"%.2f\", total[\"up_rate\"] / 1000),\n \t\t[\"select\"] = \"%hidden%\"\n \t})\nend\n\nfunction html_format(details)\n\ttable.sort(details, function(a, b) return a[\"name\"] < b[\"name\"] end)\n\tfor _, d in ipairs(details) do\n\t\tfor m, v in pairs(d) do\n\t\t\td[m] = format[m] and format[m](d, v) or tostring(v)\n\t\tend\n\tend\nend\n\nf = SimpleForm(\"rtorrent\", translate(\"Torrent List\"))\nf.reset = false\nf.submit = false\n\nlocal details = rtorrent.multicall(\"d.\", \"default\", unpack(methods))\nadd_custom(details)\nlocal tags = get_tags(details)\nlocal user = luci.dispatcher.context.authuser\nlocal page = arg[1] or (has_tag(tags, user) and user or \"all\")\nlocal filtered = filter(details, page)\n\nhtml_format(filtered)\nif #filtered > 1 then add_summary(filtered) end\nt = f:section(Table, filtered)\nt.template = \"rtorrent/list\"\nt.pages = tags\nt.page = page\nt.headcol = 2\n\nAbstractValue.tooltip = function(self, s) self.hint = s return self end\n\nt:option(DummyValue, \"icon\").rawhtml = true\nt:option(DummyValue, \"name\", translate(\"Name\")).rawhtml = true\nt:option(DummyValue, \"size_bytes\", translate(\"Size\")):tooltip(\"Full size of torrent\").rawhtml = true\nt:option(DummyValue, \"done_percent\", translate(\"Done\")):tooltip(\"Download done percent\").rawhtml = true\nt:option(DummyValue, \"status\", translate(\"Status\")).rawhtml = true\nt:option(DummyValue, \"peers_accounted\", \"&uarr;\"):tooltip(\"Seeder count\").rawhtml = true\nt:option(DummyValue, \"peers_complete\", \"&darr;\"):tooltip(\"Leecher count\").rawhtml = true\nt:option(DummyValue, \"down_rate\", translate(\"Down<br />Speed\")):tooltip(\"Download speed in kb/s\").rawhtml = true\nt:option(DummyValue, \"up_rate\", translate(\"Up<br />Speed\")):tooltip(\"Upload speed in kb/s\").rawhtml = true\nt:option(DummyValue, \"ratio\", translate(\"Ratio\")):tooltip(\"Download/upload ratio\").rawhtml = true\nt:option(DummyValue, \"eta\", translate(\"ETA\")):tooltip(\"Estimated Time of Arrival\").rawhtml = true\n\nselect = t:option(Flag, \"select\")\nselect.template = \"rtorrent/fvalue\"\n\nfunction select.write(self, section, value)\n\ttable.insert(selected, filtered[section].hash)\nend\n\ns = f:section(SimpleSection)\ns.template = \"rtorrent/buttonsection\"\ns.style = \"float: right;\"\n\nstart = s:option(Button, \"start\", translate(\"Start\"))\nstart.template = \"rtorrent/button\"\nstart.inputstyle = \"apply\"\n\nfunction start.write(self, section, value)\n\tif next(selected) ~= nil then\n\t\tfor _, hash in ipairs(selected) do\n\t\t\trtorrent.call(\"d.open\", hash)\n\t\t\trtorrent.call(\"d.start\", hash)\n\t\tend\n\t\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/main/\" .. page))\n\tend\nend\n\nstop = s:option(Button, \"stop\", translate(\"Stop\"))\nstop.template = \"rtorrent/button\"\nstop.inputstyle = \"reset\"\n\nfunction stop.write(self, section, value)\n\tif next(selected) ~= nil then\n\t\tfor _, hash in ipairs(selected) do\n\t\t\trtorrent.call(\"d.stop\", hash)\n\t\t\trtorrent.call(\"d.close\", hash)\n\t\tend\n\t\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/main/\" .. page))\n\tend\nend\n\nremove = s:option(Button, \"remove\", translate(\"Remove\"))\nremove.template = \"rtorrent/button\"\nremove.inputstyle = \"remove\"\n\nfunction remove.write(self, section, value)\n\tif next(selected) ~= nil then\n\t\tfor _, hash in ipairs(selected) do\n\t\t\trtorrent.call(\"d.close\", hash)\n\t\t\trtorrent.call(\"d.erase\", hash)\n\t\tend\n\t\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/main/\" .. page))\n\tend\nend\n\nr = f:section(SimpleSection)\nr.template = \"rtorrent/buttonsection\"\nr.style = \"float: right;\"\n\ndelete = r:option(Button, \"delete\", translate(\"Remove and delete data\"))\ndelete.template = \"rtorrent/button\"\ndelete.inputstyle = \"remove\"\n\nfunction delete.write(self, section, value)\n\tif next(selected) ~= nil then\n\t\tfor _, hash in ipairs(selected) do\n\t\t\trtorrent.call(\"d.custom5.set\", hash, \"1\")\n\t\t\trtorrent.call(\"d.close\", hash)\n\t\t\trtorrent.call(\"d.erase\", hash)\n\t\tend\n\t\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/main/\" .. page))\n\tend\nend\n\nreturn f\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/rss-rule.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal rtorrent = require \"rtorrent\"\n\nm = Map(\"rtorrent\")\nm.redirect = luci.dispatcher.build_url(\"admin/rtorrent/rss\")\n\nlocal name = m:get(arg[1], \"name\")\nm.title = \"%s - %s\" %{\"RSS Downloader\", name or \"(Unnamed rule)\"}\n\ns = m:section(NamedSection, arg[1], \"rss-rule\")\ns.anonymous = true\ns.addremove = false\n\nenabled = s:option(Flag, \"enabled\", \"Enabled\")\nenabled.rmempty = false\n\nname = s:option(Value, \"name\", \"Name\")\nname.rmempty = false\n\nmatch = s:option(TextValue, \"match\", \"Match (lua regex)\")\nmatch.template = \"rtorrent/tvalue\"\nmatch.rmempty = false\nmatch.rows = 1\n\nexclude = s:option(TextValue, \"exclude\", \"Exclude (lua regex)\")\nexclude.rows = 1\n\ns:option(Value, \"minsize\", \"Min size (MiB):\")\ns:option(Value, \"maxsize\", \"Max size (MiB):\")\n\nfeed = s:option(MultiValue, \"feed\", \"Feed\")\nfeed.delimiter = \";\"\nm.uci:foreach(m.config, \"rss-feed\", function(f)\n\tfeed:value(f.name, f.name)\nend)\n\ntags = s:option(Value, \"tags\", \"Add tags\")\ntags.default = \"all\"\n\ndestdir = s:option(Value, \"destdir\", \"Download directory\")\ndestdir.default = rtorrent.call(\"directory.default\")\ndestdir.datatype = \"directory\"\ndestdir.rmempty = false\n\nautostart = s:option(Flag, \"autostart\", \"Start download\")\nautostart.default  = \"1\"\nautostart.rmempty = false\n\nreturn m\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/rss.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nm = Map(\"rtorrent\", \"RSS Downloader\")\n\ns = m:section(TypedSection, \"rss-rule\")\ns.addremove = true\ns.anonymous = true\ns.sortable = true\ns.template = \"cbi/tblsection\"\ns.extedit = luci.dispatcher.build_url(\"admin/rtorrent/rss/%s\")\ns.template_addremove = \"rtorrent/rss_addrule\"\n\nfunction s.parse(self, ...)\n\tTypedSection.parse(self, ...)\n\n\tlocal newrule_name = m:formvalue(\"_newrule.name\")\n\tlocal newrule_submit = m:formvalue(\"_newrule.submit\")\n\n\tif newrule_submit then\n\t\tnewrule = TypedSection.create(self, section)\n\t\tself.map:set(newrule, \"name\", newrule_name)\n\n\t\tm.uci:save(\"rtorrent\")\n\t\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/rss\", newrule))\n\tend\nend\n\nname = s:option(DummyValue, \"name\", \"Name\")\nname.width = \"30%\"\n\nmatch = s:option(DummyValue, \"match\", \"Match\")\nmatch.width = \"60%\"\n\nenabled = s:option(Flag, \"enabled\", \"Enabled\")\nenabled.rmempty = false\n\nreturn m\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/string.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nfunction string.starts(str, begin)\n\tif not str then return false end\n\treturn string.sub(str, 1, string.len(begin)) == begin\nend\n\nfunction string.ends(str, tail)\n\tif not str then return false end\n\treturn string.sub(str, -string.len(tail)) == tail\nend\n\nfunction string.split(str, sep)\n\tif sep == nil then sep = \"%s\" end\n\tlocal t = {}\n\tfor s in str:gmatch(\"([^\" .. sep .. \"]+)\") do\n\t\ttable.insert(t, s)\n\tend\n\treturn t\nend\n\nfunction string.ucfirst(str)\n\treturn (str:gsub(\"^%l\", string.upper))\nend\n\nfunction string.trim(str)\n\treturn str:match(\"^()%s*$\") and \"\" or str:match(\"^%s*(.*%S)\")\nend\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/files.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal rtorrent = require \"rtorrent\"\nlocal nixio = require \"nixio\"\nlocal common = require \"luci.model.cbi.rtorrent.common\"\n\nlocal hash = arg[1]\nlocal details = rtorrent.batchcall({\"name\", \"base_path\"}, hash, \"d.\")\nlocal files = rtorrent.multicall(\"f.\", hash, 0, \"path\", \"path_depth\", \"path_components\", \"size_bytes\",\n\t\"size_chunks\", \"completed_chunks\", \"priority\", \"frozen_path\")\n\nlocal format, total = {}, {}\n\nfunction format.icon(r, v)\n\tlocal icon_path = \"/luci-static/resources/icons/filetypes\"\n\tlocal ext = v:match(\"%.([^%.]+)$\")\n\tif ext and nixio.fs.stat(\"/www/%s/%s.png\" % {icon_path, ext:lower()}, \"type\") then\n\t\treturn \"%s/%s.png\" % {icon_path, ext:lower()}\n\tend\n\treturn \"%s/%s.png\" % {icon_path, \"file\"}\nend\n\nfunction format.dir(r, v)\n\treturn \"<img style=\\\"vertical-align: text-top;\\\" src=\\\"/luci-static/resources/icons/filetypes/dir.png\\\" /> \" .. v\nend\n\nfunction format.file(r, v)\n\ttotal[\"name\"] = (total[\"name\"] or 0) + 1\n\tlocal url = luci.dispatcher.build_url(\"admin/rtorrent/download/\") .. nixio.bin.b64encode(r[\"frozen_path\"])\n\tlocal link = r[\"chunks_percent\"] == 100 and \"<a href=\\\"\" .. url .. \"\\\">\" .. v .. \"</a>\" or v\n\treturn \"<img style=\\\"vertical-align: middle;\\\" src=\\\"\" .. format[\"icon\"](r, v) .. \"\\\" /> \" .. link\nend\n\nfunction format.size_bytes(r, v)\n\ttotal[\"size_bytes\"] = (total[\"size_bytes\"] or 0) + v\n\treturn common.human_size(v)\nend\n\nfunction format.chunks_percent(r, v)\n\treturn common.div(string.format(\"%.1f%%\", v), v < 100 and \"red\")\nend\n\nfunction format.priority(r, v)\n\treturn tostring(v)\nend\n\nfunction add_custom(files)\n\tfor i, r in ipairs(files) do\n\t\tr[\"id\"] = i\n\t\tr[\"chunks_percent\"] = r[\"completed_chunks\"] * 100.0 / r[\"size_chunks\"]\n\tend\nend\n\nfunction add_summary(list)\n \ttable.insert(list, {\n \t\t[\"name\"] = (translate(\"TOTAL\").. \": \".. total[\"name\"]),\n \t\t[\"size_bytes\"] = common.human_size(total[\"size_bytes\"]),\n\t\t[\"priority\"] = \"%hidden%\"\n \t})\nend\n\nfunction path_compare(a, b)\n\tif a[\"path_depth\"] ~= b[\"path_depth\"] and (a[\"path_depth\"] == 1 or b[\"path_depth\"] == 1) then\n\t\treturn a[\"path_depth\"] > b[\"path_depth\"]\n\tend\n\treturn a[\"path\"] < b[\"path\"]\nend\n\nlocal list, last_path = {}, {}\nadd_custom(files)\ntable.sort(files, path_compare)\nfor _, r in ipairs(files) do\n\tfor i, p in ipairs(r[\"path_components\"]) do\n\t\tif last_path[i] ~= p then\n\t\t\tlocal t = i == #r[\"path_components\"] and \"file\" or \"dir\"\n\t\t\tlocal n = {}\n\t\t\tif t == \"file\" then\n\t\t\t\tfor m, v in pairs(r) do\n\t\t\t\t\tn[m] = format[m] and format[m](r, v) or tostring(v)\n\t\t\t\tend\n\t\t\telse\n\t\t\t\tn[\"priority\"] = \"%hidden%\"\n\t\t\tend\n\t\t\tn[\"name\"] = string.rep(\"&emsp;\", i - 1) .. format[t](r, p)\n\t\t\ttable.insert(list, n)\n\t\tend\n\t\tlast_path[i] = p\n\tend\nend\n\nf = SimpleForm(\"rtorrent\", details[\"name\"])\nf.redirect = luci.dispatcher.build_url(\"admin/rtorrent/main\")\nif nixio.fs.stat(details[\"base_path\"], \"type\") == \"dir\" and table.getn(list) > 1 then\n\tf.cancel = \"Download all\"\nelse\n\tf.cancel = false\nend\n\nif #list > 1 then add_summary(list) end\nt = f:section(Table, list)\nt.template = \"rtorrent/list\"\nt.pages = common.get_torrent_pages(hash)\nt.page = \"File List\"\n\nAbstractValue.tooltip = function(self, s) self.hint = s return self end\n\nt:option(DummyValue, \"name\", translate(\"Name\")).rawhtml = true\nt:option(DummyValue, \"size_bytes\", translate(\"Size\"))\nt:option(DummyValue, \"chunks_percent\", translate(\"Done\")):tooltip(\"Download done percent\").rawhtml = true\nprio = t:option(ListValue, \"priority\", translate(\"Priority\")):tooltip(\"Rotate priority\")\nprio.template = \"rtorrent/lvalue\"\nprio.onclick = [[\n\tvar inputs = document.getElementsByClassName(\"cbi-input-select\");\n\tfor (var i = 0; i < inputs.length; i++) {\n\t\tif (inputs[i].selectedIndex < inputs[i].length - 1) {\n\t\t\tinputs[i].selectedIndex++;\n\t\t} else {\n\t\t\tinputs[i].selectedIndex = 0;\n\t\t}\n\t}\n]]\nprio:value(\"0\", \"off\")\nprio:value(\"1\", \"normal\")\nprio:value(\"2\", \"high\")\n\nfunction prio.write(self, section, value)\n\trtorrent.call(\"f.priority.set\", hash .. \":f\" .. (list[tonumber(section)].id - 1), tonumber(value))\n\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/files/%s\" % hash))\nend\n\nfunction f:on_cancel()\n\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/downloadall/\")\n\t\t.. nixio.bin.b64encode(details[\"base_path\"]))\nend\n\nreturn f\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/info.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal rtorrent = require \"rtorrent\"\nlocal common = require \"luci.model.cbi.rtorrent.common\"\n\nlocal hash = arg[1]\nlocal details = rtorrent.batchcall({\"name\", \"custom1\", \"timestamp.started\", \"timestamp.finished\"}, hash, \"d.\")\n\nf = SimpleForm(\"rtorrent\", details[\"name\"])\nf.redirect = luci.dispatcher.build_url(\"admin/rtorrent/main\")\n\nt = f:section(Table, list)\nt.template = \"rtorrent/list\"\nt.pages = common.get_torrent_pages(hash)\nt.page = \"Info\"\n\nhash_id = f:field(DummyValue, \"hash\", translate(\"Hash\"))\nfunction hash_id.cfgvalue(self, section)\n\treturn hash\nend\n\nstarted = f:field(DummyValue, \"started\", translate(\"Download started\"))\nstarted.value = details[\"timestamp.started\"] == 0\n\tand \"not yet started\"\n\tor os.date(\"!%Y-%m-%d %H:%M:%S\", details[\"timestamp.started\"])\n\nfinished = f:field(DummyValue, \"finished\", translate(\"Download finished\"))\nfinished.value = details[\"timestamp.finished\"] == 0\n\tand \"not yet finished\"\n\tor os.date(\"!%Y-%m-%d %H:%M:%S\", details[\"timestamp.finished\"])\n\ntags = f:field(Value, \"tags\", translate(\"Tags\"))\ntags.default = details[\"custom1\"]\ntags.rmempty = false\n\nfunction tags.write(self, section, value)\n\trtorrent.call(\"d.custom1.set\", hash, value)\nend\n\nfunction f.handle(self, state, data)    \n\tif state == FORM_VALID then\n\t\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/info/\") .. hash)\n\tend\n\treturn true\nend\n\nreturn f\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal rtorrent = require \"rtorrent\"\nlocal http = require \"socket.http\"\nlocal common = require \"luci.model.cbi.rtorrent.common\"\n\nlocal hash = arg[1]\nlocal details = rtorrent.batchcall({\"name\"}, hash, \"d.\")\nlocal format, total, map = {}, {}, {}\n\nlocal geoplugin_net = {\n\taddress = \"http://www.geoplugin.net/json.gp?ip=%s\",\n\tfields = { geoplugin_countryCode = \"country_code\", geoplugin_countryName = \"country\", geoplugin_region = \"region\",\n\t\t   geoplugin_city = \"city\", geoplugin_latitude = \"latitude\", geoplugin_longitude = \"longitude\"\n}}\n\nlocal ip2geo = geoplugin_net \n\nfunction map.googlemap(latitude, longitude, zoom)\n\treturn \"https://google.com/maps/place/%s,%s/@%s,%s,%sz\" % {latitude, longitude, latitude, longitude, zoom}\nend\n\nfunction map.openstreetmap(latitude, longitude, zoom)\n\treturn \"http://www.openstreetmap.org/?mlat=%s&mlon=%s#map=%s/%s/%s/m\" % {latitude, longitude, zoom, latitude, longitude}\nend\n\nfunction format.address(r, v)\n\ttotal[\"address\"] = (total[\"address\"] or 0) + 1\n\tlocal map = map.googlemap(r.latitude, r.longitude, 11)\n\t-- local map = map.openstreetmap(r.latitude, r.longitude, 11)\n\t-- local flag = \"<img src=\\\"http://www.iplocation.net/images/flags/%s.gif\\\" />\" % r.country_code:lower()\n\t-- local flag = \"<img src=\\\"http://static.hltv.org/images/flag/%s.gif\\\" />\" % r.country_code:lower()\n\tlocal flag = \"<img src=\\\"https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/gif/%s.gif\\\" />\" % r.country_code:lower()\n\treturn \"%s <a href=\\\"%s\\\" target=\\\"_blank\\\">%s</a>\" % {flag, map, v}\nend\n\nfunction format.completed_percent(r, v)\n\treturn string.format(\"%.1f%%\", v)\nend\n\nfunction format.down_rate(d, v)\n\ttotal[\"down_rate\"] = (total[\"down_rate\"] or 0) + v\n\treturn string.format(\"%.2f\", v / 1000)\nend\n\nfunction format.up_rate(d, v)\n\ttotal[\"up_rate\"] = (total[\"up_rate\"] or 0) + v\n\treturn string.format(\"%.2f\", v / 1000)\nend\n\nfunction format.down_total(d, v)\n\treturn \"<div title=\\\"%s B\\\">%s</div>\" % {v, common.human_size(v)}\nend\n\nfunction format.up_total(d, v)\n\treturn format.down_total(d, v)\nend\n\nfunction json2table(json)\n\tloadstring(\"j2t = \" .. string.gsub(string.gsub(json, '([,%{])%s*\\n?%s*\"', '%1[\"'), '\"%s*:%s*', '\"]='))()\n\treturn j2t\nend\n\nfunction add_location(r)\n\tfor i, j in pairs(json2table(http.request(ip2geo.address % r.address))) do\n\t\tif ip2geo.fields[i] then r[ip2geo.fields[i]] = j end\n\tend\n\tlocal location = {}\n\tfor _, k in ipairs({\"country\", \"region\", \"city\"}) do\n\t\tif r[k] ~= \"\" and not tonumber(r[k]) then\n\t\t\ttable.insert(location, (r[k]:gsub(\"\\u(%x%x%x%x)\", \"&#x%1\")))\n\t\tend\n\tend\n\tr[\"location\"] = table.concat(location, \"/\")\nend\n\nfunction add_summary(list)\n \ttable.insert(list, {\n \t\t[\"address\"] = (translate(\"TOTAL\").. \": \"  .. total[\"address\"]),\n \t\t[\"down_rate\"] = string.format(\"%.2f\", total[\"down_rate\"] / 1000),\n \t\t[\"up_rate\"] = string.format(\"%.2f\", total[\"up_rate\"] / 1000)\n \t})\nend\n\nlocal list = rtorrent.multicall(\"p.\", hash, 0, \"address\", \"completed_percent\", \"client_version\", \n\t\"down_rate\", \"up_rate\", \"up_total\", \"down_total\")\n\nfor _, r in ipairs(list) do\n\tadd_location(r)\n\tfor k, v in pairs(r) do\n\t\tr[k] = format[k] and format[k](r, v) or tostring(v)\n\tend\nend\n\nf = SimpleForm(\"rtorrent\", details[\"name\"])\nf.redirect = luci.dispatcher.build_url(\"admin/rtorrent/main\")\nf.reset = false\nf.submit = false\n\nif #list > 1 then add_summary(list) end\nt = f:section(Table, list)\nt.template = \"rtorrent/list\"\nt.pages = common.get_torrent_pages(hash)\nt.page = \"Peer List\"\n\nAbstractValue.tooltip = function(self, s) self.hint = s return self end\n\nt:option(DummyValue, \"address\", translate(\"Address\")):tooltip(\"Peer IP address\").rawhtml = true\nt:option(DummyValue, \"client_version\", translate(\"Client\")):tooltip(\"Client version\")\nt:option(DummyValue, \"location\", translate(\"Location\")):tooltip(\"Location: country/region/city\").rawhtml = true\nt:option(DummyValue, \"completed_percent\", translate(\"Done\")):tooltip(\"Download done percent\")\nt:option(DummyValue, \"down_rate\", translate(\"Down<br />Speed\")):tooltip(\"Download speed in kb/s\")\nt:option(DummyValue, \"up_rate\", translate(\"Up<br />Speed\")):tooltip(\"Upload speed in kb/s\")\nt:option(DummyValue, \"down_total\", translate(\"Downloaded\")):tooltip(\"Total downloaded\").rawhtml = true\nt:option(DummyValue, \"up_total\", translate(\"Uploaded\")):tooltip(\"Total uploaded\").rawhtml = true\n\nreturn f\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal rtorrent = require \"rtorrent\"\nlocal common = require \"luci.model.cbi.rtorrent.common\"\n\nlocal hash = arg[1]\nlocal details = rtorrent.batchcall({\"name\"}, hash, \"d.\")\n\nlocal format, total = {}, {}\n\nfunction format.url(r, v)\n\ttotal[\"url\"] = (total[\"url\"] or 0) + 1\nend\n\nfunction format.scrape_downloaded(r, v)\n\ttotal[\"scrape_downloaded\"] = (total[\"scrape_downloaded\"] or 0) + v\n\treturn tostring(v)\nend\n\nfunction format.scrape_complete(r, v)\n\ttotal[\"scrape_complete\"] = (total[\"scrape_complete\"] or 0) + v\n\treturn tostring(v)\nend\n\nfunction format.scrape_incomplete(r, v)\n\ttotal[\"scrape_incomplete\"] = (total[\"scrape_incomplete\"] or 0) + v\n\treturn tostring(v)\nend\n\nfunction format.scrape_time_last(r, v)\n\treturn common.human_time(os.time() - v)\nend\n\nfunction add_summary(list)\n \ttable.insert(list, {\n \t\t[\"url\"] = (translate(\"TOTAL\").. \": \" .. total[\"url\"]),\n\t\t[\"scrape_downloaded\"] = tostring(total[\"scrape_downloaded\"]),\n\t\t[\"scrape_complete\"] = tostring(total[\"scrape_complete\"]),\n\t\t[\"scrape_incomplete\"] = tostring(total[\"scrape_incomplete\"]),\n\t\t[\"is_enabled\"] = \"%hidden%\"\n \t})\nend\n\nlocal list = rtorrent.multicall(\"t.\", hash, 0, \"is_enabled\", \"url\", \"scrape_downloaded\", \"scrape_complete\",\n\t\"scrape_incomplete\", \"scrape_time_last\")\n\nfor _, r in ipairs(list) do\n\tfor k, v in pairs(r) do\n\t\tr[k] = format[k] and format[k](r, v) or tostring(v)\n\tend\nend\n\nf = SimpleForm(\"rtorrent\", details[\"name\"])\nf.redirect = luci.dispatcher.build_url(\"admin/rtorrent/main\")\n\nif #list > 1 then add_summary(list) end\nt = f:section(Table, list)\nt.template = \"rtorrent/list\"\nt.pages = common.get_torrent_pages(hash)\nt.page = \"Tracker List\"\n\nAbstractValue.tooltip = function(self, s) self.hint = s return self end\n\nt:option(DummyValue, \"url\", translate(\"Url\"))\nt:option(DummyValue, \"scrape_downloaded\", translate(\"D\")):tooltip(\"Downloaded\")\nt:option(DummyValue, \"scrape_complete\", translate(\"S\")):tooltip(\"Seeders\")\nt:option(DummyValue, \"scrape_incomplete\", translate(\"L\")):tooltip(\"Leechers\")\nt:option(DummyValue, \"scrape_time_last\", translate(\"Updated\")):tooltip(\"Last update time\").rawhtml = true\n\nenabled = t:option(Flag, \"is_enabled\", translate(\"Enabled\"))\nenabled.template = \"rtorrent/fvalue\"\nenabled.rmempty = false\nenabled.rawhtml = true\n\nfunction enabled.write(self, section, value)\n\tif value ~= tostring(list[section].is_enabled) then\n\t\trtorrent.call(\"t.is_enabled.set\", hash .. \":t\" .. (section - 1), tonumber(value))\n\t\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/trackers/%s\" % hash))\n\tend\nend\n\nadd = f:field(Value, \"add_tracker\", translate(\"Add tracker\"))\nfunction add.write(self, section, value)\n\trtorrent.call(\"d.tracker.insert\", hash, table.getn(list), value)\n\tluci.http.redirect(luci.dispatcher.build_url(\"admin/rtorrent/trackers/%s\" % hash))\nend\n\nreturn f\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/view/rtorrent/button.htm",
    "content": "<%+cbi/valueheader%>\n<input class=\"cbi-button cbi-button-<%=self.inputstyle or \"button\" %>\" type=\"submit\"<%= attr(\"name\", cbid) .. attr(\"id\", cbid) .. ifattr(self.style, \"style\") .. attr(\"value\", self:cfgvalue(section) or self.inputtitle or self.title)%> />\n<%+cbi/valuefooter%>\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/view/rtorrent/buttonsection.htm",
    "content": "<fieldset class=\"cbi-section\" style=\"padding-top: 0\">\n\t<div class=\"cbi-section-node\" id=\"cbi-<%=self.config%>-<%=tostring(self):sub(8)%>\"<%= ifattr(self.style, \"style\")%>>\n\t\t<% self:render_children(1, { valueheader = \"rtorrent/empty\", valuefooter = \"rtorrent/empty\" }) %>\n\t</div>\n</fieldset>\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/view/rtorrent/empty.htm",
    "content": "\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/view/rtorrent/fvalue.htm",
    "content": "<%+cbi/valueheader%>\n\t<% if not self:cfgvalue(section) or self:cfgvalue(section) ~= \"%hidden%\" then -%>\n\t<input type=\"hidden\" value=\"1\"<%=\n\t\tattr(\"name\", \"cbi.cbe.\" .. self.config .. \".\" .. section .. \".\" .. self.option)\n\t%> />\n\t<input class=\"cbi-input-checkbox\" onclick=\"cbi_d_update(this.id)\" onchange=\"cbi_d_update(this.id)\" type=\"checkbox\"<%=\n\t\tattr(\"id\", cbid) .. attr(\"name\", cbid) .. attr(\"value\", self.enabled or 1) ..\n\t\tifattr((self:cfgvalue(section) or self.default) == self.enabled, \"checked\", \"checked\")\n\t%> />\n\t<%- end %>\n<%+cbi/valuefooter%>\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/view/rtorrent/list.htm",
    "content": "<%-\nlocal rowcnt = 1\nfunction rowstyle()\n\trowcnt = rowcnt + 1\n\treturn (rowcnt % 2) + 1\nend\n-%>\n\n<style type=\"text/css\">\n\t.center { text-align: center }\n\t.nowrap { white-space: nowrap }\n\t.wrap   { word-break: break-all; min-width: 100px }\n\t.red    { color: #b20000 }\n\t.green  { color: #00a100 }\n\t.blue   { color: #0000bf }\n\t.bold   { font-weight: bold }\n\ttable.cbi-section-table a:link { color: #404040 }\n\ttable.cbi-section-table a:visited { color: #404040 }\n</style>\n\n<%+rtorrent/tabmenu%>\n<fieldset class=\"cbi-section\" id=\"cbi-<%=self.config%>-<%=self.sectiontype%>\">\n\t<div class=\"cbi-section-descr\"><%=self.description%></div>\n\t<div class=\"cbi-section-node\">\n\t\t<div class=\"table\">\n\t\t\t<div class=\"tr cbi-rowstyle-2\">\n\t\t\t<%- for i, k in pairs(self.children) do -%>\n\t\t\t\t<div class=\"td <% if i ~= (self.headcol or 1) then %> center nowrap<% else %> wrap<% end -%>\n\t\t\t\t\"<%=ifattr(k.hint, \"title\", k.hint)%><%=ifattr(k.onclick, \"onclick\", k.onclick)%>><b><%=k.title%></b></div>\n\t\t\t<%- end -%>\n\t\t\t</div>\n\n\t\t\t<%- for j, k in ipairs(self:cfgsections()) do\n\t\t\t\tsection = k\n\t\t\t\tscope = { valueheader = \"rtorrent/empty\", valuefooter = \"rtorrent/empty\" }\n\t\t\t-%>\n\t\t\t\t<div class=\"tr cbi-rowstyle-<%=rowstyle()%>\" id=\"cbi-<%=self.config%>-<%=section%>\">\n\t\t\t\t<%- for i, node in ipairs(self.children) do -%>\n\t\t\t\t\t<div class=\" td cbi-value-field<% if i ~= (self.headcol or 1) then %> center nowrap<% else %> wrap\n\t\t\t\t\t<%- end if j > 1 and j == table.getn(self:cfgsections()) then %> bold<% end %>\">\n\t\t\t\t\t\t<div id=\"cbi-<%=node.config..\"-\"..section..\"-\"..node.option%>\">\n\t\t\t\t\t\t\t<%- node:render(section, scope or {}) -%>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div id=\"cbip-<%=node.config..\"-\"..section..\"-\"..node.option%>\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t<%- end -%>\n\t\t\t\t</div>\n\t\t\t<%- end -%>\n\t\t</div>\n\t</div>\n</fieldset>\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/view/rtorrent/lvalue.htm",
    "content": "<%+cbi/valueheader%>\n<% if not self:cfgvalue(section) or self:cfgvalue(section) ~= \"%hidden%\" then -%>\n<% if self.widget == \"select\" then %>\n\t<select class=\"cbi-input-select\" onchange=\"cbi_d_update(this.id)\"<%= attr(\"id\", cbid) .. attr(\"name\", cbid) .. ifattr(self.size, \"size\") .. ifattr(self.style, \"style\") %>>\n\t<% for i, key in pairs(self.keylist) do -%>\n\t\t<option id=\"cbi-<%=self.config..\"-\"..section..\"-\"..self.option..\"-\"..key%>\"<%= attr(\"value\", key) .. ifattr(tostring(self:cfgvalue(section) or self.default) == key, \"selected\", \"selected\") %>><%=striptags(self.vallist[i])%></option>\n\t<%- end %>\n\t</select>\n<% elseif self.widget == \"radio\" then\n\tlocal c = 0\n\tfor i, key in pairs(self.keylist) do\n\tc = c + 1\n%>\n\t<input class=\"cbi-input-radio\" onclick=\"cbi_d_update(this.id)\" onchange=\"cbi_d_update(this.id)\" type=\"radio\"<%= attr(\"id\", cbid..c) .. attr(\"name\", cbid) .. attr(\"value\", key) .. ifattr((self:cfgvalue(section) or self.default) == key, \"checked\", \"checked\") %> />\n\t<label<%= attr(\"for\", cbid..c) %>><%=self.vallist[i]%></label>\n<% if c == self.size then c = 0 %><% if self.orientation == \"horizontal\" then %>&#160;<% else %><br /><% end %>\n<% end end %>\n<% end %>\n<%- end %>\n<%+cbi/valuefooter%>\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/view/rtorrent/rss_addrule.htm",
    "content": "<table>\n\t<tr>\n\t\t<td style=\"width:130px\">\n\t\t\t<input type=\"text\" class=\"cbi-input-text\" id=\"_newrule.name\" name=\"_newrule.name\" placeholder=\"New RSS rule\" />\n\t\t</td>\n\t\t<td>\n\t\t\t<input type=\"submit\" class=\"cbi-button cbi-button-add\" name=\"_newrule.submit\" value=\"<%:Add%>\" />\n\t\t</td>\n\t</tr>\n</table>\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/view/rtorrent/tabmenu.htm",
    "content": "<ul class=\"cbi-tabmenu\" style=\"margin: 9px 0px 0px 0px;\">\n\t<%- for i, k in ipairs(self.pages) do -%>\n\t\t<%- if k.name:lower() ~= \"protected\" then if self.page:lower() == k.name:lower() then -%>\n\t\t\t<li class=\"cbi-tab\"><a href=\"<%=k.link%>\"><%=k.name%></a></li>\n\t\t<%- else -%>\n\t\t\t<li class=\"cbi-tab-disabled\"><a href=\"<%=k.link%>\"><%=k.name%></a></li>\n\t\t<%- end end -%>\n\t<%- end -%>\n</ul>\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/luasrc/view/rtorrent/tvalue.htm",
    "content": "<style type=\"text/css\">\n\t.cbi-value-error textarea {\n\t\tborder-color: #ff0000;\n\t\tcolor: #ff0000;\n\t}\n</style>\n<%+cbi/tvalue%>\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/po/en/rtorrent.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Language: en\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua:10\nmsgid \"Torrent\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua:11\nmsgid \"Torrent List\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua:12\nmsgid \"Add Torrent\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua:13\nmsgid \"RSS Downloader\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua:14\nmsgid \"Torrent Settings\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:11\nmsgid \"Add Torrent\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:16\nmsgid \"Torrent<br />or magnet URI\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:32\nmsgid \"Upload torrent file\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:42\nmsgid \"Download directory\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:47\nmsgid \"Tags\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:52\nmsgid \"Start now\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:79\nmsgid \"Incomplete\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:138\nmsgid \"not yet started\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:140\nmsgid \"not yet finished\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:182\nmsgid \"Torrent List\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:204\nmsgid \"Name\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:205\nmsgid \"Size\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:206\nmsgid \"Done\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:207\nmsgid \"Status\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:210\nmsgid \"Down<br />Speed\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:211\nmsgid \"Up<br />Speed\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:212\nmsgid \"Ratio\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:213\nmsgid \"ETA\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:226\nmsgid \"Start\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:240\nmsgid \"Stop\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:254\nmsgid \"Remove\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:272\nmsgid \"Remove and delete data\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:22\nmsgid \"Admin - rTorrent\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:25\nmsgid \"Bandwidth limits\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:34\nmsgid \"Upload limit (KiB/sec)\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:35\nmsgid \"Global upload rate (0: unlimited)\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:43\nmsgid \"Download limit (KiB/sec)\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:44\nmsgid \"Global downlaod rate (0: unlimited)\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:54\nmsgid \"Global limits\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:56\nmsgid \"Download slots\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:57\nmsgid \"Maximum number of simultaneous downloads\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:65\nmsgid \"Upload slots\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:66\nmsgid \"Maximum number of simultaneous uploads\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:76\nmsgid \"Torrent limits\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:78\nmsgid \"Maximum uploads\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:79\nmsgid \"Maximum number of simultanious uploads per torrent\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:87\nmsgid \"Minimum peers\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:88\nmsgid \"Minimum number of peers to connect to per torrent\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:96\nmsgid \"Maximum peers\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:97\nmsgid \"Maximum number of peers to connect to per torrent\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:105\nmsgid \"Minimum seeds\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:106\nmsgid \"Minimum number of seeds for completed torrents (-1 = same as peers)\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:114\nmsgid \"Maximum seeds\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:115\nmsgid \"Maximum number of seeds for completed torrents (-1 = same as peers)\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/files.lua:57\nmsgid \"TOTAL\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/files.lua:108\nmsgid \"Name\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/files.lua:110\nmsgid \"Done\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/files.lua:111\nmsgid \"Priority\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/info.lua:18\nmsgid \"Hash\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/info.lua:23\nmsgid \"Download started\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/info.lua:28\nmsgid \"Download finished\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/info.lua:33\nmsgid \"Tags\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:80\nmsgid \"TOTAL\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:109\nmsgid \"Address\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:110\nmsgid \"Client\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:111\nmsgid \"Location\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:112\nmsgid \"Done\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:113\nmsgid \"Down<br />Speed\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:114\nmsgid \"Up<br />Speed\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:115\nmsgid \"Downloaded\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:116\nmsgid \"Uploaded\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:37\nmsgid \"TOTAL\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:65\nmsgid \"Url\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:66\nmsgid \"D\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:67\nmsgid \"S\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:68\nmsgid \"L\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:69\nmsgid \"Updated\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:71\nmsgid \"Enabled\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:83\nmsgid \"Add tracker\"\nmsgstr \"\"\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/po/ru/rtorrent.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Language: ru\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Last-Translator: Konstantine Shevlakov <shevlakov@132lan.ru>\\n\"\n\n#: applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua:10\nmsgid \"Torrent\"\nmsgstr \"Торрент\"\n\n#: applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua:11\nmsgid \"Torrent List\"\nmsgstr \"Список торрентов\"\n\n#: applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua:12\nmsgid \"Add Torrent\"\nmsgstr \"Добавить торрент\"\n\n#: applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua:13\nmsgid \"RSS Downloader\"\nmsgstr \"Лента RSS\"\n\n#: applications/luci-app-rtorrent/luasrc/controller/rtorrent.lua:14\nmsgid \"Torrent Settings\"\nmsgstr \"Настройки Торрента\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:11\nmsgid \"Add Torrent\"\nmsgstr \"Добавить торрент\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:16\nmsgid \"Torrent<br />or magnet URI\"\nmsgstr \"Торрент<br /> или magnet-ссылка\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:32\nmsgid \"Upload torrent file\"\nmsgstr \"Загрузить файл\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:42\nmsgid \"Download directory\"\nmsgstr \"Директория загрузок\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:47\nmsgid \"Tags\"\nmsgstr \"Тег\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/add.lua:52\nmsgid \"Start now\"\nmsgstr \"Запустить сейчас\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:79\nmsgid \"Incomplete\"\nmsgstr \"Незавершено\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:138\nmsgid \"not yet started\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:140\nmsgid \"not yet finished\"\nmsgstr \"\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:182\nmsgid \"Torrent List\"\nmsgstr \"Список загрузок\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:204\nmsgid \"Name\"\nmsgstr \"Имя\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:205\nmsgid \"Size\"\nmsgstr \"Размер\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:206\nmsgid \"Done\"\nmsgstr \"Завершено\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:207\nmsgid \"Status\"\nmsgstr \"Статус\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:210\nmsgid \"Down<br />Speed\"\nmsgstr \"Входящ.<br />Скорость\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:211\nmsgid \"Up<br />Speed\"\nmsgstr \"Исх.<br />Скорость\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:212\nmsgid \"Ratio\"\nmsgstr \"Рейтинг\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:213\nmsgid \"ETA\"\nmsgstr \"Осталось\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:226\nmsgid \"Start\"\nmsgstr \"Пуск\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:240\nmsgid \"Stop\"\nmsgstr \"Стоп\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:254\nmsgid \"Remove\"\nmsgstr \"Удалить\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/main.lua:272\nmsgid \"Remove and delete data\"\nmsgstr \"Удалить данные\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:22\nmsgid \"Admin - rTorrent\"\nmsgstr \"Настройки rTorrent\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:25\nmsgid \"Bandwidth limits\"\nmsgstr \"Ограничения скорости\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:34\nmsgid \"Upload limit (KiB/sec)\"\nmsgstr \"Лимит отдачи (KiB/sec)\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:35\nmsgid \"Global upload rate (0: unlimited)\"\nmsgstr \"Общий лимит отдачи (0: без ограничений)\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:43\nmsgid \"Download limit (KiB/sec)\"\nmsgstr \"Лимит загрузки (KiB/sec)\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:44\nmsgid \"Global downlaod rate (0: unlimited)\"\nmsgstr \"Общий лимит загрузки (0: без ограничений)\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:54\nmsgid \"Global limits\"\nmsgstr \"Общие ограничения\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:56\nmsgid \"Download slots\"\nmsgstr \"Слоты загрузки\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:57\nmsgid \"Maximum number of simultaneous downloads\"\nmsgstr \"Максимальное кооличество одновременных загрузок\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:65\nmsgid \"Upload slots\"\nmsgstr \"Слоты отдачи\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:66\nmsgid \"Maximum number of simultaneous uploads\"\nmsgstr \"Максимальное количество одновременных раздач\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:76\nmsgid \"Torrent limits\"\nmsgstr \"Ограничения торрента\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:78\nmsgid \"Maximum uploads\"\nmsgstr \"Максимум потоков\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:79\nmsgid \"Maximum number of simultanious uploads per torrent\"\nmsgstr \"Максимальное количество потоков для оного торрента\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:87\nmsgid \"Minimum peers\"\nmsgstr \"Минимальное кол-во пиров\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:88\nmsgid \"Minimum number of peers to connect to per torrent\"\nmsgstr \"Минимальное количество пиров для торрента\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:96\nmsgid \"Maximum peers\"\nmsgstr \"Максимальное кол-во пиров\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:97\nmsgid \"Maximum number of peers to connect to per torrent\"\nmsgstr \"Максимально количество пиров для одного торрента\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:105\nmsgid \"Minimum seeds\"\nmsgstr \"Минимальное кол-во сидов\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:106\nmsgid \"Minimum number of seeds for completed torrents (-1 = same as peers)\"\nmsgstr \"Минимальное количество сидов для одного торрента (-1 = согласно пирам)\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:114\nmsgid \"Maximum seeds\"\nmsgstr \"Максимус сидов\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/admin/rtorrent.lua:115\nmsgid \"Maximum number of seeds for completed torrents (-1 = same as peers)\"\nmsgstr \"Максимальное количество сидов для одного торрента (-1 = согласно пирам)\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/files.lua:57\nmsgid \"TOTAL\"\nmsgstr \"Всего\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/files.lua:108\nmsgid \"Name\"\nmsgstr \"Имя\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/files.lua:110\nmsgid \"Done\"\nmsgstr \"Завершено\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/files.lua:111\nmsgid \"Priority\"\nmsgstr \"Приоритет\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/info.lua:18\nmsgid \"Hash\"\nmsgstr \"Хеш\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/info.lua:23\nmsgid \"Download started\"\nmsgstr \"Загрузка начата\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/info.lua:28\nmsgid \"Download finished\"\nmsgstr \"Загрузка окончена\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/info.lua:33\nmsgid \"Tags\"\nmsgstr \"Тег\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:80\nmsgid \"TOTAL\"\nmsgstr \"Всего\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:109\nmsgid \"Address\"\nmsgstr \"Адрес\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:110\nmsgid \"Client\"\nmsgstr \"Клиент\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:111\nmsgid \"Location\"\nmsgstr \"Место\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:112\nmsgid \"Done\"\nmsgstr \"Завершено\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:113\nmsgid \"Down<br />Speed\"\nmsgstr \"Входящ.<br />Скорость\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:114\nmsgid \"Up<br />Speed\"\nmsgstr \"Исх.<br />Скорость\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:115\nmsgid \"Downloaded\"\nmsgstr \"Загружено\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/peers.lua:116\nmsgid \"Uploaded\"\nmsgstr \"Отдано\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:37\nmsgid \"TOTAL\"\nmsgstr \"Всего\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:65\nmsgid \"Url\"\nmsgstr \"Адрес\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:66\nmsgid \"D\"\nmsgstr \"Загр.\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:67\nmsgid \"S\"\nmsgstr \"Сиды\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:68\nmsgid \"L\"\nmsgstr \"Личи\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:69\nmsgid \"Updated\"\nmsgstr \"Обновлен\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:71\nmsgid \"Enabled\"\nmsgstr \"Включен\"\n\n#: applications/luci-app-rtorrent/luasrc/model/cbi/rtorrent/torrent/trackers.lua:83\nmsgid \"Add tracker\"\nmsgstr \"Добавить трекер\"\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/root/etc/config/rtorrent",
    "content": ""
  },
  {
    "path": "luci/applications/luci-app-rtorrent/root/etc/cookies.txt",
    "content": "# Netscape Cookie File\n\n# From left-to-right, the cookie data consists of the following fields:\n# domain     - The domain that created and that can read the variable.\n# tailmatch  - A TRUE/FALSE value indicating if all machines within a given domain can access the variable.\n# path       - The path within the domain that the variable is valid for. Use / for any url.\n# secure     - A TRUE/FALSE value indicating if a secure connection with the domain is needed to access the variable.\n# expiration - The UNIX time that the variable will expire on, max: 2147483647 (2038-01-19).\n# name       - The name of the variable.\n# value      - The value of the variable.\n\n# domain\ttailmatch \tpath\tsecure\t\texpiration\tname\tvalue\n.netscape.com\tTRUE\t\t/\tFALSE\t\t2147483647\tdummy\t1234\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/root/etc/init.d/rtorrent",
    "content": "#!/bin/sh /etc/rc.common\n\nSTART=99\nSTOP=99\n\nstart() {\n  screen -dmS rtorrent nice -19 rtorrent -n -o import=/etc/rtorrent.conf\n}\n\nboot() {\n  start\n}\n\nstop() {\n  killall rtorrent\n}\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/root/etc/rtorrent.conf",
    "content": "# This is an example resource file for rTorrent.\n# Enable/modify the options as needed. Remember to uncomment\n# the options you wish to enable.\n\n# Default directory to save the downloaded torrents\n#directory = /path/to/downloads/\n\n# Default session directory\n#session = /path/to/session/\n\n# rTorrent xml-rpc scgi port\n# Use the same port in luci-app-rtorrent configuration!\nscgi_port = 127.0.0.1:5000\n\n# Port range to use for listening\nport_range = 21244-21244\nport_random = no\n\n# Enable DHT support for trackerless torrents or when all trackers are down.\n# May be set to \"disable\" (completely disable DHT), \"off\" (do not start DHT),\n# \"auto\" (start and stop DHT as needed), or \"on\" (start DHT immediately).\n# The default is \"off\". For DHT to work, a session directory must be defined.\ndht = auto\ndht_port = 6881\n\n# Enable tracker requests\ntrackers.enable = 1\n\n# Set whether the client should try to connect to UDP trackers\ntrackers.use_udp.set = true\n\n# Enable peer exchange (for torrents not marked private)\nprotocol.pex.set = yes\n\n# Global upload and download rate in KiB. \"0\" for unlimited\n#throttle.global_down.max_rate.set_kb = 0\n#throttle.global_up.max_rate.set_kb = 0\n\n# Maximum number of simultaneous downloads/uploads\n#throttle.max_downloads.global.set = 0\n#throttle.max_uploads.global.set = 0\n\n# Maximum number of simultanious uploads per torrent\n#throttle.max_uploads.set = 20\n\n# Maximum and minimum number of peers to connect to per torrent\n#throttle.min_peers.normal.set = 40\n#throttle.max_peers.normal.set = 100\n\n# Same as above but for seeding completed torrents (-1 = same as downloading)\n#throttle.min_peers.seed.set = 10\n#throttle.max_peers.seed.set = 50\n\n# Check hash for finished torrents\ncheck_hash = no\n\n# Encryption options, set to none (default) or any combination of the following:\n# allow_incoming, try_outgoing, require, require_RC4, enable_retry, prefer_plaintext\nencryption = allow_incoming,try_outgoing,enable_retry\n\n# Set the umask applied to all files created by rTorrent\nsystem.umask.set = 022\n\n# Support non-ascii characters in the filenames\nencoding_list = UTF-8\n\n# Maximum number of socket connections rtorrent can accept/make\n#network.max_open_sockets.set = 300\n\n# Maximum number of open files rtorrent can keep open\n#network.max_open_files.set = 600\n\n# Maximum number of simultaneous HTTP request\n#network.http.max_open.set = 50\n\n# CURL option to lower DNS timeout\n#network.http.dns_cache_timeout.set = 25\n\n# Max packet size using xmlrpc\n#network.xmlrpc.size_limit.set = 2M\n\n# rTorrent scheduler/events\nschedule = rss_downloader,300,300,\"execute2=/usr/lib/lua/rss_downloader.lua\"\nmethod.set_key = event.download.erased,on_erase,\"branch=d.custom5=,\\\"execute2={rm,-rf,--,$d.base_path=}\\\"\"\n\n# Logging\n#log.execute = /path/to/log/rtorrent.execute.log\n#log.xmlrpc = /path/to/log/rtorrent.xmlrpc.log\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/root/etc/uci-defaults/rtorrent",
    "content": "#!/bin/sh\n/etc/init.d/rtorrent enable\n/etc/init.d/rtorrent start\nrm -fr /tmp/luci-indexcache /tmp/luci-modulecache\nexit 0\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/root/usr/lib/lua/bencode.lua",
    "content": "-- Copyright (c) 2009, 2010, 2011, 2012 by Moritz Wilhelmy\n-- Copyright (c) 2009 by Kristofer Karlsson\n-- Public domain lua-module for handling bittorrent-bencoded data.\n-- This module includes both a recursive decoder and a recursive encoder.\n\nlocal sort, concat, insert = table.sort, table.concat, table.insert\nlocal pairs, ipairs, type, tonumber = pairs, ipairs, type, tonumber\nlocal sub, find = string.sub, string.find\n\nlocal M = {}\n\n-- helpers\n\nlocal function islist(t) \n\tlocal n = #t \n\tfor k, v in pairs(t) do \n\t\tif type(k) ~= \"number\" \n\t\tor k % 1 ~= 0 \t\t-- integer?\n\t\tor k < 1\n\t\tor k > n \n\t\tthen \n\t\t\treturn false \n\t\tend \n\tend \n\tfor i = 1, n do\n\t\tif t[i] == nil then \n\t\t\treturn false \n\t\tend \n\tend \n\treturn true\nend \n\n-- encoder functions\n\nlocal encode_rec -- encode_list/dict and encode_rec are mutually recursive...\n\nlocal function encode_list(t, x)\n\n\tinsert(t, \"l\")\n\n\tfor _,v in ipairs(x) do \n\t\tlocal err,ev = encode_rec(t, v);    if err then return err,ev end\n\tend\n\n\tinsert(t, \"e\") \nend\n\nlocal function encode_dict(t, x)\n\tinsert(t, \"d\")\n\t-- bittorrent requires the keys to be sorted.\n\tlocal sortedkeys = {}\n\tfor k, v in pairs(x) do\n\t\tif type(k) ~= \"string\" then\n\t\t\treturn \"bencoding requires dictionary keys to be strings\", k\n\t\tend\n\t\tinsert(sortedkeys, k)\n\tend\n\tsort(sortedkeys)\n\n\tfor k, v in ipairs(sortedkeys) do\n\t\tlocal err,ev = encode_rec(t, v);    if err then return err,ev end\n\t\t      err,ev = encode_rec(t, x[v]); if err then return err,ev end\n\tend\n\tinsert(t, \"e\")\nend\n\nlocal function encode_int(t, x)\n\n\tif x % 1 ~= 0 then return \"number is not an integer\", x end\n\tinsert(t, \"i\" )\n\tinsert(t,  x  )\n\tinsert(t, \"e\" )\nend\n\nlocal function encode_str(t, x)\n\n\tinsert(t, #x  ) \n\tinsert(t, \":\" )\n\tinsert(t,  x  )\nend\n\nencode_rec = function(t, x)\n\n\tlocal  typx = type(x)\n\tif     typx == \"string\" then  return encode_str  (t, x)\n\telseif typx == \"number\" then  return encode_int  (t, x)\n\telseif typx == \"table\"  then\n\n\t\tif islist(x)    then  return encode_list (t, x)\n\t\telse                  return encode_dict (t, x)\n\t\tend\n\telse\n\t\treturn \"type cannot be converted to an acceptable type for bencoding\", typx\n\tend\nend\n\n-- call recursive bencoder function with empty table, stringify that table.\n-- this is the only encode* function visible to module users.\nM.encode = function (x)\n\n\tlocal t = {}\n\tlocal err, val = encode_rec(t,x)\n\tif not err then\n\t\treturn concat(t)\n\telse\n\t\treturn nil, err, val\n\tend\nend\n\n-- decoder functions\n\nlocal function decode_integer(s, index) \n\tlocal a, b, int = find(s, \"^(%-?%d+)e\", index) \n\tif not int then return nil, \"not a number\", nil end\n\tint = tonumber(int) \n\tif not int then return nil, \"not a number\", int end\n\treturn int, b + 1 \nend \n\nlocal function decode_list(s, index) \n\tlocal t = {} \n\twhile sub(s, index, index) ~= \"e\" do \n\t\tlocal obj, ev\n\t\tobj, index, ev = M.decode(s, index) \n\t\tif not obj then return obj, index, ev end\n\t\tinsert(t, obj)\n\tend \n\tindex = index + 1 \n\treturn t, index \nend \n\t \nlocal function decode_dictionary(s, index) \n\tlocal t = {} \n\twhile sub(s, index, index) ~= \"e\" do \n\t\tlocal obj1, obj2, ev\n\n\t\tobj1, index, ev = M.decode(s, index) \n\t\tif not obj1 then return obj1, index, ev end\n\n\t\tobj2, index, ev = M.decode(s, index) \n\t\tif not obj2 then return obj2, index, ev end\n\n\t\tt[obj1] = obj2 \n\tend \n\tindex = index + 1 \n\treturn t, index \nend \n\t \nlocal function decode_string(s, index) \n\tlocal a, b, len = find(s, \"^([0-9]+):\", index) \n\tif not len then return nil, \"not a length\", len end\n\tindex = b + 1 \n\t \n\tlocal v = sub(s, index, index + len - 1) \n\tif #v < tonumber(len) then return nil, \"truncated string at end of input\", v end\n\tindex = index + len \n\treturn v, index \nend \n\t \n\t \nM.decode = function (s, index) \n\tif not s then return nil, \"no data\", nil end\n\tindex = index or 1 \n\tlocal t = sub(s, index, index) \n\tif not t then return nil, \"truncation error\", nil end\n\n\tif t == \"i\" then \n\t\treturn decode_integer(s, index + 1) \n\telseif t == \"l\" then \n\t\treturn decode_list(s, index + 1) \n\telseif t == \"d\" then \n\t\treturn decode_dictionary(s, index + 1) \n\telseif t >= '0' and t <= '9' then \n\t\treturn decode_string(s, index) \n\telse \n\t\treturn nil, \"invalid type\", t\n\tend \nend\n\nreturn M\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/root/usr/lib/lua/rss_downloader.lua",
    "content": "#!/usr/bin/lua\n-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal uci = require \"luci.model.uci\".cursor()\nlocal date = require \"luci.http.protocol.date\"\nlocal bencode = require \"bencode\"\nlocal xml = require \"lxp.lom\"\nlocal xmlrpc = require \"xmlrpc\"\nlocal rtorrent = require \"rtorrent\"\nlocal nixio = require \"nixio\"\nlocal common = require \"luci.model.cbi.rtorrent.common\"\nrequire \"luci.model.cbi.rtorrent.string\"\n\nCONFIG = \"rtorrent\"\n\nfunction log(str)\n\tprint(os.date(\"%Y-%m-%d %H:%M:%S\") .. \" \" .. str)\nend\n\nfunction uci_get_all_sections(config, stype, filter)\n\tlocal sections = {}\n\tuci:foreach(config, stype, function(s)\n\t\tif not filter or filter(s) then\n\t\t\ttable.insert(sections, s)\n\t\tend\n\tend)\n\treturn sections\nend\n\nfunction get_feeds(filter)\n\treturn uci_get_all_sections(CONFIG, \"rss-feed\", filter)\nend\n\nfunction get_rules(filter)\n\treturn uci_get_all_sections(CONFIG, \"rss-rule\", filter)\nend\n\nfunction filter_enabled(s)\n\treturn s.enabled == \"1\"\nend\n\nfunction next_tag(t, tag, i)\n\tif not i then i = 1 end\n\tif t ~= nil then\n\t\twhile t[i] do\n\t\t\tif type(t[i]) == \"table\" and t[i].tag == tag then\n\t\t\t\treturn t[i], i\n\t\t\tend\n\t\t\ti = i + 1\n\t\tend\n\tend\n\treturn nil, i\nend\n\nfunction parse_feed(url)\n\tlocal ok, content = common.get(url)\n\tif ok and content ~= nil then\n\t\tlocal rss, err = xml.parse(content)\n\t\tif rss ~= nil then\n\t\t\treturn rss\n\t\telse\n\t\t\tlog(\"Failed to parse rss feed: \" .. err)\n\t\tend\n\telse\n\t\tlog(\"Failed to download rss feed: \" .. url)\n\tend\nend\n\nfunction contains(tbl, value)\n\tfor _, v in ipairs(tbl) do\n\t\tif v == value then\n\t\t\treturn true\n\t\tend\n\tend\n\treturn false\nend\n\nfunction get_torrent_size(torrent)\n\tlocal sha1_size = 20\n\tlocal t, err = bencode.decode(torrent)\n\tif t then\n\t\tlocal piece_length = tonumber(t[\"info\"][\"piece length\"])\n\t\tlocal piece_count = t[\"info\"][\"pieces\"]:len() / sha1_size\n\t\treturn piece_length * piece_count / 1024 / 1024\n\telse\n\t\tlog(\"Failed to parse torrent file: \" .. tostring(torrent))\n\t\treturn nil\n\tend\nend\n\nfunction get_torrent_link(item)\n\tlocal enclosure = next_tag(item, \"enclosure\")\n\tif enclosure == nil then\n\t\treturn next_tag(item, \"link\")[1]\n\tend\n\treturn enclosure.attr.url\nend\n\n--[[ M A I N ]]--\n\n-- TODO: fix character encoding (eg.: lua expat does not support iso8859-2)\n-- string.gsub(rss, \"[^\\128-\\193]\", \"\")\n\nlocal feed_logfile = uci:get(CONFIG, \"logging\", \"feed_logfile\")\nif feed_logfile ~= nil then\n\tfeed_log = assert(io.open(feed_logfile, \"a\"))\nend\n\nlocal rules = get_rules(filter_enabled)\nfor _, feed in ipairs(get_feeds(filter_enabled)) do\n\tlog(\"Processing \\\"\" .. feed.name .. \"\\\" rss feed\")\n\tlocal rss = parse_feed(feed.url)\n\tlocal channel = next_tag(rss, \"channel\")\n\tlocal item, i = next_tag(channel, \"item\")\n\tlocal lastupdate = tonumber(feed.lastupdate) or 0\n\tif item then\n\t\tuci:set(CONFIG, feed[\".name\"], \"lastupdate\", date.to_unix(next_tag(item, \"pubDate\")[1]))\n\t\tuci:save(CONFIG)\n\t\tuci:commit(CONFIG)\n\tend\n\twhile item do\n\t\tlocal pubdate = date.to_unix(next_tag(item, \"pubDate\")[1])\n\t\tif pubdate > lastupdate then\n\t\t\tlocal title = next_tag(item, \"title\")[1]\n\t\t\tif feed_log ~= nil then\n\t\t\t\tfeed_log:write(os.date(\"!%Y-%m-%d %H:%M:%S\", pubdate) .. \n\t\t\t\t\t\" (\" .. feed.name .. \") \" .. title .. \"\\n\")\n\t\t\tend\n\t\t\tfor _, rule in ipairs(rules) do\n\t\t\t\tif rule.feed and contains(rule.feed:split(\";\"), feed.name)\n\t\t\t\tand title:lower():find(rule.match:lower())\n\t\t\t\tand (not rule.exclude or not title:lower():find(rule.exclude:lower())) then\n\t\t\t\t\tlocal link = get_torrent_link(item)\n\t\t\t\t\tlocal ok, torrent = common.get(link)\n\t\t\t\t\tlocal size = get_torrent_size(torrent)\n\t\t\t\t\tif ok and size \n\t\t\t\t\tand (not rule.minsize or (size >= tonumber(rule.minsize)))\n\t\t\t\t\tand (not rule.maxsize or (size <= tonumber(rule.maxsize))) then\n\t\t\t\t\t\tlog(\"Matched rss rule: \" .. rule.name .. \" (\" .. link .. \")\")\n\t\t\t\t\t\tlocal params = {}\n\t\t\t\t\t\ttable.insert(params, rule.autostart == \"1\" and \"load.raw_start\" or \"load.raw\")\n\t\t\t\t\t\ttable.insert(params, \"\") -- target\n\t\t\t\t\t\ttable.insert(params, xmlrpc.newTypedValue((nixio.bin.b64encode(torrent)), \"base64\"))\n\t\t\t\t\t\ttable.insert(params, \"d.directory.set=\\\"\" .. rule.destdir .. \"\\\"\")\n\t\t\t\t\t\tif rule.tags then table.insert(params, \"d.custom1.set=\\\"\" .. rule.tags .. \"\\\"\") end\n\t\t\t\t\t\ttable.insert(params, \"d.custom3.set=\" .. nixio.bin.b64encode(link))\n\t\t\t\t\t\trtorrent.call(unpack(params))\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\t\titem, i = next_tag(channel, \"item\", i + 1)\n\tend\nend\n\nif feed_log ~= nil then\n\tfeed_log:flush()\n\tfeed_log:close()\nend\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/root/usr/lib/lua/rtorrent.lua",
    "content": "-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- Licensed to the public under the GNU General Public License.\n\nlocal ipairs, string, tostring, tonumber, table = ipairs, string, tostring, tonumber, table\nlocal assert, type, unpack = assert, type, unpack\n\nlocal nixio = require \"nixio\"\nlocal socket = require \"socket\"\nlocal xmlrpc = require \"xmlrpc\"\nlocal scgi = require \"xmlrpc.scgi\"\n\nlocal SCGI_ADDRESS = \"localhost\"\nlocal SCGI_PORT = 5000\n\nlocal HTTP_AUTH_USER = \"rtorrent\"\nlocal HTTP_AUTH_PASSWORD = \"czNz9JwdcLYcGDcVnZbQ\"\n\nmodule \"rtorrent\"\n\nfunction map(array, func)\n\tlocal new_array = {}\n\tfor i, v in ipairs(array) do\n\t\tnew_array[i] = func(v)\n\tend\n\treturn new_array\nend\n\nfunction alter(prefix, methods, postfix)\n\tmethods = map(methods, function(method)\n\t\tif method == 0 then return method end\n\t\tif prefix then method = prefix .. method end\n\t\tif postfix then method = method .. postfix end\n\t\treturn method\n\tend)\n\treturn methods\nend\n\nfunction format(method_type, res, methods)\n\tlocal formatted = {}\n\tfor _, r in ipairs(res) do\n\t\tlocal item = {}\n\t\tfor i, v in ipairs(r) do\n\t\t\titem[methods[method_type == \"d.\" and i or i + 1]:gsub(\"%.\", \"_\")] = v\n\t\tend\n\t\ttable.insert(formatted, item)\n\tend\n\treturn formatted\nend\n\nfunction call(method, ...)\n\tlocal ok, res = scgi.call(SCGI_ADDRESS, SCGI_PORT, method, ...)\n\tif not ok and res == \"socket connect failed\" then\n\t\tassert(ok, \"\\n\\nFailed to connect to rtorrent: rpc port not reachable!\\n\"\n\t\t\t.. \"Possible reasons:\\n\"\n\t\t\t.. \"- not the rpc version of rtorrent is installed\\n\"\n\t\t\t.. \"- scgi port is not defined in .rtorrent.rc (scgi_port = 127.0.0.1:5000)\\n\"\n\t\t\t.. \"- rtorrent is not running (ps | grep rtorrent)\\n\")\n\tend\n\tassert(ok, string.format(\"XML-RPC call failed on client: %s\", tostring(res)))\n\treturn res\nend\n\nfunction rpc(xml)\n\tlocal auth = \"Basic \" .. nixio.bin.b64encode(HTTP_AUTH_USER .. \":\" .. HTTP_AUTH_PASSWORD)\n\tif auth ~= nixio.getenv(\"HTTP_AUTHORIZATION\") then\n\t\treturn 'Status: 401 Unauthorized\\r\\n'\n\t\t\t.. 'WWW-Authenticate: Basic realm=\"rTorrent\"\\r\\n\\r\\n'\n\tend\n\tlocal sock = socket.connect(SCGI_ADDRESS, SCGI_PORT)\n\tif sock ~= nil then\n\t\tsock:send(scgi.netstring(xml))\n\t\tlocal err, code, headers, body = scgi.receive(sock)\n\t\tif tonumber(code) == 200 then\n\t\t\treturn 'Status: 200 OK\\r\\n'\n\t\t\t\t.. 'Content-Type: application/xml\\r\\n\\r\\n' .. body\n\t\tend\n\tend\n\treturn 'Status: 500 Internal Server Error\\r\\n\\r\\n'\nend\n\nfunction multicall(method_type, filter, ...)\n\tlocal res = (method_type == \"d.\")\n\t\tand call(method_type .. \"multicall2\", \"\", filter, unpack(alter(method_type, {...}, \"=\")))\n\t\tor call(method_type .. \"multicall\", filter, unpack(alter(method_type, {...}, \"=\")))\n\treturn format(method_type, res, {...})\nend\n\nfunction batchcall(methods, params, prefix, postfix)\n\tlocal p = type(params) == \"table\" and params or { params }\n\tlocal methods_array = {}\n\tfor _, m in ipairs(alter(prefix, methods, postfix)) do\n\t\ttable.insert(methods_array, {\n\t\t\t[\"methodName\"] = m,\n\t\t\t[\"params\"] = xmlrpc.newTypedValue(p, \"array\")\n\t\t})\n\tend\n\tlocal res = {}\n\tfor i, r in ipairs(call(\"system.multicall\", xmlrpc.newTypedValue(methods_array, \"array\"))) do\n\t\tres[methods[i]] = r[1]\n\tend\n\treturn res\nend\n\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/root/usr/lib/lua/xmlrpc/init.lua",
    "content": "-- Copyright 2003-2010 Kepler Project\n-- XML-RPC implementation for Lua.\n\nlocal lxp = require \"lxp\"\nlocal lom = require \"lxp.lom\"\n\nlocal assert, error, ipairs, pairs, select, type, tonumber, unpack = assert, error, ipairs, pairs, select, type, tonumber, unpack\nlocal format, gsub, strfind, strsub = string.format, string.gsub, string.find, string.sub\nlocal concat, tinsert = table.concat, table.insert\nlocal ceil = math.ceil\nlocal parse = lom.parse\n\nmodule (...)\n\n_COPYRIGHT = \"Copyright (C) 2003-2014 Kepler Project\"\n_DESCRIPTION = \"LuaXMLRPC is a library to make remote procedure calls using XML-RPC\"\n_PKGNAME = \"LuaXMLRPC\"\n_VERSION_MAJOR = 1\n_VERSION_MINOR = 2\n_VERSION_MICRO = 2\n_VERSION = _VERSION_MAJOR .. \".\" .. _VERSION_MINOR .. \".\" .. _VERSION_MICRO\n\n---------------------------------------------------------------------\n-- XML-RPC Parser\n---------------------------------------------------------------------\n\n---------------------------------------------------------------------\nlocal function trim (s)\n\treturn (type(s) == \"string\" and gsub (s, \"^%s*(.-)%s*$\", \"%1\"))\nend\n\n---------------------------------------------------------------------\nlocal function is_space (s)\n\treturn type(s) == \"string\" and trim(s) == \"\"\nend\n\n---------------------------------------------------------------------\n-- Get next non-space element from tab starting from index i.\n-- @param tab Table.\n-- @param i Numeric index.\n-- @return Object and its position on table; nil and an invalid index\n--\twhen there is no more elements.\n---------------------------------------------------------------------\nfunction next_nonspace (tab, i)\n\tif not i then i = 1 end\n\twhile is_space (tab[i]) do i = i+1 end\n\treturn tab[i], i\nend\n\n---------------------------------------------------------------------\n-- Get next element of tab with the given tag starting from index i.\n-- @param tab Table.\n-- @param tag String with the name of the tag.\n-- @param i Numeric index.\n-- @return Object and its position on table; nil and an invalid index\n--\twhen there is no more elements.\n---------------------------------------------------------------------\nlocal function next_tag (tab, tag, i)\n\tif not i then i = 1 end\n\twhile tab[i] do\n\t\tif type (tab[i]) == \"table\" and tab[i].tag == tag then\n\t\t\treturn tab[i], i\n\t\tend\n\t\ti = i + 1\n\tend\n\treturn nil, i\nend\n\n---------------------------------------------------------------------\nlocal function x2number (tab)\n\tif tab.tag == \"int\" or tab.tag == \"i4\" or tab.tag == \"i8\" or tab.tag == \"double\" then\n\t\treturn tonumber (next_nonspace (tab, 1), 10)\n\tend\nend\n\n---------------------------------------------------------------------\nlocal function x2boolean (tab)\n\tif tab.tag == \"boolean\" then\n\t\tlocal v = next_nonspace (tab, 1)\n\t\treturn v == true or v == \"true\" or tonumber (v) == 1 or false\n\tend\nend\n\n---------------------------------------------------------------------\nlocal function x2string (tab)\n\treturn tab.tag == \"string\" and (tab[1] or \"\")\nend\n\n---------------------------------------------------------------------\nlocal function x2date (tab)\n\treturn tab.tag == \"dateTime.iso8601\" and next_nonspace (tab, 1)\nend\n\n---------------------------------------------------------------------\nlocal function x2base64 (tab)\n\treturn tab.tag == \"base64\" and next_nonspace (tab, 1)\nend\n\n---------------------------------------------------------------------\nlocal function x2name (tab)\n\treturn tab.tag == \"name\" and next_nonspace (tab, 1)\nend\n\nlocal x2value\n\n---------------------------------------------------------------------\n-- Disassemble a member object in its name and value parts.\n-- @param tab Table with a DOM representation.\n-- @return String (name) and Object (value).\n-- @see x2name, x2value.\n---------------------------------------------------------------------\nlocal function x2member (tab)\n\treturn\n\t\tx2name (next_tag(tab,\"name\")),\n\t\tx2value (next_tag(tab,\"value\"))\nend\n\n---------------------------------------------------------------------\n-- Disassemble a struct object into a Lua table.\n-- @param tab Table with DOM representation.\n-- @return Table with \"name = value\" pairs.\n---------------------------------------------------------------------\nlocal function x2struct (tab)\n\tif tab.tag == \"struct\" then\n\t\tlocal res = {}\n\t\tfor i = 1, #tab do\n\t\t\tif not is_space (tab[i]) then\n\t\t\t\tlocal name, val = x2member (tab[i])\n\t\t\t\tres[name] = val\n\t\t\tend\n\t\tend\n\t\treturn res\n\tend\nend\n\n---------------------------------------------------------------------\n-- Disassemble an array object into a Lua table.\n-- @param tab Table with DOM representation.\n-- @return Table.\n---------------------------------------------------------------------\nlocal function x2array (tab)\n\tif tab.tag == \"array\" then\n\t\tlocal d = next_tag (tab, \"data\")\n\t\tlocal res = {}\n\t\tfor i = 1, #d do\n\t\t\tif not is_space (d[i]) then\n\t\t\t\ttinsert (res, x2value (d[i]))\n\t\t\tend\n\t\tend\n\t\treturn res\n\tend\nend\n\n---------------------------------------------------------------------\nlocal xmlrpc_types = {\n\tint = x2number,\n\ti4 = x2number,\n\ti8 = x2number,\n\tboolean = x2boolean,\n\tstring = x2string,\n\tdouble = x2number,\n\t[\"dateTime.iso8601\"] = x2date,\n\tbase64 = x2base64,\n\tstruct = x2struct,\n\tarray = x2array,\n}\n\nlocal x2param, x2fault\n\n---------------------------------------------------------------------\n-- Disassemble a methodResponse into a Lua object.\n-- @param tab Table with DOM representation.\n-- @return Boolean (indicating wether the response was successful)\n--\tand (a Lua object representing the return values OR the fault\n--\tstring and the fault code).\n---------------------------------------------------------------------\nlocal function x2methodResponse (tab)\n\tassert (type(tab) == \"table\", \"Not a table\")\n\tassert (tab.tag == \"methodResponse\",\n\t\t\"Not a `methodResponse' tag: \"..tab.tag)\n\tlocal t = next_nonspace (tab, 1)\n\tif t.tag == \"params\" then\n\t\treturn true, unpack (x2param (t))\n\telseif t.tag == \"fault\" then\n\t\tlocal f = x2fault (t)\n\t\treturn false, f.faultString, f.faultCode\n\telse\n\t\terror (\"Couldn't find a <params> nor a <fault> element\")\n\tend\nend\n\n---------------------------------------------------------------------\n-- Disassemble a value element into a Lua object.\n-- @param tab Table with DOM representation.\n-- @return Object.\n---------------------------------------------------------------------\nx2value = function (tab)\n\tlocal t = tab.tag\n\tassert (t == \"value\", \"Not a `value' tag: \"..t)\n\tlocal n = next_nonspace (tab)\n\tif type(n) == \"string\" or type(n) == \"number\" then\n\t\treturn n\n\telseif type (n) == \"table\" then\n\t\tlocal t = n.tag\n\t\tlocal get = xmlrpc_types[t]\n\t\tif not get then error (\"Invalid <\"..t..\"> element\") end\n\t\treturn get (next_nonspace (tab))\n        elseif type(n) == \"nil\" then\n                -- the next best thing is to assume it's an empty string\n               return \"\"\n\n\tend\nend\n\n---------------------------------------------------------------------\n-- Disassemble a fault element into a Lua object.\n-- @param tab Table with DOM representation.\n-- @return Object.\n---------------------------------------------------------------------\nx2fault = function (tab)\n\tassert (tab.tag == \"fault\", \"Not a `fault' tag: \"..tab.tag)\n\treturn x2value (next_nonspace (tab))\nend\n\n---------------------------------------------------------------------\n-- Disassemble a param element into a Lua object.\n-- Ignore white spaces between elements.\n-- @param tab Table with DOM representation.\n-- @return Object.\n---------------------------------------------------------------------\nx2param = function (tab)\n\tassert (tab.tag == \"params\", \"Not a `params' tag\")\n\tlocal res = {}\n\tlocal p, i = next_nonspace (tab, 1)\n\twhile p do\n\t\tif p.tag == \"param\" then\n\t\t\ttinsert (res, x2value (next_tag (p, \"value\")))\n\t\tend\n\t\tp, i = next_nonspace (tab, i+1)\n\tend\n\treturn res\nend\n\n---------------------------------------------------------------------\n-- Disassemble a methodName element into a Lua object.\n-- @param tab Table with DOM representation.\n-- @return Object.\n---------------------------------------------------------------------\nlocal function x2methodName (tab)\n\tassert (tab.tag == \"methodName\", \"Not a `methodName' tag: \"..tab.tag)\n\treturn (next_nonspace (tab, 1))\nend\n\n---------------------------------------------------------------------\n-- Disassemble a methodCall element into its name and a list of parameters.\n-- @param tab Table with DOM representation.\n-- @return Object.\n---------------------------------------------------------------------\nlocal function x2methodCall (tab)\n\tassert (tab.tag == \"methodCall\", \"Not a `methodCall' tag: \"..tab.tag)\n\treturn\n\t\tx2methodName (next_tag (tab,\"methodName\")),\n\t\tx2param (next_tag (tab,\"params\"))\nend\n\n---------------------------------------------------------------------\n-- End of XML-RPC Parser\n---------------------------------------------------------------------\n\n---------------------------------------------------------------------\n-- Convert a Lua Object into an XML-RPC string.\n---------------------------------------------------------------------\n\n---------------------------------------------------------------------\nlocal formats = {\n\tboolean = \"<boolean>%d</boolean>\",\n\tnumber = \"<double>%d</double>\",\n\tstring = \"<string>%s</string>\",\n\tbase64 = \"<base64>%s</base64>\",\n\n\tarray = \"<array><data>\\n%s\\n</data></array>\",\n\tdouble = \"<double>%s</double>\",\n\tint = \"<int>%s</int>\",\n\tstruct = \"<struct>%s</struct>\",\n\n\tmember = \"<member><name>%s</name>%s</member>\",\n\tvalue = \"<value>%s</value>\",\n\n\tparam = \"<param>%s</param>\",\n\n\tparams = [[\n    <params>\n      %s\n    </params>]],\n\n\tfault = [[\n    <fault>\n      %s\n    </fault>]],\n\n\tmethodCall = [[\n<?xml version=\"1.0\"?>\n<methodCall>\n  <methodName>%s</methodName>\n%s\n</methodCall>\n]],\n\n\tmethodResponse = [[\n<?xml version=\"1.0\"?>\n<methodResponse>\n%s\n</methodResponse>]],\n}\nformats.table = formats.struct\n\nlocal toxml = {}\ntoxml.double = function (v,t) return format (formats.double, v) end\ntoxml.int = function (v,t) return format (formats.int, v) end\ntoxml.string = function (v,t) return format (formats.string, v) end\ntoxml.base64 = function (v,t) return format (formats.base64, v) end\n\n---------------------------------------------------------------------\n-- Build a XML-RPC representation of a boolean.\n-- @param v Object.\n-- @return String.\n---------------------------------------------------------------------\nfunction toxml.boolean (v)\n\tlocal n = (v and 1) or 0\n\treturn format (formats.boolean, n)\nend\n\n---------------------------------------------------------------------\n-- Build a XML-RPC representation of a number.\n-- @param v Object.\n-- @param t Object representing the XML-RPC type of the value.\n-- @return String.\n---------------------------------------------------------------------\nfunction toxml.number (v, t)\n\tlocal tt = (type(t) == \"table\") and t[\"*type\"]\n\tif tt == \"int\" or tt == \"i4\" or tt == \"i8\" then\n\t\treturn toxml.int (v, t)\n\telseif tt == \"double\" then\n\t\treturn toxml.double (v, t)\n\telseif v == ceil(v) then\n\t\treturn toxml.int (v, t)\n\telse\n\t\treturn toxml.double (v, t)\n\tend\nend\n\n---------------------------------------------------------------------\n-- @param typ Object representing a type.\n-- @return Function that generate an XML element of the given type.\n-- The object could be a string (as usual in Lua) or a table with\n-- a field named \"type\" that should be a string with the XML-RPC\n-- type name.\n---------------------------------------------------------------------\nlocal function format_func (typ)\n\tif type (typ) == \"table\" then\n\t\treturn toxml[typ.type]\n\telse\n\t\treturn toxml[typ]\n\tend\nend\n\n---------------------------------------------------------------------\n-- @param val Object representing an array of values.\n-- @param typ Object representing the type of the value.\n-- @return String representing the equivalent XML-RPC value.\n---------------------------------------------------------------------\nfunction toxml.array (val, typ)\n\tlocal ret = {}\n\tlocal et = typ.elemtype\n\tlocal f = format_func (et)\n\tfor i,v in ipairs (val) do\n\t\tif et and et ~= \"array\" then\n\t\t\ttinsert (ret, format (formats.value, f (v, et)))\n\t\telse\n\t\t\tlocal ct,cv = type_val(v)\n\t\t\tlocal cf = format_func(ct)\n\t\t\ttinsert (ret, format (formats.value, cf(cv, ct)))\n\t\tend\n\n\tend\n\treturn format (formats.array, concat (ret, '\\n'))\nend\n\n---------------------------------------------------------------------\n---------------------------------------------------------------------\nfunction toxml.struct (val, typ)\n\tlocal ret = {}\n\tif type (typ) == \"table\" then\n\t\tfor n,t in pairs (typ.elemtype) do\n\t\t\tlocal f = format_func (t)\n\t\t\ttinsert (ret, format (formats.member, n, f (val[n], t)))\n\t\tend\n\telse\n\t\tfor i, v in pairs (val) do\n\t\t\ttinsert (ret, toxml.member (i, v))\n\t\tend\n\tend\n\treturn format (formats.struct, concat (ret))\nend\n\ntoxml.table = toxml.struct\n\n---------------------------------------------------------------------\n---------------------------------------------------------------------\nfunction toxml.member (n, v)\n\treturn format (formats.member, n, toxml.value (v))\nend\n\n---------------------------------------------------------------------\n-- Get type and value of object.\n---------------------------------------------------------------------\nfunction type_val (obj)\n\tlocal t = type (obj)\n\tlocal v = obj\n\tif t == \"table\" then\n\t\tt = obj[\"*type\"] or \"table\"\n\t\tv = obj[\"*value\"] or obj\n\tend\n\treturn t, v\nend\n\n---------------------------------------------------------------------\n-- Convert a Lua object to a XML-RPC object (plain string).\n---------------------------------------------------------------------\nfunction toxml.value (obj)\n\tlocal to, val = type_val (obj)\n\tif type(to) == \"table\" then\n\t\treturn format (formats.value, toxml[to.type] (val, to))\n\telse\n\t\t-- primitive (not structured) types.\n\t\t--return format (formats[to], val)\n\t\treturn format (formats.value, toxml[to] (val, to))\n\tend\nend\n\n---------------------------------------------------------------------\n-- @param ... List of parameters.\n-- @return String representing the `params' XML-RPC element.\n---------------------------------------------------------------------\nfunction toxml.params (...)\n\tlocal params_list = {}\n\tfor i = 1, select (\"#\", ...) do\n\t\tparams_list[i] = format (formats.param, toxml.value (select (i, ...)))\n\tend\n\treturn format (formats.params, concat (params_list, '\\n      '))\nend\n\n---------------------------------------------------------------------\n-- @param method String with method's name.\n-- @param ... List of parameters.\n-- @return String representing the `methodCall' XML-RPC element.\n---------------------------------------------------------------------\nfunction toxml.methodCall (method, ...)\n\tlocal idx = strfind (method, \"[^A-Za-z_.:/0-9]\")\n\tif idx then\n\t\terror (format (\"Invalid character `%s'\", strsub (method, idx, idx)))\n\tend\n\treturn format (formats.methodCall, method, toxml.params (...))\nend\n\n---------------------------------------------------------------------\n-- @param err String with error message.\n-- @return String representing the `fault' XML-RPC element.\n---------------------------------------------------------------------\nfunction toxml.fault (err)\n\tlocal code\n\tlocal message = err\n\tif type (err) == \"table\" then\n\t\tcode = err.code\n\t\tmessage = err.message\n\tend\n\treturn format (formats.fault, toxml.value {\n\t\tfaultCode = { [\"*type\"] = \"int\", [\"*value\"] = code or err.faultCode or 1 },\n\t\tfaultString = message or err.faultString or \"fatal error\",\n\t})\nend\n\n---------------------------------------------------------------------\n-- @param ok Boolean indicating if the response was correct or a\n--\tfault one.\n-- @param params Object containing the response contents.\n-- @return String representing the `methodResponse' XML-RPC element.\n---------------------------------------------------------------------\nfunction toxml.methodResponse (ok, params)\n\tlocal resp\n\tif ok then\n\t\tresp = toxml.params (params)\n\telse\n\t\tresp = toxml.fault (params)\n\tend\n\treturn format (formats.methodResponse, resp)\nend\n\n---------------------------------------------------------------------\n-- End of converter from Lua to XML-RPC.\n---------------------------------------------------------------------\n\n\n---------------------------------------------------------------------\n-- Create a representation of an array with the given element type.\n---------------------------------------------------------------------\nfunction newArray (elemtype)\n\treturn { type = \"array\", elemtype = elemtype, }\nend\n\n---------------------------------------------------------------------\n-- Create a representation of a structure with the given members.\n---------------------------------------------------------------------\nfunction newStruct (members)\n\treturn { type = \"struct\", elemtype = members, }\nend\n\n---------------------------------------------------------------------\n-- Create a representation of a value according to a type.\n-- @param val Any Lua value.\n-- @param typ A XML-RPC type.\n---------------------------------------------------------------------\nfunction newTypedValue (val, typ)\n\treturn { [\"*type\"] = typ, [\"*value\"] = val }\nend\n\n---------------------------------------------------------------------\n-- Create the XML-RPC string used to call a method.\n-- @param method String with method name.\n-- @param ... Parameters to the call.\n-- @return String with the XML string/document.\n---------------------------------------------------------------------\nfunction clEncode (method, ...)\n\treturn toxml.methodCall (method, ...)\nend\n\n---------------------------------------------------------------------\n-- Convert the method response document to a Lua table.\n-- @param meth_resp String with XML document.\n-- @return Boolean indicating whether the call was successful or not;\n--\tand a Lua object with the converted response element.\n---------------------------------------------------------------------\nfunction clDecode (meth_resp)\n\tlocal d = parse (meth_resp)\n\tif type(d) ~= \"table\" then\n\t\terror (\"Not an XML document: \"..meth_resp)\n\tend\n\treturn x2methodResponse (d)\nend\n\n---------------------------------------------------------------------\n-- Convert the method call (client request) document to a name and\n--\ta list of parameters.\n-- @param request String with XML document.\n-- @return String with method's name AND the table of arguments.\n---------------------------------------------------------------------\nfunction srvDecode (request)\n\tlocal d = parse (request)\n\tif type(d) ~= \"table\" then\n\t\terror (\"Not an XML document: \"..request)\n\tend\n\treturn x2methodCall (d)\nend\n\n---------------------------------------------------------------------\n-- Convert a table into an XML-RPC methodReponse element.\n-- @param obj Lua object.\n-- @param is_fault Boolean indicating wether the result should be\n--\ta `fault' element (default = false).\n-- @return String with XML-RPC response.\n---------------------------------------------------------------------\nfunction srvEncode (obj, is_fault)\n\tlocal ok = not (is_fault or false)\n\treturn toxml.methodResponse (ok, obj)\nend\n\n---------------------------------------------------------------------\n-- Register the methods.\n-- @param tab_or_func Table or mapping function.\n-- If a table is given, it can have one level of objects and then the\n-- methods;\n-- if a function is given, it will be used as the dispatcher.\n-- The given function should return a Lua function that implements.\n---------------------------------------------------------------------\ndispatch = error\nfunction srvMethods (tab_or_func)\n\tlocal t = type (tab_or_func)\n\tif t == \"function\" then\n\t\tdispatch = tab_or_func\n\telseif t == \"table\" then\n\t\tdispatch = function (name)\n\t\t\tlocal ok, _, obj, method = strfind (name, \"^([^.]+)%.(.+)$\")\n\t\t\tif not ok then\n\t\t\t\treturn tab_or_func[name]\n\t\t\telse\n\t\t\t\tif tab_or_func[obj] and tab_or_func[obj][method] then\n\t\t\t\t\treturn function (...)\n\t\t\t\t\t\treturn tab_or_func[obj][method] (obj, ...)\n\t\t\t\t\tend\n\t\t\t\telse\n\t\t\t\t\treturn nil\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\telse\n\t\terror (\"Argument is neither a table nor a function\")\n\tend\nend\n"
  },
  {
    "path": "luci/applications/luci-app-rtorrent/root/usr/lib/lua/xmlrpc/scgi.lua",
    "content": "-- Copyright 2003-2010 Kepler Project\n-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>\n-- XML-RPC over SCGI.\n\nlocal error, tonumber, tostring, unpack = error, tonumber, tostring, unpack\n\nlocal socket= require\"socket\"\nlocal string= require\"string\"\nlocal xmlrpc= require\"xmlrpc\"\n\nmodule(\"xmlrpc.scgi\")\n\n---------------------------------------------------------------------\n-- Call a remote method.\n-- @param addr String with the address of the SCGI server.\n-- @param port The port of the SCGI server.\n-- @param method String with the name of the method to be called.\n-- @return Table with the response(could be a `fault' or a `params'\n--\tXML-RPC element).\n---------------------------------------------------------------------\nfunction call(addr, port, method, ...)\n\tlocal request_body = xmlrpc.clEncode(method, ...)\n\tlocal sock = socket.connect(addr, port)\n\tif sock == nil then\n\t\treturn false, \"socket connect failed\"\n\tend\n\tsock:send(netstring(request_body))\n\tlocal err, code, headers, body = receive(sock)\n\n\tif tonumber(code) == 200 then\n\t\treturn xmlrpc.clDecode(body)\n\telse\n\t\terror(tostring(err or code))\n\tend\nend\n\n---------------------------------------------------------------------\n-- Encode message as netstring\n-- @param request_body String with the message\n-- @return String with the encoded message\n---------------------------------------------------------------------\nfunction netstring(request_body)\n\tlocal null = \"\\0\"\n\tlocal content_length = \"CONTENT_LENGTH\" .. null .. string.len(request_body) .. null\n\tlocal scgi_enable = \"SCGI\" .. null .. \"1\" .. null\n\tlocal request_method = \"REQUEST_METHOD\" .. null .. \"POST\" .. null\n\tlocal server_protocol = \"SERVER_PROTOCOL\" .. null .. \"HTTP/1.1\" .. null\n\tlocal header = content_length .. scgi_enable .. request_method .. server_protocol\n\treturn string.len(header) .. \":\" .. header .. \",\" .. request_body\nend\n\n---------------------------------------------------------------------\n-- Receive and parse socket response\n-- @param sock Socket instance\n-- @return Headers, body and error codes\n---------------------------------------------------------------------\nfunction receive(sock)\n\tlocal line, body, err\n\tlocal headers = {}\n\n\tline, err = sock:receive()\n\tif err then return err, \"500\" end\n\twhile line ~= \"\" do\n\t\tlocal name, value = socket.skip(2, string.find(line, \"^(.-):%s*(.*)\"))\n\t\tif not(name and value) then return \"malformed reponse header: \" .. line, \"500\" end\n\t\theaders[string.lower(name)] = value\n\n\t\tline, err = sock:receive()\n\t\tif err then return err, \"500\" end\n\tend\n\n\tbody = sock:receive(headers[\"content-length\"])\n\tlocal code = socket.skip(2, string.find(headers[\"status\"], \"^(%d%d%d)\"))\n\treturn err, code, headers, body\nend\n\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Web UI for smstools3\nLUCI_DEPENDS:=+smstools3 +iconv +jq\nPKG_LICENSE:=GPLv3\nPKG_VERSION:=0.1.3\nPKG_RELEASE:=4\n\ndefine Package/luci-app-smstools3/postrm\n\trm -f /etc/config/smstools3\nendef\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/README.md",
    "content": "# luci-app-smstools3\n\nWeb UI smstools3 for OpenWrt LuCI.\n\nNote: If you use this app with modemmanager, please move or remove /etc/hotplug.d/tty/25-modemmanager-tty\n\n<details>\n   <summary>Screenshots</summary>\n   \n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/applications/luci-app-smstools3/screenshots/incoming.png)\n   \n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/applications/luci-app-smstools3/screenshots/outcoming.png)\n   \n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/applications/luci-app-smstools3/screenshots/push.png)\n   \n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/applications/luci-app-smstools3/screenshots/setup.png)\n   \n</details>\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/htdocs/luci-static/resources/view/smstools3/cmd.js",
    "content": "'use strict';\n'require form';\n'require rpc';\n'require fs';\n'require view';\n'require uci';\n'require ui';\n'require tools.widgets as widgets'\n\nreturn view.extend({\n\tload: function() {\n\t\treturn uci.load('smstools3');\n\t},\n\n\trender: function(data) {\n\t\tvar m, s, o;\n\n\t\tm = new form.Map('smstools3', _('Smstools3: Commands'));\n\t\tm.description = _('Command List interface.');\n\n\t\t// Root Phone numbers\n\t\ts = m.section(form.TypedSection, 'root_phone', _('Phone'), _('Root Phone list to accept commands.'));\n\t\ts.rmempty = true;\n\t\ts.anonymous = true;\n\n\t\to = s.option(form.DynamicList, 'phone', _('Phone'), _('Phone number must be without \\\"+\\\"'));\n\t\to.rmempty = true;\n\n\t\t// Grid Section modems\n\t\ts = m.section(form.GridSection, 'command', _('Command List'));\n\t\ts.addremove = true;\n\t\ts.rmempty = true;\n\n\t\t// get modem list\n\t\tvar modemOptions = [['', _('Any modem (default)')]];\n\t\tvar sections = uci.sections('smstools3', 'modem');\n\t\to = s.option(form.ListValue, 'modem', _('Modem'));\n\n\t\tfor (var i = 0; i < sections.length; i++) {\n\t\t\tvar section = sections[i];\n\t\t\tif (section['enable'] === '1') {\n\t\t\t\tmodemOptions.push([section['.name'], '%s (%s)'.format(section['.name'], section['device'] || 'N/A')]);\n\t\t\t}\n\t\t}\n\n\t\tfor (var i = 0; i < modemOptions.length; i++) {\n\t\t\to.value(modemOptions[i][0], modemOptions[i][1]);\n\t\t}\n\t\to.default = '';\n\t\to.rmempty = true;\n\n\t\to = s.option(form.Value, 'command', _('SMS Command'));\n                o = s.option(form.Value, 'exec', _('Execute'));\n\t\to = s.option(form.Flag, 'delay_en', _('Delay'));\n\t\to = s.option(form.Value, 'delay', _('Delay in sec.'));\n\t\to.depends('delay_en', '1');\n\t\to = s.option(form.Flag, 'answer_en', _('Answer'));\n\t\to = s.option(form.Value, 'answer', _('Answer MSG'));\n\t\to.depends('answer_en', '1');\n\n\t\treturn m.render();\n\t}\n\n});\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/htdocs/luci-static/resources/view/smstools3/in.js",
    "content": "'use strict';\n'require dom';\n'require form';\n'require fs';\n'require ui';\n'require uci';\n'require view';\n\n/*\n        Copyright 2025 Konstantine Shevlakov <shevlakov@132lan.ru>\n        Licensed to the GNU General Public License v3.0.\n*/\n\nreturn view.extend({\n\n\tload: function() {\n\t\tL.resolveDefault(fs.exec_direct('/usr/share/luci-app-smstools3/led.sh', [ 'off' ]));\n\t\treturn L.resolveDefault(fs.exec_direct('/usr/bin/msg_control', [ 'recv' ]));\n\t},\n\n\thandleClear: function(ev) {\n\t\treturn L.resolveDefault(fs.exec_direct('/usr/bin/msg_control', [ 'rmrecv' ])).then(function() {\n\t\t\tlocation.reload();\n\t\t});\n\t},\n\n\thandleDelete: function(filename) {\n\t        if (confirm(_('Are you sure you want to delete this message?'))) {\n        \t    return L.resolveDefault(fs.exec_direct('/usr/bin/msg_control', [ 'delete', filename ])).then(function() {\n                \tlocation.reload();\n\t            });\n        \t}\n\t},\n\n\thandleRefresh: function(ev) {\n\t\tlocation.reload();\n\t},\n\n\trender: function (data) {\n\t\tvar obj = JSON.parse(data);\n\t\tlet tableHeaders = [\n\t\t\t_('Modem'),\n\t\t\t_('Send Date'),\n\t\t\t_('Recv.Date'),\n\t\t\t_('From'),\n\t\t\t_('Message'),\n\t\t\t_('Action')\n\t\t];\n\n\t\tlet tableSMS = E('table', { 'class': 'table' },\n\t\t\t E('tr', { 'class': 'tr table-titles' }, [\n\t\t\t\tE('th', { 'class': 'th left', 'width': '10%' }, tableHeaders[0]),\n\t\t\t\tE('th', { 'class': 'th left', 'width': '10%' }, tableHeaders[1]),\n\t\t\t\tE('th', { 'class': 'th left', 'width': '10%' }, tableHeaders[2]),\n\t\t\t\tE('th', { 'class': 'th left', 'width': '20%' }, tableHeaders[3]),\n\t\t\t\tE('th', { 'class': 'th left', 'width': '45%' }, tableHeaders[4]),\n\t\t\t\tE('th', { 'class': 'th left', 'width': '5%' }, tableHeaders[5]),\n\t\t\t]),\n\t\t);\n\n\t\tvar s = 1;\n\t\tfor (let i = 0; i < obj.recv.length; i++) {\n\t\t\tif (obj.recv[i].from.length > 6 && Number(obj.recv[i].from)) {\n\t\t\t\tvar from = '+' + obj.recv[i].from;\n\t\t\t} else {\n\t\t\t\tvar from = obj.recv[i].from;\n\t\t\t}\n\t\t\t//  get msg filename\n\t\t\tvar filename = obj.recv[i].filename || '';\n\n\t\t\ttableSMS.append(E('tr', { 'class': 'tr cbi-rowstyle-'+s }, [\n\t\t\t\t\tE('td', { 'class': 'td left', 'data-title': tableHeaders[0], 'width': '10%' }, obj.recv[i].modem),\n\t\t\t\t\tE('td', { 'class': 'td left', 'data-title': tableHeaders[1], 'width': '10%' }, obj.recv[i].srecv),\n\t\t\t\t\tE('td', { 'class': 'td left', 'data-title': tableHeaders[2], 'width': '10%' }, obj.recv[i].drecv),\n\t\t\t\t\tE('td', { 'class': 'td left', 'data-title': tableHeaders[3], 'width': '20%' }, from),\n\t\t\t\t\tE('td', { 'class': 'td left', 'data-title': tableHeaders[4], 'width': '45%' }, obj.recv[i].content),\n\t\t\t\t\tE('td', { 'class': 'td left', 'data-title': tableHeaders[5], 'width': '5%' }, \n\t\t\t\t\t\tE('button', {\n\t\t\t\t\t\t\t'class': 'cbi-button cbi-button-remove',\n\t\t\t\t\t\t\t'click': ui.createHandlerFn(this, 'handleDelete', obj.recv[i].filename),\n\t\t\t\t\t\t\t'title': _('Delete this message')\n\t\t\t\t\t\t}, [ '×' ])\n\t\t\t\t\t)\n\t\t\t\t]),\n\t\t\t);\n\t\t\ts = (s % 2) + 1;\n\t\t};\n\n\t\tvar button = (\n\t\t\tE('hr'),\n\t\t\tE('div', { 'class': 'right'  }, [\n\t\t\t\tE('button', { \n\t\t\t\t\t'class': 'cbi-button cbi-button-remove', 'id': 'clr', 'click': ui.createHandlerFn(this, 'handleClear')\n\t\t\t\t}, [ _('Remove All SMS') ]),\n\t\t\t\t'\\xa0\\xa0\\xa0',\n\t\t\t\tE('button', {\n\t\t\t\t\t'class': 'cbi-button cbi-button-save', 'id': 'clr', 'click': ui.createHandlerFn(this, 'handleRefresh')\n\t\t\t\t}, [ _('Refresh') ])\n\t\t\t])\n\t\t);\n\t\tvar result = E('fieldset', { 'class': 'cbi-section' }, [E('h2', {}, _('Smstools3: Incoming messages')), tableSMS, button]);\n\t\treturn result;\n\t},\n\thandleSaveApply: null,\t\n\thandleSave: null,\n\thandleReset:  null\n});\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/htdocs/luci-static/resources/view/smstools3/out.js",
    "content": "'use strict';\n'require dom';\n'require form';\n'require fs';\n'require ui';\n'require uci';\n'require view';\n\n/*\n        Copyright 2025 Konstantine Shevlakov <shevlakov@132lan.ru> \n        Licensed to the GNU General Public License v3.0.\n*/\n\nreturn view.extend({\n\n    load: function() {\n        return L.resolveDefault(fs.exec_direct('/usr/bin/msg_control', [ 'sent' ]));\n    },\n\n    handleClear: function(ev) {\n        return L.resolveDefault(fs.exec_direct('/usr/bin/msg_control', [ 'rmsent' ])).then(function() {\n            location.reload();\n        });\n    },\n\n    handleDelete: function(filename) {\n        if (confirm(_('Are you sure you want to delete this message?'))) {\n            return L.resolveDefault(fs.exec_direct('/usr/bin/msg_control', [ 'delete', filename ])).then(function() {\n                location.reload();\n            });\n        }\n    },\n\n    handleRefresh: function(ev) {\n        location.reload();\n    },\n\n    render: function (data) {\n        var obj = JSON.parse(data);\n        let tableHeaders = [\n            _('Modem'),\n            _('Send Date'),\n            _('Time(sec)'),\n            _('To'),\n            _('Message'),\n            _('Action')\n        ];\n\n        let tableSMS = E('table', { 'class': 'table' },\n            E('tr', { 'class': 'tr cbi-section-table-titles' }, [\n                E('th', { 'class': 'th left', 'width': '12%' }, tableHeaders[0]),\n                E('th', { 'class': 'th left', 'width': '12%' }, tableHeaders[1]),\n                E('th', { 'class': 'th left', 'width': '12%' }, tableHeaders[2]),\n                E('th', { 'class': 'th left', 'width': '18%' }, tableHeaders[3]),\n                E('th', { 'class': 'th left', 'width': '41%' }, tableHeaders[4]),\n                E('th', { 'class': 'th left', 'width': '5%' }, tableHeaders[5]),\n            ]),\n        );\n\n        var s = 1;\n        for (let i = 0; i < obj.sent.length; i++) {\n            let message = obj.sent[i];\n\n            if (!message.filename) {\n                continue;\n            }\n\n            let to = message.to;\n            if (to && to.length > 6 && Number(to)) {\n                to = '+' + to;\n            } else {\n                to = to || '';\n            }\n\n            let content = message.content || _('No content');\n\n            tableSMS.append(\n                E('tr', { 'class': 'cbi-rowstyle-'+s }, [\n                    E('td', { 'class': 'td left', 'data-title': tableHeaders[0] }, message.modem || ''),\n                    E('td', { 'class': 'td left', 'data-title': tableHeaders[1] }, message.sent || ''),\n                    E('td', { 'class': 'td left', 'data-title': tableHeaders[2] }, message.time || ''),\n                    E('td', { 'class': 'td left', 'data-title': tableHeaders[3] }, to),\n                    E('td', { 'class': 'td left', 'data-title': tableHeaders[4] }, E('div', { \n                        'style': 'max-height: 100px; overflow-y: auto; word-wrap: break-word;'\n                    }, content)),\n                    E('td', { 'class': 'td left', 'data-title': tableHeaders[5] }, \n                        E('button', {\n                            'class': 'cbi-button cbi-button-remove',\n                            'click': ui.createHandlerFn(this, 'handleDelete', message.filename),\n                            'title': _('Delete this message')\n                        }, [ '×' ])\n                    )\n                ]),\n            );\n            s = (s % 2) + 1;\n        };\n\n        var button = ( \n            E('hr'),\n                E('div', { 'class': 'right'  }, [\n                E('button', { \n                    'class': 'cbi-button cbi-button-remove', \n                    'click': ui.createHandlerFn(this, 'handleClear')\n                }, [ _('Remove All SMS') ]),\n                '\\xa0\\xa0\\xa0',\n                E('button', {\n                    'class': 'cbi-button cbi-button-save', \n                    'click': ui.createHandlerFn(this, 'handleRefresh')\n                }, [ _('Refresh') ])\n            ])\n        );\n\n        var result = E('fieldset', { 'class': 'cbi-section' }, [\n            E('h2', {}, _('Smstools3: Outgoing messages')), \n            tableSMS, \n            button\n        ]);\n        return result;\n    },\n    handleSaveApply: null,    \n    handleSave: null,\n    handleReset: null\n});\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/htdocs/luci-static/resources/view/smstools3/pb.js",
    "content": "'use strict';\n'require form';\n'require fs';\n'require view';\n'require uci';\n'require ui';\n'require tools.widgets as widgets'\n\n/*\n\tCopyright 2022-2023 Rafał Wabik - IceG - From eko.one.pl forum\n\tModified for smstools3 by Konstantine Shevlakov <shevlakov@132lan.ru> 2024\n\tLicensed to the GNU General Public License v3.0.\n*/\n\n\nvar cmddesc = _(\"Each line must have the following format: 'Description;Phone Number'. For user convenience, the file is saved to the location <code>/etc/smstools3.pb</code>.\"); \n\nreturn view.extend({\n\n\trender: function() {\n\t\tvar m, s, o;\n\t\tm = new form.Map('smstools3', _('Smstools3: Phonebook'));\n\n\t\ts = m.section(form.TypedSection, 'sms', '', _(''));\n\t\ts.anonymous = true;\n\n\t\to = s.option(form.TextValue, '_tmpl', _('Phonebook'), cmddesc);\n\t\to.rows = 20;\n\t\to.cfgvalue = function(section_id) {\n\t\t\treturn fs.trimmed('/etc/smstools3.pb');\n\t\t};\n\t\to.write = function(section_id, formvalue) {\n\t\t\treturn fs.write('/etc/smstools3.pb', formvalue.trim().replace(/\\r\\n/g, '\\n') + '\\n');\n\t\t};\n\n\t\treturn m.render();\n\t},\n\thandleSaveApply: null,\n\thandleReset: null\n});\n\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/htdocs/luci-static/resources/view/smstools3/script.js",
    "content": "'use strict';\n'require form';\n'require fs';\n'require view';\n'require uci';\n'require ui';\n'require tools.widgets as widgets'\n\n\nreturn view.extend({\n\tload: function(){\n\t\tuci.load('smstools3');\n\t},\n\n\trender: function(data) {\n\t\tvar desc_head = _('Edit smstools3 user script. Add user\\'s actions for incoming and outcoming messages.<br />Is shell script for smstools3 scenario. See \\<a href\\=\\\"http://smstools3.kekekasvi.com/index.php?p=eventhandler\\\"\\>smstools3 manual page\\</a\\> for more details.');\n\t\tvar config = uci.sections('smstools3');\n\t\tvar m, s, o;\n\n\t\tm = new form.Map('smstools3', _('Smtools3: User Script'), desc_head);\n\n\t\ts = m.section(form.TypedSection, 'sms', null);\n\t\ts.anonymous = true;\n\n\t\to = s.option(form.TextValue, '_tmpl', _('Edit User script smstools3.<br />File stored in <code>/etc/smstools3.user</code>'));\n\t\to.rows = 20;\n\t\to.cfgvalue = function(section_id) {\n\t\t\treturn fs.trimmed('/etc/smstools3.user');\n\t\t};\n\t\to.write = function(section_id, formvalue) {\n\t\t\treturn fs.write(('/etc/smstools3.user'), formvalue.trim().replace(/\\r\\n/g, '\\n') + '\\n');\n\t\t};\n\n\t\treturn m.render();\n\t},\n\thandleSaveApply: null,\t\n\thandleReset:  null\n});\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/htdocs/luci-static/resources/view/smstools3/send.js",
    "content": "'use strict';\n'require dom';\n'require form';\n'require fs';\n'require uci';\n'require ui';\n'require view';\n\n/*\n        Copyright 2022-2023 Rafal Wabik - IceG - From eko.one.pl forum\n        Modified for smstools3 by Konstantine Shevlakov <shevlakov@132lan.ru> 2025\n        Licensed to the GNU General Public License v3.0.\n*/\n\nreturn view.extend({\n        handleCommand: function(exec, args) {\n                var buttons = document.querySelectorAll('.cbi-button');\n\n                for (var i = 0; i < buttons.length; i++)\n                        buttons[i].setAttribute('disabled', 'true');\n\n                return fs.exec(exec, args).then(function(res) {\n\n                        res.stdout = res.stdout?.replace(/(?=\\n)$|\\s*|\\s*$|\\n\\n+/gm, \"\") || '';\n                        res.stderr = res.stderr?.replace(/(?=\\n)$|\\s*|\\s*$|\\n\\n+/gm, \"\") || '';\n\n                }).catch(function(err) {\n                        ui.addNotification(null, E('p', [ err ]))\n                }).finally(function() {\n                        for (var i = 0; i < buttons.length; i++)\n                        buttons[i].removeAttribute('disabled');\n\n                });\n        },\n\n        handleGo: function(ev) {\n                var smstext = document.getElementById('textvalue').value;\n                var phone = document.getElementById('phonevalue').value;\n                var modem = document.getElementById('modemvalue').value;\n\n                if ( phone.length < 2 ) {\n                        ui.addNotification(null, E('p', _('Please specify the phone number to send')), 'info');\n                        return false;\n                } else if ( phone.length < 6 && Number(phone) && !phone.startsWith('+') )  {\n                        // send short numbers\n                        ui.addNotification(null, E('p', _('Message sent')), 'info');\n                        return this.handleCommand('/usr/bin/send_sms', [ 's'+phone, smstext, modem ]);\n                } else {\n                        ui.addNotification(null, E('p', _('Message sent')), 'info');\n                        return this.handleCommand('/usr/bin/send_sms', [ phone, smstext, modem ]);\n                }\n        },\n\n        handleClear: function(ev) {\n                var ov = document.getElementById('phonevalue');\n                ov.value = '';\n                var ov = document.getElementById('textvalue');\n                ov.value = '';\n                document.getElementById('textvalue').focus();\n        },\n\n        handleCopy: function(ev) {\n                var ov = document.getElementById('phonevalue');\n                ov.value = '';\n                var x = document.getElementById('tk').value;\n                ov.value = x;\n        },\n\n        load: function() {\n                return Promise.all([\n                        L.resolveDefault(fs.read_direct('/etc/smstools3.pb'), null),\n                        uci.load('smstools3')\n                ]);\n        },\n\n        render: function (loadResults) {\n\n        var info = _('User interface for sending SMS via smsd.');\n        var execBtn = document.getElementById('execute');\n\n        // get defined modems from uci\n        var modems = [];\n        var sections = uci.sections('smstools3', 'modem');\n\n        for (var i = 0; i < sections.length; i++) {\n                var section = sections[i];\n                // check enabled modems\n                if (section['enable'] === '1') {\n                        modems.push({\n                                name: section['.name'],\n                                device: section['device'] || 'N/A'\n                        });\n                }\n        }\n\n        // if modems not defined or found\n        if (modems.length === 0) {\n                for (var i = 0; i < sections.length; i++) {\n                        var section = sections[i];\n                        modems.push({\n                                name: section['.name'],\n                                device: section['device'] || 'N/A'\n                        });\n                }\n        }\n\n                return E('div', { 'class': 'cbi-map', 'id': 'map' }, [\n                                E('h2', {}, [ _('Smstools3: send message') ]),\n                                E('div', { 'class': 'cbi-map-descr'}, info),\n                                E('hr'),\n                                E('div', { 'class': 'cbi-section' }, [\n                                        E('div', { 'class': 'cbi-section-node' }, [\n                                                E('div', { 'class': 'cbi-value' }, [\n                                                        E('label', { 'class': 'cbi-value-title' }, [ _('Modem') ]),\n                                                        E('div', { 'class': 'cbi-value-field' }, [\n                                                                E('select', { \n                                                                        'class': 'cbi-input-select',\n                                                                        'id': 'modemvalue',\n                                                                        'style': 'margin:5px 0; width:70%;'\n                                                                }, modems.map(function(modem) {\n                                                                        return E('option', { \n                                                                                'value': modem.name \n                                                                        }, [ \n                                                                                '%s (%s)'.format(modem.name, modem.device) \n                                                                        ]);\n                                                                }))\n                                                        ]) \n                                                ]),\n                                                E('div', { 'class': 'cbi-value' }, [\n                                                        E('label', { 'class': 'cbi-value-title' }, [ _('Phonebook') ]),\n                                                        E('div', { 'class': 'cbi-value-field' }, [\n                                                                E('select', { 'class':  'cbi-input-select',\n                                                                                        'id': 'tk',\n                                                                                        'style': 'margin:5px 0; width:70%;',\n                                                                                        'change': ui.createHandlerFn(this, 'handleCopy')\n                                                                                },\n                                                                        (loadResults[0] || \"\").trim().split(\"\\n\").map(function(cmd) {\n                                                                                var fields = cmd.split(/;/);\n                                                                                var name = fields[0];\n                                                                                var code = fields[1];\n                                                                        return E('option', { 'value': code }, name ) })\n                                                                )\n                                                        ]) \n                                                ]),\n                                                E('div', { 'class': 'cbi-value' }, [\n                                                        E('label', { 'class': 'cbi-value-title' }, [ _('Phone Number') ]),\n                                                        E('div', { 'class': 'cbi-value-field' }, [\n                                                        E('input', {\n                                                                'style': 'margin:5px 0; ; width:70%;',\n                                                                'type': 'text',\n                                                                'id': 'phonevalue',\n                                                                }),\n                                                        ])\n                                                ]),\n                                                E('div', { 'class': 'cbi-value' }, [\n                                                        E('label', { 'class': 'cbi-value-title' }, [ _('Text Message') ]),\n                                                        E('div', { 'class': 'cbi-value-field' }, [\n                                                        E('textarea', {\n                                                                'class': 'cbi-section',\n                                                                'style': 'margin:600px 20; ; width:70%;',\n                                                                'type': 'text',\n                                                                'rows': '6',\n                                                                'id': 'textvalue'\n                                                                }),\n                                                        ])\n                                                ]),\n\n                                        ])\n                                ]),\n                                E('hr'),\n                                E('div', { 'class': 'right' }, [\n                                        E('button', {\n                                                'class': 'cbi-button cbi-button-remove',\n                                                'id': 'clr',\n                                                'click': ui.createHandlerFn(this, 'handleClear')\n                                        }, [ _('Clear text') ]),\n                                        '\\xa0\\xa0\\xa0',\n                                        E('button', {\n                                                'class': 'cbi-button cbi-button-action important',\n                                                'id': 'execute',\n                                                'click': ui.createHandlerFn(this, 'handleGo')\n                                        }, [ _('Send message') ]),\n                                ]),\n\n                        ]);\n        },\n\n        handleSaveApply: null,\n        handleSave: null,\n        handleReset: null\n});\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/htdocs/luci-static/resources/view/smstools3/setup.js",
    "content": "'use strict';\n'require form';\n'require rpc';\n'require fs';\n'require view';\n'require uci';\n'require ui';\n'require tools.widgets as widgets'\n\nvar callSerialPort = rpc.declare({\n\tobject: 'file',\n\tmethod: 'list',\n\tparams: [ 'path' ],\n\texpect: { entries: [] },\n\tfilter: function(list, params) {\n\t\tvar rv = [];\n\t\tfor (var i = 0; i < list.length; i++)\n\t\t\tif (list[i].name.match(/^ttyACM/) ||\n\t\t\t\tlist[i].name.match(/^ttyUSB/) ||\n\t\t\t\tlist[i].name.match(/^wwan\\d+at\\d+/))\n\t\t\t\trv.push(params.path + list[i].name);\n\t\treturn rv.sort();\n\t}\n});\n\nvar callLEDs = rpc.declare({\n\tobject: 'luci',\n\tmethod: 'getLEDs',\n\texpect: { '': {} }\n});\n\n\nreturn view.extend({\n\n\tload: function() {\n\t\treturn Promise.all([\n\t\t\tcallLEDs(),\n\t\t\tL.resolveDefault(fs.list('/www' + L.resource('view/system/led-trigger')), []),\n\t\t]).then(function(data) {\n\t\t\tvar plugins = data[1];\n\t\t\tvar tasks = [];\n\n\t\t\tfor (var i = 0; i < plugins.length; i++) {\n\t\t\t\tvar m = plugins[i].name.match(/^(.+)\\.js$/);\n\n\t\t\t\tif (plugins[i].type != 'file' || m == null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\ttasks.push(L.require('view.system.led-trigger.' + m[1]).then(L.bind(function(name){\n\t\t\t\t\treturn L.resolveDefault(L.require('view.system.led-trigger.' + name)).then(function(form) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: name,\n\t\t\t\t\t\t\tform: form,\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t}, this, m[1])));\n\t\t\t}\n\n\t\t\treturn Promise.all(tasks).then(function(plugins) {\n\t\t\t\tvar value = {};\n\t\t\t\tvalue[0] = data[0];\n\t\t\t\tvalue[1] = plugins;\n\t\t\t\t\n\t\t\t\treturn value;\n\t\t\t});\n\t\t});\n\t},\n\n\trender: function(data) {\n\t\tvar m, s, o, triggers = [];\n\t\tvar leds = data[0];\n\t\tvar plugins = data[1];\n\n\t\tfor (var k in leds)\n\t\t\tfor (var i = 0; i < leds[k].triggers.length; i++)\n\t\t\t\ttriggers[i] = leds[k].triggers[i];\n\n\t\tm = new form.Map('smstools3', _('Smstools3: Setup'), _('Configure smstools3 daemon.'));\n\t\ts = m.section(form.TypedSection, 'sms', null);\n\t\t//s.tab('general', _('General'));\n\t\t//s.tab('advanced', _('Advanced'));\n\t\ts.anonymous = true;\n\n\t\to = s.option(form.Flag, 'decode_utf', _('Decode SMS'), _('Decode Incoming messages to UTF-8 codepage.'));\n\t\to.rmempty = true;\n\n\t\to = s.option(form.ListValue, 'storage', _('SMS Storage'), _('Select storage to save SMS.'));\n\t\to.value('temporary', _('Temporary'));\n\t\to.value('persistent', _('Persistent'));\n\t\to.default = 'temporary';\n\n\t\to = s.option(form.ListValue, 'loglevel', _('Loglevel'), _('Logging output.'));\n\t\to.value('1', _('Emergency'));\n\t\to.value('2', _('Alert'));\n\t\to.value('3', _('Critical'));\n\t\to.value('4', _('Error'));\n\t\to.value('5', _('Warning'));\n\t\to.value('6', _('Notice'));\n\t\to.value('7', _('Info'));\n\t\to.value('8', _('Debug'));\n\t\to.default = '5';\n\n\t\to = s.option(form.Flag, 'led_enable', _('LED'), _('LED indicate to Incoming messages.'));\n\t\to.rmempty = true;\n\n\t\to = s.option(form.ListValue, 'led', _('Select LED'));\n\t\t\t\tObject.keys(leds).sort().forEach(function(name) {\n\t\t\to.value(name);\n\t\t});\n\t\to.rmempty = true;\n\t\to.depends('led_enable', '1');\n\n\t\ts = m.section(form.GridSection, 'modem', _('Modems List'));\n\t\ts.addremove = true;\n\t\ts.rmempty = true;\n\n\t\to = s.option(form.Flag, 'ui', _('Unexepted Input'), _('Enable Unexpected input from COM port.'));\n\t\to.rmempty = true;\n\n\t\to = s.option(form.ListValue, 'device', _('Select COM port'));\n\t\to.load = function(section_id) {\n\t\t\treturn callSerialPort('/dev/').then(L.bind(function(devices) {\n\t\t\t\tthis.keylist = []; \n\t\t\t\tthis.vallist = [];\n\t\t\t\tfor (var i = 0; i < devices.length; i++)\n\t\t\t\t\tthis.value(devices[i]);\n\t\t\t\treturn form.Value.prototype.load.apply(this, [section_id]);\n\t\t\t}, this));\n\t\t};\n\n\t\to = s.option(form.ListValue, 'init', _('Init string'), _('Initialise modem for more vendors'));\n\t\to.value('huawei', _('Huawei'));\n\t\to.value('intel', _('Intel XMM'));\n\t\to.value('asr', _('ASR or more'));\n\t\to.value('', _('Qualcomm or more'));\n\t\to.default = '';\n\t\to.rmempty = true;\n\n\t\to = s.option(form.Value, 'pin', _('PIN Code'), _('Default value: not in use.<br />Specifies the PIN number of the SIM card inside the modem.'));\n\t\to.datatype = 'and(rangelength,(4,8),uinteger)';\n\t\to.rmempty = true;\n\n\t\to = s.option(form.ListValue, 'net_check', _('Check network'), _('Setup network checking. Some modems incorrect test network.'));\n\t\to.value('0', _('Ignore'));\n\t\to.value('1', _('Always'));\n\t\to.value('2', _('Before messages'));\n\n\t\to = s.option(form.Flag, 'sig_check', _('Ignore signal level'), _('Some devices do not support Bit Error Rate'));\n\t\to.rmempty = true;\n\n                o = s.option(form.Flag, 'enable', _('Enable'));\n                o.modalonly = false;\n                o.editable = true;\n\n\t\treturn m.render();\n\t}\n});\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/po/ru/smstools3.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Language: ru\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Last-Translator: Konstantine Shevlyakov <shevlakov@132lan.ru>\\n\"\n\nmsgid \"Incoming\"\nmsgstr \"Входящие\"\n\nmsgid \"Outcoming\"\nmsgstr \"Исходящие\"\n\nmsgid \"Push\"\nmsgstr \"Отправить\"\n\nmsgid \"Phonebook\"\nmsgstr \"Тел.Книга\"\n\nmsgid \"Commands\"\nmsgstr \"Команды\"\n\nmsgid \"Setup\"\nmsgstr \"Настройка\"\n\nmsgid \"User Script\"\nmsgstr \"Сценарий\"\n\nmsgid \"Send Date\"\nmsgstr \"Отправлено\"\n\nmsgid \"Recv.Date\"\nmsgstr \"Получено\"\n\nmsgid \"From\"\nmsgstr \"Отправитель\"\n\nmsgid \"Smstools3: Incoming messages\"\nmsgstr \"Smstools3: Входящие сообщения\"\n\nmsgid \"Remove All SMS\"\nmsgstr \"Удалить все СМС\"\n\nmsgid \"Refresh\"\nmsgstr \"Обновить\"\n\nmsgid \"Time(sec)\"\nmsgstr \"Время(сек)\"\n\nmsgid \"To\"\nmsgstr \"Получатель\"\n\nmsgid \"Message\"\nmsgstr \"Сообщение\"\n\nmsgid \"Smstools3: Outgoing messages\"\nmsgstr \"Smstools3: Исходящие сообщения\"\n\nmsgid \"Smstools3: Commands\"\nmsgstr \"Smstools3: Команды\"\n\nmsgid \"Command List interface.\"\nmsgstr \"Команды управления по СМС.\"\n\nmsgid \"Phone\"\nmsgstr \"Ном.телеф.\"\n\nmsgid \"Root Phone list to accept commands.\"\nmsgstr \"Список телефонов для управления.\"\n\nmsgid \"Phone number must be without \\\"+\\\"\"\nmsgstr \"Телефный номер без \\\"+\\\"\"\n\nmsgid \"Command List\"\nmsgstr \"Список команд\"\n\nmsgid \"SMS Command\"\nmsgstr \"Команда СМС\"\n\nmsgid \"Execute\"\nmsgstr \"Выполнить\"\n\nmsgid \"Delay\"\nmsgstr \"Задержка\"\n\nmsgid \"Delay in sec.\"\nmsgstr \"В секундах\"\n\nmsgid \"Answer\"\nmsgstr \"Ответ\"\n\nmsgid \"Answer MSG\"\nmsgstr \"Сообщ. в ответ\"\n\nmsgid \"Smstools3: Phonebook\"\nmsgstr \"Smstools3: Телефонная книга\"\n\nmsgid \"Phonebook\"\nmsgstr \"Тел.книга\"\n\nmsgid \"Each line must have the following format: 'Description;Phone Number'. For user convenience, the file is saved to the location <code>/etc/smstools3.pb</code>.\"\nmsgstr \"Каждая строчка должна иметь формат: 'Описание;Телефонный номер'. Файл телефонной книги находиться в <code>/etc/smstools3.pb</code>.\"\n\nmsgid \"Smtools3: User Script\"\nmsgstr \"Smtools3: сценарий пользователя\"\n\nmsgid \"Edit smstools3 user script. Add user\\'s actions for incoming and outcoming messages.<br />Is shell script for smstools3 scenario. See \\<a href\\=\\\"http://smstools3.kekekasvi.com/index.php?p=eventhandler\\\"\\>smstools3 manual page\\</a\\> for more details.\"\nmsgstr \"Отредактируйте пользовательский сценарий smstools3. Добавляйте действия для входящих и исходящих сообщений.<br />Это сценарий оболочки для smstools3. Более подробную информацию смотрите \\<a href\\=\\\"http://smstools3.kekekasvi.com/index.php?p=eventhandler\\\"\\>на странице руководства smstools3\\</a\\>.\"\n\nmsgid \"Edit User script smstools3.<br />File stored in <code>/etc/smstools3.user</code>\"\nmsgstr \"Сценарий smstools3.<br />Файл находиться в <code>/etc/smstools3.user</code>\"\n\nmsgid \"User interface for sending SMS via smsd. If use multiplie modems, every next sending sms outgoing round robin.\"\nmsgstr \"Отправить СМС через smsd. При использовании нескольких модемов, каждое последующее сообщение будет отправлено через следущий модем.\"\n\nmsgid \"Smstools3: send message\"\nmsgstr \"Smstools3: отправить сообщение\"\n\nmsgid \"Phone Number\"\nmsgstr \"Номер телефона\"\n\nmsgid \"Text Message\"\nmsgstr \"Текстовое сообщение\"\n\nmsgid \"Please specify the phone number to send\"\nmsgstr \"Пожадуйста введите номер телефона получателя\"\n\nmsgid \"Message sent\"\nmsgstr \"Сообщение отправлено\"\n\nmsgid \"Clear text\"\nmsgstr \"Очистить\"\n\nmsgid \"Send message\"\nmsgstr \"Отправить СМС\"\n\nmsgid \"Smstools3: Setup\"\nmsgstr \"Smstools3: Настройка\"\n\nmsgid \"Configure smstools3 daemon.\"\nmsgstr \"Конфигурация smstools3.\"\n\nmsgid \"General\"\nmsgstr \"Основные\"\n\nmsgid \"Advanced\"\nmsgstr \"Дополнительные\"\n\nmsgid \"Decode SMS\"\nmsgstr \"Декодировать СМС\"\n\nmsgid \"Decode Incoming messages to UTF-8 codepage.\"\nmsgstr \"Декодировать входящие сообщения в UTF-8.\"\n\nmsgid \"Unexepted Input\"\nmsgstr \"Нештатные команды\"\n\nmsgid \"Enable Unexpected input from COM port.\"\nmsgstr \"Принимать команды, не предназначенные для службы на COM-порт.\"\n\nmsgid \"SMS Storage\"\nmsgstr \"Хранилице СМС\"\n\nmsgid \"Select storage to save SMS.\"\nmsgstr \"Выберите тип хранилища СМС\"\n\nmsgid \"Temporary\"\nmsgstr \"Временное\"\n\nmsgid \"Persistent\"\nmsgstr \"Постоянное\"\n\nmsgid \"Select COM port\"\nmsgstr \"COM-порт\"\n\nmsgid \"Init string\"\nmsgstr \"Иниациалиция\"\n\nmsgid \"Initialise modem for more vendors\"\nmsgstr \"Инициализировать модем для некоторых производителей\"\n\nmsgid \"ASR or more\"\nmsgstr \"ARS и подобные\"\n\nmsgid \"Qualcomm or more\"\nmsgstr \"Qualcomm и подобные\"\n\nmsgid \"PIN Code\"\nmsgstr \"ПИН-код\"\n\nmsgid \"Default value: not in use.<br />Specifies the PIN number of the SIM card inside the modem.\"\nmsgstr \"По-умалчанию не используется.<br />ПИН-код симкарты модема.\"\n\nmsgid \"Loglevel\"\nmsgstr \"Журнал\"\n\nmsgid \"Logging output.\"\nmsgstr \"Уровень журнала.\"\n\nmsgid \"Emergency\"\nmsgstr \"Чрезвычайный\"\n\nmsgid \"Alert\"\nmsgstr \"Тревожный\"\n\nmsgid \"Critical\"\nmsgstr \"Критический\"\n\nmsgid \"Error\"\nmsgstr \"Ошибка\"\n\nmsgid \"Warning\"\nmsgstr \"Предупреждение\"\n\nmsgid \"Notice\"\nmsgstr \"Уведомление\"\n\nmsgid \"Info\"\nmsgstr \"Информирование\"\n\nmsgid \"Debug\"\nmsgstr \"Отладка\"\n\nmsgid \"Check network\"\nmsgstr \"Проверять сеть\"\n\nmsgid \"Ignore\"\nmsgstr \"Никогда\"\n\nmsgid \"Always\"\nmsgstr \"Всегда\"\n\nmsgid \"Before messages\"\nmsgstr \"После сообщений\"\n\nmsgid \"Setup network checking. Some modems incorrect test network.\"\nmsgstr \"Задать проверку сети. Некоторые модемы неверно определяют наличие сети.\"\n\nmsgid \"Ignore signal level\"\nmsgstr \"Игнорировать уровень сигнала\"\n\nmsgid \"Some devices do not support Bit Error Rate\"\nmsgstr \"Некоторые модемы не поддерживают Bit Error Rate\"\n\nmsgid \"LED indicate to Incoming messages.\"\nmsgstr \"Индикация входящих сообщений.\"\n\nmsgid \"Select LED\"\nmsgstr \"Выберите LED\"\n\nmsgid \"Any modem (default)\"\nmsgstr \"Любой доступный\"\n\nmsgid \"Are you sure you want to delete this message?\"\nmsgstr \"Вы уверены, что хотите удалить это сообщение?\"\n\nmsgid \"Action\"\nmsgstr \"Действие\"\n\nmsgid \"Delete this message\"\nmsgstr \"Удалить это сообщение\"\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/po/zh_Hans/smstools3.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Language: zh_Hans\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Last-Translator: Y0518 <yangqi@easybj.cn>\\n\"\n\nmsgid \"Incoming\"\nmsgstr \"收件箱\"\n\nmsgid \"Outcoming\"\nmsgstr \"发件箱\"\n\nmsgid \"Push\"\nmsgstr \"发送\"\n\nmsgid \"Phonebook\"\nmsgstr \"电话簿\"\n\nmsgid \"Commands\"\nmsgstr \"命令\"\n\nmsgid \"Setup\"\nmsgstr \"设置\"\n\nmsgid \"User Script\"\nmsgstr \"用户脚本\"\n\nmsgid \"Send Date\"\nmsgstr \"发送日期\"\n\nmsgid \"Recv.Date\"\nmsgstr \"接收日期\"\n\nmsgid \"From\"\nmsgstr \"发件人\"\n\nmsgid \"Smstools3: Incoming messages\"\nmsgstr \"Smstools3: 接收的消息\"\n\nmsgid \"Remove SMS\"\nmsgstr \"删除短信\"\n\nmsgid \"Refresh\"\nmsgstr \"刷新\"\n\nmsgid \"Time(sec)\"\nmsgstr \"时间(秒)\"\n\nmsgid \"To\"\nmsgstr \"收件人\"\n\nmsgid \"Message\"\nmsgstr \"消息\"\n\nmsgid \"Smstools3: Outgoing messages\"\nmsgstr \"Smstools3: 发送的消息\"\n\nmsgid \"Smstools3: Commands\"\nmsgstr \"Smstools3: 命令\"\n\nmsgid \"Command List interface.\"\nmsgstr \"通过短信命令的控制界面\"\n\nmsgid \"Phone\"\nmsgstr \"电话号码\"\n\nmsgid \"Root Phone list to accept commands.\"\nmsgstr \"接受命令的根电话号码列表\"\n\nmsgid \"Phone number must be without \\\"+\\\"\"\nmsgstr \"电话号码必须不包含 \\\"+\\\"\"\n\nmsgid \"Command List\"\nmsgstr \"命令列表\"\n\nmsgid \"SMS Command\"\nmsgstr \"短信命令\"\n\nmsgid \"Execute\"\nmsgstr \"执行\"\n\nmsgid \"Delay\"\nmsgstr \"延迟\"\n\nmsgid \"Delay in sec.\"\nmsgstr \"延迟时间（秒）\"\n\nmsgid \"Answer\"\nmsgstr \"回复\"\n\nmsgid \"Answer MSG\"\nmsgstr \"回复消息\"\n\nmsgid \"Smstools3: Phonebook\"\nmsgstr \"Smstools3: 电话簿\"\n\nmsgid \"Phonebook\"\nmsgstr \"电话簿\"\n\nmsgid \"Each line must have the following format: 'Description;Phone Number'. For user convenience, the file is saved to the location <code>/etc/smstools3.pb</code>.\"\nmsgstr \"每行应按照以下格式：'描述;电话号码'。为了方便用户，该文件将保存在<code>/etc/smstools3.pb</code>位置。\"\n\nmsgid \"Smtools3: User Script\"\nmsgstr \"Smtools3: 用户脚本\"\n\nmsgid \"Edit smstools3 user script. Add user's actions for incoming and outcoming messages.<br />Is shell script for smstools3 scenario. See <a href=\\\"http://smstools3.kekekasvi.com/index.php?p=eventhandler\\\">smstools3 manual page</a> for more details.\"\nmsgstr \"编辑smstools3的用户脚本。为接收到和发送的消息添加用户的处理动作。<br />这是用于smstools3场景的Shell脚本。欲了解更多信息，请参阅<a href=\\\"http://smstools3.kekekasvi.com/index.php?p=eventhandler\\\">smstools3手册页面</a>。\"\n\nmsgid \"Edit User script smstools3.<br />File stored in <code>/etc/smstools3.user</code>\"\nmsgstr \"编辑Smstools3用户脚本。<br />文件存储在 <code>/etc/smstools3.user</code>\"\n\nmsgid \"User interface for sending SMS via smsd.\"\nmsgstr \"通过smsd发送短信的用户界面。\"\n\nmsgid \"Smstools3: send message\"\nmsgstr \"Smstools3：发送消息\"\n\nmsgid \"Phone Number\"\nmsgstr \"电话号码\"\n\nmsgid \"Text Message\"\nmsgstr \"文本消息\"\n\nmsgid \"Please specify the phone number to send\"\nmsgstr \"请输入接收短信的电话号码\"\n\nmsgid \"Message sent\"\nmsgstr \"消息已发送\"\n\nmsgid \"Clear text\"\nmsgstr \"清除文本\"\n\nmsgid \"Send message\"\nmsgstr \"发送短信\"\n\nmsgid \"Smstools3: Setup\"\nmsgstr \"Smstools3：设置\"\n\nmsgid \"Configure smstools3 daemon.\"\nmsgstr \"配置smstools3守护进程。\"\n\nmsgid \"General\"\nmsgstr \"常规\"\n\nmsgid \"Advanced\"\nmsgstr \"高级\"\n\nmsgid \"Decode SMS\"\nmsgstr \"解码短信\"\n\nmsgid \"Decode Incoming messages to UTF-8 codepage.\"\nmsgstr \"将接收到的消息解码为UTF-8编码。\"\n\nmsgid \"Unexepted Input\"\nmsgstr \"未预期的输入\"\n\nmsgid \"Enable Unexpected input from COM port.\"\nmsgstr \"允许COM端口接收非预设服务的命令。\"\n\nmsgid \"SMS Storage\"\nmsgstr \"短信存储\"\n\nmsgid \"Select storage to save SMS.\"\nmsgstr \"选择用于保存短信的存储方式。\"\n\nmsgid \"Temporary\"\nmsgstr \"临时\"\n\nmsgid \"Persistent\"\nmsgstr \"持久化\"\n\nmsgid \"Select COM port\"\nmsgstr \"选择COM端口\"\n\nmsgid \"Init string\"\nmsgstr \"初始化串行指令\"\n\nmsgid \"Initialise modem for more vendors\"\nmsgstr \"针对更多供应商初始化调制解调器\"\n\nmsgid \"ASR or more\"\nmsgstr \"ASR或其他类似设备\"\n\nmsgid \"Qualcomm or more\"\nmsgstr \"Qualcomm或其他类似设备\"\n\nmsgid \"PIN Code\"\nmsgstr \"PIN码\"\n\nmsgid \"Default value: not in use.<br />Specifies the PIN number of the SIM card inside the modem.\"\nmsgstr \"默认值：未使用。<br />指定内置在调制解调器中的SIM卡的PIN码。\"\n\nmsgid \"Loglevel\"\nmsgstr \"日志级别\"\n\nmsgid \"Logging output.\"\nmsgstr \"记录输出等级。\"\n\nmsgid \"Emergency\"\nmsgstr \"紧急情况\"\n\nmsgid \"Alert\"\nmsgstr \"警报\"\n\nmsgid \"Critical\"\nmsgstr \"严重\"\n\nmsgid \"Error\"\nmsgstr \"错误\"\n\nmsgid \"Warning\"\nmsgstr \"警告\"\n\nmsgid \"Notice\"\nmsgstr \"通知\"\n\nmsgid \"Info\"\nmsgstr \"信息\"\n\nmsgid \"Debug\"\nmsgstr \"调试\"\n\nmsgid \"Check network\"\nmsgstr \"检查网络\"\n\nmsgid \"Ignore\"\nmsgstr \"忽略\"\n\nmsgid \"Always\"\nmsgstr \"始终\"\n\nmsgid \"Before messages\"\nmsgstr \"在消息之前\"\n\nmsgid \"Setup network checking. Some modems incorrect test network.\"\nmsgstr \"设置网络检测。某些调制解调器可能无法正确检测网络状况。\"\n\nmsgid \"Ignore signal level\"\nmsgstr \"忽略信号强度\"\n\nmsgid \"Some devices do not support Bit Error Rate\"\nmsgstr \"某些设备不支持误码率（Bit Error Rate）\"\n\nmsgid \"LED indicate to Incoming messages.\"\nmsgstr \"使用LED指示灯显示接收到的短信。\"\n\nmsgid \"Select LED\"\nmsgstr \"选择LED指示灯\"\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/root/etc/init.d/luci-sms",
    "content": "#!/bin/sh /etc/rc.common\n\nSTART=99\nUSE_PROCD=1\n\nstart_service(){\n\tsleep 10 && \\\n\t        /usr/share/luci-app-smstools3/event.sh > /tmp/smsd.conf && \\\n\t        rm -f /etc/smsd.conf && ln -s /tmp/smsd.conf /etc && \\\n\t        /etc/init.d/smstools3 stop && \\\n        \tsleep 3 && \\\n\t        /etc/init.d/smstools3 start && \\\n        \t/etc/init.d/led restart &\n}\n\nreload_service(){\n\tstart\n}\n\nservice_triggers() {\n\tprocd_add_reload_trigger smstools3\n}\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/root/etc/uci-defaults/63_luci-app-smstools3",
    "content": "#!/bin/sh\n\nif [ ! -f /etc/config/smstools3 ]; then\n        touch /etc/config/smstools3\n\ttouch /etc/smstools3.user\n\ttouch /etc/smstools3.pb\n        uci add smstools3 sms\n\tuci set smstools3.@sms[-1].decode_utf='1'\n\tuci add smstools3 root_phone\n\tuci commit smstools3\nfi\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/root/usr/bin/msg_control",
    "content": "#!/bin/sh\n\nIFS=$'\\n'\n\nfunction send_msg(){\n\tlocal filename=$(basename \"$2\")\n\tmodem=$(echo \"$1\" | awk '/Modem:/{print $2}')\n\tto=$(echo \"$1\" | awk '/To:/{print $2}')\n\tsent=$(echo \"$1\" | awk '/Sent:/{gsub(\"Sent: \",\"\");print $0}')\n\ttime=$(echo \"$1\" | awk '/Sending_time:/{print $2}')\n\tmsg=$(echo \"$1\" |sed -e '1,/^$/ d' -e 's/[][\"]/\\\\&/g')\n\n\t# jq processing\n\tmodem_json=$(jq -Rn --arg str \"$modem\" '$str')\n\tmsg_json=$(jq -Rn --arg str \"$msg\" '$str')\n\n\tstring=\"{ \\\"filename\\\": \\\"$filename\\\", \\\"modem\\\": $modem_json, \\\"to\\\": \\\"$to\\\", \\\"sent\\\": \\\"$sent\\\", \\\"time\\\": \\\"$time\\\", \\\"content\\\": $msg_json },\"\n}\n\n\nfunction recv_msg(){\n\tlocal filename=$(basename \"$2\")\n\tlocal modem from srecv drecv msg\n\tmodem=$(echo \"$1\" | awk '/Modem:/{print $2}')\n\tfrom=$(echo \"$1\" | awk '/From:/{print $2}')\n\tsrecv=$(echo \"$1\" | awk '/Sent:/{gsub(\"Sent: \",\"\");print $0}')\n\tdrecv=$(echo \"$1\" | awk '/Received:/{gsub(\"Received: \",\"\");print $0}')\n\tmsg=$(echo \"$1\" |sed -e '1,/^$/ d' -e 's/[][\"]/\\\\&/g' -e 's/[\\r\\n\\^M]//g' | awk '{printf \"%s<br />\",$0} END {print \"\"}')\n\n\t# jq processing\n\tmodem_json=$(jq -Rn --arg str \"$modem\" '$str')\n\tfrom_json=$(jq -Rn --arg str \"$from\" '$str')\n\tsrecv_json=$(jq -Rn --arg str \"$srecv\" '$str')\n\tdrecv_json=$(jq -Rn --arg str \"$drecv\" '$str')\n\tmsg_json=$(jq -Rn --arg str \"$msg\" '$str')\n\n\tstring=\"{ \\\"filename\\\": \\\"$filename\\\", \\\"modem\\\": $modem_json, \\\"from\\\": $from_json, \\\"srecv\\\": $srecv_json, \\\"drecv\\\": $drecv_json, \\\"content\\\": $msg_json },\"\n}\n\n\ncase $1 in\n\tsent)\n\t\tMSG=$(find /var/spool/sms/sent/ -type f | sort -r)\n\t\techo \"{ \\\"sent\\\": [\"\n\t\tfor m in $MSG; do\n\t\t\tf=$(cat $m)\n\t\t\tsend_msg \"$f\" \"$m\"\n\t\t\tjson=\"$json $string\"\n\t\tdone\n\t\techo -e $json | sed 's/.$//'\n\t\techo \" ]}\"\n\t;;\n\trecv)\n\t\tMSG=$(find /var/spool/sms/incoming/ -type f | sort -r)\n\t\techo \"{ \\\"recv\\\": [\"\n\t\tfor m in $MSG; do\n\t\t\tf=$(cat $m)\n\t\t\trecv_msg \"$f\" \"$m\"\n\t\t\tjson=\"$json $string\"\n\t\tdone\n\t\t\techo -e $json | sed 's/.$//'\n\t\techo \" ]}\"\n\t;;\n\trmsent) rm -rf /var/spool/sms/sent/* ;;\n\trmrecv) rm -rf /var/spool/sms/incoming/* ;;\n\tdelete)\n\t\tif [ -n \"$2\" ]; then\n\t\t\tif [ -f \"/var/spool/sms/incoming/$2\" ]; then\n\t\t\t\trm -f \"/var/spool/sms/incoming/$2\"\n\t\t\telif [ -f \"/var/spool/sms/sent/$2\" ]; then\n\t\t\t\trm -f \"/var/spool/sms/sent/$2\"\n\t\t\tfi\n\t\tfi\n\t;;\nesac\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/root/usr/bin/send_sms",
    "content": "#!/bin/ash\n# This script send a text sms at the command line by creating\n# a sms file in the outgoing queue.\n\n# $1 is the destination phone number.\n# $2 is the message text.\n# $3 is the modem name (optional) - from smsd.conf section names\n# If you leave $2 or both empty, the script will ask you.\n# If you give more than 2 arguments, last is taken as a text and\n# all other are taken as destination numbers.\n# If a destination is asked, you can type multiple numbers\n# delimited with spaces.\n\n# Keys for example: \"password\" and \"keke\":\n# KEYS=\"5f4dcc3b5aa765d61d8327deb882cf99 4a5ea11b030ec1cfbc8b9947fdf2c872 \"\n\nKEYS=\"\"\n\n# When creating keys, remember to use -n for echo:\n# echo -n \"key\" | md5sum\n\nsmsd_group=\"smsd\"\nSMSD_CONF=\"/etc/smsd.conf\"\n\n# Will need echo which accepts -n argument:\nECHO=echo\ncase `uname` in\n\tSunOS) ECHO=/usr/ucb/echo ;;\nesac\n\n# get lst modems from smsd.conf\nget_modems_from_conf() {\n\tif [ -f \"$SMSD_CONF\" ]; then\n\t\tgrep -E '^\\[.*\\]$' \"$SMSD_CONF\" | sed 's/\\[\\(.*\\)\\]/\\1/' | grep -v '^global$' | tr '\\n' ' '\n\telse\n\t\techo \"\"\n\tfi\n}\n\nmodem_exists() {\n\tlocal modem=$1\n\tif [ -f \"$SMSD_CONF\" ]; then\n\t\tgrep -q \"^\\[$modem\\]$\" \"$SMSD_CONF\"\n\t\treturn $?\n\telse\n\t\treturn 1\n\tfi\n}\n\nget_first_modem() {\n\tlocal modems=$(get_modems_from_conf)\n\tif [ -n \"$modems\" ]; then\n\t\techo \"$modems\" | awk '{print $1}'\n\telse\n\t\techo \"\"\n\tfi\n}\n\ncheck_modem_availability() {\n\tlocal modem=$1\n\tif ps | grep -v grep | grep -q smsd; then\n\t\techo \"Using modem: $modem\" >&2\n\t\treturn 0\n\telse\n\t\tlocal device=$(grep -A 10 \"^\\[$modem\\]\" \"$SMSD_CONF\" 2>/dev/null | grep \"device\\s*=\" | head -1 | awk -F= '{print $2}' | tr -d ' ')\n\t\tif [ -n \"$device\" ] && [ -e \"$device\" ]; then\n\t\t\treturn 0\n\t\telse\n\t\t\treturn 1\n\t\tfi\n\tfi\n}\n\nif ! [ -z \"$KEYS\" ]; then\n\tprintf \"Key: \"\n\tread KEY\n\tif [ -z \"$KEY\" ]; then\n\t\techo \"Key required, stopping.\"\n\t\texit 1\n\tfi\n\tKEY=`$ECHO -n \"$KEY\" | md5sum | awk '{print $1;}'`\n\tif ! echo \"$KEYS\" | grep \"$KEY\" >/dev/null; then\n\t\techo \"Incorrect key, stopping.\"\n\t\texit 1\n\tfi\nfi\n\nDEST=$1\nTEXT=$2\nMODEM_NAME=$3\n\ncase $# in\n\t0)\n\t\tprintf \"Destination(s): \"\n\t\tread DEST\n\t\tif [ -z \"$DEST\" ]; then\n\t\t\techo \"No destination, stopping.\"\n\t\t\texit 1\n\t\tfi\n\t\tprintf \"Text: \"\n\t\tread TEXT\n\t\tif [ -z \"$TEXT\" ]; then\n\t\t\techo \"No text, stopping.\"\n\t\t\texit 1\n\t\tfi\n\t;;\n\t1)\n\t\tprintf \"Text: \"\n\t\tread TEXT\n\t\tif [ -z \"$TEXT\" ]; then\n\t\t\techo \"No text, stopping.\"\n\t\t\texit 1\n\t\tfi\n\t;;\n\t2) destinations=$DEST ;;\n\t3)\n\t\tdestinations=$DEST\n\t\tTEXT=$2\n\t\tMODEM_NAME=$3\n\t;;\n\t*)\n\t\tn=$#\n\t\twhile [ $n -gt 1 ]; do\n\t\t\tif [ $n -eq 2 ]; then\n\t\t\t\tif modem_exists \"$1\"; then\n\t\t\t\t\tMODEM_NAME=$1\n\t\t\t\telse\n\t\t\t\t\tif [ -z \"$TEXT\" ]; then\n\t\t\t\t\t\tTEXT=$1\n\t\t\t\t\telse\n\t\t\t\t\t\tdestinations=\"$destinations $1\"\n\t\t\t\t\tfi\n\t\t\t\tfi\n\t\t\telse\n\t\t\t\tdestinations=\"$destinations $1\"\n\t\t\tfi\n\t\t\tshift\n\t\t\tn=`expr $n - 1`\n\t\tdone\n\t\tif [ -z \"$TEXT\" ]; then\n\t\t\tTEXT=$1\n\t\tfi\n\t;;\nesac\n\nif [ -z \"$destinations\" ] && [ -n \"$DEST\" ]; then\n\tdestinations=$DEST\nfi\n\nAUTO_SELECTED=false\nif [ -z \"$MODEM_NAME\" ]; then\n\tMODEM_NAME=$(get_first_modem)\n\tif [ -n \"$MODEM_NAME\" ]; then\n\t\tAUTO_SELECTED=true\n\t\techo \"No modem specified, auto-selected: $MODEM_NAME\"\n\telse\n\t\techo \"Error: No modems found in configuration and no modem specified.\"\n\t\texit 1\n\tfi\nelse\n\tif ! modem_exists \"$MODEM_NAME\"; then\n\t\techo \"Warning: Modem '$MODEM_NAME' not found in config.\"\n\t\tMODEM_NAME=$(get_first_modem)\n\t\tif [ -n \"$MODEM_NAME\" ]; then\n\t\t\tAUTO_SELECTED=true\n\t\t\techo \"Auto-selected available modem: $MODEM_NAME\"\n\t\telse\n\t\t\techo \"Error: No modems available in configuration.\"\n\t\t\texit 1\n\t\tfi\n\tfi\nfi\n\nif ! check_modem_availability \"$MODEM_NAME\"; then\n\techo \"Warning: Modem '$MODEM_NAME' might not be available.\"\n\tALL_MODEMS=$(get_modems_from_conf)\n\tfor alt_modem in $ALL_MODEMS; do\n\t\tif [ \"$alt_modem\" != \"$MODEM_NAME\" ] && check_modem_availability \"$alt_modem\"; then\n\t\t\techo \"Switching to alternative modem: $alt_modem\"\n\t\t\tMODEM_NAME=$alt_modem\n\t\t\tAUTO_SELECTED=true\n\t\t\tbreak\n\t\tfi\n\tdone\nfi\n\necho \"-- \"\necho \"Text: $TEXT\"\necho \"Modem: $MODEM_NAME\"\n[ \"$AUTO_SELECTED\" = \"true\" ] && echo \"Note: Modem was auto-selected\"\n\nALPHABET=\"\"\nif which iconv > /dev/null 2>&1; then\n\tif ! $ECHO -n \"$TEXT\" | iconv -t ISO-8859-15 >/dev/null 2>&1; then\n\t\tALPHABET=\"Alphabet: UCS\"\n\tfi\nfi\n\ngroup=\"\"\nif [ -f /etc/group ]; then\n\tif grep $smsd_group: /etc/group >/dev/null; then\n\t\tgroup=$smsd_group\n\tfi\nfi\n\nfor destination in $destinations; do\n\techo \"To: $destination\"\n\n\tTMPFILE=`mktemp /tmp/smsd_XXXXXX`\n\n\t$ECHO \"To: $destination\" >> $TMPFILE\n\t[ -n \"$ALPHABET\" ] && $ECHO \"$ALPHABET\" >> $TMPFILE\n\t[ -n \"$MODEM_NAME\" ] && $ECHO \"Modem: $MODEM_NAME\" >> $TMPFILE\n\t$ECHO \"\" >> $TMPFILE\n\tif [ -z \"$ALPHABET\" ]; then\n\t\t$ECHO -n \"$TEXT\" >> $TMPFILE\n\telse\n\t\t$ECHO -n \"$TEXT\" | iconv -t UNICODEBIG >> $TMPFILE\n\tfi\n\n\tif [ \"x$group\" != x ]; then\n\t\tchgrp $group $TMPFILE\n\tfi\n\n\tchmod 0660 $TMPFILE\n\n\tFILE=`mktemp /var/spool/sms/outgoing/send_XXXXXX`\n\tmv $TMPFILE $FILE\n\techo \"Message queued: $FILE\"\ndone\n\necho \"SMS will be sent via modem: $MODEM_NAME\"\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/root/usr/share/luci/menu.d/luci-app-smstools3.json",
    "content": "{\n\t\"admin/modem\": {\n\t\t\"title\": \"Modem\",\n\t\t\"order\": 45,\n\t\t\"action\": {\n\t\t\t\"type\": \"firstchild\",\n\t\t\t\"recurse\": true\n\t\t}\n\t},\n\n\t\"admin/modem/sms\": {\n\t\t\"title\": \"Smstools3 SMS\",\n\t\t\"order\": 21,\n\t\t\"action\": {\n\t\t\t\"type\": \"alias\",\n\t\t\t\"path\": \"admin/modem/sms/in\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"uci\": { \"smstools3\": true},\n\t\t\t\"acl\": [ \"luci-app-smstools3\" ]\n\t\t}\n\t},\n\n\n\t\"admin/modem/sms/in\": {\n\t\t\"title\": \"Incoming\",\n\t\t\"order\": 22,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"smstools3/in\"\n\t\t}\n\t},\n\n\t\"admin/modem/sms/out\": {\n\t\t\"title\": \"Outcoming\",\n\t\t\"order\": 23,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"smstools3/out\"\n\t\t}\n\t},\n\n\t\"admin/modem/sms/send\": {\n\t\t\"title\": \"Push\",\n\t\t\"order\": 24,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"smstools3/send\"\n\t\t}\n\t},\n\n\t\"admin/modem/sms/pb\": {\n\t\t\"title\": \"Phonebook\",\n\t\t\"order\": 25,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"smstools3/pb\"\n\t\t}\n\t},\n\n\t\"admin/modem/sms/command\": {\n\t\t\"title\": \"Commands\",\n\t\t\"order\": 26,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"smstools3/cmd\"\n\t\t}\n\t},\n\n\t\"admin/modem/sms/setup\": {\n\t\t\"title\": \"Setup\",\n\t\t\"order\": 27,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"smstools3/setup\"\n\t\t}\n\t},\n\n\t\"admin/modem/sms/script\": {\n\t\t\"title\": \"User Script\",\n\t\t\"order\": 28,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"smstools3/script\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/root/usr/share/luci-app-smstools3/event.sh",
    "content": "#!/bin/sh\n\nkillall smsd\n\n. /lib/functions.sh\n\n# Modems in system config\nMODEMS=\"\"\nconfig_load smstools3\n\nget_modem_names() {\n\tlocal modem_name=\"$1\"\n\tconfig_get ENABLE \"$modem_name\" enable\n\t[ \"$ENABLE\" = \"1\" ] && MODEMS=\"${MODEMS}${MODEMS:+, }$modem_name\"\n}\n\nconfig_foreach get_modem_names modem\n\n# General settings using config_get\nDECODE=$(uci -q get smstools3.@sms[0].decode_utf)\nSTORAGE=$(uci -q get smstools3.@sms[0].storage)\nLOG=$(uci -q get smstools3.@sms[0].loglevel)\nLED_EN=$(uci -q get smstools3.@sms[0].led_enable)\n\n# Set default loglevel if not set\n[ -z \"$LOG\" ] && LOG=\"5\"\n\n\nif [ ! -d /root/sms ]; then\n\tmkdir -p /root/sms\n\tfor d in checked failed incoming outgoing sent; do\n\t\tmkdir -p /root/sms/${d}\n\tdone\nfi\n\ncase \"$STORAGE\" in\n\tpersistent)\n\t\tif [ -d /var/spool/sms ]; then\n\t\t\tmv /var/spool/sms /var/spool/sms_tmp\n\t\t\tln -sf /root/sms /var/spool/sms\n\t\tfi\n\t;;\n\ttemporary)\n\t\tif [ -d /var/spool/sms_tmp ]; then\n\t\t\trm -f /var/spool/sms\n\t\t\tmv /var/spool/sms_tmp /var/spool/sms\n\t\tfi\n\t;;\nesac\n\n# template config\necho \"devices = $MODEMS\"\necho \"incoming = /var/spool/sms/incoming\"\necho \"outgoing = /var/spool/sms/outgoing\"\necho \"checked = /var/spool/sms/checked\"\necho \"failed = /var/spool/sms/failed\"\necho \"sent = /var/spool/sms/sent\"\necho \"receive_before_send = no\"\necho \"date_filename = 1\"\necho \"date_filename_format = %s\"\necho \"eventhandler = /usr/share/luci-app-smstools3/led.sh\"\n\n[ -n \"$DECODE\" ] && {\n\techo \"decode_unicode_text = yes\"\n\techo \"incoming_utf8 = yes\"\n}\n\necho \"receive_before_send = no\"\necho \"autosplit = 4\"\n\n[ -n \"$LOG\" ] && echo \"loglevel = $LOG\"\n\n# Process each modem using config_foreach\nprocess_modem() {\n\tlocal modem_name=\"$1\"\n\tlocal UI DEVICE PIN INIT_ NET_CHECK SIG_CHECK ENABLE\n\n\tconfig_get UI \"$modem_name\" ui\n\tconfig_get DEVICE \"$modem_name\" device\n\tconfig_get PIN \"$modem_name\" pin\n\tconfig_get INIT_ \"$modem_name\" init\n\tconfig_get NET_CHECK \"$modem_name\" net_check\n\tconfig_get SIG_CHECK \"$modem_name\" sig_check\n\tconfig_get ENABLE \"$modem_name\" enable\n\n\t[ \"$ENABLE\" = \"1\" ] || return 0\n\n\techo \"\"\n\techo \"[${modem_name}]\"\n\n\tcase \"$INIT_\" in\n\t\thuawei) echo \"init = AT+CPMS=\\\"SM\\\";+CNMI=2,0,0,2,1\" ;;\n\t\tintel) echo \"init = AT+CPMS=\\\"SM\\\"\" ;;\n\t\tasr) echo \"init = AT+CPMS=\\\"SM\\\",\\\"SM\\\",\\\"SM\\\"\" ;;\n\t\t*) echo \"init = AT+CPMS=\\\"ME\\\",\\\"ME\\\",\\\"ME\\\"\" ;;\n\tesac\n\n\techo \"device = $DEVICE\"\n\n\tcase \"$SIG_CHECK\" in\n\t\t1) echo \"signal_quality_ber_ignore = yes\" ;;\n\tesac\n\n\tcase \"$NET_CHECK\" in\n\t\t0) echo \"check_network = 0\" ;;\n\t\t1) echo \"check_network = 1\" ;;\n\t\t2) echo \"check_network = 2\" ;;\n\tesac\n\n\t[ -z \"$UI\" ] && echo \"detect_unexpected_input = no\"\n\n\techo \"incoming = yes\"\n\n\t# PIN validation\n\t[ -n \"$PIN\" ] && {\n\t\tcase \"${PIN#}\" in\n\t\t\t*[!0-9]*)\n\t\t\t\tlogger -t luci-app-smstools3 \"invalid pin for modem $modem_name\"\n\t\t\t;;\n\t\t\t*[0-9]*)\n\t\t\t\t[ ${#PIN} -lt 4 -a ${#PIN} -gt 8 ] && {\n\t\t\t\t\techo \"pin = $PIN\"\n\t\t\t\t} || {\n\t\t\t\t\tlogger -t luci-app-smstools3 \"invalid pin length for modem $modem_name\"\n\t\t\t\t}\n\t\t\t;;\n\t\tesac\n\t}\n\n\techo \"baudrate = 115200\"\n}\n\n# Process all modems\nconfig_foreach process_modem modem\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/root/usr/share/luci-app-smstools3/led.sh",
    "content": "#!/bin/sh\n\nLED_EN=$(uci -q get smstools3.@sms[0].led_enable)\nLED=$(uci -q get smstools3.@sms[0].led)\n\ncase $1 in\n\toff) \n\t\tif [ $LED_EN ]; then\n\t\t\techo none > /sys/class/leds/${LED}/trigger \n\t\t\t/etc/init.d/led restart\n\t\tfi\n\t;;\n\tRECEIVED) \n\t\tif [ $LED_EN ]; then\n\t\t\techo timer > /sys/class/leds/${LED}/trigger \n\t\tfi\n\t;;\nesac\n\nif [ -r /usr/share/luci-app-smstools3/smscommand.sh ]; then\n\t. /usr/share/luci-app-smstools3/smscommand.sh\nfi\n\nif [ -r /etc/smstools3.user ]; then\n\t. /etc/smstools3.user\nfi\n\nNUM=$(ls -1 /var/spool/sms/incoming/ | wc -l)\nBODY=$(echo $2 | awk -F [\\/] '{print $NF}')\n\nif [ $NUM -ge 100 ] && [ $NUM -lt 1000 ]; then\n\tNUM=0$NUM\nelif [ $NUM -ge 10 ] && [ $NUM -lt 100 ]; then\n        NUM=00$NUM\nelif [ $NUM -ge 0 ] && [ $NUM -lt 10 ]; then\n        NUM=000$NUM\nfi\n\n\ncase $1 in\n        RECEIVED) \n\t\tmv $2 /var/spool/sms/incoming/${NUM}_${BODY}\n\t\trm -f /var/spool/sms/incoming/*concat*\n\t;;\n\tSENT)\n\t\tif sed -e '/^$/ q' < \"$2\" | grep \"^Alphabet: UCS\" > /dev/null; then\n\t\t\tTMPFILE=`mktemp /tmp/smsd_XXXXXX`\n\t\t\tsed -e '/^$/ q' < \"$2\" | sed -e 's/Alphabet: UCS/Alphabet: UTF-8/g' > $TMPFILE\n\t\t\tsed -e '1,/^$/ d' < $2 | iconv -f UNICODEBIG -t UTF-8 >> $TMPFILE\n\t\t\tmv -f $TMPFILE $2\n\t\tfi\n\t;;\nesac\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/root/usr/share/luci-app-smstools3/smscommand.sh",
    "content": "#!/bin/sh\n\n# luci-app-smstools3 command handler by koshev-msk 2025\n\n#SECTIONS=$(uci show smstools3 | awk -F [\\.][\\]\\[\\@=] '/=command/{print $3}')\nSECTIONS=$(uci show smstools3 | awk -F [\\.,=] '/=command/{print $2}')\nPHONE=$(uci -q get smstools3.@root_phone[0].phone)\n\n# Send SMS\nsend_sms() {\n    local phone=\"$1\"\n    local message=\"$2\"\n    local modem=\"$3\"\n\n    if [ -n \"$modem\" ] && [ \"$modem\" != \"\" ]; then\n        # if modem selected\n        /usr/bin/send_sms \"$phone\" \"$message\" \"$modem\"\n    else\n        # if not\n        /usr/bin/send_sms \"$phone\" \"$message\"\n    fi\n}\n\n# smscommand function\nsmscmd(){\n    local message_modem=\"$1\"\n\n    for s in $SECTIONS; do\n        CMD=\"$(uci -q get smstools3.${s}.command)\"\n        MSG=\"$(echo $content)\"\n        COMMAND_MODEM=$(uci -q get smstools3.${s}.modem)\n\n        # check recieved \n        case $MSG in\n            *${CMD}*)\n                # check modem\n                if [ -n \"$COMMAND_MODEM\" ] && [ \"$COMMAND_MODEM\" != \"\" ]; then\n                    if [ \"$message_modem\" != \"$COMMAND_MODEM\" ]; then\n                        # if not from modem message\n                        continue\n                    fi\n                fi\n                # run commmand\n                ANSWER=$(uci -q get smstools3.${s}.answer)\n                if [ \"$ANSWER\" ]; then\n                    send_sms \"$PHONE\" \"$ANSWER\" \"$COMMAND_MODEM\"\n                fi\n                EXEC=$(uci -q get smstools3.${s}.exec)\n                DELAY=$(uci -q get smstools3.${s}.delay)\n                if [ $DELAY ]; then\n                    sleep $DELAY && $EXEC &\n                else\n                    $EXEC\n                fi\n            ;;\n        esac\n    done\n}\n\n# parse incoming message\nif [ \"$1\" == \"RECEIVED\" ]; then\n    from=`grep \"From:\" $2 | awk -F ': ' '{printf $2}'`\n    content=$(sed -e '1,/^$/ d' < \"$2\")\n    message_modem=`grep \"Modem:\" $2 | awk -F ': ' '{printf $2}'`\n    # check ROOT messages\n    for n in ${PHONE}; do\n        if [ \"$from\" -eq \"$n\" ]; then\n            PHONE=$n\n            smscmd \"$message_modem\"\n        fi\n    done\nfi\n"
  },
  {
    "path": "luci/applications/luci-app-smstools3/root/usr/share/luci-app-smstools3/smstools3.user",
    "content": "#!/bin/sh\n\n# Simple resend incoming messages to telegram\n\nROUTER=$(uci -q get system.@system[0].hostname)\nchat_id=$(uci -q get telegrambot.config.chat_id)\ntoken=$(uci -q get telegrambot.config.bot_token)\n\n# parse incoming message\nif [ \"$1\" == \"RECEIVED\" ]; then\n\tfrom=`grep \"From:\" $2 | awk -F ': ' '{printf $2}'`\n\tcontent=$(sed -e '1,/^$/ d' < \"$2\")\t\n\ttext=$(cat <<EOF\nHostname: $ROUTER\n$content\nFrom $from\nEOF\n)\n\t# resent incoming messages to telegram\n\tif [ x$chat_id != x ] && [ x$token != x ]; then\n\t\t/usr/bin/curl -d \"chat_id=$chat_id&text=$text\" -X POST https://api.telegram.org/bot\"$token\"/sendMessage\n\tfi\nfi"
  },
  {
    "path": "luci/applications/luci-app-smstools3/root/usr/share/rpcd/acl.d/luci-app-smstools3.json",
    "content": "{\n\t\"luci-app-smstools3\": {\n\t\t\"description\": \"Grant UCI access for luci-app-smstools3\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/usr/bin/send_sms\": [ \"exec\" ],\n\t\t\t\t\"/usr/bin/msg_control\": [ \"exec\" ],\n\t\t\t\t\"/etc/init.d/luci-sms\": [ \"exec\" ],\n\t\t\t\t\"/usr/share/luci-app-smstools3/led.sh\": [ \"exec\" ],\n\t\t\t\t\"/etc/smstools3.user\": [ \"read\" ],\n\t\t\t\t\"/etc/smstools3.pb\": [ \"read\" ]\n\t\t\t},\n\t\t\t\"cgi-io\": [ \"exec\" ],   \n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"exec\" ],\n\t\t\t\t\"uci\": [ \"changes\", \"get\" ]\n\t\t\t},\t\t\n\t\t\t\"uci\": [ \"smstools3\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/smstools3.user\": [ \"write\" ],\n\t\t\t\t\"/etc/smstools3.pb\": [ \"write\" ]\n\t\t\t},\n\t\t\t\"cgi-io\": [ \"exec\" ],   \n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"exec\" ],\n\t\t\t\t\"uci\": [ \"add\", \"apply\", \"confirm\", \"delete\", \"order\", \"rename\", \"set\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"smstools3\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-ssw/Makefile",
    "content": "#\n# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Luci app for ssw\nLUCI_DEPENDS:= +luci-app-modeminfo\n\nPKG_LICENSE:=GPLv3\nPKG_VERSION:=0.0.1\nPKG_RELEASE:=1\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-ssw/htdocs/luci-static/resources/view/modem/ssw.js",
    "content": "'use strict';\n'require form';\n'require fs';\n'require ui';\n'require uci';\n'require view';\n'require poll';\n'require dom';\n\nreturn view.extend({\n\n\trender: function(modems) {\n\t\tvar m, s, o\n\n\t\tm = new form.Map('ssw', _('SSW - SIM Card switch'))\n\n\t\ts = m.section(form.TypedSection, 'modem');\n\t\to = s.option(form.ListValue, 'value', _('State'));\n\t\to.value(1, _('Enable'));\n\t\to.value(0, _('Disable'));\n\n\t\ts = m.section(form.TypedSection, 'sim');\n\t\to = s.option(form.ListValue, 'value', _('Default SIM Slot'));\n\t\to.value(1, _('SLOT 1'));\n\t\to.value(0, _('SLOT 2'));\n\n\t\ts = m.section(form.TypedSection, 'failover');\n\t\to = s.option(form.Flag, 'enable', _('Enable'));\n\n\t\to = s.option(form.Value, 'apn1', _('APN Default SIM'));\n\t\to.depends({enable: '1'});\n\n\t\to = s.option(form.Value, 'apn2', _('APN Reserved SIM'));\n\t\to.depends({enable: '1'});\n\n\t\to = s.option(form.Flag, 'revert', _('Revert'),\n\t\t\t_('Revert to default sim slot. Each failed attempt doubles revert time.'));\n\t\to.depends({enable: '1'});\n\n\t\to = s.option(form.ListValue, 'rsrp', _('RSRP value'),\n\t\t\t_('Switch sim lower by value.'));\n\t\tfor (var rsrp = -120; rsrp <= -80; rsrp++) {\n\t\t\to.value(rsrp, rsrp +' '+ _('dBm'));\n\t\t};\n\t\to.depends({enable: '1'});\n\n\t\to = s.option(form.Value, 'interval',\n\t\t\t_('Interval check. sec'));\n\t\to.value('5', 5 +' '+ _('sec'));\n\t\tfor (var sec = 10; sec <= 60; sec+=10) {\n\t\t\to.value(sec,sec +' '+  _('sec'));\n\t\t};\n\t\to.value('2m', 2 +' '+ _('minute'));\n\t\to.value('5m', 5 +' '+ _('minute'));\n\t\tfor (var d = 10; d <= 60; d+=10) {\n\t\t\to.value(d+'m',d +' '+  _('minute'));\n\t\t};\n\t\to.value('2h', 2 +' '+ _('hour'));\n\t\to.value('4h', 4 +' '+ _('hour'));\n\t\t\n\t\to.depends({enable: '1'});\n\n\t\to = s.option(form.Value, 'times_rsrp', _('Probes'),\n\t\t\t_('RSRP check average values.<br />NOTE: all time check is <code>Interval*Probes</code><br />Example: <code>Interval=60</code> sec, <code>Probes=5</code> times, all time check <code>300</code> sec.'));\n\t\tfor (var p = 5; p <=10; p++) {\n\t\t\to.value(p,p);\n\t\t};\n\t\to.depends({enable: '1'});\n\n\t\treturn m.render();\n\t}\n});\n"
  },
  {
    "path": "luci/applications/luci-app-ssw/po/ru/ssw.po",
    "content": "msgid \"SSW - SIM Card switch\"\nmsgstr \"SSW - управление SIM картами\"\n\nmsgid \"Enable\"\nmsgstr \"Включить\"\n\nmsgid \"Disable\"\nmsgstr \"Отключить\"\n\nmsgid \"Default SIM Slot\"\nmsgstr \"SIM cлот по-умолчанию\"\n\nmsgid \"APN Default SIM\"\nmsgstr \"APN SIM по-умолчанию\"\n\nmsgid \"APN Reserved SIM\"\nmsgstr \"APN резервной SIM\"\n\nmsgid \"Revert\"\nmsgstr \"Вернуть\"\n\nmsgid \"Revert to default sim slot. Each failed attempt doubles revert time.\"\nmsgstr \"Вернуть на SIM слот по-умолчанию. Каждая неудачная попытка увеличивает время возврата.\"\n\nmsgid \"RSRP value\"\nmsgstr \"Значение RSRP\"\n\nmsgid \"Switch sim lower by value.\"\nmsgstr \"Переключить sim ниже значения.\"\n\nmsgid \"Interval check. sec\"\nmsgstr \"Интервал проверки. сек\"\n\nmsgid \"Probes\"\nmsgstr \"Попытки\"\n\nmsgid \"RSRP check average values.<br />NOTE: all time check is <code>Interval*Probes</code><br />Example: <code>Interval=60</code> sec, <code>Probes=5</code> times, all time check <code>300</code> sec.\"\nmsgstr \"Проверяется усреднённое значение RSRP.<br />ВНИМАНИЕ: общее время итоговой проверки равно <code>Интервал*Попытки</code><br />Например: <code>интервал=60</code> sec, <code>попыток=5</code>, время до переключения <code>300</code> сек.\"\n"
  },
  {
    "path": "luci/applications/luci-app-ssw/root/usr/share/luci/menu.d/luci-app-ssw.json",
    "content": "{\n\t\"admin/modem/main\": {\n\t\t\"title\": \"Modeminfo\",\n\t\t\"order\": 10,\n\t\t\"action\": {\n\t\t\t\"type\": \"alias\",\n\t\t\t\"path\": \"admin/modem/main/main\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"acl\": [ \"luci-app-ssw\" ],\n\t\t\t\"uci\": { \"system\": true }\n\t\t}\n\t},\n\t\n\t\"admin/modem/main/ssw\": {\n\t\t\"title\": \"SIM\",\n\t\t\"order\": 73,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"modem/ssw\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-ssw/root/usr/share/rpcd/acl.d/luci-app-ssw.json",
    "content": "{\n\t\"luci-app-ssw\": {\n\t\t\"description\": \"Grant access to ssw configuration\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/init.d/ssw\": [ \"exec\" ]\n\t\t\t},\n\t\t\t\"cgi-io\": [ \"exec\" ], \n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"exec\" ],\n\t\t\t\t\"uci\": [ \"changes\", \"get\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"ssw\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"cgi-io\": [ \"exec\" ],\n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"exec\" ],\n\t\t\t\t\"uci\": [ \"add\", \"apply\", \"confirm\", \"delete\", \"order\", \"rename\", \"set\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"ssw\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-telegrambot/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=TelegramBot simple webUI\nLUCI_DEPENDS:=+telegrambot\nPKG_LICENSE:=GPLv3\nPKG_VERSION:=0.1.0\nPKG_RELEASE:=3\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-telegrambot/README.md",
    "content": "# luci-app-telegrambot\n"
  },
  {
    "path": "luci/applications/luci-app-telegrambot/htdocs/luci-static/resources/view/telegrambot.js",
    "content": "'use strict';\n'require form';\n'require fs';\n'require view';\n'require uci';\n'require ui';\n'require tools.widgets as widgets'\n\n\nreturn view.extend({\n\trender: function(data) {\n\t\tvar m, s, o\n\n\t\tm = new form.Map('telegrambot', _('TelegramBot'), _('Telegram bot for router with firmware Lede/Openwrt.'));\n\t\ts = m.section(form.TypedSection, 'telegram_bot', null);\n\t\ts.anonymous = true;\n\n\t\to = s.option(form.Flag, 'enabled', _('Enable'), _('Enable Bot'));\n\t\to.rmempty = true;\n\n\t\to = s.option(form.Value, 'bot_token', _('Bot Token'), _('Token ID your Telegram Bot'));\n\t\to.password = true;\n\n\t\to = s.option(form.Value, 'chat_id', _('Chat ID'), _('Chat ID your Telegram'));\n\n\t\to = s.option(form.Value, 'timeout', _('Delay'), _('Time Out respone Bot in sec.'));\n\n\t\to = s.option(form.Value, 'polling_time', _('Polling Time'), _('Polling Time in sec.'));\n\n\t\to = s.option(form.Value, 'plugins', _('Plugins'), _('Path to plugins directory.'));\n\t\to.default = ('/usr/lib/telegrambot/plugins');\n\n\t\to = s.option(form.Value, 'log_file', _('Log File'), _('Log File'));\n\t\to.default = ('/tmp/telegrambot.log');\n\n\t\treturn m.render();\n\t}\n});\n"
  },
  {
    "path": "luci/applications/luci-app-telegrambot/po/ru/telegrambot.po",
    "content": "\"Language: ru\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Last-Translator: Konstantine Shevlyakov <shevlakov@132lan.ru>\\n\"\n\nmsgid \"TelegramBot\"\nmsgstr \"ТелеграмБот\"\n\nmsgid \"Telegram bot for router with firmware Lede/Openwrt.\"\nmsgstr \"Телеграм бот для ротеров на прошивке Lede/Openwrt.\"\n\nmsgid \"Enable\"\nmsgstr \"Включить\"\n\nmsgid \"Enable Bot\"\nmsgstr \"Включить бота\"\n\nmsgid \"Bot Token\"\nmsgstr \"Токен бота\"\n\nmsgid \"Token ID your Telegram Bot\"\nmsgstr \"Токен ID Вашего бота в Telegram\"\n\nmsgid \"Chat ID\"\nmsgstr \"ID чата\"\n\nmsgid \"Chat ID your Telegram\"\nmsgstr \"ID чата Вашего Telegram\"\n\nmsgid \"Delay\"\nmsgstr \"Задержка\"\n\nmsgid \"Time Out respone Bot in sec.\"\nmsgstr \"Задержка ответа бота в сек.\"\n\nmsgid \"Polling Time\"\nmsgstr \"Время опроса\"\n\nmsgid \"Polling Time in sec.\"\nmsgstr \"Врема опроса в сек.\"\n\nmsgid \"Plugins\"\nmsgstr \"Плагины\"\n\nmsgid \"Path to plugins directory.\"\nmsgstr \"Путь к директории с плагинами.\"\n\nmsgid \"Log File\"\nmsgstr \"Файл лога\"\n\n\n"
  },
  {
    "path": "luci/applications/luci-app-telegrambot/root/usr/share/luci/menu.d/luci-app-telegrambot.json",
    "content": "{\n\t\"admin/services/telegrambot\": {\n\t\t\"title\": \"TelegramBot\",\n\t\t\"order\": 63,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"telegrambot\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"acl\": [ \"luci-app-telegrambot\" ],\n\t\t\t\"uci\": { \"telegrambot\": true }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-telegrambot/root/usr/share/rpcd/acl.d/luci-app-telegrambot.json",
    "content": "{\n\t\"luci-app-telegrambot\": {\n\t\t\"description\": \"Grant UCI access for luci-app-telegrambot\",\n\t\t\"read\": {\n\t\t\t\"uci\": [ \"telegrambot\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"uci\": [ \"telegrambot\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-ttl/Makefile",
    "content": "#\n# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Antitethering module for luci-app-firewall\n#LUCI_DEPENDS:= +luci-app-firewall\n\nPKG_LICENSE:=Apache-2.0\nPKG_VERSION:=0.0.5\nPKG_RELEASE:=3\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/applications/luci-app-ttl/htdocs/luci-static/resources/view/firewall/ttl.js",
    "content": "'use strict';\n'require view';\n'require ui';\n'require rpc';\n'require uci';\n'require form';\n'require fs';\n'require network';\n'require firewall as fwmodel';\n'require tools.firewall as fwtool';\n'require tools.widgets as widgets';\n\nvar briefInfo = _('In Method proxy Proxy server must be configured in transparent mode.<br />Default on lan ipaddress interface and port 3128 tcp.<br />Disable masquerade recommended.');\n\nreturn view.extend({\n\n\trender: function() {\n\t\tvar m, s, o;\n\n\t\tm = new form.Map('ttl', _('Antitethering Config'),\n\t\t\tbriefInfo);\n\n\t\ts = m.section(form.TypedSection, 'ttl');\n\t\ts.anonymous = true;\n\n\t\ts = m.section(form.TypedSection, 'ttl', _('TTL or Proxy'));\n\t\ts.anonymous = true;\n\t\ts.addremove = true;\n\n\t\to = s.option(widgets.NetworkSelect, 'iface', _('Set interface'));\n\t\to.exclude = s.section;\n\t\to.nocreate = true;\n\t\to.optional = true;\n\n\t\to = s.option(form.ListValue, 'method', _('Method'),\n\t\t\t_('TTL method outgoing interface<br />Proxy method incoming interface'));\n\t\to.value('ttl', 'TTL');\n\t\to.value('proxy', 'Proxy');\n\n\t\to = s.option(form.Flag, 'advanced', _('Advanced Option'));\n\t\to.default = '0';\n\t\to.rmempty = false;\n\n\t\to = s.option(form.ListValue, 'inet', _('Inet Family'));\n\t\to.value('ipv4', 'IPv4');\n\t\to.value('ipv6', 'IPv6');\n\t\to.value('ipv4v6', _('Both'));\n\t\to.rmempty = true;\n\t\to.editable = true;\n\t\to.depends('advanced', '1');\n\n\t\to = s.option(form.Value, 'ttl', _('TTL Value'),\n\t\t\t_('Select TTL value. Range 1 - 255'));\n\t\to.value('64','64')\n\t\to.value('128','128')\n\t\to.default = '64';\n\t\to.rmempty = true;\n\t\to.editable = true;\n\t\to.depends({advanced: '1', method: /ttl/});\n\n\t\to = s.option(form.Value, 'ports', _('Ports'),\n\t\t\t_('Incoming ports route to proxy-server<br />Custom ports range: 0-65535'));\n\t\to.editable = true;\n\t\to.rmempty = true;\n\t\to.value('all', _('ALL Ports'));\n\t\to.value('http', _('HTTP Ports'));\n\t\to.default = 'all';\n\t\to.depends({advanced: '1', method: /proxy/})\n\n\t\to = s.option(form.Value, 'proxy', _('Proxy Server Address'),\n                        _('IP-address proxy<br/>Format: <code>ipaddress:port</code><br />If not defined: use selected interface address.<br />Default: <code>lan</code> interface ipaddress'));\n                o.datatype = 'ipaddrport';\n                o.depends({advanced: '1', method: /proxy/})\n\n\t\treturn m.render();\n\t},\n});\n"
  },
  {
    "path": "luci/applications/luci-app-ttl/po/ru/ttl.po",
    "content": "msgid \"\"\nmsgstr \"Content-Type: text/plain; charset=UTF-8\"\n\n\"Language: ru\\n\"\n\"Last-Translator: Konstantine Shevlakov <shevlakov@132lan.ru\\n\"\n\nmsgid \"'In Method proxy Proxy server must be configured in transparent mode.<br />Default on lan ipaddress interface and port 3128 tcp.<br />Disable masquerade recommended.'\"\nmsgstr \"В режиме прокси, прокси-сервер должен быть настроен в прозрачном режиме.<br />По-умолчанию прокси-сервер ожидается на адресе lan-интерфейса на порту 3128 tcp.<br />Рекомендуется отключить NAT.\"\n\nmsgid \"Antitethering Config\"\nmsgstr \"Настройки антитетеринга\"\n\nmsgid \"TTL or Proxy antitether\"\nmsgstr \"TTL или прокси\"\n\nmsgid \"Set interface\"\nmsgstr \"Выбор интерфейса\"\n\nmsgid \"Method\"\nmsgstr \"Способ\"\n\nmsgid \"TTL method outgoing interface<br />Proxy method incoming interface\"\nmsgstr \"Для метода TTL интерфейс - исходящий<br />Для метода прокси интерфейс - входящий\"\n\nmsgid \"Inet Family\"\nmsgstr \"Протокол IP\"\n\nmsgid \"Advanced Option\"\nmsgstr \"Дополнительно\" \n\nmsgid \"Both\"\nmsgstr \"Оба\"\n\nmsgid \"Ports\"\nmsgstr \"Порты\"\n\nmsgid \"TTL Value\"\nmsgstr \"Значение TTL\"\n\nmsgid \"Select TTL value. Range 1 - 255\"\nmsgstr \"Выбор значения TTL. От 1 до 255\"\n\nmsgid \"Incoming ports route to proxy-server<br />Custom ports range: 0-65535\"\nmsgstr \"Порты для перенаправления на прокси-сервер<br />Диапазон 0-65535\"\n\nmsgid \"ALL Ports\"\nmsgstr \"Все порты\"\n\nmsgid \"HTTP Ports\"\nmsgstr \"Порты HTTP\"\n\nmsgid \"Proxy Server Address\"\nmsgstr \"Адрес прокси-сервера\"\n\nmsgid \"IP-address proxy<br/>Format: <code>ipaddress:port</code><br />If not defined: use selected interface address.<br />Default: <code>lan</code> interface ipaddress\"\nmsgstr \"IP-адрес прокси<br />Формат: <code>ip-адрес:порт</code><br />Если не задано: используется адрес выбранного интерфейса.<br />По-умолчанию: адрес интерфейса <code>lan</code>\""
  },
  {
    "path": "luci/applications/luci-app-ttl/root/etc/config/ttl",
    "content": ""
  },
  {
    "path": "luci/applications/luci-app-ttl/root/etc/hotplug.d/iface/90-ttl",
    "content": ". /lib/functions.sh\n\nhandle_config(){\n\tconfig_get iface \"$1\" iface\n\t[ \"$iface\" = \"$INTERFACE\" ] && {\n\t\trun_ttl=1\n\t}\n}\n\nconfig_load ttl\nconfig_foreach handle_config ttl\n\ncase $run_ttl in\n\t1) /etc/init.d/ttl reload ;;\nesac\n\nexit 0\n\n"
  },
  {
    "path": "luci/applications/luci-app-ttl/root/etc/init.d/ttl",
    "content": "#!/bin/sh /etc/rc.common\n\nSTART=96\nUSE_PROCD=1\n\nstart_service(){\n\t/usr/share/ttl.sh\n\tlogger -t \"TTL\" \"TTL reload\"\n}\n\nreload_service(){\n\tsleep 10 && start &\n}\n\nservice_triggers() {\n\tprocd_add_reload_trigger ttl\n}\n"
  },
  {
    "path": "luci/applications/luci-app-ttl/root/usr/share/luci/menu.d/luci-app-ttl.json",
    "content": "{\n\t\"admin/network/firewall\": {\n\t\t\"title\": \"Firewall\",\n\t\t\"order\": 60,\n\t\t\"action\": {\n\t\t\t\"type\": \"alias\",\n\t\t\t\"path\": \"admin/network/firewall/zones\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"acl\": [ \"luci-app-firewall\" ],\n\t\t\t\"fs\": { \"/sbin/fw3\": \"executable\" },\n\t\t\t\"uci\": { \"firewall\": true }\n\t\t}\n\t},\n\n\t\"admin/network/firewall/ttl\": {\n\t\t\"title\": \"TTL\",\n\t\t\"order\": 45,\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"firewall/ttl\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/applications/luci-app-ttl/root/usr/share/rpcd/acl.d/luci-app-ttl.json",
    "content": "{\n\t\"luci-app-ttl\": {\n\t\t\"description\": \"Grant access to TTL configuration\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/init.d/ttl\": [ \"exec\" ],\n\t\t\t\t\"/etc/init.d/firewall\": [ \"exec\" ]\n\t\t\t},\n\t\t\t\"cgi-io\": [ \"exec\" ],\n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"exec\" ],\n\t\t\t\t\"uci\": [ \"changes\", \"get\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"ttl\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"cgi-io\": [ \"exec\" ],\n\t\t\t\"ubus\": {\n\t\t\t\t\"file\": [ \"exec\" ],\n\t\t\t\t\"uci\": [ \"add\", \"apply\", \"confirm\", \"delete\", \"order\", \"rename\", \"set\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"ttl\" ]\n\t\t}\n\t}\n}"
  },
  {
    "path": "luci/applications/luci-app-ttl/root/usr/share/ttl.sh",
    "content": "#!/bin/sh\n\n\nSECTIONS=$(echo $(uci show ttl | awk -F [\\]\\[\\@=] '/=ttl/{print $3}'))\n\nget_vars(){\n\tfor v in method advanced inet ports ttl iface proxy; do\n\t\teval $v=$(uci -q get ttl.@ttl[${s}].${v} 2>/dev/nul)\n\tdone\n}\n\n\n# check iptables or nft \n#\nif [ -x /usr/sbin/iptables -o /usr/sbin/ip6tables -a ! -x /usr/sbin/nft ]; then\n\t. /usr/share/ttlipt.sh\nelse\n\t. /usr/share/ttlnft.sh\nfi\n"
  },
  {
    "path": "luci/applications/luci-app-ttl/root/usr/share/ttlipt.sh",
    "content": "method_ttl(){\n\n\tttl=${ttl:=64}\n\n\t#case $(($ttl % 2)) in\n\t#\t0) TTL_INC=$(($ttl-1)) ;;\n\t#\t*) TTL_INC=$ttl ;;\n\t#esac\n\tTTL_INC=$(($ttl-1))\n\n\tfor T in $IPT; do\n\t\tcase $T in\n\t\t\tiptables)\n\t\t\t\tSUFFIX=\"TTL --ttl-set\"\n\t\t\t\tif [ $iface ]; then\n\t\t\t\t\t$T -t mangle -A TTLFIX -i $DEV -m ttl --ttl 1 -j TTL --ttl-inc $TTL_INC\n\t\t\t\telse\n\t\t\t\t\t$T -t mangle -A TTLFIX -m ttl --ttl 1 -j TTL --ttl-inc $TTL_INC\n\t\t\t\tfi\n\t\t\t;;\n\t\t\tip6tables)\n\t\t\t\tSUFFIX=\"HL --hl-set\"\n\t\t\t\tif [ $iface ]; then\n\t\t\t\t\t$T -t mangle -A TTLFIX -i $DEV -m hl --hl 1 -j HL --hl-inc $TTL_INC\n\t\t\t\telse\n\t\t\t\t\t$T -t mangle -A TTLFIX -m hl --hl 1 -j HL --hl-inc $TTL_INC\n\t\t\t\tfi\n\t\t\t;;\n\t\tesac\n\n\t\tif [ $iface ]; then\n\t\t\t$T -t mangle -A TTL_OUT -o $DEV -j $SUFFIX $ttl\n\t\t\t$T -t mangle -A TTL_POST -o $DEV -j $SUFFIX $ttl\n\t\telse\n\t\t\t$T -t mangle -A TTL_OUT -j $SUFFIX $ttl\n\t\t\t$T -t mangle -A TTL_POST -j $SUFFIX $ttl\n\t\tfi\n\tdone\n}\n\n\nmethod_proxy(){\n\n\tfor T in $IPT; do\n\t\t[ \"$proxy\" ] && {\n\t\t\tIPADDR=${proxy%:*}\n\t\t\tcase $T in\n\t\t\t\tiptables) END=${IPADDR}:${proxy#*:} ;;\n\t\t\t\tip6tables) END=\"[${IPADDR}]:${proxy#*:}\" ;;\n\t\t\tesac\n\t        } || {\n\t\t\tcase $T in\n\t\t\t\tiptables)\n\t\t\t\t\tIPADDR=$(ifstatus $ifn | jsonfilter -e '@[\"ipv4-address\"][*][\"address\"]')\n\t\t\t\t\tEND=\"${IPADDR}:3128\"\n\t\t\t\t;;\n\t\t\t\tip6tables)\n\t\t\t\t\tfor a in $(ifstatus $ifn | jsonfilter -e '@[\"ipv6-prefix-assignment\"][*][\"local-address\"][\"address\"]'); do\n\t\t\t\t\t\tIPADDR=\"$a\"\n\t\t\t\t\tdone\n\t\t\t\t\tEND=\"[$IPADDR]:3128\"\n\t\t\t\t;;\n\t\t\tesac\n\t\t}\n\n\t\t$T -t nat -A PROXY -i $DEV -j FIXPROXY\n\n\t\tcase $ports in\n\t\t\tall)\n\t\t\t\t$T -t nat -A FIXPROXY ! -d ${IPADDR} \\\n\t\t\t\t\t! -s ${IPADDR} -p tcp \\\n\t\t\t\t\t-j DNAT --to-destination $END\n\t\t\t;;\n\t\t\thttp)\n\t\t\t\t$T -t nat -A FIXPROXY ! -d ${IPADDR} \\\n\t\t\t\t\t! -s ${IPADDR} -p tcp -m multiport \\\n\t\t\t\t\t--dports 80,443 -j DNAT --to-destination $END\n\t\t\t;;\n\t\t\t*)\n\t\t\t\tif [ $ports ]; then\n\t\t\t\t\t$T -t nat -A FIXPROXY ! -d ${IPADDR} \\\n\t\t\t\t\t\t! -s ${IPADDR} -p tcp -m multiport \\\n\t\t\t\t\t\t--dports $ports -j DNAT --to-destination $END\n\t\t\t\telse\n\t\t\t\t\t$T -t nat -A FIXPROXY ! -d ${IPADDR} \\\n\t\t\t\t\t\t! -s ${IPADDR} -p tcp \\\n\t\t\t\t\t\t-j DNAT --to-destination $END\n\t\t\t\tfi\n\t\t\t;;\n\t\tesac\n\tdone\n}\n\n# check nat66 module\nif [ -f /lib/modules/$(uname -r)/ip6table_nat.ko ]; then\n\tIPT=\"iptables ip6tables\"\nelse\n\tIPT=\"iptables\"\nfi\n\t\n# Create and flush mangle table\nfor T in $IPT; do\n\tfor t in N F; do\n\t\tfor c in TTLFIX TTL_OUT TTL_POST; do\n\t\t\t$T -t mangle -${t} ${c}\n\t\tdone\n\tdone\n\tfor a in D I; do\n\t\t$T -t mangle -${a} PREROUTING -j TTLFIX\n\t\t$T -t mangle -${a} OUTPUT -j TTL_OUT\n\t\t$T -t mangle -${a} POSTROUTING -j TTL_POST\n\tdone\ndone\n\n# Create and flush nat table\nfor T in $IPT; do\n\tfor t in N F; do\n\t\t$T -t nat -${t} PROXY\n\t\t$T -t nat -${t} FIXPROXY\n\tdone\n\tfor a in D I; do\n\t\t$T -t nat -${a} PREROUTING -j PROXY\n\tdone\ndone\n\nfor s in $SECTIONS; do\n\tif [ \"$s\" ]; then\n\t\tget_vars\n\telse\n\t\texit 0\n\tfi\n\n        [ -n $iface ] && {\n                ifn=$iface\n        } || {\n                ifn=lan\n        }\n\n\tcase $inet in\n\t\tipv4) IPT=\"iptables\" ;;\n\t\tipv6) IPT=\"ip6tables\" ;;\n\t\t*) IPT=\"iptables ip6tables\";;\n\tesac\n\tif ! [ -f /lib/modules/$(uname -r)/ip6table_nat.ko ]; then\n\t\tIPT=\"iptables\"\n\tfi\n\tDEV=$(ifstatus $iface | jsonfilter -e '@[\"l3_device\"]')\n\tcase $method in\n\t\tttl) method_ttl ;;\n\t\tproxy) method_proxy ;;\n\tesac\ndone\n"
  },
  {
    "path": "luci/applications/luci-app-ttl/root/usr/share/ttlnft.sh",
    "content": "method_ttl(){\n\n    ttl=${ttl:=64}\n\n    # create mangle table\n    nft add table ip mangle 2>/dev/null\n    nft add table ip6 mangle 2>/dev/null\n\n    for fam in $family; do\n        [ \"$fam\" = \"ip6\" ] && [ \"$inet\" = \"ipv4\" ] && continue\n        [ \"$fam\" = \"ip\" ] && [ \"$inet\" = \"ipv6\" ] && continue\n\n        # define nftables chains\n        nft add chain $fam mangle TTLFIX { type filter hook prerouting priority -150 \\; }\n        nft add chain $fam mangle TTL_OUT { type route hook output priority -150 \\; }\n        nft add chain $fam mangle TTL_POST { type filter hook postrouting priority -150 \\; }\n\n        # TTL/HL change rules\n\tcase $fam in\n\t\tip) TTLNAME=ttl ;;\n\t\tip6) TTLNAME=hoplimit ;;\n\tesac\n\tif [ $iface ]; then\n\t\tnft add rule $fam mangle TTLFIX iif $DEV $fam $TTLNAME 1 $fam $TTLNAME set $ttl\n\t\tnft add rule $fam mangle TTL_OUT oif $DEV $fam $TTLNAME set $ttl\n\t\tnft add rule $fam mangle TTL_POST oif $DEV $fam $TTLNAME set $ttl\n\telse\n\t\tnft add rule $fam mangle TTLFIX $fam $TTLNAME 1 $fam $TTLNAME set $ttl\n\t\tnft add rule $fam mangle TTL_OUT $fam $TTLNAME set $ttl\n\t\tnft add rule $fam mangle TTL_POST $fam $TTLNAME set $ttl\n\tfi\n    done\n}\n\nmethod_proxy(){\n    for fam in $family; do\n\t# create nat table\n\tnft add table $fam nat 2>/dev/null\n        [ \"$fam\" = \"ip6\" ] && [ \"$inet\" = \"ipv4\" ] && continue\n        [ \"$fam\" = \"ip\" ] && [ \"$inet\" = \"ipv6\" ] && continue\n\n\t[ \"$proxy\" ] && {\n\t\tIPADDR=${proxy%:*}\n\t\tEND=${IPADDR}:${proxy#*:}\n\t} || {\n\t        # get ipaddress from iface if not defined\n\t\tcase $fam in\n\t        \tip)\n\t\t\t\tIPADDR=$(ifstatus $ifn | jsonfilter -e '@[\"ipv4-address\"][*][\"address\"]')\n\t\t\t\tEND=\"${IPADDR}:3128\"\n\t\t\t;;\n\t\t\tip6)\n\t\t\t\tIPADDR=$(ifstatus $ifn | jsonfilter -e '@[\"ipv6-prefix-assignment\"][*][\"local-address\"][\"address\"]' | head -n1)\n\t\t\t\tEND=\"${IPADDR}:3128\"\n\t\t\t;;\n\t\tesac\n\t}\n\n        # create NAT chains\n        nft add chain $fam nat PROXY { type nat hook prerouting priority -100 \\; }\n        nft add chain $fam nat FIXPROXY\n\n        # add traffic rule\n\t[ $iface ] && {\n\t        nft add rule $fam nat PROXY iif $DEV jump FIXPROXY\n\t} || {\n\t\tnft add rule $fam nat PROXY jump FIXPROXY\n\t}\n\n\n        case $ports in\n            all)\n                nft add rule $fam nat FIXPROXY $fam daddr != $IPADDR $fam saddr != $IPADDR \\\n                    meta l4proto tcp dnat to $END\n                ;;\n            http)\n                nft add rule $fam nat FIXPROXY $fam daddr != $IPADDR $fam saddr != $IPADDR \\\n                    meta l4proto tcp tcp dport {80,443} dnat to $END\n                ;;\n            *)\n                if [ $ports ]; then\n                    nft add rule $fam nat FIXPROXY $fam daddr != $IPADDR $fam saddr != $IPADDR \\\n                        meta l4proto tcp tcp dport {$(echo $ports | tr ',' ',')} dnat to $END\n                else\n                    nft add rule $fam nat FIXPROXY $fam daddr != $IPADDR $fam saddr != $IPADDR \\\n                        meta l4proto tcp dnat to $END\n                fi\n                ;;\n        esac\n    done\n}\n\n# init tables and chains\nfor fml in ip ip6; do\n    # create table mangle\n    nft delete table $fml mangle 2>/dev/null\n    nft delete table $fml nat 2>/dev/null\n\n    # define chains\n    nft add table $fml mangle\n    nft add chain $fml mangle TTLFIX { type filter hook prerouting priority -150 \\; }\n    nft add chain $fml mangle TTL_OUT { type route hook output priority -150 \\; }\n    nft add chain $fml mangle TTL_POST { type filter hook postrouting priority -150 \\; }\ndone\n\nfor s in $SECTIONS; do\n\tif [ \"$s\" ]; then\n\t\tget_vars\n\telse\n\t\texit 0\n\tfi\n\n\t[ $iface ] && {\n                ifn=$iface\n\t} || {\n\t\tifn=lan\n\t}\n\n\tcase $inet in\n\t\tipv4) family=\"ip\" ;;\n\t\tipv6) family=\"ip6\" ;;\n\t\t*) family=\"ip ip6\" ;;\n\tesac\n\n\t#if [ ! -f /lib/modules/$(uname -r)/ip6table_nat.ko ]; then\n\t#\tfamily=\"ip\"\n\t#fi\n\n\tDEV=$(ifstatus $iface | jsonfilter -e '@[\"l3_device\"]')\n\n\tcase $method in\n\t\tttl) method_ttl ;;\n\t\tproxy) method_proxy ;;\n\tesac\n\ndone\n\n"
  },
  {
    "path": "luci/protocols/luci-proto-openvpn/Makefile",
    "content": "#\n# Copyright (C) 2026 OpenWRT\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Support for OpenVPN\nLUCI_DEPENDS:=+openvpn\nLUCI_PKGARCH:=all\n\nPKG_LICENSE:=Apache-2.0\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/protocols/luci-proto-openvpn/htdocs/luci-static/resources/protocol/openvpn.js",
    "content": "// luci-proto-openvpn: OpenVPN protocol handler for LuCI\n'use strict';\n'require form';\n'require fs';\n'require network';\n'require rpc';\n'require uci';\n'require ui';\n\nconst callGenKey = rpc.declare({\n\tobject: 'luci.openvpn',\n\tmethod: 'generateKey',\n\tparams: { keytype: 'keytype', ifname: 'ifname', server_key: 'server_key', cl_meta: '' },\n});\n\n\nconst callGetSKeys = rpc.declare({\n\tobject: 'luci.openvpn',\n\tmethod: 'getSKeys',\n\tparams: [ 'ifname' ],\n});\n\n\nconst openvpnOptions = [\n\t/* Basic options (unchanged) */\n\t{\n\t\ttab: 'general',\n\t\ttype: form.Value,\n\t\tname: 'port',\n\t\tplaceholder: '1194',\n\t\tlabel: _('TCP/UDP port # for both local and remote')\n\t},\n\t{\n\t\ttab: 'general',\n\t\ttype: form.Flag,\n\t\tname: 'nobind',\n\t\tlabel: _('Do not bind to local address and port'),\n\t\tdefault: 0\n\t},\n\t// --client Options error: specify only one of --tls-server, --tls-client, or --secret\n\t// --client also needs DCO(?)\n\t{\n\t\ttab: 'general',\n\t\ttype: form.Flag,\n\t\tname: 'client',\n\t\tlabel: _('Configure client mode') + '<br/>' + _('Requires --tls-server, --tls-client, or --secret'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'general',\n\t\ttype: form.DynamicList,\n\t\tname: 'remote',\n\t\tdatatype: 'or(host,tuple(host,port),tuple(host,port,string))',\n\t\tlabel: _('Remote host name or IP address'),\n\t\tplaceholder: '1.2.3.4'\n\t},\n\t{\n\t\ttab: 'general',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc/openvpn',\n\t\tname: 'ca',\n\t\tlabel: _('Certificate authority'),\n\t\tplaceholder: '/etc/easy-rsa/keys/ca.crt'\n\t},\n\t{\n\t\ttab: 'general',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc/openvpn',\n\t\tname: 'cert',\n\t\tlabel: _('Local certificate'),\n\t\tplaceholder: '/etc/easy-rsa/keys/some-client.crt'\n\t},\n\t{\n\t\ttab: 'general',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc/openvpn',\n\t\tname: 'key',\n\t\tlabel: _('Local private key'),\n\t\tplaceholder: '/etc/easy-rsa/keys/some-client.key'\n\t},\n\t{\n\t\ttab: 'general',\n\t\ttype: form.Value,\n\t\tname: 'ifconfig',\n\t\tdatatype: 'tuple(ipaddr,ipaddr)',\n\t\tplaceholder: '10.200.200.3 10.200.200.1',\n\t\tlabel: _('Set tun/tap adapter parameters')\n\t},\n\t{\n\t\ttab: 'general',\n\t\ttype: form.Value,\n\t\tname: 'ifconfig_ipv6',\n\t\tdatatype: 'or(ip6addr,tuple(ip6addr,ip6addr))',\n\t\tplaceholder: 'fd15:53b6:dead::2/64 [fd15:53b6:dead::1]',\n\t\tlabel: _('Set tun/tap adapter parameters')\n\t},\n\t{\n\t\ttab: 'general',\n\t\ttype: form.Value,\n\t\tname: 'server',\n\t\tdatatype: 'tuple(ipaddr,ipaddr)',\n\t\tplaceholder: '10.200.200.0 255.255.255.0',\n\t\tlabel: _('Configure server mode')\n\t},\n\t{\n\t\ttab: 'general',\n\t\ttype: form.Value,\n\t\tname: 'server_bridge',\n\t\tdatatype: 'tuple(ipaddr,ipaddr,ipaddr,ipaddr)',\n\t\tplaceholder: '192.168.1.1 255.255.255.0 192.168.1.128 192.168.1.254',\n\t\tlabel: _('Configure server bridge')\n\t},\n\t/*\n\t * {\n\t * \ttab: 'general',\n\t * \ttype: form.ListValue,\n\t * \tname: 'comp_lzo',\n\t * \tlvalues: [\n\t * \t\t'yes',\n\t * \t\t'no',\n\t * \t\t'adaptive'\n\t * \t],\n\t * \tlabel: _('Security recommendation: It is recommended to not enable compression and set this parameter to `no`'),\n\t * \tdefault: 'no'\n\t * },\n\t */\n\t{\n\t\ttab: 'general',\n\t\ttype: form.Value,\n\t\tname: 'keepalive',\n\t\tplaceholder: '10 60',\n\t\tlabel: _('Helper directive to simplify the expression of --ping and --ping-restart in server mode configurations')\n\t},\n\t{\n\t\ttab: 'general',\n\t\ttype: form.Flag,\n\t\tname: 'client_to_client',\n\t\tlabel: _('Allow client-to-client traffic'),\n\t\tdefault: 0\n\t},\n\t// secret requires --cipher *-CBC \n\t{\n\t\ttab: 'basic',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc/openvpn',\n\t\tname: 'secret',\n\t\tlabel: _('Enable Static Key encryption mode (non-TLS)') + '¹' + '<br />' + _('Used with auth and cipher params'),\n\t\tplaceholder: '/etc/openvpn/secret.key'\n\t},\n\t{\n\t\ttab: 'basic',\n\t\ttype: form.ListValue,\n\t\tname: 'key_direction',\n\t\tvalues: [\n\t\t\t0,\n\t\t\t1\n\t\t],\n\t\tlabel: _('The key direction for \\'tls-auth\\' and \\'secret\\' options'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'basic',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc/openvpn',\n\t\tname: 'pkcs12',\n\t\tlabel: _('PKCS#12 file containing keys') + '²',\n\t\tplaceholder: '/etc/easy-rsa/keys/some-client.pk12'\n\t},\n\t{\n\t\ttab: 'basic',\n\t\ttype: form.Value,\n\t\tname: 'peer_fingerprint',\n\t\tlabel: _('The peer key fingerprint'),\n\t\tplaceholder: 'AD:B0:95:D8:09:...'\n\t},\n\n\t/* Devices */\n\t{\n\t\ttab: 'devices',\n\t\ttype: form.Value,\n\t\tname: 'dev',\n\t\tplaceholder: 'tun0',\n\t\tlabel: _('tun/tap device')\n\t},\n\t// tun == L3 IPv4/6 tap == L2 Ethernet 802.3\n\t{\n\t\ttab: 'devices',\n\t\ttype: form.Value,\n\t\tname: 'dev_type',\n\t\tlvalues: [\n\t\t\t'tun',\n\t\t\t'tap'\n\t\t],\n\t\tlabel: _('Type of used device'),\n\t\tdefault: 'tun'\n\t},\n\t{\n\t\ttab: 'devices',\n\t\ttype: form.Value,\n\t\tname: 'dev_node',\n\t\tlabel: _('Use tun/tap device node'),\n\t\tplaceholder: '/dev/net/tun'\n\t},\n\t{\n\t\ttab: 'devices',\n\t\ttype: form.Flag,\n\t\tname: 'vlan_tagging',\n\t\tlabel: _('Use VLAN tagging'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'devices',\n\t\ttype: form.ListValue,\n\t\tlvalues: [\n\t\t\t'all',\n\t\t\t'tagged',\n\t\t\t'untagged'\n\t\t],\n\t\tname: 'vlan_accept',\n\t\tlabel: _('Accept VLANs')\n\t},\n\t{\n\t\ttab: 'devices',\n\t\ttype: form.Value,\n\t\tdatatype: 'and(uinteger,min(1),max(4094))',\n\t\tname: 'vlan_pvid',\n\t\tlabel: _('Use these PVIDs')\n\t},\n\n\t/* Service (init/daemon) */\n\t{\n\t\ttab: 'service',\n\t\ttype: form.Flag,\n\t\tname: 'mlock',\n\t\tlabel: _('Disable Paging'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'service',\n\t\ttype: form.Flag,\n\t\tname: 'disable_occ',\n\t\tlabel: _('Disable options consistency check'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'service',\n\t\ttype: form.DirectoryPicker,\n\t\tname: 'cd',\n\t\troot_directory: '/etc/openvpn',\n\t\tlabel: _('Change to directory before initialization'),\n\t\tplaceholder: '/etc/openvpn'\n\t},\n\t{\n\t\ttab: 'service',\n\t\ttype: form.DirectoryPicker,\n\t\tname: 'chroot',\n\t\troot_directory: '/var/run',\n\t\tlabel: _('Chroot to directory after initialization'),\n\t\tplaceholder: '/var/run'\n\t},\n\t{\n\t\ttab: 'service',\n\t\ttype: form.Flag,\n\t\tname: 'passtos',\n\t\tlabel: _('TOS passthrough (applies to IPv4 only)'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'service',\n\t\ttype: form.Value,\n\t\tname: 'nice',\n\t\tdatatype: 'and(integer,min(-20),max(19))',\n\t\tlabel: _('Change process priority'),\n\t\tplaceholder: 0\n\t},\n\t{\n\t\ttab: 'service',\n\t\ttype: form.Flag,\n\t\tname: 'fast_io',\n\t\tlabel: _('Optimize TUN/TAP/UDP writes'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'service',\n\t\ttype: form.ListValue,\n\t\tname: 'remap_usr1',\n\t\tlvalues: [\n\t\t\t'SIGHUP',\n\t\t\t'SIGTERM'\n\t\t],\n\t\tlabel: _('Remap SIGUSR1 signals')\n\t},\n\t/*\n\t * {\n\t * \ttab: 'service',\n\t * \ttype: form.Value,\n\t * \tname: 'status',\n\t * \tplaceholder: '/var/run/openvpn.status 5',\n\t * \tlabel: _('Write status to file every n seconds'),\n\t * \tplaceholder: '/var/run/openvpn.status 5'\n\t * },\n\t */\n\t{\n\t\ttab: 'service',\n\t\ttype: form.ListValue,\n\t\tname: 'status_version',\n\t\tvalues: [\n\t\t\t1,\n\t\t\t2\n\t\t],\n\t\tlabel: _('Status file format version'),\n\t\tdefault: 2\n\t},\n\t/*\n\t * {\n\t * \ttab: 'service',\n\t * \ttype: form.ListValue,\n\t * \tname: 'compress',\n\t * \tvalues: [\n\t * \t\t'frames_only',\n\t * \t\t'lzo',\n\t * \t\t'lz4',\n\t * \t\t'stub-v2'\n\t * \t],\n\t * \tlabel: _('Security recommendation: It is recommended to not enable compression and set this parameter to `stub-v2`'),\n\t * \tdefault: 'stub-v2'\n\t * },\n\t */\n\n\t/* Scripts */\n\t{\n\t\ttab: 'scripts',\n\t\ttype: form.ListValue,\n\t\tname: 'script_security',\n\t\tvalues: [\n\t\t\t_('0: Deny'),\n\t\t\t_('1: OS utils'),\n\t\t\t_('2: User scripts'),\n\t\t\t_('3: Allow passwords in env')\n\t\t],\n\t\tlabel: _('Policy level over usage of external programs and scripts'),\n\t\tdefault: 1\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'up',\n\t\tplaceholder: '/usr/bin/ovpn-up',\n\t\tlabel: _('Shell cmd to execute after tun device open')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'up_delay',\n\t\tplaceholder: '5',\n\t\tlabel: _('Delay tun/tap open and up script execution')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'down',\n\t\tplaceholder: '/usr/bin/ovpn-down',\n\t\tlabel: _('Shell cmd to run after tun device close')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Flag,\n\t\tname: 'down_pre',\n\t\tlabel: _('Call down cmd/script before TUN/TAP close'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Flag,\n\t\tname: 'up_restart',\n\t\tlabel: _('Run up/down scripts for all restarts'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'route_up',\n\t\tplaceholder: '/usr/bin/ovpn-routeup',\n\t\tlabel: _('Execute shell cmd after routes are added')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'ipchange',\n\t\tplaceholder: '/usr/bin/ovpn-ipchange',\n\t\tlabel: _('Execute shell command on remote IP change')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.DynamicList,\n\t\tname: 'setenv',\n\t\tlabel: _('Pass environment variables to script'),\n\t\tplaceholder: _('name value')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.DynamicList,\n\t\tname: 'setenv_safe',\n\t\tlabel: _('Pass environment variables to script prepended with OPENVPN_'),\n\t\tplaceholder: _('name value')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'tls_verify',\n\t\tplaceholder: '/usr/bin/ovpn-tlsverify',\n\t\tlabel: _('Shell command to verify X509 name')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'client_connect',\n\t\tplaceholder: '/usr/bin/ovpn-clientconnect',\n\t\tlabel: _('Run script cmd on client connection')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'client_crresponse',\n\t\tplaceholder: '/usr/bin/ovpn-clientcrresponse',\n\t\tlabel: _('Run script cmd to validate client certificates')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'client_disconnect',\n\t\tplaceholder: '/usr/bin/ovpn-clientdisconnect',\n\t\tlabel: _('Run script cmd on client disconnection')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'tls_crypt_v2_verify',\n\t\tplaceholder: '/usr/bin/ovpn-tlscryptv2verify',\n\t\tlabel: _('Run script cmd for client TLS verification')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'learn_address',\n\t\tplaceholder: '/usr/bin/ovpn-learnaddress',\n\t\tlabel: _('Executed in server mode whenever an IPv4 address/route or MAC address is added to OpenVPN\\'s internal routing table')\n\t},\n\t{\n\t\ttab: 'scripts',\n\t\tdepends: { script_security: /[1-3]/ },\n\t\ttype: form.Value,\n\t\tname: 'auth_user_pass_verify',\n\t\tplaceholder: '/usr/bin/ovpn-userpass via-env',\n\t\tlabel: _('Executed in server mode on new client connections, when the client is still untrusted')\n\t},\n\n\t/* Logging */\n\t{\n\t\ttab: 'logging',\n\t\ttype: form.Value,\n\t\tname: 'echo',\n\t\tlabel: _('Echo parameters to log'),\n\t\tplaceholder: _('some params echoed to log')\n\t},\n\t{\n\t\ttab: 'logging',\n\t\ttype: form.Value,\n\t\tname: 'log',\n\t\tlabel: _('Write log to file'),\n\t\tplaceholder: '/var/log/openvpn.log'\n\t},\n\t{\n\t\ttab: 'logging',\n\t\ttype: form.Value,\n\t\tname: 'log_append',\n\t\tlabel: _('Append log to file'),\n\t\tplaceholder: '/var/log/openvpn.log'\n\t},\n\t/*\n\t * {\n\t * \ttab: 'logging',\n\t * \ttype: form.Value,\n\t * \tname: 'syslog',\n\t * \tplaceholder: 'openvpn',\n\t * \tlabel: _('Syslog tag')\n\t * },\n\t */\n\t{\n\t\ttab: 'logging',\n\t\ttype: form.Value,\n\t\tname: 'mute',\n\t\tlabel: _('Limit repeated log messages'),\n\t\tplaceholder: 5\n\t},\n\t{\n\t\ttab: 'logging',\n\t\ttype: form.Flag,\n\t\tname: 'suppress_timestamps',\n\t\tlabel: _('Don\\'t log timestamps'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'logging',\n\t\ttype: form.Value,\n\t\tname: 'verb',\n\t\tdatatype: 'and(uinteger,min(0),max(11))',\n\t\tlabel: _('Set output verbosity'),\n\t\tplaceholder: '0-11'\n\t},\n\n\t/* Networking (socket, device, routing) */\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.ListValue,\n\t\tname: 'mode',\n\t\tlvalues: [\n\t\t\t'p2p',\n\t\t\t'server'\n\t\t],\n\t\tlabel: _('Major mode')\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'local',\n\t\tdatatype: 'or(hostname,ipaddr)',\n\t\tlabel: _('Local host name or IP address'),\n\t\tplaceholder: '0.0.0.0'\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'port',\n\t\tdatatype: 'port',\n\t\tlabel: _('TCP/UDP port # for both local and remote'),\n\t\tplaceholder: 1194\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'lport',\n\t\tdatatype: 'port',\n\t\tlabel: _('TCP/UDP port # for local'),\n\t\tplaceholder: 1194\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'rport',\n\t\tdatatype: 'port',\n\t\tlabel: _('TCP/UDP port # for remote'),\n\t\tplaceholder: 1194\n\t},\n\t// name: 'proto' collides with netifd 'proto' -> map to ovpnproto\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'ovpnproto',\n\t\tlvalues: [\n\t\t\t'udp',\n\t\t\t'tcp-client',\n\t\t\t'tcp-server'\n\t\t],\n\t\tlabel: _('Use protocol'),\n\t\tplaceholder: 'udp'\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'float',\n\t\tlabel: _('Allow remote to change its IP or port'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'nobind',\n\t\tlabel: _('Do not bind to local address and port'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'multihome',\n\t\tlabel: _('When you have more than one IP address (e.g. multiple interfaces, or secondary IP addresses), and do not use --local to force binding to one specific address only'),\n\t\tdefault: 0\n\t},\n\t/*\n\t * {\n\t * \ttab: 'networking',\n\t * \ttype: form.Value,\n\t * \tname: 'ifconfig',\n\t * \tlabel: _('Set tun/tap adapter parameters'),\n\t * \tplaceholder: '10.200.200.3 10.200.200.1'\n\t * },\n\t */\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'ifconfig_noexec',\n\t\tlabel: _('Don\\'t actually execute ifconfig'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'ifconfig_nowarn',\n\t\tlabel: _('Don\\'t warn on ifconfig inconsistencies'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.DynamicList,\n\t\tname: 'route',\n\t\tlabel: _('Add route after establishing connection'),\n\t\tplaceholder: '10.123.0.0 255.255.0.0'\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'route_gateway',\n\t\tdatatype: 'ipaddr',\n\t\tlabel: _('Specify a placeholder gateway for routes'),\n\t\tplaceholder: '10.234.1.1'\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'route_delay',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Delay n seconds after connection'),\n\t\tplaceholder: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'route_noexec',\n\t\tlabel: _('Don\\'t add routes automatically'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'route_nopull',\n\t\tlabel: _('Don\\'t pull routes automatically'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'allow_recursive_routing',\n\t\tlabel: _('Don\\'t drop incoming tun packets with same destination as host'),\n\t\tplaceholder: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.ListValue,\n\t\tname: 'mtu_disc',\n\t\tlvalues: [\n\t\t\t'yes',\n\t\t\t'maybe',\n\t\t\t'no'\n\t\t],\n\t\tlabel: _('Enable Path MTU discovery'),\n\t\tdefault: 'no'\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'mtu_test',\n\t\tlabel: _('Empirically measure MTU'),\n\t\tdefault: 0\n\t},\n\t/*\n\t * {\n\t * \ttab: 'networking',\n\t * \ttype: form.ListValue,\n\t * \tname: 'comp_lzo',\n\t * \tlvalues: [\n\t * \t\t'yes',\n\t * \t\t'no',\n\t * \t\t'adaptive'\n\t * \t],\n\t * \tlabel: _('Security recommendation: It is recommended to not enable compression and set this parameter to `no`') + '¹',\n\t * \tdefault: 'no'\n\t * },\n\t */\n\t/* {\n\t * \ttab: 'networking',\n\t * \ttype: form.Flag,\n\t * \tname: 'comp_noadapt',\n\t * \tlabel: _('Don\\'t use adaptive lzo compression') + '¹',\n\t * \tplaceholder: 0\n\t * },\n\t */\n\t/* {\n\t * \ttab: 'networking',\n\t * \ttype: form.Value,\n\t * \tname: 'link_mtu',\n\t * \tdatatype: 'uinteger',\n\t * \tlabel: _('Set TCP/UDP MTU') + '¹',\n\t * \tplaceholder: 1500\n\t * },\n\t */\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'tun_mtu',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Set tun/tap device MTU'),\n\t\tplaceholder: 1500\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'tun_mtu_extra',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Set tun/tap device overhead'),\n\t\tplaceholder: 1500\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'fragment',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Enable internal datagram fragmentation'),\n\t\tplaceholder: 1500\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'mssfix',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Set upper bound on TCP MSS'),\n\t\tplaceholder: 1450\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'sndbuf',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Set the TCP/UDP send buffer size'),\n\t\tplaceholder: 65536\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'rcvbuf',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Set the TCP/UDP receive buffer size'),\n\t\tplaceholder: 65536\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'txqueuelen',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Set tun/tap TX queue length'),\n\t\tplaceholder: 100\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'shaper',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Shaping for peer bandwidth'),\n\t\tplaceholder: 10240\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'inactive',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('tun/tap inactivity timeout'),\n\t\tplaceholder: 240\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'keepalive',\n\t\tlabel: _('Helper directive to simplify the expression of --ping and --ping-restart in server mode configurations'),\n\t\tplaceholder: '10 60'\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'ping',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Ping remote every n seconds over TCP/UDP port'),\n\t\tplaceholder: 30\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'ping_exit',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Remote ping timeout'),\n\t\tplaceholder: 120\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Value,\n\t\tname: 'ping_restart',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Restart after remote ping timeout'),\n\t\tplaceholder: 60\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'ping_timer_rem',\n\t\tlabel: _('Only process ping timeouts if routes exist'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'persist_tun',\n\t\tlabel: _('Keep tun/tap device open on restart'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'persist_key',\n\t\tlabel: _('Don\\'t re-read key on restart'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'persist_local_ip',\n\t\tlabel: _('Keep local IP address on restart'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'networking',\n\t\ttype: form.Flag,\n\t\tname: 'persist_remote_ip',\n\t\tlabel: _('Keep remote IP address on restart'),\n\t\tdefault: 0\n\t},\n\n\t/* Management */\n\t{\n\t\ttab: 'management',\n\t\ttype: form.Value,\n\t\tname: 'management',\n\t\tlabel: _('Enable management interface on <em>IP</em> <em>port</em>'),\n\t\tplaceholder: '127.0.0.1 31194 /etc/openvpn/mngmt-pwds'\n\t},\n\t{\n\t\ttab: 'management',\n\t\ttype: form.Flag,\n\t\tname: 'management_query_passwords',\n\t\tlabel: _('Query management channel for private key'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'management',\n\t\ttype: form.Flag,\n\t\tname: 'management_hold',\n\t\tlabel: _('Start OpenVPN in a hibernating state'), \n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'management',\n\t\ttype: form.Value,\n\t\tname: 'management_log_cache',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Number of lines for log file history'),\n\t\tplaceholder: 100\n\t},\n\t{\n\t\ttab: 'management',\n\t\ttype: form.Value,\n\t\tname: 'management_external_cert',\n\t\tdatatype: 'file',\n\t\tlabel: _('Management cert'),\n\t\tplaceholder: 'certificate-hint'\n\t},\n\t{\n\t\ttab: 'management',\n\t\ttype: form.Value,\n\t\tname: 'management_external_key',\n\t\tdatatype: 'file',\n\t\tlabel: _('Management key'),\n\t\tplaceholder: 'nopadding pkcs1'\n\t},\n\n\t/* Topology */\n\t{\n\t\ttab: 'topology',\n\t\ttype: form.ListValue,\n\t\tname: 'topology',\n\t\tlvalues: [\n\t\t\t'net30',\n\t\t\t'p2p',\n\t\t\t'subnet'\n\t\t],\n\t\tlabel: _('\\'net30\\', \\'p2p\\', or \\'subnet\\''),\n\t\tdefault: ''\n\t},\n\t{\n\t\ttab: 'topology',\n\t\ttype: form.Flag,\n\t\tname: 'disable_dco',\n\t\tlabel: _('Disable Data Channel Offloading (DCO) support'),\n\t\tdefault: 0\n\t},\n\n\t/* Crypto */\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc/openvpn',\n\t\tname: 'dh',\n\t\tlabel: _('Diffie-Hellman parameters'),\n\t\tplaceholder: '/etc/easy-rsa/keys/dh1024.pem'\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.DirectoryPicker,\n\t\troot_directory: '/etc',\n\t\tname: 'capath',\n\t\tlabel: _('CA path') + '²'\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc',\n\t\tname: 'askpass_file',\n\t\tlabel: _('Get certificate password from file before we daemonize')\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'auth',\n\t\tlabel: _('HMAC authentication for packets'),\n\t\tlvalues: [\n\t\t\t'SHA1',\n\t\t\t'SHA224',\n\t\t\t'SHA256',\n\t\t\t'SHA384',\n\t\t\t'SHA512',\n\t\t\t'SHA3-224',\n\t\t\t'SHA3-256',\n\t\t\t'SHA3-384',\n\t\t\t'SHA3-512',\n\t\t\t'none'\n\t\t]\n\t},\n\t// --data-ciphers-fallback with cipher 'AES-256-CBC' disables data channel offload.\n\t// for --secret pre-shared-key mode. Ignored in >= 2.6 (TLS mode).\n\t{ tab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'cipher',\n\t\tlabel: _('Encryption cipher for packets') + '¹',\n\t\tlvalues: [\n\t\t\t'AES-128-CBC',\n\t\t\t'AES-128-GCM',\n\t\t\t'AES-192-CBC',\n\t\t\t'AES-192-GCM',\n\t\t\t'AES-256-CBC',\n\t\t\t'AES-256-GCM',\n\t\t\t'CHACHA20-POLY1305'\n\t\t]\n\t},\n\t/*\n\t * {\n\t * \ttab: 'cryptography',\n\t * \ttype: form.Value,\n\t * \tname: 'keysize',\n\t * \tlabel: _('Size of cipher key'),\n\t * \tplaceholder: 1024\n\t * },\n\t */\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'engine',\n\t\tlabel: _('Enable OpenSSL hardware crypto engines') + '²',\n\t\tlvalues: [\n\t\t\t'dynamic'\n\t\t]\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'ecdh_curve',\n\t\tlabel: _('Specify the curve to use for ECDH') + '²',\n\t\tlvalues: [\n\t\t\t'x25519',\n\t\t\t'secp521r1',\n\t\t\t'secp384r1',\n\t\t\t'secp256r1',\n\t\t\t'secp256k1'\n\t\t]\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'replay_window',\n\t\tlabel: _('Replay protection sliding window size'),\n\t\tplaceholder: '64 15'\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'replay_window',\n\t\tlabel: _('Replay protection sliding window size'),\n\t\tplaceholder: '64 15'\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Flag,\n\t\tname: 'mute_replay_warnings',\n\t\tlabel: _('Silence the output of replay warnings'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/var/run/',\n\t\tname: 'replay_persist',\n\t\tlabel: _('Persist replay-protection state'),\n\t\tplaceholder: '/var/run/openvpn-replay-state'\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Flag,\n\t\tname: 'tls_server',\n\t\tlabel: _('Enable TLS and assume server role'),\n\t\tdefault: 0\n\t}, // depends: { tls_client: 0}\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Flag,\n\t\tname: 'tls_client',\n\t\tlabel: _('Enable TLS and assume client role'),\n\t\tdefault: 0\n\t}, // depends: { tls_server: 0}\n\t/*\n\t * {\n\t * \ttab: 'cryptography',\n\t * \ttype: form.Value,\n\t * \tname: 'key_method',\n\t * \tlabel: _('Enable TLS and assume client role')\n\t * },\n\t */\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.DynamicList,\n\t\tname: 'tls_cipher',\n\t\tlabel: _('TLS cipher'), \n\t\tlvalues: [\n\t\t\t'TLS1-3-CHACHA20-POLY1305-SHA256',\n\t\t\t'TLS1-3-AES-256-GCM-SHA384',\n\t\t\t'TLS1-3-AES-128-GCM-SHA256',\n\t\t\t'TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384',\n\t\t\t'TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384',\n\t\t\t'TLS-DHE-RSA-WITH-AES-256-GCM-SHA384',\n\t\t\t'TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256',\n\t\t\t'TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256',\n\t\t\t'TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256',\n\t\t\t'TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256',\n\t\t\t'TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256',\n\t\t\t'TLS-DHE-RSA-WITH-AES-128-GCM-SHA256',\n\t\t\t'TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384',\n\t\t\t'TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384',\n\t\t\t'TLS-DHE-RSA-WITH-AES-256-CBC-SHA256',\n\t\t\t'TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256',\n\t\t\t'TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256',\n\t\t\t'TLS-DHE-RSA-WITH-AES-128-CBC-SHA256',\n\t\t\t'TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA',\n\t\t\t'TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA',\n\t\t\t'TLS-DHE-RSA-WITH-AES-256-CBC-SHA',\n\t\t\t'TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA',\n\t\t\t'TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA',\n\t\t\t'TLS-DHE-RSA-WITH-AES-128-CBC-SHA',\n\t\t\t'TLS-ECDHE-PSK-WITH-CHACHA20-POLY1305-SHA256',\n\t\t\t'TLS-ECDHE-PSK-WITH-AES-256-CBC-SHA384',\n\t\t\t'TLS-ECDHE-PSK-WITH-AES-256-CBC-SHA',\n\t\t\t'TLS-ECDHE-PSK-WITH-AES-128-CBC-SHA256',\n\t\t\t'TLS-ECDHE-PSK-WITH-AES-128-CBC-SHA',\n\t\t\t'TLS-PSK-WITH-CHACHA20-POLY1305-SHA256',\n\t\t\t'TLS-PSK-WITH-AES-256-GCM-SHA384',\n\t\t\t'TLS-PSK-WITH-AES-256-CBC-SHA384',\n\t\t\t'TLS-PSK-WITH-AES-256-CBC-SHA',\n\t\t\t'TLS-PSK-WITH-AES-128-GCM-SHA256',\n\t\t\t'TLS-PSK-WITH-AES-128-CBC-SHA256',\n\t\t\t'TLS-PSK-WITH-AES-128-CBC-SHA'\n\t\t]\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.DynamicList,\n\t\tname: 'tls_ciphersuites',\n\t\tlabel: _('TLS 1.3 or newer cipher'),\n\t\tlvalues: [\n\t\t\t'TLS_AES_256_GCM_SHA384',\n\t\t\t'TLS_AES_128_GCM_SHA256',\n\t\t\t'TLS_CHACHA20_POLY1305_SHA256'\n\t\t]\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'tls_timeout',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Retransmit timeout on TLS control channel'),\n\t\tplaceholder: 2\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'reneg_bytes',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Renegotiate data chan. key after bytes'),\n\t\tplaceholder: 1024\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'reneg_pkts',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Renegotiate data chan. key after packets'),\n\t\tplaceholder: 100\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'reneg_sec',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Renegotiate data chan. key after seconds'),\n\t\tplaceholder: 3600\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'hand_window',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Timeframe for key exchange'),\n\t\tplaceholder: 60\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'tran_window',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Key transition window'),\n\t\tplaceholder: 3600\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Flag,\n\t\tname: 'single_session',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Allow only one session'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Flag,\n\t\tname: 'tls_exit',\n\t\tdatatype: 'uinteger',\n\t\tlabel: _('Exit on TLS negotiation failure'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc/openvpn',\n\t\tname: 'tls_auth', label: _('Additional authentication over TLS'),\n\t\tplaceholder: '/etc/openvpn/tlsauth.key'\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc/openvpn',\n\t\tname: 'tls_crypt',\n\t\tlabel: _('Encrypt and authenticate all control channel packets with the key'),\n\t\tplaceholder: '/etc/openvpn/tlscrypt.key'\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc/openvpn',\n\t\tname: 'tls_crypt_v2',\n\t\tlabel: _('Encrypt and authenticate all control channel packets with the key, version 2.'),\n\t\tplaceholder: '/etc/openvpn/servertlscryptv2.key'\n\t},\n\t{\n\t\ttab: 'cryptography', \n\t\ttype: form.Flag,\n\t\tname: 'auth_nocache',\n\t\tlabel: _('Don\\'t cache --askpass or --auth-user-pass passwords'),\n\t\tdefault: 0\n\t},\n\t/*\n\t * {\n\t * \ttab: 'cryptography',\n\t * \ttype: form.Value,\n\t * \tname: 'tls_remote',\n\t * \tlabel: _('Only accept connections from given X509 name'),\n\t * \tplaceholder: 'remote_x509_name'\n\t * },\n\t */\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.ListValue,\n\t\tname: 'ns_cert_type',\n\t\tlvalues: [\n\t\t\t'client',\n\t\t\t'server'\n\t\t],\n\t\tlabel: _('Require explicit designation on certificate')\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'remote_cert_eku',\n\t\tlabel: _('Require remote cert extended key usage on certificate'),\n\t\tplaceholder: 'oid'\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'remote_cert_ku',\n\t\tlabel: _('Require explicit key usage on certificate'),\n\t\tplaceholder: 'a0'\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.ListValue,\n\t\tname: 'remote_cert_tls',\n\t\tlvalues: [\n\t\t\t'client',\n\t\t\t'server'\n\t\t],\n\t\tlabel: _('Require explicit key usage on certificate')\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.FileUpload,\n\t\troot_directory: '/etc/openvpn',\n\t\tname: 'crl_verify',\n\t\tlabel: _('Check peer certificate against a CRL'),\n\t\tplaceholder: '/etc/easy-rsa/keys/crl.pem'\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'tls_version_min',\n\t\tlabel: _('The lowest supported TLS version'),\n\t\tlvalues: [\n\t\t\t'1.2',\n\t\t\t'1.3'\n\t\t]\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'tls_version_max',\n\t\tlabel: _('The highest supported TLS version'),\n\t\tlvalues: [\n\t\t\t'1.2',\n\t\t\t'1.3'\n\t\t]\n\t},\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.Value,\n\t\tname: 'tls_cert_profile',\n\t\tlabel: _('TLS cet profile'),\n\t\tlvalues: [\n\t\t\t'insecure',\n\t\t\t'legacy',\n\t\t\t'preferred',\n\t\t\t'suiteb'\n\t\t]\n\t},\n\t/*\n\t * {\n\t * \ttab: 'cryptography',\n\t * \ttype: form.Flag,\n\t * \tname: 'ncp_disable',\n\t * \tlabel: _('This completely disables cipher negotiation'),\n\t * \tdefault: 0\n\t * },\n\t */\n\t/*\n\t * {\n\t * \ttab: 'cryptography',\n\t * \ttype: form.DynamicList,\n\t * \tname: 'ncp_ciphers',\n\t * \tlabel: _('Restrict the allowed ciphers to be negotiated'),\n\t * \tlvalues: [\n\t * \t\t'AES-256-GCM',\n\t * \t\t'AES-128-GCM'\n\t * \t]\n\t * },\n\t */\n\t{\n\t\ttab: 'cryptography',\n\t\ttype: form.DynamicList,\n\t\tname: 'data_ciphers',\n\t\tlabel: _('Restrict the allowed ciphers to be negotiated'),\n\t\tlvalues: [\n\t\t\t'CHACHA20-POLY1305',\n\t\t\t'AES-256-GCM',\n\t\t\t'AES-128-GCM',\n\t\t\t'AES-256-CBC'\n\t\t]\n\t},\n\n\t/* Push/Client */\n\t{\n\t\ttab: 'push_opt',\n\t\ttype: form.DynamicList,\n\t\tname: 'push',\n\t\tlabel: _('Push options to peer'),\n\t\tlvalues: ['redirect-gateway']\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\ttype: form.DynamicList,\n\t\tname: 'push_remove',\n\t\tlabel: _('Remove Push options'),\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\ttype: form.Flag,\n\t\tname: 'push_reset',\n\t\tlabel: _('Don\\'t inherit global push options'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Flag,\n\t\tname: 'disable',\n\t\tlabel: _('Client is disabled'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'ifconfig_pool',\n\t\tlabel: _('Set aside a pool of subnets')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'ifconfig_pool_persist',\n\t\tlabel: _('Persist/unpersist ifconfig-pool')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'ifconfig_push',\n\t\tlabel: _('Push an ifconfig option to remote')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'iroute',\n\t\tlabel: _('Route subnet to client')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'iroute_ipv6',\n\t\tlabel: _('Route v6 subnet to client')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Flag,\n\t\tname: 'duplicate_cn',\n\t\tlabel: _('Allow multiple clients with same certificate'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.DirectoryPicker,\n\t\troot_directory: '/etc/openvpn',\n\t\tdirectory_create: true,\n\t\tname: 'client_config_dir',\n\t\tlabel: _('Directory for custom client config files')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Flag,\n\t\tname: 'ccd_exclusive',\n\t\tlabel: _('Refuse connection if no custom client config'),\n\t\tdefault: 0\n\t},\n\t/*\n\t * {\n\t * \ttab: 'push_opt',\n\t * \tdepends: { server: \"\", \"!reverse\": true },\n\t * \ttype: form.DirectoryPicker,\n\t * \troot_directory: '/var/run',\n\t * \tdirectory_create: true,\n\t * \tname: 'tmp_dir',\n\t * \tlabel: _('Temporary directory for client-connect return file')\n\t * },\n\t */\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'hash_size',\n\t\tlabel: _('Set size of real and virtual address hash tables')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'bcast_buffers',\n\t\tlabel: _('Number of allocated broadcast buffers')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'tcp_queue_limit',\n\t\tlabel: _('Maximum number of queued TCP output packets')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'max_clients',\n\t\tlabel: _('Allowed maximum of connected clients')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'max_routes_per_client',\n\t\tlabel: _('Allowed maximum of internal')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'connect_freq',\n\t\tlabel: _('Allowed maximum of new connections')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Flag,\n\t\tname: 'username_as_common_name',\n\t\tlabel: _('Use username as common name'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Flag,\n\t\tname: 'pull',\n\t\tlabel: _('Accept options pushed from server'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'auth_user_pass',\n\t\tlabel: _('Authenticate using username/password')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'auth_retry',\n\t\tlabel: _('Handling of authentication failures')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'explicit_exit_notify',\n\t\tlabel: _('Send notification to peer on disconnect')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Flag,\n\t\tname: 'remote_random',\n\t\tlabel: _('Randomly choose remote server'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'connect_retry',\n\t\tlabel: _('Connection retry interval')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'http_proxy',\n\t\tlabel: _('Connect to remote host through an HTTP proxy')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Flag,\n\t\tname: 'http_proxy_retry',\n\t\tlabel: _('Retry indefinitely on HTTP proxy errors'),\n\t\tdefault: 0\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'http_proxy_timeout',\n\t\tlabel: _('Proxy timeout in seconds')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.DynamicList,\n\t\tname: 'http_proxy_option',\n\t\tlabel: _('Set extended HTTP proxy options')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'socks_proxy',\n\t\tlabel: _('Connect through Socks5 proxy')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'socks_proxy_retry',\n\t\tlabel: _('Retry indefinitely on Socks proxy errors')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'resolv_retry',\n\t\tlabel: _('If hostname resolve fails, retry')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'redirect_gateway',\n\t\tlabel: _('Automatically redirect default route')\n\t},\n\t{\n\t\ttab: 'push_opt',\n\t\tdepends: { server: \"\", \"!reverse\": true },\n\t\ttype: form.Value,\n\t\tname: 'verify_client_cert',\n\t\tlabel: _('Specify whether the client is required to supply a valid certificate')\n\t},\n\n];\n\n// Tabs for UI\nvar tabs = [\n\t{ id: 'basic', label: _('Basic Settings') },\n\t{ id: 'file', label: _('Config File') },\n\t{ id: 'general' },\n\t{ id: 'cryptography', label: _('Cryptography') },\n\t{ id: 'devices', label: _('Devices') },\n\t{ id: 'expert', label: _('Expert Settings') },\n\t{ id: 'keygen', label: _('Keygen') },\n\t{ id: 'logging', label: _('Logging') },\n\t{ id: 'management', label: _('Management') },\n\t{ id: 'networking', label: _('Networking') },\n\t{ id: 'push_opt', label: _('Push') },\n\t{ id: 'scripts', label: _('Scripting') },\n\t{ id: 'service', label: _('Service') },\n\t{ id: 'topology', label: _('Topology') },\n];\n\nfunction renderOpenVPNOptions(s, tabId) {\n\n\topenvpnOptions.filter(function(opt) { return opt.tab === tabId; }).forEach(function(opt) {\n\t\tlet o = s.taboption(tabId, opt.type, opt.name, opt.name, opt.label);\n\t\tif (opt.values) {\n\t\t\t// index based values (o.value(i, string))\n\t\t\topt.values.forEach((v, i) => o.value(i, v));\n\t\t} else if (opt.lvalues) {\n\t\t\t// literal values: the text string is the value\n\t\t\topt.lvalues.forEach((v) => o.value(v));\n\t\t}\n\t\t// Copy any extra properties to the widget\n\t\tObject.keys(opt).forEach(function(key) {\n\t\t\tif (!['tab', 'type', 'name', 'label', 'values', 'lvalues'].includes(key)) {\n\t\t\t\tif (key === 'depends')\n\t\t\t\t\to.depends(opt[key])\n\t\t\t\telse\n\t\t\t\t\to[key] = opt[key];\n\t\t\t}\n\t\t});\n\t\to.optional = true;\n\t});\n}\n\nnetwork.registerPatternVirtual(/^openvpn-.+$/);\n\nreturn network.registerProtocol('openvpn', {\n\tgetI18n: function() {\n\t\treturn _('OpenVPN');\n\t},\n\n\tgetIfname: function() {\n\t\treturn this._ubus('l3_device') || this.sid;\n\t},\n\n\tgetPackageName: function() {\n\t\treturn 'openvpn-openssl';\n\t},\n\n\tisFloating: function() {\n\t\treturn true;\n\t},\n\n\tisVirtual: function() {\n\t\treturn true;\n\t},\n\n\tgetDevices: function() {\n\t\treturn null;\n\t},\n\n\tcontainsDevice: function(ifname) {\n\t\treturn (network.getIfnameOf(ifname) == this.getIfname());\n\t},\n\n\trenderFormOptions: function(s) {\n\n\t\tlet o, kt, sk, krp, krc, clm, ovconf;\n\t\t// Add main tabs\n\t\ttabs.forEach(function(tab) {\n\t\t\tif (!s.tabs[tab.id])\n\t\t\t\ts.tab(tab.id, tab.label);\n\t\t});\n\n\t\to = s.taboption('general', form.DummyValue, '_dummy');\n\t\to.rawhtml = true;\n\t\to.default = _('Almost nothing here prevents you from selecting invalid configuration options which prevent openvpn from starting. Read the manual.') + '<br/>' + \n\t\t\t_('Options marked with ¹ are deprecated and will be removed.') /* + '<br/>' +\n\t\t\t_('Options marked with * are server only.') */ + '<br/>' +\n\t\t\t_('Options marked with ² are OpenSSL only.');\n\n\t\t// Render options for each tab\n\t\ttabs.forEach(function(tab) {\n\t\t\trenderOpenVPNOptions(s, tab.id);\n\t\t});\n\n\t\tkt = s.taboption('keygen', form.ListValue, '_keytype', _('Type'));\n\t\tkt.default = 'secret';\n\t\tkt.value('secret');\n\t\tkt.value('tls-crypt');\n\t\tkt.value('tls-auth');\n\t\tkt.value('auth-token');\n\t\tkt.value('tls-crypt-v2-server');\n\t\tkt.value('tls-crypt-v2-client');\n\t\tkt.write = function(section_id, value) {};\n\n\t\tsk = s.taboption('keygen', form.Value, '_skey', _('Server key'));\n\t\tcallGetSKeys(s.map.section).then(keys => {\n\t\t\tif (keys.skeys) {\n\t\t\t\tfor (let key of keys.skeys)\n\t\t\t\t\tsk.value(key);\n\t\t\t}\n\t\t});\n\n\t\tclm = s.taboption('keygen', form.TextValue, '_cmeta', _('Client metadata'),\n\t\t\t_('Freeform metadata to embed into the client key'));\n\t\tclm.depends('_keytype', 'tls-crypt-v2-client');\n\t\tclm.default = '';\n\t\tclm.placeholder = '{\"cn\":\"alice\",\"exp\":1735689600}';\n\t\tclm.monospace = true;\n\t\tclm.rows = 10;\n\t\tclm.wrap = 90;\n\n\t\to = s.taboption('keygen', form.Button, '_keygen', _('Generate'));\n\t\to.onclick = L.bind(function(ev, sid) {\n\t\t\tconst ktype = kt.formvalue(sid);\n\t\t\tconst svk = sk.formvalue(sid);\n\t\t\tconst clmeta = clm.formvalue(sid);\n\n\t\t\tcallGenKey({ ifname: sid, keytype: ktype, server_key: svk ?? '', cl_meta: btoa(clmeta) }).then(result => {\n\t\t\t\tconst path_output = krp.getUIElement(sid);\n\t\t\t\tconst cont_output = krc.getUIElement(sid);\n\t\t\t\tpath_output.setValue(result.path);\n\t\t\t\tcont_output.setValue(result.content);\n\t\t\t\tif (ktype === 'tls-crypt-v2-server') {\n\t\t\t\t\tconst skv = sk.getUIElement(sid);\n\t\t\t\t\tskv.setValue(result.filename);\n\t\t\t\t}\n\t\t\t});\n\t\t}, this);\n\n\t\tkrp = s.taboption('keygen', form.TextValue, '_pathresult', _('Path'));\n\t\tkrp.default = '';\n\t\tkrp.monospace = true;\n\t\tkrp.readonly = true;\n\t\tkrp.readonly = true;\n\t\tkrp.rows = 2;\n\t\tkrp.wrap = 90;\n\t\tkrp.write = function(sid, value) { return; };\n\n\t\tkrc = s.taboption('keygen', form.TextValue, '_contentresult');\n\t\tkrc.default = '';\n\t\tkrc.monospace = true;\n\t\tkrc.readonly = true;\n\t\tkrc.rows = 10;\n\t\tkrc.wrap = 90;\n\t\tkrc.write = function(sid, value) { return; };\n\n\t\to = s.taboption('file', form.Button, '_clear', _('Clear'));\n\t\to.onclick = L.bind(function(ev, sid) {\n\t\t\tconst config_name = `/etc/openvpn/${sid}/${sid}_config.cfg`;\n\t\t\tlet v = ovconf.getUIElement(sid);\n\t\t\t// remove file, clear field, mark it changed and save\n\t\t\tfs.remove(config_name).catch(() => {}).then(() => {\n\t\t\t\ttry {\n\t\t\t\t\tv.setValue('');\n\t\t\t\t\tif (v && v.node)\n\t\t\t\t\t\tv.node.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t} catch (e) {}\n\t\t\t\tuci.unset(this.config, sid, 'config');\n\t\t\t\tif (s && s.map)\n\t\t\t\t\ts.map.save(null, true);\n\t\t\t});\n\t\t}, this);\n\n\t\tovconf = s.taboption('file', form.TextValue, 'ovpn_config', _('Raw OVPN config'));\n\t\tovconf.rows = 20;\n\t\tovconf.optional = true;\n\t\tovconf.placeholder = _('Drag and drop an ovpn config file here');\n\n\t\t// Attach drag-and-drop handler for file import\n\t\tfunction attachDragDrop(sid) {\n\n\t\t\ttry {\n\t\t\t\t// If the map root is not yet available, retry a few times with back-off\n\t\t\t\tovconf._attach_attempts = ovconf._attach_attempts || {};\n\t\t\t\tovconf._attach_attempts[sid] = (ovconf._attach_attempts[sid] || 0) + 1;\n\t\t\t\tif (!s || !s.map || !s.map.root) {\n\t\t\t\t\tif (ovconf._attach_attempts[sid] < 10) {\n\t\t\t\t\t\tsetTimeout(() => attachDragDrop(sid), 1000);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst w = ovconf.getUIElement(sid);\n\t\t\t\tif (!w || !w.node || w._ovpn_drop_attached) {\n\t\t\t\t\tif (ovconf._attach_attempts[sid] < 10)\n\t\t\t\t\t\tsetTimeout(() => attachDragDrop(sid), 1000);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst ta = w.node.firstElementChild;\n\t\t\t\tif (!ta) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tta.addEventListener('dragover', ev => {\n\t\t\t\t\tev.preventDefault();\n\t\t\t\t\tev.dataTransfer.dropEffect = 'copy';\n\t\t\t\t\tta.classList.add('cbi-dragover');\n\t\t\t\t});\n\n\t\t\t\tta.addEventListener('dragleave', ev => {\n\t\t\t\t\tev.preventDefault();\n\t\t\t\t\tta.classList.remove('cbi-dragover');\n\t\t\t\t});\n\n\t\t\t\tta.addEventListener('drop', ev => {\n\t\t\t\t\tev.preventDefault();\n\t\t\t\t\tev.stopPropagation();\n\t\t\t\t\tta.classList.remove('cbi-dragover');\n\n\t\t\t\t\tconst files = ev.dataTransfer && (ev.dataTransfer.files || ev.dataTransfer.items);\n\n\t\t\t\t\tlet file = files && files[0];\n\t\t\t\t\tif (files && files[0] && files[0].kind === 'file' && files[0].getAsFile)\n\t\t\t\t\t\tfile = files[0].getAsFile();\n\t\t\t\t\tif (!file) {\n\t\t\t\t\t\t// fallback: try plain text data\n\t\t\t\t\t\tconst text = ev.dataTransfer && (ev.dataTransfer.getData && (ev.dataTransfer.getData('text') || ev.dataTransfer.getData('text/plain')));\n\n\t\t\t\t\t\tif (text) {\n\t\t\t\t\t\t\tw.setValue(text);\n\t\t\t\t\t\t\tw.node.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\treader.onload = function() {\n\t\t\t\t\t\tconst content = reader.result;\n\n\t\t\t\t\t\tw.setValue(content);\n\t\t\t\t\t\tw.node.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t\t};\n\t\t\t\t\treader.onerror = function(err) {\n\t\t\t\t\t\tconsole.error('ovpn file read error', err);\n\t\t\t\t\t};\n\t\t\t\t\treader.readAsText(file);\n\t\t\t\t});\n\n\t\t\t\tw._ovpn_drop_attached = true;\n\n\t\t\t} catch (e) { }\n\t\t}\n\n\t\tovconf.cfgvalue = function(sid) {\n\t\t\tattachDragDrop(sid);\n\t\t\tconst config_name = `/etc/openvpn/${sid}/${sid}_config.cfg`;\n\t\t\treturn fs.read(config_name).then(readresult => {\n\t\t\t\tattachDragDrop(sid);\n\t\t\t\tif (readresult == null || readresult === '') {\n\t\t\t\t\tuci.unset(this.config, sid, 'config');\n\t\t\t\t\treturn '';\n\t\t\t\t}\n\t\t\t\tuci.set(this.config, sid, 'config', config_name);\n\t\t\t\treturn readresult;\n\t\t\t}).catch(() => {\n\t\t\t\tattachDragDrop(sid);\n\t\t\t\tuci.unset(this.config, sid, 'config');\n\t\t\t\treturn '';\n\t\t\t});\n\t\t};\n\t\tovconf.write = function(sid, value) {\n\t\t\tconst config_name = `/etc/openvpn/${sid}/${sid}_config.cfg`;\n\t\t\treturn fs.exec('/bin/mkdir', ['-p', `/etc/openvpn/${sid}/`]).then(() => {\n\t\t\t\treturn fs.write(config_name, value);\n\t\t\t}).then(() => {\n\t\t\t\ttry {\n\t\t\t\t\tuci.set(this.config, sid, 'config', config_name);\n\t\t\t\t} catch (err) {}\n\t\t\t});\n\t\t};\n\t\tovconf.rmempty = true;\n\t},\n\n\tdeleteConfiguration: function() {\n\t\tuci.sections('network', 'openvpn_%s'.format(this.sid), function(s) {\n\t\t\tuci.remove('network', s['.name']);\n\t\t});\n\t}\n});\n"
  },
  {
    "path": "luci/protocols/luci-proto-openvpn/root/usr/share/rpcd/acl.d/luci-proto-openvpn.json",
    "content": "{\n\t\"luci-proto-openvpn\": {\n\t\t\"description\": \"Grant access to LuCI openvpn procedures\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/bin/mkdir -p /etc/openvpn/*\": [ \"exec\" ],\n\t\t\t\t\"/etc/openvpn/*\": [ \"read\" ]\n\t\t\t},\n\t\t\t\"ubus\": {\n\t\t\t\t\"luci.openvpn\": [\n\t\t\t\t\t\"*\"\n\t\t\t\t]\n\t\t\t},\n\t\t\t\"uci\": [ \"network\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/openvpn/*\": [ \"write\", \"remove\" ]\n\t\t\t},\n\t\t\t\"ubus\": {\n\t\t\t\t\"luci.openvpn\": [\n\t\t\t\t\t\"*\"\n\t\t\t\t]\n\t\t\t},\n\t\t\t\"uci\": [ \"network\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "luci/protocols/luci-proto-openvpn/root/usr/share/rpcd/ucode/luci.openvpn.uc",
    "content": "#!/usr/bin/env ucode\n\n'use strict';\n\nimport { stdin, access, chmod, dirname, basename, open, popen, glob, lsdir, readfile, readlink, error, mkdir } from 'fs';\nimport { cursor } from 'uci';\nimport { connect } from 'ubus';\n\n\nconst openvpn_dir = '/etc/openvpn';\n\nfunction shellquote(s) {\n\treturn `'${replace(s, \"'\", \"'\\\\''\")}'`;\n}\n\nfunction command(cmd) {\n\treturn trim(popen(cmd)?.read?.('all'));\n}\n\nfunction makedirs(path) {\n\tconst parts = split(path, '/');\n\tconst grow = [];\n\tfor(let i = 0; i < length(parts); i++) {\n\t\tpush(grow, parts[i]);\n\t\tmkdir(join('/', grow));\n\t}\n\treturn join('/', grow);\n}\n\nfunction keyDir(ifname, kt) {\n\treturn `${openvpn_dir}/${ifname}/${kt}`;\n}\n\nconst methods = {\n\tgenerateKey: {\n\t\targs: { keytype: 'keytype', ifname: 'ifname', server_key: 'server_key', cl_meta: 'c_metadata' },\n\t\tcall: function(req) {\n\t\t\tconst kt = req.args?.keytype;\n\t\t\tconst ifname = req.args?.ifname || 'unnamed';\n\t\t\tconst ts = time();\n\n\t\t\tif (!kt) return { error: 'missing keytype' };\n\n\t\t\tlet dir;\n\t\t\tlet outfile = `${ifname}_${kt}_${ts}.key`;\n\n\t\t\tlet mkpath = makedirs(`${keyDir(ifname, kt)}`);\n\t\t\tlet path = `${mkpath}/${outfile}`;\n\n\t\t\t// find openvpn binary\n\t\t\tconst openvpn = trim(popen('command -v openvpn 2>/dev/null')?.read?.('all'));\n\t\t\tif (!length(openvpn)) return { error: 'openvpn binary not found' };\n\n\t\t\tlet cmd;\n\t\t\tif (kt == 'tls-crypt-v2-client') {\n\t\t\t\t// client generation needs server key\n\t\t\t\tlet server_key = req.args?.server_key;\n\t\t\t\tif (!server_key) {\n\t\t\t\t\t// try to pick latest server key for same interface\n\t\t\t\t\tconst serverDir = `${keyDir(ifname, 'tls-crypt-v2-server')}`;\n\t\t\t\t\tconst list = lsdir(serverDir);\n\t\t\t\t\tif (length(list) > 0) server_key = serverDir + '/' + list[-1];\n\t\t\t\t} else {\n\t\t\t\t\tserver_key = `${keyDir(ifname, 'tls-crypt-v2-server')}/${req.args?.server_key}`;\n\t\t\t\t}\n\n\t\t\t\tif (!server_key) return { error: 'missing server_key for tls-crypt-v2-client' };\n\n\t\t\t\t// denote which server key this client key is derived from in the name\n\t\t\t\tpath = `${mkpath}/${ifname}_${kt}_${ts}-${req.args?.server_key}`;\n\t\t\t\tcmd = `${openvpn} --tls-crypt-v2 ${shellquote(server_key)} --genkey tls-crypt-v2-client ${shellquote(path)} ${req.args?.cl_meta} 2>/dev/null`;\n\t\t\t} else {\n\t\t\t\t// basic genkey\n\t\t\t\tcmd = `${openvpn} --genkey ${kt} ${shellquote(path)} 2>/dev/null`;\n\t\t\t}\n\n\t\t\tconst out = popen(cmd)?.read?.('all') || '';\n\n\t\t\t// ensure permissions\n\t\t\tchmod(path, 0o600);\n\n\t\t\tlet content = '';\n\t\t\ttry { content = readfile(path); } catch (e) { /* ignore */ }\n\n\t\t\tif (!length(content)) {\n\t\t\t\treturn { error: 'failed to generate key', cmd_output: out };\n\t\t\t}\n\n\t\t\treturn { path, filename: outfile, content };\n\t\t}\n\t},\n\n\tgetSKeys: {\n\t\targs: { ifname: 'ifname' },\n\t\tcall: function(req) {\n\t\t\tconst serverDir = `${keyDir(req.args?.ifname, 'tls-crypt-v2-server')}`;\n\t\t\tconst list = lsdir(serverDir);\n\n\t\t\treturn { skeys: list, path: serverDir };\n\t\t}\n\t}\n};\n\nreturn { 'luci.openvpn': methods };\n"
  },
  {
    "path": "luci/protocols/luci-proto-tun2socks/Makefile",
    "content": "#\n# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Support for tun2socks\nLUCI_DEPENDS:=+tun2socks\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/protocols/luci-proto-tun2socks/htdocs/luci-static/resources/protocol/t2s.js",
    "content": "'use strict';\n'require rpc';\n'require form';\n'require network';\n'require validation';\n\nnetwork.registerPatternVirtual(/^t2s-.+$/);\n\nreturn network.registerProtocol('t2s', {\n\tgetI18n: function() {\n\t\treturn _('tun2socks');\n\t},\n\n\tgetIfname: function() {\n\t\treturn this._ubus('l3_device') || 't2s-%s'.format(this.sid);\n\t},\n\n\tgetOpkgPackage: function() {\n\t\treturn 'tun2socks';\n\t},\n\n\tisFloating: function() {\n\t\treturn true;\n\t},\n\n\tisVirtual: function() {\n\t\treturn true;\n\t},\n\n\tgetDevices: function() {\n\t\treturn null;\n\t},\n\n\tcontainsDevice: function(ifname) {\n\t\treturn (network.getIfnameOf(ifname) == this.getIfname());\n\t},\n\n\trenderFormOptions: function(s) {\n\t\tvar dev = this.getL3Device() || this.getDevice(), o;\n\n\n                o = s.taboption('general', form.ListValue, 'proxy', _('Proxy Type'));\n\t\to.value('http', 'HTTP');\n                o.value('socks4', 'SOCKS4');\n                o.value('socks5', 'SOCKS5');\n                o.value('ss', 'Shadowsocks');\n                o.value('relay', _('Relay'));\n                o.value('direct', _('Direct'));\n                o.value('reject', _('Reject'));\n\t\to.default = 'socks5';\n                o.rmempty = true;\n\t\t\n\t\t// TODO\n\t\to = s.taboption('general', form.Flag, 'socket', _('Use Socket'), _('SOCKS5 only!<br />Use Unix Domain Socket instead address'));\n\t\to.rmempty = true;\n\n                o = s.taboption('general', form.Value, 'host', _('Proxy Address'), _('IP-address or FQDN hostname proxy<br/>Format: <code>host:port</code>'));\n                o.datatype = 'or(hostport,ipaddrport)';\n                o.depends({'socket': '0', 'proxy': /http|socks4|socks5|relay|ss/ });\n                o.rmempty = true;\n\n\t\to = s.taboption('general', form.Value, 'sockpath', _('Unix Socket'), _('Path to Unix Socket<br/>Format: <code>/path/to/unix.socket</code>'));\n\t\to.depends({'socket': '1' , 'proxy': 'socks5' });\n\n\t\to = s.taboption('general', form.Flag, 'advanced', _('Autentification'), _('Authentification and encryption.'));\n\t\to.depends({'proxy': /socks4|socks5|relay|ss/ });\n\t\to.rmempty = true;\n\n\t\to = s.taboption('general', form.Value, 'username', _('Proxy USER'));\n\t\to.depends({'advanced': '1', 'proxy': /socks|relay/});\n\n\t\to = s.taboption('general', form.ListValue, 'encrypt', _('Encryption'));\n\t\to.value('none','none');\n\t\to.value('table','table');\n\t\to.value('rc4','rc4');\n\t\to.value('rc4-md5','rc4-md5');\n\t\to.value('aes-128-cfb','aes-128-cfb');\n\t\to.value('aes-192-cfb','aes-192-cfb');\n\t\to.value('aes-256-cfb','aes-256-cfb');\n\t\to.value('aes-128-ctr','aes-128-ctr');\n\t\to.value('aes-192-ctr','aes-192-ctr');\n\t\to.value('aes-256-ctr','aes-256-ctr');\n\t\to.value('aes-128-gcm','aes-128-gcm');\n\t\to.value('aes-192-gcm','aes-192-gcm');\n\t\to.value('aes-256-gcm','aes-256-gcm');\n\t\to.value('camellia-128-cfb','camellia-128-cfb');\n\t\to.value('camellia-192-cfb','camellia-192-cfb');\n\t\to.value('camellia-256-cfb','camellia-256-cfb');\n\t\to.value('bf-cfb','bf-cfb');\n\t\to.value('salsa20','salsa20');\n\t\to.value('chacha20','chacha20');\n\t\to.value('chacha20-ietf','chacha20-ietf');\n\t\to.value('chacha20-ietf-poly1305','chacha20-ietf-poly1305');\n\t\to.value('xchacha20-ietf-poly1305','xchacha20-ietf-poly1305');\n\t\to.depends({advanced: '1', proxy: 'ss'});\n\n\t\to = s.taboption('general', form.Value, 'password', _('Proxy Password'));\n\t\to.password = true;\n\t\to.depends({'advanced': '1', 'proxy': /socks5|relay|ss/ });\n\n\t\to = s.taboption('general',form.Flag, 'base64enc', _('Encrypt base64'));\n\t\to.depends({'advanced': '1', 'proxy': 'ss' });\n\t\to.rmempty = true;\n\n                o = s.taboption('general', form.Flag, 'ip_manual', _('Assigh Address Automatically'));\n                o.enabled = '';\n                o.disabled = '1';\n                o.default = '';\n\n                o = s.taboption('general', form.Value, 'network', _('Select class net by assign Address'), _('By default is <code>10.0.0.0/8</code>'));\n                o.value('10.0.0.0/8', '10.0.0.0/8');\n                o.value('172.16.0.0/12', '172.16.0.0/12');\n                o.value('192.168.0.0/16', '192.168.0.0/16');\n\t\to.default = '10.0.0.0/8';\n\t\to.depends({'ip_manual': ''});\n\n                o = s.taboption('general', form.Value, 'ipaddr', _('IPv4 Address'));\n                o.datatype = 'ip4addr(\"nomask\")';\n                o.depends({'ip_manual': '1', 'proxy': /http|socks4|socks5|relay|ss/ });\n                o.rmempty = true;\n\n                o = s.taboption('general', form.Value, 'netmask', _('IPv4 Netmask'));\n                o.value('255.255.255.0', '255.255.255.0');\n                o.value('255.255.0.0', '255.255.0.0');\n                o.depends({'ip_manual': '1', 'proxy': /http|socks4|socks5|relay|ss/ });\n                o.rmempty = true;\n\n                o = s.taboption('general', form.Value, 'gateway', _('IPv4 Gateway'));\n                o.datatype = 'ip4addr(\"nomask\")';\n                o.depends({'ip_manual': '1', 'proxy': /http|socks4|socks5|relay|ss/ });\n                o.rmempty = true;\n\n\t\to = s.taboption('advanced', form.Value, 'mtu', _('Set MTU'), _('Set device maximum transmission unit'));\n\t\to.placeholder = dev ? (dev.getMTU() || '1500') : '1500';\n\t\to.datatype    = 'max(9200)';\n\t\t\n\t\to = s.taboption('advanced', form.ListValue, 'loglevel', _('Logging level'));\n\t\to.value('debug', _('Debug'));\n\t\to.value('info', _('Info'));\n\t\to.value('warning', _('Warning'));\n\t\to.value('error', _('Error'));\n\t\to.value('silent', _('Silent'));\n\t\to.default = 'error';\n\t\t\n\t\to = s.taboption('advanced', form.Value, 'opts', _('Advaced options'), _('Command line arguments to tun2socks app'));\n\t\to.rmempty = true;\n\n\t\to = s.taboption('advanced', form.Flag, 'defaultroute',\n\t\t\t_('Use default gateway'),\n\t\t\t_('If unchecked, no default route is configured'));\n\t\to.default = o.enabled;\n\n\t\to = s.taboption('advanced', form.Value, 'metric',\n\t\t\t_('Use gateway metric'));\n\t\t\to.placeholder = '0';\n\t\to.datatype = 'uinteger';\n\t\to.depends('defaultroute', '1');\n\n\t\to = s.taboption('advanced', form.Flag, 'peerdns',\n\t\t\t_('Use DNS servers advertised by peer'),\n\t\t\t_('If unchecked, the advertised DNS server addresses are ignored'));\n\t\to.default = o.enabled;\n\n\t}\n});\n\n"
  },
  {
    "path": "luci/protocols/luci-proto-xmm/Makefile",
    "content": "#\n# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Support for Intel XMM\nLUCI_DEPENDS:=+xmm-modem\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/protocols/luci-proto-xmm/README.md",
    "content": "# luci-proto-xmm\nThe OpenWrt Luci protocol handler to configure Intel cellular XMM modems.\n\n<details>\n   <summary>Screenshots</summary>\n\n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/protocols/luci-proto-xmm/screenshots/main.png)\n\n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/protocols/luci-proto-xmm/screenshots/interfaces.png)\n\n   ![](https://raw.githubusercontent.com/koshev-msk/modemfeed/master/luci/protocols/luci-proto-xmm/screenshots/setup.png)\n\n</details>\n"
  },
  {
    "path": "luci/protocols/luci-proto-xmm/htdocs/luci-static/resources/protocol/xmm.js",
    "content": "'use strict';\n'require rpc';\n'require form';\n'require network';\n\nvar callFileList = rpc.declare({\n\tobject: 'file',\n\tmethod: 'list',\n\tparams: [ 'path' ],\n\texpect: { entries: [] },\n\tfilter: function(list, params) {\n\t\tvar rv = [];\n\t\tfor (var i = 0; i < list.length; i++)\n\t\t\tif (list[i].name.match(/^ttyACM/) || list[i].name.match(/^ttyUSB/))\n\t\t\t\trv.push(params.path + list[i].name);\n\t\treturn rv.sort();\n\t}\n});\n\nnetwork.registerPatternVirtual(/^xmm-.+$/);\nnetwork.registerErrorCode('NO_DEVICE_SUPPORT', _('Unsupported modem'));\nnetwork.registerErrorCode('NO_PORT_FOUND', _('No control device specified'));\nnetwork.registerErrorCode('NO_PORT_ANSWER', _('Failed to get modem information'));\nnetwork.registerErrorCode('NO_DEVICE_FOUND', _('Failed to initialize modem'));\nnetwork.registerErrorCode('NO_IFACE', _('The interface could not be found'));\nnetwork.registerErrorCode('NO_SIM_CARD', _('SIM-card not insert!'));\nnetwork.registerErrorCode('CONFIGURE_FAILED', _('Failed to configure modem'));\n\n\nreturn network.registerProtocol('xmm', {\n\tgetI18n: function() {\n\t\treturn _('Intel XMM Cellular');\n\t},\n\n\tgetIfname: function() {\n\t\treturn this._ubus('l3_device') || 'xmm-%s'.format(this.sid);\n\t},\n\n\tgetOpkgPackage: function() {\n\t\treturn 'xmm-modem';\n\t},\n\n\tisFloating: function() {\n\t\treturn true;\n\t},\n\n\tisVirtual: function() {\n\t\treturn true;\n\t},\n\n\tgetDevices: function() {\n\t\treturn null;\n\t},\n\n\tcontainsDevice: function(ifname) {\n\t\treturn (network.getIfnameOf(ifname) == this.getIfname());\n\t},\n\n\trenderFormOptions: function(s) {\n\t\tvar dev = this.getL3Device() || this.getDevice(), o;\n\n\t\to = s.taboption('general', form.Value, 'device', _('Modem port'));\n\t\to.ucioption = 'device';\n\t\to.rmempty = false;\n\t\to.load = function(section_id) {\n\t\t\treturn callFileList('/dev/').then(L.bind(function(devices) {\n\t\t\t\tfor (var i = 0; i < devices.length; i++)\n\t\t\t\t\tthis.value(devices[i]);\n\t\t\t\treturn form.Value.prototype.load.apply(this, [section_id]);\n\t\t\t}, this));\n\t\t};\n\n\t\to = s.taboption('general', form.Value, 'apn', _('APN'));\n\t\to.validate = function(section_id, value) {\n\t\t\tif (value == null || value == '')\n\t\t\t\treturn true;\n\n\t\t\tif (!/^[a-zA-Z0-9\\-.]*[a-zA-Z0-9]$/.test(value))\n\t\t\t\treturn _('Invalid APN provided');\n\n\t\t\treturn true;\n\t\t};\n\n\t\to = s.taboption('general', form.Value, 'pincode', _('PIN'));\n\t\to.datatype = 'and(uinteger,minlength(4),maxlength(8))';\n\n\t\to = s.taboption('general', form.Value, 'username', _('PAP/CHAP username'));\n\n\t\to = s.taboption('general', form.Value, 'password', _('PAP/CHAP password'));\n\n\t\to = s.taboption('general', form.ListValue, 'auth', _('Auth Type'));\n\t\to.value('auto', 'Auto');\n\t\to.value('pap', 'PAP');\n\t\to.value('chap', 'CHAP');\n\t\to.default = 'auto';\n\n\t\to = s.taboption('advanced', form.Value, 'delay', _('Modem init timeout'), _('Maximum amount of seconds to wait for the modem to become ready'));\n\t\to.placeholder = '10';\n\t\to.datatype    = 'min(1)';\n\n\t\to = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));\n\t\to.placeholder = dev ? (dev.getMTU() || '1500') : '1500';\n\t\to.datatype    = 'max(9200)';\n\n\t\to = s.taboption('general', form.ListValue, 'pdp', _('PDP Type'));\n\t\to.value('ipv4v6', 'IPv4/IPv6');\n\t\to.value('ip', 'IPv4');\n\t\to.value('ipv6', 'IPv6');\n\t\to.default = 'ipv4v6';\n\n\t\to = s.taboption('advanced', form.Flag, 'defaultroute',\n\t\t\t_('Use default gateway'),\n\t\t\t_('If unchecked, no default route is configured'));\n\t\to.default = o.enabled;\n\n\t\to = s.taboption('advanced', form.Value, 'metric',\n\t\t\t_('Use gateway metric'));\n\t\t\to.placeholder = '0';\n\t\to.datatype = 'uinteger';\n\t\to.depends('defaultroute', '1');\n\n\t\to = s.taboption('advanced', form.Flag, 'peerdns',\n\t\t\t_('Use DNS servers advertised by peer'),\n\t\t\t_('If unchecked, the advertised DNS server addresses are ignored'));\n\t\to.default = o.enabled;\n\n\t}\n});\n\n"
  },
  {
    "path": "luci/themes/luci-theme-lightblue/Makefile",
    "content": "#\n# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Bootstrap lightblue Theme\nLUCI_DEPENDS:= +luci-lua-runtime\n\nPKG_LICENSE:=Apache-2.0\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/themes/luci-theme-lightblue/htdocs/luci-static/lightblue/cascade.css",
    "content": "/*!\n * LuCI Bootstrap Theme\n * Copyright 2012 Nut & Bolt\n * By David Menting <david@nut-bolt.nl>\n * Based on Bootstrap v1.4.0\n *\n * Copyright 2011 Twitter, Inc\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world @twitter by @mdo and @fat.\n */\n/* Reset.less\n * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here\tthat cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc).\n * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */\n\n:root {\n\t--background-color-delta-l-sign: -1;\n\t--background-color-h: 0;\n\t--background-color-s: 0%;\n\t--background-color-l: 100%;\n\n\t--background-color-high-hsl:\n\t\tvar(--background-color-h),\n\t\tvar(--background-color-s),\n\t\tvar(--background-color-l);\n\t--background-color-high: hsl(var(--background-color-high-hsl));\n\n\t--background-color-medium-hsl:\n\t\tvar(--background-color-h),\n\t\tvar(--background-color-s),\n\t\tcalc(var(--background-color-l) + var(--background-color-delta-l-sign) * calc(6 / 255 * 100%));\n\t--background-color-medium: hsl(var(--background-color-medium-hsl));\n\n\t--background-color-low-hsl:\n\t\tvar(--background-color-h),\n\t\tvar(--background-color-s),\n\t\tcalc(var(--background-color-l) + var(--background-color-delta-l-sign) * calc(10 / 255 * 100%));\n\t--background-color-low: hsl(var(--background-color-low-hsl));\n\n\t--text-color-delta-l-sign: 1;\n\t--text-color-h: 0;\n\t--text-color-s: 0%;\n\t--text-color-l: 0%;\n\n\t--text-color-highest-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tvar(--text-color-l);\n\t--text-color-highest: hsl(var(--text-color-highest-hsl));\n\n\t--text-color-high-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tcalc(var(--text-color-l) + var(--text-color-delta-l-sign) * calc(64 / 255 * 100%));\n\t--text-color-high: hsl(var(--text-color-high-hsl));\n\n\t--text-color-medium-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tcalc(var(--text-color-l) + var(--text-color-delta-l-sign) * calc(128 / 255 * 100%));\n\t--text-color-medium: hsl(var(--text-color-medium-hsl));\n\n\t--text-color-low-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tcalc(var(--text-color-l) + var(--text-color-delta-l-sign) * calc(191 / 255 * 100%));\n\t--text-color-low: hsl(var(--text-color-low-hsl));\n\n\t--border-color-delta-l-sign: -1;\n\t--border-color-h: var(--background-color-h);\n\t--border-color-s: var(--background-color-s);\n\t--border-color-l: var(--background-color-l);\n\n\t--border-color-high-hsl:\n\t\tvar(--border-color-h),\n\t\tvar(--border-color-s),\n\t\tcalc(var(--border-color-l) + var(--border-color-delta-l-sign) * calc(51 / 255 * 100%));\n\t--border-color-high: hsl(var(--border-color-high-hsl));\n\n\t--border-color-medium-hsl:\n\t\tvar(--border-color-h),\n\t\tvar(--border-color-s),\n\t\tcalc(var(--border-color-l) + var(--border-color-delta-l-sign) * calc(34 / 255 * 100%));\n\t--border-color-medium: hsl(var(--border-color-medium-hsl));\n\n\t--border-color-low-hsl:\n\t\tvar(--border-color-h),\n\t\tvar(--border-color-s),\n\t\tcalc(var(--border-color-l) + var(--border-color-delta-l-sign) * calc(17 / 255 * 100%));\n\t--border-color-low: hsl(var(--border-color-low-hsl));\n\n\t--primary-color-high: #1976d2;\n\t--primary-color-medium: #1564c0;\n\t--primary-color-low: #0d46a1;\n\t--on-primary-color: var(--background-color-high);\n\n\t--error-color-high-rgb: 246, 43, 18;\n\t--error-color-high: rgb(var(--error-color-high-rgb));\n\t--error-color-medium: #e8210d;\n\t--error-color-low: #d00000;\n\t--on-error-color: var(--background-color-high);\n\n\t--success-color-high-rgb: 0, 172, 89;\n\t--success-color-high: rgb(var(--success-color-high-rgb));\n\t--success-color-medium: #009a4c;\n\t--success-color-low: #007936;\n\t--on-success-color: var(--background-color-high);\n\n\t--warn-color-high: #efbd0b;\n\t--warn-color-medium: #f0c629;\n\t--warn-color-low: #f2d24f;\n\t--on-warn-color: var(--text-color-highest);\n\n\t--disabled-opacity: .7;\n\n\tcolor-scheme: light;\n}\n\n:root[data-darkmode=\"true\"] {\n\t--background-color-delta-l-sign: 1;\n\t--background-color-h: 0;\n\t--background-color-s: 0%;\n\t--background-color-l: calc(34 / 255 * 100%);\n\t--text-color-delta-l-sign: -1;\n\t--text-color-h: 0;\n\t--text-color-s: 0%;\n\t--text-color-l: 100%;\n\t--border-color-delta-l-sign: 1;\n\n\t--primary-color-high: #4da1c0;\n\t--primary-color-medium: #448da6;\n\t--primary-color-low: #3c7a8d;\n\n\t--error-color-high-rgb: 209, 86, 83;\n\t--error-color-medium: #bf4e4c;\n\t--error-color-low: #b14946;\n\n\t--success-color-high-rgb: 0, 166, 108;\n\t--success-color-medium: #00945e;\n\t--success-color-low: #008252;\n\n\t--warn-color-high: #a69461;\n\t--warn-color-medium: #a68d45;\n\t--warn-color-low: #a68732;\n\t--on-warn-color: var(--background-color-high);\n\n\t--disabled-opacity: .4;\n\n\tcolor-scheme: dark;\n}\n\n* {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbox-sizing: border-box;\n}\n\nabbr[title], acronym[title] {\n\tborder-bottom: 1px dotted;\n\tfont-weight: inherit;\n\tcursor: help;\n}\n\ntable {\n\tborder-collapse: collapse;\n\tborder-spacing: 0;\n}\n\nol, ul {\n\tlist-style: none;\n}\n\nhtml {\n\tfont-size: 100%;\n\t-webkit-text-size-adjust: 100%;\n\t-ms-text-size-adjust: 100%;\n}\n\na:focus {\n\toutline: none;\n}\n\na:hover, a:active {\n\toutline: none;\n}\n\nfooter,\nheader,\nnav,\nsection {\n\tdisplay: block;\n}\n\nsub, sup {\n\tfont-size: 75%;\n\tline-height: 0;\n\tposition: relative;\n\tvertical-align: baseline;\n}\n\nsup {\n\ttop: -0.5em;\n}\n\nsub {\n\tbottom: -0.25em;\n}\n\nimg {\n\t-ms-interpolation-mode: bicubic;\n}\n\nbutton,\ninput,\nselect,\noption,\ntextarea {\n\tfont-size: 100%;\n\tmargin: 0;\n\tbox-sizing: border-box;\n\tvertical-align: baseline;\n\tline-height: normal;\n}\n\nbutton::-moz-focus-inner, input::-moz-focus-inner {\n\tborder: 0;\n\tpadding: 0;\n}\n\nbutton,\ninput[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n\tcursor: pointer;\n\t-webkit-appearance: button;\n\tword-break: break-all;\n}\n\nbutton[disabled],\ninput[type=\"button\"][disabled],\ninput[type=\"reset\"][disabled],\ninput[type=\"submit\"][disabled] {\n\topacity: 0.7;\n}\n\ninput[type=\"search\"] {\n\t-webkit-appearance: textfield;\n\tbox-sizing: content-box;\n}\n\ninput[type=\"search\"]::-webkit-search-decoration {\n\t-webkit-appearance: none;\n}\n\ntextarea {\n\toverflow: auto;\n\tvertical-align: top;\n}\n\n/*\n * Scaffolding\n * Basic and global styles for generating a grid system, structural layout, and page templates\n * ------------------------------------------------------------------------------------------- */\nbody {\n\tbackground-color: #fff;\n\tfont-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n\tfont-size: 13px;\n\tfont-weight: normal;\n\tline-height: 18px;\n\tcolor: #404040;\n\tpadding: 18px 5px 5px 5px;\n\tmargin-top: 40px;\n}\n\n.container {\n\twidth: 100%;\n\tmax-width: 940px;\n\tmargin-left: auto;\n\tmargin-right: auto;\n\tzoom: 1;\n}\n\n.container:before, .container:after {\n\tdisplay: table;\n\tcontent: \"\";\n\tzoom: 1;\n}\n\n.container:after {\n\tclear: both;\n}\n\na {\n\tcolor: #49729e;\n\ttext-decoration: none;\n\tline-height: inherit;\n\tfont-weight: inherit;\n}\n\na:hover {\n\tcolor: #B1266e3a;\n\ttext-decoration: underline;\n}\n\n.pull-right {\n\tfloat: right;\n}\n\n.pull-left {\n\tfloat: left;\n}\n\n.nowrap {\n\twhite-space: nowrap;\n}\n\n/* Typography.less\n * Headings, body text, lists, code, and more for a versatile and durable typography system\n * ---------------------------------------------------------------------------------------- */\np,\n.cbi-map-descr,\n.cbi-section-descr,\n.table .tr.cbi-section-table-descr .th {\n\tfont-size: 13px;\n\tfont-weight: normal;\n\tline-height: 18px;\n\tmargin-bottom: 9px;\n}\n\np small {\n\tfont-size: 11px;\n\tcolor: #bfbfbf;\n}\n\nh1,\nh2,\nh3, legend,\nh4,\nh5,\nh6 {\n\tfont-weight: bold;\n\tcolor: #49729e;\n}\n\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small {\n\tcolor: #bfbfbf;\n}\n\nh1 {\n\tmargin-bottom: 18px;\n\tfont-size: 30px;\n\tline-height: 36px;\n}\n\nh1 small {\n\tfont-size: 18px;\n}\n\nh2 {\n\tfont-size: 24px;\n\tline-height: 36px;\n}\n\nh2 small {\n\tfont-size: 14px;\n}\n\nh3, legend,\nh4,\nh5,\nh6 {\n\tline-height: 36px;\n}\n\nh3, legend {\n\tfont-size: 18px;\n}\n\nh3 small {\n\tfont-size: 14px;\n}\n\nh4 {\n\tfont-size: 16px;\n}\n\nh4 small {\n\tfont-size: 12px;\n}\n\nh5 {\n\tfont-size: 14px;\n}\n\nh6 {\n\tfont-size: 13px;\n\tcolor: #bfbfbf;\n\ttext-transform: uppercase;\n}\n\nul, ol {\n\tmargin: 0 0 18px 25px;\n}\n\nul ul,\nul ol,\nol ol,\nol ul {\n\tmargin-bottom: 0;\n}\n\nul {\n\tlist-style: disc;\n}\n\nol {\n\tlist-style: decimal;\n}\n\nli {\n\tline-height: 18px;\n\tcolor: #808080;\n}\n\nul.unstyled {\n\tlist-style: none;\n\tmargin-left: 0;\n}\n\ndl {\n\tmargin-bottom: 18px;\n}\n\ndl dt, dl dd {\n\tline-height: 18px;\n}\n\ndl dt {\n\tfont-weight: bold;\n}\n\ndl dd {\n\tmargin-left: 9px;\n}\n\nhr {\n\tmargin: 20px 0 19px;\n\tborder: 0;\n\tborder-bottom: 1px solid #eee;\n}\n\nstrong {\n\tfont-style: inherit;\n\tfont-weight: bold;\n}\n\nem {\n\tfont-style: italic;\n\tfont-weight: inherit;\n\tline-height: inherit;\n}\n\nsmall { font-size: 0.9em }\n\naddress {\n\tdisplay: block;\n\tline-height: 18px;\n\tmargin-bottom: 18px;\n}\n\ncode, pre {\n\tpadding: 0 3px 2px;\n\tfont-family: Monaco, Andale Mono, Courier New, monospace;\n\tfont-size: 12px;\n\tborder-radius: 3px;\n}\n\ncode {\n\tbackground-color: #6a89a6;\n\tcolor: rgba(0, 0, 0, 0.75);\n\tpadding: 1px 3px;\n}\n\npre {\n\tbackground-color: #f5f5f5;\n\tdisplay: block;\n\tpadding: 8.5px;\n\tmargin: 0 0 18px;\n\tline-height: 18px;\n\tfont-size: 12px;\n\tborder: 1px solid #ccc;\n\tborder: 1px solid rgba(0, 0, 0, 0.15);\n\tborder-radius: 3px;\n\twhite-space: pre;\n\twhite-space: pre-wrap;\n\tword-wrap: break-word;\n}\n\n/* Forms.less\n * Base styles for various input types, form layouts, and states\n * ------------------------------------------------------------- */\nform {\n\tmargin-bottom: 18px;\n}\n\nfieldset {\n\tmargin-bottom: 9px;\n\tpadding-top: 9px;\n}\n\nfieldset legend {\n\tdisplay: block;\n\tfont-size: 19.5px;\n\tline-height: 1;\n\tcolor: #404040;\n\tpadding-top: 20px;\n\n}\nform .cbi-tab-descr {\n\tline-height: 18px;\n\tmargin-bottom: 18px;\n}\n\nform .clearfix,\n.cbi-value {\n\tmargin-bottom: 18px;\n\tzoom: 1;\n}\n\nform .clearfix:before, form .clearfix:after,\n.cbi-value:before, .cbi-value:after  {\n\tdisplay: table;\n\tcontent: \"\";\n\tzoom: 1;\n}\n\nform .clearfix:after,\n.cbi-value:after {\n\tclear: both;\n}\n\nlabel,\ninput,\nbutton,\nselect,\ntextarea {\n\tfont-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n\tfont-size: 13px;\n\tfont-weight: normal;\n\tline-height: normal;\n}\n\nform .input,\n.cbi-value-field {\n\tmargin-left: 200px;\n}\n\n.cbi-value label.cbi-value-title {\n\tpadding-top: 6px;\n\tfont-size: 13px;\n\tline-height: 18px;\n\tfloat: left;\n\twidth: 180px;\n\ttext-align: right;\n\tcolor: #404040;\n}\n\ninput[type=checkbox], input[type=radio] {\n\tcursor: pointer;\n}\n\nlabel > input[type=\"checkbox\"],\nlabel > input[type=\"radio\"] {\n\tvertical-align: bottom;\n\tmargin: 0;\n}\n\ninput,\ntextarea,\nselect,\n.cbi-dropdown:not(.btn):not(.cbi-button),\n.uneditable-input {\n\tdisplay: inline-block;\n\twidth: 210px;\n\tpadding: 4px;\n\tfont-size: 13px;\n\tline-height: 18px;\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n}\n\ninput,\nselect,\n.cbi-dropdown:not(.btn):not(.cbi-button),\n.uneditable-input {\n\theight: 30px;\n}\n\n.uneditable-input {\n\tcolor: #808080;\n}\n\n.cbi-dropdown:not(.btn):not(.cbi-button),\n.cbi-dynlist {\n\tmin-width: 210px;\n\tmax-width: 400px;\n\twidth: auto;\n}\n\n.cbi-dynlist {\n\theight: auto;\n\tmin-height: 30px;\n\tdisplay: inline-flex;\n\tflex-direction: column;\n}\n\n.cbi-dynlist > .item {\n\tmargin-bottom: 4px;\n\tbox-shadow: 0 0 2px #ccc;\n\tbackground: #fff;\n\tpadding: 2px 2em 2px 4px;\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n\tposition: relative;\n\tpointer-events: none;\n\toverflow: hidden;\n\tword-break: break-all;\n}\n\n.cbi-dynlist > .item::after {\n\tcontent: \"×\";\n\tposition: absolute;\n\tdisplay: inline-flex;\n\talign-items: center;\n\ttop: -1px;\n\tright: -1px;\n\tbottom: -1px;\n\tpadding: 0 6px;\n\tborder: 1px solid #ccc;\n\tborder-radius: 0 3px 3px 0;\n\tfont-weight: bold;\n\tcolor: #179;\n\tpointer-events: auto;\n}\n\n.cbi-dynlist > .add-item {\n\tdisplay: flex;\n}\n\n.cbi-dynlist > .add-item > input,\n.cbi-dynlist > .add-item > button {\n\tflex: 1 1 auto;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\nselect {\n\tpadding: initial;\n\tbackground: #fff;\n\tbox-shadow: inset 0 -1px 3px rgba(0, 0, 0, 0.1);\n}\n\ninput[type=checkbox], input[type=radio] {\n\twidth: auto;\n\theight: auto;\n\tpadding: 0;\n\tmargin: 3px 0;\n\t*margin-top: 0;\n\t/* IE6-7 */\n\n\tline-height: normal;\n\tborder: none;\n}\n\ninput[type=file] {\n\tbackground-color: #fff;\n\tpadding: initial;\n\tborder: initial;\n\tline-height: initial;\n\tbox-shadow: none;\n\twidth: auto !important;\n}\n\ninput[type=button], input[type=reset], input[type=submit] {\n\twidth: auto;\n\theight: auto;\n}\n\nselect[multiple] {\n\theight: inherit;\n\tbackground-color: #fff;\n}\n\n.td > input[type=text],\n.td > input[type=password],\n.td > select,\n.td > .cbi-dropdown:not(.btn):not(.cbi-button),\n.cbi-dynlist > .add-item > .cbi-dropdown {\n\twidth: 100%;\n}\n\n.uneditable-input {\n\tbackground-color: #fff;\n\tdisplay: block;\n\tborder-color: #eee;\n\tbox-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);\n\tcursor: not-allowed;\n}\n\n::-moz-placeholder {\n\tcolor: #bfbfbf;\n}\n\n::-webkit-input-placeholder {\n\tcolor: #bfbfbf;\n}\n\n.item::after, .btn, .cbi-button, input, button, textarea {\n\ttransition: border linear 0.2s, box-shadow linear 0.2s;\n\tbox-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n.item:hover::after,\n.btn:hover, .cbi-button:hover, button:hover,\ninput:focus, textarea:focus {\n\toutline: 0;\n\tborder-color: rgba(210, 73, 45, 0.8) !important;\n\tbox-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(210, 73, 45, 0.6);\n\ttext-decoration: none;\n}\n\ninput[type=file]:focus, input[type=checkbox]:focus, select:focus {\n\tbox-shadow: none;\n\toutline: 1px dotted #666;\n}\n\ninput[disabled],\nbutton[disabled],\nselect[disabled],\ntextarea[disabled],\ninput[readonly],\nbutton[readonly],\nselect[readonly],\ntextarea[readonly] {\n\tbackground-color: #f5f5f5;\n\tborder-color: #ddd;\n\tpointer-events: none;\n\tcursor: default;\n}\n\nselect[readonly],\ntextarea[readonly] {\n\tpointer-events: auto;\n\tcursor: auto;\n}\n\n.cbi-optionals,\n.cbi-section-create {\n\tpadding: 0 0 10px 10px;\n}\n\n.cbi-section-create {\n\tmargin: -3px;\n\tdisplay: inline-flex;\n\talign-items: center;\n}\n\n.cbi-section-create > * {\n\tmargin: 3px;\n\tflex: 1 1 auto;\n}\n\n.cbi-section-create > * > input {\n\twidth: 100%;\n}\n\n.actions,\n.cbi-page-actions {\n\tbackground: #f5f5f5;\n\tmargin-bottom: 18px;\n\tpadding: 17px 20px 18px 17px;\n\tborder-top: 1px solid #ddd;\n\tborder-radius: 0 0 3px 3px;\n\ttext-align: right;\n}\n\n.actions .secondary-action,\n.cbi-page-actions .secondary-action{\n\tfloat: right;\n}\n\n.actions .secondary-action a,\n.cbi-page-actions .secondary-action a {\n\tline-height: 30px;\n}\n\n.actions .secondary-action a:hover,\n.cbi-page-actions .secondary-action a:hover {\n\ttext-decoration: underline;\n}\n\n.cbi-page-actions > form {\n\tdisplay: inline;\n\tmargin: 0;\n}\n\n.help-inline, .help-block {\n\tfont-size: 13px;\n\tline-height: 18px;\n\tcolor: #bfbfbf;\n}\n\n.help-inline {\n\tpadding-left: 5px;\n\t*position: relative;\n\t/* IE6-7 */\n\n\t*top: -5px;\n\t/* IE6-7 */\n\n}\n\n.help-block {\n\tdisplay: block;\n\tmax-width: 600px;\n}\n\n/*\n * Tables.less\n * Tables for, you guessed it, tabular data\n * ---------------------------------------- */\n.tr { display: table-row; }\n.table[width=\"33%\"], .th[width=\"33%\"], .td[width=\"33%\"] { width: 33%; }\n.table[width=\"100%\"], .th[width=\"100%\"], .td[width=\"100%\"] { width: 100%; }\n\n.table {\n\tdisplay: table;\n\twidth: 100%;\n\tmargin-bottom: 18px;\n\tpadding: 0;\n\tfont-size: 13px;\n\tborder-collapse: collapse;\n\tposition: relative;\n}\n\n.table .th, .table .td {\n\tdisplay: table-cell;\n\tvertical-align: middle; /* Fixme */\n\tpadding: 10px 10px 9px;\n\tline-height: 18px;\n\ttext-align: left;\n}\n\n.table .tr:first-child .th {\n\tpadding-top: 9px;\n\tfont-weight: bold;\n\tvertical-align: top;\n}\n\n.table .td, .table .th {\n\tborder-top: 1px solid #ddd;\n}\n\n.tr.placeholder {\n\theight: calc(3em + 20px);\n}\n\n.tr.placeholder > .td {\n\tposition: absolute;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\ttext-align: center;\n\tline-height: 3em;\n}\n\n.tr.drag-over-above,\n.tr.drag-over-below {\n\tborder: 2px solid #0069d6;\n\tborder-width: 2px 0 0 0;\n}\n\n.tr.drag-over-below {\n\tborder-width: 0 0 2px 0;\n}\n\n/* Patterns.less\n * Repeatable UI elements outside the base styles provided from the scaffolding\n * ---------------------------------------------------------------------------- */\nheader {\n\theight: 40px;\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tz-index: 10000;\n\toverflow: visible;\n\tcolor: rgb(244,244,244);\n}\n\nheader a {\n\tcolor: #49729e;\n\t/*text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);*/\n}\n\nheader h3 a:hover, header .brand:hover, header ul .active > a {\n\t/*background-color: #333;*/\n\t/*background-color: rgba(255, 255, 255, 0.05);*/\n\tcolor: #1021b5;\n\ttext-decoration: none;\n}\n\nheader h3 {\n\tposition: relative;\n}\n\nheader h3 a, header .brand {\n\tfloat: left;\n\tdisplay: block;\n\tpadding: 8px 20px 12px;\n\tmargin-left: 0px;\n\tcolor: #49729e;\n\tfont-size: 20px;\n\tfont-family: sans-serif;\n\tfont-weight: 200;\n\tline-height: 1;\n\toutline:none;\n}\n\n.brand {\n\tdisplay: inline-block;\n\tpadding: 5px 0 5px 0 !important;\n\twidth: 135px !important;\n\theight: 40px !important;\n\tbackground-image: url('logo.png');\n\tbackground-size: 135px 30px;\n\tbackground-position: center center;\n\tbackground-repeat: no-repeat;\n\tmargin-right: 5px;\n}\n\nheader p {\n\tmargin: 0;\n\tline-height: 40px;\n}\n\nheader .fill {\n\tbackground-color: #f2f2f2;\n\tbackground-repeat: repeat-x;\n\t-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);\n\t-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);\n\tpadding: 0 5px;\n}\n\nheader div > ul, .nav {\n\tdisplay: block;\n\tfloat: left;\n\tmargin: 0 10px 0 0;\n\tposition: relative;\n\tleft: 0;\n}\n\nheader div > ul > li, .nav > li {\n\tdisplay: block;\n\tfloat: left;\n}\n\nheader div > ul a, .nav a {\n\tdisplay: block;\n\tfloat: none;\n\tpadding: 10px 10px 11px;\n\tline-height: 19px;\n\ttext-decoration: none;\n}\n\nheader div > ul a:hover, .nav a:hover {\n\tcolor: #1021b5;\n\ttext-decoration: none;\n}\n\nheader div > ul .active > a, .nav .active > a {\n\tbackground-color: #fff;\n\tbackground-color: rgba(0, 0, 0, 0.5);\n}\n\nheader div > ul.secondary-nav, .nav.secondary-nav {\n\tfloat: right;\n\tmargin-left: 10px;\n\tmargin-right: 0;\n}\n\nheader div > ul.secondary-nav .menu-dropdown,\n.nav.secondary-nav .menu-dropdown,\nheader div > ul.secondary-nav .dropdown-menu,\n.nav.secondary-nav .dropdown-menu {\n\tright: 0;\n\tborder: 0;\n}\n\nheader div > ul a.menu:hover,\n.nav a.menu:hover,\nheader div > ul li.open .menu,\n.nav li.open .menu,\nheader div > ul .dropdown-toggle:hover,\n.nav .dropdown-toggle:hover,\nheader div > ul .dropdown.open .dropdown-toggle,\n.nav .dropdown.open .dropdown-toggle {\n\tbackground: #444;\n\tbackground: rgba(255, 255, 255, 0.05);\n}\n\nheader div > ul .menu-dropdown,\n.nav .menu-dropdown,\nheader div > ul .dropdown-menu,\n.nav .dropdown-menu {\n\tbackground-color: #f2f2f2;\n}\n\nheader div > ul .menu-dropdown a.menu,\n.nav .menu-dropdown a.menu,\nheader div > ul .dropdown-menu a.menu,\n.nav .dropdown-menu a.menu,\nheader div > ul .menu-dropdown .dropdown-toggle,\n.nav .menu-dropdown .dropdown-toggle,\nheader div > ul .dropdown-menu .dropdown-toggle,\n.nav .dropdown-menu .dropdown-toggle {\n\tcolor: #f2f2f2;\n}\n\nheader div > ul .menu-dropdown a.menu.open,\n.nav .menu-dropdown a.menu.open,\nheader div > ul .dropdown-menu a.menu.open,\n.nav .dropdown-menu a.menu.open,\nheader div > ul .menu-dropdown .dropdown-toggle.open,\n.nav .menu-dropdown .dropdown-toggle.open,\nheader div > ul .dropdown-menu .dropdown-toggle.open,\n.nav .dropdown-menu .dropdown-toggle.open {\n\tbackground: #444;\n\tbackground: rgba(255, 255, 255, 0.05);\n}\n\nheader div > ul .menu-dropdown li a,\n.nav .menu-dropdown li a,\nheader div > ul .dropdown-menu li a,\n.nav .dropdown-menu li a {\n\tcolor: #666;\n\ttext-shadow: none !important;\n}\n\nheader div > ul .menu-dropdown li a:hover,\n.nav .menu-dropdown li a:hover,\nheader div > ul .dropdown-menu li a:hover,\n.nav .dropdown-menu li a:hover {\n\tbackground-color: #ffffff;\n\tbackground-repeat: repeat-x;\n\tcolor: #1f4287;\n\ttext-shadow: none !important;\n}\n\nheader div > ul .menu-dropdown .active a,\n.nav .menu-dropdown .active a,\nheader div > ul .dropdown-menu .active a,\n.nav .dropdown-menu .active a {\n\tcolor: #ffffff;\n}\n\nheader div > ul .menu-dropdown .divider,\n.nav .menu-dropdown .divider,\nheader div > ul .dropdown-menu .divider,\n.nav .dropdown-menu .divider {\n\tbackground-color: #222;\n\tborder-color: #444;\n}\n\nheader ul .menu-dropdown li a, header ul .dropdown-menu li a {\n\tpadding: 4px 15px;\n}\n\nli.menu, .dropdown {\n\tposition: relative;\n}\n\na.menu:after, .dropdown-toggle:after {\n\twidth: 0;\n\theight: 0;\n\tdisplay: inline-block;\n\tcontent: \"&darr;\";\n\ttext-indent: -99999px;\n\tvertical-align: top;\n\tmargin-top: 8px;\n\tmargin-left: 4px;\n\tborder-left: 4px solid transparent;\n\tborder-right: 4px solid transparent;\n\tborder-top: 4px solid #1f4287;\n\topacity: 0.5;\n}\n\n.menu-dropdown, .dropdown-menu {\n\tbackground-color: #ffffff;\n\tfloat: left;\n\tposition: absolute;\n\ttop: 40px;\n\tleft: -9999px;\n\tz-index: 900;\n\tmin-width: 160px;\n\tmax-width: 220px;\n\t_width: 160px;\n\tmargin-left: 0;\n\tmargin-right: 0;\n\tpadding: 6px 0;\n\tzoom: 1;\n\tborder-color: #999;\n\tborder-color: rgba(0, 0, 0, 0.2);\n\tborder-style: solid;\n\tborder-width: 0 1px 1px;\n\tborder-radius: 0 0 6px 6px;\n\tbox-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n\tbackground-clip: padding-box;\n}\n\n.menu-dropdown li, .dropdown-menu li {\n\tfloat: none;\n\tdisplay: block;\n\tbackground-color: none;\n}\n\n.menu-dropdown .divider, .dropdown-menu .divider {\n\theight: 1px;\n\tmargin: 5px 0;\n\toverflow: hidden;\n\tbackground-color: #eee;\n\tborder-bottom: 1px solid #ffffff;\n}\n\nheader .dropdown-menu a, .dropdown-menu a {\n\tdisplay: block;\n\tpadding: 4px 15px;\n\tclear: both;\n\tfont-weight: normal;\n\tline-height: 18px;\n\tcolor: #808080;\n\ttext-shadow: 0 1px 0 #ffffff;\n}\n\nheader .dropdown-menu a:hover,\n.dropdown-menu a:hover,\nheader .dropdown-menu a.hover,\n.dropdown-menu a.hover {\n\tbackground-color: #dddddd;\n\tbackground-repeat: repeat-x;\n\tbackground-image: linear-gradient(to bottom, #eee, #ddd);\n\tcolor: #404040;\n\ttext-decoration: none;\n\tbox-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.025), inset 0 -1px rgba(0, 0, 0, 0.025);\n}\n\n.open .menu,\n.dropdown.open .menu,\n.open .dropdown-toggle,\n.dropdown.open .dropdown-toggle {\n\tcolor: #fff;\n\tbackground: #ccc;\n\tbackground: rgba(0, 0, 0, 0.3);\n}\n\n.open .menu-dropdown,\n.dropdown.open .menu-dropdown,\n.open .dropdown-menu,\n.dropdown.open .dropdown-menu {\n\tleft: 0;\n}\n\n.dropdown:hover ul.dropdown-menu {\n\tleft: 0;\n}\n\n.dropdown-menu .dropdown-menu {\n\t  position: absolute;\n\t  left: 159px;\n}\n\n.dropdown-menu li {\n\t  position: relative;\n}\n\n.tabs, .cbi-tabmenu {\n\tmargin: 0 -5px 18px;\n\tpadding: 0 2px;\n\tlist-style: none;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tbackground: linear-gradient(#fff 28px, #ddd 28px);\n\tbackground-size: 1px 29px;\n\tbackground-position: left bottom;\n}\n\n.tabs > li, .cbi-tabmenu > li {\n\tflex: 0 1 auto;\n\tdisplay: flex;\n\talign-items: center;\n\theight: 25px;\n\tmax-width: 48%;\n\tmargin: 4px 2px 0 2px;\n\tbackground: #fff;\n\tborder: 1px solid #ddd;\n\tborder-bottom: none;\n\tborder-radius: 4px 4px 0 0;\n\tcolor: #1f4287;\n}\n\n.tabs > li > a, .cbi-tabmenu > li > a {\n\tpadding: 4px 6px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tcolor: inherit;\n\ttext-decoration: none;\n\tborder-radius: 4px 4px 0 0;\n\tline-height: 25px;\n\toutline: none;\n}\n\n.tabs > li:not(.active):hover, .cbi-tabmenu > .cbi-tab-disabled:hover {\n\tbackground: linear-gradient(#fff 90%, #ddd 100%);\n}\n\n.tabs > li:not(.active), .cbi-tabmenu > .cbi-tab-disabled {\n\tcolor: #999;\n\tbackground: linear-gradient(#eee 90%, #ddd 100%);\n}\n\n.cbi-tab-disabled[data-errors]::after {\n\tcontent: attr(data-errors);\n\tbackground: #c43c35;\n\tcolor: #fff;\n\tmin-width: 12px;\n\tline-height: 14px;\n\tborder-radius: 7px;\n\ttext-align: center;\n\tmargin: 0 5px 0 0;\n\tpadding: 1px 2px;\n}\n\n.cbi-tabmenu.map {\n\tmargin: 0;\n}\n\n.cbi-tabmenu.map > li {\n\tfont-size: 16.5px;\n\tfont-weight: bold;\n}\n\n.cbi-tabcontainer > fieldset.cbi-section[id] > legend {\n\tdisplay: none;\n}\n\n.tabs .menu-dropdown, .tabs .dropdown-menu {\n\ttop: 35px;\n\tborder-width: 1px;\n\tborder-radius: 0 6px 6px 6px;\n}\n\n.tabs a.menu:after, .tabs .dropdown-toggle:after {\n\tborder-top-color: #999;\n\tmargin-top: 15px;\n\tmargin-left: 5px;\n}\n\n.tabs li.open.menu .menu, .tabs .open.dropdown .dropdown-toggle {\n\tborder-color: #999;\n}\n\n.tabs li.open a.menu:after, .tabs .dropdown.open .dropdown-toggle:after {\n\tborder-top-color: #555;\n}\n\n.tab-content > .tab-pane,\n.tab-content > div {\n\tdisplay: none;\n}\n\n.tab-content > .active {\n\tdisplay: block;\n}\n\n.breadcrumb {\n\tpadding: 7px 14px;\n\tmargin: 0 0 18px;\n\tbackground-color: #f5f5f5;\n\tbackground-repeat: repeat-x;\n\tbackground-image: linear-gradient(to bottom, #fff, #f5f5f5);\n\tborder: 1px solid #ddd;\n\tborder-radius: 3px;\n\tbox-shadow: inset 0 1px 0 #fff;\n}\n\n.breadcrumb li {\n\tdisplay: inline;\n\ttext-shadow: 0 1px 0 #fff;\n}\n\n.breadcrumb .divider {\n\tpadding: 0 5px;\n\tcolor: #bfbfbf;\n}\n\n.breadcrumb .active a {\n\tcolor: #404040;\n}\n\nfooter {\n\tmargin-top: 17px;\n\tpadding-top: 17px;\n\tborder-top: 1px solid #eee;\n}\n\n#modal_overlay {\n\tposition: fixed;\n\ttop: 0;\n\tbottom: 0;\n\tleft: -10000px;\n\tright: 10000px;\n\tbackground: rgba(0, 0, 0, 0.7);\n\tz-index: 900;\n\toverflow-y: scroll;\n\t-webkit-overflow-scrolling: touch;\n\ttransition: opacity .125s ease-in;\n\topacity: 0;\n\tvisibility: hidden;\n}\n\n.modal {\n\twidth: 90%;\n\tmargin: 5em auto;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tmin-height: 32px;\n\tmax-width: 600px;\n\talign-items: center;\n\tborder-radius: 3px;\n\tbackground: #fff;\n\tbox-shadow: 0 0 3px #444;\n\tpadding: 1em 1em .5em 1em;\n\tmin-width: 270px;\n}\n\n.modal > * {\n\tflex-basis: 100%;\n\tline-height: normal;\n\tmargin-bottom: .5em;\n\tmax-width: 100%;\n}\n\n.modal > pre,\n.modal > textarea {\n\twhite-space: pre-wrap;\n\toverflow: auto;\n}\n\nbody.modal-overlay-active {\n\toverflow: hidden;\n\theight: 100vh;\n}\n\nbody.modal-overlay-active #modal_overlay {\n\tleft: 0;\n\tright: 0;\n\topacity: 1;\n\tvisibility: visible;\n}\n\n.btn.danger,\n.alert-message.danger,\n.btn.danger:hover,\n.alert-message.danger:hover,\n.btn.error,\n.alert-message.error,\n.btn.error:hover,\n.alert-message.error:hover,\n.btn.success,\n.alert-message.success,\n.btn.success:hover,\n.alert-message.success:hover,\n.btn.info,\n.alert-message.info,\n.btn.info:hover,\n.alert-message.info:hover,\n.cbi-tooltip.error, .cbi-tooltip.success, .cbi-tooltip.info {\n\tcolor: #fff;\n}\n\n.btn .close, .alert-message .close {\n\tfont-family: Arial, sans-serif;\n\tline-height: 18px;\n}\n\n.modal .btn.danger,\n.modal .btn {\n\twhite-space: normal;\n}\n\n.btn.danger,\n.alert-message.danger,\n.btn.error,\n.alert-message.error,\n.cbi-tooltip.error {\n\tbackground: linear-gradient(to bottom, #1962b0, #1944b0) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.btn.success, .alert-message.success, .cbi-tooltip.success {\n\tbackground: linear-gradient(to bottom, #62c462, #57a957) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.btn.info, .alert-message.info, .cbi-tooltip.info {\n\tbackground: linear-gradient(to bottom, #5bc0de, #339bb9) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.alert-message.notice, .cbi-tooltip.notice {\n\tbackground: linear-gradient(to bottom, #efefef, #fefefe) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(255, 255, 255, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.item::after,\n.btn,\n.cbi-button {\n\tcursor: pointer;\n\tdisplay: inline-block;\n\tbackground: #fff;\n\tpadding: 5px 14px 6px;\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n\tcolor: #333;\n\tfont-size: 13px;\n\tline-height: normal;\n\tborder: 1px solid #ccc;\n\tborder-bottom-color: #bbb;\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n\twhite-space: pre;\n}\n\n.btn:focus,\n.cbi-button:focus {\n\toutline: 1px dotted #666;\n}\n\n.cbi-input-invalid,\n.cbi-input-invalid.cbi-dropdown:not(.btn):not(.cbi-button),\n.cbi-input-invalid.cbi-dropdown:not([open]) > ul > li,\n.cbi-value-error input {\n\tcolor: #f00;\n\tborder-color: #f00;\n}\n\n.cbi-button-neutral,\n.cbi-button-download,\n.cbi-button-find,\n.cbi-button-link,\n.cbi-button-up,\n.cbi-button-down {\n\tcolor: #444;\n}\n\n.btn.primary,\n.cbi-button-apply,\n.cbi-button-negative {\n\tborder-color: #1f4287;\n\tcolor: #1f4287;\n\ttext-shadow: none !important;\n}\n\n.cbi-section-remove .cbi-button,\n.cbi-button-reset {\n\tborder-color: #179;\n\tcolor: #179;\n}\n\n.cbi-page-actions::after {\n\tdisplay: table;\n\tcontent: \"\";\n\tclear: both;\n}\n\n.cbi-page-actions > * {\n\tvertical-align: middle;\n}\n\n.cbi-page-actions > :not([method=\"post\"]):not(.cbi-button-apply):not(.cbi-button-negative):not(.cbi-button-save):not(.cbi-button-reset) {\n\tfloat: left;\n\tmargin-right: .4em;\n}\n\n.cbi-button-edit,\n.cbi-button-positive.important,\n.cbi-button-positive,\n.cbi-button-action.important,\n.cbi-button-action,\n.cbi-button-save,\n.cbi-button-reload,\n.cbi-page-actions .cbi-button-apply {\n\tcolor: #FFFFFF;\n\tbackground: #4b9cd1;\n\tborder-color: #2b73b5;\n\ttext-shadow: none !important;\n}\n\n.cbi-button-fieldadd,\n.cbi-section-remove .cbi-button,\n.cbi-button-remove,\n.cbi-button-add,\n.cbi-button-negative.important,\n.cbi-page-actions .cbi-button-save,\n.cbi-page-actions .cbi-button-reset,\n.cbi-section-actions .cbi-button-action {\n\tcolor: #132ba1;\n\tbackground: #ffffff;\n\tborder-color: #132ba1;\n\ttext-shadow: none !important;\n}\n\n\n.cbi-page-actions .cbi-button-reset:hover,\n.cbi-page-actions .cbi-button-save:hover,\n.cbi-page-actions .cbi-button-apply:hover {\n\tcolor: #FFFFFF;\n\tbackground: #132ba1;\n\tborder-color: #132ba1;\n\ttext-shadow: none !important;\n}\n\n.cbi-dropdown {\n\tdisplay: inline-flex !important;\n\tcursor: pointer;\n\theight: auto;\n\tposition: relative;\n\tpadding: 0 !important;\n}\n\n.cbi-dropdown:not(.btn):not(.cbi-button) {\n\tbackground: linear-gradient(#fff 0%, #e9e8e6 100%);\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n\tcolor: #404040;\n}\n\n.cbi-dynlist > .item:focus,\n.cbi-dropdown:focus {\n\toutline: 2px solid #4b6e9b;\n}\n\n.cbi-dropdown > ul {\n\tmargin: 0 !important;\n\tpadding: 0;\n\tlist-style: none;\n\toverflow-x: hidden;\n\toverflow-y: auto;\n\tdisplay: flex;\n\twidth: 100%;\n}\n\n.cbi-dropdown.btn > ul:not(.dropdown),\n.cbi-dropdown.cbi-button > ul:not(.dropdown) {\n\tmargin: 0 0 0 13px !important;\n}\n\n.cbi-dropdown.btn.spinning > ul:not(.dropdown),\n.cbi-dropdown.cbi-button.spinning > ul:not(.dropdown) {\n\tmargin: 0 !important;\n}\n\n.cbi-dropdown > ul.preview {\n\tdisplay: none;\n}\n\n.cbi-dropdown > .open,\n.cbi-dropdown > .more {\n\tflex-grow: 0;\n\tflex-shrink: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n\ttext-align: center;\n\tline-height: 2em;\n\tpadding: 0 .25em;\n}\n\n.cbi-dropdown.btn > .open,\n.cbi-dropdown.cbi-button > .open {\n\tpadding: 0 .5em;\n\tmargin-left: .5em;\n\tborder-left: 1px solid;\n}\n\n.cbi-dropdown > .more,\n.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li[placeholder] {\n\tcolor: #777;\n\tfont-weight: bold;\n\ttext-shadow: 1px 1px 0px #fff;\n\tdisplay: none;\n\tjustify-content: center;\n}\n\n.cbi-dropdown > ul > li {\n\tdisplay: none;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tflex-shrink: 1;\n\tflex-grow: 1;\n\talign-items: center;\n\talign-self: center;\n\tcolor: inherit;\n}\n\n.cbi-dropdown > ul.dropdown > li,\n.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li {\n\tmin-height: 20px;\n\tpadding: .25em;\n\tcolor: #404040;\n}\n\n.cbi-dropdown > ul > li .hide-open { display: block; display: initial; }\n.cbi-dropdown > ul > li .hide-close { display: none; }\n\n.cbi-dropdown > ul > li[display]:not([display=\"0\"]) {\n\tborder-left: 1px solid #ccc;\n}\n\n.cbi-dropdown[empty] > ul {\n\tmax-width: 1px;\n}\n\n.cbi-dropdown > ul > li > form {\n\tdisplay: none;\n\tmargin: 0;\n\tpadding: 0;\n\tpointer-events: none;\n}\n\n.cbi-dropdown > ul > li img {\n\tvertical-align: middle;\n\tmargin-right: .25em;\n}\n\n.cbi-dropdown > ul > li > form > input[type=\"checkbox\"] {\n\tmargin: 0;\n}\n\n.cbi-dropdown > ul > li input[type=\"text\"] {\n\theight: 20px;\n}\n\n.cbi-dropdown[open] {\n\tposition: relative;\n}\n\n.cbi-dropdown[open] > ul.dropdown {\n\tdisplay: block;\n\tbackground: #f6f6f5;\n\tborder: 1px solid #918e8c;\n\tbox-shadow: 0 0 4px #918e8c;\n\tposition: absolute;\n\tz-index: 1100;\n\tmax-width: none;\n\tmin-width: 100%;\n\twidth: auto;\n\ttransition: max-height .125s ease-in;\n}\n\n.cbi-dropdown > ul > li[display],\n.cbi-dropdown[open] > ul.preview,\n.cbi-dropdown[open] > ul.dropdown > li,\n.cbi-dropdown[multiple] > ul > li > label,\n.cbi-dropdown[multiple][open] > ul.dropdown > li,\n.cbi-dropdown[multiple][more] > .more,\n.cbi-dropdown[multiple][empty] > .more {\n\tflex-grow: 1;\n\tdisplay: flex !important;\n}\n\n.cbi-dropdown[empty] > ul > li,\n.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder],\n.cbi-dropdown[multiple][open] > ul.dropdown > li > form {\n\tdisplay: block !important;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; }\n.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: block; display: initial; }\n\n.cbi-dropdown[open] > ul.dropdown > li {\n\tborder-bottom: 1px solid #ccc;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li[selected] {\n\tbackground: #0a66a3;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li.focus {\n\tbackground: #6899ba;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li:last-child {\n\tmargin-bottom: 0;\n\tborder-bottom: none;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li[unselectable] {\n\topacity: 0.7;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li > input.create-item-input:first-child:last-child {\n\twidth: 100%;\n}\n\n.cbi-dropdown[disabled] {\n\tpointer-events: none;\n\topacity: .6;\n}\n\ninput[type=\"text\"] + .cbi-button,\ninput[type=\"password\"] + .cbi-button,\nselect + .cbi-button {\n\tborder-radius: 0 3px 3px 0;\n\tborder-color: #ccc;\n\tmargin-left: -2px;\n\tpadding: 0 6px;\n\tvertical-align: top;\n\theight: 30px;\n\tfont-size: 14px;\n\tline-height: 28px;\n}\n\n/*select + .cbi-button {\n\tborder-left-color: transparent;\n}*/\n\ninput[type=\"text\"] + .cbi-button-add {\n\tcolor: #fff;\n\tborder-radius: 0 3px 3px 0;\n\tborder-color: #2b73b5;\n\tbackground: #2b73b5;\n}\n\ninput[type=\"text\"] + .cbi-button-remove {\n\tcolor: #124;\n\tborder-radius: 0 3px 3px 0;\n\tborder-color: #ccc;\n}\n\n.cbi-title-ref {\n\tcolor: #234aad;\n}\n\n.cbi-title-ref::after {\n\tcontent: \"➙\";\n}\n\n.cbi-tooltip-container {\n\tcursor: help;\n}\n\n.cbi-tooltip {\n\tposition: absolute;\n\tz-index: 1000;\n\tleft: -10000px;\n\tbox-shadow: 0 0 2px #ccc;\n\tborder-radius: 3px;\n\tbackground: #fff;\n\twhite-space: pre;\n\tpadding: 2px 5px;\n\topacity: 0;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-tooltip-container:hover .cbi-tooltip:not(:empty) {\n\tleft: auto;\n\topacity: 1;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-progressbar {\n\tborder: 1px solid var(--border-color-high);\n\tborder-radius: 3px;\n\tposition: relative;\n\tmin-width: 170px;\n\theight: 8px;\n\tmargin: 1.4em 0 4px 0;\n\tbackground: var(--background-color-medium);\n}\n\n.cbi-progressbar > div {\n\tbackground: var(--primary-color-medium);\n\theight: 100%;\n\ttransition: width .25s ease-in;\n\twidth: 0%;\n\tborder-radius: 2px;\n}\n\n.cbi-progressbar::before {\n\tposition: absolute;\n\ttop: -1.4em;\n\tleft: 0;\n\tcontent: attr(title);\n\twhite-space: pre;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.zonebadge .cbi-tooltip {\n\tpadding: 1px;\n\tbackground: inherit;\n\tmargin: -1.6em 0 0 -5px;\n\tborder-radius: 3px;\n\tpointer-events: none;\n\tbox-shadow: 0 0 3px #444;\n}\n\n.zonebadge .cbi-tooltip > * {\n\tmargin: 1px;\n}\n\n.zone-forwards {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n}\n\n.zone-forwards > * {\n\tflex: 1 1 40%;\n\tpadding: 1px;\n}\n\n.zone-forwards > span {\n\tflex-basis: 10%;\n\ttext-align: center;\n}\n\n.zone-forwards .zone-src,\n.zone-forwards .zone-dest {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.btn.active, .btn:active {\n\tbox-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.btn.disabled {\n\tcursor: default;\n\topacity: 0.65;\n\tbox-shadow: none;\n}\n\n.btn[disabled] {\n\tcursor: default;\n\topacity: 0.65;\n\tbox-shadow: none;\n}\n\n.btn.large {\n\tfont-size: 15px;\n\tline-height: normal;\n\tpadding: 9px 14px 9px;\n\tborder-radius: 6px;\n}\n\n.btn.small {\n\tpadding: 7px 9px 7px;\n\tfont-size: 11px;\n}\n\nbutton.btn::-moz-focus-inner, input[type=submit].btn::-moz-focus-inner {\n\tpadding: 0;\n\tborder: 0;\n}\n\n.close {\n\tfloat: right;\n\tcolor: #000;\n\tfont-size: 20px;\n\tfont-weight: bold;\n\tline-height: 13.5px;\n\ttext-shadow: 0 1px 0 #fff;\n\topacity: 0.25;\n}\n\n.close:hover {\n\tcolor: #000;\n\ttext-decoration: none;\n\topacity: 0.4;\n}\n\n.alert-message {\n\tposition: relative;\n\tpadding: .5em .5em .25em .5em;\n\tmargin-bottom: .5em;\n\tcolor: #404040;\n\tbackground: linear-gradient(to bottom, #fceec1, #eedc94) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n\ttext-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n\tborder-width: 1px;\n\tborder-style: solid;\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n\n.alert-message .close {\n\tmargin-top: 1px;\n\t*margin-top: 0;\n}\n\n.alert-message h4,\n.alert-message h5,\n.alert-message pre,\n.alert-message ul,\n.alert-message li,\n.alert-message p {\n\tcolor: inherit;\n\tborder: none;\n\tline-height: inherit;\n\tbackground: transparent;\n\tpadding: 0;\n\tmargin: .25em 0;\n}\n\n.alert-message button {\n\tmargin: .25em 0;\n}\n\n.label,\nheader [data-indicator] {\n\tpadding: 1px 3px 2px;\n\tfont-size: 9.75px;\n\tfont-weight: bold;\n\tcolor: #fff !important;\n\ttext-transform: uppercase;\n\twhite-space: nowrap;\n\tbackground-color: #216090;\n\tborder-radius: 3px;\n\ttext-shadow: none;\n\tmargin-left: .4em;\n}\n\nheader [data-indicator][data-clickable] {\n\tcursor: pointer;\n}\n\na.label:link,\na.label:visited {\n\tcolor: #fff;\n}\n\na.label:hover {\n\ttext-decoration: none;\n}\n\n.label.important {\n\tbackground-color: #49729e;\n}\n\n.label.warning {\n\tbackground-color: #f89406;\n}\n\n.label.success {\n\tbackground-color: #339933;\n}\n\n.label.notice,\nheader [data-indicator][data-style=\"active\"] {\n\tbackground-color: #6582a1;\n}\n\n/* LuCI specific items */\n.hidden { display: none }\n\nform.inline { display: inline; margin-bottom: 0; }\n\nheader .pull-right { padding-top: 8px; }\n\n#modemenu li:last-child span.divider { display: none }\n\n#syslog {  width: 100%; }\n\n.cbi-section-table .tr:hover .td,\n.cbi-section-table .tr:hover .th,\n.cbi-section-table .tr:hover::before {\n\tbackground-color: #f5f5f5;\n}\n\n.cbi-section-table .tr.cbi-section-table-descr .th {\n\tfont-weight: normal;\n}\n\n.cbi-section-table-titles.named::before,\n.cbi-section-table-descr.named::before,\n.cbi-section-table-row[data-title]::before {\n\tcontent: attr(data-title) \" \";\n\tdisplay: table-cell;\n\tpadding: 10px 10px 9px;\n\tline-height: 18px;\n\tfont-weight: bold;\n\tvertical-align: middle;\n}\n\n.cbi-section-table-titles.named::before,\n.cbi-section-table-descr.named::before,\n.cbi-section-table-row[data-title]::before {\n\tborder-top: 1px solid #ddd;\n}\n\n.left { text-align: left !important; }\n.right { text-align: right !important; margin-bottom: 5px !important;}\n.center { text-align: center !important; }\n.top { vertical-align: top !important; }\n.middle { vertical-align: middle !important; }\n.bottom { vertical-align: bottom !important; }\n\n.cbi-value-field { line-height: 1.5em; }\n\n.cbi-value-field input[type=checkbox],\n.cbi-value-field input[type=radio] {\n\tmargin-top: 8px;\n\tmargin-right: 6px;\n}\n\ntable table td,\n.cbi-value-field table td {\n\tborder: none;\n}\n\n.table.cbi-section-table input[type=\"password\"],\n.table.cbi-section-table input[type=\"text\"],\n.table.cbi-section-table textarea,\n.table.cbi-section-table select {\n\twidth: 100%;\n}\n\n.table.cbi-section-table .td.cbi-section-table-cell {\n\twhite-space: nowrap;\n\ttext-align: right;\n}\n\n.table.cbi-section-table .td.cbi-section-table-cell select {\n\twidth: inherit;\n}\n\n.td.cbi-section-actions {\n\ttext-align: right;\n\tvertical-align: middle;\n}\n\n.td.cbi-section-actions > * {\n\tdisplay: flex;\n}\n\n.td.cbi-section-actions > * > *,\n.td.cbi-section-actions > * > form > * {\n\tflex: 1 1 4em;\n\tmargin: 0 1px;\n}\n\n.td.cbi-section-actions > * > form {\n\tdisplay: inline-flex;\n\tmargin: 0;\n}\n\n.table.valign-middle .td {\n\tvertical-align: middle;\n}\n\n.cbi-rowstyle-2,\n.tr.table-titles,\n.tr.cbi-section-table-titles {\n\tbackground: #f9f9f9;\n}\n\n.cbi-value-description {\n\tbackground-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxNicgaGVpZ2h0PScxNicgdmlld0JveD0nMCAwIDE2IDE2Jz48Y2lyY2xlIGN4PSc4JyBjeT0nOCcgcj0nNy41JyBmaWxsPScjNGI5Y2QxJyBzdHJva2U9JyMyYjczYjUnLz48dGV4dCB4PSc4JyB5PScxMicgZm9udC1zaXplPScxMScgZm9udC1mYW1pbHk9J3NhbnMtc2VyaWYnIGZvbnQtd2VpZ2h0PSdib2xkJyB0ZXh0LWFuY2hvcj0nbWlkZGxlJyBmaWxsPScjZmZmJz4/PC90ZXh0Pjwvc3ZnPg==\");\n\tbackground-position: .25em .2em;\n\tbackground-repeat: no-repeat;\n\tmargin: .25em 0 0 0;\n\tpadding: 0 0 0 1.7em;\n}\n\n.cbi-section-error {\n\tborder: 1px solid #f00;\n\tborder-radius: 3px;\n\tbackground-color: #a2c7eb;\n\tpadding: 5px;\n\tmargin-bottom: 18px;\n}\n\n.cbi-section-error ul { margin: 0 0 0 20px; }\n\n.cbi-section-error ul li {\n\tcolor: #f00;\n\tfont-weight: bold;\n}\n\n.ifacebox {\n\tbackground-color: #fff;\n\tborder: 1px solid #ccc;\n\tmargin: 0 10px;\n\ttext-align: center;\n\twhite-space: nowrap;\n\tbackground-image: linear-gradient(#fff, #fff 25%, #f9f9f9);\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n\tdisplay: inline-flex;\n\tflex-direction: column;\n\tline-height: 1.2em;\n\tmin-width: 100px;\n}\n\n.ifacebox .ifacebox-head {\n\tborder-bottom: 1px solid #ccc;\n\tpadding: 2px;\n\tbackground: #eee;\n}\n\n.ifacebox .ifacebox-head.active {\n\tbackground: #4b9cd1;\n}\n\n.ifacebox .ifacebox-body {\n\tpadding: .25em;\n}\n\n.ifacebadge {\n\tdisplay: inline-block;\n\tflex-direction: row;\n\twhite-space: nowrap;\n\tbackground-color: #fff;\n\tborder: 1px solid #ccc;\n\tpadding: 2px;\n\tbackground-image: linear-gradient(#fff, #fff 25%, #f9f9f9);\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n\tcursor: default;\n\tline-height: 1.2em;\n}\n\n.ifacebadge img {\n\twidth: 16px;\n\theight: 16px;\n\tvertical-align: middle;\n}\n\n.ifacebadge-active {\n\tborder-color: #000;\n\tfont-weight: bold;\n}\n\n.network-status-table {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n}\n\n.network-status-table .ifacebox {\n\tmargin: .5em;\n\tflex-grow: 1;\n}\n\n.network-status-table .ifacebox-body {\n\tdisplay: flex;\n\tflex-direction: column;\n\theight: 100%;\n\ttext-align: left;\n}\n\n.network-status-table .ifacebox-body > * {\n\tmargin: .25em;\n}\n\n.network-status-table .ifacebox-body > span {\n\tflex: 10 10 auto;\n\theight: 100%;\n}\n\n.network-status-table .ifacebox-body > div {\n\tmargin: -.125em;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n}\n\n#dsl_status_table .ifacebox-body span > strong {\n\tdisplay: inline-block;\n\tmin-width: 35%;\n}\n\n.ifacebadge.large,\n.network-status-table .ifacebox-body .ifacebadge {\n\tdisplay: flex;\n\tflex: 1;\n\tpadding: .25em;\n\tmin-width: 220px;\n\tmargin: .125em;\n}\n\n.ifacebadge.large {\n\tdisplay: inline-flex;\n}\n\n.network-status-table .ifacebox-body .ifacebadge > span {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.ifacebadge > *,\n.ifacebadge.large > * {\n\tmargin: 0 .125em;\n}\n\n.zonebadge {\n\tpadding: 2px;\n\tborder-radius: 4px;\n\tdisplay: inline-block;\n\twhite-space: nowrap;\n\tcolor: #666;\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n}\n\n.zonebadge > em,\n.zonebadge > strong {\n\tmargin: 0 2px;\n\tdisplay: inline-block;\n}\n\n.zonebadge input {\n\twidth: 6em;\n}\n\n.zonebadge > .ifacebadge {\n\tmargin-left: 2px;\n}\n\n.zonebadge-empty {\n\tborder: 1px dashed #aaa;\n\tcolor: #aaa;\n\tfont-style: italic;\n\tfont-size: smaller;\n}\n\ndiv.cbi-value var,\n.td.cbi-value-field var {\n\tfont-style: italic;\n\tcolor: #0069d6;\n}\n\ndiv.cbi-value var[data-tooltip],\n.td.cbi-value-field var[data-tooltip],\ndiv.cbi-value var.cbi-tooltip-container,\n.td.cbi-value-field var.cbi-tooltip-container {\n\tcursor: help;\n\tborder-bottom: 1px dotted #0069d6;\n}\n\ndiv.cbi-value var.cbi-tooltip-container,\n.td.cbi-value-field var.cbi-tooltip-container .cbi-tooltip {\n\tfont-style: normal;\n\twhite-space: normal;\n\tcolor: #444;\n}\n\n#modal_overlay > .modal.uci-dialog,\n#modal_overlay > .modal.cbi-modal {\n\tmax-width: 900px;\n}\n\n.uci-change-list {\n\tline-height: 170%;\n\twhite-space: pre;\n}\n\n.uci-change-list del,\n.uci-change-list ins,\n.uci-change-list var,\n.uci-change-legend-label del,\n.uci-change-legend-label ins,\n.uci-change-legend-label var {\n\ttext-decoration: none;\n\tfont-family: monospace;\n\tfont-style: normal;\n\tborder: 1px solid #ccc;\n\tbackground: #eee;\n\tpadding: 2px;\n\tdisplay: block;\n\tline-height: 15px;\n\tmargin-bottom: 1px;\n}\n\n.uci-change-list ins,\n.uci-change-legend-label ins {\n\tborder-color: #0f0;\n\tbackground: #cfc;\n}\n\n.uci-change-list del,\n.uci-change-legend-label del {\n\tborder-color: #f00;\n\tbackground: #fcc;\n}\n\n.uci-change-list var,\n.uci-change-legend-label var {\n\tborder-color: #ccc;\n\tbackground: #eee;\n}\n\n.uci-change-list var ins,\n.uci-change-list var del {\n\tdisplay: inline-block;\n\tborder: none;\n\twidth: 100%;\n\tpadding: 0;\n}\n\n.uci-change-legend {\n\tpadding: 5px;\n}\n\n.uci-change-legend-label {\n\twidth: 150px;\n\tfloat: left;\n}\n\n.uci-change-legend-label > ins,\n.uci-change-legend-label > del,\n.uci-change-legend-label > var {\n\tfloat: left;\n\tmargin-right: 4px;\n\twidth: 16px;\n\theight: 16px;\n\tdisplay: block;\n\tposition: relative;\n}\n\n.uci-change-legend-label var ins,\n.uci-change-legend-label var del {\n\tborder: none;\n\tposition: absolute;\n\ttop: 2px;\n\tleft: 2px;\n\tright: 2px;\n\tbottom: 2px;\n}\n\n#modal_overlay {\n\tposition: fixed;\n\ttop: 0;\n\tbottom: 0;\n\tleft: -10000px;\n\tright: 10000px;\n\tbackground: rgba(0, 0, 0, 0.7);\n\tz-index: 900;\n\toverflow-y: scroll;\n\t-webkit-overflow-scrolling: touch;\n\ttransition: opacity .125s ease-in;\n\topacity: 0;\n}\n\n#modal_overlay > .modal {\n\twidth: 90%;\n\tmargin: 5em auto;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tmin-height: 32px;\n\tmax-width: 600px;\n\talign-items: center;\n\tborder-radius: 3px;\n\tbackground: #fff;\n\tbox-shadow: 0 0 3px #444;\n\tpadding: 1em 1em .5em 1em;\n\tmin-width: 270px;\n}\n\n#modal_overlay .modal > * {\n\tflex-basis: 100%;\n\tline-height: normal;\n\tmargin-bottom: .5em;\n}\n\n#modal_overlay .modal > pre,\n#modal_overlay .modal > textarea {\n\twhite-space: pre-wrap;\n\toverflow: auto;\n}\n\nbody.modal-overlay-active {\n\toverflow: hidden;\n\theight: 100vh;\n}\n\nbody.modal-overlay-active #modal_overlay {\n\tleft: 0;\n\tright: 0;\n\topacity: 1;\n}\n\nhtml body.apply-overlay-active {\n\theight: calc(100vh - 63px);\n}\n\n#applyreboot-section {\n\tline-height: 300%;\n}\n\n[data-page=\"admin-network-dhcp\"] [data-name=\"ip\"] {\n\twidth: 15%;\n}\n\n@keyframes flash {\n\t0% { opacity: 1; }\n\t50% { opacity: .5; }\n\t100% { opacity: 1; }\n}\n\n.flash {\n\tanimation: flash .35s;\n}\n\n.spinning {\n\tposition: relative;\n\tpadding-left: 32px !important;\n}\n\n.spinning::before {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tbottom: 0;\n\twidth: 32px;\n\tcontent: \" \";\n\tbackground: url(../resources/icons/loading.svg) no-repeat center;\n\tbackground-size: 16px;\n}\n\n[data-tab-title] {\n\theight: 0;\n\topacity: 0;\n\toverflow: hidden;\n}\n\n[data-tab-active=\"true\"] {\n\topacity: 1;\n\theight: auto;\n\toverflow: visible;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-filebrowser {\n\tmin-width: 210px;\n\tmax-width: 100%;\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n\tdisplay: flex;\n\tflex-direction: column;\n\topacity: 0;\n\theight: 0;\n\toverflow: hidden;\n}\n\n.cbi-filebrowser.open {\n\topacity: 1;\n\theight: auto;\n\toverflow: visible;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-filebrowser > * {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tpadding: 0 0 .25em 0;\n\tmargin: .25em .25em 0px .25em;\n\twhite-space: nowrap;\n\tborder-bottom: 1px solid #ccc;\n}\n\n.cbi-filebrowser .cbi-button-positive {\n\tmargin-right: .25em;\n}\n\n.cbi-filebrowser > div {\n\tborder-bottom: none;\n}\n\n.cbi-filebrowser > ul > li {\n\tdisplay: flex;\n\tflex-direction: row;\n}\n\n.cbi-filebrowser > ul > li:hover {\n\tbackground: #f5f5f5;\n}\n\n.cbi-filebrowser > ul > li > div:first-child {\n\tflex: 10;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cbi-filebrowser > ul > li > div:last-child {\n\tflex: 3;\n\ttext-align: right;\n}\n\n.cbi-filebrowser > ul > li > div:last-child > button {\n\tpadding: .125em .25em;\n\tmargin: 1px 0 1px .25em;\n}\n\n.cbi-filebrowser .upload {\n\tdisplay: flex;\n\tflex-direction: row;\n\tflex-wrap: wrap;\n\tmargin: 0 -.125em .25em -.125em;\n\tpadding: 0 0 .125em 0px;\n\tborder-bottom: 1px solid #ccc;\n}\n\n.cbi-filebrowser .upload > * {\n\tmargin: .125em;\n\tflex: 1;\n}\n\n.cbi-filebrowser .upload > .btn {\n\tflex-basis: 60px;\n}\n\n.cbi-filebrowser .upload > div {\n\tflex: 10;\n\tmin-width: 150px;\n}\n\n.cbi-filebrowser .upload > div > input {\n\twidth: 100%;\n}\n\n@keyframes fade-in {\n\t0% { opacity: 0; }\n\t100% { opacity: 1; }\n}\n\n@keyframes fade-out {\n\t0% { opacity: 1; }\n\t100% { opacity: 0; }\n}\n\n.fade-in {\n\tanimation: fade-in .4s ease;\n}\n\n.fade-out {\n\tanimation: fade-out .4s ease;\n}\n\n.assoclist .ifacebadge {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\twhite-space: normal;\n\ttext-align: center;\n}\n\n.assoclist .ifacebadge > img {\n\tmargin: .2em;\n}\n\n.assoclist .td:nth-of-type(3),\n.assoclist .td:nth-of-type(5) {\n\twidth: 25%;\n}\n\n.assoclist .td:nth-of-type(6) button {\n\tword-break: normal;\n}\n"
  },
  {
    "path": "luci/themes/luci-theme-lightblue/htdocs/luci-static/lightblue/mobile.css",
    "content": "header h3 a, header .brand {\n\tdisplay:none !important;\n}\n\n@media screen and (max-device-width: 600px) {\n\t#maincontent.container {\n\t\tmargin-top: 30px;\n\t}\n\n\t.tabs, .cbi-tabmenu {\n\t\tbackground: linear-gradient(#fff 20%, #ddd 100%);\n\t\tbackground-size: 1px 34px;\n\t\tmargin-bottom: 10px;\n\t}\n\n\t.tabs > li, .cbi-tabmenu > li {\n\t\theight: 30px;\n\t}\n\n\t.tabs > li > a, .cbi-tabmenu > li > a {\n\t\tpadding: 0 8px;\n\t\tline-height: 30px;\n\t}\n\n\t.table {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t}\n\n\t.tr {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-wrap: wrap;\n\t\talign-items: flex-end;\n\t\tborder-top: 1px solid #ddd;\n\t\tpadding: 5px 0;\n\t\tmargin: 0 -3px;\n\t}\n\n\t.table .th,\n\t.table .td,\n\t.table .tr::before {\n\t\tflex: 2 2 33%;\n\t\talign-self: flex-start;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\tword-wrap: break-word;\n\t\tdisplay: inline-block;\n\t\tborder-top: none;\n\t\tpadding: 3px;\n\t\tbox-sizing: border-box;\n\t}\n\n\t.table .td.cbi-dropdown-open {\n\t\toverflow: visible;\n\t}\n\n\t.col-1 { flex: 1 1 30px !important; -webkit-flex: 1 1 30px !important; }\n\t.col-2 { flex: 2 2 60px !important; -webkit-flex: 2 2 60px !important; }\n\t.col-3 { flex: 3 3 90px !important; -webkit-flex: 3 3 90px !important; }\n\t.col-4 { flex: 4 4 120px !important; -webkit-flex: 4 4 120px !important; }\n\t.col-5 { flex: 5 5 150px !important; -webkit-flex: 5 5 150px !important; }\n\t.col-6 { flex: 6 6 180px !important; -webkit-flex: 6 6 180px !important; }\n\t.col-7 { flex: 7 7 210px !important; -webkit-flex: 7 7 210px !important; }\n\t.col-8 { flex: 8 8 240px !important; -webkit-flex: 8 8 240px !important; }\n\t.col-9 { flex: 9 9 270px !important; -webkit-flex: 9 9 270px !important; }\n\t.col-10 { flex: 10 10 300px !important; -webkit-flex: 10 10 300px !important; }\n\n\t.td select {\n\t\tword-wrap: normal;\n\t}\n\n\t.td[data-widget=\"button\"],\n\t.td[data-widget=\"fvalue\"] {\n\t\tflex: 1 1 17%;\n\t\ttext-align: left;\n\t}\n\n\t.td.cbi-value-field {\n\t\talign-self: flex-start;\n\t}\n\n\t.td.cbi-value-field .cbi-button {\n\t\twidth: 100%;\n\t}\n\n\t.table.cbi-section-table {\n\t\tborder: none;\n\t\tbackground: none;\n\t\tmargin: 0;\n\t}\n\n\t.tr.table-titles,\n\t.cbi-section-table-titles,\n\t.cbi-section-table-descr {\n\t\tdisplay: none;\n\t}\n\n\t.cbi-section-table-row {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-wrap: wrap;\n\t\tmargin: 0 0 .5em 0;\n\t}\n\n\t.cbi-section-table + .cbi-section-create {\n\t\tpadding-top: 0;\n\t}\n\n\t.tr[data-title]::before {\n\t\tdisplay: block;\n\t\tflex: 1 1 100%;\n\t\tbackground: #f5f5f5 !important;\n\t\tfont-size: 16px;\n\t\tborder-bottom: 1px solid #ddd;\n\t}\n\n\t.td[data-title]::before,\n\t.td[data-description]::after {\n\t\tdisplay: block;\n\t}\n\n\t.td[data-title] ~ .td.cbi-section-actions {\n\t\talign-self: flex-start;\n\t}\n\n\t.td[data-title] ~ .td.cbi-section-actions::before {\n\t\tdisplay: block;\n\t\tcontent: \"\\a0\";\n\t}\n\n\t.td.cbi-section-actions {\n\t\toverflow: initial;\n\t\tmax-width: 100%;\n\t\tpadding: 3px 2px;\n\t}\n\n\t.hide-sm,\n\t.hide-xs {\n\t\tdisplay: none !important;\n\t}\n\n\t.td.cbi-value-field {\n\t\tflex-basis: 100%;\n\t}\n\n\t.td.cbi-value-field[data-widget=\"dvalue\"] {\n\t\tflex-basis: 50%;\n\t}\n\n\t.td.cbi-value-field[data-widget=\"button\"],\n\t.td.cbi-value-field[data-widget=\"fvalue\"] {\n\t\tflex-basis: 25%;\n\t\ttext-align: left;\n\t}\n\n\t.cbi-section-table .tr:hover .td,\n\t.cbi-section-table .tr:hover .th,\n\t.cbi-section-table .tr:hover::before {\n\t\tbackground-color: transparent;\n\t}\n\n\t.cbi-value {\n\t\tpadding-bottom: .5em;\n\t\tborder-bottom: 1px solid #ddd;\n\t\tmargin-bottom: .5em;\n\t}\n\n\t.cbi-value label.cbi-value-title {\n\t\tfloat: none;\n\t\tfont-weight: bold;\n\t}\n\n\t.cbi-value-field, .cbi-dropdown {\n\t\twidth: 100%;\n\t\tmargin: 0;\n\t}\n\n\tinput, textarea, select,\n\t.cbi-dropdown > ul > li input[type=\"text\"] {\n\t\tfont-size: 16px !important;\n\t\tline-height: 28px;\n\t\theight: auto;\n\t}\n\n\tselect, input[type=\"text\"], input[type=\"password\"] {\n\t\twidth: 100%;\n\t\theight: 30px;\n\t}\n\n\tinput.cbi-input-password {\n\t\twidth: calc(100% - 25px);\n\t}\n\n\t[data-dynlist] {\n\t\tdisplay: block;\n\t}\n\n\t[data-dynlist] > .add-item > input {\n\t\twidth: calc(100% - 21px);\n\t}\n\n\t[data-dynlist] > .add-item > .cbi-button {\n\t\tmargin-right: -1px;\n\t}\n\n\tinput[type=\"text\"] + .cbi-button,\n\tinput[type=\"password\"] + .cbi-button,\n\tselect + .cbi-button {\n\t\tfont-size: 14px !important;\n\t\tline-height:  28px;\n\t\theight: 30px;\n\t\tbox-sizing: border-box;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\t.cbi-value-field input[type=\"checkbox\"],\n\t.cbi-value-field input[type=\"radio\"] {\n\t\tmargin: 0;\n\t}\n\n\t.btn, .cbi-button {\n\t\tfont-size: 14px !important;\n\t\tpadding: 4px 8px;\n\t}\n\n\t.actions,\n\t.cbi-page-actions {\n\t\tborder-top: none;\n\t\tmargin-top: -.5em;\n\t\tpadding: 8px;\n\t}\n\n\t[data-page=\"admin-status-overview\"] .cbi-section:nth-of-type(1) .td:first-child,\n\t[data-page=\"admin-status-overview\"] .cbi-section:nth-of-type(2) .td:first-child {\n\t\tflex-grow: 1;\n\t}\n\n\theader .pull-right .label {\n\t\twhite-space: normal;\n\t\tdisplay: inline-block;\n\t\ttext-align: center;\n\t\tline-height: 12px;\n\t\tmargin: 1px 0;\n\t}\n\n\theader > .fill {\n\t\tpadding: 1px;\n\t}\n\n\theader > .fill > .container {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t}\n\n\theader .nav {\n\t\tflex: 3 3 80%;\n\t\tmargin: 2px 5px 2px 0;\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: flex-start;\n\t}\n\n\theader .nav a {\n\t\tpadding: 2px 6px;\n\t}\n\n\theader .pull-right {\n\t\tflex: 1 1 20%;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tpadding: 0;\n\t\tjustify-content: space-around;\n\t}\n\n\t.menu-dropdown, .dropdown-menu {\n\t\ttop: 23px;\n\t}\n\n\tbody {\n\t\tpadding-top: 30px;\n\t}\n\n\t.cbi-optionals,\n\t.cbi-section-create {\n\t\tpadding: 0 0 14px 0;\n\t}\n\n\t#cbi-network-switch_vlan .th,\n\t#cbi-network-switch_vlan .td {\n\t\tflex-basis: 12%;\n\t}\n\n\t#cbi-network-switch_vlan .td.cbi-section-actions {\n\t\tflex-basis: 100%;\n\t}\n\n\t#cbi-network-switch_vlan .td.cbi-section-actions::before {\n\t\tdisplay: none;\n\t}\n\n\t#cbi-network-switch_vlan .td.cbi-section-actions > * {\n\t\twidth: auto;\n\t\tdisplay: block;\n\t}\n\n\t#wifi_assoclist_table .td,\n\t[data-page=\"admin-status-processes\"] .td {\n\t\tflex-basis: 50% !important;\n\t}\n\n\t[data-page=\"admin-status-processes\"] .td[data-widget=\"button\"] {\n\t\tflex-basis: 33% !important;\n\t}\n\n\t[data-page=\"admin-status-processes\"] .td[data-name=\"PID\"],\n\t[data-page=\"admin-status-processes\"] .td[data-name=\"USER\"] {\n\t\tflex-basis: 25% !important;\n\t}\n\n\t[data-page=\"admin-system-fstab\"] .td[data-widget=\"button\"]::before,\n\t[data-page=\"admin-system-startup\"] .td[data-widget=\"button\"]::before,\n\t[data-page=\"admin-status-processes\"] .td[data-widget=\"button\"]::before {\n\t\tdisplay: none;\n\t}\n}\n\n@media screen and (max-device-width: 375px) {\n\t#maincontent.container {\n\t\tmargin-top: 55px;\n\t}\n\n\t.cbi-page-actions {\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: space-between;\n\t\tmargin: 0 -1px;\n\t\tpadding: 0;\n\t}\n\n\t.cbi-page-actions .cbi-button:not(.cbi-dropdown) {\n\t\tflex: 1 1 calc(50% - 2px);\n\t\tmargin: 1px !important;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\t.cbi-page-actions .cbi-button-negative,\n\t.cbi-page-actions .cbi-button-primary,\n\t.cbi-page-actions .cbi-button-apply {\n\t\tflex-basis: calc(100% - -2px);\n\t}\n\n\t.cbi-section-actions .cbi-button {\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\tbody[data-page=\"admin-network-wireless\"] .td[data-name=\"_badge\"] {\n\t\tmax-width: 50px;\n\t\talign-self: center;\n\t}\n\n\tbody[data-page=\"admin-network-wireless\"] .td[data-name=\"_badge\"] .ifacebadge {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tflex-direction: column;\n\t}\n\n\tbody[data-page=\"admin-network-wireless\"] .td[data-name=\"_stat\"] {\n\t\tflex-basis: 60%;\n\t}\n\n\tbody[data-page=\"admin-network-network\"] .td.cbi-section-actions::before,\n\tbody[data-page=\"admin-network-wireless\"] .td.cbi-section-actions::before {\n\t\tcontent: none !important;\n\t}\n}\n\n@media screen and (max-device-width: 200px) {\n\t#maincontent.container {\n\t\tmargin-top: 230px;\n\t}\n}\n\n@media screen and (max-width: 375px) {\n\t.td .ifacebox {\n\t\twidth: 100%;\n\t\tmargin: 0 !important;\n\t\tflex-direction: row;\n\t}\n\n\t.td .ifacebox .ifacebox-head {\n\t\tmin-width: 25%;\n\t\tjustify-content: space-around;\n\t}\n\n\t.td .ifacebox .ifacebox-head,\n\t.td .ifacebox .ifacebox-body {\n\t\tdisplay: flex;\n\t\tborder-bottom: none;\n\t\talign-items: center;\n\t}\n\n\t.td .ifacebox .ifacebox-head > *,\n\t.ifacebox .ifacebox-body > * {\n\t\tmargin: .125em;\n\t}\n}\n"
  },
  {
    "path": "luci/themes/luci-theme-lightblue/htdocs/luci-static/resources/menu-lightblue.js",
    "content": "'use strict';\n'require baseclass';\n'require ui';\n\nreturn baseclass.extend({\n\t__init__: function() {\n\t\tui.menu.load().then(L.bind(this.render, this));\n\t},\n\n\trender: function(tree) {\n\t\tvar node = tree,\n\t\t    url = '';\n\n\t\tthis.renderModeMenu(tree);\n\n\t\tif (L.env.dispatchpath.length >= 3) {\n\t\t\tfor (var i = 0; i < 3 && node; i++) {\n\t\t\t\tnode = node.children[L.env.dispatchpath[i]];\n\t\t\t\turl = url + (url ? '/' : '') + L.env.dispatchpath[i];\n\t\t\t}\n\n\t\t\tif (node)\n\t\t\t\tthis.renderTabMenu(node, url);\n\t\t}\n\n\t\tdocument.addEventListener('poll-start', this.handleBodyMargin);\n\t\tdocument.addEventListener('poll-stop', this.handleBodyMargin);\n\t\tdocument.addEventListener('uci-new-changes', this.handleBodyMargin);\n\t\tdocument.addEventListener('uci-clear-changes', this.handleBodyMargin);\n\t\twindow.addEventListener('resize', this.handleBodyMargin);\n\n\t\tthis.handleBodyMargin();\n\t},\n\n\trenderTabMenu: function(tree, url, level) {\n\t\tvar container = document.querySelector('#tabmenu'),\n\t\t    ul = E('ul', { 'class': 'tabs' }),\n\t\t    children = ui.menu.getChildren(tree),\n\t\t    activeNode = null;\n\n\t\tfor (var i = 0; i < children.length; i++) {\n\t\t\tvar isActive = (L.env.dispatchpath[3 + (level || 0)] == children[i].name),\n\t\t\t    activeClass = isActive ? ' active' : '',\n\t\t\t    className = 'tabmenu-item-%s %s'.format(children[i].name, activeClass);\n\n\t\t\tul.appendChild(E('li', { 'class': className }, [\n\t\t\t\tE('a', { 'href': L.url(url, children[i].name) }, [ _(children[i].title) ] )]));\n\n\t\t\tif (isActive)\n\t\t\t\tactiveNode = children[i];\n\t\t}\n\n\t\tif (ul.children.length == 0)\n\t\t\treturn E([]);\n\n\t\tcontainer.appendChild(ul);\n\t\tcontainer.style.display = '';\n\n\t\tif (activeNode)\n\t\t\tthis.renderTabMenu(activeNode, url + '/' + activeNode.name, (level || 0) + 1);\n\n\t\treturn ul;\n\t},\n\n\trenderMainMenu: function(tree, url, level) {\n\t\tvar ul = level ? E('ul', { 'class': 'dropdown-menu' }) : document.querySelector('#topmenu'),\n\t\t    children = ui.menu.getChildren(tree);\n\n\t\tif (children.length == 0 || level > 1)\n\t\t\treturn E([]);\n\n\t\tfor (var i = 0; i < children.length; i++) {\n\t\t\tvar submenu = this.renderMainMenu(children[i], url + '/' + children[i].name, (level || 0) + 1),\n\t\t\t    subclass = (!level && submenu.firstElementChild) ? 'dropdown' : null,\n\t\t\t    linkclass = (!level && submenu.firstElementChild) ? 'menu' : null,\n\t\t\t    linkurl = submenu.firstElementChild ? '#' : L.url(url, children[i].name);\n\n\t\t\tvar li = E('li', { 'class': subclass }, [\n\t\t\t\tE('a', { 'class': linkclass, 'href': linkurl }, [ _(children[i].title) ]),\n\t\t\t\tsubmenu\n\t\t\t]);\n\n\t\t\tul.appendChild(li);\n\t\t}\n\n\t\tul.style.display = '';\n\n\t\treturn ul;\n\t},\n\n\trenderModeMenu: function(tree) {\n\t\tvar ul = document.querySelector('#modemenu'),\n\t\t    children = ui.menu.getChildren(tree);\n\n\t\tfor (var i = 0; i < children.length; i++) {\n\t\t\tvar isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[0] : i == 0);\n\n\t\t\tul.appendChild(E('li', { 'class': isActive ? 'active' : null }, [\n\t\t\t\tE('a', { 'href': L.url(children[i].name) }, [ _(children[i].title) ]),\n\t\t\t\t' ',\n\t\t\t\tE('span', { 'class': 'divider' }, [ '|' ])\n\t\t\t]));\n\n\t\t\tif (isActive)\n\t\t\t\tthis.renderMainMenu(children[i], children[i].name);\n\t\t}\n\n\t\tif (ul.children.length > 1)\n\t\t\tul.style.display = '';\n\t},\n\n\thandleBodyMargin: function(ev) {\n\t\tvar body = document.querySelector('body'),\n\t\t    head = document.querySelector('header');\n\n\t\tbody.style.marginTop = head.offsetHeight + 'px';\n\t}\n});\n"
  },
  {
    "path": "luci/themes/luci-theme-lightblue/luasrc/view/themes/lightblue/footer.htm",
    "content": "<%#\n Copyright 2008 Steven Barth <steven@midlink.org>\n Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>\n Copyright 2012 David Menting <david@nut-bolt.nl>\n Licensed to the public under the Apache License 2.0.\n-%>\n\n<% local ver = require \"luci.version\" %>\n\n   <footer>\n    <div style=\"height:40px;font-size:95%;color:#999;font-family: Arial,sans-serif;\"><%= ver.distversion %>&nbsp;&#169;&nbsp132lan.ru&nbsp;<%= os.date(\"%Y\") %>&nbsp;</div>\n    <ul class=\"breadcrumb pull-right\" id=\"modemenu\" style=\"display:none\"></ul>\n   </footer>\n  </div>\n  <script type=\"text/javascript\">L.require('menu-lightblue')</script>\n </body>\n</html>\n\n"
  },
  {
    "path": "luci/themes/luci-theme-lightblue/luasrc/view/themes/lightblue/header.htm",
    "content": "<%#\n Copyright 2008 Steven Barth <steven@midlink.org>\n Copyright 2008-2016 Jo-Philipp Wich <jow@openwrt.org>\n Copyright 2012 David Menting <david@nut-bolt.nl>\n Licensed to the public under the Apache License 2.0.\n-%>\n\n<%\n\tlocal sys  = require \"luci.sys\"\n\tlocal util = require \"luci.util\"\n\tlocal http = require \"luci.http\"\n\tlocal disp = require \"luci.dispatcher\"\n\n\tlocal boardinfo = util.ubus(\"system\", \"board\")\n\n\tlocal node = disp.context.dispatched\n\n\t-- send as HTML5\n\thttp.prepare_content(\"text/html\")\n-%>\n<!DOCTYPE html>\n<html lang=\"<%=luci.i18n.context.lang%>\">\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title><%=striptags( (boardinfo.hostname or \"?\") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>\n\t\t<meta name=\"viewport\" content=\"initial-scale=1.0\">\n\t\t<link rel=\"stylesheet\" href=\"<%=media%>/cascade.css\">\n\t\t<link rel=\"stylesheet\" media=\"only screen and (max-device-width: 854px)\" href=\"<%=media%>/mobile.css\" type=\"text/css\" />\n\t\t<link rel=\"shortcut icon\" href=\"<%=media%>/favicon.ico\">\n\t\t<% if node and node.css then %>\n\t\t\t<link rel=\"stylesheet\" href=\"<%=resource%>/<%=node.css%>\">\n\t\t<% end -%>\n\t\t<% if css then %>\n\t\t\t<style title=\"text/css\"><%= css %></style>\n\t\t<% end -%>\n\t\t<script src=\"<%=url('admin/translations', luci.i18n.context.lang)%><%# ?v=PKG_VERSION %>\"></script>\n\t\t<script src=\"<%=resource%>/cbi.js\"></script>\n\t</head>\n\n\t<body class=\"lang_<%=luci.i18n.context.lang%> <% if node then %><%= striptags( node.title ) %><%- end %>\" data-page=\"<%= pcdata(table.concat(disp.context.requestpath, \"-\")) %>\">\n\t\t<header>\n\t\t\t<div class=\"fill\">\n\t\t\t\t<div class=\"container\">\n\t\t\t\t\t<a class=\"brand\" href=\"/\"></a>\n\t\t\t\t\t<ul class=\"nav\" id=\"topmenu\" style=\"display:none\"></ul>\n\t\t\t\t\t<div id=\"indicators\" class=\"pull-right\"></div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<div id=\"maincontent\" class=\"container\">\n\t\t\t<%- if luci.sys.process.info(\"uid\") == 0 and luci.sys.user.getuser(\"root\") and not luci.sys.user.getpasswd(\"root\") then -%>\n\t\t\t\t<div class=\"alert-message warning\">\n\t\t\t\t\t<h4><%:No password set!%></h4>\n\t\t\t\t\t<p><%:There is no password set on this router. Please configure a root password to protect the web interface.%></p>\n\t\t\t\t\t<% if disp.lookup(\"admin/system/admin\") then %>\n\t\t\t\t\t  <div class=\"right\"><a class=\"btn\" href=\"<%=url(\"admin/system/admin\")%>\"><%:Go to password configuration...%></a></div>\n\t\t\t\t\t<% end %>\n\t\t\t\t</div>\n\t\t\t<%- end -%>\n\n\t\t\t<noscript>\n\t\t\t\t<div class=\"alert-message warning\">\n\t\t\t\t\t<h4><%:JavaScript required!%></h4>\n\t\t\t\t\t<p><%:You must enable JavaScript in your browser or LuCI will not work properly.%></p>\n\t\t\t\t</div>\n\t\t\t</noscript>\n\n\t\t\t<div id=\"tabmenu\" style=\"display:none\"></div>\n"
  },
  {
    "path": "luci/themes/luci-theme-lightblue/root/etc/uci-defaults/30_luci-theme-lightblue",
    "content": "#!/bin/sh\n\nif [ \"$PKG_UPGRADE\" != 1 ]; then\n\tuci get luci.themes.Lightblue >/dev/null 2>&1 || \\\n\tuci batch <<-EOF\n\t\tset luci.themes.Lightblue=/luci-static/lightblue\n\t\tset luci.main.mediaurlbase=/luci-static/lightblue\n\t\tcommit luci\n\tEOF\nfi\n\nexit 0\n"
  },
  {
    "path": "luci/themes/luci-theme-merona/Makefile",
    "content": "#\n# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Bootstrap Merona Theme\nLUCI_DEPENDS:= +luci-lua-runtime\n\nPKG_LICENSE:=Apache-2.0\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/themes/luci-theme-merona/htdocs/luci-static/merona/cascade.css",
    "content": "/*!\n * LuCI Bootstrap Theme\n * Copyright 2012 Nut & Bolt\n * By David Menting <david@nut-bolt.nl>\n * Based on Bootstrap v1.4.0\n *\n * Copyright 2011 Twitter, Inc\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world @twitter by @mdo and @fat.\n */\n/* Reset.less\n * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here\tthat cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc).\n * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */\n\n:root {\n\t--background-color-delta-l-sign: -1;\n\t--background-color-h: 0;\n\t--background-color-s: 0%;\n\t--background-color-l: 100%;\n\n\t--background-color-high-hsl:\n\t\tvar(--background-color-h),\n\t\tvar(--background-color-s),\n\t\tvar(--background-color-l);\n\t--background-color-high: hsl(var(--background-color-high-hsl));\n\n\t--background-color-medium-hsl:\n\t\tvar(--background-color-h),\n\t\tvar(--background-color-s),\n\t\tcalc(var(--background-color-l) + var(--background-color-delta-l-sign) * calc(6 / 255 * 100%));\n\t--background-color-medium: hsl(var(--background-color-medium-hsl));\n\n\t--background-color-low-hsl:\n\t\tvar(--background-color-h),\n\t\tvar(--background-color-s),\n\t\tcalc(var(--background-color-l) + var(--background-color-delta-l-sign) * calc(10 / 255 * 100%));\n\t--background-color-low: hsl(var(--background-color-low-hsl));\n\n\t--text-color-delta-l-sign: 1;\n\t--text-color-h: 0;\n\t--text-color-s: 0%;\n\t--text-color-l: 0%;\n\n\t--text-color-highest-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tvar(--text-color-l);\n\t--text-color-highest: hsl(var(--text-color-highest-hsl));\n\n\t--text-color-high-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tcalc(var(--text-color-l) + var(--text-color-delta-l-sign) * calc(64 / 255 * 100%));\n\t--text-color-high: hsl(var(--text-color-high-hsl));\n\n\t--text-color-medium-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tcalc(var(--text-color-l) + var(--text-color-delta-l-sign) * calc(128 / 255 * 100%));\n\t--text-color-medium: hsl(var(--text-color-medium-hsl));\n\n\t--text-color-low-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tcalc(var(--text-color-l) + var(--text-color-delta-l-sign) * calc(191 / 255 * 100%));\n\t--text-color-low: hsl(var(--text-color-low-hsl));\n\n\t--border-color-delta-l-sign: -1;\n\t--border-color-h: var(--background-color-h);\n\t--border-color-s: var(--background-color-s);\n\t--border-color-l: var(--background-color-l);\n\n\t--border-color-high-hsl:\n\t\tvar(--border-color-h),\n\t\tvar(--border-color-s),\n\t\tcalc(var(--border-color-l) + var(--border-color-delta-l-sign) * calc(51 / 255 * 100%));\n\t--border-color-high: hsl(var(--border-color-high-hsl));\n\n\t--border-color-medium-hsl:\n\t\tvar(--border-color-h),\n\t\tvar(--border-color-s),\n\t\tcalc(var(--border-color-l) + var(--border-color-delta-l-sign) * calc(34 / 255 * 100%));\n\t--border-color-medium: hsl(var(--border-color-medium-hsl));\n\n\t--border-color-low-hsl:\n\t\tvar(--border-color-h),\n\t\tvar(--border-color-s),\n\t\tcalc(var(--border-color-l) + var(--border-color-delta-l-sign) * calc(17 / 255 * 100%));\n\t--border-color-low: hsl(var(--border-color-low-hsl));\n\n\t/* ИЗМЕНЕНИЕ: Заменяем голубые цвета на зеленые */\n\t--primary-color-high: #2e7d32;\n\t--primary-color-medium: #388e3c;\n\t--primary-color-low: #1b5e20;\n\t--on-primary-color: var(--background-color-high);\n\n\t--error-color-high-rgb: 246, 43, 18;\n\t--error-color-high: rgb(var(--error-color-high-rgb));\n\t--error-color-medium: #e8210d;\n\t--error-color-low: #d00000;\n\t--on-error-color: var(--background-color-high);\n\n\t--success-color-high-rgb: 76, 175, 80;\n\t--success-color-high: rgb(var(--success-color-high-rgb));\n\t--success-color-medium: #4caf50;\n\t--success-color-low: #388e3c;\n\t--on-success-color: var(--background-color-high);\n\n\t--warn-color-high: #ff9800;\n\t--warn-color-medium: #ffa726;\n\t--warn-color-low: #ffb74d;\n\t--on-warn-color: var(--text-color-highest);\n\n\t--disabled-opacity: .7;\n\n\tcolor-scheme: light;\n}\n\n:root[data-darkmode=\"true\"] {\n\t--background-color-delta-l-sign: 1;\n\t--background-color-h: 0;\n\t--background-color-s: 0%;\n\t--background-color-l: calc(34 / 255 * 100%);\n\t--text-color-delta-l-sign: -1;\n\t--text-color-h: 0;\n\t--text-color-s: 0%;\n\t--text-color-l: 100%;\n\t--border-color-delta-l-sign: 1;\n\n\t/* ИЗМЕНЕНИЕ: Темно-зеленые тона для темной темы */\n\t--primary-color-high: #4caf50;\n\t--primary-color-medium: #388e3c;\n\t--primary-color-low: #2e7d32;\n\n\t--error-color-high-rgb: 209, 86, 83;\n\t--error-color-medium: #bf4e4c;\n\t--error-color-low: #b14946;\n\n\t--success-color-high-rgb: 76, 175, 80;\n\t--success-color-medium: #4caf50;\n\t--success-color-low: #388e3c;\n\n\t--warn-color-high: #ff9800;\n\t--warn-color-medium: #f57c00;\n\t--warn-color-low: #ef6c00;\n\t--on-warn-color: var(--background-color-high);\n\n\t--disabled-opacity: .4;\n\n\tcolor-scheme: dark;\n}\n\n* {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbox-sizing: border-box;\n}\n\nabbr[title], acronym[title] {\n\tborder-bottom: 1px dotted;\n\tfont-weight: inherit;\n\tcursor: help;\n}\n\ntable {\n\tborder-collapse: collapse;\n\tborder-spacing: 0;\n}\n\nol, ul {\n\tlist-style: none;\n}\n\nhtml {\n\tfont-size: 100%;\n\t-webkit-text-size-adjust: 100%;\n\t-ms-text-size-adjust: 100%;\n}\n\na:focus {\n\toutline: none;\n}\n\na:hover, a:active {\n\toutline: none;\n}\n\nfooter,\nheader,\nnav,\nsection {\n\tdisplay: block;\n}\n\nsub, sup {\n\tfont-size: 75%;\n\tline-height: 0;\n\tposition: relative;\n\tvertical-align: baseline;\n}\n\nsup {\n\ttop: -0.5em;\n}\n\nsub {\n\tbottom: -0.25em;\n}\n\nimg {\n\t-ms-interpolation-mode: bicubic;\n}\n\nbutton,\ninput,\nselect,\noption,\ntextarea {\n\tfont-size: 100%;\n\tmargin: 0;\n\tbox-sizing: border-box;\n\tvertical-align: baseline;\n\tline-height: normal;\n}\n\nbutton::-moz-focus-inner, input::-moz-focus-inner {\n\tborder: 0;\n\tpadding: 0;\n}\n\nbutton,\ninput[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n\tcursor: pointer;\n\t-webkit-appearance: button;\n\tword-break: break-all;\n}\n\nbutton[disabled],\ninput[type=\"button\"][disabled],\ninput[type=\"reset\"][disabled],\ninput[type=\"submit\"][disabled] {\n\topacity: 0.7;\n}\n\ninput[type=\"search\"] {\n\t-webkit-appearance: textfield;\n\tbox-sizing: content-box;\n}\n\ninput[type=\"search\"]::-webkit-search-decoration {\n\t-webkit-appearance: none;\n}\n\ntextarea {\n\toverflow: auto;\n\tvertical-align: top;\n}\n\n/*\n * Scaffolding\n * Basic and global styles for generating a grid system, structural layout, and page templates\n * ------------------------------------------------------------------------------------------- */\nbody {\n\tbackground-color: #fff;\n\tfont-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n\tfont-size: 13px;\n\tfont-weight: normal;\n\tline-height: 18px;\n\tcolor: #404040;\n\tpadding: 18px 5px 5px 5px;\n\tmargin-top: 40px;\n}\n\n.container {\n\twidth: 100%;\n\tmax-width: 940px;\n\tmargin-left: auto;\n\tmargin-right: auto;\n\tzoom: 1;\n}\n\n.container:before, .container:after {\n\tdisplay: table;\n\tcontent: \"\";\n\tzoom: 1;\n}\n\n.container:after {\n\tclear: both;\n}\n\n/* ИЗМЕНЕНИЕ: Цвет ссылок на зеленый */\na {\n\tcolor: #2e7d32;\n\ttext-decoration: none;\n\tline-height: inherit;\n\tfont-weight: inherit;\n}\n\na:hover {\n\tcolor: #1b5e20;\n\ttext-decoration: underline;\n}\n\n.pull-right {\n\tfloat: right;\n}\n\n.pull-left {\n\tfloat: left;\n}\n\n.nowrap {\n\twhite-space: nowrap;\n}\n\n/* Typography.less\n * Headings, body text, lists, code, and more for a versatile and durable typography system\n * ---------------------------------------------------------------------------------------- */\np,\n.cbi-map-descr,\n.cbi-section-descr,\n.table .tr.cbi-section-table-descr .th {\n\tfont-size: 13px;\n\tfont-weight: normal;\n\tline-height: 18px;\n\tmargin-bottom: 9px;\n}\n\np small {\n\tfont-size: 11px;\n\tcolor: #bfbfbf;\n}\n\n/* ИЗМЕНЕНИЕ: Цвет заголовков на зеленый */\nh1,\nh2,\nh3, legend,\nh4,\nh5,\nh6 {\n\tfont-weight: bold;\n\tcolor: #2e7d32;\n}\n\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small {\n\tcolor: #bfbfbf;\n}\n\nh1 {\n\tmargin-bottom: 18px;\n\tfont-size: 30px;\n\tline-height: 36px;\n}\n\nh1 small {\n\tfont-size: 18px;\n}\n\nh2 {\n\tfont-size: 24px;\n\tline-height: 36px;\n}\n\nh2 small {\n\tfont-size: 14px;\n}\n\nh3, legend,\nh4,\nh5,\nh6 {\n\tline-height: 36px;\n}\n\nh3, legend {\n\tfont-size: 18px;\n}\n\nh3 small {\n\tfont-size: 14px;\n}\n\nh4 {\n\tfont-size: 16px;\n}\n\nh4 small {\n\tfont-size: 12px;\n}\n\nh5 {\n\tfont-size: 14px;\n}\n\nh6 {\n\tfont-size: 13px;\n\tcolor: #bfbfbf;\n\ttext-transform: uppercase;\n}\n\nul, ol {\n\tmargin: 0 0 18px 25px;\n}\n\nul ul,\nul ol,\nol ol,\nol ul {\n\tmargin-bottom: 0;\n}\n\nul {\n\tlist-style: disc;\n}\n\nol {\n\tlist-style: decimal;\n}\n\nli {\n\tline-height: 18px;\n\tcolor: #808080;\n}\n\nul.unstyled {\n\tlist-style: none;\n\tmargin-left: 0;\n}\n\ndl {\n\tmargin-bottom: 18px;\n}\n\ndl dt, dl dd {\n\tline-height: 18px;\n}\n\ndl dt {\n\tfont-weight: bold;\n}\n\ndl dd {\n\tmargin-left: 9px;\n}\n\nhr {\n\tmargin: 20px 0 19px;\n\tborder: 0;\n\tborder-bottom: 1px solid #eee;\n}\n\nstrong {\n\tfont-style: inherit;\n\tfont-weight: bold;\n}\n\nem {\n\tfont-style: italic;\n\tfont-weight: inherit;\n\tline-height: inherit;\n}\n\nsmall { font-size: 0.9em }\n\naddress {\n\tdisplay: block;\n\tline-height: 18px;\n\tmargin-bottom: 18px;\n}\n\ncode, pre {\n\tpadding: 0 3px 2px;\n\tfont-family: Monaco, Andale Mono, Courier New, monospace;\n\tfont-size: 12px;\n\tborder-radius: 3px;\n}\n\ncode {\n\tbackground-color: #81c784;\n\tcolor: rgba(0, 0, 0, 0.75);\n\tpadding: 1px 3px;\n}\n\npre {\n\tbackground-color: #f5f5f5;\n\tdisplay: block;\n\tpadding: 8.5px;\n\tmargin: 0 0 18px;\n\tline-height: 18px;\n\tfont-size: 12px;\n\tborder: 1px solid #ccc;\n\tborder: 1px solid rgba(0, 0, 0, 0.15);\n\tborder-radius: 3px;\n\twhite-space: pre;\n\twhite-space: pre-wrap;\n\tword-wrap: break-word;\n}\n\n/* Forms.less\n * Base styles for various input types, form layouts, and states\n * ------------------------------------------------------------- */\nform {\n\tmargin-bottom: 18px;\n}\n\nfieldset {\n\tmargin-bottom: 9px;\n\tpadding-top: 9px;\n}\n\nfieldset legend {\n\tdisplay: block;\n\tfont-size: 19.5px;\n\tline-height: 1;\n\tcolor: #404040;\n\tpadding-top: 20px;\n\n}\nform .cbi-tab-descr {\n\tline-height: 18px;\n\tmargin-bottom: 18px;\n}\n\nform .clearfix,\n.cbi-value {\n\tmargin-bottom: 18px;\n\tzoom: 1;\n}\n\nform .clearfix:before, form .clearfix:after,\n.cbi-value:before, .cbi-value:after  {\n\tdisplay: table;\n\tcontent: \"\";\n\tzoom: 1;\n}\n\nform .clearfix:after,\n.cbi-value:after {\n\tclear: both;\n}\n\nlabel,\ninput,\nbutton,\nselect,\ntextarea {\n\tfont-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n\tfont-size: 13px;\n\tfont-weight: normal;\n\tline-height: normal;\n}\n\nform .input,\n.cbi-value-field {\n\tmargin-left: 200px;\n}\n\n.cbi-value label.cbi-value-title {\n\tpadding-top: 6px;\n\tfont-size: 13px;\n\tline-height: 18px;\n\tfloat: left;\n\twidth: 180px;\n\ttext-align: right;\n\tcolor: #404040;\n}\n\ninput[type=checkbox], input[type=radio] {\n\tcursor: pointer;\n}\n\nlabel > input[type=\"checkbox\"],\nlabel > input[type=\"radio\"] {\n\tvertical-align: bottom;\n\tmargin: 0;\n}\n\ninput,\ntextarea,\nselect,\n.cbi-dropdown:not(.btn):not(.cbi-button),\n.uneditable-input {\n\tdisplay: inline-block;\n\twidth: 210px;\n\tpadding: 4px;\n\tfont-size: 13px;\n\tline-height: 18px;\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n}\n\ninput,\nselect,\n.cbi-dropdown:not(.btn):not(.cbi-button),\n.uneditable-input {\n\theight: 30px;\n}\n\n.uneditable-input {\n\tcolor: #808080;\n}\n\n.cbi-dropdown:not(.btn):not(.cbi-button),\n.cbi-dynlist {\n\tmin-width: 210px;\n\tmax-width: 400px;\n\twidth: auto;\n}\n\n.cbi-dynlist {\n\theight: auto;\n\tmin-height: 30px;\n\tdisplay: inline-flex;\n\tflex-direction: column;\n}\n\n.cbi-dynlist > .item {\n\tmargin-bottom: 4px;\n\tbox-shadow: 0 0 2px #ccc;\n\tbackground: #fff;\n\tpadding: 2px 2em 2px 4px;\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n\tposition: relative;\n\tpointer-events: none;\n\toverflow: hidden;\n\tword-break: break-all;\n}\n\n.cbi-dynlist > .item::after {\n\tcontent: \"×\";\n\tposition: absolute;\n\tdisplay: inline-flex;\n\talign-items: center;\n\ttop: -1px;\n\tright: -1px;\n\tbottom: -1px;\n\tpadding: 0 6px;\n\tborder: 1px solid #ccc;\n\tborder-radius: 0 3px 3px 0;\n\tfont-weight: bold;\n\tcolor: #2e7d32;\n\tpointer-events: auto;\n}\n\n.cbi-dynlist > .add-item {\n\tdisplay: flex;\n}\n\n.cbi-dynlist > .add-item > input,\n.cbi-dynlist > .add-item > button {\n\tflex: 1 1 auto;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\nselect {\n\tpadding: initial;\n\tbackground: #fff;\n\tbox-shadow: inset 0 -1px 3px rgba(0, 0, 0, 0.1);\n}\n\ninput[type=checkbox], input[type=radio] {\n\twidth: auto;\n\theight: auto;\n\tpadding: 0;\n\tmargin: 3px 0;\n\t*margin-top: 0;\n\t/* IE6-7 */\n\n\tline-height: normal;\n\tborder: none;\n}\n\ninput[type=file] {\n\tbackground-color: #fff;\n\tpadding: initial;\n\tborder: initial;\n\tline-height: initial;\n\tbox-shadow: none;\n\twidth: auto !important;\n}\n\ninput[type=button], input[type=reset], input[type=submit] {\n\twidth: auto;\n\theight: auto;\n}\n\nselect[multiple] {\n\theight: inherit;\n\tbackground-color: #fff;\n}\n\n.td > input[type=text],\n.td > input[type=password],\n.td > select,\n.td > .cbi-dropdown:not(.btn):not(.cbi-button),\n.cbi-dynlist > .add-item > .cbi-dropdown {\n\twidth: 100%;\n}\n\n.uneditable-input {\n\tbackground-color: #fff;\n\tdisplay: block;\n\tborder-color: #eee;\n\tbox-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);\n\tcursor: not-allowed;\n}\n\n::-moz-placeholder {\n\tcolor: #bfbfbf;\n}\n\n::-webkit-input-placeholder {\n\tcolor: #bfbfbf;\n}\n\n.item::after, .btn, .cbi-button, input, button, textarea {\n\ttransition: border linear 0.2s, box-shadow linear 0.2s;\n\tbox-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n/* ИЗМЕНЕНИЕ: Цвет фокуса на зеленый */\n.item:hover::after,\n.btn:hover, .cbi-button:hover, button:hover,\ninput:focus, textarea:focus {\n\toutline: 0;\n\tborder-color: rgba(46, 125, 50, 0.8) !important;\n\tbox-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(46, 125, 50, 0.6);\n\ttext-decoration: none;\n}\n\ninput[type=file]:focus, input[type=checkbox]:focus, select:focus {\n\tbox-shadow: none;\n\toutline: 1px dotted #666;\n}\n\ninput[disabled],\nbutton[disabled],\nselect[disabled],\ntextarea[disabled],\ninput[readonly],\nbutton[readonly],\nselect[readonly],\ntextarea[readonly] {\n\tbackground-color: #f5f5f5;\n\tborder-color: #ddd;\n\tpointer-events: none;\n\tcursor: default;\n}\n\nselect[readonly],\ntextarea[readonly] {\n\tpointer-events: auto;\n\tcursor: auto;\n}\n\n.cbi-optionals,\n.cbi-section-create {\n\tpadding: 0 0 10px 10px;\n}\n\n.cbi-section-create {\n\tmargin: -3px;\n\tdisplay: inline-flex;\n\talign-items: center;\n}\n\n.cbi-section-create > * {\n\tmargin: 3px;\n\tflex: 1 1 auto;\n}\n\n.cbi-section-create > * > input {\n\twidth: 100%;\n}\n\n.actions,\n.cbi-page-actions {\n\tbackground: #f5f5f5;\n\tmargin-bottom: 18px;\n\tpadding: 17px 20px 18px 17px;\n\tborder-top: 1px solid #ddd;\n\tborder-radius: 0 0 3px 3px;\n\ttext-align: right;\n}\n\n.actions .secondary-action,\n.cbi-page-actions .secondary-action{\n\tfloat: right;\n}\n\n.actions .secondary-action a,\n.cbi-page-actions .secondary-action a {\n\tline-height: 30px;\n}\n\n.actions .secondary-action a:hover,\n.cbi-page-actions .secondary-action a:hover {\n\ttext-decoration: underline;\n}\n\n.cbi-page-actions > form {\n\tdisplay: inline;\n\tmargin: 0;\n}\n\n.help-inline, .help-block {\n\tfont-size: 13px;\n\tline-height: 18px;\n\tcolor: #bfbfbf;\n}\n\n.help-inline {\n\tpadding-left: 5px;\n\t*position: relative;\n\t/* IE6-7 */\n\n\t*top: -5px;\n\t/* IE6-7 */\n\n}\n\n.help-block {\n\tdisplay: block;\n\tmax-width: 600px;\n}\n\n/*\n * Tables.less\n * Tables for, you guessed it, tabular data\n * ---------------------------------------- */\n.tr { display: table-row; }\n.table[width=\"33%\"], .th[width=\"33%\"], .td[width=\"33%\"] { width: 33%; }\n.table[width=\"100%\"], .th[width=\"100%\"], .td[width=\"100%\"] { width: 100%; }\n\n.table {\n\tdisplay: table;\n\twidth: 100%;\n\tmargin-bottom: 18px;\n\tpadding: 0;\n\tfont-size: 13px;\n\tborder-collapse: collapse;\n\tposition: relative;\n}\n\n.table .th, .table .td {\n\tdisplay: table-cell;\n\tvertical-align: middle; /* Fixme */\n\tpadding: 10px 10px 9px;\n\tline-height: 18px;\n\ttext-align: left;\n}\n\n.table .tr:first-child .th {\n\tpadding-top: 9px;\n\tfont-weight: bold;\n\tvertical-align: top;\n}\n\n.table .td, .table .th {\n\tborder-top: 1px solid #ddd;\n}\n\n.tr.placeholder {\n\theight: calc(3em + 20px);\n}\n\n.tr.placeholder > .td {\n\tposition: absolute;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\ttext-align: center;\n\tline-height: 3em;\n}\n\n.tr.drag-over-above,\n.tr.drag-over-below {\n\tborder: 2px solid #2e7d32;\n\tborder-width: 2px 0 0 0;\n}\n\n.tr.drag-over-below {\n\tborder-width: 0 0 2px 0;\n}\n\n/* Patterns.less\n * Repeatable UI elements outside the base styles provided from the scaffolding\n * ---------------------------------------------------------------------------- */\nheader {\n\theight: 40px;\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tz-index: 10000;\n\toverflow: visible;\n\tcolor: rgb(244,244,244);\n}\n\n/* ИЗМЕНЕНИЕ: Цвета хедера на зеленые */\nheader a {\n\tcolor: #2e7d32;\n}\n\nheader h3 a:hover, header .brand:hover, header ul .active > a {\n\tcolor: #1b5e20;\n\ttext-decoration: none;\n}\n\nheader h3 {\n\tposition: relative;\n}\n\nheader h3 a, header .brand {\n\tfloat: left;\n\tdisplay: block;\n\tpadding: 8px 20px 12px;\n\tmargin-left: 0px;\n\tcolor: #2e7d32;\n\tfont-size: 20px;\n\tfont-family: sans-serif;\n\tfont-weight: 200;\n\tline-height: 1;\n\toutline:none;\n}\n\n.brand {\n    display: inline-block;\n    width: 135px !important;\n    height: 40px !important;\n    background-image: url('merona.svg');\n    background-size: contain;\n    background-position: center;\n    background-repeat: no-repeat;\n    margin-right: 5px;\n}\n\nheader p {\n\tmargin: 0;\n\tline-height: 40px;\n}\n\nheader .fill {\n\tbackground-color: #f2f2f2;\n\tbackground-repeat: repeat-x;\n\t-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);\n\t-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);\n\tpadding: 0 5px;\n}\n\nheader div > ul, .nav {\n\tdisplay: block;\n\tfloat: left;\n\tmargin: 0 10px 0 0;\n\tposition: relative;\n\tleft: 0;\n}\n\nheader div > ul > li, .nav > li {\n\tdisplay: block;\n\tfloat: left;\n}\n\nheader div > ul a, .nav a {\n\tdisplay: block;\n\tfloat: none;\n\tpadding: 10px 10px 11px;\n\tline-height: 19px;\n\ttext-decoration: none;\n}\n\nheader div > ul a:hover, .nav a:hover {\n\tcolor: #1b5e20;\n\ttext-decoration: none;\n}\n\nheader div > ul .active > a, .nav .active > a {\n\tbackground-color: #fff;\n\tbackground-color: rgba(0, 0, 0, 0.5);\n}\n\nheader div > ul.secondary-nav, .nav.secondary-nav {\n\tfloat: right;\n\tmargin-left: 10px;\n\tmargin-right: 0;\n}\n\nheader div > ul.secondary-nav .menu-dropdown,\n.nav.secondary-nav .menu-dropdown,\nheader div > ul.secondary-nav .dropdown-menu,\n.nav.secondary-nav .dropdown-menu {\n\tright: 0;\n\tborder: 0;\n}\n\nheader div > ul a.menu:hover,\n.nav a.menu:hover,\nheader div > ul li.open .menu,\n.nav li.open .menu,\nheader div > ul .dropdown-toggle:hover,\n.nav .dropdown-toggle:hover,\nheader div > ul .dropdown.open .dropdown-toggle,\n.nav .dropdown.open .dropdown-toggle {\n\tbackground: #444;\n\tbackground: rgba(255, 255, 255, 0.05);\n}\n\nheader div > ul .menu-dropdown,\n.nav .menu-dropdown,\nheader div > ul .dropdown-menu,\n.nav .dropdown-menu {\n\tbackground-color: #f2f2f2;\n}\n\nheader div > ul .menu-dropdown a.menu,\n.nav .menu-dropdown a.menu,\nheader div > ul .dropdown-menu a.menu,\n.nav .dropdown-menu a.menu,\nheader div > ul .menu-dropdown .dropdown-toggle,\n.nav .menu-dropdown .dropdown-toggle,\nheader div > ul .dropdown-menu .dropdown-toggle,\n.nav .dropdown-menu .dropdown-toggle {\n\tcolor: #f2f2f2;\n}\n\nheader div > ul .menu-dropdown a.menu.open,\n.nav .menu-dropdown a.menu.open,\nheader div > ul .dropdown-menu a.menu.open,\n.nav .dropdown-menu a.menu.open,\nheader div > ul .menu-dropdown .dropdown-toggle.open,\n.nav .menu-dropdown .dropdown-toggle.open,\nheader div > ul .dropdown-menu .dropdown-toggle.open,\n.nav .dropdown-menu .dropdown-toggle.open {\n\tbackground: #444;\n\tbackground: rgba(255, 255, 255, 0.05);\n}\n\nheader div > ul .menu-dropdown li a,\n.nav .menu-dropdown li a,\nheader div > ul .dropdown-menu li a,\n.nav .dropdown-menu li a {\n\tcolor: #666;\n\ttext-shadow: none !important;\n}\n\nheader div > ul .menu-dropdown li a:hover,\n.nav .menu-dropdown li a:hover,\nheader div > ul .dropdown-menu li a:hover,\n.nav .dropdown-menu li a:hover {\n\tbackground-color: #ffffff;\n\tbackground-repeat: repeat-x;\n\tcolor: #2e7d32;\n\ttext-shadow: none !important;\n}\n\nheader div > ul .menu-dropdown .active a,\n.nav .menu-dropdown .active a,\nheader div > ul .dropdown-menu .active a,\n.nav .dropdown-menu .active a {\n\tcolor: #ffffff;\n}\n\nheader div > ul .menu-dropdown .divider,\n.nav .menu-dropdown .divider,\nheader div > ul .dropdown-menu .divider,\n.nav .dropdown-menu .divider {\n\tbackground-color: #222;\n\tborder-color: #444;\n}\n\nheader ul .menu-dropdown li a, header ul .dropdown-menu li a {\n\tpadding: 4px 15px;\n}\n\nli.menu, .dropdown {\n\tposition: relative;\n}\n\n/* ИЗМЕНЕНИЕ: Цвет стрелки выпадающего меню на зеленый */\na.menu:after, .dropdown-toggle:after {\n\twidth: 0;\n\theight: 0;\n\tdisplay: inline-block;\n\tcontent: \"&darr;\";\n\ttext-indent: -99999px;\n\tvertical-align: top;\n\tmargin-top: 8px;\n\tmargin-left: 4px;\n\tborder-left: 4px solid transparent;\n\tborder-right: 4px solid transparent;\n\tborder-top: 4px solid #2e7d32;\n\topacity: 0.5;\n}\n\n.menu-dropdown, .dropdown-menu {\n\tbackground-color: #ffffff;\n\tfloat: left;\n\tposition: absolute;\n\ttop: 40px;\n\tleft: -9999px;\n\tz-index: 900;\n\tmin-width: 160px;\n\tmax-width: 220px;\n\t_width: 160px;\n\tmargin-left: 0;\n\tmargin-right: 0;\n\tpadding: 6px 0;\n\tzoom: 1;\n\tborder-color: #999;\n\tborder-color: rgba(0, 0, 0, 0.2);\n\tborder-style: solid;\n\tborder-width: 0 1px 1px;\n\tborder-radius: 0 0 6px 6px;\n\tbox-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n\tbackground-clip: padding-box;\n}\n\n.menu-dropdown li, .dropdown-menu li {\n\tfloat: none;\n\tdisplay: block;\n\tbackground-color: none;\n}\n\n.menu-dropdown .divider, .dropdown-menu .divider {\n\theight: 1px;\n\tmargin: 5px 0;\n\toverflow: hidden;\n\tbackground-color: #eee;\n\tborder-bottom: 1px solid #ffffff;\n}\n\nheader .dropdown-menu a, .dropdown-menu a {\n\tdisplay: block;\n\tpadding: 4px 15px;\n\tclear: both;\n\tfont-weight: normal;\n\tline-height: 18px;\n\tcolor: #808080;\n\ttext-shadow: 0 1px 0 #ffffff;\n}\n\nheader .dropdown-menu a:hover,\n.dropdown-menu a:hover,\nheader .dropdown-menu a.hover,\n.dropdown-menu a.hover {\n\tbackground-color: #dddddd;\n\tbackground-repeat: repeat-x;\n\tbackground-image: linear-gradient(to bottom, #eee, #ddd);\n\tcolor: #404040;\n\ttext-decoration: none;\n\tbox-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.025), inset 0 -1px rgba(0, 0, 0, 0.025);\n}\n\n.open .menu,\n.dropdown.open .menu,\n.open .dropdown-toggle,\n.dropdown.open .dropdown-toggle {\n\tcolor: #fff;\n\tbackground: #ccc;\n\tbackground: rgba(0, 0, 0, 0.3);\n}\n\n.open .menu-dropdown,\n.dropdown.open .menu-dropdown,\n.open .dropdown-menu,\n.dropdown.open .dropdown-menu {\n\tleft: 0;\n}\n\n.dropdown:hover ul.dropdown-menu {\n\tleft: 0;\n}\n\n.dropdown-menu .dropdown-menu {\n\t  position: absolute;\n\t  left: 159px;\n}\n\n.dropdown-menu li {\n\t  position: relative;\n}\n\n.tabs, .cbi-tabmenu {\n\tmargin: 0 -5px 18px;\n\tpadding: 0 2px;\n\tlist-style: none;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tbackground: linear-gradient(#fff 28px, #ddd 28px);\n\tbackground-size: 1px 29px;\n\tbackground-position: left bottom;\n}\n\n/* ИЗМЕНЕНИЕ: Цвет вкладок на зеленый */\n.tabs > li, .cbi-tabmenu > li {\n\tflex: 0 1 auto;\n\tdisplay: flex;\n\talign-items: center;\n\theight: 25px;\n\tmax-width: 48%;\n\tmargin: 4px 2px 0 2px;\n\tbackground: #fff;\n\tborder: 1px solid #ddd;\n\tborder-bottom: none;\n\tborder-radius: 4px 4px 0 0;\n\tcolor: #2e7d32;\n}\n\n.tabs > li > a, .cbi-tabmenu > li > a {\n\tpadding: 4px 6px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tcolor: inherit;\n\ttext-decoration: none;\n\tborder-radius: 4px 4px 0 0;\n\tline-height: 25px;\n\toutline: none;\n}\n\n.tabs > li:not(.active):hover, .cbi-tabmenu > .cbi-tab-disabled:hover {\n\tbackground: linear-gradient(#fff 90%, #ddd 100%);\n}\n\n.tabs > li:not(.active), .cbi-tabmenu > .cbi-tab-disabled {\n\tcolor: #999;\n\tbackground: linear-gradient(#eee 90%, #ddd 100%);\n}\n\n.cbi-tab-disabled[data-errors]::after {\n\tcontent: attr(data-errors);\n\tbackground: #c43c35;\n\tcolor: #fff;\n\tmin-width: 12px;\n\tline-height: 14px;\n\tborder-radius: 7px;\n\ttext-align: center;\n\tmargin: 0 5px 0 0;\n\tpadding: 1px 2px;\n}\n\n.cbi-tabmenu.map {\n\tmargin: 0;\n}\n\n.cbi-tabmenu.map > li {\n\tfont-size: 16.5px;\n\tfont-weight: bold;\n}\n\n.cbi-tabcontainer > fieldset.cbi-section[id] > legend {\n\tdisplay: none;\n}\n\n.tabs .menu-dropdown, .tabs .dropdown-menu {\n\ttop: 35px;\n\tborder-width: 1px;\n\tborder-radius: 0 6px 6px 6px;\n}\n\n.tabs a.menu:after, .tabs .dropdown-toggle:after {\n\tborder-top-color: #999;\n\tmargin-top: 15px;\n\tmargin-left: 5px;\n}\n\n.tabs li.open.menu .menu, .tabs .open.dropdown .dropdown-toggle {\n\tborder-color: #999;\n}\n\n.tabs li.open a.menu:after, .tabs .dropdown.open .dropdown-toggle:after {\n\tborder-top-color: #555;\n}\n\n.tab-content > .tab-pane,\n.tab-content > div {\n\tdisplay: none;\n}\n\n.tab-content > .active {\n\tdisplay: block;\n}\n\n.breadcrumb {\n\tpadding: 7px 14px;\n\tmargin: 0 0 18px;\n\tbackground-color: #f5f5f5;\n\tbackground-repeat: repeat-x;\n\tbackground-image: linear-gradient(to bottom, #fff, #f5f5f5);\n\tborder: 1px solid #ddd;\n\tborder-radius: 3px;\n\tbox-shadow: inset 0 1px 0 #fff;\n}\n\n.breadcrumb li {\n\tdisplay: inline;\n\ttext-shadow: 0 1px 0 #fff;\n}\n\n.breadcrumb .divider {\n\tpadding: 0 5px;\n\tcolor: #bfbfbf;\n}\n\n.breadcrumb .active a {\n\tcolor: #404040;\n}\n\nfooter {\n\tmargin-top: 17px;\n\tpadding-top: 17px;\n\tborder-top: 1px solid #eee;\n}\n\n#modal_overlay {\n\tposition: fixed;\n\ttop: 0;\n\tbottom: 0;\n\tleft: -10000px;\n\tright: 10000px;\n\tbackground: rgba(0, 0, 0, 0.7);\n\tz-index: 900;\n\toverflow-y: scroll;\n\t-webkit-overflow-scrolling: touch;\n\ttransition: opacity .125s ease-in;\n\topacity: 0;\n\tvisibility: hidden;\n}\n\n.modal {\n\twidth: 90%;\n\tmargin: 5em auto;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tmin-height: 32px;\n\tmax-width: 600px;\n\talign-items: center;\n\tborder-radius: 3px;\n\tbackground: #fff;\n\tbox-shadow: 0 0 3px #444;\n\tpadding: 1em 1em .5em 1em;\n\tmin-width: 270px;\n}\n\n.modal > * {\n\tflex-basis: 100%;\n\tline-height: normal;\n\tmargin-bottom: .5em;\n}\n\n.modal > pre,\n.modal > textarea {\n\twhite-space: pre-wrap;\n\toverflow: auto;\n}\n\nbody.modal-overlay-active {\n\toverflow: hidden;\n\theight: 100vh;\n}\n\nbody.modal-overlay-active #modal_overlay {\n\tleft: 0;\n\tright: 0;\n\topacity: 1;\n\tvisibility: visible;\n}\n\n.btn.danger,\n.alert-message.danger,\n.btn.danger:hover,\n.alert-message.danger:hover,\n.btn.error,\n.alert-message.error,\n.btn.error:hover,\n.alert-message.error:hover,\n.btn.success,\n.alert-message.success,\n.btn.success:hover,\n.alert-message.success:hover,\n.btn.info,\n.alert-message.info,\n.btn.info:hover,\n.alert-message.info:hover,\n.cbi-tooltip.error, .cbi-tooltip.success, .cbi-tooltip.info {\n\tcolor: #fff;\n}\n\n.btn .close, .alert-message .close {\n\tfont-family: Arial, sans-serif;\n\tline-height: 18px;\n}\n\n.modal .btn.danger,\n.modal .btn {\n\twhite-space: normal;\n}\n\n.btn.danger,\n.alert-message.danger,\n.btn.error,\n.alert-message.error,\n.cbi-tooltip.error {\n\tbackground: linear-gradient(to bottom, #c43c35, #882a25) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.btn.success, .alert-message.success, .cbi-tooltip.success {\n\tbackground: linear-gradient(to bottom, #62c462, #57a957) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.btn.info, .alert-message.info, .cbi-tooltip.info {\n\tbackground: linear-gradient(to bottom, #5bc0de, #339bb9) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.alert-message.notice, .cbi-tooltip.notice {\n\tbackground: linear-gradient(to bottom, #efefef, #fefefe) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(255, 255, 255, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.item::after,\n.btn,\n.cbi-button {\n\tcursor: pointer;\n\tdisplay: inline-block;\n\tbackground: #fff;\n\tpadding: 5px 14px 6px;\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n\tcolor: #333;\n\tfont-size: 13px;\n\tline-height: normal;\n\tborder: 1px solid #ccc;\n\tborder-bottom-color: #bbb;\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n\twhite-space: pre;\n}\n\n.btn:focus,\n.cbi-button:focus {\n\toutline: 1px dotted #666;\n}\n\n.cbi-input-invalid,\n.cbi-input-invalid.cbi-dropdown:not(.btn):not(.cbi-button),\n.cbi-input-invalid.cbi-dropdown:not([open]) > ul > li,\n.cbi-value-error input {\n\tcolor: #f00;\n\tborder-color: #f00;\n}\n\n.cbi-button-neutral,\n.cbi-button-download,\n.cbi-button-find,\n.cbi-button-link,\n.cbi-button-up,\n.cbi-button-down {\n\tcolor: #444;\n}\n\n/* ИЗМЕНЕНИЕ: Основные кнопки на зеленый */\n.btn.primary,\n.cbi-button-apply,\n.cbi-button-negative {\n\tborder-color: #2e7d32;\n\tcolor: #2e7d32;\n\ttext-shadow: none !important;\n}\n\n.cbi-section-remove .cbi-button,\n.cbi-button-reset {\n\tborder-color: #2e7d32;\n\tcolor: #2e7d32;\n}\n\n.cbi-page-actions::after {\n\tdisplay: table;\n\tcontent: \"\";\n\tclear: both;\n}\n\n.cbi-page-actions > * {\n\tvertical-align: middle;\n}\n\n.cbi-page-actions > :not([method=\"post\"]):not(.cbi-button-apply):not(.cbi-button-negative):not(.cbi-button-save):not(.cbi-button-reset) {\n\tfloat: left;\n\tmargin-right: .4em;\n}\n\n/* ИЗМЕНЕНИЕ: Активные кнопки на зеленый */\n.cbi-button-edit,\n.cbi-button-positive.important,\n.cbi-button-positive,\n.cbi-button-action.important,\n.cbi-button-action,\n.cbi-button-save,\n.cbi-button-reload,\n.cbi-page-actions .cbi-button-apply {\n\tcolor: #FFFFFF;\n\tbackground: #4caf50;\n\tborder-color: #2e7d32;\n\ttext-shadow: none !important;\n}\n\n.cbi-button-fieldadd,\n.cbi-section-remove .cbi-button,\n.cbi-button-remove,\n.cbi-button-add,\n.cbi-button-negative.important,\n.cbi-page-actions .cbi-button-save,\n.cbi-page-actions .cbi-button-reset,\n.cbi-section-actions .cbi-button-action {\n\tcolor: #2e7d32;\n\tbackground: #ffffff;\n\tborder-color: #2e7d32;\n\ttext-shadow: none !important;\n}\n\n.cbi-page-actions .cbi-button-reset:hover,\n.cbi-page-actions .cbi-button-save:hover,\n.cbi-page-actions .cbi-button-apply:hover {\n\tcolor: #FFFFFF;\n\tbackground: #2e7d32;\n\tborder-color: #2e7d32;\n\ttext-shadow: none !important;\n}\n\n.cbi-dropdown {\n\tdisplay: inline-flex !important;\n\tcursor: pointer;\n\theight: auto;\n\tposition: relative;\n\tpadding: 0 !important;\n}\n\n.cbi-dropdown:not(.btn):not(.cbi-button) {\n\tbackground: linear-gradient(#fff 0%, #e9e8e6 100%);\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n\tcolor: #404040;\n}\n\n.cbi-dynlist > .item:focus,\n.cbi-dropdown:focus {\n\toutline: 2px solid #4caf50;\n}\n\n.cbi-dropdown > ul {\n\tmargin: 0 !important;\n\tpadding: 0;\n\tlist-style: none;\n\toverflow-x: hidden;\n\toverflow-y: auto;\n\tdisplay: flex;\n\twidth: 100%;\n}\n\n.cbi-dropdown.btn > ul:not(.dropdown),\n.cbi-dropdown.cbi-button > ul:not(.dropdown) {\n\tmargin: 0 0 0 13px !important;\n}\n\n.cbi-dropdown.btn.spinning > ul:not(.dropdown),\n.cbi-dropdown.cbi-button.spinning > ul:not(.dropdown) {\n\tmargin: 0 !important;\n}\n\n.cbi-dropdown > ul.preview {\n\tdisplay: none;\n}\n\n.cbi-dropdown > .open,\n.cbi-dropdown > .more {\n\tflex-grow: 0;\n\tflex-shrink: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n\ttext-align: center;\n\tline-height: 2em;\n\tpadding: 0 .25em;\n}\n\n.cbi-dropdown.btn > .open,\n.cbi-dropdown.cbi-button > .open {\n\tpadding: 0 .5em;\n\tmargin-left: .5em;\n\tborder-left: 1px solid;\n}\n\n.cbi-dropdown > .more,\n.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li[placeholder] {\n\tcolor: #777;\n\tfont-weight: bold;\n\ttext-shadow: 1px 1px 0px #fff;\n\tdisplay: none;\n\tjustify-content: center;\n}\n\n.cbi-dropdown > ul > li {\n\tdisplay: none;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tflex-shrink: 1;\n\tflex-grow: 1;\n\talign-items: center;\n\talign-self: center;\n\tcolor: inherit;\n}\n\n.cbi-dropdown > ul.dropdown > li,\n.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li {\n\tmin-height: 20px;\n\tpadding: .25em;\n\tcolor: #404040;\n}\n\n.cbi-dropdown > ul > li .hide-open { display: block; display: initial; }\n.cbi-dropdown > ul > li .hide-close { display: none; }\n\n.cbi-dropdown > ul > li[display]:not([display=\"0\"]) {\n\tborder-left: 1px solid #ccc;\n}\n\n.cbi-dropdown[empty] > ul {\n\tmax-width: 1px;\n}\n\n.cbi-dropdown > ul > li > form {\n\tdisplay: none;\n\tmargin: 0;\n\tpadding: 0;\n\tpointer-events: none;\n}\n\n.cbi-dropdown > ul > li img {\n\tvertical-align: middle;\n\tmargin-right: .25em;\n}\n\n.cbi-dropdown > ul > li > form > input[type=\"checkbox\"] {\n\tmargin: 0;\n}\n\n.cbi-dropdown > ul > li input[type=\"text\"] {\n\theight: 20px;\n}\n\n.cbi-dropdown[open] {\n\tposition: relative;\n}\n\n.cbi-dropdown[open] > ul.dropdown {\n\tdisplay: block;\n\tbackground: #f6f6f5;\n\tborder: 1px solid #918e8c;\n\tbox-shadow: 0 0 4px #918e8c;\n\tposition: absolute;\n\tz-index: 1100;\n\tmax-width: none;\n\tmin-width: 100%;\n\twidth: auto;\n\ttransition: max-height .125s ease-in;\n}\n\n.cbi-dropdown > ul > li[display],\n.cbi-dropdown[open] > ul.preview,\n.cbi-dropdown[open] > ul.dropdown > li,\n.cbi-dropdown[multiple] > ul > li > label,\n.cbi-dropdown[multiple][open] > ul.dropdown > li,\n.cbi-dropdown[multiple][more] > .more,\n.cbi-dropdown[multiple][empty] > .more {\n\tflex-grow: 1;\n\tdisplay: flex !important;\n}\n\n.cbi-dropdown[empty] > ul > li,\n.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder],\n.cbi-dropdown[multiple][open] > ul.dropdown > li > form {\n\tdisplay: block !important;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; }\n.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: block; display: initial; }\n\n.cbi-dropdown[open] > ul.dropdown > li {\n\tborder-bottom: 1px solid #ccc;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li[selected] {\n\tbackground: #2e7d32;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li.focus {\n\tbackground: #81c784;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li:last-child {\n\tmargin-bottom: 0;\n\tborder-bottom: none;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li[unselectable] {\n\topacity: 0.7;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li > input.create-item-input:first-child:last-child {\n\twidth: 100%;\n}\n\n.cbi-dropdown[disabled] {\n\tpointer-events: none;\n\topacity: .6;\n}\n\ninput[type=\"text\"] + .cbi-button,\ninput[type=\"password\"] + .cbi-button,\nselect + .cbi-button {\n\tborder-radius: 0 3px 3px 0;\n\tborder-color: #ccc;\n\tmargin-left: -2px;\n\tpadding: 0 6px;\n\tvertical-align: top;\n\theight: 30px;\n\tfont-size: 14px;\n\tline-height: 28px;\n}\n\ninput[type=\"text\"] + .cbi-button-add {\n\tcolor: #fff;\n\tborder-radius: 0 3px 3px 0;\n\tborder-color: #2e7d32;\n\tbackground: #2e7d32;\n}\n\ninput[type=\"text\"] + .cbi-button-remove {\n\tcolor: #124;\n\tborder-radius: 0 3px 3px 0;\n\tborder-color: #ccc;\n}\n\n.cbi-title-ref {\n\tcolor: #2e7d32;\n}\n\n.cbi-title-ref::after {\n\tcontent: \"➙\";\n}\n\n.cbi-tooltip-container {\n\tcursor: help;\n}\n\n.cbi-tooltip {\n\tposition: absolute;\n\tz-index: 1000;\n\tleft: -10000px;\n\tbox-shadow: 0 0 2px #ccc;\n\tborder-radius: 3px;\n\tbackground: #fff;\n\twhite-space: pre;\n\tpadding: 2px 5px;\n\topacity: 0;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-tooltip-container:hover .cbi-tooltip:not(:empty) {\n\tleft: auto;\n\topacity: 1;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-progressbar {\n\tborder: 1px solid var(--border-color-high);\n\tborder-radius: 3px;\n\tposition: relative;\n\tmin-width: 170px;\n\theight: 8px;\n\tmargin: 1.4em 0 4px 0;\n\tbackground: var(--background-color-medium);\n}\n\n.cbi-progressbar > div {\n\tbackground: var(--primary-color-medium);\n\theight: 100%;\n\ttransition: width .25s ease-in;\n\twidth: 0%;\n\tborder-radius: 2px;\n}\n\n.cbi-progressbar::before {\n\tposition: absolute;\n\ttop: -1.4em;\n\tleft: 0;\n\tcontent: attr(title);\n\twhite-space: pre;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.zonebadge .cbi-tooltip {\n\tpadding: 1px;\n\tbackground: inherit;\n\tmargin: -1.6em 0 0 -5px;\n\tborder-radius: 3px;\n\tpointer-events: none;\n\tbox-shadow: 0 0 3px #444;\n}\n\n.zonebadge .cbi-tooltip > * {\n\tmargin: 1px;\n}\n\n.zone-forwards {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n}\n\n.zone-forwards > * {\n\tflex: 1 1 40%;\n\tpadding: 1px;\n}\n\n.zone-forwards > span {\n\tflex-basis: 10%;\n\ttext-align: center;\n}\n\n.zone-forwards .zone-src,\n.zone-forwards .zone-dest {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.btn.active, .btn:active {\n\tbox-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.btn.disabled {\n\tcursor: default;\n\topacity: 0.65;\n\tbox-shadow: none;\n}\n\n.btn[disabled] {\n\tcursor: default;\n\topacity: 0.65;\n\tbox-shadow: none;\n}\n\n.btn.large {\n\tfont-size: 15px;\n\tline-height: normal;\n\tpadding: 9px 14px 9px;\n\tborder-radius: 6px;\n}\n\n.btn.small {\n\tpadding: 7px 9px 7px;\n\tfont-size: 11px;\n}\n\nbutton.btn::-moz-focus-inner, input[type=submit].btn::-moz-focus-inner {\n\tpadding: 0;\n\tborder: 0;\n}\n\n.close {\n\tfloat: right;\n\tcolor: #000;\n\tfont-size: 20px;\n\tfont-weight: bold;\n\tline-height: 13.5px;\n\ttext-shadow: 0 1px 0 #fff;\n\topacity: 0.25;\n}\n\n.close:hover {\n\tcolor: #000;\n\ttext-decoration: none;\n\topacity: 0.4;\n}\n\n.alert-message {\n\tposition: relative;\n\tpadding: .5em .5em .25em .5em;\n\tmargin-bottom: .5em;\n\tcolor: #404040;\n\tbackground: linear-gradient(to bottom, #fceec1, #eedc94) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n\ttext-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n\tborder-width: 1px;\n\tborder-style: solid;\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n\n.alert-message .close {\n\tmargin-top: 1px;\n\t*margin-top: 0;\n}\n\n.alert-message h4,\n.alert-message h5,\n.alert-message pre,\n.alert-message ul,\n.alert-message li,\n.alert-message p {\n\tcolor: inherit;\n\tborder: none;\n\tline-height: inherit;\n\tbackground: transparent;\n\tpadding: 0;\n\tmargin: .25em 0;\n}\n\n.alert-message button {\n\tmargin: .25em 0;\n}\n\n.label,\nheader [data-indicator] {\n\tpadding: 1px 3px 2px;\n\tfont-size: 9.75px;\n\tfont-weight: bold;\n\tcolor: #fff !important;\n\ttext-transform: uppercase;\n\twhite-space: nowrap;\n\tbackground-color: #2e7d32;\n\tborder-radius: 3px;\n\ttext-shadow: none;\n\tmargin-left: .4em;\n}\n\nheader [data-indicator][data-clickable] {\n\tcursor: pointer;\n}\n\na.label:link,\na.label:visited {\n\tcolor: #fff;\n}\n\na.label:hover {\n\ttext-decoration: none;\n}\n\n.label.important {\n\tbackground-color: #2e7d32;\n}\n\n.label.warning {\n\tbackground-color: #f89406;\n}\n\n.label.success {\n\tbackground-color: #4caf50;\n}\n\n.label.notice,\nheader [data-indicator][data-style=\"active\"] {\n\tbackground-color: #81c784;\n}\n\n/* LuCI specific items */\n.hidden { display: none }\n\nform.inline { display: inline; margin-bottom: 0; }\n\nheader .pull-right { padding-top: 8px; }\n\n#modemenu li:last-child span.divider { display: none }\n\n#syslog {  width: 100%; }\n\n.cbi-section-table .tr:hover .td,\n.cbi-section-table .tr:hover .th,\n.cbi-section-table .tr:hover::before {\n\tbackground-color: #f5f5f5;\n}\n\n.cbi-section-table .tr.cbi-section-table-descr .th {\n\tfont-weight: normal;\n}\n\n.cbi-section-table-titles.named::before,\n.cbi-section-table-descr.named::before,\n.cbi-section-table-row[data-title]::before {\n\tcontent: attr(data-title) \" \";\n\tdisplay: table-cell;\n\tpadding: 10px 10px 9px;\n\tline-height: 18px;\n\tfont-weight: bold;\n\tvertical-align: middle;\n}\n\n.cbi-section-table-titles.named::before,\n.cbi-section-table-descr.named::before,\n.cbi-section-table-row[data-title]::before {\n\tborder-top: 1px solid #ddd;\n}\n\n.left { text-align: left !important; }\n.right { text-align: right !important; margin-bottom: 5px !important;}\n.center { text-align: center !mportant; }\n.top { vertical-align: top !important; }\n.middle { vertical-align: middle !important; }\n.bottom { vertical-align: bottom !important; }\n\n.cbi-value-field { line-height: 1.5em; }\n\n.cbi-value-field input[type=checkbox],\n.cbi-value-field input[type=radio] {\n\tmargin-top: 8px;\n\tmargin-right: 6px;\n}\n\ntable table td,\n.cbi-value-field table td {\n\tborder: none;\n}\n\n.table.cbi-section-table input[type=\"password\"],\n.table.cbi-section-table input[type=\"text\"],\n.table.cbi-section-table textarea,\n.table.cbi-sectable select {\n\twidth: 100%;\n}\n\n.table.cbi-section-table .td.cbi-section-table-cell {\n\twhite-space: nowrap;\n\ttext-align: right;\n}\n\n.table.cbi-section-table .td.cbi-section-table-cell select {\n\twidth: inherit;\n}\n\n.td.cbi-section-actions {\n\ttext-align: right;\n\tvertical-align: middle;\n}\n\n.td.cbi-section-actions > * {\n\tdisplay: flex;\n}\n\n.td.cbi-section-actions > * > *,\n.td.cbi-section-actions > * > form > * {\n\tflex: 1 1 4em;\n\tmargin: 0 1px;\n}\n\n.td.cbi-section-actions > * > form {\n\tdisplay: inline-flex;\n\tmargin:0;\n}\n\n.table.valign-middle .td {\n\tvertical-align: middle;\n}\n\n.cbi-rowstyle-2,\n.tr.table-titles,\n.tr.cbi-section-table-titles {\n\tbackground: #f9f9f9;\n}\n\n.cbi-value-description {\n\tbackground-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxNicgaGVpZ2h0PScxNicgdmlld0JveD0nMCAwIDE2IDE2Jz48Y2lyY2xlIGN4PSc4JyBjeT0nOCcgcj0nNy41JyBmaWxsPScjZThmNWU5JyBzdHJva2U9JyMzODhlM2MnIHN0cm9rZS13aWR0aD0nMS4yJy8+PHRleHQgeD0nOCcgeT0nMTInIGZvbnQtc2l6ZT0nMTEnIGZvbnQtZmFtaWx5PSdzYW5zLXNlcmlmJyBmb250LXdlaWdodD0nYm9sZCcgdGV4dC1hbmNob3I9J21pZGRsZScgZmlsbD0nIzJlN2QzMic+PzwvdGV4dD48L3N2Zz4=\");\n\tbackground-position: .25em .2em;\n\tbackground-repeat: no-repeat;\n\tmargin: .25em 0 0 0;\n\tpadding: 0 0 0 1.7em;\n}\n\n.cbi-section-error {\n\tborder: 1px solid #f00;\n\tborder-radius: 3px;\n\tbackground-color: #c8e6c9;\n\tpadding: 5px;\n\tmargin-bottom: 18px;\n}\n\n.cbi-section-error ul { ma 0 0 0 20px; }\n\n.cbi-section-error ul li {\n\tcolor: #f00;\n\tfont-weight: bold;\n}\n\n.ifacebox {\n\tbackground-color: #fff;\n\tborder: 1px solid #ccc;\n\tmargin: 0 10px;\n\ttext-align: center;\n\twhite-space: nowrap;\n\tbackground-image: linear-gradient(#fff, #fff 25%, #f9f9f9);\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n\tdisplay: inline-flex;\n\tflex-direction: column;\n\tline-height: 1.2em;\n\tmin-width: 100px;\n}\n\n.ibox .ifacebox-head {\n\tborder-bottom: 1px solid #ccc;\n\tpadding: 2px;\n\tbackground: #eee;\n}\n\n.ifacebox .ifacebox-head.active {\n\tbackground: #4caf50;\n}\n\n.ifacebox .ifacebox-body {\n\tpadding: .25em;\n}\n\n.ifacebadge {\n\tdisplay: inline-block;\n\tflex-direction: row;\n\twhite-space: nowrap;\n\tbackground-color: #fff;\n\tborder: 1px solid #ccc;\n\tpadding: 2px;\n\tbackground-image: linear-gradient(#fff, #fff 25%, #f9f9f9);\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255,, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n\tcursor: default;\n\tline-height: 1.2em;\n}\n\n.ifacebadge img {\n\twidth: 16px;\n\theight: 16px;\n\tvertical-align: middle;\n}\n\n.ifacebadge-active {\n\tborder-color: #000;\n\tfont-weight: bold;\n}\n\n.network-status-table {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n}\n\n.network-status-table .ifacebox {\n\tmargin: .5em;\n\tflex-grow: 1;\n}\n\n.network-status-table .ifacebox-body {\n\tdisplay: flex;\n\tflex-direction: column;\n\theight: 100%;\n\ttext-align: left;\n}\n\n.network-status-table .ifacebox-body > * gin: .25em;\n}\n\n.network-status-table .ifacebox-body > span {\n\tflex: 10 10 auto;\n\theight: 100%;\n}\n\n.network-status-table .ifacebox-body > div {\n\tmargin: -.125em;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n}\n\n#dsl_status_table .ifacebox-body span > strong {\n\tdisplay: inline-block;\n\tmin-width: 35%;\n}\n\n.ifacebadge.large,\n.network-status-table .ifacebox-body .ifacebadge {\n\tdisplay: flex;\n\tflex: 1;\n\tpadding: .25em;\n\tmin-width: 220px;\n\tmargin: .125em;\n}\n\n.ifacebadge.large {\n\tdisplay: inline-flex;\n}\n\n.network-status-table .x-body .ifacebadge > span {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.ifacebadge > *,\n.ifacebadge.large > * {\n\tmargin: 0 .125em;\n}\n\n.zonebadge {\n\tpadding: 2px;\n\tborder-radius: 4px;\n\tdisplay: inline-block;\n\twhite-space: nowrap;\n\tcolor: #666;\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n}\n\n.zonebadge > em,\n.zonebadge > strong {\n\tmargin: 0 2px;\n\tdisplay: inline-block;\n}\n\n.zonebadge input {\n\twidth: 6em;\n}\n\n.zonebadge > .ifacebadge {\n\tmargin-left: 2px;\n}\n\n.zonebadge-empty {\n\tborder: 1px dashed #aaa;\tcolor: #aaa;\n\tfont-style: italic;\n\tfont-size: smaller;\n}\n\ndiv.cbi-value var,\n.td.cbi-value-field var {\n\tfont-style: italic;\n\tcolor: #2e7d32;\n}\n\ndiv.cbi-value var[data-tooltip],\n.td.cbi-value-field var[data-tooltip],\ndiv.cbi-value var.cbi-tooltip-container,\n.td.cbi-value-field var.cbi-tooltip-container {\n\tcursor: help;\n\tborder-bottom: 1px dotted #2e7d32;\n}\n\ndiv.cbi-value var.cbi-tooltip-container,\n.td.cbi-value-field var.cbi-tooltip-container .cbi-tooltip {\n\tfont-style: normal;\n\twhite-space: normal;\n\tcolor:4;\n}\n\n#modal_overlay > .modal.uci-dialog,\n#modal_overlay > .modal.cbi-modal {\n\tmax-width: 900px;\n}\n\n.uci-change-list {\n\tline-height: 170%;\n\twhite-space: pre;\n}\n\n.uci-change-list del,\n.uci-change-list ins,\n.uci-change-list var,\n.uci-change-legend-label del,\n.uci-change-legend-label ins,\n.uci-change-legend-label var {\n\ttext-decoration: none;\n\tfont-family: monospace;\n\tfont-style: normal;\n\tborder: 1px solid #ccc;\n\tbackground: #eee;\n\tpadding: 2px;\n\tdisplay: block;\n\tline-height: 15px;\n\tmargin-bottom: 1px;\n}\n\n.uci-change-list ins,\n.uci-change-legend-label ins {\n\tborder-color: #0f0;\n\tbackground: #cfc;\n}\n\n.uci-change-list del,\n.uci-change-legend-label del {\n\tborder-color: #f00;\n\tbackground: #fcc;\n}\n\n.uci-change-list var,\n.uci-change-legend-label var {\n\tborder-color: #ccc;\n\tbackground: #eee;\n}\n\n.uci-change-list var ins,\n.uci-change-list var del {\n\tdisplay: inline-block;\n\tborder: none;\n\twidth: 100%;\n\tpadding: 0;\n}\n\n.uci-change-legend {\n\tpadding: 5px;\n}\n\n.uci-change-legend-label {\n\twidth: 150px;\n\tfloat: left;\n}\n\n.uci-change-legend-label > ins,\n.uci-change-legend-label > del,\n.uci-change-legend-label > var {\n\tfloat: left;\n\tmargin-right: 4px;\n\twidth: 16px;\n\theight: 16px;\n\tdisplay: block;\n\tposition: relative;\n}\n\n.uci-change-legend-label var ins,\n.uci-change-legend-label var del {\n\tborder: none;\n\tposition: absolute;\n\ttop: 2px;\n\tleft: 2px;\n\tright: 2px;\n\tbottom: 2px;\n}\n\n#modal_overlay {\n\tposition: fixed;\n\ttop: 0;\n\tbottom: 0;\n\tleft: -10000px;\n\tright: 10000px;\n\tbackground: rgba(0, 0, 0, 0.7);\n\tz-index: 900;\n\toverflow-y: scroll;\n\t-webkit-overflow-scrolling: touch;\n\ttransition: opacity .125s ease-in;\n\topacity: 0;\n}\n\n#modal_overlay > .modal {\n\twidth: 90%;\n\tmargin: 5em auto;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tmin-height: 32px;\n\tmax-width: 600px;\n\talign-items: center;\n\tborder-radius: 3px;\n\tbackground: #fff;\n\tbox-shadow: 0 0 3px #444;\n\tpadding: 1em 1em .5em 1em;\n\tmin-width: 270px;\n}\n\n#modal_overlay .modal > * {\n\tflex-basis: 100%;\n\tline-height: normal;\n\tmargin-bottom: .5em;\n}\n\n#modal_overlay .modal > pre,\n#modal_overlay .modal > textarea {\n\twhite-space: pre-wrap;\n\toverflow: auto;\n}\n\nbody.modal-overlay-active {\n\toverflow: hidden;\n\theight: 100vh;\n}\n\nbody.modal-overlay-active #modal_overlay {\n\tleft: 0;\n\tright: 0;\n\topacity: 1;\n}\n\nhtml body.apply-overlay-active {\n\theight: calc(100vh - 63px);\n}\n\n#applyreboot-section {\n\tline-height: 300%;\n}\n\n[data-page=\"admin-network-dhcp\"] [data-name=\"ip\"] {\n\twidth: 15%;\n}\n\n@keyframes flash {\n\t0% { opacity: 1; }\n\t50% { opacity: .5; }\n\t100% { opacity: 1; }\n}\n\n.flash {\n\tanimation: flash .35s;\n}\n\n.spinning {\n\tposition: relative;\n\tpadding-left: 32px !important;\n}\n\n.spinning::before {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tbottom: 0;\n\twidth: 32px;\n\tcontent: \" \";\n\tbackground: url(../resources/icons/loading.svg) no-repeat center;\n\tbackground-size: 16px;\n}\n\n[data-tab-title] {\n\theight: 0;\n\topacity: 0;\n\toverflow: hidden;\n}\n\n[data-tab-active=\"true\"] {\n\topacity: 1;\n\theight: auto;\n\toverflow: visible;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-filebrowser {\n\tmin-width: 210px;\n\tmax-width: 100%;\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n\tdisplay: flex;\n\tflex-direction: column;\n\topacity: 0;\n\theight: 0;\n\toverflow: hidden;\n}\n\n.cbi-filebrowser.open {\n\topacity: 1;\n\theight: auto;\n\toverflow: visible;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-filebrowser > * {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tpadding: 0 0 .25em 0;\n\tmargin: .25em .25em 0px .25em;\n\twhite-space: nowrap;\n\tborder-bottom: 1px solid #ccc;\n}\n\n.cbi-filebrowser .cbi-button-positive {\n\tmargin-right: .25em;\n}\n\n.cbi-filebrowser > div {\n\tborder-bottom: none;\n}\n\n.cbi-filebrowser > ul > li {\n\tdisplay: flex;\n\tflex-direction: row;\n}\n\n.cbi-filebrowser > ul > li:hover {\n\tbackground: #f5f5f5;\n}\n\n.cbi-filebrowser > ul > li > div:first-child {\n\tflex: 10;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cbi-filebrowser > ul > li > div:last-child {\n\tflex: 3;\n\ttext-align: right;\n}\n\n.cbi-filebrowser > ul > li > div:last-child > button {\n\tpadding: .125em .25em;\n\tmargin: 1px 0 1px .25em;\n}\n\n.cbi-filebrowser .upload {\n\tdisplay: flex;\n\tflex-direction: row;\n\tflex-wrap: wrap;\n\tmargin: 0 -.125em .25em -.125em;\n\tpadding: 0 0 .125em 0px;\n\tborder-bottom: 1px solid #ccc;\n}\n\n.cbi-filebrowser .upload > * {\n\tmargin: .125em;\n\tflex: 1;\n}\n\n.cbi-filebrowser .upload > .btn {\n\tflex-basis: 60px;\n}\n\n.cbi-filebrowser .upload > div {\n\tflex: 10;\n\tmin-width: 150px;\n}\n\n.cbi-filebrowser .upload > div > input {\n\twidth: 100%;\n}\n\n@keyframes fade-in {\n\t0% { opacity: 0; }\n\t100% { opacity: 1; }\n}\n\n@keyframes fade-out {\n\t0% { opacity: 1; }\n\t100% { opacity: 0; }\n}\n\n.fade-in {\n\tanimation: fade-in .4s ease;\n}\n\n.fade-out {\n\tanimation: fade-out .4s ease;\n}\n\n.assoclist .ifacebadge {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\twhite-space: normal;\n\ttext-align: center;\n}\n\n.assoclist .ifacebadge > img {\n\tmargin: .2em;\n}\n\n.assoclist .td:nth-of-type(3),\n.assoclist .td:nth-of-type(5) {\n\twidth: 25%;\n}\n\n.assoclist .td:nth-of-type(6) button {\n\tword-break: normal;\n}\n\nimg[src*=\"signal-0\"] {\n\tfilter: sepia(1) saturate(6) hue-rotate(80deg) brightness(0.65) contrast(1.1);\n}\n"
  },
  {
    "path": "luci/themes/luci-theme-merona/htdocs/luci-static/merona/checkbox.css",
    "content": "/* Custom checkbox and radio styles for green theme */\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n    appearance: none;\n    -webkit-appearance: none;\n    width: 18px;\n    height: 18px;\n    border: 2px solid #ccc;\n    background: #fff;\n    position: relative;\n    cursor: pointer;\n    vertical-align: middle;\n}\n\ninput[type=\"checkbox\"] {\n    border-radius: 3px;\n}\n\ninput[type=\"checkbox\"]:checked {\n    background-color: #2e7d32;\n    border-color: #2e7d32;\n}\n\ninput[type=\"checkbox\"]:checked::before {\n    content: \"✓\";\n    position: absolute;\n    color: white;\n    font-size: 14px;\n    font-weight: bold;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n}\n\ninput[type=\"radio\"] {\n    border-radius: 50%;\n}\n\ninput[type=\"radio\"]:checked {\n    background-color: #2e7d32;\n    border-color: #2e7d32;\n}\n\ninput[type=\"radio\"]:checked::after {\n    content: \"\";\n    position: absolute;\n    width: 8px;\n    height: 8px;\n    background: white;\n    border-radius: 50%;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n}\n\ninput[type=\"checkbox\"]:hover,\ninput[type=\"radio\"]:hover {\n    border-color: #2e7d32;\n}\n\ninput[type=\"checkbox\"]:focus,\ninput[type=\"radio\"]:focus {\n    outline: none;\n    box-shadow: 0 0 0 2px rgba(46, 125, 50, 0.2);\n}\n"
  },
  {
    "path": "luci/themes/luci-theme-merona/htdocs/luci-static/merona/mobile.css",
    "content": "header h3 a, header .brand {\n\tdisplay:none !important;\n}\n\n@media screen and (max-device-width: 600px) {\n\t#maincontent.container {\n\t\tmargin-top: 30px;\n\t}\n\n\t.tabs, .cbi-tabmenu {\n\t\tbackground: linear-gradient(#fff 20%, #ddd 100%);\n\t\tbackground-size: 1px 34px;\n\t\tmargin-bottom: 10px;\n\t}\n\n\t.tabs > li, .cbi-tabmenu > li {\n\t\theight: 30px;\n\t}\n\n\t.tabs > li > a, .cbi-tabmenu > li > a {\n\t\tpadding: 0 8px;\n\t\tline-height: 30px;\n\t}\n\n\t.table {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t}\n\n\t.tr {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-wrap: wrap;\n\t\talign-items: flex-end;\n\t\tborder-top: 1px solid #ddd;\n\t\tpadding: 5px 0;\n\t\tmargin: 0 -3px;\n\t}\n\n\t.table .th,\n\t.table .td,\n\t.table .tr::before {\n\t\tflex: 2 2 33%;\n\t\talign-self: flex-start;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\tword-wrap: break-word;\n\t\tdisplay: inline-block;\n\t\tborder-top: none;\n\t\tpadding: 3px;\n\t\tbox-sizing: border-box;\n\t}\n\n\t.table .td.cbi-dropdown-open {\n\t\toverflow: visible;\n\t}\n\n\t.col-1 { flex: 1 1 30px !important; -webkit-flex: 1 1 30px !important; }\n\t.col-2 { flex: 2 2 60px !important; -webkit-flex: 2 2 60px !important; }\n\t.col-3 { flex: 3 3 90px !important; -webkit-flex: 3 3 90px !important; }\n\t.col-4 { flex: 4 4 120px !important; -webkit-flex: 4 4 120px !important; }\n\t.col-5 { flex: 5 5 150px !important; -webkit-flex: 5 5 150px !important; }\n\t.col-6 { flex: 6 6 180px !important; -webkit-flex: 6 6 180px !important; }\n\t.col-7 { flex: 7 7 210px !important; -webkit-flex: 7 7 210px !important; }\n\t.col-8 { flex: 8 8 240px !important; -webkit-flex: 8 8 240px !important; }\n\t.col-9 { flex: 9 9 270px !important; -webkit-flex: 9 9 270px !important; }\n\t.col-10 { flex: 10 10 300px !important; -webkit-flex: 10 10 300px !important; }\n\n\t.td select {\n\t\tword-wrap: normal;\n\t}\n\n\t.td[data-widget=\"button\"],\n\t.td[data-widget=\"fvalue\"] {\n\t\tflex: 1 1 17%;\n\t\ttext-align: left;\n\t}\n\n\t.td.cbi-value-field {\n\t\talign-self: flex-start;\n\t}\n\n\t.td.cbi-value-field .cbi-button {\n\t\twidth: 100%;\n\t}\n\n\t.table.cbi-section-table {\n\t\tborder: none;\n\t\tbackground: none;\n\t\tmargin: 0;\n\t}\n\n\t.tr.table-titles,\n\t.cbi-section-table-titles,\n\t.cbi-section-table-descr {\n\t\tdisplay: none;\n\t}\n\n\t.cbi-section-table-row {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-wrap: wrap;\n\t\tmargin: 0 0 .5em 0;\n\t}\n\n\t.cbi-section-table + .cbi-section-create {\n\t\tpadding-top: 0;\n\t}\n\n\t.tr[data-title]::before {\n\t\tdisplay: block;\n\t\tflex: 1 1 100%;\n\t\tbackground: #f5f5f5 !important;\n\t\tfont-size: 16px;\n\t\tborder-bottom: 1px solid #ddd;\n\t}\n\n\t.td[data-title]::before,\n\t.td[data-description]::after {\n\t\tdisplay: block;\n\t}\n\n\t.td[data-title] ~ .td.cbi-section-actions {\n\t\talign-self: flex-start;\n\t}\n\n\t.td[data-title] ~ .td.cbi-section-actions::before {\n\t\tdisplay: block;\n\t\tcontent: \"\\a0\";\n\t}\n\n\t.td.cbi-section-actions {\n\t\toverflow: initial;\n\t\tmax-width: 100%;\n\t\tpadding: 3px 2px;\n\t}\n\n\t.hide-sm,\n\t.hide-xs {\n\t\tdisplay: none !important;\n\t}\n\n\t.td.cbi-value-field {\n\t\tflex-basis: 100%;\n\t}\n\n\t.td.cbi-value-field[data-widget=\"dvalue\"] {\n\t\tflex-basis: 50%;\n\t}\n\n\t.td.cbi-value-field[data-widget=\"button\"],\n\t.td.cbi-value-field[data-widget=\"fvalue\"] {\n\t\tflex-basis: 25%;\n\t\ttext-align: left;\n\t}\n\n\t.cbi-section-table .tr:hover .td,\n\t.cbi-section-table .tr:hover .th,\n\t.cbi-section-table .tr:hover::before {\n\t\tbackground-color: transparent;\n\t}\n\n\t.cbi-value {\n\t\tpadding-bottom: .5em;\n\t\tborder-bottom: 1px solid #ddd;\n\t\tmargin-bottom: .5em;\n\t}\n\n\t.cbi-value label.cbi-value-title {\n\t\tfloat: none;\n\t\tfont-weight: bold;\n\t}\n\n\t.cbi-value-field, .cbi-dropdown {\n\t\twidth: 100%;\n\t\tmargin: 0;\n\t}\n\n\tinput, textarea, select,\n\t.cbi-dropdown > ul > li input[type=\"text\"] {\n\t\tfont-size: 16px !important;\n\t\tline-height: 28px;\n\t\theight: auto;\n\t}\n\n\tselect, input[type=\"text\"], input[type=\"password\"] {\n\t\twidth: 100%;\n\t\theight: 30px;\n\t}\n\n\tinput.cbi-input-password {\n\t\twidth: calc(100% - 25px);\n\t}\n\n\t[data-dynlist] {\n\t\tdisplay: block;\n\t}\n\n\t[data-dynlist] > .add-item > input {\n\t\twidth: calc(100% - 21px);\n\t}\n\n\t[data-dynlist] > .add-item > .cbi-button {\n\t\tmargin-right: -1px;\n\t}\n\n\tinput[type=\"text\"] + .cbi-button,\n\tinput[type=\"password\"] + .cbi-button,\n\tselect + .cbi-button {\n\t\tfont-size: 14px !important;\n\t\tline-height:  28px;\n\t\theight: 30px;\n\t\tbox-sizing: border-box;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\t.cbi-value-field input[type=\"checkbox\"],\n\t.cbi-value-field input[type=\"radio\"] {\n\t\tmargin: 0;\n\t}\n\n\t.btn, .cbi-button {\n\t\tfont-size: 14px !important;\n\t\tpadding: 4px 8px;\n\t}\n\n\t.actions,\n\t.cbi-page-actions {\n\t\tborder-top: none;\n\t\tmargin-top: -.5em;\n\t\tpadding: 8px;\n\t}\n\n\t[data-page=\"admin-status-overview\"] .cbi-section:nth-of-type(1) .td:first-child,\n\t[data-page=\"admin-status-overview\"] .cbi-section:nth-of-type(2) .td:first-child {\n\t\tflex-grow: 1;\n\t}\n\n\theader .pull-right .label {\n\t\twhite-space: normal;\n\t\tdisplay: inline-block;\n\t\ttext-align: center;\n\t\tline-height: 12px;\n\t\tmargin: 1px 0;\n\t}\n\n\theader > .fill {\n\t\tpadding: 1px;\n\t}\n\n\theader > .fill > .container {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t}\n\n\theader .nav {\n\t\tflex: 3 3 80%;\n\t\tmargin: 2px 5px 2px 0;\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: flex-start;\n\t}\n\n\theader .nav a {\n\t\tpadding: 2px 6px;\n\t}\n\n\theader .pull-right {\n\t\tflex: 1 1 20%;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tpadding: 0;\n\t\tjustify-content: space-around;\n\t}\n\n\t.menu-dropdown, .dropdown-menu {\n\t\ttop: 23px;\n\t}\n\n\tbody {\n\t\tpadding-top: 30px;\n\t}\n\n\t.cbi-optionals,\n\t.cbi-section-create {\n\t\tpadding: 0 0 14px 0;\n\t}\n\n\t#cbi-network-switch_vlan .th,\n\t#cbi-network-switch_vlan .td {\n\t\tflex-basis: 12%;\n\t}\n\n\t#cbi-network-switch_vlan .td.cbi-section-actions {\n\t\tflex-basis: 100%;\n\t}\n\n\t#cbi-network-switch_vlan .td.cbi-section-actions::before {\n\t\tdisplay: none;\n\t}\n\n\t#cbi-network-switch_vlan .td.cbi-section-actions > * {\n\t\twidth: auto;\n\t\tdisplay: block;\n\t}\n\n\t#wifi_assoclist_table .td,\n\t[data-page=\"admin-status-processes\"] .td {\n\t\tflex-basis: 50% !important;\n\t}\n\n\t[data-page=\"admin-status-processes\"] .td[data-widget=\"button\"] {\n\t\tflex-basis: 33% !important;\n\t}\n\n\t[data-page=\"admin-status-processes\"] .td[data-name=\"PID\"],\n\t[data-page=\"admin-status-processes\"] .td[data-name=\"USER\"] {\n\t\tflex-basis: 25% !important;\n\t}\n\n\t[data-page=\"admin-system-fstab\"] .td[data-widget=\"button\"]::before,\n\t[data-page=\"admin-system-startup\"] .td[data-widget=\"button\"]::before,\n\t[data-page=\"admin-status-processes\"] .td[data-widget=\"button\"]::before {\n\t\tdisplay: none;\n\t}\n}\n\n@media screen and (max-device-width: 375px) {\n\t#maincontent.container {\n\t\tmargin-top: 55px;\n\t}\n\n\t.cbi-page-actions {\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: space-between;\n\t\tmargin: 0 -1px;\n\t\tpadding: 0;\n\t}\n\n\t.cbi-page-actions .cbi-button:not(.cbi-dropdown) {\n\t\tflex: 1 1 calc(50% - 2px);\n\t\tmargin: 1px !important;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\t.cbi-page-actions .cbi-button-negative,\n\t.cbi-page-actions .cbi-button-primary,\n\t.cbi-page-actions .cbi-button-apply {\n\t\tflex-basis: calc(100% - -2px);\n\t}\n\n\t.cbi-section-actions .cbi-button {\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\tbody[data-page=\"admin-network-wireless\"] .td[data-name=\"_badge\"] {\n\t\tmax-width: 50px;\n\t\talign-self: center;\n\t}\n\n\tbody[data-page=\"admin-network-wireless\"] .td[data-name=\"_badge\"] .ifacebadge {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tflex-direction: column;\n\t}\n\n\tbody[data-page=\"admin-network-wireless\"] .td[data-name=\"_stat\"] {\n\t\tflex-basis: 60%;\n\t}\n\n\tbody[data-page=\"admin-network-network\"] .td.cbi-section-actions::before,\n\tbody[data-page=\"admin-network-wireless\"] .td.cbi-section-actions::before {\n\t\tcontent: none !important;\n\t}\n}\n\n@media screen and (max-device-width: 200px) {\n\t#maincontent.container {\n\t\tmargin-top: 230px;\n\t}\n}\n\n@media screen and (max-width: 375px) {\n\t.td .ifacebox {\n\t\twidth: 100%;\n\t\tmargin: 0 !important;\n\t\tflex-direction: row;\n\t}\n\n\t.td .ifacebox .ifacebox-head {\n\t\tmin-width: 25%;\n\t\tjustify-content: space-around;\n\t}\n\n\t.td .ifacebox .ifacebox-head,\n\t.td .ifacebox .ifacebox-body {\n\t\tdisplay: flex;\n\t\tborder-bottom: none;\n\t\talign-items: center;\n\t}\n\n\t.td .ifacebox .ifacebox-head > *,\n\t.ifacebox .ifacebox-body > * {\n\t\tmargin: .125em;\n\t}\n}\n"
  },
  {
    "path": "luci/themes/luci-theme-merona/htdocs/luci-static/resources/menu-merona.js",
    "content": "'use strict';\n'require baseclass';\n'require ui';\n\nreturn baseclass.extend({\n\t__init__: function() {\n\t\tui.menu.load().then(L.bind(this.render, this));\n\t},\n\n\trender: function(tree) {\n\t\tvar node = tree,\n\t\t    url = '';\n\n\t\tthis.renderModeMenu(tree);\n\n\t\tif (L.env.dispatchpath.length >= 3) {\n\t\t\tfor (var i = 0; i < 3 && node; i++) {\n\t\t\t\tnode = node.children[L.env.dispatchpath[i]];\n\t\t\t\turl = url + (url ? '/' : '') + L.env.dispatchpath[i];\n\t\t\t}\n\n\t\t\tif (node)\n\t\t\t\tthis.renderTabMenu(node, url);\n\t\t}\n\n\t\tdocument.addEventListener('poll-start', this.handleBodyMargin);\n\t\tdocument.addEventListener('poll-stop', this.handleBodyMargin);\n\t\tdocument.addEventListener('uci-new-changes', this.handleBodyMargin);\n\t\tdocument.addEventListener('uci-clear-changes', this.handleBodyMargin);\n\t\twindow.addEventListener('resize', this.handleBodyMargin);\n\n\t\tthis.handleBodyMargin();\n\t},\n\n\trenderTabMenu: function(tree, url, level) {\n\t\tvar container = document.querySelector('#tabmenu'),\n\t\t    ul = E('ul', { 'class': 'tabs' }),\n\t\t    children = ui.menu.getChildren(tree),\n\t\t    activeNode = null;\n\n\t\tfor (var i = 0; i < children.length; i++) {\n\t\t\tvar isActive = (L.env.dispatchpath[3 + (level || 0)] == children[i].name),\n\t\t\t    activeClass = isActive ? ' active' : '',\n\t\t\t    className = 'tabmenu-item-%s %s'.format(children[i].name, activeClass);\n\n\t\t\tul.appendChild(E('li', { 'class': className }, [\n\t\t\t\tE('a', { 'href': L.url(url, children[i].name) }, [ _(children[i].title) ] )]));\n\n\t\t\tif (isActive)\n\t\t\t\tactiveNode = children[i];\n\t\t}\n\n\t\tif (ul.children.length == 0)\n\t\t\treturn E([]);\n\n\t\tcontainer.appendChild(ul);\n\t\tcontainer.style.display = '';\n\n\t\tif (activeNode)\n\t\t\tthis.renderTabMenu(activeNode, url + '/' + activeNode.name, (level || 0) + 1);\n\n\t\treturn ul;\n\t},\n\n\trenderMainMenu: function(tree, url, level) {\n\t\tvar ul = level ? E('ul', { 'class': 'dropdown-menu' }) : document.querySelector('#topmenu'),\n\t\t    children = ui.menu.getChildren(tree);\n\n\t\tif (children.length == 0 || level > 1)\n\t\t\treturn E([]);\n\n\t\tfor (var i = 0; i < children.length; i++) {\n\t\t\tvar submenu = this.renderMainMenu(children[i], url + '/' + children[i].name, (level || 0) + 1),\n\t\t\t    subclass = (!level && submenu.firstElementChild) ? 'dropdown' : null,\n\t\t\t    linkclass = (!level && submenu.firstElementChild) ? 'menu' : null,\n\t\t\t    linkurl = submenu.firstElementChild ? '#' : L.url(url, children[i].name);\n\n\t\t\tvar li = E('li', { 'class': subclass }, [\n\t\t\t\tE('a', { 'class': linkclass, 'href': linkurl }, [ _(children[i].title) ]),\n\t\t\t\tsubmenu\n\t\t\t]);\n\n\t\t\tul.appendChild(li);\n\t\t}\n\n\t\tul.style.display = '';\n\n\t\treturn ul;\n\t},\n\n\trenderModeMenu: function(tree) {\n\t\tvar ul = document.querySelector('#modemenu'),\n\t\t    children = ui.menu.getChildren(tree);\n\n\t\tfor (var i = 0; i < children.length; i++) {\n\t\t\tvar isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[0] : i == 0);\n\n\t\t\tul.appendChild(E('li', { 'class': isActive ? 'active' : null }, [\n\t\t\t\tE('a', { 'href': L.url(children[i].name) }, [ _(children[i].title) ]),\n\t\t\t\t' ',\n\t\t\t\tE('span', { 'class': 'divider' }, [ '|' ])\n\t\t\t]));\n\n\t\t\tif (isActive)\n\t\t\t\tthis.renderMainMenu(children[i], children[i].name);\n\t\t}\n\n\t\tif (ul.children.length > 1)\n\t\t\tul.style.display = '';\n\t},\n\n\thandleBodyMargin: function(ev) {\n\t\tvar body = document.querySelector('body'),\n\t\t    head = document.querySelector('header');\n\n\t\tbody.style.marginTop = head.offsetHeight + 'px';\n\t}\n});\n"
  },
  {
    "path": "luci/themes/luci-theme-merona/luasrc/view/themes/merona/footer.htm",
    "content": "<%#\n Copyright 2008 Steven Barth <steven@midlink.org>\n Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>\n Copyright 2012 David Menting <david@nut-bolt.nl>\n Licensed to the public under the Apache License 2.0.\n-%>\n\n<% local ver = require \"luci.version\" %>\n\n   <footer>\n    <div style=\"height:40px;font-size:95%;color:#999;font-family: Arial,sans-serif;\"><%= ver.distversion %>&nbsp;&#169;&nbspmerona.ru&nbsp;<%= os.date(\"%Y\") %>&nbsp;</div>\n    <ul class=\"breadcrumb pull-right\" id=\"modemenu\" style=\"display:none\"></ul>\n   </footer>\n  </div>\n  <script type=\"text/javascript\">L.require('menu-merona')</script>\n </body>\n</html>\n\n"
  },
  {
    "path": "luci/themes/luci-theme-merona/luasrc/view/themes/merona/header.htm",
    "content": "<%#\n Copyright 2008 Steven Barth <steven@midlink.org>\n Copyright 2008-2016 Jo-Philipp Wich <jow@openwrt.org>\n Copyright 2012 David Menting <david@nut-bolt.nl>\n Licensed to the public under the Apache License 2.0.\n-%>\n\n<%\n\tlocal sys  = require \"luci.sys\"\n\tlocal util = require \"luci.util\"\n\tlocal http = require \"luci.http\"\n\tlocal disp = require \"luci.dispatcher\"\n\n\tlocal boardinfo = util.ubus(\"system\", \"board\")\n\n\tlocal node = disp.context.dispatched\n\n\t-- send as HTML5\n\thttp.prepare_content(\"text/html\")\n-%>\n<!DOCTYPE html>\n<html lang=\"<%=luci.i18n.context.lang%>\">\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title><%=striptags( (boardinfo.hostname or \"?\") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>\n\t\t<meta name=\"viewport\" content=\"initial-scale=1.0\">\n\t\t<link rel=\"stylesheet\" href=\"<%=media%>/cascade.css\">\n\t\t<link rel=\"stylesheet\" href=\"<%=media%>/checkbox.css?v=0\">\t\t\n\t\t<link rel=\"stylesheet\" media=\"only screen and (max-device-width: 854px)\" href=\"<%=media%>/mobile.css\" type=\"text/css\" />\n\t\t<link rel=\"shortcut icon\" href=\"<%=media%>/favicon.ico\">\n\t\t<% if node and node.css then %>\n\t\t\t<link rel=\"stylesheet\" href=\"<%=resource%>/<%=node.css%>\">\n\t\t<% end -%>\n\t\t<% if css then %>\n\t\t\t<style title=\"text/css\"><%= css %></style>\n\t\t<% end -%>\n\t\t<script src=\"<%=url('admin/translations', luci.i18n.context.lang)%><%# ?v=PKG_VERSION %>\"></script>\n\t\t<script src=\"<%=resource%>/cbi.js\"></script>\n\t</head>\n\n\t<body class=\"lang_<%=luci.i18n.context.lang%> <% if node then %><%= striptags( node.title ) %><%- end %>\" data-page=\"<%= pcdata(table.concat(disp.context.requestpath, \"-\")) %>\">\n\t\t<header>\n\t\t\t<div class=\"fill\">\n\t\t\t\t<div class=\"container\">\n\t\t\t\t\t<a class=\"brand\" href=\"/\"></a>\n\t\t\t\t\t<ul class=\"nav\" id=\"topmenu\" style=\"display:none\"></ul>\n\t\t\t\t\t<div id=\"indicators\" class=\"pull-right\"></div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<div id=\"maincontent\" class=\"container\">\n\t\t\t<%- if luci.sys.process.info(\"uid\") == 0 and luci.sys.user.getuser(\"root\") and not luci.sys.user.getpasswd(\"root\") then -%>\n\t\t\t\t<div class=\"alert-message warning\">\n\t\t\t\t\t<h4><%:No password set!%></h4>\n\t\t\t\t\t<p><%:There is no password set on this router. Please configure a root password to protect the web interface.%></p>\n\t\t\t\t\t<% if disp.lookup(\"admin/system/admin\") then %>\n\t\t\t\t\t  <div class=\"right\"><a class=\"btn\" href=\"<%=url(\"admin/system/admin\")%>\"><%:Go to password configuration...%></a></div>\n\t\t\t\t\t<% end %>\n\t\t\t\t</div>\n\t\t\t<%- end -%>\n\n\t\t\t<noscript>\n\t\t\t\t<div class=\"alert-message warning\">\n\t\t\t\t\t<h4><%:JavaScript required!%></h4>\n\t\t\t\t\t<p><%:You must enable JavaScript in your browser or LuCI will not work properly.%></p>\n\t\t\t\t</div>\n\t\t\t</noscript>\n\n\t\t\t<div id=\"tabmenu\" style=\"display:none\"></div>\n"
  },
  {
    "path": "luci/themes/luci-theme-merona/root/etc/uci-defaults/30_luci-theme-merona",
    "content": "#!/bin/sh\n\nif [ \"$PKG_UPGRADE\" != 1 ]; then\n\tuci get luci.themes.Merona >/dev/null 2>&1 || \\\n\tuci batch <<-EOF\n\t\tset luci.themes.Merona=/luci-static/merona\n\t\tset luci.main.mediaurlbase=/luci-static/merona\n\t\tcommit luci\n\tEOF\nfi\n\nexit 0\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/Makefile",
    "content": "#\n# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Routerich Theme\nLUCI_DEPENDS:= +luci-lua-runtime\n\nPKG_LICENSE:=Apache-2.0\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/htdocs/luci-static/resources/menu-routerich.js",
    "content": "'use strict';\n'require baseclass';\n'require ui';\n\nreturn baseclass.extend({\n  __init__: function () {\n    ui.menu.load().then(L.bind(this.render, this));\n  },\n\n  render: function (tree) {\n    var node = tree,\n      url = '',\n      children = ui.menu.getChildren(tree);\n\n    for (var i = 0; i < children.length; i++) {\n      var isActive =\n        L.env.requestpath.length\n          ? children[i].name == L.env.requestpath[0]\n          : i == 0;\n      if (isActive) this.renderMainMenu(children[i], children[i].name);\n    }\n\n    if (L.env.dispatchpath.length >= 3) {\n      for (var i = 0; i < 3 && node; i++) {\n        node = node.children[L.env.dispatchpath[i]];\n        url = url + (url ? '/' : '') + L.env.dispatchpath[i];\n      }\n      if (node) this.renderTabMenu(node, url);\n    }\n\n    document\n      .querySelector('a.showSide')\n      .addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));\n    document\n      .querySelector('.darkMask')\n      .addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));\n  },\n\n  handleMenuExpand: function (ev) {\n    var a = ev.target,\n      slide = a.parentNode,\n      slide_menu = a.nextElementSibling;\n    var collapse = false;\n\n    document\n      .querySelectorAll('.main .main-left .nav > li >ul.active')\n      .forEach(function (ul) {\n        $(ul).stop(true).slideUp('fast', function () {\n          ul.classList.remove('active');\n          ul.previousElementSibling.classList.remove('active');\n        });\n        if (!collapse && ul === slide_menu) {\n          collapse = true;\n        }\n      });\n\n    if (!slide_menu) return;\n\n    if (!collapse) {\n      $(slide)\n        .find('.slide-menu')\n        .slideDown('fast', function () {\n          slide_menu.classList.add('active');\n          a.classList.add('active');\n        });\n      a.blur();\n    }\n\n    ev.preventDefault();\n    ev.stopPropagation();\n  },\n\n  renderMainMenu: function (tree, url, level) {\n    var l = (level || 0) + 1,\n      ul = E('ul', { class: level ? 'slide-menu' : 'nav' }),\n      children = ui.menu.getChildren(tree);\n\n    if (children.length == 0 || l > 2) return E([]);\n\n    for (var i = 0; i < children.length; i++) {\n      var isActive =\n          L.env.dispatchpath[l] == children[i].name &&\n          L.env.dispatchpath[l - 1] == tree.name,\n        submenu = this.renderMainMenu(children[i], url + '/' + children[i].name, l),\n        hasChildren = submenu.children.length,\n        slideClass = hasChildren ? 'slide' : null,\n        menuClass = hasChildren ? 'menu' : 'food';\n\n      if (isActive) {\n        ul.classList.add('active');\n        slideClass += ' active';\n        menuClass += ' active';\n      }\n\n      ul.appendChild(\n        E(\n          'li',\n          { class: slideClass },\n          [\n            E(\n              'a',\n              {\n                href: L.url(url, children[i].name),\n                click: l == 1 ? ui.createHandlerFn(this, 'handleMenuExpand') : null,\n                class: menuClass,\n                'data-title': hasChildren\n                  ? children[i].title.replace(' ', '_')\n                  : children[i].title.replace(' ', '_'),\n              },\n              [_(children[i].title)]\n            ),\n            submenu,\n          ]\n        )\n      );\n    }\n\n    if (l == 1) {\n      document.querySelector('#mainmenu').appendChild(ul);\n      document.querySelector('#mainmenu').style.display = '';\n    }\n\n    return ul;\n  },\n\n  renderTabMenu: function (tree, url, level) {\n    var container = document.querySelector('#tabmenu'),\n      l = (level || 0) + 1,\n      ul = E('ul', { class: 'tabs' }),\n      children = ui.menu.getChildren(tree),\n      activeNode = null;\n\n    if (children.length == 0) return E([]);\n\n    for (var i = 0; i < children.length; i++) {\n      var isActive = L.env.dispatchpath[l + 2] == children[i].name,\n        activeClass = isActive ? ' active' : '',\n        className = 'tabmenu-item-%s %s'.format(children[i].name, activeClass);\n\n      ul.appendChild(\n        E('li', { class: className }, [\n          E('a', { href: L.url(url, children[i].name) }, [_(children[i].title)]),\n        ])\n      );\n\n      if (isActive) activeNode = children[i];\n    }\n\n    container.appendChild(ul);\n    container.style.display = '';\n\n    if (activeNode)\n      container.appendChild(this.renderTabMenu(activeNode, url + '/' + activeNode.name, l));\n\n    return ul;\n  },\n\n  handleSidebarToggle: function (ev) {\n    var showside = document.querySelector('a.showSide'),\n      sidebar = document.querySelector('#mainmenu'),\n      darkmask = document.querySelector('.darkMask'),\n      scrollbar = document.querySelector('.main-right');\n\n    if (showside.classList.contains('active')) {\n      showside.classList.remove('active');\n      sidebar.classList.remove('active');\n      scrollbar.classList.remove('active');\n      darkmask.classList.remove('active');\n    } else {\n      showside.classList.add('active');\n      sidebar.classList.add('active');\n      scrollbar.classList.add('active');\n      darkmask.classList.add('active');\n    }\n  },\n});"
  },
  {
    "path": "luci/themes/luci-theme-routerich/htdocs/luci-static/routerich/background/README.md",
    "content": "Drop background here!\naccept jpg png gif mp4 webm\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/htdocs/luci-static/routerich/css/cascade.css",
    "content": "/*!\nPure v2.0.3\nCopyright 2013 Yahoo!\nLicensed under the BSD License.\nhttps://github.com/pure-css/pure/blob/master/LICENSE.md\n*/ /*!\nnormalize.css v | MIT License | git.io/normalize\nCopyright (c) Nicolas Gallagher and Jonathan Neal\n*/ /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\nhtml {\n  line-height: 1.15;\n  -webkit-text-size-adjust: 100%;\n}\nbody {\n  margin: 0;\n}\nmain {\n  display: block;\n}\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\nhr {\n  -webkit-box-sizing: content-box;\n  box-sizing: content-box;\n  height: 0;\n  overflow: visible;\n}\npre {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\na {\n  background-color: transparent;\n}\nabbr[title] {\n  border-bottom: none;\n  text-decoration: underline;\n  -webkit-text-decoration: underline dotted;\n  text-decoration: underline dotted;\n}\nb,\nstrong {\n  font-weight: bolder;\n}\ncode,\nkbd,\nsamp {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\nsmall {\n  font-size: 80%;\n}\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\nsub {\n  bottom: -0.25em;\n}\nsup {\n  top: -0.5em;\n}\nimg {\n  border-style: none;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-family: inherit;\n  font-size: 100%;\n  line-height: 1.15;\n  margin: 0;\n}\nbutton,\ninput {\n  overflow: visible;\n}\nbutton,\nselect {\n  text-transform: none;\n}\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"],\nbutton {\n  -webkit-appearance: button;\n}\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner,\nbutton::-moz-focus-inner {\n  border-style: none;\n  padding: 0;\n}\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring,\nbutton:-moz-focusring {\n  outline: 1px dotted ButtonText;\n}\nfieldset {\n  padding: 0.35em 0.75em 0.625em;\n}\nlegend {\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n  color: inherit;\n  display: table;\n  max-width: 100%;\n  padding: 0;\n  white-space: normal;\n}\nprogress {\n  vertical-align: baseline;\n}\ntextarea {\n  overflow: auto;\n}\n[type=\"checkbox\"],\n[type=\"radio\"] {\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n  padding: 0;\n}\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\n[type=\"search\"] {\n  -webkit-appearance: textfield;\n  outline-offset: -2px;\n}\n[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n::-webkit-file-upload-button {\n  -webkit-appearance: button;\n  font: inherit;\n}\ndetails {\n  display: block;\n}\nsummary {\n  display: list-item;\n}\ntemplate {\n  display: none;\n}\n[hidden] {\n  display: none;\n}\nhtml {\n  font-family: sans-serif;\n}\n.hidden,\n[hidden] {\n  display: none !important;\n}\n.pure-img {\n  max-width: 100%;\n  height: auto;\n}\n.pure-g {\n  letter-spacing: -0.31em;\n  text-rendering: optimizespeed;\n  font-family: FreeSans, Arimo, \"Droid Sans\", Helvetica, Arial, sans-serif;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal;\n  -ms-flex-flow: row wrap;\n  flex-flow: row wrap;\n  -ms-flex-line-pack: start;\n  align-content: flex-start;\n}\n@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {\n  table .pure-g {\n    display: block;\n  }\n}\n.opera-only :-o-prefocus,\n.pure-g {\n  word-spacing: -0.43em;\n}\n.pure-u {\n  display: inline-block;\n  letter-spacing: normal;\n  word-spacing: normal;\n  vertical-align: top;\n  text-rendering: auto;\n}\n.pure-g [class*=\"pure-u\"] {\n  font-family: sans-serif;\n}\n.pure-u-1,\n.pure-u-1-1,\n.pure-u-1-12,\n.pure-u-1-2,\n.pure-u-1-24,\n.pure-u-1-3,\n.pure-u-1-4,\n.pure-u-1-5,\n.pure-u-1-6,\n.pure-u-1-8,\n.pure-u-10-24,\n.pure-u-11-12,\n.pure-u-11-24,\n.pure-u-12-24,\n.pure-u-13-24,\n.pure-u-14-24,\n.pure-u-15-24,\n.pure-u-16-24,\n.pure-u-17-24,\n.pure-u-18-24,\n.pure-u-19-24,\n.pure-u-2-24,\n.pure-u-2-3,\n.pure-u-2-5,\n.pure-u-20-24,\n.pure-u-21-24,\n.pure-u-22-24,\n.pure-u-23-24,\n.pure-u-24-24,\n.pure-u-3-24,\n.pure-u-3-4,\n.pure-u-3-5,\n.pure-u-3-8,\n.pure-u-4-24,\n.pure-u-4-5,\n.pure-u-5-12,\n.pure-u-5-24,\n.pure-u-5-5,\n.pure-u-5-6,\n.pure-u-5-8,\n.pure-u-6-24,\n.pure-u-7-12,\n.pure-u-7-24,\n.pure-u-7-8,\n.pure-u-8-24,\n.pure-u-9-24 {\n  display: inline-block;\n  letter-spacing: normal;\n  word-spacing: normal;\n  vertical-align: top;\n  text-rendering: auto;\n}\n.pure-u-1-24 {\n  width: 4.1667%;\n}\n.pure-u-1-12,\n.pure-u-2-24 {\n  width: 8.3333%;\n}\n.pure-u-1-8,\n.pure-u-3-24 {\n  width: 12.5%;\n}\n.pure-u-1-6,\n.pure-u-4-24 {\n  width: 16.6667%;\n}\n.pure-u-1-5 {\n  width: 20%;\n}\n.pure-u-5-24 {\n  width: 20.8333%;\n}\n.pure-u-1-4,\n.pure-u-6-24 {\n  width: 25%;\n}\n.pure-u-7-24 {\n  width: 29.1667%;\n}\n.pure-u-1-3,\n.pure-u-8-24 {\n  width: 33.3333%;\n}\n.pure-u-3-8,\n.pure-u-9-24 {\n  width: 37.5%;\n}\n.pure-u-2-5 {\n  width: 40%;\n}\n.pure-u-10-24,\n.pure-u-5-12 {\n  width: 41.6667%;\n}\n.pure-u-11-24 {\n  width: 45.8333%;\n}\n.pure-u-1-2,\n.pure-u-12-24 {\n  width: 50%;\n}\n.pure-u-13-24 {\n  width: 54.1667%;\n}\n.pure-u-14-24,\n.pure-u-7-12 {\n  width: 58.3333%;\n}\n.pure-u-3-5 {\n  width: 60%;\n}\n.pure-u-15-24,\n.pure-u-5-8 {\n  width: 62.5%;\n}\n.pure-u-16-24,\n.pure-u-2-3 {\n  width: 66.6667%;\n}\n.pure-u-17-24 {\n  width: 70.8333%;\n}\n.pure-u-18-24,\n.pure-u-3-4 {\n  width: 75%;\n}\n.pure-u-19-24 {\n  width: 79.1667%;\n}\n.pure-u-4-5 {\n  width: 80%;\n}\n.pure-u-20-24,\n.pure-u-5-6 {\n  width: 83.3333%;\n}\n.pure-u-21-24,\n.pure-u-7-8 {\n  width: 87.5%;\n}\n.pure-u-11-12,\n.pure-u-22-24 {\n  width: 91.6667%;\n}\n.pure-u-23-24 {\n  width: 95.8333%;\n}\n.pure-u-1,\n.pure-u-1-1,\n.pure-u-24-24,\n.pure-u-5-5 {\n  width: 100%;\n}\n.pure-button {\n  display: inline-block;\n  line-height: normal;\n  white-space: nowrap;\n  vertical-align: middle;\n  text-align: center;\n  cursor: pointer;\n  -webkit-user-drag: none;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n.pure-button::-moz-focus-inner {\n  padding: 0;\n  border: 0;\n}\n.pure-button-group {\n  letter-spacing: -0.31em;\n  text-rendering: optimizespeed;\n}\n.opera-only :-o-prefocus,\n.pure-button-group {\n  word-spacing: -0.43em;\n}\n.pure-button-group .pure-button {\n  letter-spacing: normal;\n  word-spacing: normal;\n  vertical-align: top;\n  text-rendering: auto;\n}\n.pure-button {\n  font-family: inherit;\n  font-size: 100%;\n  padding: 0.5em 1em;\n  color: rgba(0, 0, 0, 0.8);\n  border: none transparent;\n  background-color: #e6e6e6;\n  text-decoration: none;\n  border-radius: 2px;\n}\n.pure-button-hover,\n.pure-button:focus,\n.pure-button:hover {\n  background-image: -webkit-gradient(\n    linear,\n    left top,\n    left bottom,\n    from(transparent),\n    color-stop(40%, rgba(0, 0, 0, 0.05)),\n    to(rgba(0, 0, 0, 0.1))\n  );\n  background-image: linear-gradient(\n    transparent,\n    rgba(0, 0, 0, 0.05) 40%,\n    rgba(0, 0, 0, 0.1)\n  );\n}\n.pure-button:focus {\n  outline: 0;\n}\n.pure-button-active,\n.pure-button:active {\n  -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset,\n    0 0 6px rgba(0, 0, 0, 0.2) inset;\n  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset,\n    0 0 6px rgba(0, 0, 0, 0.2) inset;\n  border-color: #000;\n}\n.pure-button-disabled,\n.pure-button-disabled:active,\n.pure-button-disabled:focus,\n.pure-button-disabled:hover,\n.pure-button[disabled] {\n  border: none;\n  background-image: none;\n  opacity: 0.4;\n  cursor: not-allowed;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n  pointer-events: none;\n}\n.pure-button-hidden {\n  display: none;\n}\n.pure-button-primary,\n.pure-button-selected,\na.pure-button-primary,\na.pure-button-selected {\n  background-color: #0078e7;\n  color: #fff;\n}\n.pure-button-group .pure-button {\n  margin: 0;\n  border-radius: 0;\n  border-right: 1px solid rgba(0, 0, 0, 0.2);\n}\n.pure-button-group .pure-button:first-child {\n  border-top-left-radius: 2px;\n  border-bottom-left-radius: 2px;\n}\n.pure-button-group .pure-button:last-child {\n  border-top-right-radius: 2px;\n  border-bottom-right-radius: 2px;\n  border-right: none;\n}\n.pure-form input[type=\"color\"],\n.pure-form input[type=\"date\"],\n.pure-form input[type=\"datetime-local\"],\n.pure-form input[type=\"datetime\"],\n.pure-form input[type=\"email\"],\n.pure-form input[type=\"month\"],\n.pure-form input[type=\"number\"],\n.pure-form input[type=\"password\"],\n.pure-form input[type=\"search\"],\n.pure-form input[type=\"tel\"],\n.pure-form input[type=\"text\"],\n.pure-form input[type=\"time\"],\n.pure-form input[type=\"url\"],\n.pure-form input[type=\"week\"],\n.pure-form select,\n.pure-form textarea {\n  padding: 0.5em 0.6em;\n  display: inline-block;\n  border: 1px solid #ccc;\n  -webkit-box-shadow: inset 0 1px 3px #ddd;\n  box-shadow: inset 0 1px 3px #ddd;\n  border-radius: 4px;\n  vertical-align: middle;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n.pure-form input:not([type]) {\n  padding: 0.5em 0.6em;\n  display: inline-block;\n  border: 1px solid #ccc;\n  -webkit-box-shadow: inset 0 1px 3px #ddd;\n  box-shadow: inset 0 1px 3px #ddd;\n  border-radius: 4px;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n.pure-form input[type=\"color\"] {\n  padding: 0.2em 0.5em;\n}\n.pure-form input[type=\"color\"]:focus,\n.pure-form input[type=\"date\"]:focus,\n.pure-form input[type=\"datetime-local\"]:focus,\n.pure-form input[type=\"datetime\"]:focus,\n.pure-form input[type=\"email\"]:focus,\n.pure-form input[type=\"month\"]:focus,\n.pure-form input[type=\"number\"]:focus,\n.pure-form input[type=\"password\"]:focus,\n.pure-form input[type=\"search\"]:focus,\n.pure-form input[type=\"tel\"]:focus,\n.pure-form input[type=\"text\"]:focus,\n.pure-form input[type=\"time\"]:focus,\n.pure-form input[type=\"url\"]:focus,\n.pure-form input[type=\"week\"]:focus,\n.pure-form select:focus,\n.pure-form textarea:focus {\n  outline: 0;\n  border-color: #129fea;\n}\n.pure-form input:not([type]):focus {\n  outline: 0;\n  border-color: #129fea;\n}\n.pure-form input[type=\"checkbox\"]:focus,\n.pure-form input[type=\"file\"]:focus,\n.pure-form input[type=\"radio\"]:focus {\n  outline: thin solid #129fea;\n  outline: 1px auto #129fea;\n}\n.pure-form .pure-checkbox,\n.pure-form .pure-radio {\n  margin: 0.5em 0;\n  display: block;\n}\n.pure-form input[type=\"color\"][disabled],\n.pure-form input[type=\"date\"][disabled],\n.pure-form input[type=\"datetime-local\"][disabled],\n.pure-form input[type=\"datetime\"][disabled],\n.pure-form input[type=\"email\"][disabled],\n.pure-form input[type=\"month\"][disabled],\n.pure-form input[type=\"number\"][disabled],\n.pure-form input[type=\"password\"][disabled],\n.pure-form input[type=\"search\"][disabled],\n.pure-form input[type=\"tel\"][disabled],\n.pure-form input[type=\"text\"][disabled],\n.pure-form input[type=\"time\"][disabled],\n.pure-form input[type=\"url\"][disabled],\n.pure-form input[type=\"week\"][disabled],\n.pure-form select[disabled],\n.pure-form textarea[disabled] {\n  cursor: not-allowed;\n  background-color: #eaeded;\n  color: #cad2d3;\n}\n.pure-form input:not([type])[disabled] {\n  cursor: not-allowed;\n  background-color: #eaeded;\n  color: #cad2d3;\n}\n.pure-form input[readonly],\n.pure-form select[readonly],\n.pure-form textarea[readonly] {\n  background-color: #eee;\n  color: #777;\n  border-color: #ccc;\n}\n.pure-form input:focus:invalid,\n.pure-form select:focus:invalid,\n.pure-form textarea:focus:invalid {\n  color: #b94a48;\n  border-color: #e9322d;\n}\n.pure-form input[type=\"checkbox\"]:focus:invalid:focus,\n.pure-form input[type=\"file\"]:focus:invalid:focus,\n.pure-form input[type=\"radio\"]:focus:invalid:focus {\n  outline-color: #e9322d;\n}\n.pure-form select {\n  height: 2.25em;\n  border: 1px solid #ccc;\n  background-color: #fff;\n}\n.pure-form select[multiple] {\n  height: auto;\n}\n.pure-form label {\n  margin: 0.5em 0 0.2em;\n}\n.pure-form fieldset {\n  margin: 0;\n  padding: 0.35em 0 0.75em;\n  border: 0;\n}\n.pure-form legend {\n  display: block;\n  width: 100%;\n  padding: 0.3em 0;\n  margin-bottom: 0.3em;\n  color: #333;\n  border-bottom: 1px solid #e5e5e5;\n}\n.pure-form-stacked input[type=\"color\"],\n.pure-form-stacked input[type=\"date\"],\n.pure-form-stacked input[type=\"datetime-local\"],\n.pure-form-stacked input[type=\"datetime\"],\n.pure-form-stacked input[type=\"email\"],\n.pure-form-stacked input[type=\"file\"],\n.pure-form-stacked input[type=\"month\"],\n.pure-form-stacked input[type=\"number\"],\n.pure-form-stacked input[type=\"password\"],\n.pure-form-stacked input[type=\"search\"],\n.pure-form-stacked input[type=\"tel\"],\n.pure-form-stacked input[type=\"text\"],\n.pure-form-stacked input[type=\"time\"],\n.pure-form-stacked input[type=\"url\"],\n.pure-form-stacked input[type=\"week\"],\n.pure-form-stacked label,\n.pure-form-stacked select,\n.pure-form-stacked textarea {\n  display: block;\n  margin: 0.25em 0;\n}\n.pure-form-stacked input:not([type]) {\n  display: block;\n  margin: 0.25em 0;\n}\n.pure-form-aligned input,\n.pure-form-aligned select,\n.pure-form-aligned textarea,\n.pure-form-message-inline {\n  display: inline-block;\n  vertical-align: middle;\n}\n.pure-form-aligned textarea {\n  vertical-align: top;\n}\n.pure-form-aligned .pure-control-group {\n  margin-bottom: 0.5em;\n}\n.pure-form-aligned .pure-control-group label {\n  text-align: right;\n  display: inline-block;\n  vertical-align: middle;\n  width: 10em;\n  margin: 0 1em 0 0;\n}\n.pure-form-aligned .pure-controls {\n  margin: 1.5em 0 0 11em;\n}\n.pure-form .pure-input-rounded,\n.pure-form input.pure-input-rounded {\n  border-radius: 2em;\n  padding: 0.5em 1em;\n}\n.pure-form .pure-group fieldset {\n  margin-bottom: 10px;\n}\n.pure-form .pure-group input,\n.pure-form .pure-group textarea {\n  display: block;\n  padding: 10px;\n  margin: 0 0 -1px;\n  border-radius: 0;\n  position: relative;\n  top: -1px;\n}\n.pure-form .pure-group input:focus,\n.pure-form .pure-group textarea:focus {\n  z-index: 3;\n}\n.pure-form .pure-group input:first-child,\n.pure-form .pure-group textarea:first-child {\n  top: 1px;\n  border-radius: 4px 4px 0 0;\n  margin: 0;\n}\n.pure-form .pure-group input:first-child:last-child,\n.pure-form .pure-group textarea:first-child:last-child {\n  top: 1px;\n  border-radius: 4px;\n  margin: 0;\n}\n.pure-form .pure-group input:last-child,\n.pure-form .pure-group textarea:last-child {\n  top: -2px;\n  border-radius: 0 0 4px 4px;\n  margin: 0;\n}\n.pure-form .pure-group button {\n  margin: 0.35em 0;\n}\n.pure-form .pure-input-1 {\n  width: 100%;\n}\n.pure-form .pure-input-3-4 {\n  width: 75%;\n}\n.pure-form .pure-input-2-3 {\n  width: 66%;\n}\n.pure-form .pure-input-1-2 {\n  width: 50%;\n}\n.pure-form .pure-input-1-3 {\n  width: 33%;\n}\n.pure-form .pure-input-1-4 {\n  width: 25%;\n}\n.pure-form-message-inline {\n  display: inline-block;\n  padding-left: 0.3em;\n  color: #666;\n  vertical-align: middle;\n  font-size: 0.875em;\n}\n.pure-form-message {\n  display: block;\n  color: #666;\n  font-size: 0.875em;\n}\n@media only screen and (max-width: 480px) {\n  .pure-form button[type=\"submit\"] {\n    margin: 0.7em 0 0;\n  }\n  .pure-form input:not([type]),\n  .pure-form input[type=\"color\"],\n  .pure-form input[type=\"date\"],\n  .pure-form input[type=\"datetime-local\"],\n  .pure-form input[type=\"datetime\"],\n  .pure-form input[type=\"email\"],\n  .pure-form input[type=\"month\"],\n  .pure-form input[type=\"number\"],\n  .pure-form input[type=\"password\"],\n  .pure-form input[type=\"search\"],\n  .pure-form input[type=\"tel\"],\n  .pure-form input[type=\"text\"],\n  .pure-form input[type=\"time\"],\n  .pure-form input[type=\"url\"],\n  .pure-form input[type=\"week\"],\n  .pure-form label {\n    margin-bottom: 0.3em;\n    display: block;\n  }\n  .pure-group input:not([type]),\n  .pure-group input[type=\"color\"],\n  .pure-group input[type=\"date\"],\n  .pure-group input[type=\"datetime-local\"],\n  .pure-group input[type=\"datetime\"],\n  .pure-group input[type=\"email\"],\n  .pure-group input[type=\"month\"],\n  .pure-group input[type=\"number\"],\n  .pure-group input[type=\"password\"],\n  .pure-group input[type=\"search\"],\n  .pure-group input[type=\"tel\"],\n  .pure-group input[type=\"text\"],\n  .pure-group input[type=\"time\"],\n  .pure-group input[type=\"url\"],\n  .pure-group input[type=\"week\"] {\n    margin-bottom: 0;\n  }\n  .pure-form-aligned .pure-control-group label {\n    margin-bottom: 0.3em;\n    text-align: left;\n    display: block;\n    width: 100%;\n  }\n  .pure-form-aligned .pure-controls {\n    margin: 1.5em 0 0 0;\n  }\n  .pure-form-message,\n  .pure-form-message-inline {\n    display: block;\n    font-size: 0.75em;\n    padding: 0.2em 0 0.8em;\n  }\n}\n.pure-menu {\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n.pure-menu-fixed {\n  position: fixed;\n  left: 0;\n  top: 0;\n  z-index: 3;\n}\n.pure-menu-item,\n.pure-menu-list {\n  position: relative;\n}\n.pure-menu-list {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n.pure-menu-item {\n  padding: 0;\n  margin: 0;\n  height: 100%;\n}\n.pure-menu-heading,\n.pure-menu-link {\n  display: block;\n  text-decoration: none;\n  white-space: nowrap;\n}\n.pure-menu-horizontal {\n  width: 100%;\n  white-space: nowrap;\n}\n.pure-menu-horizontal .pure-menu-list {\n  display: inline-block;\n}\n.pure-menu-horizontal .pure-menu-heading,\n.pure-menu-horizontal .pure-menu-item,\n.pure-menu-horizontal .pure-menu-separator {\n  display: inline-block;\n  vertical-align: middle;\n}\n.pure-menu-item .pure-menu-item {\n  display: block;\n}\n.pure-menu-children {\n  display: none;\n  position: absolute;\n  left: 100%;\n  top: 0;\n  margin: 0;\n  padding: 0;\n  z-index: 3;\n}\n.pure-menu-horizontal .pure-menu-children {\n  left: 0;\n  top: auto;\n  width: inherit;\n}\n.pure-menu-active > .pure-menu-children,\n.pure-menu-allow-hover:hover > .pure-menu-children {\n  display: block;\n  position: absolute;\n}\n.pure-menu-has-children > .pure-menu-link:after {\n  padding-left: 0.5em;\n  content: \"\\25B8\";\n  font-size: small;\n}\n.pure-menu-horizontal .pure-menu-has-children > .pure-menu-link:after {\n  content: \"\\25BE\";\n}\n.pure-menu-scrollable {\n  overflow-y: scroll;\n  overflow-x: hidden;\n}\n.pure-menu-scrollable .pure-menu-list {\n  display: block;\n}\n.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list {\n  display: inline-block;\n}\n.pure-menu-horizontal.pure-menu-scrollable {\n  white-space: nowrap;\n  overflow-y: hidden;\n  overflow-x: auto;\n  padding: 0.5em 0;\n}\n.pure-menu-horizontal .pure-menu-children .pure-menu-separator,\n.pure-menu-separator {\n  background-color: #ccc;\n  height: 1px;\n  margin: 0.3em 0;\n}\n.pure-menu-horizontal .pure-menu-separator {\n  width: 1px;\n  height: 1.3em;\n  margin: 0 0.3em;\n}\n.pure-menu-horizontal .pure-menu-children .pure-menu-separator {\n  display: block;\n  width: auto;\n}\n.pure-menu-heading {\n  text-transform: uppercase;\n  color: #565d64;\n}\n.pure-menu-link {\n  color: #777;\n}\n.pure-menu-children {\n  background-color: #fff;\n}\n.pure-menu-disabled,\n.pure-menu-heading,\n.pure-menu-link {\n  padding: 0.5em 1em;\n}\n.pure-menu-disabled {\n  opacity: 0.5;\n}\n.pure-menu-disabled .pure-menu-link:hover {\n  background-color: transparent;\n}\n.pure-menu-active > .pure-menu-link,\n.pure-menu-link:focus,\n.pure-menu-link:hover {\n  background-color: #eee;\n}\n.pure-menu-selected > .pure-menu-link,\n.pure-menu-selected > .pure-menu-link:visited {\n  color: #000;\n}\n.pure-table {\n  border-collapse: collapse;\n  border-spacing: 0;\n  empty-cells: show;\n  border: 1px solid #cbcbcb;\n}\n.pure-table caption {\n  color: #000;\n  font: italic 85%/1 arial, sans-serif;\n  padding: 1em 0;\n  text-align: center;\n}\n.pure-table td,\n.pure-table th {\n  border-left: 1px solid #cbcbcb;\n  border-width: 0 0 0 1px;\n  font-size: inherit;\n  margin: 0;\n  overflow: visible;\n  padding: 0.5em 1em;\n}\n.pure-table thead {\n  background-color: #e0e0e0;\n  color: #000;\n  text-align: left;\n  vertical-align: bottom;\n}\n.pure-table td {\n  background-color: transparent;\n}\n.pure-table-odd td {\n  background-color: #f2f2f2;\n}\n.pure-table-striped tr:nth-child(2n-1) td {\n  background-color: #f2f2f2;\n}\n.pure-table-bordered td {\n  border-bottom: 1px solid #cbcbcb;\n}\n.pure-table-bordered tbody > tr:last-child > td {\n  border-bottom-width: 0;\n}\n.pure-table-horizontal td,\n.pure-table-horizontal th {\n  border-width: 0 0 1px 0;\n  border-bottom: 1px solid #cbcbcb;\n}\n.pure-table-horizontal tbody > tr:last-child > td {\n  border-bottom-width: 0;\n}\n@font-face {\n  font-family: \"Google Sans\";\n  src: url(\"data:application/font-woff2;charset=utf-8;base64,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\")\n      format(\"woff2\"),\n    url(\"data:application/font-woff;charset=utf-8;base64,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\")\n      format(\"woff\");\n  font-weight: normal;\n  font-style: normal;\n}\n@font-face {\n  font-family: \"TypoGraphica\";\n  src: url(\"../fonts/TypoGraphica.eot?#iefix\") format(\"embedded-opentype\"),\n    url(\"../fonts/TypoGraphica.woff\") format(\"woff\"),\n    url(\"../fonts/TypoGraphica.ttf\") format(\"truetype\"),\n    url(\"../fonts/TypoGraphica.svg#TypoGraphica\") format(\"svg\");\n  font-weight: normal;\n  font-style: normal;\n}\n@font-face {\n  font-family: \"argon\";\n  src: url(\"../fonts/argon.eot?u6kthm\");\n  src: url(\"../fonts/argon.eot?u6kthm#iefix\") format(\"embedded-opentype\"),\n    url(\"../fonts/argon.ttf?u6kthm\") format(\"truetype\"),\n    url(\"../fonts/argon.woff?u6kthm\") format(\"woff\"),\n    url(\"../fonts/argon.svg?u6kthm#argon\") format(\"svg\");\n  font-weight: normal;\n  font-style: normal;\n  font-display: block;\n}\n[class^=\"icon-\"],\n[class*=\" icon-\"] {\n  font-family: \"argon\" !important;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n.icon-expand_more:before {\n  content: \"\\e20b\";\n}\n.icon-menu:before {\n  content: \"\\e20e\";\n}\n.icon-favorite:before {\n  content: \"\\e291\";\n}\n.icon-spinner:before {\n  content: \"\\e603\";\n}\n.icon-delete:before {\n  content: \"\\e900\";\n}\n.icon-edit:before {\n  content: \"\\e901\";\n}\n.icon-use:before {\n  content: \"\\e902\";\n}\n.icon-loading:before {\n  content: \"\\e903\";\n}\n.icon-switch:before {\n  content: \"\\e904\";\n}\n.icon-error:before {\n  content: \"\\e905\";\n}\n.icon-dashboard:before {\n  content: \"\\e906\";\n}\n.icon-logout:before {\n  content: \"\\e907\";\n}\n.icon-Network:before {\n  content: \"\\e908\";\n}\n.icon-services:before {\n  content: \"\\e909\";\n}\n.icon-system:before {\n  content: \"\\e90a\";\n}\n.icon-vpn:before {\n  content: \"\\e90b\";\n}\n.icon-storage:before {\n  content: \"\\e90c\";\n}\n.icon-statistics:before {\n  content: \"\\e90d\";\n}\n.icon-hello-world:before {\n  content: \"\\e90e\";\n}\n.icon-angle-right:before {\n  content: \"\\e90f\";\n}\n.icon-password:before {\n  content: \"\\e910\";\n}\n.icon-user:before {\n  content: \"\\e971\";\n}\n.icon-question:before {\n  content: \"\\f059\";\n}\n.icon-docker:before {\n  content: \"\\e911\";\n}\n.icon-control:before {\n  content: \"\\e912\";\n}\n.icon-statistics1:before {\n  content: \"\\e913\";\n}\n.icon-asterisk:before {\n  content: \"\\e914\";\n}\n.icon-app:before {\n  content: \"\\e915\";\n}\n:root {\n  --primary: #5e72e4;\n  --dark-primary: #483d8b;\n  --main-color: #09c;\n  --header-bg: #09c;\n  --header-color: #fff;\n  --bar-bg: #5e72e4;\n  --menu-bg-color: #fff;\n  --menu-color: #5f6368;\n  --menu-color-hover: #202124;\n  --main-menu-color: #202124;\n  --submenu-bg-hover: #d4d4d4;\n  --submenu-bg-hover-active: #09c;\n  --blue: #5e72e4;\n  --indigo: #5603ad;\n  --purple: #8965e0;\n  --pink: #f3a4b5;\n  --red: #f5365c;\n  --orange: #fb6340;\n  --yellow: #ffd600;\n  --green: #2dce89;\n  --teal: #11cdef;\n  --cyan: #2bffc6;\n  --gray: #8898aa;\n  --gray-dark: #32325d;\n  --light: #ced4da;\n  --lighter: #e9ecef;\n  --secondary: #f7fafc;\n  --success: #2dce89;\n  --info: #11cdef;\n  --warning: #fb6340;\n  --danger: #f5365c;\n  --light: #adb5bd;\n  --dark: #212529;\n  --default: #172b4d;\n  --white: #fff;\n  --neutral: #fff;\n  --darker: black;\n  --background-color: #f4f5f7;\n  --login-form-bg-color: rgba(244, 245, 247, 0.8);\n  --breakpoint-xs: 0;\n  --breakpoint-sm: 576px;\n  --breakpoint-md: 768px;\n  --breakpoint-lg: 992px;\n  --breakpoint-xl: 1200px;\n  --blur-radius: 10px;\n  --blur-opacity: 0.5;\n  --blur-radius-dark: 10px;\n  --blur-opacity-dark: 0.5;\n  --font-family-sans-serif: \"Google Sans\", \"Microsoft Yahei\",\n    \"WenQuanYi Micro Hei\", \"sans-serif\", \"Helvetica Neue\", \"Helvetica\",\n    \"Hiragino Sans GB\";\n  --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas,\n    \"Liberation Mono\", \"Courier New\", monospace;\n  --font-family-normal: Open Sans, PingFangSC-Regular, Microsoft Yahei,\n    WenQuanYi Micro Hei, \"Helvetica Neue\", Helvetica, Hiragino Sans GB,\n    sans-serif;\n}\n* {\n  margin: 0px;\n  padding: 0px;\n  box-sizing: border-box;\n}\nhtml,\nbody {\n  margin: 0px;\n  padding: 0px;\n  height: 100%;\n  font-size: 16px;\n  font-family: \"Google Sans\", \"Microsoft Yahei\", \"WenQuanYi Micro Hei\",\n    \"sans-serif\", \"Helvetica Neue\", \"Helvetica\", \"Hiragino Sans GB\";\n  font-family: var(--font-family-sans-serif);\n}\nhtml {\n  -webkit-text-size-adjust: 100%;\n  -ms-text-size-adjust: 100%;\n}\nbody {\n  font-size: 0.875rem;\n  background-color: #f4f5f7;\n  background-color: var(--background-color);\n  color: #32325d;\n  color: var(--gray-dark);\n  -webkit-tap-highlight-color: transparent;\n}\ntextarea {\n  padding: 0.2rem;\n}\ntextarea:focus-visible {\n  outline: none;\n  border: 1px solid var(--primary);\n}\n::selection {\n  background-color: #5e72e4;\n  background-color: var(--primary);\n  color: #ffffff;\n  color: var(--white);\n}\n::placeholder {\n  color: var(--lighter);\n}\na:link,\na:visited,\na:active {\n  color: #5e72e4;\n  color: var(--primary);\n  text-decoration: none;\n}\na:hover {\n  text-decoration: underline;\n}\nli {\n  list-style-type: none;\n}\n.table {\n  position: relative;\n  display: table;\n}\n.tr {\n  display: table-row;\n}\n.thead {\n  display: table-header-group;\n}\n.tbody {\n  display: table-row-group;\n}\n.tfoot {\n  display: table-footer-group;\n}\n.td,\n.th {\n  line-height: normal;\n  display: table-cell;\n  padding: 0.5em;\n  text-align: center;\n  vertical-align: middle;\n}\n.th {\n  font-weight: bold;\n  white-space: nowrap;\n}\n.tr.placeholder {\n  height: 4em;\n}\n.tr.placeholder > .td {\n  line-height: 3;\n  position: absolute;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  padding: 0.4rem 0 !important;\n  text-align: center !important;\n  background: inherit;\n}\n.td[width=\"33%\"] {\n  padding: 1.1em 1.5rem;\n}\n.table[width=\"33%\"],\n.th[width=\"33%\"],\n.td[width=\"33%\"] {\n  width: 33%;\n}\n.table[width=\"100%\"],\n.th[width=\"100%\"],\n.td[width=\"100%\"] {\n  width: 100%;\n}\n.col-1 {\n  flex: 1 1 30px !important;\n}\n.col-2 {\n  flex: 2 2 60px !important;\n}\n.col-3 {\n  flex: 3 3 90px !important;\n}\n.col-4 {\n  flex: 4 4 120px !important;\n}\n.col-5 {\n  flex: 5 5 150px !important;\n}\n.col-6 {\n  flex: 6 6 180px !important;\n}\n.col-7 {\n  flex: 7 7 210px !important;\n}\n.col-8 {\n  flex: 8 8 240px !important;\n}\n.col-9 {\n  flex: 9 9 270px !important;\n}\n.col-10 {\n  flex: 10 10 300px !important;\n}\n* {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n}\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-family: inherit;\n  font-weight: normal;\n  line-height: 1.1 !important;\n  color: inherit;\n}\nselect {\n  padding: 0.36rem 0.8rem;\n  color: #555;\n  border: thin solid #ccc;\n  background-color: #fff;\n  background-image: none;\n}\n.btn,\nbutton,\nselect,\ninput,\n.cbi-dropdown {\n  line-height: 1.5em;\n  padding: 0.5rem 0.75rem;\n  color: #8898aa;\n  border: 1px solid #dee2e6;\n  border-radius: 0.25rem;\n  outline: 0;\n  background-image: none;\n  box-shadow: none;\n  transition: box-shadow 0.15s ease;\n}\nselect,\n.cbi-dropdown {\n  width: inherit;\n  cursor: default;\n}\nselect:not([multiple=\"multiple\"]):focus,\ninput:not(.cbi-button):focus,\n.cbi-dropdown:focus {\n  border-color: #5e72e4;\n  border-color: var(--primary);\n  box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);\n}\n.cbi-dropdown,\nselect[multiple=\"multiple\"] {\n  height: auto;\n}\npre {\n  overflow: auto;\n}\ncode {\n  font-size: 0.875rem;\n  padding: 1px 3px;\n  color: #101010;\n  border-radius: 2px;\n  background: #ddd;\n}\nabbr {\n  cursor: help;\n  text-decoration: underline;\n  color: #5e72e4;\n  color: var(--primary);\n}\nhr {\n  margin: 1rem 0;\n  opacity: 0.1;\n  border-color: #eee;\n}\nul {\n  line-height: normal;\n}\nli {\n  list-style-type: none;\n}\nh1 {\n  font-size: 2rem;\n  padding-bottom: 10px;\n  border-bottom: thin solid #eee;\n}\nh2 {\n  margin: 0 0 1rem 0;\n  font-size: 1.25rem;\n  letter-spacing: 0.1rem;\n  padding: 1rem 1.25rem;\n  color: #32325d;\n  border-radius: 0.25rem;\n  background: #fff;\n  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);\n  font-weight: bold;\n}\nh3 {\n  font-size: 1.1rem;\n  line-height: 1;\n  display: block;\n  width: 100%;\n  margin: 0;\n  margin-bottom: 0;\n  padding: 0.8755rem 1.25rem;\n  color: #32325d;\n  color: var(--gray-dark);\n  border-radius: 0.25rem;\n  background: #fff;\n  font-weight: bold;\n}\nh4 {\n  margin: 0;\n  padding: 0.75rem 1.25rem;\n  font-size: 0.875rem;\n  font-weight: 600;\n  color: #525f7f;\n  font-weight: bold;\n}\nh4 em {\n  padding: 0 0.5rem;\n}\nh5 {\n  font-size: 1rem;\n  margin: 2rem 0 0 0;\n  padding-bottom: 10px;\n}\n.pull-right {\n  float: right;\n}\n.pull-left {\n  float: left;\n}\n.nowrap:not(.td) {\n  white-space: nowrap;\n}\n[disabled=\"disabled\"] {\n  pointer-events: none;\n}\n.login-page {\n  height: 100%;\n}\n.login-page .video {\n  position: absolute;\n  width: 100%;\n  height: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background-color: #000;\n  background-color: var(--darker);\n  overflow: hidden;\n}\n.login-page .video video {\n  width: 100%;\n  height: auto;\n}\n.login-page .volume-control {\n  position: fixed;\n  right: 1rem;\n  top: 1rem;\n  width: 1.5rem;\n  height: 1.5rem;\n  z-index: 5000;\n  cursor: pointer;\n  background-size: contain;\n  background-image: url(../img/volume_high.svg);\n}\n.login-page .volume-control.mute {\n  background-image: url(../img/volume_off.svg);\n}\n.login-page .main-bg {\n  position: absolute;\n  width: 100%;\n  height: 100%;\n  left: 0;\n  top: 0;\n  background-image: url(../img/blank.png);\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: cover;\n  transition: all 0.5s;\n}\n.login-page .login-container {\n  height: 100%;\n  margin-left: 4.5rem;\n  position: absolute;\n  top: 0px;\n  display: flex;\n  flex-direction: column;\n  -webkit-box-pack: center;\n  justify-content: center;\n  align-items: flex-start;\n  min-height: 100%;\n  z-index: 2;\n  width: 420px;\n  box-shadow: rgba(0, 0, 0, 0.75) 0 0 35px -5px;\n  margin-left: 5%;\n  background: transparent;\n}\n.login-page .login-container .login-form {\n  display: flex;\n  flex-direction: column;\n  -webkit-box-align: center;\n  align-items: center;\n  position: absolute;\n  top: 0px;\n  width: 100%;\n  min-height: 100%;\n  max-width: 420px;\n  background-color: #fff;\n  background-color: var(--white);\n  -webkit-backdrop-filter: blur(var(--blur-radius));\n  backdrop-filter: blur(var(--blur-radius));\n  background-color: rgba(244, 245, 247, var(--blur-opacity));\n}\n.login-page .login-container .login-form .brand {\n  display: flex;\n  -webkit-box-align: center;\n  align-items: center;\n  margin: 50px auto 100px 50px;\n  color: #525461;\n  color: var(--default);\n  justify-content: center;\n}\n.login-page .login-container .login-form .brand .icon {\n  width: 50px;\n  height: auto;\n  margin-right: 25px;\n}\n.login-page .login-container .login-form .brand .brand-text {\n  font-size: 1.25rem;\n  font-weight: 700;\n  font-family: \"TypoGraphica\";\n}\n.login-page .login-container .login-form .brand:hover {\n  text-decoration: none;\n}\n.login-page .login-container .login-form .form-login {\n  width: 100%;\n  padding: 20px 50px;\n  box-sizing: border-box;\n}\n.login-page .login-container .login-form .form-login .errorbox {\n  text-align: center;\n  color: #fb6340;\n  color: var(--warning);\n  padding-bottom: 2rem;\n}\n.login-page .login-container .login-form .form-login .input-group {\n  margin-bottom: 1.25rem;\n  position: relative;\n}\n.login-page .login-container .login-form .form-login .input-group::before {\n  font-family: \"argon\" !important;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  color: #525461;\n  color: var(--default);\n  font-size: 1.5rem;\n  position: absolute;\n  z-index: 100;\n  left: 10px;\n  top: 10px;\n}\n.login-page .login-container .login-form .form-login .input-group .border {\n  position: absolute;\n  width: 100%;\n  height: 1px;\n  bottom: 0;\n  border-bottom: 1px #5e72e4 solid;\n  border-bottom: 1px var(--primary) solid;\n  transform: scaleX(0);\n  transition: transform 0.3s;\n}\n.login-page .login-container .login-form .form-login .input-group input {\n  font-size: 1rem;\n  line-height: 1.5em;\n  display: block;\n  width: 100%;\n  padding: 0.5rem 0.75rem 0.5rem 3rem;\n  margin: 0.825rem 0;\n  box-sizing: border-box;\n  transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);\n  color: #525461;\n  color: var(--default);\n  border: 0;\n  border-radius: 0;\n  border-bottom: 1px solid #fff;\n  border-bottom: 1px solid var(--white);\n  background-color: transparent;\n  background-clip: padding-box;\n  box-shadow: 0 3px 2px rgba(233, 236, 239, 0.05);\n  outline: none;\n}\n.login-page\n  .login-container\n  .login-form\n  .form-login\n  .input-group\n  input:focus\n  + .border {\n  transform: scaleX(1);\n}\n.login-page\n  .login-container\n  .login-form\n  .form-login\n  .input-group\n  .cbi-input-password {\n  margin-bottom: 2rem;\n  position: relative;\n}\n.login-page .login-container .login-form .form-login .user-icon::before {\n  content: \"\\e971\";\n}\n.login-page .login-container .login-form .form-login .pass-icon::before {\n  content: \"\\e910\";\n}\n.login-page .login-container .login-form .cbi-button-apply {\n  width: 100% !important;\n  box-shadow: rgba(0, 0, 0, 0.1) 0 0 50px 0;\n  font-weight: 600;\n  font-size: 15px;\n  color: #fff;\n  color: var(--white);\n  text-align: center;\n  width: 100%;\n  cursor: pointer;\n  min-height: 50px;\n  background-color: #5e72e4 !important;\n  background-color: var(--primary) !important;\n  border-radius: 6px;\n  outline: none;\n  border-width: initial;\n  border-style: none;\n  border-color: initial;\n  border-image: initial;\n  padding: 10px 0px;\n  margin: 30px 0px 100px;\n  transition: all 0.3s !important;\n  letter-spacing: 0.8rem;\n}\n.login-page .login-container .login-form .cbi-button-apply:hover,\n.login-page .login-container .login-form .cbi-button-apply :focus {\n  opacity: 0.9;\n}\n.login-page .login-container footer {\n  box-sizing: border-box;\n  width: 100%;\n  text-align: center;\n  line-height: 1.6rem;\n  display: flex;\n  justify-content: space-evenly;\n  margin-top: auto;\n  padding: 0px 0px 30px;\n  z-index: 10;\n  color: #525461;\n  color: var(--default);\n  position: absolute;\n  bottom: 0;\n}\n.login-page .login-container footer .ftc {\n  position: absolute;\n  bottom: 30px;\n  width: 100%;\n}\n.login-page .login-container footer .luci-link {\n  display: block;\n}\nheader,\n.main {\n  width: 100%;\n}\nfooter {\n  font-size: 0.875rem;\n  overflow: hidden;\n  padding: 1rem;\n  text-align: right;\n  white-space: nowrap;\n  color: #aaa;\n}\nfooter > a {\n  text-decoration: none;\n  color: #aaa;\n}\nsmall {\n  font-size: 90%;\n  line-height: 1.42857143;\n  white-space: normal;\n}\n.main {\n  position: relative;\n  top: 0;\n  bottom: 0;\n  overflow-y: auto;\n  height: 100%;\n  display: flex;\n  flex-direction: row;\n}\n.main-left {\n  flex-shrink: 0;\n  width: 15rem;\n  height: 100%;\n  background-color: var(--menu-bg-color);\n  box-shadow: rgba(0, 0, 0, 0.75) 0 0 15px -5px;\n  overflow-x: auto;\n  z-index: 100;\n}\n.main-left .sidenav-header {\n  padding: 1.5rem 0.5rem;\n  text-align: center;\n}\n.main-left .sidenav-header .brand {\n  display: block;\n  font-size: 1.8rem;\n  color: #5e72e4;\n  color: var(--primary);\n  font-family: \"TypoGraphica\";\n  text-decoration: none;\n  text-align: center;\n  cursor: default;\n  margin: 0 2rem;\n}\n.main-left .sidenav-header .brand .logo {\n  max-width: 100%;\n  height: auto;\n}\n.main-left::-webkit-scrollbar {\n  width: 5px;\n  height: 1px;\n}\n.main-left::-webkit-scrollbar-thumb {\n  background-color: #f6f9fc;\n}\n.main-left::-webkit-scrollbar-track {\n  background-color: #fff;\n}\n.main-right {\n  flex-grow: 1;\n  height: 100%;\n  transition: all 0.2s;\n  overflow-x: hidden;\n  overflow-y: auto;\n  display: flex;\n  flex-direction: column;\n}\n.main-right > #maincontent {\n  position: relative;\n  z-index: 50;\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n}\n.main-right > #maincontent > .container {\n  margin: 0 1.25rem 1rem 1.25rem;\n  flex-grow: 1;\n}\n.main-right > #maincontent .Dashboard {\n  color: var(--gray-dark) !important;\n}\n.main-right > #maincontent .Dashboard h3 {\n  color: var(--gray-dark);\n}\n.main-right > #maincontent .Dashboard p {\n  margin-bottom: 3px;\n  margin-top: 3px;\n}\n.main-right > #maincontent .Dashboard hr {\n  border-top: 1px solid #000;\n}\n.main-right > #maincontent .Dashboard .dashboard-bg {\n  background-color: #fff;\n}\n.main-right > #maincontent .Dashboard .settings-info {\n  padding-top: 1em;\n  padding-bottom: 1em;\n}\n.main-right > #maincontent .Dashboard .settings-info p span:nth-child(2) {\n  max-height: 18.5px;\n  top: 4px;\n}\n.main-right > #maincontent .Dashboard .settings-info .label {\n  font-size: 0.7rem;\n  padding: 0.2rem 0.6rem;\n}\nheader {\n  color: #fff;\n  color: var(--header-color);\n  padding: 0;\n  position: relative;\n}\nheader.bg-primary {\n  background-color: #5e72e4 !important;\n  background-color: var(--primary) !important;\n}\nheader::after {\n  content: \"\";\n  position: absolute;\n  height: 2rem;\n  width: 100%;\n  background-color: #5e72e4 !important;\n  background-color: var(--primary) !important;\n}\nheader .fill {\n  padding: 0.8rem 0;\n  border-bottom: 0 solid rgba(255, 255, 255, 0.08) !important;\n  display: flex;\n}\nheader .fill .container {\n  height: 2rem;\n  padding: 0 1.25rem;\n  display: flex;\n  align-items: center;\n  width: 100%;\n}\nheader .fill .container .flex1 {\n  flex: 1;\n}\nheader .fill .container .flex1 .showSide {\n  display: none;\n  color: #fff;\n  font-size: 1.4rem;\n}\nheader .fill .container .flex1 .showSide:hover {\n  text-decoration: none;\n}\nheader .fill .container .flex1 .brand {\n  font-size: 1.5rem;\n  color: #fff;\n  font-family: \"TypoGraphica\";\n  text-decoration: none;\n  padding-left: 1rem;\n  cursor: default;\n  vertical-align: text-bottom;\n  display: none;\n}\nheader .fill .container .pull-right {\n  float: right;\n  margin-top: 0rem;\n  display: flex;\n}\nheader .fill .status span {\n  display: inline-block;\n  font-size: 0.875rem;\n  font-weight: bold;\n  padding: 0.3rem 0.8rem;\n  white-space: nowrap;\n  text-decoration: none;\n  text-transform: uppercase;\n  text-shadow: none;\n  border-radius: 4px;\n  cursor: pointer;\n  transition: all 0.3s;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);\n  margin: 0 0.25rem;\n}\nheader .fill .status span:last-child {\n  margin-right: 0;\n}\nheader .fill .status span[data-indicator=\"poll-status\"] {\n  color: #fff;\n}\nheader .fill .status span[data-style=\"active\"] {\n  background-color: var(--green);\n}\nheader .fill .status span[data-style=\"inactive\"] {\n  color: #ffffff !important;\n  background-color: #32325d;\n}\n#xhr_poll_status {\n  display: flex;\n  margin-left: 0.5rem;\n}\n#xhr_poll_status * {\n  color: #fff;\n}\ndiv[style=\"width:100%;height:300px;border:1px solid #000;background:#fff\"] {\n  border: 0 !important;\n}\n.danger {\n  background-color: #ff7d60 !important;\n}\n.warning {\n  background-color: #f0e68c !important;\n}\n.success {\n  background-color: #5cb85c !important;\n}\n.notice {\n  background-color: #11cdef !important;\n  color: #fff;\n}\n.error {\n  color: #f00;\n}\n.alert,\n.alert-message {\n  font-weight: bold;\n  margin-bottom: 1.25rem;\n  margin-left: 1.25rem;\n  margin-right: 1.25rem;\n  padding: 1rem 1.25rem;\n  border: 0;\n  border-radius: 0.25rem !important;\n  background-color: #fff;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);\n  text-shadow: none;\n}\n.alert.error,\n.alert-message.error {\n  background-color: #ffd600;\n}\n.alert h4,\n.alert-message h4 {\n  padding: 0.25rem 0;\n  border-radius: 4px;\n  background-color: #ffd600;\n}\n.alert .btn,\n.alert-message .btn {\n  height: auto;\n}\n.alert-message > h4 {\n  font-size: 110%;\n  font-weight: bold;\n}\n.alert-message > * {\n  margin: 0.5rem 0;\n}\n.alert-message .btn {\n  padding: 0.3rem 0.6rem;\n}\n.container .alert,\n.container .alert-message {\n  margin-left: 0;\n  margin-right: 0;\n  margin-top: 0rem;\n}\n.main .main-left {\n  transition: all 0.2s;\n}\n.main .main-left .nav {\n  margin-top: 0.5rem;\n}\n.main .main-left .nav > li > a:first-child {\n  display: block;\n  margin: 0.1rem 0.5rem 0.1rem 0.5rem;\n  padding: 0.675rem 0 0.675rem 2.5rem;\n  border-radius: 0.25rem;\n  text-decoration: none;\n  cursor: default;\n  font-size: 1rem;\n  transition: all 0.2s;\n  position: relative;\n}\n.main .main-left .nav > li > a:first-child.active {\n  color: #fff;\n  background: #5e72e4;\n  background: var(--primary);\n}\n.main .main-left .nav > li > a:first-child.active::before {\n  color: #fff !important;\n}\n.main .main-left .nav > li > a:first-child.active::after {\n  transform: rotate(90deg);\n  color: #fff !important;\n}\n.main .main-left .nav > li > a:first-child:hover {\n  cursor: pointer;\n  color: #fff;\n  background: #5e72e4;\n  background: var(--primary);\n}\n.main .main-left .nav > li > a:first-child:hover::before {\n  color: #fff !important;\n}\n.main .main-left .nav > li > a:first-child::before {\n  font-family: \"argon\" !important;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  position: absolute;\n  left: 0.8rem;\n  padding-top: 3px;\n  transition: all 0.3s;\n  content: \"\\e915\";\n  color: #5e72e4;\n  color: var(--primary);\n}\n.main .main-left .nav li {\n  padding: 0.5rem 1rem;\n  cursor: pointer;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  padding: 0;\n}\n.main .main-left .nav li a {\n  display: block;\n  color: #5f6368;\n  color: var(--menu-color);\n}\n.main .main-left .nav li.slide {\n  padding: 0;\n}\n.main .main-left .nav li.slide ul {\n  display: none;\n  overflow: hidden;\n}\n.main .main-left .nav li.slide:hover {\n  background: none;\n}\n.main .main-left .nav li.slide .slide-menu {\n  margin: 0 0.5rem 0 2.5rem;\n  padding: 0 0.5rem;\n}\n.main .main-left .nav li.slide .slide-menu.active {\n  display: block;\n}\n.main .main-left .nav li.slide .slide-menu li {\n  position: relative;\n  border-radius: 0.25rem;\n  margin: 0;\n  background: none;\n  list-style: none;\n}\n.main .main-left .nav li.slide .slide-menu li a {\n  text-decoration: none;\n  padding: 0.5rem 0;\n}\n.main .main-left .nav li.slide .slide-menu li::after {\n  content: \"\";\n  position: absolute;\n  left: 0;\n  bottom: 0;\n  width: 0;\n  height: 2px;\n  background-color: #5e72e4;\n  background-color: var(--primary);\n  transition: all 0.2s;\n}\n.main .main-left .nav li.slide .slide-menu li:hover {\n  background: none;\n}\n.main .main-left .nav li.slide .slide-menu li:hover::after {\n  width: 100%;\n}\n.main .main-left .nav li.slide .slide-menu .active {\n  background: none;\n  color: var(--menu-color);\n}\n.main .main-left .nav li.slide .slide-menu .active a {\n  color: var(--menu-color);\n}\n.main .main-left .nav li.slide .slide-menu .active::after {\n  content: \"\";\n  position: absolute;\n  left: 0;\n  bottom: 0;\n  width: 100%;\n  height: 2px;\n  background-color: #5e72e4;\n  background-color: var(--primary);\n  transition: all 0.2s;\n}\n.main .main-left .nav li.slide .slide-menu .active:hover {\n  background: none;\n}\n.main .main-left .nav li.slide .slide-menu .active:hover::after {\n  width: 100%;\n}\n.main .main-left .nav li .menu {\n  display: block;\n  margin: 0.1rem 0.5rem 0.1rem 0.5rem;\n  padding: 0.675rem 0 0.675rem 2.5rem;\n  border-radius: 0.25rem;\n  text-decoration: none;\n  cursor: default;\n  font-size: 1rem;\n  transition: all 0.2s;\n  position: relative;\n}\n.main .main-left .nav li .menu.active {\n  color: #fff;\n  background: #5e72e4;\n  background: var(--primary);\n}\n.main .main-left .nav li .menu.active::before {\n  color: #fff !important;\n}\n.main .main-left .nav li .menu.active::after {\n  transform: rotate(90deg);\n  color: #fff !important;\n}\n.main .main-left .nav li .menu:hover {\n  cursor: pointer;\n  color: #fff;\n  background: #5e72e4;\n  background: var(--primary);\n}\n.main .main-left .nav li .menu:hover::before {\n  color: #fff !important;\n}\n.main .main-left .nav li .menu::before {\n  font-family: \"argon\" !important;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  position: absolute;\n  left: 0.8rem;\n  padding-top: 3px;\n  transition: all 0.3s;\n  content: \"\\e915\";\n  color: #5e72e4;\n  color: var(--primary);\n}\n.main .main-left .nav li .menu::after {\n  position: absolute;\n  right: 0.5rem;\n  top: 0.8rem;\n  font-family: \"argon\" !important;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n  -moz-osx-font-smoothing: grayscale;\n  content: \"\\e90f\";\n  transition: all 0.15s ease;\n  color: #ced4da;\n  text-rendering: auto;\n  -webkit-font-smoothing: antialiased;\n  transition: all 0.3s;\n}\n.main .main-left .nav li .menu[data-title=\"Status\"]:before {\n  content: \"\\e906\";\n  color: #5e72e4;\n  color: var(--primary);\n}\n.main .main-left .nav li .menu[data-title=\"System\"]:before {\n  content: \"\\e90a\";\n  color: #fb6340;\n}\n.main .main-left .nav li .menu[data-title=\"Services\"]:before {\n  content: \"\\e909\";\n  color: #11cdef;\n}\n.main .main-left .nav li .menu[data-title=\"NAS\"]:before {\n  content: \"\\e90c\";\n  color: #f3a4b5;\n}\n.main .main-left .nav li .menu[data-title=\"VPN\"]:before {\n  content: \"\\e90b\";\n  color: #8965e0;\n}\n.main .main-left .nav li .menu[data-title=\"Network\"]:before {\n  content: \"\\e908\";\n  color: #8965e0;\n}\n.main .main-left .nav li .menu[data-title=\"Bandwidth_Monitor\"]:before {\n  content: \"\\e90d\";\n  color: #2dce89;\n}\n.main .main-left .nav li .menu[data-title=\"Docker\"]:before {\n  content: \"\\e911\";\n  color: #6699ff;\n}\n.main .main-left .nav li .menu[data-title=\"Statistics\"]:before {\n  content: \"\\e913\";\n  color: #8965e0;\n}\n.main .main-left .nav li .menu[data-title=\"Control\"]:before {\n  content: \"\\e912\";\n  color: #5e72e4;\n  color: var(--primary);\n}\n.main .main-left .nav li .menu[data-title=\"Asterisk\"]:before {\n  content: \"\\e914\";\n  color: #fb6340;\n}\n.main .main-left .nav li a[data-title=\"Log_out\"]::before,\n.main .main-left .nav li .food[data-title=\"Log_out\"]::before {\n  content: \"\\e907\";\n  color: #adb5bd;\n}\n.lg {\n  margin: 0;\n  padding: 0 !important;\n}\n.logout {\n  display: block;\n  margin: 0.8rem 0.5rem 0.1rem 0.5rem;\n  padding: 0.675rem 0 0.675rem 2.5rem;\n  border-radius: 0.25rem;\n  text-decoration: none;\n  font-size: 1rem;\n  transition: all 0.2s;\n  position: relative;\n}\n.logout:before {\n  font-family: \"argon\" !important;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  position: absolute;\n  left: 0.8rem;\n  padding-top: 3px;\n  transition: all 0.3s;\n  content: \"\\e907\";\n  color: #32325d !important;\n}\nbody[class*=\"node-\"] > .main > .main-left > .nav > .slide > .menu::before {\n  transition: transform 0.1s ease-in-out;\n}\nbody[class*=\"node-\"]\n  > .main\n  > .main-left\n  > .nav\n  > .slide\n  > .menu.active::before {\n  transition: transform 0.2s ease-in-out;\n}\n.main > .main-left[style*=\"overflow: hidden\"] > .nav > .slide > .menu::before {\n  display: none;\n}\n.cbi-section,\n.cbi-section-error,\n#iptables,\n.Firewall form,\n#cbi-network > .cbi-section-node,\n#cbi-wireless > .cbi-section-node,\n#cbi-wireless > #wifi_assoclist_table,\n[data-tab-title],\n[data-page^=\"admin-system-admin\"]:not(.node-main-login)\n  .cbi-map:not(#cbi-dropbear),\n[data-page=\"admin-system-opkg\"] #maincontent > .container {\n  font-family: inherit;\n  font-weight: normal;\n  font-style: normal;\n  line-height: normal;\n  min-width: inherit;\n  margin: 1.25rem 0;\n  padding: 0;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: #fff;\n  box-shadow: 0 0 1rem 0 rgba(136, 152, 170, 0.15);\n}\n.cbi-section:last-child,\n.cbi-section-error:last-child,\n#iptables:last-child,\n.Firewall form:last-child,\n#cbi-network > .cbi-section-node:last-child,\n#cbi-wireless > .cbi-section-node:last-child,\n#cbi-wireless > #wifi_assoclist_table:last-child,\n[data-tab-title]:last-child,\n[data-page^=\"admin-system-admin\"]:not(.node-main-login)\n  .cbi-map:not(#cbi-dropbear):last-child,\n[data-page=\"admin-system-opkg\"] #maincontent > .container:last-child {\n  margin: 0;\n  border: 0;\n}\n.cbi-modal .cbi-section,\n.cbi-section .cbi-section {\n  padding: 0;\n  box-shadow: none;\n}\n.cbi-modal .cbi-tabmenu {\n  margin-left: 0;\n}\n.cbi-map:not(:first-child) {\n  margin-top: 1rem;\n}\n.cbi-map-descr {\n  font-size: small;\n  line-height: 1.5;\n  padding: 0 1.25rem 1rem 1.25rem;\n}\n.cbi-section > .cbi-section-descr {\n  padding-top: 1rem !important;\n  padding-bottom: 1rem !important;\n}\n.cbi-section > .cbi-section-descr:empty {\n  padding-top: 0 !important;\n  padding-bottom: 0rem !important;\n}\n.cbi-section-descr:not(:empty) {\n  font-size: small;\n  line-height: 1.5;\n  padding: 0rem 1rem;\n}\n.cbi-map-descr + fieldset {\n  margin-top: 1rem;\n}\n.cbi-map-descr > abbr {\n  cursor: help;\n  text-decoration: underline;\n}\n.cbi-section > legend {\n  display: none !important;\n}\nfieldset > fieldset,\n.cbi-section > .cbi-section {\n  margin: 0;\n  padding: 0;\n  border: 0;\n  box-shadow: none;\n}\n.cbi-section > h3:first-child,\n.panel-title {\n  font-size: 1.1rem;\n  line-height: 1;\n  display: block;\n  width: 100%;\n  margin: 0;\n  margin-bottom: 0;\n  padding: 0.8755rem 1.25rem;\n  color: #32325d;\n  color: var(--gray-dark);\n}\n.cbi-section > h3:first-child,\n.cbi-section > h4:first-child,\n.cbi-section > p:first-child,\n[data-tab-title] > h3:first-child,\n[data-tab-title] > h4:first-child,\n[data-tab-title] > p:first-child {\n  padding: 1rem 1.25rem;\n}\n.cbi-section p {\n  padding: 1rem;\n}\n.cbi-tblsection {\n  overflow-x: auto;\n}\ntable {\n  border-spacing: 0;\n  border-collapse: collapse;\n}\ntable,\n.table {\n  overflow-y: hidden;\n  width: 100%;\n  font-size: 90%;\n}\n.table .table-titles th {\n  background-color: #e9ecef;\n  background-color: var(--lighter);\n}\ntable > tbody > tr > td,\ntable > tbody > tr > th,\ntable > tfoot > tr > td,\ntable > tfoot > tr > th,\ntable > thead > tr > td,\ntable > thead > tr > th,\n.table > .tbody > .tr > .td,\n.table > .tbody > .tr > .th,\n.table > .tfoot > .tr > .td,\n.table > .tfoot > .tr > .th,\n.table > .thead > .tr > .td,\n.table > .thead > .tr > .th,\n.table > .tr > .td.cbi-value-field,\n.table > .tr > .th.cbi-section-table-cell {\n  padding: 0.5rem;\n}\n.container > .cbi-section:first-of-type > .table[width=\"100%\"] > .tr > .td {\n  padding: 0.6rem;\n}\n.cbi-section-table-cell {\n  line-height: 1.1;\n  align-self: flex-end;\n  flex: 1 1 auto;\n}\ntr > td,\ntr > th,\n.tr > .td,\n.tr > .th,\n.cbi-section-table-row::before,\n#cbi-wireless > #wifi_assoclist_table > .tr:nth-child(2) {\n  border-top: thin solid #ddd;\n  padding: 1.1em 1.25rem;\n}\n#cbi-wireless .td,\n.table[width=\"100%\"] > .tr:first-child > .td,\n[data-page=\"admin-network-diagnostics\"] .tr > .td,\n.tr.table-titles > .th,\n.tr.cbi-section-table-titles > .th {\n  border-top: 0 !important;\n  background-color: #f6f9fc;\n  padding: 1.1em 1.25rem;\n  line-height: 1.3rem;\n}\n[data-page=\"admin-network-network\"] .cbi-value-field .cbi-dynlist {\n  padding: 0 !important;\n}\n#cbi-network .tr:first-child > .td {\n  border-top: 0;\n}\n.table[width=\"100%\"] > .tr:first-child > .td {\n  margin: auto 0;\n}\n.cbi-section-table-row {\n  margin-bottom: 1rem;\n  text-align: center !important;\n  background: #f4f4f4;\n}\n.cbi-section-table-row:last-child {\n  margin-bottom: 0;\n}\n.cbi-section-table-row > .cbi-value-field .cbi-dropdown,\n.cbi-section-table-row > .cbi-value-field .cbi-input-select,\n.cbi-section-table-row > .cbi-value-field .cbi-input-text,\n.cbi-section-table-row > .cbi-value-field .cbi-input-password {\n  width: 100%;\n}\n.cbi-section-table-row > .cbi-value-field .cbi-input-text,\n.cbi-section-table-row > .cbi-value-field .cbi-input-password {\n  min-width: 80px;\n}\n.cbi-section-table-row > .cbi-value-field [data-dynlist] > input,\n.cbi-section-table-row > .cbi-value-field input.cbi-input-password {\n  width: calc(100% - 1.5rem);\n}\n.cbi-section-table-row .td {\n  text-align: center !important;\n}\n.cbi-section-table-row .td .cbi-checkbox input[type=\"checkbox\"] {\n  margin: 0;\n}\n.control-group {\n  display: inline-flex;\n  width: 100%;\n  flex-wrap: wrap;\n  gap: 0px;\n}\n.control-group input {\n  border-bottom-right-radius: 0;\n  border-top-right-radius: 0;\n  border-right-width: 0;\n  margin-right: 0;\n}\n.control-group input + button {\n  border-bottom-left-radius: 0;\n  border-top-left-radius: 0;\n  margin-left: 0;\n  border-left-width: 0;\n}\n.control-group > * {\n  vertical-align: middle;\n}\ndiv > table > tbody > tr:nth-of-type(2n),\ndiv > .table > .tr:nth-of-type(2n) {\n  background-color: #f9f9f9;\n}\ntable table,\n.table .table,\n.cbi-value-field table,\n.cbi-value-field .table,\ntd > table > tbody > tr > td,\n.td > .table > .tbody > .tr > .td,\n.cbi-value-field > table > tbody > tr > td,\n.cbi-value-field > .table > .tbody > .tr > .td {\n  border: 0;\n}\n.btn,\n.cbi-button,\n.item::after {\n  font-size: 0.875rem;\n  display: inline-block;\n  width: auto !important;\n  padding: 0.5rem 0.75rem;\n  margin-left: 5px;\n  margin-right: 5px;\n  cursor: pointer;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  transition: all 0.2s ease-in-out;\n  text-align: center;\n  vertical-align: middle;\n  white-space: nowrap;\n  text-decoration: none;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: #f0f0f0;\n  background-image: none;\n  appearance: none;\n  -ms-touch-action: manipulation;\n  touch-action: manipulation;\n}\n.btn:last-child,\n.cbi-button:last-child {\n  margin-right: 0 !important;\n}\n.btn:first-child,\n.cbi-button:first-child {\n  margin-left: 0 !important;\n}\n.btn:only-child,\n.cbi-button:only-child {\n  margin-left: 5px !important;\n  margin-right: 5px !important;\n}\n.btn:not(button) ul:not(.dropdown) li {\n  padding: 0;\n}\n.cbi-button-up,\n.cbi-button-down {\n  display: inline-block;\n  min-width: 0;\n  padding: 0.2rem 1rem;\n  font-size: 0;\n  color: transparent !important;\n  background: url(../icon/arrow.svg) no-repeat center;\n  background-size: 12px 20px;\n}\n.cbi-button-up {\n  transform: scaleY(-1);\n}\n.cbi-button:not(select) {\n  appearance: none !important;\n}\n.btn:hover,\n.btn:focus,\n.btn:active,\n.cbi-button:hover,\n.cbi-button:focus,\n.cbi-button:active,\n.item:hover::after,\n.item:focus::after,\n.item:active::after,\n.cbi-page-actions .cbi-button-apply + .cbi-button-save:hover,\n.cbi-page-actions .cbi-button-apply + .cbi-button-save:focus,\n.cbi-page-actions .cbi-button-apply + .cbi-button-save:active {\n  text-decoration: none;\n  outline: 0;\n}\n.btn:hover,\n.btn:focus,\n.cbi-button:hover,\n.cbi-button:focus,\n.item:hover::after,\n.item:focus::after {\n  box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 2px rgba(0, 0, 0, 0.2);\n}\n.btn:active,\n.cbi-button:active,\n.item:active::after {\n  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);\n}\n.cbi-button-up:hover,\n.cbi-button-up:focus {\n  box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 -2px 2px rgba(0, 0, 0, 0.2);\n}\n.cbi-button-up:active {\n  box-shadow: 0 -10px 20px rgba(0, 0, 0, 0.19), 0 -6px 6px rgba(0, 0, 0, 0.23);\n}\n.btn:disabled,\n.cbi-button:disabled {\n  cursor: not-allowed;\n  pointer-events: none;\n  opacity: 0.5;\n  box-shadow: none;\n}\n.alert-message [class=\"btn\"],\n.modal div[class=\"btn\"],\n.cbi-button-find,\n.cbi-button-link,\n.cbi-button-up,\n.cbi-button-down,\n.cbi-button-neutral,\n.cbi-button[name=\"zero\"],\n.cbi-button[name=\"restart\"],\n.cbi-button[onclick=\"hide_empty(this)\"] {\n  color: #fff;\n  border: thin solid #8898aa;\n  background-color: #8898aa;\n}\n.btn.primary,\n.cbi-page-actions .cbi-button-save,\n.cbi-page-actions .cbi-button-apply + .cbi-button-save,\n.cbi-button-add,\n.cbi-button-save,\n.cbi-button-positive,\n.cbi-button-link,\n.cbi-button[value=\"Enable\"],\n.cbi-button[value=\"Scan\"],\n.cbi-button[value^=\"Back\"],\n.cbi-button-neutral[onclick=\"handleConfig(event)\"] {\n  font-weight: normal;\n  color: #fff !important;\n  border: thin solid #5e72e4;\n  border: thin solid var(--primary);\n  background-color: #5e72e4;\n  background-color: var(--primary);\n}\n.cbi-page-actions .cbi-button-apply,\n.cbi-section-actions .cbi-button-edit,\n.cbi-button-edit,\n.cbi-button-apply,\n.cbi-button-reload,\n.cbi-button-action,\n.cbi-button[value=\"Submit\"],\n.cbi-button[value=\"Upload\"],\n.cbi-button[value$=\"Apply\"],\n.cbi-button[onclick=\"addKey(event)\"] {\n  font-weight: normal;\n  color: #fff !important;\n  border: thin solid #5e72e4;\n  border: thin solid var(--primary);\n  background-color: #5e72e4;\n  background-color: var(--primary);\n}\n.btn.danger,\n.cbi-section-remove > .cbi-button,\n.cbi-button-remove,\n.cbi-button-reset,\n.cbi-button-negative,\n.cbi-button[value=\"Stop\"],\n.cbi-button[value=\"Kill\"],\n.cbi-button[onclick=\"reboot(this)\"],\n.cbi-button-neutral[value=\"Restart\"] {\n  font-weight: normal;\n  color: #fff;\n  border: thin solid #f5365c;\n  border: thin solid var(--red);\n  background-color: #f5365c;\n  background-color: var(--red);\n}\n.btn[value=\"Dismiss\"],\n.cbi-button[value=\"Terminate\"],\n.cbi-button[value=\"Reset\"],\n.cbi-button[value=\"Disabled\"],\n.cbi-button[onclick^=\"iface_reconnect\"],\n.cbi-button[onclick=\"handleReset(event)\"],\n.cbi-button-neutral[value=\"Disable\"] {\n  font-weight: normal;\n  color: #fff;\n  border: thin solid #eea236;\n  background-color: #f0ad4e;\n}\n.cbi-button-success,\n.cbi-button-download {\n  font-weight: normal;\n  color: #fff;\n  border: thin solid #4cae4c;\n  background-color: #5cb85c;\n}\n.cbi-page-actions .cbi-button-link:first-child {\n  float: left;\n}\n.a-to-btn {\n  text-decoration: none;\n}\n.cbi-value-field .cbi-button-add {\n  font-weight: bold;\n  padding: 1px 6px;\n  display: inline-block;\n  align-items: center;\n}\n.tabs {\n  margin: 0 0 1rem 0;\n  padding: 0 1rem;\n  background-color: #ffffff;\n  border-radius: 0.25rem;\n  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);\n  white-space: nowrap;\n  overflow-x: auto;\n}\n.tabs::-webkit-scrollbar {\n  width: 1px;\n  height: 5px;\n}\n.tabs::-webkit-scrollbar-thumb {\n  background-color: #f6f9fc;\n}\n.tabs::-webkit-scrollbar-track {\n  background-color: #fff;\n}\n.tabs li[class~=\"active\"],\n.tabs li:hover {\n  cursor: pointer;\n  border-bottom: 0.18751rem solid #5e72e4;\n  border-bottom: 0.18751rem solid var(--primary);\n  color: #5e72e4;\n  color: var(--primary);\n  background-color: #e4e9ee;\n  margin-bottom: 0;\n  border-radius: 0;\n}\n.tabs li[class~=\"active\"] a,\n.tabs li:hover a {\n  color: #5e72e4;\n  color: var(--primary);\n}\n.tabs li {\n  font-size: 0.875rem;\n  display: inline-block;\n  padding: 0.875rem 0;\n  border-bottom: 0.18751rem solid rgba(0, 0, 0, 0);\n  margin: 0;\n  transition: all 0.2s;\n}\n.tabs li a {\n  text-decoration: none;\n  color: #404040;\n  padding: 0.5rem 0.8rem;\n}\n.tabs li:hover {\n  border-bottom: 0.18751rem solid #5e72e4;\n  border-bottom: 0.18751rem solid var(--primary);\n}\n.cbi-tabmenu {\n  color: white;\n  padding: 0.5rem 1rem 0 1rem;\n  white-space: nowrap;\n  overflow-x: auto;\n}\n.cbi-tabmenu::-webkit-scrollbar {\n  width: 1px;\n  height: 5px;\n}\n.cbi-tabmenu::-webkit-scrollbar-thumb {\n  background-color: #f6f9fc;\n}\n.cbi-tabmenu::-webkit-scrollbar-track {\n  background-color: #fff;\n}\n.cbi-tabmenu li {\n  background: #dce3e9;\n  display: inline-block;\n  font-size: 0.875rem;\n  border-top-left-radius: 0.25rem;\n  border-top-right-radius: 0.25rem;\n  padding: 0.5rem 0;\n  border-bottom: 0.18751rem solid rgba(0, 0, 0, 0);\n  margin: 0 0.2rem;\n}\n.cbi-tabmenu li a {\n  text-decoration: none;\n  color: #404040;\n  padding: 0.5rem 0.8rem;\n}\n.cbi-tabmenu li:hover {\n  cursor: pointer;\n  border-bottom: 0.18751rem solid #5e72e4;\n  border-bottom: 0.18751rem solid var(--primary);\n  color: #5e72e4;\n  color: var(--primary);\n  background-color: #e4e9ee;\n  margin-bottom: 0;\n}\n.cbi-tabmenu li:hover a {\n  color: #525f7f;\n}\n.cbi-tabmenu li[class~=\"cbi-tab\"] {\n  border-bottom: 0.18751rem solid #5e72e4;\n  border-bottom: 0.18751rem solid var(--primary);\n  color: #5e72e4;\n  color: var(--primary);\n  background-color: #e4e9ee;\n  margin-bottom: 0;\n}\n.cbi-tabmenu li[class~=\"cbi-tab\"] a {\n  color: #5e72e4;\n  color: var(--primary);\n}\n.cbi-tab-descr {\n  padding: 0.5rem 1.5rem;\n}\n.cbi-section-node {\n  padding: 0;\n}\n.cbi-section .cbi-section-remove:nth-of-type(2n),\n.container > .cbi-section .cbi-section-node:nth-of-type(2n) {\n  background-color: #f9f9f9;\n}\n[data-tab-title] {\n  overflow: hidden;\n  height: 0;\n  opacity: 0;\n  margin: 0;\n  padding: 0rem 0rem !important;\n}\n[data-tab-title] p {\n  margin-left: 1rem;\n  margin-bottom: 1rem;\n}\n[data-tab-active=\"true\"] {\n  overflow: visible;\n  height: auto;\n  opacity: 1;\n  transition: opacity 0.25s ease-in;\n  margin: inherit !important;\n}\n.cbi-section[id] .cbi-section-remove:nth-of-type(4n + 3),\n.cbi-section[id] .cbi-section-node:nth-of-type(4n + 4) {\n  background-color: #f9f9f9;\n}\n.cbi-section-node-tabbed {\n  margin-top: 0;\n  padding: 0;\n  border: 0 solid #d4d4d4;\n  border-radius: 0.25rem;\n}\n.cbi-tabcontainer > .cbi-value:nth-of-type(2n) {\n  background-color: #f9f9f9;\n}\n.cbi-value-field {\n  display: table-cell;\n}\n.cbi-value-description {\n  line-height: 1.25;\n  display: table-cell;\n}\n.cbi-value-description abbr {\n  color: #32325d;\n  color: var(--gray-dark);\n}\n.cbi-value-description {\n  font-size: small;\n  padding: 0.5rem;\n  opacity: 0.5;\n}\n.cbi-value-title {\n  display: table-cell;\n  float: left;\n  width: 23rem;\n  padding-right: 2rem;\n  text-align: right;\n  word-wrap: break-word;\n}\n.cbi-value {\n  display: inline-block;\n  width: 100%;\n  padding: 0.35rem 1rem 0.2rem 1rem;\n  line-height: 2.4rem;\n}\n.cbi-value:first-child {\n  padding-top: 1rem;\n}\n.cbi-value:last-child {\n  padding-bottom: 1rem;\n}\n.cbi-value ul {\n  line-height: 1.25;\n}\n.cbi-value-field .cbi-dropdown,\n.cbi-value-field .cbi-input-select,\n.cbi-value input[type=\"text\"],\n.cbi-value input[type=\"password\"],\n.cbi-value textarea {\n  min-width: 18rem;\n}\n.cbi-value input[type=\"password\"] {\n  border-bottom-right-radius: 0;\n  border-top-right-radius: 0;\n  font-size: 0.875rem;\n  margin: 0.25rem 0 0.25rem 0.1rem;\n}\n.cbi-value input[type=\"password\"] + .cbi-button-neutral {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem !important;\n  padding: 0.5rem 0;\n  margin: 0.25rem 0;\n  font-weight: normal;\n  font-size: 1.2rem;\n  line-height: 1.5rem;\n  color: #fff;\n  outline: 0;\n  background-color: #8898aa;\n  box-shadow: none;\n  border: 1px solid #8898aa;\n  border-radius: 0.25rem;\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n#cbi-firewall-zone .cbi-input-select,\n#cbi-network-switch_vlan .cbi-input-select {\n  min-width: 11rem;\n}\n#cbi-network-switch_vlan .cbi-input-text {\n  max-width: 3rem;\n}\n.cbi-input-invalid {\n  color: #f5365c !important;\n  border-color: #f5365c !important;\n}\n.cbi-section-error {\n  font-weight: bold;\n  line-height: 1.42857143;\n  margin: 18px;\n  padding: 6px;\n  border: thin solid #f5365c;\n  border-radius: 3px;\n  background-color: #fce6e6;\n}\n.cbi-section-error ul {\n  margin: 0 0 0 20px;\n}\n.cbi-section-error ul li {\n  font-weight: bold;\n  color: #f5365c;\n}\n.td[data-title]::before {\n  font-weight: bold;\n  display: none;\n  padding: 0.25rem 0;\n  content: attr(data-title) \":\\20\";\n  text-align: left;\n  white-space: nowrap;\n}\n.tr.placeholder .td[data-title]::before {\n  display: none;\n}\n.tr[data-title]::before,\n.tr.cbi-section-table-titles.named::before {\n  font-weight: bold;\n  display: table-cell;\n  align-self: center;\n  flex: 1 1 5%;\n  padding: 0.25rem;\n  content: attr(data-title) \"\\20\";\n  text-align: center;\n  vertical-align: middle;\n  white-space: normal;\n  word-wrap: break-word;\n}\n.cbi-rowstyle-1 {\n  background-color: #f9f9f9;\n}\n.cbi-rowstyle-2 {\n  background-color: #eee;\n}\n.cbi-rowstyle-2 .cbi-button-up,\n.cbi-rowstyle-2 .cbi-button-down,\nbody:not(.Interfaces) .cbi-rowstyle-2:first-child {\n  background-color: #fff !important;\n}\n.cbi-section-table .cbi-section-table-titles .cbi-section-table-cell {\n  width: auto !important;\n}\n.td.cbi-section-actions {\n  text-align: right !important;\n  vertical-align: middle;\n}\n.td.cbi-section-actions > * {\n  display: inline-flex;\n}\n.td.cbi-section-actions > * > *,\n.td.cbi-section-actions > * > form > * {\n  margin: 0 5px;\n  display: flex;\n  align-items: center;\n}\n.td.cbi-section-actions > * > form {\n  display: inline-flex;\n  margin: 0;\n}\n.cbi-checkbox {\n  margin: 0 0.25rem;\n}\n.cbi-dynlist {\n  line-height: 1.3;\n  flex-direction: column;\n  min-height: 30px;\n  cursor: text;\n}\n.cbi-dynlist > .item {\n  display: inline-flex;\n  flex-wrap: nowrap;\n  margin: 0.25rem 0;\n  position: relative;\n  max-width: 25rem;\n  pointer-events: none;\n  color: #8898aa;\n  outline: 0;\n}\n.cbi-dynlist > .item::after {\n  content: \"\\00D7\";\n  pointer-events: auto;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem !important;\n  margin: 0;\n  font-weight: normal;\n  font-size: 1.2rem;\n  line-height: 1.5rem;\n  color: #fff;\n  border: 1px solid #f5365c;\n  border-radius: 0 0.25rem 0.25rem 0;\n  outline: 0;\n  background-color: var(--red);\n  background-image: none;\n  box-shadow: none;\n  box-sizing: border-box;\n}\n.cbi-dynlist > .item > span {\n  display: block;\n  padding: 0.5rem 0.75rem;\n  min-width: 15.5rem;\n  width: 15.5rem;\n  transition: box-shadow 0.15s ease;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  word-break: break-word;\n  font-size: 0.875rem;\n  line-height: 24px;\n  color: #8898aa;\n  border: 1px solid #dee2e6;\n  border-radius: 0.25rem 0 0 0.25rem;\n  outline: 0;\n  background-image: none;\n  box-shadow: none;\n  box-sizing: border-box;\n}\n.cbi-dynlist > .add-item {\n  display: inline-flex;\n  align-items: center;\n  width: 100%;\n  min-width: 16rem;\n  margin: 0.25rem 0;\n  gap: 0;\n  flex-wrap: nowrap;\n}\n.cbi-dynlist > .add-item input {\n  display: block;\n  padding: 0.5rem 0.75rem;\n  box-sizing: border-box;\n  min-width: 15.5rem;\n  width: 15.5rem;\n  transition: box-shadow 0.15s ease;\n  white-space: nowrap;\n  word-break: break-word;\n  font-size: 0.875rem;\n  line-height: 1.5rem;\n  color: #8898aa;\n  border: 1px solid #dee2e6;\n  border-radius: 0.25rem 0 0 0.25rem;\n  border-right-width: 0;\n  outline: 0;\n  background-image: none;\n  box-shadow: none;\n}\n.cbi-dynlist > .add-item .cbi-button {\n  display: flex;\n  width: auto !important;\n  padding-left: 0.8rem;\n  padding-right: 0.8rem;\n  margin-left: 0;\n  align-items: center;\n  justify-content: center;\n  font-size: 0.875rem;\n  line-height: 1.5rem;\n  outline: 0;\n  background-image: none;\n  background-color: var(--gray);\n  box-shadow: none;\n  color: var(--white);\n  border-color: var(--gray);\n  border-radius: 0.25rem;\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.cbi-dynlist > .add-item .cbi-button-add {\n  width: 2.5rem !important;\n  padding: 0.5rem 0 !important;\n  font-weight: normal;\n  font-size: 1.2rem;\n  color: #fff;\n  background-color: var(--primary);\n  border: 1px solid var(--primary);\n}\n.cbi-dynlist > .add-item:not([ondrop]) > input {\n  overflow: hidden;\n  min-width: 15.5rem;\n  width: 15.5rem;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n.cbi-dynlist[name=\"sshkeys\"] > .item {\n  max-width: none;\n}\n.cbi-dynlist > .cbi-dynlist > .add-item[ondrop] > input {\n  min-width: 13rem;\n}\n.cbi-dynlist,\n.cbi-dropdown {\n  position: relative;\n  display: inline-flex;\n  min-height: 2.1875rem;\n}\n.cbi-dropdown[placeholder*=\"select\"] {\n  max-width: 25rem;\n  height: auto;\n  margin-top: -3px;\n}\n.cbi-dropdown > ul {\n  display: flex;\n  overflow-x: hidden;\n  overflow-y: auto;\n  width: 100%;\n  margin: 0 !important;\n  padding: 0;\n  list-style: none;\n  outline: 0;\n}\n.cbi-dropdown > ul.preview {\n  display: none;\n}\n.cbi-button-apply > ul.preview {\n  display: none;\n}\n.cbi-button-apply > ul.preview li {\n  color: #fff;\n}\n.cbi-button-apply > ul:first-child li {\n  color: #fff;\n}\n.cbi-dropdown > .open {\n  flex-basis: 15px;\n}\n.cbi-dropdown > .open,\n.cbi-dropdown > .more {\n  font-size: 1rem;\n  font-weight: 900;\n  line-height: 1em;\n  display: flex;\n  flex-direction: column;\n  flex-grow: 0;\n  flex-shrink: 0;\n  justify-content: center;\n  padding: 0 0.25em;\n  cursor: default;\n  text-align: center;\n  outline: 0;\n}\n.cbi-dropdown > .more,\n.cbi-dropdown > ul > li[placeholder] {\n  font-weight: bold;\n  display: none;\n  color: #777;\n  text-shadow: none;\n}\n.cbi-dropdown > ul > li {\n  display: none;\n  overflow: hidden;\n  align-items: center;\n  align-self: center;\n  flex-grow: 1;\n  flex-shrink: 1;\n  min-height: 20px;\n  padding: 0.125rem 0.25em;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n.cbi-dropdown > ul > li .hide-open {\n  display: initial;\n}\n.cbi-dropdown > ul > li .hide-close {\n  display: none;\n}\n.cbi-dropdown > ul > li[display]:not([display=\"0\"]) {\n  border-left: thin solid #ccc;\n}\n.cbi-dropdown[empty] > ul {\n  max-width: 1px;\n}\n.cbi-dropdown > ul > li > form {\n  display: none;\n  margin: 0;\n  padding: 0;\n  pointer-events: none;\n}\n.cbi-dropdown > ul > li img {\n  margin-right: 0.25em;\n  vertical-align: middle;\n}\n.cbi-dropdown > ul > li > form > input[type=\"checkbox\"] {\n  height: auto;\n  margin: 0;\n}\n.cbi-dropdown > ul > li input[type=\"text\"] {\n  height: 20px;\n}\n.cbi-dropdown[open] > ul.dropdown {\n  position: absolute;\n  z-index: 1100;\n  display: block;\n  width: auto;\n  min-width: 100%;\n  max-width: none;\n  max-height: 200px !important;\n  border: 0 solid #918e8c;\n  background: #ffffff;\n  box-shadow: 0 0 4px #918e8c;\n  border-bottom-left-radius: 0.25rem;\n  border-bottom-right-radius: 0.25rem;\n  color: var(--main-menu-color);\n  margin-left: 0 !important;\n  left: 0;\n}\n.cbi-dropdown[open] > ul.dropdown li {\n  color: #000;\n}\n.cbi-dropdown > ul > li[display],\n.cbi-dropdown[open] > ul.preview,\n.cbi-dropdown[open] > ul.dropdown > li,\n.cbi-dropdown[multiple] > ul > li > label,\n.cbi-dropdown[multiple][open] > ul.dropdown > li,\n.cbi-dropdown[multiple][more] > .more,\n.cbi-dropdown[multiple][empty] > .more {\n  display: flex;\n  align-items: center;\n  flex-grow: 1;\n}\n.cbi-dropdown[empty] > ul > li,\n.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder],\n.cbi-dropdown[multiple][open] > ul.dropdown > li > form {\n  display: block;\n}\n.cbi-dropdown[open] > ul.dropdown > li .hide-open {\n  display: none;\n}\n.cbi-dropdown[open] > ul.dropdown > li .hide-close {\n  display: initial;\n}\n.cbi-dropdown[open] > ul.dropdown > li {\n  border-bottom: thin solid #ccc;\n  padding: 0.5rem 0.8rem;\n}\n.cbi-dropdown[open] > ul.dropdown > li label {\n  margin-left: 0.5rem;\n}\n.cbi-dropdown[open] > ul.dropdown > li[selected] {\n  background: #e4e9ee;\n}\n.cbi-dropdown[open] > ul.dropdown > li.focus {\n  background: #e4e9ee;\n  outline: none;\n}\n.cbi-dropdown[open] > ul.dropdown > li:last-child {\n  margin-bottom: 0;\n  border-bottom: 0;\n}\n.cbi-dropdown[open] > ul.dropdown > li[unselectable] {\n  opacity: 0.7;\n}\n.cbi-dropdown[open]\n  > ul.dropdown\n  > li\n  > input.create-item-input:first-child:last-child {\n  width: 100%;\n}\n.cbi-dropdown[disabled] {\n  pointer-events: none;\n  opacity: 0.6;\n}\n.cbi-dropdown .zonebadge {\n  width: 100%;\n}\n.cbi-dropdown[open] .zonebadge {\n  width: auto;\n}\n.cbi-progressbar {\n  position: relative;\n  display: flex;\n  width: 100%;\n  font-size: 0.75rem;\n  background-color: #e9ecef;\n  border-radius: 0.5rem;\n  height: 1rem;\n  overflow: hidden;\n}\n.cbi-progressbar > div {\n  display: block;\n  position: absolute;\n  height: 100%;\n  background-color: var(--bar-bg);\n  border-radius: 0.5rem;\n  transition: width 0.3s;\n}\n.cbi-progressbar::after {\n  content: attr(title);\n  position: absolute;\n  font-size: 0.75rem;\n  color: var(--bs-heading-color);\n  width: 100%;\n  height: 100%;\n  text-align: center;\n  line-height: 1rem;\n  z-index: 2;\n}\n#modal_overlay {\n  position: fixed;\n  z-index: 900;\n  top: 0;\n  right: 10000px;\n  bottom: 0;\n  left: -10000px;\n  overflow-y: scroll;\n  transition: opacity 0.125s ease-in;\n  opacity: 0;\n  background: rgba(0, 0, 0, 0.7);\n  -webkit-overflow-scrolling: touch;\n}\n.modal {\n  display: flex;\n  align-items: center;\n  flex-wrap: wrap;\n  width: 90%;\n  min-width: 270px;\n  max-width: 600px;\n  min-height: 32px;\n  margin: 5em auto;\n  padding: 1rem;\n  border-radius: 0.25rem !important;\n  background: #fff;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);\n}\n.modal > * {\n  line-height: normal;\n  flex-basis: 100%;\n  margin-bottom: 0.5em;\n  max-width: 100%;\n}\n.modal > pre,\n.modal > textarea {\n  font-size: 1rem;\n  font-size-adjust: 0.35;\n  overflow: auto;\n  margin-bottom: 0.5em;\n  padding: 8.5px;\n  cursor: auto;\n  white-space: pre-wrap;\n  color: #eee;\n  outline: 0;\n  background-color: #101010;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);\n}\n.modal > h4 {\n  display: block;\n  flex-grow: 1;\n  max-width: none;\n  padding: 1rem;\n  margin: -1rem -1rem 0 -1rem;\n  font-size: 1rem;\n  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);\n  border-radius: 0.25rem 0 0 0.25rem;\n}\n.modal h5 {\n  margin-top: 1rem;\n  font-weight: 600;\n}\n.modal label > input[type=\"checkbox\"] {\n  top: 0;\n}\n.modal ul {\n  margin-left: 2.2em;\n}\n.modal ul:not(.cbi-tabmenu) {\n  margin-top: 1rem;\n}\n.modal ul li {\n  list-style-type: square;\n  color: #808080;\n}\n.modal p {\n  word-break: break-word;\n  margin-top: 1rem;\n}\n.modal .label {\n  font-size: 0.6rem;\n  font-weight: normal;\n  padding: 0.1rem 0.3rem;\n  padding-bottom: 0;\n  cursor: default;\n  border-radius: 0;\n}\n.modal .label.warning {\n  background-color: #f0ad4e !important;\n}\n.modal .btn {\n  padding: 0.45rem 0.8rem;\n}\n.modal.cbi-modal {\n  max-width: 90%;\n  max-height: none;\n}\nbody.modal-overlay-active {\n  overflow: hidden;\n  height: 100vh;\n}\nbody.modal-overlay-active #modal_overlay {\n  right: 0;\n  left: 0;\n  opacity: 1;\n}\n.spinning {\n  position: relative;\n  padding-left: 32px !important;\n}\n.spinning::before {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0.2em;\n  width: 32px;\n  content: \"\";\n  background: url(/luci-static/resources/icons/loading.gif) no-repeat center;\n  background-size: 16px;\n}\n#view {\n  border-radius: 0.25rem;\n  overflow: hidden;\n}\n#view > .spinning {\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translateX(-50%) translateY(-50%);\n  padding: 1rem;\n  border-radius: 0.5rem;\n  background: #ffffff;\n  box-shadow: 0 0 1rem 0 rgba(136, 152, 170, 0.15);\n}\n.hidden {\n  display: none;\n}\n.left,\n.left::before {\n  text-align: left !important;\n}\n.right,\n.right::before {\n  text-align: right !important;\n}\n.center,\n.center::before {\n  text-align: center !important;\n}\n.top {\n  align-self: flex-start !important;\n  vertical-align: top !important;\n}\n.bottom {\n  align-self: flex-end !important;\n  vertical-align: bottom !important;\n}\n.inline {\n  display: inline;\n}\n.cbi-page-actions {\n  padding: 1rem;\n  text-align: right;\n  justify-content: flex-end;\n}\n.cbi-page-actions > form[method=\"post\"] {\n  display: inline-block;\n}\n.th[data-type=\"button\"],\n.td[data-type=\"button\"],\n.th[data-type=\"fvalue\"],\n.td[data-type=\"fvalue\"] {\n  flex: 1 1 2em;\n  text-align: center;\n}\n.ifacebadge {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.2rem;\n  padding: 0.25rem 0.8rem;\n  background: #eee;\n  border-radius: 4px;\n}\ntd > .ifacebadge,\n.td > .ifacebadge {\n  font-size: 0.875rem;\n  background-color: #f0f0f0;\n}\n.ifacebadge > em,\n.ifacebadge > img {\n  display: inline-block;\n  margin: 0 0.75rem;\n}\n.ifacebadge > img + img {\n  margin: 0 0.2rem 0 0;\n}\n.network-status-table {\n  display: flex;\n  flex-wrap: wrap;\n}\n.network-status-table .ifacebox {\n  flex-grow: 1;\n  border-radius: 0.25rem;\n  overflow: hidden;\n  margin: 1rem;\n}\n.network-status-table .ifacebox-body {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  gap: 0.5em;\n}\n.network-status-table .ifacebox-body > span {\n  flex: 10 10 auto;\n}\n.network-status-table .ifacebox-body > div {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.5rem;\n  height: 100%;\n}\n.network-status-table .ifacebox-body .ifacebadge {\n  align-items: center;\n  flex: 1 1 auto;\n  min-width: 220px;\n  padding: 0.5em;\n  background-color: #fff;\n}\n.network-status-table .ifacebox-body .ifacebadge > span {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n.cbi-input-textarea {\n  font-family: inherit;\n  width: 100%;\n  font-size: 0.875rem;\n  min-height: 14rem;\n  padding: 0.8rem;\n  color: #8898aa;\n  border-radius: 0.25rem;\n  border: 1px solid #dee2e6;\n  min-width: 16rem;\n}\n#content_syslog {\n  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);\n}\n#syslog {\n  font-size: small;\n  font-family: \"argon\";\n  line-height: 1.25;\n  overflow-y: hidden;\n  width: 100%;\n  min-height: 15rem;\n  padding: 1rem;\n  resize: none;\n  color: #242424;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: #ffffff;\n}\n#syslog:focus {\n  outline: 0;\n}\n.uci-change-list {\n  font-family: inherit;\n  overflow: scroll;\n  width: 100%;\n  display: flex;\n  flex-direction: column;\n  flex-wrap: wrap;\n}\n.uci-change-list ins,\n.uci-change-legend-label ins {\n  display: block;\n  padding: 2px;\n  text-decoration: none;\n  border: thin solid #0f0;\n  background-color: #cfc;\n}\n.uci-change-list del,\n.uci-change-legend-label del {\n  font-style: normal;\n  display: block;\n  padding: 2px;\n  text-decoration: none;\n  border: thin solid #f00;\n  background-color: #fcc;\n}\n.uci-change-list var,\n.uci-change-legend-label var {\n  font-style: normal;\n  display: block;\n  padding: 2px;\n  text-decoration: none;\n  border: thin solid #ccc;\n  background-color: #eee;\n}\n.uci-change-list var ins,\n.uci-change-list var del {\n  font-style: normal;\n  padding: 0;\n  white-space: pre;\n  border: 0;\n}\n.uci-change-legend {\n  padding: 5px;\n}\n.uci-change-legend-label {\n  float: left;\n  width: 150px;\n}\n.uci-change-legend-label > ins,\n.uci-change-legend-label > del,\n.uci-change-legend-label > var {\n  display: block;\n  float: left;\n  width: 10px;\n  height: 10px;\n  margin-right: 4px;\n}\n.uci-change-legend-label var ins,\n.uci-change-legend-label var del {\n  line-height: 0.4;\n  border: 0;\n}\n.uci-change-list var,\n.uci-change-list del,\n.uci-change-list ins {\n  padding: 0.5rem;\n}\n.uci-dialog .cbi-section {\n  padding: 0.5rem;\n}\n.uci-dialog .cbi-section .uci-change-legend {\n  line-height: 15px;\n  padding: 10px 20px 0 20px;\n}\n.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label {\n  padding: 0;\n  margin: 0;\n  position: relative;\n  float: none;\n  display: inline-block;\n  width: 25%;\n}\n.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > ins,\n.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > del {\n  width: 14px;\n  height: 14px;\n}\n.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > var {\n  position: relative;\n  width: 14px;\n  height: 14px;\n}\n.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > var ins,\n.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > var del {\n  position: absolute;\n  left: 2px;\n  top: 2px;\n  right: 2px;\n  bottom: 2px;\n}\n.uci-dialog .cbi-section .uci-change-list {\n  overflow: auto;\n}\n.uci-dialog .cbi-section .uci-change-list + .right .btn {\n  color: #333;\n}\n.uci-dialog\n  .cbi-section\n  .uci-change-list\n  + .right\n  .cbi-dropdown\n  ul:not(.dropdown)\n  li {\n  color: #fff;\n}\n.uci-dialog .cbi-section .uci-change-list + .right .cbi-button {\n  padding: 0.45rem 0.8rem;\n}\n#iwsvg,\n#iwsvg2,\n#bwsvg {\n  border: thin solid #d4d4d4 !important;\n}\n#iwsvg,\n[data-page=\"admin-status-realtime-bandwidth\"] #bwsvg {\n  border-top: 0 !important;\n}\n.ifacebox {\n  line-height: 1.25;\n  display: inline-flex;\n  overflow: hidden;\n  flex-direction: column;\n  border-radius: 4px;\n  min-width: 100px;\n  background-color: #f9f9f9;\n}\n.ifacebox-head {\n  padding: 0.25em;\n  background: #eee;\n}\n.ifacebox-head.active {\n  background: #5e72e4;\n  background: var(--primary);\n}\n.ifacebox-head.active * {\n  color: #fff;\n  color: var(--white);\n}\n.ifacebox-body {\n  padding: 0.875rem 1rem;\n  line-height: 1.6em;\n}\n.cbi-image-button {\n  margin-left: 0.5rem;\n}\n.zonebadge {\n  display: inline-block;\n  padding: 0.2rem 0.5rem;\n  border-radius: 4px;\n}\n.zonebadge .ifacebadge {\n  margin: 0.1rem 0.2rem;\n  padding: 0.2rem 0.3rem;\n  border: thin solid #6c6c6c;\n}\n.zonebadge > input[type=\"text\"] {\n  min-width: 10rem;\n  margin-top: 0.3rem;\n  padding: 0.16rem 1rem;\n}\n.zonebadge > em,\n.zonebadge > strong {\n  display: inline-block;\n  margin: 0 0.2rem;\n}\n.cbi-value-field .cbi-input-checkbox,\n.cbi-value-field .cbi-input-radio {\n  margin-top: 0.1rem;\n}\n.cbi-value-field > ul > li {\n  display: flex;\n}\n.cbi-value-field > ul > li > label {\n  margin-top: 0.5rem;\n}\n.cbi-value-field > ul > li .ifacebadge {\n  margin-top: -0.5rem;\n  margin-left: 0.4rem;\n  background-color: #eee;\n}\n.cbi-section-table-row > .cbi-value-field .cbi-dropdown {\n  min-width: 3rem;\n}\n.cbi-section-create {\n  display: inline-flex;\n  align-items: center;\n  padding: 0.5rem 1rem;\n}\n.cbi-section-remove {\n  padding: 0.5rem 1rem;\n}\ndiv.cbi-value var,\ntd.cbi-value-field var,\n.td.cbi-value-field var {\n  font-style: italic;\n  color: #0069d6;\n}\n.cbi-optionals {\n  padding: 1rem 1rem 0 1rem;\n  border-top: thin solid #ccc;\n}\n.cbi-dropdown-container {\n  position: relative;\n}\n.cbi-tooltip-container,\nspan[data-tooltip],\nspan[data-tooltip] .label {\n  cursor: help !important;\n}\n.cbi-tooltip {\n  position: absolute;\n  z-index: 1000;\n  left: -1000px;\n  padding: 2px 5px;\n  transition: opacity 0.25s ease-out;\n  white-space: pre;\n  pointer-events: none;\n  opacity: 0;\n  border-radius: 3px;\n  background: #fff;\n  box-shadow: 0 0 2px #444;\n}\n.cbi-tooltip-container:hover .cbi-tooltip {\n  left: auto;\n  transition: opacity 0.25s ease-in;\n  opacity: 1;\n}\n.zonebadge .cbi-tooltip {\n  margin: -1.5rem 0 0 -0.5rem;\n  padding: 0.25rem;\n  background: inherit;\n}\n.zonebadge-empty {\n  color: #404040;\n  background: repeating-linear-gradient(\n    45deg,\n    rgba(204, 204, 204, 0.5),\n    rgba(204, 204, 204, 0.5) 5px,\n    rgba(255, 255, 255, 0.5) 5px,\n    rgba(255, 255, 255, 0.5) 10px\n  );\n}\n.zone-forwards {\n  display: flex;\n  min-width: 10rem;\n}\n.zone-forwards > * {\n  flex: 1 1 45%;\n}\n.zone-forwards > span {\n  flex-basis: 10%;\n  padding: 0 0.25rem;\n  text-align: center;\n}\n.zone-forwards .zone-src,\n.zone-forwards .zone-dest {\n  display: flex;\n  flex-direction: column;\n}\n.label {\n  font-size: 0.875rem;\n  font-weight: bold;\n  padding: 0.3rem 0.8rem;\n  white-space: nowrap;\n  text-decoration: none;\n  text-transform: uppercase;\n  color: #fff !important;\n  border-radius: 3px;\n  background-color: #bfbfbf;\n  text-shadow: none;\n}\nlabel > input[type=\"checkbox\"],\nlabel > input[type=\"radio\"] {\n  position: relative;\n  top: 0.4rem;\n  right: 0.2rem;\n  margin: 0;\n  vertical-align: bottom;\n}\nlabel[data-index][data-depends] {\n  padding-right: 2em;\n}\n.showSide {\n  display: none;\n}\n.darkMask {\n  position: fixed;\n  z-index: 99;\n  display: none;\n  width: 100%;\n  height: 100%;\n  content: \"\";\n  top: 0;\n  background-color: rgba(0, 0, 0, 0.56);\n  transition: all 0.2s;\n}\n.darkMask.active {\n  display: block;\n}\n#diag-rc-output > pre,\n#command-rc-output > pre,\n[data-page=\"admin-services-wol\"] .notice code {\n  font-size: 1.2rem;\n  font-size-adjust: 0.35;\n  line-height: normal;\n  display: block;\n  overflow-y: hidden;\n  width: 100%;\n  padding: 8.5px;\n  white-space: pre;\n  color: #eee;\n  background-color: #101010;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);\n}\n[data-page=\"admin-network-diagnostics\"] .table {\n  box-shadow: none;\n}\n[data-page=\"admin-network-diagnostics\"] .cbi-section {\n  padding: 1rem;\n  font-family: monospace;\n  background: #fff !important;\n}\n[data-page=\"admin-network-diagnostics\"] textarea {\n  background: transparent;\n  border-radius: 0.25rem;\n  font-family: \"argon\" !important;\n  color: #8898aa;\n  border: 1px solid #dee2e6;\n  padding: 0.5rem;\n}\n[data-page=\"admin-network-diagnostics\"] .tr > .td {\n  background-color: #fff !important;\n  border-bottom: 1px solid #dee2e6 !important;\n}\ninput[name=\"ping\"],\ninput[name=\"traceroute\"],\ninput[name=\"nslookup\"] {\n  width: 80%;\n}\n.node-status-overview > .main fieldset:nth-child(4) .td:nth-child(2),\n.node-status-processes > .main .table .tr .td:nth-child(3) {\n  white-space: normal;\n}\ndiv[style*=\"display:grid;grid-template-columns:repeat\"] {\n  display: flex !important;\n  justify-content: space-evenly !important;\n  padding-bottom: 1rem;\n  flex-wrap: wrap;\n  font-family: \"argon\";\n}\ndiv[style*=\"display:grid;grid-template-columns:repeat\"] .ifacebox {\n  text-align: center;\n  flex-basis: 100px;\n}\ndiv[style*=\"display:grid;grid-template-columns:repeat\"]\n  .ifacebox\n  .ifacebox-body {\n  font-size: 0.7rem;\n  padding: 0.875rem;\n}\ndiv[style*=\"display:grid;grid-template-columns:repeat\"]\n  .ifacebox\n  .ifacebox-body\n  .cbi-tooltip-container {\n  font-size: inherit !important;\n}\n@media screen and (max-width: 484px) {\n  div[style*=\"display:grid;grid-template-columns:repeat\"] .ifacebox {\n    flex-basis: 80px;\n  }\n  div[style*=\"display:grid;grid-template-columns:repeat\"]\n    .ifacebox\n    .ifacebox-body {\n    padding: 0.875rem 0.5rem;\n    font-size: 0.6rem;\n  }\n}\n[data-page=\"admin-system-attendedsysupgrade\"] #view .cbi-button {\n  margin-left: 0 !important;\n  margin-top: 1rem !important;\n}\n[data-page=\"admin-system-reboot\"] p {\n  padding-left: 1.5rem;\n}\n[data-page=\"admin-system-reboot\"] p > span {\n  position: relative;\n  top: 0.1rem;\n  left: 1rem;\n}\n[data-page=\"admin-system-reboot\"] .cbi-button {\n  background: #fb6340 !important;\n  border-color: #fb6340 !important;\n  margin-left: 0 !important;\n}\n[data-page=\"admin-system-reboot\"] #view > h2:first-child + p {\n  margin-bottom: 1rem;\n}\n[data-page=\"admin-system-poweroff\"] .container h2 + br + p {\n  margin-bottom: 1rem;\n  padding-left: 1.5rem;\n}\n[data-page=\"admin-vpn-passwall\"] h4 {\n  background: transparent;\n}\n[data-page=\"admin-system-filetransfer\"] #cbi-upload {\n  margin-top: 0;\n}\n[data-page=\"admin-system-filetransfer\"] .cbi-section-table {\n  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);\n}\n#cbi-samba [data-tab=\"template\"] .cbi-value-field {\n  display: block;\n}\n#cbi-samba [data-tab=\"template\"] .cbi-value-title {\n  width: auto;\n  padding-bottom: 0.6rem;\n}\n[data-page=\"admin-system-admin\"] .cbi-map h2,\n[data-page=\"admin-system-admin-password\"] .cbi-map h2,\n[data-page=\"admin-system-admin\"] .cbi-map .cbi-map-descr,\n[data-page=\"admin-system-admin-password\"] .cbi-map .cbi-map-descr {\n  margin-left: 0;\n  color: #32325d;\n  color: var(--gray-dark);\n}\n[data-page=\"admin-system-admin-sshkeys\"] .cbi-dynlist {\n  margin-left: 1rem;\n}\n[data-page=\"admin-system-opkg\"] h2 {\n  margin-left: 0;\n  color: #32325d;\n  color: var(--gray-dark);\n}\n.controls {\n  margin: 0.5em 1rem 1em 1rem !important;\n}\n.controls > * > .btn:not([aria-label$=\"page\"]) {\n  flex-grow: initial !important;\n  margin-top: 0.25rem;\n}\n.controls > #pager > .btn[aria-label$=\"page\"] {\n  font-size: 1.4rem;\n  font-weight: bold;\n}\n.controls > * > label {\n  margin-bottom: 0.2rem;\n}\n[data-page=\"admin-system-opkg\"] div.btn {\n  line-height: 3;\n  display: inline;\n  padding: 0.3rem 0.6rem;\n}\n[data-page^=\"admin-system-admin\"]:not(.node-main-login)\n  .cbi-map:not(#cbi-dropbear),\n[data-page=\"admin-system-opkg\"] #maincontent > .container {\n  margin-top: 1rem;\n  padding-top: 0.01rem;\n}\n[data-page=\"admin-system-opkg\"] #maincontent > .container {\n  margin: 0 1.25rem 1rem 1.25rem;\n  margin-bottom: 1rem;\n}\n.td.version,\n.td.size {\n  white-space: normal !important;\n  word-break: break-word;\n}\n.cbi-tabmenu + .cbi-section {\n  margin-top: 0;\n}\n[data-page=\"admin-system-system\"] .control-group {\n  margin-top: 0.5rem;\n}\n[data-page=\"admin-system-system\"] .cbi-dynlist {\n  margin: 0.25rem 0;\n}\n[data-page=\"admin-system-startup\"] [data-tab-title] p {\n  margin-left: 0;\n  margin-bottom: 0;\n  position: relative;\n}\n[data-page=\"admin-system-startup\"] textarea {\n  line-height: 1.25;\n  overflow-y: auto;\n  width: 100%;\n  min-height: 15rem;\n  padding: 1rem;\n  resize: none;\n  color: #8898aa;\n  border-radius: 0.25rem;\n  border: 1px solid #dee2e6;\n}\n[data-page=\"admin-system-startup\"] textarea:focus-visible {\n  outline: none;\n  box-shadow: none;\n  border: 1px solid var(--primary);\n}\n[data-page=\"admin-system-crontab\"] #view p {\n  margin-bottom: 1rem;\n}\n[data-page=\"admin-system-crontab\"] #view p:last-child {\n  margin-bottom: 0;\n}\n[data-page=\"admin-system-crontab\"] #view p textarea {\n  line-height: 1.25;\n  overflow-y: hidden;\n  width: 100%;\n  min-height: 15rem;\n  padding: 1rem;\n  resize: none;\n  background-color: transparent;\n  background: var(--white);\n  outline: none;\n  color: #8898aa;\n  border-radius: 0.25rem;\n  border: 1px solid #dee2e6;\n}\n[data-page=\"admin-system-attendedsysupgrade-configuration\"]\n  .cbi-map\n  .cbi-map-descr {\n  padding-bottom: 0;\n}\n[data-page=\"admin-system-flash\"] .cbi-value {\n  padding: 0;\n}\n[data-page=\"admin-system-flash\"] .cbi-section .cbi-section {\n  margin-top: 0;\n}\n[data-page=\"admin-system-flash\"] .cbi-map-tabbed {\n  border-radius: 0.25rem;\n}\n[data-page=\"admin-system-flash\"] .cbi-section-node {\n  padding-top: 0;\n  padding-bottom: 0.5rem;\n}\n[data-page=\"admin-system-flash\"] legend {\n  display: block !important;\n  font-size: 1.2rem;\n  width: 100%;\n  display: block;\n  margin-bottom: 0;\n  padding: 1rem 0 1rem 1.5rem;\n  border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n  line-height: 1.5;\n  margin-bottom: 0rem;\n  letter-spacing: 0.1rem;\n  color: #32325d;\n  font-weight: bold;\n}\n[data-page=\"admin-system-flash\"] .cbi-section-descr {\n  font-weight: 600;\n  padding: 1rem 0 1rem 1.5rem;\n  color: #525f7f;\n}\n[data-page=\"admin-system-flash\"] .cbi-page-actions {\n  padding: 0rem 1rem 1rem 0rem;\n}\n[data-page=\"admin-system-flash\"] .modal label > input[type=\"checkbox\"] {\n  top: -0.25rem;\n}\n[data-page=\"admin-system-flash\"] .modal .btn {\n  white-space: normal !important;\n}\n#cbi-wireless > #wifi_assoclist_table > .tr {\n  box-shadow: inset 1px -1px 0 #ddd, inset -1px -1px 0 #ddd;\n}\n#cbi-wireless > #wifi_assoclist_table > .tr.placeholder > .td {\n  right: 33px;\n  bottom: 33px;\n  left: 33px;\n  border-top: thin solid #ddd !important;\n}\n#cbi-wireless > #wifi_assoclist_table > .tr.table-titles {\n  box-shadow: inset 1px 0 0 #ddd, inset -1px 0 0 #ddd;\n}\n#cbi-wireless > #wifi_assoclist_table > .tr.table-titles > .th {\n  border-bottom: thin solid #ddd;\n  box-shadow: 0 -1px 0 0 #ddd;\n}\n#wifi_assoclist_table > .tr > .td[data-title=\"RX Rate / TX Rate\"] {\n  width: 23rem;\n}\n[data-page=\"admin-network-dhcp\"] .cbi-value {\n  padding: 0;\n}\n[data-page=\"admin-network-dhcp\"] [data-tab-active=\"true\"] {\n  padding: 1rem 0 !important;\n}\n#iptables {\n  margin: 0;\n}\n.Firewall form {\n  margin: 2rem 2rem 0 0;\n  padding: 0;\n  box-shadow: none;\n}\n#cbi-firewall-redirect table *,\n#cbi-network-switch_vlan table *,\n#cbi-firewall-zone table * {\n  font-size: small;\n}\n#cbi-firewall-redirect table input[type=\"text\"],\n#cbi-network-switch_vlan table input[type=\"text\"],\n#cbi-firewall-zone table input[type=\"text\"] {\n  width: 5rem;\n}\n#cbi-firewall-redirect table select,\n#cbi-network-switch_vlan table select,\n#cbi-firewall-zone table select {\n  min-width: 3.5rem;\n}\n#cbi-network-switch_vlan .th,\n#cbi-network-switch_vlan .td {\n  flex-basis: 12%;\n}\n#cbi-firewall-zone .table,\n#cbi-network-switch_vlan .table {\n  display: block;\n}\n#cbi-firewall-zone .td,\n#cbi-network-switch_vlan .td {\n  width: 100%;\n}\n[data-page=\"admin-network-firewall-custom\"] #view p,\n[data-page=\"admin-status-routes\"] #view p {\n  padding: 0 1.5rem;\n  margin-bottom: 1rem;\n}\n[data-page=\"admin-network-firewall-custom\"] #view p textarea,\n[data-page=\"admin-status-routes\"] #view p textarea {\n  padding: 1rem;\n  border-radius: 0.25rem;\n}\n[data-page=\"admin-network-firewall-custom\"] #view > h3,\n[data-page=\"admin-status-routes\"] #view > h3 {\n  border-radius: 0.25rem 0.25rem 0 0;\n}\n#applyreboot-container {\n  margin: 2rem;\n}\n#applyreboot-section {\n  line-height: 300%;\n  margin: 2rem;\n}\n.OpenVPN a {\n  line-height: initial !important;\n}\n.commandbox {\n  width: 24% !important;\n  margin: 10px 0 0 10px !important;\n  padding: 0.5rem 1rem;\n  border-bottom: thin solid #ccc;\n  background: #eee;\n  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),\n    0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.commandbox h3 {\n  line-height: normal !important;\n  overflow: hidden;\n  margin: 6px 0 !important;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n.commandbox div {\n  left: auto !important;\n}\n.commandbox code {\n  position: absolute;\n  overflow: hidden;\n  max-width: 60%;\n  margin-left: 4px;\n  padding: 2px 3px;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n.commandbox code:hover {\n  overflow-y: auto;\n  max-height: 50px;\n  white-space: normal;\n}\n.commandbox p:first-of-type {\n  margin-top: -6px;\n}\n.commandbox p:nth-of-type(2) {\n  margin-top: 2px;\n}\n[data-page^=\"admin-system-commands\"] .panel-title,\n[data-page^=\"command-cfg\"] .mobile-hide,\n[data-page^=\"command-cfg\"] .showSide {\n  display: none;\n}\n#command-rc-output .alert-message {\n  line-height: 1.42857143;\n  position: absolute;\n  top: 40px;\n  right: 32px;\n  max-width: 40%;\n  margin: 0;\n  animation: anim-fade-in 1.5s forwards;\n  word-break: break-word;\n  opacity: 0;\n}\n@keyframes anim-fade-in {\n  100% {\n    opacity: 1;\n  }\n}\ninput[type=\"checkbox\"] {\n  appearance: none !important;\n  -webkit-appearance: none !important;\n  border: 1px solid var(--primary);\n  width: 16px !important;\n  height: 16px !important;\n  padding: 0;\n  cursor: pointer;\n  transition: all 0.2s;\n  margin: 0.75rem 0 0 0;\n}\ninput[type=\"checkbox\"]:checked {\n  border: 1px solid #5e72e4;\n  border: 1px solid var(--primary);\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e\") !important;\n  background-color: #5e72e4;\n  background-color: var(--primary);\n  background-size: 70%;\n  background-repeat: no-repeat;\n  background-position: center;\n}\n.fb-container .cbi-button {\n  height: auto !important;\n}\n#cbi-usb_printer-printer em {\n  display: block;\n  padding: 1rem;\n  text-align: center;\n}\npre.command-output {\n  padding: 1.5rem;\n}\n[data-page=\"admin-nlbw-display\"] .cbi-section[data-tab=\"export\"] {\n  padding: 1.5rem !important;\n}\n[data-page=\"admin-nlbw-backup\"] form {\n  padding-left: 1.5rem;\n}\n[data-page=\"admin-status-iptables\"] .right {\n  margin-bottom: 0 !important;\n}\n[data-page=\"admin-services-ttyd\"] .container {\n  display: flex;\n  flex-direction: column;\n}\n[data-page=\"admin-services-ttyd\"] #view {\n  flex: 1;\n}\n[data-page=\"admin-services-ttyd\"] #view iframe {\n  height: 100%;\n}\n[data-page=\"admin-system-fileassistant\"] .fb-container .panel-title {\n  padding: 0.5rem 0.75rem !important;\n}\n[data-page=\"admin-system-fileassistant\"] .cbi-section.fb-container {\n  padding: 0.5rem;\n}\n[data-page=\"admin-system-fileassistant\"] .fb-container .panel-container {\n  border-bottom-color: #dee2e6;\n}\n[data-page^=\"admin-services-openclash\"] .cbi-tabmenu > li {\n  border-right: none !important;\n  margin: 0 0.4rem 0 0 !important;\n}\n[data-page^=\"admin-services-openclash\"] .cbi-tabmenu > li:last-child {\n  margin-right: 0 !important;\n}\n[data-page^=\"admin-services-openclash\"] #tab-content .dom {\n  padding: 0 1rem 1rem 1rem;\n}\n[data-page^=\"admin-services-openclash\"] .cbi-input-file {\n  padding: 0.2813rem;\n  box-sizing: content-box;\n  width: 15rem !important;\n}\n[data-page^=\"admin-services-openclash\"]\n  [id=\"container.openclash.config.debug\"]\n  fieldset {\n  border: none !important;\n  padding: 1rem !important;\n}\n[data-page^=\"admin-services-openclash\"] #diag-rc-output > pre,\n[data-page^=\"admin-services-openclash\"] #dns-rc-output > pre {\n  font-size: 0.875rem;\n  color: #8898aa;\n  border: 1px solid #dee2e6;\n  background-color: transparent;\n  border-radius: 0.25rem;\n  font-family: \"argon\" !important;\n  box-shadow: none;\n}\n[data-page^=\"admin-services-openclash\"] #debug-rc-output > textarea {\n  font-family: \"argon\" !important;\n}\n[data-page^=\"admin-services-openclash\"] .CodeMirror {\n  font-size: inherit;\n  font-family: \"argon\" !important;\n}\n[data-page^=\"admin-services-openclash\"] .cbi-button-up,\n[data-page^=\"admin-services-openclash\"] .cbi-button-down {\n  padding: 0.8rem 1.5rem;\n  background-color: #f1f1f1;\n  font-size: 0;\n}\n[data-page^=\"admin-services-openclash\"] select#CORE_VERSION,\n[data-page^=\"admin-services-openclash\"] select#RELEASE_BRANCH {\n  width: auto;\n}\n@media all and (-ms-high-contrast: none) {\n  .main > .main-left > .nav > .slide > .menu::before {\n    top: 30.25%;\n  }\n  .main > .main-left > .nav > li:last-child::before {\n    top: 20%;\n  }\n  .showSide::before {\n    top: -12px;\n  }\n}\n@media screen and (max-width: 1600px) {\n  header > .fill > .container > #logo {\n    margin: 0 2.5rem 0 0.5rem;\n  }\n  .main-left {\n    width: calc(0% + 13rem);\n  }\n  .btn:not(button),\n  .label {\n    padding: 0.5rem 0.75rem;\n  }\n  .cbi-value-title {\n    width: 15rem;\n    padding-right: 0.6rem;\n  }\n  .cbi-value-field .cbi-dropdown,\n  .cbi-value-field .cbi-input-select,\n  .cbi-value input[type=\"text\"],\n  .cbi-value input[type=\"password\"],\n  .cbi-value textarea {\n    min-width: 18rem;\n  }\n  #cbi-firewall-zone .cbi-input-select {\n    min-width: 9rem;\n  }\n  .cbi-input-textarea {\n    font-size: small;\n  }\n  .node-admin-status > .main fieldset li > a {\n    padding: 0.3rem 0.6rem;\n  }\n}\n@media screen and (max-width: 1366px) {\n  header > .fill > .container {\n    cursor: default;\n  }\n  .main-left {\n    width: calc(0% + 13rem);\n  }\n  .tabs > li > a,\n  .cbi-tabmenu > li > a {\n    padding: 0.2rem 0.8rem;\n  }\n  .panel-title {\n    font-size: 1.1rem;\n    padding-bottom: 1rem;\n  }\n  table {\n    font-size: 0.875rem !important;\n    width: 100% !important;\n  }\n  .table .cbi-input-text {\n    width: 100%;\n  }\n  .cbi-value-field .cbi-dropdown,\n  .cbi-value-field .cbi-input-select,\n  .cbi-value input[type=\"text\"],\n  .cbi-value input[type=\"password\"] {\n    min-width: 16rem;\n  }\n  #cbi-firewall-zone .cbi-input-select {\n    min-width: 4rem;\n  }\n  .main > .main-left > .nav > li,\n  .main > .main-left > .nav > li > a,\n  .main .main-left .nav > li > a:first-child,\n  .main > .main-left > .nav > .slide > .menu,\n  .main > .main-left > .nav > li > [data-title=\"Log_out\"] {\n    font-size: 0.9rem;\n  }\n  .main > .main-left > .nav > .slide > .slide-menu > li > a {\n    font-size: 0.875rem;\n  }\n  #modal_overlay {\n    top: 0rem;\n  }\n  [data-page=\"admin-network-firewall-forwards\"] .table:not(.cbi-section-table) {\n    display: block;\n  }\n  [data-page=\"admin-network-firewall-forwards\"] .table:not(.cbi-section-table),\n  [data-page=\"admin-network-firewall-rules\"] .table:not(.cbi-section-table),\n  [data-page=\"admin-network-hosts\"] .table,\n  [data-page=\"admin-network-routes\"] .table {\n    overflow-y: visible;\n  }\n  .commandbox {\n    width: 32% !important;\n  }\n  .btn:not(button),\n  .cbi-button {\n    font-size: 0.875rem;\n  }\n}\n@media screen and (max-width: 1152px) {\n  header > .fill > .container > #logo {\n    display: none;\n  }\n  header > .fill > .container > .brand {\n    position: relative;\n  }\n  html,\n  .main {\n    overflow-y: visible;\n  }\n  .main > .loading > span {\n    top: 25%;\n  }\n  .main-left {\n    width: calc(0% + 13rem);\n  }\n  body:not(.logged-in) .showSide {\n    visibility: hidden;\n    width: 0;\n    margin: 0;\n  }\n  .node-main-login > .main .cbi-value-title {\n    text-align: left;\n  }\n  .cbi-value-title {\n    width: 12rem;\n    padding-right: 1rem;\n  }\n  .cbi-value-field .cbi-dropdown,\n  .cbi-value-field .cbi-input-select,\n  .cbi-value input[type=\"text\"] {\n    width: 16rem;\n    min-width: 16rem;\n  }\n  .cbi-value input[name^=\"pw\"],\n  .cbi-value input[data-update=\"change\"]:nth-child(2) {\n    width: 13rem !important;\n    min-width: 13rem;\n  }\n  #diag-rc-output > pre,\n  #command-rc-output > pre,\n  [data-page=\"admin-services-wol\"] .notice code {\n    font-size: 1rem;\n  }\n  .table {\n    display: block;\n  }\n  .Interfaces .table {\n    overflow-x: hidden;\n  }\n  #packages.table {\n    display: grid;\n  }\n  .tr {\n    display: flex;\n    flex-direction: row;\n    flex-wrap: wrap;\n  }\n  .Overview .table[width=\"100%\"] > .tr {\n    flex-wrap: nowrap;\n  }\n  .tr.placeholder {\n    border-bottom: thin solid #ddd;\n  }\n  .tr.placeholder > .td,\n  #cbi-firewall .tr > .td,\n  #cbi-network .tr:nth-child(2) > .td,\n  .cbi-section #wifi_assoclist_table .tr > .td {\n    border-top: 0;\n  }\n  .th,\n  .td {\n    display: inline-block;\n    align-self: flex-start;\n    flex: 2 2 10%;\n    text-overflow: ellipsis;\n    word-wrap: break-word;\n  }\n  .td select,\n  .td input[type=\"text\"] {\n    width: 100%;\n    word-wrap: normal;\n  }\n  .td [data-dynlist] > input,\n  .td input.cbi-input-password {\n    width: calc(100% - 1.5rem);\n  }\n  .td[data-type=\"button\"],\n  .td[data-type=\"fvalue\"] {\n    flex: 1 1 12.5%;\n    text-align: left;\n  }\n  .th.cbi-value-field,\n  .td.cbi-value-field,\n  .th.cbi-section-table-cell,\n  .td.cbi-section-table-cell {\n    flex-basis: auto;\n    padding-top: 1rem;\n  }\n  .cbi-section-table-row {\n    display: flex;\n    flex-direction: row;\n    flex-wrap: wrap;\n    justify-content: space-between;\n    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);\n  }\n  .td.cbi-value-field,\n  .cbi-section-table-cell {\n    display: inline-block;\n    flex: 10 10 auto;\n    flex-basis: 50%;\n    text-align: center;\n  }\n  .td.cbi-section-actions {\n    vertical-align: bottom;\n  }\n  .tr.table-titles,\n  .tr.cbi-section-table-titles,\n  .tr.cbi-section-table-descr {\n    display: none;\n  }\n  .tr[data-title]::before,\n  .tr.cbi-section-table-titles.named::before {\n    font-size: 0.9rem;\n    display: block;\n    flex: 1 1 100%;\n    border-bottom: thin solid rgba(0, 0, 0, 0.26);\n    background: #e9ecef;\n  }\n  .td[data-title],\n  [data-page^=\"admin-status-realtime\"] .td[id] {\n    text-align: left;\n  }\n  .td[data-title]::before {\n    display: block;\n  }\n  .cbi-button + .cbi-button {\n    margin-left: 0;\n  }\n  .td.cbi-section-actions > * > *,\n  .td.cbi-section-actions > * > form > * {\n    margin: 2.1px 3px;\n  }\n  .Firewall form {\n    position: static !important;\n    margin: 0 0 2rem 0;\n    padding: 2rem;\n    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);\n  }\n  .Firewall form input {\n    width: 100% !important;\n    margin: 0;\n    margin-top: 1rem;\n  }\n  .Firewall .center,\n  .Firewall .center::before {\n    text-align: left !important;\n  }\n  .commandbox {\n    width: 100% !important;\n    margin-left: 0 !important;\n  }\n  .btn:not(button),\n  .cbi-button {\n    font-size: 0.875rem;\n  }\n}\n@media screen and (max-width: 768px) {\n  body {\n    font-size: 0.875rem;\n  }\n  .cbi-progressbar::after {\n    font-size: 0.6rem;\n  }\n  .main-left {\n    position: fixed;\n    z-index: 100;\n    width: 0;\n  }\n  .main-left.active {\n    width: 13rem;\n  }\n  .main-right {\n    width: 100%;\n  }\n  .main-right.active {\n    overflow-y: hidden;\n  }\n  .darkMask.active {\n    display: block;\n  }\n  .showSide {\n    padding: 0.1rem;\n    position: relative;\n    z-index: 99;\n    display: inline-block !important;\n  }\n  .showSide::before {\n    font-family: \"argon\" !important;\n    font-style: normal !important;\n    font-weight: normal !important;\n    font-variant: normal !important;\n    text-transform: none !important;\n    line-height: 1;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    content: \"\\e20e\";\n    font-size: 1.7rem;\n  }\n  header > .fill > .container > .flex1 > .brand {\n    display: inline-block;\n  }\n  .main > .main-left > .nav > .slide > .slide-menu > li > a {\n    font-size: 0.875rem;\n  }\n}\n@media screen and (max-width: 600px) {\n  .mobile-hide {\n    display: none;\n  }\n  #maincontent > .container {\n    margin: 0 1rem 1rem 1rem;\n  }\n  .cbi-value-title {\n    text-align: left;\n  }\n  [data-page=\"admin-system-flash\"] legend {\n    padding: 1rem 0 1rem 1rem;\n  }\n  [data-page=\"admin-system-flash\"] .cbi-section-descr {\n    padding: 1rem 0 1rem 1rem;\n  }\n  [data-page=\"admin-system-flash\"] .cbi-value {\n    padding: 0 1rem;\n  }\n  [data-page=\"admin-network-dhcp\"] [data-tab-active=\"true\"] {\n    padding: 1rem 1rem !important;\n  }\n  .cbi-dynlist p {\n    padding: 0.5rem 1rem;\n  }\n  body {\n    overflow-x: hidden;\n  }\n  .node-main-login\n    .main\n    .main-right\n    #maincontent\n    .container\n    .cbi-map\n    .cbi-section\n    .cbi-section-node\n    .cbi-value\n    .cbi-value-field {\n    width: 16rem;\n  }\n  .node-main-login footer {\n    display: none;\n  }\n  .tabs::-webkit-scrollbar,\n  .cbi-tabmenu::-webkit-scrollbar {\n    width: 0px;\n    height: 0px;\n  }\n  .cbi-value-field,\n  .cbi-value-description {\n    display: block !important;\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n  }\n  [data-page=\"admin-system-admin-password\"] .cbi-value-field {\n    display: table-cell !important;\n  }\n  .modal.cbi-modal {\n    max-width: 100%;\n    max-height: none;\n  }\n  .modal {\n    display: flex;\n    align-items: center;\n    flex-wrap: wrap;\n    width: 100%;\n    min-width: 270px;\n    max-width: 600px;\n    min-height: 32px;\n    margin: 5em auto;\n    padding: 1em;\n    border-radius: 3px !important;\n    background: #fff;\n    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);\n  }\n  .cbi-dropdown[open] > ul.dropdown {\n    margin-bottom: 1rem;\n  }\n  .login-page .login-container footer {\n    display: none;\n  }\n}\n@media screen and (min-width: 600px) {\n  ::-webkit-scrollbar {\n    width: 10px;\n    height: 10px;\n  }\n  ::-webkit-scrollbar,\n  ::-webkit-scrollbar-corner {\n    background: transparent;\n  }\n  ::-webkit-scrollbar-thumb {\n    background: var(--primary);\n    border-radius: 10px;\n  }\n  ::-webkit-scrollbar-thumb:hover {\n    background: var(--primary);\n  }\n  ::-webkit-scrollbar-thumb:active {\n    background: var(--primary);\n  }\n}\n@media screen and (max-width: 480px) {\n  .mobile-hide {\n    display: none;\n  }\n  .login-page .login-container {\n    margin-left: 0rem !important;\n    width: 100%;\n  }\n  .login-page .login-container .login-form .form-login .input-group::before {\n    color: #525461;\n  }\n  .login-page .login-container .login-form .form-login .input-group input {\n    color: #525461;\n    border-bottom: white 1px solid;\n    border-bottom: var(--white) 1px solid;\n    border-radius: 0;\n  }\n}\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/htdocs/luci-static/routerich/css/dark.css",
    "content": "/**/"
  },
  {
    "path": "luci/themes/luci-theme-routerich/htdocs/luci-static/routerich/css/routerich.css",
    "content": "/*\n! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com\n*/\n\n/*\n1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)\n2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)\n*/\n\n*,\n::before,\n::after {\n  box-sizing: border-box;\n  /* 1 */\n  border-width: 0;\n  /* 2 */\n  border-style: solid;\n  /* 2 */\n  border-color: #e5e7eb;\n  /* 2 */\n}\n\n::before,\n::after {\n  --tw-content: '';\n}\n\n/*\n1. Use a consistent sensible line-height in all browsers.\n2. Prevent adjustments of font size after orientation changes in iOS.\n3. Use a more readable tab size.\n4. Use the user's configured `sans` font-family by default.\n5. Use the user's configured `sans` font-feature-settings by default.\n6. Use the user's configured `sans` font-variation-settings by default.\n7. Disable tap highlights on iOS\n*/\n\nhtml,\n:host {\n  line-height: 1.5;\n  /* 1 */\n  -webkit-text-size-adjust: 100%;\n  /* 2 */\n  -moz-tab-size: 4;\n  /* 3 */\n  tab-size: 4;\n  /* 3 */\n  font-family: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  /* 4 */\n  font-feature-settings: normal;\n  /* 5 */\n  font-variation-settings: normal;\n  /* 6 */\n  -webkit-tap-highlight-color: transparent;\n  /* 7 */\n}\n\n/*\n1. Remove the margin in all browsers.\n2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.\n*/\n\nbody {\n  margin: 0;\n  /* 1 */\n  line-height: inherit;\n  /* 2 */\n}\n\n/*\n1. Add the correct height in Firefox.\n2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)\n3. Ensure horizontal rules are visible by default.\n*/\n\nhr {\n  height: 0;\n  /* 1 */\n  color: inherit;\n  /* 2 */\n  border-top-width: 1px;\n  /* 3 */\n}\n\n/*\nAdd the correct text decoration in Chrome, Edge, and Safari.\n*/\n\nabbr:where([title]) {\n  text-decoration: underline dotted;\n}\n\n/*\nRemove the default font size and weight for headings.\n*/\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-size: inherit;\n  font-weight: inherit;\n}\n\n/*\nReset links to optimize for opt-in styling instead of opt-out.\n*/\n\na {\n  color: inherit;\n  text-decoration: inherit;\n}\n\n/*\nAdd the correct font weight in Edge and Safari.\n*/\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\n/*\n1. Use the user's configured `mono` font-family by default.\n2. Use the user's configured `mono` font-feature-settings by default.\n3. Use the user's configured `mono` font-variation-settings by default.\n4. Correct the odd `em` font sizing in all browsers.\n*/\n\ncode,\nkbd,\nsamp,\npre {\n  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n  /* 1 */\n  font-feature-settings: normal;\n  /* 2 */\n  font-variation-settings: normal;\n  /* 3 */\n  font-size: 1em;\n  /* 4 */\n}\n\n/*\nAdd the correct font size in all browsers.\n*/\n\nsmall {\n  font-size: 80%;\n}\n\n/*\nPrevent `sub` and `sup` elements from affecting the line height in all browsers.\n*/\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\n/*\n1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)\n2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)\n3. Remove gaps between table borders by default.\n*/\n\ntable {\n  text-indent: 0;\n  /* 1 */\n  border-color: inherit;\n  /* 2 */\n  border-collapse: collapse;\n  /* 3 */\n}\n\n/*\n1. Change the font styles in all browsers.\n2. Remove the margin in Firefox and Safari.\n3. Remove default padding in all browsers.\n*/\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-family: inherit;\n  /* 1 */\n  font-feature-settings: inherit;\n  /* 1 */\n  font-variation-settings: inherit;\n  /* 1 */\n  font-size: 100%;\n  /* 1 */\n  font-weight: inherit;\n  /* 1 */\n  line-height: inherit;\n  /* 1 */\n  color: inherit;\n  /* 1 */\n  margin: 0;\n  /* 2 */\n  padding: 0;\n  /* 3 */\n}\n\n/*\nRemove the inheritance of text transform in Edge and Firefox.\n*/\n\nbutton,\nselect {\n  text-transform: none;\n}\n\n/*\n1. Correct the inability to style clickable types in iOS and Safari.\n2. Remove default button styles.\n*/\n\nbutton,\n[type='button'],\n[type='reset'],\n[type='submit'] {\n  -webkit-appearance: button;\n  /* 1 */\n  background-color: transparent;\n  /* 2 */\n  background-image: none;\n  /* 2 */\n}\n\n/*\nUse the modern Firefox focus style for all focusable elements.\n*/\n\n:-moz-focusring {\n  outline: auto;\n}\n\n/*\nRemove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)\n*/\n\n:-moz-ui-invalid {\n  box-shadow: none;\n}\n\n/*\nAdd the correct vertical alignment in Chrome and Firefox.\n*/\n\nprogress {\n  vertical-align: baseline;\n}\n\n/*\nCorrect the cursor style of increment and decrement buttons in Safari.\n*/\n\n::-webkit-inner-spin-button,\n::-webkit-outer-spin-button {\n  height: auto;\n}\n\n/*\n1. Correct the odd appearance in Chrome and Safari.\n2. Correct the outline style in Safari.\n*/\n\n[type='search'] {\n  -webkit-appearance: textfield;\n  /* 1 */\n  outline-offset: -2px;\n  /* 2 */\n}\n\n/*\nRemove the inner padding in Chrome and Safari on macOS.\n*/\n\n::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n/*\n1. Correct the inability to style clickable types in iOS and Safari.\n2. Change font properties to `inherit` in Safari.\n*/\n\n::-webkit-file-upload-button {\n  -webkit-appearance: button;\n  /* 1 */\n  font: inherit;\n  /* 2 */\n}\n\n/*\nAdd the correct display in Chrome and Safari.\n*/\n\nsummary {\n  display: list-item;\n}\n\n/*\nRemoves the default spacing and border for appropriate elements.\n*/\n\nblockquote,\ndl,\ndd,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nhr,\nfigure,\np,\npre {\n  margin: 0;\n}\n\nfieldset {\n  margin: 0;\n  padding: 0;\n}\n\nlegend {\n  padding: 0;\n}\n\nol,\nul,\nmenu {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n\n/*\nReset default styling for dialogs.\n*/\n\ndialog {\n  padding: 0;\n}\n\n/*\nPrevent resizing textareas horizontally by default.\n*/\n\ntextarea {\n  resize: vertical;\n}\n\n/*\n1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)\n2. Set the default placeholder color to the user's configured gray 400 color.\n*/\n\ninput::placeholder,\ntextarea::placeholder {\n  opacity: 1;\n  /* 1 */\n  color: #9ca3af;\n  /* 2 */\n}\n\n/*\nSet the default cursor for buttons.\n*/\n\nbutton,\n[role=\"button\"] {\n  cursor: pointer;\n}\n\n/*\nMake sure disabled buttons don't get the pointer cursor.\n*/\n\n:disabled {\n  cursor: default;\n}\n\n/*\n1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)\n2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)\n   This can trigger a poorly considered lint error in some tools but is included by design.\n*/\n\nimg,\nsvg,\nvideo,\ncanvas,\naudio,\niframe,\nembed,\nobject {\n  display: block;\n  /* 1 */\n  vertical-align: middle;\n  /* 2 */\n}\n\n/*\nConstrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)\n*/\n\nimg,\nvideo {\n  max-width: 100%;\n  height: auto;\n}\n\n/* Make elements with the HTML hidden attribute stay hidden by default */\n\n[hidden] {\n  display: none;\n}\n\n*, ::before, ::after{\n  --tw-border-spacing-x: 0;\n  --tw-border-spacing-y: 0;\n  --tw-translate-x: 0;\n  --tw-translate-y: 0;\n  --tw-rotate: 0;\n  --tw-skew-x: 0;\n  --tw-skew-y: 0;\n  --tw-scale-x: 1;\n  --tw-scale-y: 1;\n  --tw-pan-x:  ;\n  --tw-pan-y:  ;\n  --tw-pinch-zoom:  ;\n  --tw-scroll-snap-strictness: proximity;\n  --tw-gradient-from-position:  ;\n  --tw-gradient-via-position:  ;\n  --tw-gradient-to-position:  ;\n  --tw-ordinal:  ;\n  --tw-slashed-zero:  ;\n  --tw-numeric-figure:  ;\n  --tw-numeric-spacing:  ;\n  --tw-numeric-fraction:  ;\n  --tw-ring-inset:  ;\n  --tw-ring-offset-width: 0px;\n  --tw-ring-offset-color: #fff;\n  --tw-ring-color: rgb(59 130 246 / 0.5);\n  --tw-ring-offset-shadow: 0 0 #0000;\n  --tw-ring-shadow: 0 0 #0000;\n  --tw-shadow: 0 0 #0000;\n  --tw-shadow-colored: 0 0 #0000;\n  --tw-blur:  ;\n  --tw-brightness:  ;\n  --tw-contrast:  ;\n  --tw-grayscale:  ;\n  --tw-hue-rotate:  ;\n  --tw-invert:  ;\n  --tw-saturate:  ;\n  --tw-sepia:  ;\n  --tw-drop-shadow:  ;\n  --tw-backdrop-blur:  ;\n  --tw-backdrop-brightness:  ;\n  --tw-backdrop-contrast:  ;\n  --tw-backdrop-grayscale:  ;\n  --tw-backdrop-hue-rotate:  ;\n  --tw-backdrop-invert:  ;\n  --tw-backdrop-opacity:  ;\n  --tw-backdrop-saturate:  ;\n  --tw-backdrop-sepia:  ;\n}\n\n::backdrop{\n  --tw-border-spacing-x: 0;\n  --tw-border-spacing-y: 0;\n  --tw-translate-x: 0;\n  --tw-translate-y: 0;\n  --tw-rotate: 0;\n  --tw-skew-x: 0;\n  --tw-skew-y: 0;\n  --tw-scale-x: 1;\n  --tw-scale-y: 1;\n  --tw-pan-x:  ;\n  --tw-pan-y:  ;\n  --tw-pinch-zoom:  ;\n  --tw-scroll-snap-strictness: proximity;\n  --tw-gradient-from-position:  ;\n  --tw-gradient-via-position:  ;\n  --tw-gradient-to-position:  ;\n  --tw-ordinal:  ;\n  --tw-slashed-zero:  ;\n  --tw-numeric-figure:  ;\n  --tw-numeric-spacing:  ;\n  --tw-numeric-fraction:  ;\n  --tw-ring-inset:  ;\n  --tw-ring-offset-width: 0px;\n  --tw-ring-offset-color: #fff;\n  --tw-ring-color: rgb(59 130 246 / 0.5);\n  --tw-ring-offset-shadow: 0 0 #0000;\n  --tw-ring-shadow: 0 0 #0000;\n  --tw-shadow: 0 0 #0000;\n  --tw-shadow-colored: 0 0 #0000;\n  --tw-blur:  ;\n  --tw-brightness:  ;\n  --tw-contrast:  ;\n  --tw-grayscale:  ;\n  --tw-hue-rotate:  ;\n  --tw-invert:  ;\n  --tw-saturate:  ;\n  --tw-sepia:  ;\n  --tw-drop-shadow:  ;\n  --tw-backdrop-blur:  ;\n  --tw-backdrop-brightness:  ;\n  --tw-backdrop-contrast:  ;\n  --tw-backdrop-grayscale:  ;\n  --tw-backdrop-hue-rotate:  ;\n  --tw-backdrop-invert:  ;\n  --tw-backdrop-opacity:  ;\n  --tw-backdrop-saturate:  ;\n  --tw-backdrop-sepia:  ;\n}\n\nbody{\n  --tw-bg-opacity: 1;\n  background-color: rgb(226 232 240 / var(--tw-bg-opacity));\n  font-family: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  font-size: 1rem;\n  line-height: 1.5rem;\n  letter-spacing: 0em;\n}\n\nbody.logged-in{\n  --tw-bg-opacity: 1;\n  background-color: rgb(255 255 255 / var(--tw-bg-opacity));\n}\n\n:root {\n  --logo: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' style='enable-background:new 0 0 380 380' viewBox='0 0 380 380'%3E%3Cpath d='M324.6 186.6c-6.9 0-12.5 5.6-12.5 12.5s5.6 12.5 12.5 12.5 12.5-5.6 12.5-12.5-5.6-12.5-12.5-12.5z'/%3E%3Cpath d='M292.6 241h48.3c8.8 0 15.9-7.1 15.9-15.9v-51.9c0-8.8-7.1-15.9-15.9-15.9h-43.3c2.4 10.7 3.7 21.8 3.7 33.2 0 17.7-3 34.7-8.7 50.5z' style='fill:none'/%3E%3Cpath d='M340.9 135.3h-5.3V51.5c0-6.1-4.9-11-11-11s-11 4.9-11 11v83.8h-22.8c2.8 7.1 5.1 14.4 6.8 22h43.3c8.8 0 15.9 7.1 15.9 15.9V225c0 8.8-7.1 15.9-15.9 15.9h-48.3c-2.7 7.6-6 15-9.9 22h58.2c20.9 0 37.9-17 37.9-37.9v-51.8c0-20.9-17-37.9-37.9-37.9z'/%3E%3Ccircle cx='152.2' cy='190.5' r='150' style='fill:%23ffd400'/%3E%3Cpath d='M101.9 169.6s1.4-2.8 4.8-5.9c1.7-1.5 4-3.1 6.8-4.3 2.8-1.1 6.3-1.8 9.9-1.1l1.3.3.5.1.2.1.5.2 2 .8c1.3.5 2.6 1.1 3.8 1.7 2.4 1.2 4.7 2.3 6.6 3.6 3.8 2.4 6 4.5 6 4.5s-3.1 0-7.3-.8c-2.1-.4-4.6-.9-7.2-1.6-1.3-.4-2.7-.7-4-1.1l-2-.7-.5-.2-.2-.1c.2.1-.1 0-.1 0l-.8-.1c-1-.2-2.2-.2-3.3-.2-1.2.1-2.3.2-3.5.4-2.3.5-4.6 1.2-6.6 1.9-4.2 1.5-6.9 2.5-6.9 2.5zM197 169.7s-2.7-1-6.8-2.5c-1-.3-2-.6-3.2-1-1.1-.3-2.3-.6-3.4-.9-1.2-.2-2.3-.4-3.5-.4-1.1 0-2.3 0-3.3.2l-.8.1s-.3.1-.1 0l-.2.1-.5.2-2 .7c-1.4.4-2.7.8-4 1.2-2.6.7-5.1 1.2-7.2 1.6-4.2.8-7.3.8-7.3.8s2.2-2.1 6-4.5c1.9-1.3 4.1-2.4 6.6-3.6 1.2-.6 2.5-1.2 3.8-1.7l2-.8.5-.2.2-.1.5-.1 1.3-.3c3.6-.7 7.1 0 9.9 1.2 2.8 1.2 5.1 2.7 6.8 4.3 3.4 2.9 4.7 5.7 4.7 5.7z'/%3E%3Cpath d='M229.2 220.5c-2.4-13.9-18.8-29-18.6-29.7.1-.2.2-.5.4-.7.8.3 2.6 1.5 3.4 1.5 4.8-.3 7.1-4.7 7.7-6.7 1-3.2 4-19.9 3.7-23.4-.5-7.3-5.1-11.2-9.9-13.2.4-10-.1-21.9-3.1-33.1-1.6-6.2-4.2-12.1-7.4-17.4-8.6-17.5-25.9-32.1-54.4-32.1-15.8 0-28.2 4.5-37.7 11.5-12.2 5.4-22.7 20.7-26.5 38.8-2.4 11.2-2.6 23.1-1.9 32.9-1.8.9-3.5 1.9-4.9 3.4-2.5 2.6-3.8 5.7-3.7 9.4.1 2.8 2.2 19.8 4.2 23.6 1.8 3.4 3.5 7.1 7.2 7.1.8 0 2.8-1.8 3.6-2.1.1.1.1.2.2.4.1.6-15 12.8-17.2 26.9-3.1 19.1-5.7 38.2 3.2 61.3 4.7 12.1 44.7 36.3 73 36.3s66.8-22.8 72.5-34.4c10.1-20.1 9.5-41.6 6.2-60.3zm-13.7-66.4c2.4 1.4 4.6 3.8 4.9 7.8.2 2.4-2.5 18.1-3.5 21.4-.4 1.2-2.2 1.5-3.7 1.5.9-6.1 1.6-23.4 2.3-30.7zM89.1 185.2c-1.7 0-2.9-.8-3.8-2.4-1.2-2.4-3.5-18-3.6-21.1 0-2.2.7-4 2.2-5.5.5-.5 1-.9 1.6-1.3.8 6.6 2.7 23.3 3.9 30.3h-.3zm50.1 37.5c1.5-.2 3-.6 4.2-1.2 8.6-3.8 9.4-4 17.4-.1 1.2.6 2.7 1 4.3 1.3-7.3-.1-18.3-.1-25.9 0zm64.5-27.8c-2.1 2.5-8.1 9.4-13.5 20-1-.4-2.3-.7-3.6-.9-12.2-1.5-11.9-4.3-17.6-7.5-3.3-1.8-9.2-2.2-16.9 0-6.2-2.1-12.9-2.1-16.2-.3-4.3 2.4-9.1 5.9-13.7 7.3-.4-.1-.8 0-1.1.3-1.2.3-2.3.4-3.5.3-2 .3-3.7.9-5.1 1.6-9.8-15.1-14.9-20.3-14.8-20.8 0-.2-.1-.4-.1-.6 0-.1.1-.1.1-.2 2-3.7-1.1-34.5-.8-40.9l1.2-3.2c.9-3.9.8-7.8.2-11.6.4-2 .8-4 1.5-5.9 4-11.3 8.9.5 27.8-18.3 4.4-4.4 6.3-9.3 6.7-14.1 13.8 7.8 30.2 14.8 39.2 14.8 5.6 0 22.4 7.5 27.8 18.1 1.2 2.4 2 4.8 2.5 7.4-.2 3.1-.1 6.3.7 9.7l.3 1.3c.1 7.1-3.3 39.4-1.1 43.5z'/%3E%3C/svg%3E\");\n  --logo-white: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' style='enable-background:new 0 0 380 380' viewBox='0 0 380 380'%3E%3Cpath d='M324.6 186.6c-6.9 0-12.5 5.6-12.5 12.5s5.6 12.5 12.5 12.5 12.5-5.6 12.5-12.5-5.6-12.5-12.5-12.5z' fill='%23fff'/%3E%3Cpath d='M292.6 241h48.3c8.8 0 15.9-7.1 15.9-15.9v-51.9c0-8.8-7.1-15.9-15.9-15.9h-43.3c2.4 10.7 3.7 21.8 3.7 33.2 0 17.7-3 34.7-8.7 50.5z' style='fill:none'/%3E%3Cpath d='M340.9 135.3h-5.3V51.5c0-6.1-4.9-11-11-11s-11 4.9-11 11v83.8h-22.8c2.8 7.1 5.1 14.4 6.8 22h43.3c8.8 0 15.9 7.1 15.9 15.9V225c0 8.8-7.1 15.9-15.9 15.9h-48.3c-2.7 7.6-6 15-9.9 22h58.2c20.9 0 37.9-17 37.9-37.9v-51.8c0-20.9-17-37.9-37.9-37.9z' style='&%2310;' fill='%23fff'/%3E%3Ccircle cx='152.2' cy='190.5' r='150' style='fill:%23ffd400'/%3E%3Cpath d='M101.9 169.6s1.4-2.8 4.8-5.9c1.7-1.5 4-3.1 6.8-4.3 2.8-1.1 6.3-1.8 9.9-1.1l1.3.3.5.1.2.1.5.2 2 .8c1.3.5 2.6 1.1 3.8 1.7 2.4 1.2 4.7 2.3 6.6 3.6 3.8 2.4 6 4.5 6 4.5s-3.1 0-7.3-.8c-2.1-.4-4.6-.9-7.2-1.6-1.3-.4-2.7-.7-4-1.1l-2-.7-.5-.2-.2-.1c.2.1-.1 0-.1 0l-.8-.1c-1-.2-2.2-.2-3.3-.2-1.2.1-2.3.2-3.5.4-2.3.5-4.6 1.2-6.6 1.9-4.2 1.5-6.9 2.5-6.9 2.5zM197 169.7s-2.7-1-6.8-2.5c-1-.3-2-.6-3.2-1-1.1-.3-2.3-.6-3.4-.9-1.2-.2-2.3-.4-3.5-.4-1.1 0-2.3 0-3.3.2l-.8.1s-.3.1-.1 0l-.2.1-.5.2-2 .7c-1.4.4-2.7.8-4 1.2-2.6.7-5.1 1.2-7.2 1.6-4.2.8-7.3.8-7.3.8s2.2-2.1 6-4.5c1.9-1.3 4.1-2.4 6.6-3.6 1.2-.6 2.5-1.2 3.8-1.7l2-.8.5-.2.2-.1.5-.1 1.3-.3c3.6-.7 7.1 0 9.9 1.2 2.8 1.2 5.1 2.7 6.8 4.3 3.4 2.9 4.7 5.7 4.7 5.7z'/%3E%3Cpath d='M229.2 220.5c-2.4-13.9-18.8-29-18.6-29.7.1-.2.2-.5.4-.7.8.3 2.6 1.5 3.4 1.5 4.8-.3 7.1-4.7 7.7-6.7 1-3.2 4-19.9 3.7-23.4-.5-7.3-5.1-11.2-9.9-13.2.4-10-.1-21.9-3.1-33.1-1.6-6.2-4.2-12.1-7.4-17.4-8.6-17.5-25.9-32.1-54.4-32.1-15.8 0-28.2 4.5-37.7 11.5-12.2 5.4-22.7 20.7-26.5 38.8-2.4 11.2-2.6 23.1-1.9 32.9-1.8.9-3.5 1.9-4.9 3.4-2.5 2.6-3.8 5.7-3.7 9.4.1 2.8 2.2 19.8 4.2 23.6 1.8 3.4 3.5 7.1 7.2 7.1.8 0 2.8-1.8 3.6-2.1.1.1.1.2.2.4.1.6-15 12.8-17.2 26.9-3.1 19.1-5.7 38.2 3.2 61.3 4.7 12.1 44.7 36.3 73 36.3s66.8-22.8 72.5-34.4c10.1-20.1 9.5-41.6 6.2-60.3zm-13.7-66.4c2.4 1.4 4.6 3.8 4.9 7.8.2 2.4-2.5 18.1-3.5 21.4-.4 1.2-2.2 1.5-3.7 1.5.9-6.1 1.6-23.4 2.3-30.7zM89.1 185.2c-1.7 0-2.9-.8-3.8-2.4-1.2-2.4-3.5-18-3.6-21.1 0-2.2.7-4 2.2-5.5.5-.5 1-.9 1.6-1.3.8 6.6 2.7 23.3 3.9 30.3h-.3zm50.1 37.5c1.5-.2 3-.6 4.2-1.2 8.6-3.8 9.4-4 17.4-.1 1.2.6 2.7 1 4.3 1.3-7.3-.1-18.3-.1-25.9 0zm64.5-27.8c-2.1 2.5-8.1 9.4-13.5 20-1-.4-2.3-.7-3.6-.9-12.2-1.5-11.9-4.3-17.6-7.5-3.3-1.8-9.2-2.2-16.9 0-6.2-2.1-12.9-2.1-16.2-.3-4.3 2.4-9.1 5.9-13.7 7.3-.4-.1-.8 0-1.1.3-1.2.3-2.3.4-3.5.3-2 .3-3.7.9-5.1 1.6-9.8-15.1-14.9-20.3-14.8-20.8 0-.2-.1-.4-.1-.6 0-.1.1-.1.1-.2 2-3.7-1.1-34.5-.8-40.9l1.2-3.2c.9-3.9.8-7.8.2-11.6.4-2 .8-4 1.5-5.9 4-11.3 8.9.5 27.8-18.3 4.4-4.4 6.3-9.3 6.7-14.1 13.8 7.8 30.2 14.8 39.2 14.8 5.6 0 22.4 7.5 27.8 18.1 1.2 2.4 2 4.8 2.5 7.4-.2 3.1-.1 6.3.7 9.7l.3 1.3c.1 7.1-3.3 39.4-1.1 43.5z'/%3E%3C/svg%3E\");\n  --primary: rgb(15, 23, 42);\n  --dark-primary: #483d8b;\n  --bar-bg: rgb(15, 23, 42);\n}\n\nh1, h2, h3, h4, h5 {\n  box-shadow: none;\n  font-weight: 600;\n  letter-spacing: 0em;\n  --tw-text-opacity: 1;\n  color: rgb(55 65 81 / var(--tw-text-opacity));\n}\n\nh2 {\n  box-shadow: none;\n  margin-left: -2.5rem;\n  margin-right: -2.5rem;\n  border-radius: 0px;\n  --tw-bg-opacity: 1;\n  background-color: rgb(248 250 252 / var(--tw-bg-opacity));\n  padding-left: 2.5rem;\n  padding-right: 2.5rem;\n  padding-top: 1.25rem;\n  padding-bottom: 1.25rem;\n  font-size: 1.25rem;\n  line-height: 1.75rem;\n  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n}\n\nh3{\n  padding-left: 0px;\n  padding-right: 0px;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n}\n\nh4{\n  padding-left: 0px;\n  padding-right: 0px;\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n.main-left {\n  min-width: 300px;\n  box-shadow: none;\n  --tw-bg-opacity: 1;\n  background-color: rgb(255 255 255 / var(--tw-bg-opacity));\n  padding-right: 0px;\n  --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\n  --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n}\n\n@media screen and (max-width: 768px) {\n  .main-left {\n    min-width: 0;\n  }\n\n  .main-left.active {\n    min-width: 300px;\n    max-width: 90%;\n  }\n}\n\n.main-left .sidenav-header{\n  padding-top: 0.75rem;\n  padding-bottom: 0.75rem;\n}\n\n.main-left .sidenav-header .brand,\n.login-page .login-container .login-form .brand {\n  background: var(--logo);\n  text-indent: -5000px;\n  width: 80px;\n  height: 80px;\n  margin: 0 auto;\n  background-size: 100% auto;\n}\n\n.login-page .login-container .login-form .brand img{\n  display: none;\n}\n\nheader .fill .container .flex1 .brand {\n  background: var(--logo);\n  text-indent: -5000px;\n  width: 48px;\n  height: 48px;\n  margin: 0 auto;\n  background-size: 100% auto;\n  position: absolute;\n  z-index: 51;\n  top: 0.1rem;\n  left: 50%;\n  transform: translateX(-50%);\n}\n\n.main .main-left .nav li.slide .slide-menu{\n  margin-left: 1rem;\n  margin-right: 1rem;\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  padding-left: 0.5rem;\n  padding-right: 0.5rem;\n}\n\n.main .main-left .nav li.slide .slide-menu li:not(:first-child){\n  margin-top: 0.25rem;\n}\n\n.main .main-left .nav li.slide .slide-menu li a{\n  border-radius: 0.375rem;\n  padding-left: 1rem;\n  padding-right: 1rem;\n}\n\n.main .main-left .nav li.slide .slide-menu li a:hover{\n  --tw-bg-opacity: 1;\n  background-color: rgb(226 232 240 / var(--tw-bg-opacity));\n}\n\n.main .main-left .nav li.slide .slide-menu .active::after,\n.main .main-left .nav li.slide .slide-menu li::after {\n  display: none!important;\n}\n\n.main .main-left .nav li.slide .slide-menu .active a{\n  --tw-bg-opacity: 1;\n  background-color: rgb(226 232 240 / var(--tw-bg-opacity));\n}\n\n.main-right>#maincontent>.container{\n  margin: 0px;\n  --tw-bg-opacity: 1;\n  background-color: rgb(255 255 255 / var(--tw-bg-opacity));\n  padding-left: 2.5rem;\n  padding-right: 2.5rem;\n}\n\nheader {\n  /*display: none;*/\n}\n\n.cbi-section, .cbi-section-error, #iptables, .Firewall form, #cbi-network>.cbi-section-node, #cbi-wireless>.cbi-section-node, #cbi-wireless>#wifi_assoclist_table, [data-tab-title], [data-page^=\"admin-system-admin\"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear), [data-page=\"admin-system-opkg\"] #maincontent>.container {\n  box-shadow: none;\n}\n\n#view {\n  overflow: initial;\n}\n\n#view h2 + p,\n.cbi-map-descr,\n[data-page=\"admin-system-attendedsysupgrade-configuration\"] .cbi-map .cbi-map-descr,\n[data-tab-title]>p:first-child,\n.cbi-section-descr:not(:empty),\n[data-page=\"admin-network-firewall-custom\"] #view h2 + p, [data-page=\"admin-status-routes\"] #view h2 + p,\nh3 + .cbi-section-descr,\n[data-page=\"admin-system-flash\"] .cbi-section-descr{\n  margin-bottom: 1.25rem;\n  border-radius: 0.375rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(239 246 255 / var(--tw-bg-opacity));\n  padding: 1rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 400;\n  --tw-text-opacity: 1;\n  color: rgb(29 78 216 / var(--tw-text-opacity));\n}\n\n[data-page=\"admin-network-firewall-custom\"] #view h2 + p, [data-page=\"admin-status-routes\"] #view h2 + p{\n  margin-top: 1.25rem;\n}\n\nh2 + .cbi-map-descr{\n  margin-top: 1.25rem;\n}\n\ntr>td, tr>th, .tr>.td, .tr>.th, .cbi-section-table-row::before, #cbi-wireless>#wifi_assoclist_table>.tr:nth-child(2) {\n  border-top: 0;\n}\n\n.table {\n  background: none;\n  min-width: 100%;\n}\n\n.table > :not([hidden]) ~ :not([hidden]){\n  --tw-divide-y-reverse: 0;\n  border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));\n  border-bottom-width: calc(1px * var(--tw-divide-y-reverse));\n  --tw-divide-opacity: 1;\n  border-color: rgb(229 231 235 / var(--tw-divide-opacity));\n}\n\n.table{\n  --tw-bg-opacity: 1;\n  background-color: rgb(255 255 255 / var(--tw-bg-opacity));\n  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\n  --tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity));\n  --tw-ring-opacity: 0.05;\n}\n\n@media (min-width: 640px){\n  .table{\n    border-radius: 0.5rem;\n  }\n}\n\n.table .tr.table-titles {\n  background: none;\n  border-top-left-radius: 0.5rem;\n  border-top-right-radius: 0.5rem;\n}\n\n.table .tr:first-child .th:first-child{\n  border-top-left-radius: 0.5rem;\n  border-width: 0px;\n  --tw-bg-opacity: 1;\n  background-color: rgb(249 250 251 / var(--tw-bg-opacity));\n  padding-top: 0.875rem;\n  padding-bottom: 0.875rem;\n  padding-left: 1rem;\n  padding-right: 0.75rem;\n  text-align: left;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 600;\n  --tw-text-opacity: 1;\n  color: rgb(17 24 39 / var(--tw-text-opacity));\n}\n\n@media (min-width: 640px){\n  .table .tr:first-child .th:first-child{\n    padding-left: 1.5rem;\n  }\n}\n\n.table .td:first-child{\n  padding-top: 1rem;\n  padding-bottom: 1rem;\n  padding-left: 1rem;\n  padding-right: 0.75rem;\n  text-align: left;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  --tw-text-opacity: 1;\n  color: rgb(17 24 39 / var(--tw-text-opacity));\n}\n\n@media (min-width: 640px){\n  .table .td:first-child{\n    padding-left: 1.5rem;\n  }\n}\n\n.table .tr:first-child .th:not(:first-child){\n  border-width: 0px;\n  --tw-bg-opacity: 1;\n  background-color: rgb(249 250 251 / var(--tw-bg-opacity));\n  padding-left: 0.75rem;\n  padding-right: 0.75rem;\n  padding-top: 0.875rem;\n  padding-bottom: 0.875rem;\n  text-align: left;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 600;\n  --tw-text-opacity: 1;\n  color: rgb(17 24 39 / var(--tw-text-opacity));\n}\n\n.table .td:not(:first-child){\n  padding-left: 0.75rem;\n  padding-right: 0.75rem;\n  padding-top: 1rem;\n  padding-bottom: 1rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  --tw-text-opacity: 1;\n  color: rgb(107 114 128 / var(--tw-text-opacity));\n}\n\n.table .tr:last-child{\n  border-bottom-right-radius: 0.5rem;\n  border-bottom-left-radius: 0.5rem;\n}\n\n.table .tr:last-child .td:first-child{\n  border-bottom-left-radius: 0.5rem;\n}\n\n.table .tr:last-child .td:last-child{\n  border-bottom-right-radius: 0.5rem;\n}\n\n.cbi-title-section{\n  margin-left: -2.5rem;\n  margin-right: -2.5rem;\n  margin-bottom: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  --tw-bg-opacity: 1;\n  background-color: rgb(248 250 252 / var(--tw-bg-opacity));\n  padding-left: 2.5rem;\n  padding-right: 2.5rem;\n}\n\n.cbi-title-section h2{\n  margin-bottom: 0px;\n}\n\n#indicators{\n  display: flex;\n  align-items: center;\n  transform: translateY(1rem);\n  z-index: 51;\n}\n\nheader.bg-primary .fill{\n  margin-top: -0.5rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(226 232 240 / var(--tw-bg-opacity));\n  padding-top: 0px;\n  padding-bottom: 0.125rem;\n}\n\nheader .fill .status span{\n  display: inline-flex;\n  align-items: center;\n  border-radius: 0.375rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(249 250 251 / var(--tw-bg-opacity));\n  padding-left: 0.5rem;\n  padding-right: 0.5rem;\n  padding-top: 0.25rem;\n  padding-bottom: 0.25rem;\n  font-size: 0.75rem;\n  line-height: 1rem;\n  font-weight: 500;\n  --tw-text-opacity: 1;\n  color: rgb(75 85 99 / var(--tw-text-opacity));\n  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\n  --tw-ring-inset: inset;\n  --tw-ring-color: rgb(107 114 128 / 0.1);\n}\n\nheader .fill .status span[data-style=\"active\"]{\n  --tw-bg-opacity: 1;\n  background-color: rgb(240 253 244 / var(--tw-bg-opacity));\n  --tw-text-opacity: 1;\n  color: rgb(21 128 61 / var(--tw-text-opacity));\n  --tw-ring-color: rgb(22 163 74 / 0.2);\n}\n\n.cbi-section>h3:first-child, .cbi-section>h4:first-child, [data-tab-title]>h3:first-child, [data-tab-title]>h4:first-child{\n  padding-left: 0px;\n  padding-right: 0px;\n}\n\nheader .fill .container .flex1 .showSide {\n  transform: translateY(0.7rem);\n  width: 38px;\n  height: 28px;\n  text-align: center;\n  position: absolute;\n  top: 0;\n  left: 0;\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n  border-top-right-radius: 1rem;\n  border-bottom-right-radius: 1rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(75 85 99 / var(--tw-bg-opacity));\n  --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n  --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n}\n\nheader .fill .container .flex1 .showSide::before {\n  transform: translateY(0.1rem);\n  text-align: center;\n  font-size: 1.5rem;\n}\n\n.alert, .alert-message{\n  border-radius: 0.375rem;\n  padding: 1rem;\n  font-weight: 400;\n  --tw-shadow: 0 0 #0000;\n  --tw-shadow-colored: 0 0 #0000;\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n  text-shadow: none;\n}\n\n.alert-message>*{\n  margin: 0px;\n}\n\n.btn.danger, .alert-message.danger, .alert-message.error {\n  background: none;\n  border-width: 0px;\n  --tw-bg-opacity: 1;\n  background-color: rgb(254 242 242 / var(--tw-bg-opacity));\n  --tw-text-opacity: 1;\n  color: rgb(185 28 28 / var(--tw-text-opacity));\n}\n\n#maincontent > .container > #tabmenu{\n  margin-left: -2.5rem;\n  margin-right: -2.5rem;\n}\n\n#maincontent > .container > #tabmenu > .tabs{\n  margin-bottom: 0px;\n  border-radius: 0px;\n  padding-left: 0px;\n  padding-right: 0px;\n}\n\n[data-page=\"admin-system-admin\"] .cbi-map h2, [data-page=\"admin-system-admin-password\"] .cbi-map h2{\n  margin-left: -2.5rem;\n  margin-right: -2.5rem;\n}\n\n[data-page^=\"admin-system-admin\"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear), [data-page=\"admin-system-opkg\"] #maincontent>.container{\n  margin-top: 0px;\n}\n\nfooter{\n  margin-top: 1.25rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(255 255 255 / var(--tw-bg-opacity));\n  text-align: center;\n}\n\n.tabs,\n.cbi-tabmenu {\n  background: none;\n  padding: 0;\n  isolation: isolate;\n  margin-left: 0px;\n  margin-right: 0px;\n  margin-bottom: 1.25rem;\n  display: flex;\n}\n\n.tabs > :not([hidden]) ~ :not([hidden]),\n.cbi-tabmenu > :not([hidden]) ~ :not([hidden]){\n  --tw-space-x-reverse: 0;\n  margin-right: calc(0.25rem * var(--tw-space-x-reverse));\n  margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse)));\n  --tw-divide-x-reverse: 0;\n  border-right-width: calc(1px * var(--tw-divide-x-reverse));\n  border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));\n  --tw-divide-opacity: 1;\n  border-color: rgb(229 231 235 / var(--tw-divide-opacity));\n}\n\n.tabs,\n.cbi-tabmenu{\n  border-top-width: 1px;\n  border-bottom-width: 1px;\n  --tw-border-opacity: 1;\n  border-bottom-color: rgb(226 232 240 / var(--tw-border-opacity));\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n}\n\n.tabs li,\n.cbi-tabmenu li{\n  display: flex;\n  align-items: center;\n  padding-top: 0px;\n  padding-bottom: 0px;\n}\n\n.cbi-tabmenu li[class~=\"cbi-tab\"] {\n  border-bottom: 0;\n}\n\n.cbi-tabmenu > li:not(.cbi-tab-disabled),\n.cbi-tabmenu li[class~=\"cbi-tab\"]:not(.cbi-tab-disabled)\n.tabs li.active,\n.tabs li[class~=\"active\"] {\n  background: none;\n  height: auto;\n  border: none;\n  margin: 0;\n  border-radius: 0.375rem;\n  --tw-bg-opacity: 1;\n  padding-top: 0px;\n  padding-bottom: 0px;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  --tw-text-opacity: 1;\n  color: rgb(67 56 202 / var(--tw-text-opacity));\n  background-color: rgb(224 231 255 / var(--tw-bg-opacity))!important;\n}\n\n.tabs > li:not(.active), .cbi-tabmenu > .cbi-tab-disabled {\n  background: none;\n  background-color: transparent;\n  border: 0;\n  margin: 0;\n  border-radius: 0.375rem;\n  padding-left: 0.25rem;\n  padding-right: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  --tw-text-opacity: 1;\n  color: rgb(107 114 128 / var(--tw-text-opacity));\n}\n\n.tabs > li:not(.active):hover, .cbi-tabmenu > .cbi-tab-disabled:hover{\n  --tw-text-opacity: 1;\n  color: rgb(55 65 81 / var(--tw-text-opacity));\n}\n\n.tabs > li:not(.active):hover, .cbi-tabmenu > .cbi-tab-disabled:hover,\n.tabs li:hover {\n  background: none;\n  border: 0;\n  --tw-bg-opacity: 1;\n  background-color: rgb(248 250 252 / var(--tw-bg-opacity));\n}\n\nh2 + .cbi-tabmenu{\n  margin-left: -2.5rem;\n  margin-right: -2.5rem;\n  padding-left: 2.5rem;\n  padding-right: 2.5rem;\n}\n\n.table + h3{\n  margin-top: 1.25rem;\n}\n\nbody[data-page*=\"admin-\"] #maincontent > .container > #tabmenu > .tabs{\n  padding-left: 2.5rem;\n  padding-right: 2.5rem;\n}\n\nbody[data-page*=\"admin-status-\"] #maincontent > .container > #tabmenu > .tabs{\n  margin-bottom: 1.25rem;\n}\n\n#content_syslog {\n  box-shadow: none;\n}\n\n#syslog{\n  margin-top: 1.25rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(15 23 42 / var(--tw-bg-opacity));\n  padding-left: 1rem;\n  padding-right: 1rem;\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n  --tw-text-opacity: 1;\n  color: rgb(255 255 255 / var(--tw-text-opacity));\n}\n\n.login-page .login-container .login-form .cbi-button-apply{\n  letter-spacing: 0em;\n}\n\n.login-page .login-container footer,\n.login-page .main-bg{\n  display: none;\n}\n\n.login-page .login-container{\n  margin-top: 0px;\n  margin-bottom: 0px;\n  margin-left: auto;\n  margin-right: auto;\n  display: flex;\n  width: 100%;\n  align-items: center;\n  justify-content: center;\n  padding: 0px;\n}\n\n.login-page .login-container .login-form{\n  margin-left: auto;\n  margin-right: auto;\n  margin-top: 1.25rem;\n  border-radius: 0.375rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(255 255 255 / var(--tw-bg-opacity));\n  padding-top: 2.5rem;\n  padding-bottom: 2.5rem;\n  --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n  --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n  min-height: 0;\n}\n\n.login-page .login-container .login-form .cbi-button-apply{\n  margin-top: 0px;\n  margin-bottom: 0px;\n}\n\n.login-page .login-container .login-form .form-login .input-group input{\n  display: block;\n  height: auto;\n  width: 100%;\n  border-radius: 0.375rem;\n  border-width: 0px;\n  padding-left: 1rem;\n  padding-right: 1rem;\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  --tw-text-opacity: 1;\n  color: rgb(17 24 39 / var(--tw-text-opacity));\n  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\n  --tw-ring-inset: inset;\n  --tw-ring-opacity: 1;\n  --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));\n}\n\n.login-page .login-container .login-form .form-login .input-group input::placeholder{\n  --tw-text-opacity: 1;\n  color: rgb(156 163 175 / var(--tw-text-opacity));\n}\n\n.login-page .login-container .login-form .form-login .input-group input:focus{\n  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\n  --tw-ring-inset: inset;\n  --tw-ring-opacity: 1;\n  --tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity));\n}\n\n.login-page .login-container .login-form .form-login .input-group input {\n  padding-left: calc(24px + 1rem);\n}\n\n.login-page .login-container .login-form .form-login .input-group input:focus{\n  border-width: 0px;\n  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n}\n\n.login-page .login-container .login-form .form-login .input-group .border {\n  display: none!important;\n}\n\n.cbi-input-textarea,\ntextarea,\n[data-page=\"admin-system-crontab\"] #view p textarea,\n[data-page=\"admin-system-startup\"] textarea{\n  display: block;\n  height: auto;\n  width: 100%;\n  border-radius: 0.375rem;\n  border-width: 0px;\n  padding-left: 1rem;\n  padding-right: 1rem;\n  padding-top: 1rem;\n  padding-bottom: 1rem;\n  font-family: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n  font-size: 1rem;\n  line-height: 1.5rem;\n  letter-spacing: 0em;\n  --tw-text-opacity: 1;\n  color: rgb(17 24 39 / var(--tw-text-opacity));\n  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\n  --tw-ring-inset: inset;\n  --tw-ring-opacity: 1;\n  --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));\n}\n\n.cbi-input-textarea::placeholder,\ntextarea::placeholder,\n[data-page=\"admin-system-crontab\"] #view p textarea::placeholder,\n[data-page=\"admin-system-startup\"] textarea::placeholder{\n  --tw-text-opacity: 1;\n  color: rgb(156 163 175 / var(--tw-text-opacity));\n}\n\n.cbi-input-textarea:focus,\ntextarea:focus,\n[data-page=\"admin-system-crontab\"] #view p textarea:focus,\n[data-page=\"admin-system-startup\"] textarea:focus{\n  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\n  --tw-ring-inset: inset;\n  --tw-ring-opacity: 1;\n  --tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity));\n}\n\n.cbi-input-text,\n#localtime,\n.cbi-input-select,\n.cbi-dropdown>ul>li input[type=\"text\"]{\n  display: block;\n  height: auto;\n  width: 100%;\n  border-radius: 0.375rem;\n  border-width: 0px;\n  padding-left: 1rem;\n  padding-right: 1rem;\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  --tw-text-opacity: 1;\n  color: rgb(17 24 39 / var(--tw-text-opacity));\n  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\n  --tw-ring-inset: inset;\n  --tw-ring-opacity: 1;\n  --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));\n}\n\n.cbi-input-text::placeholder,\n#localtime::placeholder,\n.cbi-input-select::placeholder,\n.cbi-dropdown>ul>li input[type=\"text\"]::placeholder{\n  --tw-text-opacity: 1;\n  color: rgb(156 163 175 / var(--tw-text-opacity));\n}\n\n.cbi-input-text:focus,\n#localtime:focus,\n.cbi-input-select:focus,\n.cbi-dropdown>ul>li input[type=\"text\"]:focus{\n  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\n  --tw-ring-inset: inset;\n  --tw-ring-opacity: 1;\n  --tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity));\n}\n\n.control-group input{\n  padding-left: 1rem;\n  padding-right: 1rem;\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n}\n\nselect:not([multiple=\"multiple\"]):focus, input:not(.cbi-button):focus, .cbi-dropdown:focus,\n#localtime:focus,\n.cbi-input-select:focus{\n  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\n  --tw-ring-inset: inset;\n  --tw-ring-opacity: 1;\n  --tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity));\n}\n\n.cbi-value-description{\n  padding-left: 0px;\n  padding-right: 0px;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  --tw-text-opacity: 1;\n  color: rgb(156 163 175 / var(--tw-text-opacity));\n}\n\n#view h2 + p{\n  margin-top: 1.25rem;\n}\n\n.cbi-section-create{\n  padding-left: 0px;\n  padding-right: 0px;\n}\n\n@media screen and (min-width: 1px) {\n  .btn:only-child, .cbi-button:only-child {\n    margin-left: 0!important;\n  }\n}\n\n.cbi-value-field br {\n  display: none;\n}\n\n.cbi-map:not(:first-child){\n  margin-top: 0px;\n}\n\n.table .cbi-value-field br {\n  display: block;\n}\n\n.btn, .cbi-button, .item::after{\n  padding-left: 1rem;\n  padding-right: 1rem;\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n.login-page .login-container .login-form .form-login .input-group::before {\n  font-size: 1.2rem;\n}\n\n.Dashboard .internet-status-self .internet-status-info .title,\n.Dashboard .title {\n  height: initial;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n\n.Dashboard .title img {\n  height: 24px;\n  width: auto!important;\n}\n\n.main-right>#maincontent .Dashboard h3{\n  text-align: right;\n}\n\n.main-right>#maincontent .Dashboard .settings-info .label{\n  padding-top: 0px;\n  padding-bottom: 0px;\n  padding-left: 0.5rem;\n  padding-right: 0.5rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 400;\n}\n\n.Dashboard .internet-status-self .settings-info p:nth-child(2) span:first-child, .Dashboard .router-status-wifi .wifi-info .settings-info p:first-child span:first-child, .Dashboard .router-status-wifi .wifi-info .settings-info p:nth-child(2) span:first-child{\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n.Dashboard .settings-info p span:first-child{\n  line-height: 1.5rem;\n  font-weight: 600;\n  font-size: 1rem!important;\n}\n\n@media screen and (min-width: 0px) {\n  .label.label-success{\n    --tw-bg-opacity: 1;\n    background-color: rgb(240 253 244 / var(--tw-bg-opacity));\n    --tw-text-opacity: 1;\n    --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n    --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n    box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\n    --tw-ring-inset: inset;\n    --tw-ring-color: rgb(22 163 74 / 0.2);\n    color: rgb(21 128 61 / var(--tw-text-opacity))!important;\n  }\n}\n\n.Dashboard .router-status-self .router-status-info .settings-info{\n  padding-left: 0px;\n}\n\n.Dashboard .section-content .internet-status-info .settings-info, .Dashboard .router-status-wifi .wifi-info .settings-info, .Dashboard .router-status-lan .lan-info .settings-info{\n  justify-content: space-between;\n}\n\n.cbi-progressbar{\n  --tw-bg-opacity: 1;\n  background-color: rgb(148 163 184 / var(--tw-bg-opacity));\n  height: 1.5rem;\n}\n\n.cbi-progressbar>div{\n  --tw-bg-opacity: 1;\n  background-color: rgb(15 23 42 / var(--tw-bg-opacity));\n}\n\n.cbi-progressbar::after{\n  padding-top: 0.25rem;\n  padding-bottom: 0.25rem;\n  font-size: 0.75rem;\n  line-height: 1rem;\n  --tw-text-opacity: 1;\n  color: rgb(255 255 255 / var(--tw-text-opacity));\n}\n\n.table .td:not(:first-child),\n.table .td:first-child{\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n.table .td:first-child,\n.table .td:not(:first-child),\n.table .tr:first-child .th:not(:first-child),\n.table .tr:first-child .th:first-child{\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 1rem;\n}\n\n.td, .th{\n  text-align: left;\n}\n\n.nft-chain-hook{\n  margin-bottom: 1.25rem;\n  border-radius: 0.375rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(239 246 255 / var(--tw-bg-opacity));\n  padding: 1rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 400;\n  --tw-text-opacity: 1;\n  color: rgb(29 78 216 / var(--tw-text-opacity));\n}\n\n.nft-rules{\n  margin-bottom: 1.25rem;\n}\n\n.nft-table > h3,\n[data-page=\"admin-system-opkg\"] h2{\n  margin-left: -2.5rem;\n  margin-right: -2.5rem;\n  border-radius: 0px;\n  border-bottom-width: 1px;\n  --tw-bg-opacity: 1;\n  background-color: rgb(248 250 252 / var(--tw-bg-opacity));\n  padding-left: 2.5rem;\n  padding-right: 2.5rem;\n  padding-top: 1.25rem;\n  padding-bottom: 1.25rem;\n  font-size: 1.5rem;\n  line-height: 2rem;\n  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n}\n\n[data-page=\"admin-system-opkg\"] .cbi-tabmenu{\n  margin-left: -2.5rem;\n  margin-right: -2.5rem;\n  padding-left: 2.5rem;\n  padding-right: 2.5rem;\n}\n\nlabel, .label-status{\n  display: inline-flex;\n  align-items: center;\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n.label-status{\n  display: inline-flex;\n  align-items: center;\n  border-radius: 0.375rem;\n  padding-left: 0.5rem;\n  padding-right: 0.5rem;\n  padding-top: 0.25rem;\n  padding-bottom: 0.25rem;\n  font-size: 0.75rem;\n  line-height: 1rem;\n  font-weight: 500;\n}\n\nlabel>input[type=\"checkbox\"], label>input[type=\"radio\"] {\n  top: initial;\n  right: initial;\n  margin-right: 0.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n@media screen and (min-width: 0px) {\n  body[data-page=\"admin-system-opkg\"] .controls {\n    margin-top: 0!important;\n    margin-left: -2.5rem!important;\n    margin-right: -2.5rem!important;\n    margin-bottom: 1.25rem;\n    padding-left: 2.5rem;\n    padding-right: 2.5rem;\n  }\n\n  body[data-page=\"admin-system-opkg\"] h2 + .controls{\n    --tw-bg-opacity: 1;\n    background-color: rgb(248 250 252 / var(--tw-bg-opacity));\n    padding-top: 1.25rem;\n    padding-bottom: 1.25rem;\n    --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n    --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);\n    box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n    margin-bottom: 0!important;\n  }\n\n  body[data-page=\"admin-system-opkg\"] h2 + .controls > div:first-child{\n    margin-bottom: 1.25rem;\n  }\n\n  #maincontent > .alert-message:first-child{\n    margin: 0px;\n    align-items: center;\n    padding-left: 2.5rem;\n    padding-right: 2.5rem;\n    border-radius: 0!important;\n  }\n\n  #maincontent > .alert-message:first-child > div:last-child{\n    text-align: right;\n    flex: initial !important;\n  }\n\n  .cbi-dropdown[open]>ul.dropdown {\n    max-height: 210px!important;\n  }\n}\n\n.controls>*>.btn:not([aria-label$=\"page\"]){\n  margin: 0px;\n  --tw-bg-opacity: 1;\n  background-color: rgb(15 23 42 / var(--tw-bg-opacity));\n  padding-left: 1rem;\n  padding-right: 1rem;\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n}\n\n.cbi-dropdown.cbi-button-apply {\n  padding-top: 0.594rem;\n  padding-bottom: 0.594rem;\n}\n\n.cbi-page-actions{\n  padding-left: 0px;\n  padding-right: 0px;\n}\n\nbody[data-page=\"admin-services-ruantiblock-service\"] h2{\n  margin-bottom: 1.25rem;\n}\n\n.cbi-dropdown>ul>li input[type=\"text\"] {\n  height: initial;\n}\n\n.cbi-value input[type=\"password\"]{\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\nh2 + .cbi-section > .cbi-tabmenu:first-child{\n  margin-left: -2.5rem;\n  margin-right: -2.5rem;\n  padding-left: 2.5rem;\n  padding-right: 2.5rem;\n}\n\n::selection{\n  --tw-bg-opacity: 1;\n  background-color: rgb(199 210 254 / var(--tw-bg-opacity));\n  --tw-text-opacity: 1;\n  color: rgb(0 0 0 / var(--tw-text-opacity));\n}\n\n.main .main-left .nav>li>a[data-title=\"Wizard\"]:first-child.active::before {\n  background: var(--logo-white);\n  background-size: 100% auto;\n  content: '';\n  width: 19px;\n  height: 19px;\n}\n\n.main .main-left .nav>li:not(.active)>a[data-title=\"Wizard\"]:first-child::before {\n  background: var(--logo);\n  background-size: 100% auto;\n  content: '';\n  width: 19px;\n  height: 19px;\n}\n\n.network-status-table{\n  margin-top: 1.25rem;\n  margin-bottom: 1.25rem;\n  display: grid;\n  grid-template-columns: repeat(1, minmax(0, 1fr));\n  gap: 1.25rem;\n}\n\n@media (min-width: 640px){\n  .network-status-table{\n    grid-template-columns: repeat(2, minmax(0, 1fr));\n  }\n}\n\n@media (min-width: 1024px){\n  .network-status-table{\n    grid-template-columns: repeat(3, minmax(0, 1fr));\n  }\n}\n\n.ifacebox,\n.network-status-table .ifacebox{\n  position: relative;\n  margin: 0px;\n  border-radius: 0.5rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(249 250 251 / var(--tw-bg-opacity));\n  padding-top: 0px;\n  padding-bottom: 0px;\n  padding-left: 0px;\n  padding-right: 0px;\n  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n  overflow: initial;\n}\n\n.ifacebox-head,\n.ifacebox-head.active,\n.network-status-table .ifacebox-head,\n.network-status-table .ifacebox-head.active {\n  background: none;\n  border-top-left-radius: 0.5rem;\n  border-top-right-radius: 0.5rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(229 231 235 / var(--tw-bg-opacity));\n  padding-left: 1rem;\n  padding-right: 1rem;\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  text-align: right;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 400;\n  --tw-text-opacity: 1;\n  color: rgb(17 24 39 / var(--tw-text-opacity));\n}\n\n.ifacebox-head.cbi-tooltip-container {\n  border-radius: 0!important;\n}\n\n.ifacebox-head.active,\n.network-status-table .ifacebox-head.active{\n  --tw-bg-opacity: 1;\n  background-color: rgb(187 247 208 / var(--tw-bg-opacity));\n}\n\n.ifacebox-head.active *,\n.network-status-table .ifacebox-head.active *{\n  --tw-text-opacity: 1;\n  color: rgb(17 24 39 / var(--tw-text-opacity));\n}\n\n.ifacebox-body,\n.network-status-table .ifacebox-body{\n  padding-left: 1rem;\n  padding-right: 1rem;\n  padding-top: 0.5rem;\n  padding-bottom: 1rem;\n}\n\n.network-status-table .ifacebox-body>span {\n}\n\n.ifacebox-body .ifacebadge,\n.network-status-table .ifacebox-body .ifacebadge:not(.cbi-tooltip) {\n  background-color: transparent;\n  margin-top: 0.5rem;\n  align-items: flex-start;\n  border-top-width: 1px;\n  --tw-border-opacity: 1;\n  border-color: rgb(229 231 235 / var(--tw-border-opacity));\n  padding: 0px;\n  padding-top: 1rem;\n}\n\n.network-status-table .ifacebox-body .ifacebadge>span {\n}\n\n.ifacebox-body .ifacebadge img,\n.network-status-table .ifacebox-body .ifacebadge:not(.cbi-tooltip) img {\n  position: absolute;\n  left: 10px;\n  top: 0.75rem;\n  margin: 0px;\n}\n\ndiv[style*=\"display:grid;grid-template-columns:repeat\"] .ifacebox-body img {\n  position: absolute;\n  left: 10px;\n  top: 0.5rem;\n  margin: 0px;\n}\n\ndiv[style*=\"display:grid;grid-template-columns:repeat\"] img+br{\n  display: none;\n}\n\ndiv[style*=\"display:grid;grid-template-columns:repeat\"] .ifacebox-body,\ndiv[style*=\"display:grid;grid-template-columns:repeat\"] .ifacebox-body span {\n  font-size: 1rem!important;\n}\n\n.network-status-table .nowrap:not(.td){\n  text-overflow: ellipsis;\n  max-width: 100%;\n  overflow: hidden;\n  display: block;\n}\n\n.network-status-table .nowrap:not(.td) + br {\n  display: none;\n}\n\ndiv[style*=\"display:grid;grid-template-columns:repeat\"]{\n  margin-top: 1.25rem;\n  margin-bottom: 1.25rem;\n  display: grid;\n  grid-template-columns: repeat(1, minmax(0, 1fr));\n  gap: 1.25rem;\n}\n\n@media (min-width: 640px){\n  div[style*=\"display:grid;grid-template-columns:repeat\"]{\n    grid-template-columns: repeat(2, minmax(0, 1fr));\n  }\n}\n\n@media (min-width: 1024px){\n  div[style*=\"display:grid;grid-template-columns:repeat\"]{\n    grid-template-columns: repeat(4, minmax(0, 1fr));\n  }\n}\n\ndiv[style*=\"display:grid;grid-template-columns:repeat\"] {\n  grid-template-columns: repeat(4, minmax(0, 1fr))!important;\n}\n\ndiv[style*=\"display:grid;grid-template-columns:repeat\"] .ifacebox{\n  position: relative;\n  margin: 0px;\n  border-radius: 0.5rem;\n  --tw-bg-opacity: 1;\n  background-color: rgb(249 250 251 / var(--tw-bg-opacity));\n  padding-top: 0px;\n  padding-bottom: 0px;\n  padding-left: 0px;\n  padding-right: 0px;\n  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n  max-width: none!important;\n  flex-basis: initial!important;\n  min-width: initial!important;\n  margin: 0!important;\n  width: calc(25% - 1.25rem);\n  overflow: initial;\n}\n\n#cbi-network-interface .table,\n#cbi-network-interface .cbi-section-table-row,\n#cbi-network-interface div>table>tbody>tr:nth-of-type(2n), div>.table>.tr:nth-of-type(2n){\n  --tw-bg-opacity: 1;\n  background-color: rgb(255 255 255 / var(--tw-bg-opacity));\n  box-shadow: none;\n}\n\n#cbi-network-interface .table>.tr>.td.cbi-value-field{\n  padding-left: 0px;\n  padding-right: 0px;\n  padding-top: 1.25rem;\n  padding-bottom: 1.25rem;\n  text-align: left!important;\n  vertical-align: top!important;\n}\n\n#cbi-network-interface .ifacebox {\n  min-width: 180px;\n}\n\n#lan-ifc-devices > span:nth-child(2){\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  gap: 0.5rem;\n}\n\n.cbi-tooltip-container:hover .cbi-tooltip.ifacebadge{\n  --tw-bg-opacity: 1;\n  background-color: rgb(255 255 255 / var(--tw-bg-opacity));\n  padding: 0.5rem;\n}\n\n.cbi-tooltip-container:hover .cbi-tooltip.ifacebadge img {\n  left: initial;\n  right: 10px;\n}\n\n#cbi-network-interface .ifacebox-body > .cbi-tooltip-container:first-child > img {\n  position: absolute;\n  left: 10px;\n  top: 0.75rem;\n  margin: 0px;\n}\n\n#cbi-network-interface .ifacebox-body > br{\n  display: none;\n}\n\n#cbi-network-interface .ifacebox-body small{\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n#cbi-network-interface .table>.tr>.td.cbi-value-field strong{\n  --tw-text-opacity: 1;\n  color: rgb(0 0 0 / var(--tw-text-opacity));\n}\n\n#cbi-network-interface .table .td:not(:first-child){\n  padding-left: 0px;\n  padding-right: 0px;\n  padding-top: 1.25rem;\n  padding-bottom: 1.25rem;\n  vertical-align: top!important;\n}\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/htdocs/luci-static/routerich/icon/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig><msapplication><tile><square70x70logo src=\"/ms-icon-70x70.png\"/><square150x150logo src=\"/ms-icon-150x150.png\"/><square310x310logo src=\"/ms-icon-310x310.png\"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>"
  },
  {
    "path": "luci/themes/luci-theme-routerich/htdocs/luci-static/routerich/icon/manifest.json",
    "content": "{\n\t\"name\": \"Openwrt\",\n\t\"icons\": [\n\t\t{\n\t\t\t\"src\": \"\\/android-icon-36x36.png\",\n\t\t\t\"sizes\": \"36x36\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"0.75\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"\\/android-icon-48x48.png\",\n\t\t\t\"sizes\": \"48x48\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"1.0\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"\\/android-icon-72x72.png\",\n\t\t\t\"sizes\": \"72x72\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"1.5\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"\\/android-icon-96x96.png\",\n\t\t\t\"sizes\": \"96x96\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"2.0\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"\\/android-icon-144x144.png\",\n\t\t\t\"sizes\": \"144x144\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"3.0\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"\\/android-icon-192x192.png\",\n\t\t\t\"sizes\": \"192x192\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"4.0\"\n\t\t}\n\t]\n}"
  },
  {
    "path": "luci/themes/luci-theme-routerich/luasrc/view/themes/routerich/footer.htm",
    "content": "<%#\n\tArgon is a clean HTML5 theme for LuCI. It is based on luci-theme-material Argon Template\n\n\tluci-theme-argon\n\tCopyright 2020 Jerrykuku <jerrykuku@qq.com>\n\n\tHave a bug? Please create an issue here on GitHub!\n\thttps://github.com/jerrykuku/luci-theme-argon/issues\n\n\tluci-theme-material: \n\tCopyright 2015 Lutty Yang <lutty@wcan.in>\n\t\n\tAgron Theme\n\thttps://demos.creative-tim.com/argon-dashboard/index.html \n\n\tLicensed to the public under the Apache License 2.0 \n-%>\n\n<% local ver = require \"luci.version\" %>\n</div>\n<footer class=\"mobile-hide\">\n\t<div>\n\t\t<a class=\"luci-link\" href=\"https://github.com/openwrt/luci\" target=\"_blank\">Powered by <%= ver.luciname %> (<%= ver.luciversion %>)</a> /\n\t\t\t\t\t\t<%= ver.distversion %>\n\t\t<ul class=\"breadcrumb pull-right\" id=\"modemenu\" style=\"display:none\"></ul>\n\t</div>\n</footer>\n</div>\n</div>\n<script>\n\t// thanks for Jo-Philipp Wich <jow@openwrt.org>\n\tvar luciLocation = <%= luci.http.write_json(luci.dispatcher.context.path) %>;\n\tvar winHeight = $(window).height();\n\t$(window).resize(function () {\n\t\tvar winWidth = $(window).width()\n\t\tif(winWidth < 600){\n\t\t\tvar newHeight = $(this).height();\n\t\t\tvar keyboradHeight = newHeight - winHeight;\n\t\t\t$(\".ftc\").css(\"bottom\", keyboradHeight + 30);\n\t\t}\n\t})\n</script>\n<script type=\"text/javascript\">L.require('menu-routerich')</script>\n</body>\n</html>\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/luasrc/view/themes/routerich/footer_login.htm",
    "content": "<%#\n\tArgon is a clean HTML5 theme for LuCI. It is based on luci-theme-material Argon Template\n\n\tluci-theme-argon\n\tCopyright 2020 Jerrykuku <jerrykuku@qq.com>\n\n\tHave a bug? Please create an issue here on GitHub!\n\thttps://github.com/jerrykuku/luci-theme-argon/issues\n\n\tluci-theme-material:\n\tCopyright 2015 Lutty Yang <lutty@wcan.in>\n\n\tAgron Theme\n\thttps://demos.creative-tim.com/argon-dashboard/index.html\n\n\tLicensed to the public under the Apache License 2.0\n-%>\n\n<% local ver = require \"luci.version\" %>\n</div>\n<footer>\n\t<div>\n\t\t<a class=\"luci-link\" href=\"https://github.com/openwrt/luci\" target=\"_blank\">Powered by <%= ver.luciname %> (<%= ver.luciversion %>)</a> / <%= ver.distversion %>\n\t</div>\n</footer>\n</div>\n</div>\n<script>\n\t// thanks for Jo-Philipp Wich <jow@openwrt.org>\n\tvar luciLocation = <%= luci.http.write_json(luci.dispatcher.context.path) %>;\n\tvar winHeight = $(window).height();\n\t$(window).resize(function () {\n\t\tvar winWidth = $(window).width()\n\t\tif(winWidth < 600){\n\t\t\tvar newHeight = $(this).height();\n\t\t\tvar keyboradHeight = newHeight - winHeight;\n\t\t\t$(\".ftc\").css(\"bottom\", keyboradHeight + 30);\n\t\t}\n\t})\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/luasrc/view/themes/routerich/header.htm",
    "content": "<%#\n\tArgon is a clean HTML5 theme for LuCI. It is based on luci-theme-material Argon Template\n\n\tluci-theme-argon\n\tCopyright 2020 Jerrykuku <jerrykuku@qq.com>\n\n\tHave a bug? Please create an issue here on GitHub!\n\thttps://github.com/jerrykuku/luci-theme-argon/issues\n\n\tluci-theme-material:\n\tCopyright 2015 Lutty Yang <lutty@wcan.in>\n\n\tArgon Theme\n\thttps://demos.creative-tim.com/argon-dashboard/index.html\n\n\tLicensed to the public under the Apache License 2.0\n-%>\n\n<%\n\tlocal sys = require \"luci.sys\"\n\tlocal util = require \"luci.util\"\n\tlocal http = require \"luci.http\"\n\tlocal disp = require \"luci.dispatcher\"\n\tlocal ver = require \"luci.version\"\n\n\tlocal boardinfo = util.ubus(\"system\", \"board\")\n\n\tlocal node = disp.context.dispatched\n\n\tlocal fs = require \"nixio.fs\"\n\tlocal nutil = require \"nixio.util\"\n\tlocal uci = require 'luci.model.uci'.cursor()\n\n\t-- send as HTML5\n\thttp.prepare_content(\"text/html\")\n\n\tmath.randomseed(os.time())\n\n\t-- Custom settings\n\tlocal mode = 'normal'\n\tlocal dark_css = fs.readfile('/www/luci-static/routerich/css/dark.css')\n\tlocal bar_color = '#5e72e4'\n\tlocal primary, dark_primary, blur_radius, blur_radius_dark, blur_opacity\n\tif fs.access('/etc/config/routerich') then\n\t\tprimary = uci:get_first('routerich', 'global', 'primary')\n\t\tdark_primary = uci:get_first('routerich', 'global', 'dark_primary')\n\t\tblur_radius = uci:get_first('routerich', 'global', 'blur')\n\t\tblur_radius_dark = uci:get_first('routerich', 'global', 'blur_dark')\n\t\tblur_opacity = uci:get_first('routerich', 'global', 'transparency')\n\t\tblur_opacity_dark = uci:get_first('routerich', 'global', 'transparency_dark')\n\t\tmode = uci:get_first('routerich', 'global', 'mode')\n\t\tbar_color = mode == 'dark' and dark_primary or primary\n\tend\n\n\t-- Brand name\n\tlocal brand_name = boardinfo.hostname or \"?\"\n-%>\n<!DOCTYPE html>\n<html lang=\"<%=luci.i18n.context.lang%>\">\n\n<head>\n\t<meta charset=\"utf-8\">\n\t<title>\n\t\t<%=striptags( (boardinfo.hostname or \"?\") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %>\n\t\t- LuCI</title>\n\t<meta content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\" name=\"viewport\" />\n\t<meta name=\"format-detection\" content=\"telephone=no, email=no\" />\n\t<meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\t<meta name=\"mobile-web-app-capable\" content=\"yes\">\n\t<meta name=\"x5-fullscreen\" content=\"true\">\n\t<meta name=\"full-screen\" content=\"yes\">\n\t<meta name=\"x5-page-mode\" content=\"app\">\n\t<meta name=\"browsermode\" content=\"application\">\n\t<meta name=\"msapplication-tap-highlight\" content=\"no\">\n\t<meta name=\"msapplication-TileColor\" content=\"<%=bar_color%>\">\n\t<meta name=\"application-name\" content=\"<%=striptags( (boardinfo.hostname or \"?\") ) %> - LuCI\">\n\t<meta name=\"apple-mobile-web-app-title\" content=\"<%=striptags( (boardinfo.hostname or \"?\") ) %> - LuCI\">\n\t<link rel=\"apple-touch-icon\" sizes=\"60x60\" href=\"<%=media%>/icon/apple-icon-60x60.png\">\n\t<link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"<%=media%>/icon/apple-icon-72x72.png\">\n\t<link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"<%=media%>/icon/apple-icon-144x144.png\">\n\t<link rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"<%=media%>/icon/android-icon-192x192.png\">\n\t<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"<%=media%>/icon/favicon-32x32.png\">\n\t<link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"<%=media%>/icon/favicon-96x96.png\">\n\t<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"<%=media%>/icon/favicon-16x16.png\">\n\t<link rel=\"manifest\" href=\"<%=media%>/icon/manifest.json\"  crossorigin=\"use-credentials\">\n\t<meta name=\"msapplication-TileColor\" content=\"<%=bar_color%>\">\n\t<meta name=\"msapplication-TileImage\" content=\"<%=media%>/icon/ms-icon-144x144.png\">\n\t<meta name=\"theme-color\" content=\"<%=bar_color%>\">\n\t<link rel=\"stylesheet\" href=\"<%=media%>/css/cascade.css?v=1.0.0\">\n\t<link rel=\"stylesheet\" href=\"<%=media%>/css/routerich.css?v=1.0.0\">\n\t<style title=\"text/css\">\n\t\t<% if mode ~= 'dark' then %>\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\t<%=dark_css%>\n\t\t\t}\n\t\t<% if fs.access('/etc/config/routerich') then %>\n\t\t:root {\n\t\t\t--primary: <%=primary%>;\n\t\t\t--dark-primary: <%=dark_primary%>;\n\t\t\t\t--bar-bg: <%=primary%>;\n\t\t\t--blur-radius:<%=blur_radius%>px;\n\t\t\t--blur-opacity:<%=blur_opacity%>;\n\t\t\t--blur-radius-dark:<%=blur_radius_dark%>px;\n\t\t\t--blur-opacity-dark:<%=blur_opacity_dark%>;\n\t\t}\n\t\t<% end -%>\n\t\t<% elseif mode == 'dark' then %>\n\t\t\t<%=dark_css%>\n\t\t<% if fs.access('/etc/config/routerich') then %>\n\t\t:root {\n\t\t\t--primary: <%=primary%>;\n\t\t\t--dark-primary: <%=dark_primary%>;\n\t\t\t\t--bar-bg: <%=dark_primary%>;\n\t\t\t--blur-radius:<%=blur_radius%>px;\n\t\t\t--blur-opacity:<%=blur_opacity%>;\n\t\t\t--blur-radius-dark:<%=blur_radius_dark%>px;\n\t\t\t--blur-opacity-dark:<%=blur_opacity_dark%>;\n\t\t}\n\t\t<% end -%>\n\t\t<% end -%>\n\t</style>\n\t<link rel=\"shortcut icon\" href=\"<%=media%>/favicon.ico\">\n\t<% if node and node.css then %>\n\t<link rel=\"stylesheet\" href=\"<%=resource%>/<%=node.css%>\">\n\t<% end -%>\n\t<% if css then %>\n\t<style title=\"text/css\">\n\t\t<%=css %>\n\t</style>\n\t<% end -%>\n\t<script src=\"<%=media%>/js/polyfill.min.js?v=1.0.0\"></script>\n\t<script src=\"<%=url('admin/translations', luci.i18n.context.lang)%>?v=<%=ver.luciversion%>\"></script>\n\t<script src=\"<%=resource%>/cbi.js?v=<%=ver.luciversion%>\"></script>\n\t<script src=\"<%=resource%>/luci.js?v=<%=ver.luciversion%>\"></script>\n\t<script src=\"<%=media%>/js/jquery.min.js?v=3.5.1\"></script>\n</head>\n\n<body\n\tclass=\"lang_<%=luci.i18n.context.lang%> <% if node then %><%= striptags( node.title ) %><% end %> <% if luci.dispatcher.context.authsession then %>logged-in<% end %>\"\n\tdata-page=\"<%= table.concat(disp.context.requestpath, \"-\") %>\">\n\n\t<div class=\"main\">\n\t\t<div class=\"main-left\" id=\"mainmenu\" style=\"display:none\">\n\t\t\t<div class=\"sidenav-header d-flex align-items-center\">\n\t\t\t\t<a class=\"brand\" href=\"#\"><%=brand_name%></a>\n\t\t\t\t<div class=\"ml-auto\">\n\t\t\t\t\t<!-- Sidenav toggler -->\n\t\t\t\t\t<div class=\"sidenav-toggler d-none d-xl-block active\" data-action=\"sidenav-unpin\"\n\t\t\t\t\t\tdata-target=\"#sidenav-main\">\n\t\t\t\t\t\t<div class=\"sidenav-toggler-inner\">\n\t\t\t\t\t\t\t<i class=\"sidenav-toggler-line\"></i>\n\t\t\t\t\t\t\t<i class=\"sidenav-toggler-line\"></i>\n\t\t\t\t\t\t\t<i class=\"sidenav-toggler-line\"></i>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"main-right\">\n\t\t\t<header class=\"bg-primary\">\n\t\t\t\t<div class=\"fill\">\n\t\t\t\t\t<div class=\"container\">\n\t\t\t\t\t\t<div class=\"flex1\">\n\t\t\t\t\t\t\t<a class=\"showSide\"></a>\n\t\t\t\t\t\t\t<a class=\"brand\" href=\"#\"><%=brand_name%></a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"status\" id=\"indicators\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</header>\n\t\t\t<div class=\"darkMask\"></div>\n\t\t\t<div id=\"maincontent\">\n\t\t\t\t<div class=\"container\">\n\t\t\t\t\t<%- if luci.sys.process.info(\"uid\") == 0 and luci.sys.user.getuser(\"root\") and not luci.sys.user.getpasswd(\"root\") then -%>\n\t\t\t\t\t<div class=\"alert-message error\">\n\t\t\t\t\t\t<h4><%:No password set!%></h4>\n\t\t\t\t\t\t<p><%:There is no password set on this router. Please configure a root password to protect the web interface.%>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<% if disp.lookup(\"admin/system/admin\") then %>\n\t\t\t\t\t\t<div class=\"right\"><a class=\"btn\"\n\t\t\t\t\t\t\t\thref=\"<%=url(\"admin/system/admin\")%>\"><%:Go to password configuration...%></a></div>\n\t\t\t\t\t\t<% end %>\n\t\t\t\t\t</div>\n\t\t\t\t\t<%- end -%>\n\n\t\t\t\t\t<noscript>\n\t\t\t\t\t\t<div class=\"alert-message error\">\n\t\t\t\t\t\t\t<h4><%:JavaScript required!%></h4>\n\t\t\t\t\t\t\t<p><%:You must enable JavaScript in your browser or LuCI will not work properly.%></p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</noscript>\n\n\t\t\t\t\t<div id=\"tabmenu\" style=\"display:none\"></div>\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/luasrc/view/themes/routerich/header_login.htm",
    "content": "<%#\n\tArgon is a clean HTML5 theme for LuCI. It is based on luci-theme-material Argon Template\n\n\tluci-theme-argon\n\tCopyright 2020 Jerrykuku <jerrykuku@qq.com>\n\n\tHave a bug? Please create an issue here on GitHub!\n\thttps://github.com/jerrykuku/luci-theme-argon/issues\n\n\tluci-theme-material:\n\tCopyright 2015 Lutty Yang <lutty@wcan.in>\n\n\tArgon Theme\n\thttps://demos.creative-tim.com/argon-dashboard/index.html\n\n\tLicensed to the public under the Apache License 2.0\n-%>\n\n<%\n\tlocal sys = require \"luci.sys\"\n\tlocal util = require \"luci.util\"\n\tlocal http = require \"luci.http\"\n\tlocal disp = require \"luci.dispatcher\"\n\tlocal ver = require \"luci.version\"\n\n\tlocal boardinfo = util.ubus(\"system\", \"board\")\n\n\tlocal node = disp.context.dispatched\n\n\tlocal fs = require \"nixio.fs\"\n\tlocal nutil = require \"nixio.util\"\n\tlocal uci = require 'luci.model.uci'.cursor()\n\n\t-- send as HTML5\n\thttp.prepare_content(\"text/html\")\n\n\tmath.randomseed(tonumber(tostring(os.time()):reverse():sub(1, 9)))\n\n\t-- Custom settings\n\tlocal mode = 'normal'\n\tlocal dark_css = fs.readfile('/www/luci-static/routerich/css/dark.css')\n\tlocal bar_color = '#5e72e4'\n\tlocal primary, dark_primary, blur_radius, blur_radius_dark, blur_opacity\n\tif fs.access('/etc/config/routerich') then\n\t\tprimary = uci:get_first('routerich', 'global', 'primary')\n\t\tdark_primary = uci:get_first('routerich', 'global', 'dark_primary')\n\t\tblur_radius = uci:get_first('routerich', 'global', 'blur')\n\t\tblur_radius_dark = uci:get_first('routerich', 'global', 'blur_dark')\n\t\tblur_opacity = uci:get_first('routerich', 'global', 'transparency')\n\t\tblur_opacity_dark = uci:get_first('routerich', 'global', 'transparency_dark')\n\t\tmode = uci:get_first('routerich', 'global', 'mode')\n\t\tbar_color = mode == 'dark' and dark_primary or primary\n\tend\n-%>\n<!DOCTYPE html>\n<html lang=\"<%=luci.i18n.context.lang%>\">\n\n<head>\n\t<meta charset=\"utf-8\">\n\t<title>\n\t\t<%=striptags( (boardinfo.hostname or \"?\") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %>\n\t\t- LuCI</title>\n\t<meta content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\" name=\"viewport\" />\n\t<meta name=\"format-detection\" content=\"telephone=no, email=no\" />\n\t<meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\t<meta name=\"mobile-web-app-capable\" content=\"yes\">\n\t<meta name=\"x5-fullscreen\" content=\"true\">\n\t<meta name=\"full-screen\" content=\"yes\">\n\t<meta name=\"x5-page-mode\" content=\"app\">\n\t<meta name=\"browsermode\" content=\"application\">\n\t<meta name=\"msapplication-tap-highlight\" content=\"no\">\n\t<meta name=\"msapplication-TileColor\" content=\"<%=bar_color%>\">\n\t<meta name=\"application-name\" content=\"<%=striptags( (boardinfo.hostname or \"?\") ) %> - LuCI\">\n\t<meta name=\"apple-mobile-web-app-title\" content=\"<%=striptags( (boardinfo.hostname or \"?\") ) %> - LuCI\">\n\t<link rel=\"apple-touch-icon\" sizes=\"60x60\" href=\"<%=media%>/icon/apple-icon-60x60.png\">\n\t<link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"<%=media%>/icon/apple-icon-72x72.png\">\n\t<link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"<%=media%>/icon/apple-icon-144x144.png\">\n\t<link rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"<%=media%>/icon/android-icon-192x192.png\">\n\t<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"<%=media%>/icon/favicon-32x32.png\">\n\t<link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"<%=media%>/icon/favicon-96x96.png\">\n\t<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"<%=media%>/icon/favicon-16x16.png\">\n\t<link rel=\"manifest\" href=\"<%=media%>/icon/manifest.json\" crossorigin=\"use-credentials\">\n\t<meta name=\"msapplication-TileColor\" content=\"<%=bar_color%>\">\n\t<meta name=\"msapplication-TileImage\" content=\"<%=media%>/icon/ms-icon-144x144.png\">\n\t<meta name=\"theme-color\" content=\"<%=bar_color%>\">\n\t<link rel=\"stylesheet\" href=\"<%=media%>/css/cascade.css?v=1.0.0\">\n\t<link rel=\"stylesheet\" href=\"<%=media%>/css/routerich.css?v=1.0.0\">\n\t<style title=\"text/css\">\n\t\t<% if mode == 'normal' then %>\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\t<%=dark_css%>\n\t\t\t}\n\t\t<% elseif mode == 'dark' then %>\n\t\t\t<%=dark_css%>\n\t\t<% end -%>\n\t\t<% if fs.access('/etc/config/routerich') then %>\n\t\t:root {\n\t\t\t--primary: <%=primary%>;\n\t\t\t--dark-primary: <%=dark_primary%>;\n\t\t\t--blur-radius:<%=blur_radius%>px;\n\t\t\t--blur-opacity:<%=blur_opacity%>;\n\t\t\t--blur-radius-dark:<%=blur_radius_dark%>px;\n\t\t\t--blur-opacity-dark:<%=blur_opacity_dark%>;\n\t\t}\n\t\t<% end -%>\n\t</style>\n\t<link rel=\"shortcut icon\" href=\"<%=media%>/favicon.ico\">\n\t<% if node and node.css then %>\n\t<link rel=\"stylesheet\" href=\"<%=resource%>/<%=node.css%>\">\n\t<% end -%>\n\t<% if css then %>\n\t<style title=\"text/css\">\n\t\t<%=css %>\n\t</style>\n\t<% end -%>\n\t<script src=\"<%=media%>/js/jquery.min.js?v=3.5.1\"></script>\n\n</head>\n\n<body>\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/luasrc/view/themes/routerich/out_header_login.htm",
    "content": "<%#\n Copyright 2008 Steven Barth <steven@midlink.org>\n Copyright 2008-2019 Jo-Philipp Wich <jo@mein.io>\n Licensed to the public under the Apache License 2.0.\n-%>\n\n<%\n\tlocal ver = require \"luci.version\"\n\n\tif not luci.dispatcher.context.template_header_sent then\n\t\tinclude(\"themes/\" .. theme .. \"/header_login\")\n\t\tluci.dispatcher.context.template_header_sent = true\n\tend\n%>\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/luasrc/view/themes/routerich/sysauth.htm",
    "content": "<%#\n\tArgon is a clean HTML5 theme for LuCI. It is based on luci-theme-bootstrap and MUI and Argon Template\n\n\tluci-theme-argon\n\tCopyright 2020 Jerryk <jerrykuku@gmail.com>\n\n\tHave a bug? Please create an issue here on GitHub!\n\thttps://github.com/jerrykuku/luci-theme-argon/issues\n\n\tluci-theme-bootstrap:\n\tCopyright 2008 Steven Barth <steven@midlink.org>\n\tCopyright 2008-2016 Jo-Philipp Wich <jow@openwrt.org>\n\tCopyright 2012 David Menting <david@nut-bolt.nl>\n\n\tMUI:\n\thttps://github.com/muicss/mui\n\n\tArgon Theme\n\thttps://demos.creative-tim.com/argon-dashboard/index.html\n\n\tLicensed to the public under the Apache License 2.0\n-%>\n\n<%+themes/routerich/out_header_login%>\n<%\n\tlocal util\t\t= require \"luci.util\"\n\tlocal fs\t\t= require \"nixio.fs\"\n\tlocal nutil\t\t= require \"nixio.util\"\n\tlocal json\t\t= require \"luci.jsonc\"\n\tlocal sys\t\t= require \"luci.sys\"\n\tlocal uci\t\t= require 'luci.model.uci'.cursor()\n\n\t-- Fetch Local Background Media\n\n\tlocal function glob(...)\n\t\tlocal iter, code, msg = fs.glob(...)\n\t\tif iter then\n\t\t\treturn nutil.consume(iter)\n\t\telse\n\t\t\treturn nil, code, msg\n\t\tend\n\tend\n\n\n\tlocal imageTypes = \" jpg png gif webp \"\n\tlocal videoTypes = \" mp4 webm \"\n\tlocal allTypes = imageTypes .. videoTypes\n\tlocal function fetchMedia(path, themeDir)\n\t\tlocal backgroundTable = {}\n\t\tlocal backgroundCount = 0\n\t\tfor i, f in ipairs(glob(path)) do\n\t\t\tattr = fs.stat(f)\n\t\t\tif attr then\n\t\t\t\tlocal ext = fs.basename(f):match(\".+%.(%w+)$\")\n\t\t\t\tif ext ~= nil then\n\t\t\t\t\text = ext:lower()\n\t\t\t\tend\n\t\t\t\tif ext ~= nil and string.match(allTypes, \" \"..ext..\" \") ~= nil then\n\t\t\t\t\tlocal bg = {}\n\t\t\t\t\tbg.type = ext\n\t\t\t\t\tbg.url = themeDir .. fs.basename(f)\n\t\t\t\t\ttable.insert(backgroundTable, bg)\n\t\t\t\t\tbackgroundCount = backgroundCount + 1\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\t\treturn backgroundTable, backgroundCount\n\tend\n\tlocal function selectBackground(themeDir)\n\t\tlocal bgUrl\t\t\t= media .. \"/img/bg1.jpg\"\n\t\tlocal backgroundType\t= \"Image\"\n\t\tlocal mimeType\t\t\t= \"\"\n\n\t\tif fs.access(\"/etc/config/routerich\") then\n\t\t\tlocal online_wallpaper = uci:get_first('routerich', 'global', 'online_wallpaper') or (uci:get_first('routerich', 'global', 'bing_background') == '1' and 'bing')\n\t\t\tif (online_wallpaper and online_wallpaper ~= \"none\") then\n\t\t\t\tlocal picurl = sys.exec(\"/usr/libexec/routerich/online_wallpaper\")\n\t\t\t\tif (picurl and picurl ~= '') then\n\t\t\t\t\treturn picurl, \"Image\", \"\"\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\n\t\tlocal backgroundTable, backgroundCount = fetchMedia(\"/www\" .. themeDir .. \"*\", themeDir)\n\t\tif ( backgroundCount > 0 ) then\n\t\t\tlocal currentBg = backgroundTable[math.random(1, backgroundCount)]\n\t\t\tbgUrl\t\t\t= currentBg.url\n\t\t\tif (string.match(videoTypes, \" \"..currentBg.type..\" \") ~= nil) then\n\t\t\t\tbackgroundType\t= \"Video\"\n\t\t\t\tmimeType\t\t= \"video/\" .. currentBg.type\n\t\t\tend\n\t\tend\n\n\t\treturn bgUrl, backgroundType, mimeType\n\tend\n\n\tlocal boardinfo\t\t\t= util.ubus(\"system\", \"board\")\n\tlocal themeDir\t\t\t= media .. \"/background/\"\n\tlocal bgUrl, backgroundType, mimeType = selectBackground(themeDir)\n%>\n<!-- Login Page Start -->\n<div class=\"login-page\">\n\t<% if ( backgroundType == \"Video\" ) then %>\n\t<!-- Video Player Start -->\n\t<div class=\"video\">\n\t\t<video autoplay loop muted id=\"video\">\n\t\t\t<source src=\"<%=bgUrl%>\" type=\"<%=mimeType%>\">\n\t\t</video>\n\t</div>\n\t<div class=\"volume-control mute\"></div>\n\t<script>\n\t\t$(\".volume-control\").click(function(){\n\t\t\tif($(this).hasClass(\"mute\")){\n\t\t\t\t$(this).removeClass(\"mute\")\n\t\t\t\t$(\"#video\").prop('muted', false);\n\t\t\t}else{\n\t\t\t\t$(this).addClass(\"mute\")\n\t\t\t\t$(\"#video\").prop('muted', true);\n\t\t\t}\n\t\t})\n\t</script>\n\t<!-- Video Player End -->\n\t<% else %>\n\t<!-- Image Background Start -->\n\t<div class=\"main-bg\" id=\"main-bg\" style=\"background-image:url(<%=bgUrl%>)\"></div>\n\t<!-- Image Background End -->\n\t<% end %>\n\t<!-- Login Container Start -->\n\t<div class=\"login-container\">\n\t\t<div class=\"login-form\">\n\t\t\t<!-- Logo Start -->\n\t\t\t<a class=\"brand\" href=\"/\">\n\t\t\t\t<span class=\"brand-text\"><%=striptags( (boardinfo.hostname or \"?\") ) %></span>\n\t\t\t</a>\n\t\t\t<!-- Logo End -->\n\t\t\t<!-- Login Form Start -->\n\t\t\t<form class=\"form-login\" method=\"post\" action=\"<%=pcdata(luci.http.getenv(\"REQUEST_URI\"))%>\">\n\n\t\t\t\t<%- if fuser then %>\n\t\t\t\t<div class=\"errorbox\"><%:Invalid username and/or password! Please try again.%></div>\n\t\t\t\t<% end -%>\n\n\t\t\t\t<div class=\"input-container\">\n\t\t\t\t\t<div class=\"input-group user-icon\">\n\t\t\t\t\t\t<input class=\"cbi-input-user\" id=\"cbi-input-user\" type=\"text\" name=\"luci_username\" value=\"<%=duser%>\" />\n\t\t\t\t\t\t<label class=\"border\" for=\"cbi-input-user\"></label>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"input-group pass-icon\">\n\t\t\t\t\t\t<input class=\"cbi-input-password\" id=\"cbi-input-password\" type=\"password\" name=\"luci_password\" />\n\t\t\t\t\t\t<label class=\"border\" for=\"cbi-input-password\"></label>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<input type=\"submit\" value=\"<%:Log in%>\" class=\"cbi-button cbi-button-apply\" />\n\t\t\t\t</div>\n\t\t\t</form>\n\t\t\t<!-- Login Form End -->\n\t\t\t<script type=\"text/javascript\">//<![CDATA[\n\t\t\t\tvar input = document.getElementsByName('luci_password')[0];\n\t\t\t\tif (input)\n\t\t\t\t\tinput.focus();\n\t\t\t//]]></script>\n<%+themes/routerich/footer_login%>\n"
  },
  {
    "path": "luci/themes/luci-theme-routerich/root/etc/uci-defaults/30_luci-theme-routerich",
    "content": "#!/bin/sh\n\nif [ \"$PKG_UPGRADE\" != 1 ]; then\n\tuci get luci.themes.Routerich >/dev/null 2>&1 || \\\n\tuci batch <<-EOF\n\t\tset luci.themes.Routerich=/luci-static/routerich\n\t\tset luci.main.mediaurlbase=/luci-static/routerich\n\t\tcommit luci\n\tEOF\nfi\n\nexit 0\n"
  },
  {
    "path": "luci/themes/luci-theme-teleofis/Makefile",
    "content": "#\n# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>\n#\n# This is free software, licensed under the Apache License, Version 2.0 .\n#\n\ninclude $(TOPDIR)/rules.mk\n\nLUCI_TITLE:=Bootstrap Teleofis Theme\nLUCI_DEPENDS:= +luci-lua-runtime\n\nPKG_LICENSE:=Apache-2.0\n\ninclude $(TOPDIR)/feeds/luci/luci.mk\n\n# call BuildPackage - OpenWrt buildroot signature\n"
  },
  {
    "path": "luci/themes/luci-theme-teleofis/htdocs/luci-static/resources/menu-teleofis.js",
    "content": "'use strict';\n'require baseclass';\n'require ui';\n\nreturn baseclass.extend({\n\t__init__: function() {\n\t\tui.menu.load().then(L.bind(this.render, this));\n\t},\n\n\trender: function(tree) {\n\t\tvar node = tree,\n\t\t    url = '';\n\n\t\tthis.renderModeMenu(tree);\n\n\t\tif (L.env.dispatchpath.length >= 3) {\n\t\t\tfor (var i = 0; i < 3 && node; i++) {\n\t\t\t\tnode = node.children[L.env.dispatchpath[i]];\n\t\t\t\turl = url + (url ? '/' : '') + L.env.dispatchpath[i];\n\t\t\t}\n\n\t\t\tif (node)\n\t\t\t\tthis.renderTabMenu(node, url);\n\t\t}\n\n\t\tdocument.addEventListener('poll-start', this.handleBodyMargin);\n\t\tdocument.addEventListener('poll-stop', this.handleBodyMargin);\n\t\tdocument.addEventListener('uci-new-changes', this.handleBodyMargin);\n\t\tdocument.addEventListener('uci-clear-changes', this.handleBodyMargin);\n\t\twindow.addEventListener('resize', this.handleBodyMargin);\n\n\t\tthis.handleBodyMargin();\n\t},\n\n\trenderTabMenu: function(tree, url, level) {\n\t\tvar container = document.querySelector('#tabmenu'),\n\t\t    ul = E('ul', { 'class': 'tabs' }),\n\t\t    children = ui.menu.getChildren(tree),\n\t\t    activeNode = null;\n\n\t\tfor (var i = 0; i < children.length; i++) {\n\t\t\tvar isActive = (L.env.dispatchpath[3 + (level || 0)] == children[i].name),\n\t\t\t    activeClass = isActive ? ' active' : '',\n\t\t\t    className = 'tabmenu-item-%s %s'.format(children[i].name, activeClass);\n\n\t\t\tul.appendChild(E('li', { 'class': className }, [\n\t\t\t\tE('a', { 'href': L.url(url, children[i].name) }, [ _(children[i].title) ] )]));\n\n\t\t\tif (isActive)\n\t\t\t\tactiveNode = children[i];\n\t\t}\n\n\t\tif (ul.children.length == 0)\n\t\t\treturn E([]);\n\n\t\tcontainer.appendChild(ul);\n\t\tcontainer.style.display = '';\n\n\t\tif (activeNode)\n\t\t\tthis.renderTabMenu(activeNode, url + '/' + activeNode.name, (level || 0) + 1);\n\n\t\treturn ul;\n\t},\n\n\trenderMainMenu: function(tree, url, level) {\n\t\tvar ul = level ? E('ul', { 'class': 'dropdown-menu' }) : document.querySelector('#topmenu'),\n\t\t    children = ui.menu.getChildren(tree);\n\n\t\tif (children.length == 0 || level > 1)\n\t\t\treturn E([]);\n\n\t\tfor (var i = 0; i < children.length; i++) {\n\t\t\tvar submenu = this.renderMainMenu(children[i], url + '/' + children[i].name, (level || 0) + 1),\n\t\t\t    subclass = (!level && submenu.firstElementChild) ? 'dropdown' : null,\n\t\t\t    linkclass = (!level && submenu.firstElementChild) ? 'menu' : null,\n\t\t\t    linkurl = submenu.firstElementChild ? '#' : L.url(url, children[i].name);\n\n\t\t\tvar li = E('li', { 'class': subclass }, [\n\t\t\t\tE('a', { 'class': linkclass, 'href': linkurl }, [ _(children[i].title) ]),\n\t\t\t\tsubmenu\n\t\t\t]);\n\n\t\t\tul.appendChild(li);\n\t\t}\n\n\t\tul.style.display = '';\n\n\t\treturn ul;\n\t},\n\n\trenderModeMenu: function(tree) {\n\t\tvar ul = document.querySelector('#modemenu'),\n\t\t    children = ui.menu.getChildren(tree);\n\n\t\tfor (var i = 0; i < children.length; i++) {\n\t\t\tvar isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[0] : i == 0);\n\n\t\t\tul.appendChild(E('li', { 'class': isActive ? 'active' : null }, [\n\t\t\t\tE('a', { 'href': L.url(children[i].name) }, [ _(children[i].title) ]),\n\t\t\t\t' ',\n\t\t\t\tE('span', { 'class': 'divider' }, [ '|' ])\n\t\t\t]));\n\n\t\t\tif (isActive)\n\t\t\t\tthis.renderMainMenu(children[i], children[i].name);\n\t\t}\n\n\t\tif (ul.children.length > 1)\n\t\t\tul.style.display = '';\n\t},\n\n\thandleBodyMargin: function(ev) {\n\t\tvar body = document.querySelector('body'),\n\t\t    head = document.querySelector('header');\n\n\t\tbody.style.marginTop = head.offsetHeight + 'px';\n\t}\n});\n"
  },
  {
    "path": "luci/themes/luci-theme-teleofis/htdocs/luci-static/teleofis/cascade.css",
    "content": "/*!\n * LuCI Bootstrap Theme\n * Copyright 2012 Nut & Bolt\n * By David Menting <david@nut-bolt.nl>\n * Based on Bootstrap v1.4.0\n *\n * Copyright 2011 Twitter, Inc\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world @twitter by @mdo and @fat.\n */\n/* Reset.less\n * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here\tthat cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc).\n * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */\n\n:root {\n\t--background-color-delta-l-sign: -1;\n\t--background-color-h: 0;\n\t--background-color-s: 0%;\n\t--background-color-l: 100%;\n\n\t--background-color-high-hsl:\n\t\tvar(--background-color-h),\n\t\tvar(--background-color-s),\n\t\tvar(--background-color-l);\n\t--background-color-high: hsl(var(--background-color-high-hsl));\n\n\t--background-color-medium-hsl:\n\t\tvar(--background-color-h),\n\t\tvar(--background-color-s),\n\t\tcalc(var(--background-color-l) + var(--background-color-delta-l-sign) * calc(6 / 255 * 100%));\n\t--background-color-medium: hsl(var(--background-color-medium-hsl));\n\n\t--background-color-low-hsl:\n\t\tvar(--background-color-h),\n\t\tvar(--background-color-s),\n\t\tcalc(var(--background-color-l) + var(--background-color-delta-l-sign) * calc(10 / 255 * 100%));\n\t--background-color-low: hsl(var(--background-color-low-hsl));\n\n\t--text-color-delta-l-sign: 1;\n\t--text-color-h: 0;\n\t--text-color-s: 0%;\n\t--text-color-l: 0%;\n\n\t--text-color-highest-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tvar(--text-color-l);\n\t--text-color-highest: hsl(var(--text-color-highest-hsl));\n\n\t--text-color-high-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tcalc(var(--text-color-l) + var(--text-color-delta-l-sign) * calc(64 / 255 * 100%));\n\t--text-color-high: hsl(var(--text-color-high-hsl));\n\n\t--text-color-medium-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tcalc(var(--text-color-l) + var(--text-color-delta-l-sign) * calc(128 / 255 * 100%));\n\t--text-color-medium: hsl(var(--text-color-medium-hsl));\n\n\t--text-color-low-hsl:\n\t\tvar(--text-color-h),\n\t\tvar(--text-color-s),\n\t\tcalc(var(--text-color-l) + var(--text-color-delta-l-sign) * calc(191 / 255 * 100%));\n\t--text-color-low: hsl(var(--text-color-low-hsl));\n\n\t--border-color-delta-l-sign: -1;\n\t--border-color-h: var(--background-color-h);\n\t--border-color-s: var(--background-color-s);\n\t--border-color-l: var(--background-color-l);\n\n\t--border-color-high-hsl:\n\t\tvar(--border-color-h),\n\t\tvar(--border-color-s),\n\t\tcalc(var(--border-color-l) + var(--border-color-delta-l-sign) * calc(51 / 255 * 100%));\n\t--border-color-high: hsl(var(--border-color-high-hsl));\n\n\t--border-color-medium-hsl:\n\t\tvar(--border-color-h),\n\t\tvar(--border-color-s),\n\t\tcalc(var(--border-color-l) + var(--border-color-delta-l-sign) * calc(34 / 255 * 100%));\n\t--border-color-medium: hsl(var(--border-color-medium-hsl));\n\n\t--border-color-low-hsl:\n\t\tvar(--border-color-h),\n\t\tvar(--border-color-s),\n\t\tcalc(var(--border-color-l) + var(--border-color-delta-l-sign) * calc(17 / 255 * 100%));\n\t--border-color-low: hsl(var(--border-color-low-hsl));\n\n\t--primary-color-high: #1976d2;\n\t--primary-color-medium: #1564c0;\n\t--primary-color-low: #b00000;\n\t--on-primary-color: var(--background-color-high);\n\n\t--error-color-high-rgb: 246, 43, 18;\n\t--error-color-high: rgb(var(--error-color-high-rgb));\n\t--error-color-medium: #e8210d;\n\t--error-color-low: #d00000;\n\t--on-error-color: var(--background-color-high);\n\n\t--success-color-high-rgb: 0, 172, 89;\n\t--success-color-high: rgb(var(--success-color-high-rgb));\n\t--success-color-medium: #009a4c;\n\t--success-color-low: #007936;\n\t--on-success-color: var(--background-color-high);\n\n\t--warn-color-high: #efbd0b;\n\t--warn-color-medium: #f0c629;\n\t--warn-color-low: #f2d24f;\n\t--on-warn-color: var(--text-color-highest);\n\n\t--disabled-opacity: .7;\n\n\tcolor-scheme: light;\n}\n\n* {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbox-sizing: border-box;\n}\n\nabbr[title], acronym[title] {\n\tborder-bottom: 1px dotted;\n\tfont-weight: inherit;\n\tcursor: help;\n}\n\ntable {\n\tborder-collapse: collapse;\n\tborder-spacing: 0;\n}\n\nol, ul {\n\tlist-style: none;\n}\n\nhtml {\n\tfont-size: 100%;\n\t-webkit-text-size-adjust: 100%;\n\t-ms-text-size-adjust: 100%;\n}\n\na:focus {\n\toutline: none;\n}\n\na:hover, a:active {\n\toutline: none;\n}\n\nfooter,\nheader,\nnav,\nsection {\n\tdisplay: block;\n}\n\nsub, sup {\n\tfont-size: 75%;\n\tline-height: 0;\n\tposition: relative;\n\tvertical-align: baseline;\n}\n\nsup {\n\ttop: -0.5em;\n}\n\nsub {\n\tbottom: -0.25em;\n}\n\nimg {\n\t-ms-interpolation-mode: bicubic;\n}\n\nbutton,\ninput,\nselect,\noption,\ntextarea {\n\tfont-size: 100%;\n\tmargin: 0;\n\tbox-sizing: border-box;\n\tvertical-align: baseline;\n\tline-height: normal;\n}\n\nbutton::-moz-focus-inner, input::-moz-focus-inner {\n\tborder: 0;\n\tpadding: 0;\n}\n\nbutton,\ninput[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n\tcursor: pointer;\n\t-webkit-appearance: button;\n\tword-break: break-all;\n}\n\nbutton[disabled],\ninput[type=\"button\"][disabled],\ninput[type=\"reset\"][disabled],\ninput[type=\"submit\"][disabled] {\n\topacity: 0.7;\n}\n\ninput[type=\"search\"] {\n\t-webkit-appearance: textfield;\n\tbox-sizing: content-box;\n}\n\ninput[type=\"search\"]::-webkit-search-decoration {\n\t-webkit-appearance: none;\n}\n\ntextarea {\n\toverflow: auto;\n\tvertical-align: top;\n}\n\n/*\n * Scaffolding\n * Basic and global styles for generating a grid system, structural layout, and page templates\n * ------------------------------------------------------------------------------------------- */\nbody {\n\tbackground-color: #fff;\n\tfont-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n\tfont-size: 13px;\n\tfont-weight: normal;\n\tline-height: 18px;\n\tcolor: #404040;\n\tpadding: 18px 5px 5px 5px;\n\tmargin-top: 40px;\n}\n\n.container {\n\twidth: 100%;\n\tmax-width: 940px;\n\tmargin-left: auto;\n\tmargin-right: auto;\n\tzoom: 1;\n}\n\n.container:before, .container:after {\n\tdisplay: table;\n\tcontent: \"\";\n\tzoom: 1;\n}\n\n.container:after {\n\tclear: both;\n}\n\na {\n\tcolor: #600;\n\ttext-decoration: none;\n\tline-height: inherit;\n\tfont-weight: inherit;\n}\n\na:hover {\n\tcolor: #B60000a;\n\ttext-decoration: underline;\n}\n\n.pull-right {\n\tfloat: right;\n}\n\n.pull-left {\n\tfloat: left;\n}\n\n.nowrap {\n\twhite-space: nowrap;\n}\n\n/* Typography.less\n * Headings, body text, lists, code, and more for a versatile and durable typography system\n * ---------------------------------------------------------------------------------------- */\np,\n.cbi-map-descr,\n.cbi-section-descr,\n.table .tr.cbi-section-table-descr .th {\n\tfont-size: 13px;\n\tfont-weight: normal;\n\tline-height: 18px;\n\tmargin-bottom: 9px;\n}\n\np small {\n\tfont-size: 11px;\n\tcolor: #bfbfbf;\n}\n\nh1,\nh2,\nh3, legend,\nh4,\nh5,\nh6 {\n\tfont-weight: bold;\n\tcolor: #600;\n}\n\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small {\n\tcolor: #bfbfbf;\n}\n\nh1 {\n\tmargin-bottom: 18px;\n\tfont-size: 30px;\n\tline-height: 36px;\n}\n\nh1 small {\n\tfont-size: 18px;\n}\n\nh2 {\n\tfont-size: 24px;\n\tline-height: 36px;\n}\n\nh2 small {\n\tfont-size: 14px;\n}\n\nh3, legend,\nh4,\nh5,\nh6 {\n\tline-height: 36px;\n}\n\nh3, legend {\n\tfont-size: 18px;\n}\n\nh3 small {\n\tfont-size: 14px;\n}\n\nh4 {\n\tfont-size: 16px;\n}\n\nh4 small {\n\tfont-size: 12px;\n}\n\nh5 {\n\tfont-size: 14px;\n}\n\nh6 {\n\tfont-size: 13px;\n\tcolor: #bfbfbf;\n\ttext-transform: uppercase;\n}\n\nul, ol {\n\tmargin: 0 0 18px 25px;\n}\n\nul ul,\nul ol,\nol ol,\nol ul {\n\tmargin-bottom: 0;\n}\n\nul {\n\tlist-style: disc;\n}\n\nol {\n\tlist-style: decimal;\n}\n\nli {\n\tline-height: 18px;\n\tcolor: #808080;\n}\n\nul.unstyled {\n\tlist-style: none;\n\tmargin-left: 0;\n}\n\ndl {\n\tmargin-bottom: 18px;\n}\n\ndl dt, dl dd {\n\tline-height: 18px;\n}\n\ndl dt {\n\tfont-weight: bold;\n}\n\ndl dd {\n\tmargin-left: 9px;\n}\n\nhr {\n\tmargin: 20px 0 19px;\n\tborder: 0;\n\tborder-bottom: 1px solid #eee;\n}\n\nstrong {\n\tfont-style: inherit;\n\tfont-weight: bold;\n}\n\nem {\n\tfont-style: italic;\n\tfont-weight: inherit;\n\tline-height: inherit;\n}\n\nsmall { font-size: 0.9em }\n\naddress {\n\tdisplay: block;\n\tline-height: 18px;\n\tmargin-bottom: 18px;\n}\n\ncode, pre {\n\tpadding: 0 3px 2px;\n\tfont-family: Monaco, Andale Mono, Courier New, monospace;\n\tfont-size: 12px;\n\tborder-radius: 3px;\n}\n\ncode {\n\tbackground-color: #fee9cc;\n\tcolor: rgba(0, 0, 0, 0.75);\n\tpadding: 1px 3px;\n}\n\npre {\n\tbackground-color: #f5f5f5;\n\tdisplay: block;\n\tpadding: 8.5px;\n\tmargin: 0 0 18px;\n\tline-height: 18px;\n\tfont-size: 12px;\n\tborder: 1px solid #ccc;\n\tborder: 1px solid rgba(0, 0, 0, 0.15);\n\tborder-radius: 3px;\n\twhite-space: pre;\n\twhite-space: pre-wrap;\n\tword-wrap: break-word;\n}\n\n/* Forms.less\n * Base styles for various input types, form layouts, and states\n * ------------------------------------------------------------- */\nform {\n\tmargin-bottom: 18px;\n}\n\nfieldset {\n\tmargin-bottom: 9px;\n\tpadding-top: 9px;\n}\n\nfieldset legend {\n\tdisplay: block;\n\tfont-size: 19.5px;\n\tline-height: 1;\n\tcolor: #404040;\n\tpadding-top: 20px;\n\n}\nform .cbi-tab-descr {\n\tline-height: 18px;\n\tmargin-bottom: 18px;\n}\n\nform .clearfix,\n.cbi-value {\n\tmargin-bottom: 18px;\n\tzoom: 1;\n}\n\nform .clearfix:before, form .clearfix:after,\n.cbi-value:before, .cbi-value:after  {\n\tdisplay: table;\n\tcontent: \"\";\n\tzoom: 1;\n}\n\nform .clearfix:after,\n.cbi-value:after {\n\tclear: both;\n}\n\nlabel,\ninput,\nbutton,\nselect,\ntextarea {\n\tfont-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n\tfont-size: 13px;\n\tfont-weight: normal;\n\tline-height: normal;\n}\n\nform .input,\n.cbi-value-field {\n\tmargin-left: 200px;\n}\n\n.cbi-value label.cbi-value-title {\n\tpadding-top: 6px;\n\tfont-size: 13px;\n\tline-height: 18px;\n\tfloat: left;\n\twidth: 180px;\n\ttext-align: right;\n\tcolor: #404040;\n}\n\ninput[type=checkbox], input[type=radio] {\n\tcursor: pointer;\n}\n\nlabel > input[type=\"checkbox\"],\nlabel > input[type=\"radio\"] {\n\tvertical-align: bottom;\n\tmargin: 0;\n}\n\ninput,\ntextarea,\nselect,\n.cbi-dropdown:not(.btn):not(.cbi-button),\n.uneditable-input {\n\tdisplay: inline-block;\n\twidth: 210px;\n\tpadding: 4px;\n\tfont-size: 13px;\n\tline-height: 18px;\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n}\n\ninput,\nselect,\n.cbi-dropdown:not(.btn):not(.cbi-button),\n.uneditable-input {\n\theight: 30px;\n}\n\n.uneditable-input {\n\tcolor: #808080;\n}\n\n.cbi-dropdown:not(.btn):not(.cbi-button),\n.cbi-dynlist {\n\tmin-width: 210px;\n\tmax-width: 400px;\n\twidth: auto;\n}\n\n.cbi-dynlist {\n\theight: auto;\n\tmin-height: 30px;\n\tdisplay: inline-flex;\n\tflex-direction: column;\n}\n\n.cbi-dynlist > .item {\n\tmargin-bottom: 4px;\n\tbox-shadow: 0 0 2px #ccc;\n\tbackground: #fff;\n\tpadding: 2px 2em 2px 4px;\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n\tposition: relative;\n\tpointer-events: none;\n\toverflow: hidden;\n\tword-break: break-all;\n}\n\n.cbi-dynlist > .item::after {\n\tcontent: \"×\";\n\tposition: absolute;\n\tdisplay: inline-flex;\n\talign-items: center;\n\ttop: -1px;\n\tright: -1px;\n\tbottom: -1px;\n\tpadding: 0 6px;\n\tborder: 1px solid #ccc;\n\tborder-radius: 0 3px 3px 0;\n\tfont-weight: bold;\n\tcolor: #c44;\n\tpointer-events: auto;\n}\n\n.cbi-dynlist > .add-item {\n\tdisplay: flex;\n}\n\n.cbi-dynlist > .add-item > input,\n.cbi-dynlist > .add-item > button {\n\tflex: 1 1 auto;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\nselect {\n\tpadding: initial;\n\tbackground: #fff;\n\tbox-shadow: inset 0 -1px 3px rgba(0, 0, 0, 0.1);\n}\n\ninput[type=checkbox], input[type=radio] {\n\twidth: auto;\n\theight: auto;\n\tpadding: 0;\n\tmargin: 3px 0;\n\t*margin-top: 0;\n\t/* IE6-7 */\n\n\tline-height: normal;\n\tborder: none;\n}\n\ninput[type=file] {\n\tbackground-color: #fff;\n\tpadding: initial;\n\tborder: initial;\n\tline-height: initial;\n\tbox-shadow: none;\n\twidth: auto !important;\n}\n\ninput[type=button], input[type=reset], input[type=submit] {\n\twidth: auto;\n\theight: auto;\n}\n\nselect[multiple] {\n\theight: inherit;\n\tbackground-color: #fff;\n}\n\n.td > input[type=text],\n.td > input[type=password],\n.td > select,\n.td > .cbi-dropdown:not(.btn):not(.cbi-button),\n.cbi-dynlist > .add-item > .cbi-dropdown {\n\twidth: 100%;\n}\n\n.uneditable-input {\n\tbackground-color: #fff;\n\tdisplay: block;\n\tborder-color: #eee;\n\tbox-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);\n\tcursor: not-allowed;\n}\n\n::-moz-placeholder {\n\tcolor: #bfbfbf;\n}\n\n::-webkit-input-placeholder {\n\tcolor: #bfbfbf;\n}\n\n.item::after, .btn, .cbi-button, input, button, textarea {\n\ttransition: border linear 0.2s, box-shadow linear 0.2s;\n\tbox-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n.item:hover::after,\n.btn:hover, .cbi-button:hover, button:hover,\ninput:focus, textarea:focus {\n\toutline: 0;\n\tborder-color: rgba(210, 73, 45, 0.8) !important;\n\tbox-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(210, 73, 45, 0.6);\n\ttext-decoration: none;\n}\n\ninput[type=file]:focus, input[type=checkbox]:focus, select:focus {\n\tbox-shadow: none;\n\toutline: 1px dotted #666;\n}\n\ninput[disabled],\nbutton[disabled],\nselect[disabled],\ntextarea[disabled],\ninput[readonly],\nbutton[readonly],\nselect[readonly],\ntextarea[readonly] {\n\tbackground-color: #f5f5f5;\n\tborder-color: #ddd;\n\tpointer-events: none;\n\tcursor: default;\n}\n\nselect[readonly],\ntextarea[readonly] {\n\tpointer-events: auto;\n\tcursor: auto;\n}\n\n.cbi-optionals,\n.cbi-section-create {\n\tpadding: 0 0 10px 10px;\n}\n\n.cbi-section-create {\n\tmargin: -3px;\n\tdisplay: inline-flex;\n\talign-items: center;\n}\n\n.cbi-section-create > * {\n\tmargin: 3px;\n\tflex: 1 1 auto;\n}\n\n.cbi-section-create > * > input {\n\twidth: 100%;\n}\n\n.actions,\n.cbi-page-actions {\n\tbackground: #f5f5f5;\n\tmargin-bottom: 18px;\n\tpadding: 17px 20px 18px 17px;\n\tborder-top: 1px solid #ddd;\n\tborder-radius: 0 0 3px 3px;\n\ttext-align: right;\n}\n\n.actions .secondary-action,\n.cbi-page-actions .secondary-action{\n\tfloat: right;\n}\n\n.actions .secondary-action a,\n.cbi-page-actions .secondary-action a {\n\tline-height: 30px;\n}\n\n.actions .secondary-action a:hover,\n.cbi-page-actions .secondary-action a:hover {\n\ttext-decoration: underline;\n}\n\n.cbi-page-actions > form {\n\tdisplay: inline;\n\tmargin: 0;\n}\n\n.help-inline, .help-block {\n\tfont-size: 13px;\n\tline-height: 18px;\n\tcolor: #bfbfbf;\n}\n\n.help-inline {\n\tpadding-left: 5px;\n\t*position: relative;\n\t/* IE6-7 */\n\n\t*top: -5px;\n\t/* IE6-7 */\n\n}\n\n.help-block {\n\tdisplay: block;\n\tmax-width: 600px;\n}\n\n/*\n * Tables.less\n * Tables for, you guessed it, tabular data\n * ---------------------------------------- */\n.tr { display: table-row; }\n.table[width=\"33%\"], .th[width=\"33%\"], .td[width=\"33%\"] { width: 33%; }\n.table[width=\"100%\"], .th[width=\"100%\"], .td[width=\"100%\"] { width: 100%; }\n\n.table {\n\tdisplay: table;\n\twidth: 100%;\n\tmargin-bottom: 18px;\n\tpadding: 0;\n\tfont-size: 13px;\n\tborder-collapse: collapse;\n\tposition: relative;\n}\n\n.table .th, .table .td {\n\tdisplay: table-cell;\n\tvertical-align: middle; /* Fixme */\n\tpadding: 10px 10px 9px;\n\tline-height: 18px;\n\ttext-align: left;\n}\n\n.table .tr:first-child .th {\n\tpadding-top: 9px;\n\tfont-weight: bold;\n\tvertical-align: top;\n}\n\n.table .td, .table .th {\n\tborder-top: 1px solid #ddd;\n}\n\n.tr.placeholder {\n\theight: calc(3em + 20px);\n}\n\n.tr.placeholder > .td {\n\tposition: absolute;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\ttext-align: center;\n\tline-height: 3em;\n}\n\n.tr.drag-over-above,\n.tr.drag-over-below {\n\tborder: 2px solid #0069d6;\n\tborder-width: 2px 0 0 0;\n}\n\n.tr.drag-over-below {\n\tborder-width: 0 0 2px 0;\n}\n\n/* Patterns.less\n * Repeatable UI elements outside the base styles provided from the scaffolding\n * ---------------------------------------------------------------------------- */\nheader {\n\theight: 40px;\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tz-index: 10000;\n\toverflow: visible;\n\tcolor: rgb(244,244,244);\n}\n\nheader a {\n\tcolor: #600;\n\t/*text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);*/\n}\n\nheader h3 a:hover, header .brand:hover, header ul .active > a {\n\t/*background-color: #333;*/\n\t/*background-color: rgba(255, 255, 255, 0.05);*/\n\tcolor: #b30000;\n\ttext-decoration: none;\n}\n\nheader h3 {\n\tposition: relative;\n}\n\nheader h3 a, header .brand {\n\tfloat: left;\n\tdisplay: block;\n\tpadding: 8px 20px 12px;\n\tmargin-left: 0px;\n\tcolor: #600;\n\tfont-size: 20px;\n\tfont-family: sans-serif;\n\tfont-weight: 200;\n\tline-height: 1;\n\toutline:none;\n}\n\n.brand {\n\tdisplay: inline-block;\n\tpadding: 5px 0 5px 0 !important;\n\twidth: 135px !important;\n\theight: 40px !important;\n\tbackground-image: url('logo.jpg');\n\tbackground-size: 135px 30px;\n\tbackground-position: center center;\n\tbackground-repeat: no-repeat;\n\tmargin-right: 5px;\n}\n\nheader p {\n\tmargin: 0;\n\tline-height: 40px;\n}\n\nheader .fill {\n\tbackground-color: rgb(255,255,255);\n\tbackground-repeat: repeat-x;\n\t-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);\n\t-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);\n\tpadding: 0 5px;\n}\n\nheader div > ul, .nav {\n\tdisplay: block;\n\tfloat: left;\n\tmargin: 0 10px 0 0;\n\tposition: relative;\n\tleft: 0;\n}\n\nheader div > ul > li, .nav > li {\n\tdisplay: block;\n\tfloat: left;\n}\n\nheader div > ul a, .nav a {\n\tdisplay: block;\n\tfloat: none;\n\tpadding: 10px 10px 11px;\n\tline-height: 19px;\n\ttext-decoration: none;\n}\n\nheader div > ul a:hover, .nav a:hover {\n\tcolor: #b30000;\n\ttext-decoration: none;\n}\n\nheader div > ul .active > a, .nav .active > a {\n\tbackground-color: #fff;\n\tbackground-color: rgba(0, 0, 0, 0.5);\n}\n\nheader div > ul.secondary-nav, .nav.secondary-nav {\n\tfloat: right;\n\tmargin-left: 10px;\n\tmargin-right: 0;\n}\n\nheader div > ul.secondary-nav .menu-dropdown,\n.nav.secondary-nav .menu-dropdown,\nheader div > ul.secondary-nav .dropdown-menu,\n.nav.secondary-nav .dropdown-menu {\n\tright: 0;\n\tborder: 0;\n}\n\nheader div > ul a.menu:hover,\n.nav a.menu:hover,\nheader div > ul li.open .menu,\n.nav li.open .menu,\nheader div > ul .dropdown-toggle:hover,\n.nav .dropdown-toggle:hover,\nheader div > ul .dropdown.open .dropdown-toggle,\n.nav .dropdown.open .dropdown-toggle {\n\tbackground: #444;\n\tbackground: rgba(255, 255, 255, 0.05);\n}\n\nheader div > ul .menu-dropdown,\n.nav .menu-dropdown,\nheader div > ul .dropdown-menu,\n.nav .dropdown-menu {\n\tbackground-color: #f2f2f2;\n}\n\nheader div > ul .menu-dropdown a.menu,\n.nav .menu-dropdown a.menu,\nheader div > ul .dropdown-menu a.menu,\n.nav .dropdown-menu a.menu,\nheader div > ul .menu-dropdown .dropdown-toggle,\n.nav .menu-dropdown .dropdown-toggle,\nheader div > ul .dropdown-menu .dropdown-toggle,\n.nav .dropdown-menu .dropdown-toggle {\n\tcolor: #ffffff;\n}\n\nheader div > ul .menu-dropdown a.menu.open,\n.nav .menu-dropdown a.menu.open,\nheader div > ul .dropdown-menu a.menu.open,\n.nav .dropdown-menu a.menu.open,\nheader div > ul .menu-dropdown .dropdown-toggle.open,\n.nav .menu-dropdown .dropdown-toggle.open,\nheader div > ul .dropdown-menu .dropdown-toggle.open,\n.nav .dropdown-menu .dropdown-toggle.open {\n\tbackground: #444;\n\tbackground: rgba(255, 255, 255, 0.05);\n}\n\nheader div > ul .menu-dropdown li a,\n.nav .menu-dropdown li a,\nheader div > ul .dropdown-menu li a,\n.nav .dropdown-menu li a {\n\tcolor: #666;\n\ttext-shadow: none !important;\n}\n\nheader div > ul .menu-dropdown li a:hover,\n.nav .menu-dropdown li a:hover,\nheader div > ul .dropdown-menu li a:hover,\n.nav .dropdown-menu li a:hover {\n\tbackground-color: #ffffff;\n\tbackground-repeat: repeat-x;\n\tcolor: #B60000;\n\ttext-shadow: none !important;\n}\n\nheader div > ul .menu-dropdown .active a,\n.nav .menu-dropdown .active a,\nheader div > ul .dropdown-menu .active a,\n.nav .dropdown-menu .active a {\n\tcolor: #ffffff;\n}\n\nheader div > ul .menu-dropdown .divider,\n.nav .menu-dropdown .divider,\nheader div > ul .dropdown-menu .divider,\n.nav .dropdown-menu .divider {\n\tbackground-color: #222;\n\tborder-color: #444;\n}\n\nheader ul .menu-dropdown li a, header ul .dropdown-menu li a {\n\tpadding: 4px 15px;\n}\n\nli.menu, .dropdown {\n\tposition: relative;\n}\n\na.menu:after, .dropdown-toggle:after {\n\twidth: 0;\n\theight: 0;\n\tdisplay: inline-block;\n\tcontent: \"&darr;\";\n\ttext-indent: -99999px;\n\tvertical-align: top;\n\tmargin-top: 8px;\n\tmargin-left: 4px;\n\tborder-left: 4px solid transparent;\n\tborder-right: 4px solid transparent;\n\tborder-top: 4px solid #b60000;\n\topacity: 0.5;\n}\n\n.menu-dropdown, .dropdown-menu {\n\tbackground-color: #ffffff;\n\tfloat: left;\n\tposition: absolute;\n\ttop: 40px;\n\tleft: -9999px;\n\tz-index: 900;\n\tmin-width: 160px;\n\tmax-width: 220px;\n\t_width: 160px;\n\tmargin-left: 0;\n\tmargin-right: 0;\n\tpadding: 6px 0;\n\tzoom: 1;\n\tborder-color: #999;\n\tborder-color: rgba(0, 0, 0, 0.2);\n\tborder-style: solid;\n\tborder-width: 0 1px 1px;\n\tborder-radius: 0 0 6px 6px;\n\tbox-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n\tbackground-clip: padding-box;\n}\n\n.menu-dropdown li, .dropdown-menu li {\n\tfloat: none;\n\tdisplay: block;\n\tbackground-color: none;\n}\n\n.menu-dropdown .divider, .dropdown-menu .divider {\n\theight: 1px;\n\tmargin: 5px 0;\n\toverflow: hidden;\n\tbackground-color: #eee;\n\tborder-bottom: 1px solid #ffffff;\n}\n\nheader .dropdown-menu a, .dropdown-menu a {\n\tdisplay: block;\n\tpadding: 4px 15px;\n\tclear: both;\n\tfont-weight: normal;\n\tline-height: 18px;\n\tcolor: #808080;\n\ttext-shadow: 0 1px 0 #ffffff;\n}\n\nheader .dropdown-menu a:hover,\n.dropdown-menu a:hover,\nheader .dropdown-menu a.hover,\n.dropdown-menu a.hover {\n\tbackground-color: #dddddd;\n\tbackground-repeat: repeat-x;\n\tbackground-image: linear-gradient(to bottom, #eee, #ddd);\n\tcolor: #404040;\n\ttext-decoration: none;\n\tbox-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.025), inset 0 -1px rgba(0, 0, 0, 0.025);\n}\n\n.open .menu,\n.dropdown.open .menu,\n.open .dropdown-toggle,\n.dropdown.open .dropdown-toggle {\n\tcolor: #fff;\n\tbackground: #ccc;\n\tbackground: rgba(0, 0, 0, 0.3);\n}\n\n.open .menu-dropdown,\n.dropdown.open .menu-dropdown,\n.open .dropdown-menu,\n.dropdown.open .dropdown-menu {\n\tleft: 0;\n}\n\n.dropdown:hover ul.dropdown-menu {\n\tleft: 0;\n}\n\n.dropdown-menu .dropdown-menu {\n\t  position: absolute;\n\t  left: 159px;\n}\n\n.dropdown-menu li {\n\t  position: relative;\n}\n\n.tabs, .cbi-tabmenu {\n\tmargin: 0 -5px 18px;\n\tpadding: 0 2px;\n\tlist-style: none;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tbackground: linear-gradient(#fff 28px, #ddd 28px);\n\tbackground-size: 1px 29px;\n\tbackground-position: left bottom;\n}\n\n.tabs > li, .cbi-tabmenu > li {\n\tflex: 0 1 auto;\n\tdisplay: flex;\n\talign-items: center;\n\theight: 25px;\n\tmax-width: 48%;\n\tmargin: 4px 2px 0 2px;\n\tbackground: #fff;\n\tborder: 1px solid #ddd;\n\tborder-bottom: none;\n\tborder-radius: 4px 4px 0 0;\n\tcolor: #b60000;\n}\n\n.tabs > li > a, .cbi-tabmenu > li > a {\n\tpadding: 4px 6px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tcolor: inherit;\n\ttext-decoration: none;\n\tborder-radius: 4px 4px 0 0;\n\tline-height: 25px;\n\toutline: none;\n}\n\n.tabs > li:not(.active):hover, .cbi-tabmenu > .cbi-tab-disabled:hover {\n\tbackground: linear-gradient(#fff 90%, #ddd 100%);\n}\n\n.tabs > li:not(.active), .cbi-tabmenu > .cbi-tab-disabled {\n\tcolor: #999;\n\tbackground: linear-gradient(#eee 90%, #ddd 100%);\n}\n\n.cbi-tab-disabled[data-errors]::after {\n\tcontent: attr(data-errors);\n\tbackground: #c43c35;\n\tcolor: #fff;\n\tmin-width: 12px;\n\tline-height: 14px;\n\tborder-radius: 7px;\n\ttext-align: center;\n\tmargin: 0 5px 0 0;\n\tpadding: 1px 2px;\n}\n\n.cbi-tabmenu.map {\n\tmargin: 0;\n}\n\n.cbi-tabmenu.map > li {\n\tfont-size: 16.5px;\n\tfont-weight: bold;\n}\n\n.cbi-tabcontainer > fieldset.cbi-section[id] > legend {\n\tdisplay: none;\n}\n\n.tabs .menu-dropdown, .tabs .dropdown-menu {\n\ttop: 35px;\n\tborder-width: 1px;\n\tborder-radius: 0 6px 6px 6px;\n}\n\n.tabs a.menu:after, .tabs .dropdown-toggle:after {\n\tborder-top-color: #999;\n\tmargin-top: 15px;\n\tmargin-left: 5px;\n}\n\n.tabs li.open.menu .menu, .tabs .open.dropdown .dropdown-toggle {\n\tborder-color: #999;\n}\n\n.tabs li.open a.menu:after, .tabs .dropdown.open .dropdown-toggle:after {\n\tborder-top-color: #555;\n}\n\n.tab-content > .tab-pane,\n.tab-content > div {\n\tdisplay: none;\n}\n\n.tab-content > .active {\n\tdisplay: block;\n}\n\n.breadcrumb {\n\tpadding: 7px 14px;\n\tmargin: 0 0 18px;\n\tbackground-color: #f5f5f5;\n\tbackground-repeat: repeat-x;\n\tbackground-image: linear-gradient(to bottom, #fff, #f5f5f5);\n\tborder: 1px solid #ddd;\n\tborder-radius: 3px;\n\tbox-shadow: inset 0 1px 0 #fff;\n}\n\n.breadcrumb li {\n\tdisplay: inline;\n\ttext-shadow: 0 1px 0 #fff;\n}\n\n.breadcrumb .divider {\n\tpadding: 0 5px;\n\tcolor: #bfbfbf;\n}\n\n.breadcrumb .active a {\n\tcolor: #404040;\n}\n\nfooter {\n\tmargin-top: 17px;\n\tpadding-top: 17px;\n\tborder-top: 1px solid #eee;\n}\n\n#modal_overlay {\n\tposition: fixed;\n\ttop: 0;\n\tbottom: 0;\n\tleft: -10000px;\n\tright: 10000px;\n\tbackground: rgba(0, 0, 0, 0.7);\n\tz-index: 900;\n\toverflow-y: scroll;\n\t-webkit-overflow-scrolling: touch;\n\ttransition: opacity .125s ease-in;\n\topacity: 0;\n\tvisibility: hidden;\n}\n\n.modal {\n\twidth: 90%;\n\tmargin: 5em auto;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tmin-height: 32px;\n\tmax-width: 600px;\n\talign-items: center;\n\tborder-radius: 3px;\n\tbackground: #fff;\n\tbox-shadow: 0 0 3px #444;\n\tpadding: 1em 1em .5em 1em;\n\tmin-width: 270px;\n}\n\n.modal > * {\n\tflex-basis: 100%;\n\tline-height: normal;\n\tmargin-bottom: .5em;\n\tmax-width: 100%;\n}\n\n.modal > pre,\n.modal > textarea {\n\twhite-space: pre-wrap;\n\toverflow: auto;\n}\n\nbody.modal-overlay-active {\n\toverflow: hidden;\n\theight: 100vh;\n}\n\nbody.modal-overlay-active #modal_overlay {\n\tleft: 0;\n\tright: 0;\n\topacity: 1;\n\tvisibility: visible;\n}\n\n.btn.danger,\n.alert-message.danger,\n.btn.danger:hover,\n.alert-message.danger:hover,\n.btn.error,\n.alert-message.error,\n.btn.error:hover,\n.alert-message.error:hover,\n.btn.success,\n.alert-message.success,\n.btn.success:hover,\n.alert-message.success:hover,\n.btn.info,\n.alert-message.info,\n.btn.info:hover,\n.alert-message.info:hover,\n.cbi-tooltip.error, .cbi-tooltip.success, .cbi-tooltip.info {\n\tcolor: #fff;\n}\n\n.btn .close, .alert-message .close {\n\tfont-family: Arial, sans-serif;\n\tline-height: 18px;\n}\n\n.modal .btn.danger,\n.modal .btn {\n\twhite-space: normal;\n}\n\n.btn.danger,\n.alert-message.danger,\n.btn.error,\n.alert-message.error,\n.cbi-tooltip.error {\n\tbackground: linear-gradient(to bottom, #ee5f5b, #c43c35) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.btn.success, .alert-message.success, .cbi-tooltip.success {\n\tbackground: linear-gradient(to bottom, #62c462, #57a957) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.btn.info, .alert-message.info, .cbi-tooltip.info {\n\tbackground: linear-gradient(to bottom, #5bc0de, #339bb9) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.alert-message.notice, .cbi-tooltip.notice {\n\tbackground: linear-gradient(to bottom, #efefef, #fefefe) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(255, 255, 255, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n}\n\n.item::after,\n.btn,\n.cbi-button {\n\tcursor: pointer;\n\tdisplay: inline-block;\n\tbackground: #fff;\n\tpadding: 5px 14px 6px;\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n\tcolor: #333;\n\tfont-size: 13px;\n\tline-height: normal;\n\tborder: 1px solid #ccc;\n\tborder-bottom-color: #bbb;\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n\twhite-space: pre;\n}\n\n.btn:focus,\n.cbi-button:focus {\n\toutline: 1px dotted #666;\n}\n\n.cbi-input-invalid,\n.cbi-input-invalid.cbi-dropdown:not(.btn):not(.cbi-button),\n.cbi-input-invalid.cbi-dropdown:not([open]) > ul > li,\n.cbi-value-error input {\n\tcolor: #f00;\n\tborder-color: #f00;\n}\n\n.cbi-button-neutral,\n.cbi-button-download,\n.cbi-button-find,\n.cbi-button-link,\n.cbi-button-up,\n.cbi-button-down {\n\tcolor: #444;\n}\n\n.btn.primary,\n.cbi-button-apply,\n.cbi-button-negative {\n\tborder-color: #b60000;\n\tcolor: #b60000;\n\ttext-shadow: none !important;\n}\n\n.cbi-section-remove .cbi-button,\n.cbi-button-reset {\n\tborder-color: #c44;\n\tcolor: #c44;\n}\n\n.cbi-page-actions::after {\n\tdisplay: table;\n\tcontent: \"\";\n\tclear: both;\n}\n\n.cbi-page-actions > * {\n\tvertical-align: middle;\n}\n\n.cbi-page-actions > :not([method=\"post\"]):not(.cbi-button-apply):not(.cbi-button-negative):not(.cbi-button-save):not(.cbi-button-reset) {\n\tfloat: left;\n\tmargin-right: .4em;\n}\n\n.cbi-button-edit,\n.cbi-button-positive.important,\n.cbi-button-positive,\n.cbi-button-action.important,\n.cbi-button-action,\n.cbi-button-save,\n.cbi-button-reload,\n.cbi-page-actions .cbi-button-apply {\n\tcolor: #FFFFFF;\n\tbackground: #d2492d;\n\tborder-color: #d2492d;\n\ttext-shadow: none !important;\n}\n\n.cbi-button-fieldadd,\n.cbi-section-remove .cbi-button,\n.cbi-button-remove,\n.cbi-button-add,\n.cbi-button-negative.important,\n.cbi-page-actions .cbi-button-save,\n.cbi-page-actions .cbi-button-reset,\n.cbi-section-actions .cbi-button-action {\n\tcolor: #95110F;\n\tbackground: #ffffff;\n\tborder-color: #95110F;\n\ttext-shadow: none !important;\n}\n\n\n.cbi-page-actions .cbi-button-reset:hover,\n.cbi-page-actions .cbi-button-save:hover,\n.cbi-page-actions .cbi-button-apply:hover {\n\tcolor: #FFFFFF;\n\tbackground: #95110F;\n\tborder-color: #95110F;\n\ttext-shadow: none !important;\n}\n\n.cbi-dropdown {\n\tdisplay: inline-flex !important;\n\tcursor: pointer;\n\theight: auto;\n\tposition: relative;\n\tpadding: 0 !important;\n}\n\n.cbi-dropdown:not(.btn):not(.cbi-button) {\n\tbackground: linear-gradient(#fff 0%, #e9e8e6 100%);\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n\tcolor: #404040;\n}\n\n.cbi-dynlist > .item:focus,\n.cbi-dropdown:focus {\n\toutline: 2px solid #4b6e9b;\n}\n\n.cbi-dropdown > ul {\n\tmargin: 0 !important;\n\tpadding: 0;\n\tlist-style: none;\n\toverflow-x: hidden;\n\toverflow-y: auto;\n\tdisplay: flex;\n\twidth: 100%;\n}\n\n.cbi-dropdown.btn > ul:not(.dropdown),\n.cbi-dropdown.cbi-button > ul:not(.dropdown) {\n\tmargin: 0 0 0 13px !important;\n}\n\n.cbi-dropdown.btn.spinning > ul:not(.dropdown),\n.cbi-dropdown.cbi-button.spinning > ul:not(.dropdown) {\n\tmargin: 0 !important;\n}\n\n.cbi-dropdown > ul.preview {\n\tdisplay: none;\n}\n\n.cbi-dropdown > .open,\n.cbi-dropdown > .more {\n\tflex-grow: 0;\n\tflex-shrink: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n\ttext-align: center;\n\tline-height: 2em;\n\tpadding: 0 .25em;\n}\n\n.cbi-dropdown.btn > .open,\n.cbi-dropdown.cbi-button > .open {\n\tpadding: 0 .5em;\n\tmargin-left: .5em;\n\tborder-left: 1px solid;\n}\n\n.cbi-dropdown > .more,\n.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li[placeholder] {\n\tcolor: #777;\n\tfont-weight: bold;\n\ttext-shadow: 1px 1px 0px #fff;\n\tdisplay: none;\n\tjustify-content: center;\n}\n\n.cbi-dropdown > ul > li {\n\tdisplay: none;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tflex-shrink: 1;\n\tflex-grow: 1;\n\talign-items: center;\n\talign-self: center;\n\tcolor: inherit;\n}\n\n.cbi-dropdown > ul.dropdown > li,\n.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li {\n\tmin-height: 20px;\n\tpadding: .25em;\n\tcolor: #404040;\n}\n\n.cbi-dropdown > ul > li .hide-open { display: block; display: initial; }\n.cbi-dropdown > ul > li .hide-close { display: none; }\n\n.cbi-dropdown > ul > li[display]:not([display=\"0\"]) {\n\tborder-left: 1px solid #ccc;\n}\n\n.cbi-dropdown[empty] > ul {\n\tmax-width: 1px;\n}\n\n.cbi-dropdown > ul > li > form {\n\tdisplay: none;\n\tmargin: 0;\n\tpadding: 0;\n\tpointer-events: none;\n}\n\n.cbi-dropdown > ul > li img {\n\tvertical-align: middle;\n\tmargin-right: .25em;\n}\n\n.cbi-dropdown > ul > li > form > input[type=\"checkbox\"] {\n\tmargin: 0;\n}\n\n.cbi-dropdown > ul > li input[type=\"text\"] {\n\theight: 20px;\n}\n\n.cbi-dropdown[open] {\n\tposition: relative;\n}\n\n.cbi-dropdown[open] > ul.dropdown {\n\tdisplay: block;\n\tbackground: #f6f6f5;\n\tborder: 1px solid #918e8c;\n\tbox-shadow: 0 0 4px #918e8c;\n\tposition: absolute;\n\tz-index: 1100;\n\tmax-width: none;\n\tmin-width: 100%;\n\twidth: auto;\n\ttransition: max-height .125s ease-in;\n}\n\n.cbi-dropdown > ul > li[display],\n.cbi-dropdown[open] > ul.preview,\n.cbi-dropdown[open] > ul.dropdown > li,\n.cbi-dropdown[multiple] > ul > li > label,\n.cbi-dropdown[multiple][open] > ul.dropdown > li,\n.cbi-dropdown[multiple][more] > .more,\n.cbi-dropdown[multiple][empty] > .more {\n\tflex-grow: 1;\n\tdisplay: flex !important;\n}\n\n.cbi-dropdown[empty] > ul > li,\n.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder],\n.cbi-dropdown[multiple][open] > ul.dropdown > li > form {\n\tdisplay: block !important;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; }\n.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: block; display: initial; }\n\n.cbi-dropdown[open] > ul.dropdown > li {\n\tborder-bottom: 1px solid #ccc;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li[selected] {\n\tbackground: #e58a8a;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li.focus {\n\tbackground: #eeb4b4;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li:last-child {\n\tmargin-bottom: 0;\n\tborder-bottom: none;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li[unselectable] {\n\topacity: 0.7;\n}\n\n.cbi-dropdown[open] > ul.dropdown > li > input.create-item-input:first-child:last-child {\n\twidth: 100%;\n}\n\n.cbi-dropdown[disabled] {\n\tpointer-events: none;\n\topacity: .6;\n}\n\ninput[type=\"text\"] + .cbi-button,\ninput[type=\"password\"] + .cbi-button,\nselect + .cbi-button {\n\tborder-radius: 0 3px 3px 0;\n\tborder-color: #ccc;\n\tmargin-left: -2px;\n\tpadding: 0 6px;\n\tvertical-align: top;\n\theight: 30px;\n\tfont-size: 14px;\n\tline-height: 28px;\n}\n\n/*select + .cbi-button {\n\tborder-left-color: transparent;\n}*/\n\ninput[type=\"text\"] + .cbi-button-add {\n\tcolor: #fff;\n\tborder-radius: 0 3px 3px 0;\n\tborder-color: #d2492d;\n\tbackground: #d2492d;\n}\n\ninput[type=\"text\"] + .cbi-button-remove {\n\tcolor: #c44;\n\tborder-radius: 0 3px 3px 0;\n\tborder-color: #ccc;\n}\n\n.cbi-title-ref {\n\tcolor: #b60000;\n}\n\n.cbi-title-ref::after {\n\tcontent: \"➙\";\n}\n\n.cbi-tooltip-container {\n\tcursor: help;\n}\n\n.cbi-tooltip {\n\tposition: absolute;\n\tz-index: 1000;\n\tleft: -10000px;\n\tbox-shadow: 0 0 2px #ccc;\n\tborder-radius: 3px;\n\tbackground: #fff;\n\twhite-space: pre;\n\tpadding: 2px 5px;\n\topacity: 0;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-tooltip-container:hover .cbi-tooltip:not(:empty) {\n\tleft: auto;\n\topacity: 1;\n\ttransition: opacity .25s ease-in;\n}\n\n\n.cbi-progressbar {\n\tborder: 1px solid var(--border-color-high);\n\tborder-radius: 3px;\n\tposition: relative;\n\tmin-width: 170px;\n\theight: 8px;\n\tmargin: 1.4em 0 4px 0;\n\tbackground: #fcfcfc;\n}\n\n.cbi-progressbar > div {\n\tbackground: #df826f;\n\theight: 100%;\n\ttransition: width .25s ease-in;\n\twidth: 0%;\n\tborder-radius: 2px;\n}\n\n.cbi-progressbar::before {\n\tposition: absolute;\n\ttop: -1.4em;\n\tleft: 0;\n\tcontent: attr(title);\n\twhite-space: pre;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.zonebadge .cbi-tooltip {\n\tpadding: 1px;\n\tbackground: inherit;\n\tmargin: -1.6em 0 0 -5px;\n\tborder-radius: 3px;\n\tpointer-events: none;\n\tbox-shadow: 0 0 3px #444;\n}\n\n.zonebadge .cbi-tooltip > * {\n\tmargin: 1px;\n}\n\n.zone-forwards {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n}\n\n.zone-forwards > * {\n\tflex: 1 1 40%;\n\tpadding: 1px;\n}\n\n.zone-forwards > span {\n\tflex-basis: 10%;\n\ttext-align: center;\n}\n\n.zone-forwards .zone-src,\n.zone-forwards .zone-dest {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.btn.active, .btn:active {\n\tbox-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.btn.disabled {\n\tcursor: default;\n\topacity: 0.65;\n\tbox-shadow: none;\n}\n\n.btn[disabled] {\n\tcursor: default;\n\topacity: 0.65;\n\tbox-shadow: none;\n}\n\n.btn.large {\n\tfont-size: 15px;\n\tline-height: normal;\n\tpadding: 9px 14px 9px;\n\tborder-radius: 6px;\n}\n\n.btn.small {\n\tpadding: 7px 9px 7px;\n\tfont-size: 11px;\n}\n\nbutton.btn::-moz-focus-inner, input[type=submit].btn::-moz-focus-inner {\n\tpadding: 0;\n\tborder: 0;\n}\n\n.close {\n\tfloat: right;\n\tcolor: #000;\n\tfont-size: 20px;\n\tfont-weight: bold;\n\tline-height: 13.5px;\n\ttext-shadow: 0 1px 0 #fff;\n\topacity: 0.25;\n}\n\n.close:hover {\n\tcolor: #000;\n\ttext-decoration: none;\n\topacity: 0.4;\n}\n\n.alert-message {\n\tposition: relative;\n\tpadding: .5em .5em .25em .5em;\n\tmargin-bottom: .5em;\n\tcolor: #404040;\n\tbackground: linear-gradient(to bottom, #fceec1, #eedc94) repeat-x;\n\ttext-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n\tborder-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n\ttext-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n\tborder-width: 1px;\n\tborder-style: solid;\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n\n.alert-message .close {\n\tmargin-top: 1px;\n\t*margin-top: 0;\n}\n\n.alert-message h4,\n.alert-message h5,\n.alert-message pre,\n.alert-message ul,\n.alert-message li,\n.alert-message p {\n\tcolor: inherit;\n\tborder: none;\n\tline-height: inherit;\n\tbackground: transparent;\n\tpadding: 0;\n\tmargin: .25em 0;\n}\n\n.alert-message button {\n\tmargin: .25em 0;\n}\n\n.label,\nheader [data-indicator] {\n\tpadding: 1px 3px 2px;\n\tfont-size: 9.75px;\n\tfont-weight: bold;\n\tcolor: #fff !important;\n\ttext-transform: uppercase;\n\twhite-space: nowrap;\n\tbackground-color: #D12626;\n\tborder-radius: 3px;\n\ttext-shadow: none;\n\tmargin-left: .4em;\n}\n\nheader [data-indicator][data-clickable] {\n\tcursor: pointer;\n}\n\na.label:link,\na.label:visited {\n\tcolor: #fff;\n}\n\na.label:hover {\n\ttext-decoration: none;\n}\n\n.label.important {\n\tbackground-color: #c43c35;\n}\n\n.label.warning {\n\tbackground-color: #f89406;\n}\n\n.label.success {\n\tbackground-color: #339933;\n}\n\n.label.notice,\nheader [data-indicator][data-style=\"active\"] {\n\tbackground-color: #df826f;\n}\n\n/* LuCI specific items */\n.hidden { display: none }\n\nform.inline { display: inline; margin-bottom: 0; }\n\nheader .pull-right { padding-top: 8px; }\n\n#modemenu li:last-child span.divider { display: none }\n\n#syslog {  width: 100%; }\n\n.cbi-section-table .tr:hover .td,\n.cbi-section-table .tr:hover .th,\n.cbi-section-table .tr:hover::before {\n\tbackground-color: #f5f5f5;\n}\n\n.cbi-section-table .tr.cbi-section-table-descr .th {\n\tfont-weight: normal;\n}\n\n.cbi-section-table-titles.named::before,\n.cbi-section-table-descr.named::before,\n.cbi-section-table-row[data-title]::before {\n\tcontent: attr(data-title) \" \";\n\tdisplay: table-cell;\n\tpadding: 10px 10px 9px;\n\tline-height: 18px;\n\tfont-weight: bold;\n\tvertical-align: middle;\n}\n\n.cbi-section-table-titles.named::before,\n.cbi-section-table-descr.named::before,\n.cbi-section-table-row[data-title]::before {\n\tborder-top: 1px solid #ddd;\n}\n\n.left { text-align: left !important; }\n.right { text-align: right !important; margin-bottom: 5px !important;}\n.center { text-align: center !important; }\n.top { vertical-align: top !important; }\n.middle { vertical-align: middle !important; }\n.bottom { vertical-align: bottom !important; }\n\n.cbi-value-field { line-height: 1.5em; }\n\n.cbi-value-field input[type=checkbox],\n.cbi-value-field input[type=radio] {\n\tmargin-top: 8px;\n\tmargin-right: 6px;\n}\n\ntable table td,\n.cbi-value-field table td {\n\tborder: none;\n}\n\n.table.cbi-section-table input[type=\"password\"],\n.table.cbi-section-table input[type=\"text\"],\n.table.cbi-section-table textarea,\n.table.cbi-section-table select {\n\twidth: 100%;\n}\n\n.table.cbi-section-table .td.cbi-section-table-cell {\n\twhite-space: nowrap;\n\ttext-align: right;\n}\n\n.table.cbi-section-table .td.cbi-section-table-cell select {\n\twidth: inherit;\n}\n\n.td.cbi-section-actions {\n\ttext-align: right;\n\tvertical-align: middle;\n}\n\n.td.cbi-section-actions > * {\n\tdisplay: flex;\n}\n\n.td.cbi-section-actions > * > *,\n.td.cbi-section-actions > * > form > * {\n\tflex: 1 1 4em;\n\tmargin: 0 1px;\n}\n\n.td.cbi-section-actions > * > form {\n\tdisplay: inline-flex;\n\tmargin: 0;\n}\n\n.table.valign-middle .td {\n\tvertical-align: middle;\n}\n\n.cbi-rowstyle-2,\n.tr.table-titles,\n.tr.cbi-section-table-titles {\n\tbackground: #f9f9f9;\n}\n\n.cbi-value-description {\n\tbackground-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxNicgaGVpZ2h0PScxNicgdmlld0JveD0nMCAwIDE2IDE2Jz48Y2lyY2xlIGN4PSc4JyBjeT0nOCcgcj0nNy41JyBmaWxsPScjZDI0OTJkJyBzdHJva2U9JyNiMDMwMjAnLz48dGV4dCB4PSc4JyB5PScxMicgZm9udC1zaXplPScxMScgZm9udC1mYW1pbHk9J3NhbnMtc2VyaWYnIGZvbnQtd2VpZ2h0PSdib2xkJyB0ZXh0LWFuY2hvcj0nbWlkZGxlJyBmaWxsPScjZmZmJz4/PC90ZXh0Pjwvc3ZnPg==\");\n\tbackground-position: .25em .2em;\n\tbackground-repeat: no-repeat;\n\tmargin: .25em 0 0 0;\n\tpadding: 0 0 0 1.7em;\n}\n\n.cbi-section-error {\n\tborder: 1px solid #f00;\n\tborder-radius: 3px;\n\tbackground-color: #fce6e6;\n\tpadding: 5px;\n\tmargin-bottom: 18px;\n}\n\n.cbi-section-error ul { margin: 0 0 0 20px; }\n\n.cbi-section-error ul li {\n\tcolor: #f00;\n\tfont-weight: bold;\n}\n\n.ifacebox {\n\tbackground-color: #fff;\n\tborder: 1px solid #ccc;\n\tmargin: 0 10px;\n\ttext-align: center;\n\twhite-space: nowrap;\n\tbackground-image: linear-gradient(#fff, #fff 25%, #f9f9f9);\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n\tdisplay: inline-flex;\n\tflex-direction: column;\n\tline-height: 1.2em;\n\tmin-width: 100px;\n}\n\n.ifacebox .ifacebox-head {\n\tborder-bottom: 1px solid #ccc;\n\tpadding: 2px;\n\tbackground: #eee;\n}\n\n.ifacebox .ifacebox-head.active {\n\tbackground: #e09090;\n}\n\n.ifacebox .ifacebox-body {\n\tpadding: .25em;\n}\n\n.ifacebadge {\n\tdisplay: inline-block;\n\tflex-direction: row;\n\twhite-space: nowrap;\n\tbackground-color: #fff;\n\tborder: 1px solid #ccc;\n\tpadding: 2px;\n\tbackground-image: linear-gradient(#fff, #fff 25%, #f9f9f9);\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n\tborder-radius: 4px;\n\tbox-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n\tcursor: default;\n\tline-height: 1.2em;\n}\n\n.ifacebadge img {\n\twidth: 16px;\n\theight: 16px;\n\tvertical-align: middle;\n}\n\n.ifacebadge-active {\n\tborder-color: #000;\n\tfont-weight: bold;\n}\n\n.network-status-table {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n}\n\n.network-status-table .ifacebox {\n\tmargin: .5em;\n\tflex-grow: 1;\n}\n\n.network-status-table .ifacebox-body {\n\tdisplay: flex;\n\tflex-direction: column;\n\theight: 100%;\n\ttext-align: left;\n}\n\n.network-status-table .ifacebox-body > * {\n\tmargin: .25em;\n}\n\n.network-status-table .ifacebox-body > span {\n\tflex: 10 10 auto;\n\theight: 100%;\n}\n\n.network-status-table .ifacebox-body > div {\n\tmargin: -.125em;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n}\n\n#dsl_status_table .ifacebox-body span > strong {\n\tdisplay: inline-block;\n\tmin-width: 35%;\n}\n\n.ifacebadge.large,\n.network-status-table .ifacebox-body .ifacebadge {\n\tdisplay: flex;\n\tflex: 1;\n\tpadding: .25em;\n\tmin-width: 220px;\n\tmargin: .125em;\n}\n\n.ifacebadge.large {\n\tdisplay: inline-flex;\n}\n\n.network-status-table .ifacebox-body .ifacebadge > span {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.ifacebadge > *,\n.ifacebadge.large > * {\n\tmargin: 0 .125em;\n}\n\n.zonebadge {\n\tpadding: 2px;\n\tborder-radius: 4px;\n\tdisplay: inline-block;\n\twhite-space: nowrap;\n\tcolor: #666;\n\ttext-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n}\n\n.zonebadge > em,\n.zonebadge > strong {\n\tmargin: 0 2px;\n\tdisplay: inline-block;\n}\n\n.zonebadge input {\n\twidth: 6em;\n}\n\n.zonebadge > .ifacebadge {\n\tmargin-left: 2px;\n}\n\n.zonebadge-empty {\n\tborder: 1px dashed #aaa;\n\tcolor: #aaa;\n\tfont-style: italic;\n\tfont-size: smaller;\n}\n\ndiv.cbi-value var,\n.td.cbi-value-field var {\n\tfont-style: italic;\n\tcolor: #0069d6;\n}\n\ndiv.cbi-value var[data-tooltip],\n.td.cbi-value-field var[data-tooltip],\ndiv.cbi-value var.cbi-tooltip-container,\n.td.cbi-value-field var.cbi-tooltip-container {\n\tcursor: help;\n\tborder-bottom: 1px dotted #0069d6;\n}\n\ndiv.cbi-value var.cbi-tooltip-container,\n.td.cbi-value-field var.cbi-tooltip-container .cbi-tooltip {\n\tfont-style: normal;\n\twhite-space: normal;\n\tcolor: #444;\n}\n\n#modal_overlay > .modal.uci-dialog,\n#modal_overlay > .modal.cbi-modal {\n\tmax-width: 900px;\n}\n\n.uci-change-list {\n\tline-height: 170%;\n\twhite-space: pre;\n}\n\n.uci-change-list del,\n.uci-change-list ins,\n.uci-change-list var,\n.uci-change-legend-label del,\n.uci-change-legend-label ins,\n.uci-change-legend-label var {\n\ttext-decoration: none;\n\tfont-family: monospace;\n\tfont-style: normal;\n\tborder: 1px solid #ccc;\n\tbackground: #eee;\n\tpadding: 2px;\n\tdisplay: block;\n\tline-height: 15px;\n\tmargin-bottom: 1px;\n}\n\n.uci-change-list ins,\n.uci-change-legend-label ins {\n\tborder-color: #0f0;\n\tbackground: #cfc;\n}\n\n.uci-change-list del,\n.uci-change-legend-label del {\n\tborder-color: #f00;\n\tbackground: #fcc;\n}\n\n.uci-change-list var,\n.uci-change-legend-label var {\n\tborder-color: #ccc;\n\tbackground: #eee;\n}\n\n.uci-change-list var ins,\n.uci-change-list var del {\n\tdisplay: inline-block;\n\tborder: none;\n\twidth: 100%;\n\tpadding: 0;\n}\n\n.uci-change-legend {\n\tpadding: 5px;\n}\n\n.uci-change-legend-label {\n\twidth: 150px;\n\tfloat: left;\n}\n\n.uci-change-legend-label > ins,\n.uci-change-legend-label > del,\n.uci-change-legend-label > var {\n\tfloat: left;\n\tmargin-right: 4px;\n\twidth: 16px;\n\theight: 16px;\n\tdisplay: block;\n\tposition: relative;\n}\n\n.uci-change-legend-label var ins,\n.uci-change-legend-label var del {\n\tborder: none;\n\tposition: absolute;\n\ttop: 2px;\n\tleft: 2px;\n\tright: 2px;\n\tbottom: 2px;\n}\n\n#modal_overlay {\n\tposition: fixed;\n\ttop: 0;\n\tbottom: 0;\n\tleft: -10000px;\n\tright: 10000px;\n\tbackground: rgba(0, 0, 0, 0.7);\n\tz-index: 900;\n\toverflow-y: scroll;\n\t-webkit-overflow-scrolling: touch;\n\ttransition: opacity .125s ease-in;\n\topacity: 0;\n}\n\n#modal_overlay > .modal {\n\twidth: 90%;\n\tmargin: 5em auto;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tmin-height: 32px;\n\tmax-width: 600px;\n\talign-items: center;\n\tborder-radius: 3px;\n\tbackground: #fff;\n\tbox-shadow: 0 0 3px #444;\n\tpadding: 1em 1em .5em 1em;\n\tmin-width: 270px;\n}\n\n#modal_overlay .modal > * {\n\tflex-basis: 100%;\n\tline-height: normal;\n\tmargin-bottom: .5em;\n}\n\n#modal_overlay .modal > pre,\n#modal_overlay .modal > textarea {\n\twhite-space: pre-wrap;\n\toverflow: auto;\n}\n\nbody.modal-overlay-active {\n\toverflow: hidden;\n\theight: 100vh;\n}\n\nbody.modal-overlay-active #modal_overlay {\n\tleft: 0;\n\tright: 0;\n\topacity: 1;\n}\n\nhtml body.apply-overlay-active {\n\theight: calc(100vh - 63px);\n}\n\n#applyreboot-section {\n\tline-height: 300%;\n}\n\n[data-page=\"admin-network-dhcp\"] [data-name=\"ip\"] {\n\twidth: 15%;\n}\n\n@keyframes flash {\n\t0% { opacity: 1; }\n\t50% { opacity: .5; }\n\t100% { opacity: 1; }\n}\n\n.flash {\n\tanimation: flash .35s;\n}\n\n.spinning {\n\tposition: relative;\n\tpadding-left: 32px !important;\n}\n\n.spinning::before {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tbottom: 0;\n\twidth: 32px;\n\tcontent: \" \";\n\tbackground: url(../resources/icons/loading.svg) no-repeat center;\n\tbackground-size: 16px;\n}\n\n[data-tab-title] {\n\theight: 0;\n\topacity: 0;\n\toverflow: hidden;\n}\n\n[data-tab-active=\"true\"] {\n\topacity: 1;\n\theight: auto;\n\toverflow: visible;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-filebrowser {\n\tmin-width: 210px;\n\tmax-width: 100%;\n\tborder: 1px solid #ccc;\n\tborder-radius: 3px;\n\tdisplay: flex;\n\tflex-direction: column;\n\topacity: 0;\n\theight: 0;\n\toverflow: hidden;\n}\n\n.cbi-filebrowser.open {\n\topacity: 1;\n\theight: auto;\n\toverflow: visible;\n\ttransition: opacity .25s ease-in;\n}\n\n.cbi-filebrowser > * {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tpadding: 0 0 .25em 0;\n\tmargin: .25em .25em 0px .25em;\n\twhite-space: nowrap;\n\tborder-bottom: 1px solid #ccc;\n}\n\n.cbi-filebrowser .cbi-button-positive {\n\tmargin-right: .25em;\n}\n\n.cbi-filebrowser > div {\n\tborder-bottom: none;\n}\n\n.cbi-filebrowser > ul > li {\n\tdisplay: flex;\n\tflex-direction: row;\n}\n\n.cbi-filebrowser > ul > li:hover {\n\tbackground: #f5f5f5;\n}\n\n.cbi-filebrowser > ul > li > div:first-child {\n\tflex: 10;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cbi-filebrowser > ul > li > div:last-child {\n\tflex: 3;\n\ttext-align: right;\n}\n\n.cbi-filebrowser > ul > li > div:last-child > button {\n\tpadding: .125em .25em;\n\tmargin: 1px 0 1px .25em;\n}\n\n.cbi-filebrowser .upload {\n\tdisplay: flex;\n\tflex-direction: row;\n\tflex-wrap: wrap;\n\tmargin: 0 -.125em .25em -.125em;\n\tpadding: 0 0 .125em 0px;\n\tborder-bottom: 1px solid #ccc;\n}\n\n.cbi-filebrowser .upload > * {\n\tmargin: .125em;\n\tflex: 1;\n}\n\n.cbi-filebrowser .upload > .btn {\n\tflex-basis: 60px;\n}\n\n.cbi-filebrowser .upload > div {\n\tflex: 10;\n\tmin-width: 150px;\n}\n\n.cbi-filebrowser .upload > div > input {\n\twidth: 100%;\n}\n\n@keyframes fade-in {\n\t0% { opacity: 0; }\n\t100% { opacity: 1; }\n}\n\n@keyframes fade-out {\n\t0% { opacity: 1; }\n\t100% { opacity: 0; }\n}\n\n.fade-in {\n\tanimation: fade-in .4s ease;\n}\n\n.fade-out {\n\tanimation: fade-out .4s ease;\n}\n\n.assoclist .ifacebadge {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\twhite-space: normal;\n\ttext-align: center;\n}\n\n.assoclist .ifacebadge > img {\n\tmargin: .2em;\n}\n\n.assoclist .td:nth-of-type(3),\n.assoclist .td:nth-of-type(5) {\n\twidth: 25%;\n}\n\n.assoclist .td:nth-of-type(6) button {\n\tword-break: normal;\n}\n\nimg[src*=\"signal-0\"] {\n    filter: sepia(1) saturate(8) hue-rotate(315deg) brightness(0.65) contrast(1.1);\n}\n"
  },
  {
    "path": "luci/themes/luci-theme-teleofis/htdocs/luci-static/teleofis/checkbox.css",
    "content": "/* Custom checkbox and radio styles for teleofis theme */\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n    appearance: none;\n    -webkit-appearance: none;\n    width: 14px;\n    height: 14px;\n    border: 1px solid #ccc;\n    background: #fff;\n    position: relative;\n    cursor: pointer;\n    vertical-align: middle;\n}\n\ninput[type=\"checkbox\"] {\n    border-radius: 2px;\n}\n\ninput[type=\"checkbox\"]:checked {\n    background-color: #660000;\n    border-color: #660000;\n}\n\ninput[type=\"checkbox\"]:checked::before {\n    content: \"✓\";\n    position: absolute;\n    color: white;\n    font-size: 12px;\n    font-weight: bold;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n}\n\ninput[type=\"radio\"] {\n    border-radius: 50%;\n}\n\ninput[type=\"radio\"]:checked {\n    background-color: #660000;\n    border-color: #660000;\n}\n\ninput[type=\"radio\"]:checked::after {\n    content: \"\";\n    position: absolute;\n    width: 6px;\n    height: 6px;\n    background: white;\n    border-radius: 50%;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n}\n\ninput[type=\"checkbox\"]:hover,\ninput[type=\"radio\"]:hover {\n    border-color: #660000;\n}\n\ninput[type=\"checkbox\"]:focus,\ninput[type=\"radio\"]:focus {\n    outline: none;\n    box-shadow: 0 0 0 2px rgba(46, 125, 50, 0.0);\n}\n"
  },
  {
    "path": "luci/themes/luci-theme-teleofis/htdocs/luci-static/teleofis/mobile.css",
    "content": "header h3 a, header .brand {\n\tdisplay:none !important;\n}\n\n@media screen and (max-device-width: 600px) {\n\t#maincontent.container {\n\t\tmargin-top: 30px;\n\t}\n\n\t.tabs, .cbi-tabmenu {\n\t\tbackground: linear-gradient(#fff 20%, #ddd 100%);\n\t\tbackground-size: 1px 34px;\n\t\tmargin-bottom: 10px;\n\t}\n\n\t.tabs > li, .cbi-tabmenu > li {\n\t\theight: 30px;\n\t}\n\n\t.tabs > li > a, .cbi-tabmenu > li > a {\n\t\tpadding: 0 8px;\n\t\tline-height: 30px;\n\t}\n\n\t.table {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t}\n\n\t.tr {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-wrap: wrap;\n\t\talign-items: flex-end;\n\t\tborder-top: 1px solid #ddd;\n\t\tpadding: 5px 0;\n\t\tmargin: 0 -3px;\n\t}\n\n\t.table .th,\n\t.table .td,\n\t.table .tr::before {\n\t\tflex: 2 2 33%;\n\t\talign-self: flex-start;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\tword-wrap: break-word;\n\t\tdisplay: inline-block;\n\t\tborder-top: none;\n\t\tpadding: 3px;\n\t\tbox-sizing: border-box;\n\t}\n\n\t.table .td.cbi-dropdown-open {\n\t\toverflow: visible;\n\t}\n\n\t.col-1 { flex: 1 1 30px !important; -webkit-flex: 1 1 30px !important; }\n\t.col-2 { flex: 2 2 60px !important; -webkit-flex: 2 2 60px !important; }\n\t.col-3 { flex: 3 3 90px !important; -webkit-flex: 3 3 90px !important; }\n\t.col-4 { flex: 4 4 120px !important; -webkit-flex: 4 4 120px !important; }\n\t.col-5 { flex: 5 5 150px !important; -webkit-flex: 5 5 150px !important; }\n\t.col-6 { flex: 6 6 180px !important; -webkit-flex: 6 6 180px !important; }\n\t.col-7 { flex: 7 7 210px !important; -webkit-flex: 7 7 210px !important; }\n\t.col-8 { flex: 8 8 240px !important; -webkit-flex: 8 8 240px !important; }\n\t.col-9 { flex: 9 9 270px !important; -webkit-flex: 9 9 270px !important; }\n\t.col-10 { flex: 10 10 300px !important; -webkit-flex: 10 10 300px !important; }\n\n\t.td select {\n\t\tword-wrap: normal;\n\t}\n\n\t.td[data-widget=\"button\"],\n\t.td[data-widget=\"fvalue\"] {\n\t\tflex: 1 1 17%;\n\t\ttext-align: left;\n\t}\n\n\t.td.cbi-value-field {\n\t\talign-self: flex-start;\n\t}\n\n\t.td.cbi-value-field .cbi-button {\n\t\twidth: 100%;\n\t}\n\n\t.table.cbi-section-table {\n\t\tborder: none;\n\t\tbackground: none;\n\t\tmargin: 0;\n\t}\n\n\t.tr.table-titles,\n\t.cbi-section-table-titles,\n\t.cbi-section-table-descr {\n\t\tdisplay: none;\n\t}\n\n\t.cbi-section-table-row {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-wrap: wrap;\n\t\tmargin: 0 0 .5em 0;\n\t}\n\n\t.cbi-section-table + .cbi-section-create {\n\t\tpadding-top: 0;\n\t}\n\n\t.tr[data-title]::before {\n\t\tdisplay: block;\n\t\tflex: 1 1 100%;\n\t\tbackground: #f5f5f5 !important;\n\t\tfont-size: 16px;\n\t\tborder-bottom: 1px solid #ddd;\n\t}\n\n\t.td[data-title]::before,\n\t.td[data-description]::after {\n\t\tdisplay: block;\n\t}\n\n\t.td[data-title] ~ .td.cbi-section-actions {\n\t\talign-self: flex-start;\n\t}\n\n\t.td[data-title] ~ .td.cbi-section-actions::before {\n\t\tdisplay: block;\n\t\tcontent: \"\\a0\";\n\t}\n\n\t.td.cbi-section-actions {\n\t\toverflow: initial;\n\t\tmax-width: 100%;\n\t\tpadding: 3px 2px;\n\t}\n\n\t.hide-sm,\n\t.hide-xs {\n\t\tdisplay: none !important;\n\t}\n\n\t.td.cbi-value-field {\n\t\tflex-basis: 100%;\n\t}\n\n\t.td.cbi-value-field[data-widget=\"dvalue\"] {\n\t\tflex-basis: 50%;\n\t}\n\n\t.td.cbi-value-field[data-widget=\"button\"],\n\t.td.cbi-value-field[data-widget=\"fvalue\"] {\n\t\tflex-basis: 25%;\n\t\ttext-align: left;\n\t}\n\n\t.cbi-section-table .tr:hover .td,\n\t.cbi-section-table .tr:hover .th,\n\t.cbi-section-table .tr:hover::before {\n\t\tbackground-color: transparent;\n\t}\n\n\t.cbi-value {\n\t\tpadding-bottom: .5em;\n\t\tborder-bottom: 1px solid #ddd;\n\t\tmargin-bottom: .5em;\n\t}\n\n\t.cbi-value label.cbi-value-title {\n\t\tfloat: none;\n\t\tfont-weight: bold;\n\t}\n\n\t.cbi-value-field, .cbi-dropdown {\n\t\twidth: 100%;\n\t\tmargin: 0;\n\t}\n\n\tinput, textarea, select,\n\t.cbi-dropdown > ul > li input[type=\"text\"] {\n\t\tfont-size: 16px !important;\n\t\tline-height: 28px;\n\t\theight: auto;\n\t}\n\n\tselect, input[type=\"text\"], input[type=\"password\"] {\n\t\twidth: 100%;\n\t\theight: 30px;\n\t}\n\n\tinput.cbi-input-password {\n\t\twidth: calc(100% - 25px);\n\t}\n\n\t[data-dynlist] {\n\t\tdisplay: block;\n\t}\n\n\t[data-dynlist] > .add-item > input {\n\t\twidth: calc(100% - 21px);\n\t}\n\n\t[data-dynlist] > .add-item > .cbi-button {\n\t\tmargin-right: -1px;\n\t}\n\n\tinput[type=\"text\"] + .cbi-button,\n\tinput[type=\"password\"] + .cbi-button,\n\tselect + .cbi-button {\n\t\tfont-size: 14px !important;\n\t\tline-height:  28px;\n\t\theight: 30px;\n\t\tbox-sizing: border-box;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\t.cbi-value-field input[type=\"checkbox\"],\n\t.cbi-value-field input[type=\"radio\"] {\n\t\tmargin: 0;\n\t}\n\n\t.btn, .cbi-button {\n\t\tfont-size: 14px !important;\n\t\tpadding: 4px 8px;\n\t}\n\n\t.actions,\n\t.cbi-page-actions {\n\t\tborder-top: none;\n\t\tmargin-top: -.5em;\n\t\tpadding: 8px;\n\t}\n\n\t[data-page=\"admin-status-overview\"] .cbi-section:nth-of-type(1) .td:first-child,\n\t[data-page=\"admin-status-overview\"] .cbi-section:nth-of-type(2) .td:first-child {\n\t\tflex-grow: 1;\n\t}\n\n\theader .pull-right .label {\n\t\twhite-space: normal;\n\t\tdisplay: inline-block;\n\t\ttext-align: center;\n\t\tline-height: 12px;\n\t\tmargin: 1px 0;\n\t}\n\n\theader > .fill {\n\t\tpadding: 1px;\n\t}\n\n\theader > .fill > .container {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t}\n\n\theader .nav {\n\t\tflex: 3 3 80%;\n\t\tmargin: 2px 5px 2px 0;\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: flex-start;\n\t}\n\n\theader .nav a {\n\t\tpadding: 2px 6px;\n\t}\n\n\theader .pull-right {\n\t\tflex: 1 1 20%;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tpadding: 0;\n\t\tjustify-content: space-around;\n\t}\n\n\t.menu-dropdown, .dropdown-menu {\n\t\ttop: 23px;\n\t}\n\n\tbody {\n\t\tpadding-top: 30px;\n\t}\n\n\t.cbi-optionals,\n\t.cbi-section-create {\n\t\tpadding: 0 0 14px 0;\n\t}\n\n\t#cbi-network-switch_vlan .th,\n\t#cbi-network-switch_vlan .td {\n\t\tflex-basis: 12%;\n\t}\n\n\t#cbi-network-switch_vlan .td.cbi-section-actions {\n\t\tflex-basis: 100%;\n\t}\n\n\t#cbi-network-switch_vlan .td.cbi-section-actions::before {\n\t\tdisplay: none;\n\t}\n\n\t#cbi-network-switch_vlan .td.cbi-section-actions > * {\n\t\twidth: auto;\n\t\tdisplay: block;\n\t}\n\n\t#wifi_assoclist_table .td,\n\t[data-page=\"admin-status-processes\"] .td {\n\t\tflex-basis: 50% !important;\n\t}\n\n\t[data-page=\"admin-status-processes\"] .td[data-widget=\"button\"] {\n\t\tflex-basis: 33% !important;\n\t}\n\n\t[data-page=\"admin-status-processes\"] .td[data-name=\"PID\"],\n\t[data-page=\"admin-status-processes\"] .td[data-name=\"USER\"] {\n\t\tflex-basis: 25% !important;\n\t}\n\n\t[data-page=\"admin-system-fstab\"] .td[data-widget=\"button\"]::before,\n\t[data-page=\"admin-system-startup\"] .td[data-widget=\"button\"]::before,\n\t[data-page=\"admin-status-processes\"] .td[data-widget=\"button\"]::before {\n\t\tdisplay: none;\n\t}\n}\n\n@media screen and (max-device-width: 375px) {\n\t#maincontent.container {\n\t\tmargin-top: 55px;\n\t}\n\n\t.cbi-page-actions {\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: space-between;\n\t\tmargin: 0 -1px;\n\t\tpadding: 0;\n\t}\n\n\t.cbi-page-actions .cbi-button:not(.cbi-dropdown) {\n\t\tflex: 1 1 calc(50% - 2px);\n\t\tmargin: 1px !important;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\t.cbi-page-actions .cbi-button-negative,\n\t.cbi-page-actions .cbi-button-primary,\n\t.cbi-page-actions .cbi-button-apply {\n\t\tflex-basis: calc(100% - -2px);\n\t}\n\n\t.cbi-section-actions .cbi-button {\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\tbody[data-page=\"admin-network-wireless\"] .td[data-name=\"_badge\"] {\n\t\tmax-width: 50px;\n\t\talign-self: center;\n\t}\n\n\tbody[data-page=\"admin-network-wireless\"] .td[data-name=\"_badge\"] .ifacebadge {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tflex-direction: column;\n\t}\n\n\tbody[data-page=\"admin-network-wireless\"] .td[data-name=\"_stat\"] {\n\t\tflex-basis: 60%;\n\t}\n\n\tbody[data-page=\"admin-network-network\"] .td.cbi-section-actions::before,\n\tbody[data-page=\"admin-network-wireless\"] .td.cbi-section-actions::before {\n\t\tcontent: none !important;\n\t}\n}\n\n@media screen and (max-device-width: 200px) {\n\t#maincontent.container {\n\t\tmargin-top: 230px;\n\t}\n}\n\n@media screen and (max-width: 375px) {\n\t.td .ifacebox {\n\t\twidth: 100%;\n\t\tmargin: 0 !important;\n\t\tflex-direction: row;\n\t}\n\n\t.td .ifacebox .ifacebox-head {\n\t\tmin-width: 25%;\n\t\tjustify-content: space-around;\n\t}\n\n\t.td .ifacebox .ifacebox-head,\n\t.td .ifacebox .ifacebox-body {\n\t\tdisplay: flex;\n\t\tborder-bottom: none;\n\t\talign-items: center;\n\t}\n\n\t.td .ifacebox .ifacebox-head > *,\n\t.ifacebox .ifacebox-body > * {\n\t\tmargin: .125em;\n\t}\n}\n"
  },
  {
    "path": "luci/themes/luci-theme-teleofis/luasrc/view/themes/teleofis/footer.htm",
    "content": "<%#\n Copyright 2008 Steven Barth <steven@midlink.org>\n Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>\n Copyright 2012 David Menting <david@nut-bolt.nl>\n Licensed to the public under the Apache License 2.0.\n-%>\n\n<% local ver = require \"luci.version\" %>\n\n   <footer>\n    <div style=\"height:40px;font-size:95%;color:#999;font-family: Arial,sans-serif;\">&#169;&nbsp;TELEOFIS&nbsp;2004-2022&nbsp;</div>\n    <ul class=\"breadcrumb pull-right\" id=\"modemenu\" style=\"display:none\"></ul>\n   </footer>\n  </div>\n  <script type=\"text/javascript\">L.require('menu-teleofis')</script>\n </body>\n</html>\n\n"
  },
  {
    "path": "luci/themes/luci-theme-teleofis/luasrc/view/themes/teleofis/header.htm",
    "content": "<%#\n Copyright 2008 Steven Barth <steven@midlink.org>\n Copyright 2008-2016 Jo-Philipp Wich <jow@openwrt.org>\n Copyright 2012 David Menting <david@nut-bolt.nl>\n Licensed to the public under the Apache License 2.0.\n-%>\n\n<%\n\tlocal sys  = require \"luci.sys\"\n\tlocal util = require \"luci.util\"\n\tlocal http = require \"luci.http\"\n\tlocal disp = require \"luci.dispatcher\"\n\n\tlocal boardinfo = util.ubus(\"system\", \"board\")\n\n\tlocal node = disp.context.dispatched\n\n\t-- send as HTML5\n\thttp.prepare_content(\"text/html\")\n-%>\n<!DOCTYPE html>\n<html lang=\"<%=luci.i18n.context.lang%>\">\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title><%=striptags( (boardinfo.hostname or \"?\") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>\n\t\t<meta name=\"viewport\" content=\"initial-scale=1.0\">\n\t\t<link rel=\"stylesheet\" href=\"<%=media%>/cascade.css\">\n\t\t<link rel=\"stylesheet\" href=\"<%=media%>/checkbox.css?v=0\">\n\t\t<link rel=\"stylesheet\" media=\"only screen and (max-device-width: 854px)\" href=\"<%=media%>/mobile.css\" type=\"text/css\" />\n\t\t<link rel=\"shortcut icon\" href=\"<%=media%>/favicon.ico\">\n\t\t<% if node and node.css then %>\n\t\t\t<link rel=\"stylesheet\" href=\"<%=resource%>/<%=node.css%>\">\n\t\t<% end -%>\n\t\t<% if css then %>\n\t\t\t<style title=\"text/css\"><%= css %></style>\n\t\t<% end -%>\n\t\t<script src=\"<%=url('admin/translations', luci.i18n.context.lang)%><%# ?v=PKG_VERSION %>\"></script>\n\t\t<script src=\"<%=resource%>/cbi.js\"></script>\n\t</head>\n\n\t<body class=\"lang_<%=luci.i18n.context.lang%> <% if node then %><%= striptags( node.title ) %><%- end %>\" data-page=\"<%= pcdata(table.concat(disp.context.requestpath, \"-\")) %>\">\n\t\t<header>\n\t\t\t<div class=\"fill\">\n\t\t\t\t<div class=\"container\">\n\t\t\t\t\t<a class=\"brand\" href=\"/\"></a>\n\t\t\t\t\t<ul class=\"nav\" id=\"topmenu\" style=\"display:none\"></ul>\n\t\t\t\t\t<div id=\"indicators\" class=\"pull-right\"></div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<div id=\"maincontent\" class=\"container\">\n\t\t\t<%- if luci.sys.process.info(\"uid\") == 0 and luci.sys.user.getuser(\"root\") and not luci.sys.user.getpasswd(\"root\") then -%>\n\t\t\t\t<div class=\"alert-message warning\">\n\t\t\t\t\t<h4><%:No password set!%></h4>\n\t\t\t\t\t<p><%:There is no password set on this router. Please configure a root password to protect the web interface.%></p>\n\t\t\t\t\t<% if disp.lookup(\"admin/system/admin\") then %>\n\t\t\t\t\t  <div class=\"right\"><a class=\"btn\" href=\"<%=url(\"admin/system/admin\")%>\"><%:Go to password configuration...%></a></div>\n\t\t\t\t\t<% end %>\n\t\t\t\t</div>\n\t\t\t<%- end -%>\n\n\t\t\t<noscript>\n\t\t\t\t<div class=\"alert-message warning\">\n\t\t\t\t\t<h4><%:JavaScript required!%></h4>\n\t\t\t\t\t<p><%:You must enable JavaScript in your browser or LuCI will not work properly.%></p>\n\t\t\t\t</div>\n\t\t\t</noscript>\n\n\t\t\t<div id=\"tabmenu\" style=\"display:none\"></div>\n"
  },
  {
    "path": "luci/themes/luci-theme-teleofis/root/etc/uci-defaults/30_luci-theme-teleofis",
    "content": "#!/bin/sh\n\nif [ \"$PKG_UPGRADE\" != 1 ]; then\n\tuci get luci.themes.Teleofis >/dev/null 2>&1 || \\\n\tuci batch <<-EOF\n\t\tset luci.themes.Teleofis=/luci-static/teleofis\n\t\tset luci.main.mediaurlbase=/luci-static/teleofis\n\t\tcommit luci\n\tEOF\nfi\n\nexit 0\n"
  },
  {
    "path": "packages/net/3proxy/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=3proxy\nPKG_VERSION:=0.9.5\nPKG_RELEASE:=2\n\nPKG_MAINTAINER:=muziling <lls924@gmail.com>\nPKG_LICENSE:=GPLv2\nPKG_LICENSE_FILES:=LICENSE\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL:=https://github.com/z3APA3A/3proxy.git\nPKG_SOURCE_VERSION:=f23fc4fc4458606a257a9f26bc37251fc60e1cc7\n\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  SUBMENU:=Web Servers/Proxies\n  SECTION:=net\n  CATEGORY:=Network\n  TITLE:=3proxy OpenWRT package\n  DEPENDS:=+libpthread +libopenssl +libpcre2\nendef\n\ndefine Package/lib$(PKG_NAME)\n  SUBMENU:=Web Servers/Proxies\n  DEPENDS:=+3proxy\n  TITLE:=3proxy libraries\nendef\n\ndefine Package/$(PKG_NAME)-common\n  SUBMENU:=Web Servers/Proxies\n  DEPENDS:=+3proxy\n  TITLE:=3proxy addional servers\nendef\n\ndefine Package/$(PKG_NAME)-sql\n  PKGARCH:=all\n  DEPENDS:=+3proxy\n  TITLE:=3proxy sql template\nendef\n\ndefine Package/$(PKG_NAME)/description\n\t3APA3A 3proxy tiny proxy servers\nendef\n\ndefine Build/Configure\n\t$(CP) $(PKG_BUILD_DIR)/Makefile.Linux $(PKG_BUILD_DIR)/Makefile\nendef\n\ndefine Package/$(PKG_NAME)/conffiles\n/etc/config/3proxy\n/etc/3proxy.cfg\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/bin/3proxy \\\n\t\t\t$(PKG_BUILD_DIR)/bin/proxy \\\n\t\t\t$(1)/usr/bin/\n\t$(CP) ./files/* $(1)/\nendef\n\ndefine Package/lib$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/lib\n\t$(INSTALL_DIR) $(1)/usr/lib/3proxy\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/bin/*.so $(1)/usr/lib/3proxy\nendef\n\ndefine Package/$(PKG_NAME)-common/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/bin/ftppr \\\n\t\t$(PKG_BUILD_DIR)/bin/mycrypt \\\n\t\t$(PKG_BUILD_DIR)/bin/pop3p \\\n\t\t$(PKG_BUILD_DIR)/bin/smtpp \\\n\t\t$(PKG_BUILD_DIR)/bin/tcppm \\\n\t\t$(PKG_BUILD_DIR)/bin/udppm \\\n\t\t$(PKG_BUILD_DIR)/bin/socks \\\n\t\t$(PKG_BUILD_DIR)/bin/proxy \\\n\t\t$(PKG_BUILD_DIR)/bin/tlspr \\\n\t\t$(1)/usr/bin\nendef\n\ndefine Package/$(PKG_NAME)-sql/install\n\t$(INSTALL_DIR) $(1)/usr/share/3proxy\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/cfg/sql/* $(1)/usr/share/3proxy\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n$(eval $(call BuildPackage,lib$(PKG_NAME)))\n$(eval $(call BuildPackage,$(PKG_NAME)-common))\n$(eval $(call BuildPackage,$(PKG_NAME)-sql))\n\n"
  },
  {
    "path": "packages/net/3proxy/README.md",
    "content": "3proxy running on Openwrt/LEDE\n===\n\n编译/Compile\n---\n\n```bash\ncd openwrt\ngit clone https://github.com/muziling/3proxy-openwrt.git feeds/packages/net/3proxy\nrm -rf tmp/\n\n./scripts/feeds update -a\n./scripts/feeds install -a\n\nmake menuconfig\nmake package/3proxy/compile\n```\n"
  },
  {
    "path": "packages/net/3proxy/files/etc/3proxy.cfg",
    "content": "daemon\nauth strong\nproxy -p3128\n"
  },
  {
    "path": "packages/net/3proxy/files/etc/config/3proxy",
    "content": "config 3proxy\n\toption config '/etc/3proxy.cfg'\n"
  },
  {
    "path": "packages/net/3proxy/files/etc/init.d/3proxy",
    "content": "#!/bin/sh /etc/rc.common\n\n# init script 3proxy for openwrt\n# written by Konstantin Shevlakov <shevlakov@132lan.ru>\n\nSTART=99\nUSE_PROCD=1\n\n\nload_config(){\n\tconfig_get config $1 config\n\tconfig_get mptcp $1 mptcp\n}\n\nstart_service(){\n\tconfig_load 3proxy\n\tlocal config mptcp\n\tconfig_foreach load_config\n       \t[ -n $config ] || config=/etc/3proxy.cfg\n\tprocd_open_instance\n\t[ \"$mptcp\" = \"1\" ] && [ -x /usr/bin/mptcpize ] && {\n\t\tprocd_set_param command /usr/bin/mptcpize run /usr/bin/3proxy $config\n\t} || {\n\t\tprocd_set_param command /usr/bin/3proxy $config\n\t}\n\tprocd_set_param stdout 1\n\tprocd_close_instance\n}\n\nstop_service(){\n\tkillall 3proxy\n}\n\n"
  },
  {
    "path": "packages/net/3proxy/patches/0001-fix-makefile-loff_t-and-enable-ssl-plugin.patch",
    "content": "--- a/Makefile.Linux\n+++ b/Makefile.Linux\n@@ -8,13 +8,12 @@\n # library support. Add -DSAFESQL for poorely written ODBC library / drivers.\n \n BUILDDIR = ../bin/\n-CC = gcc\n \n-CFLAGS = -g  -fPIC -O2 -fno-strict-aliasing -c -pthread -DWITHSPLICE -D_GNU_SOURCE -DGETHOSTBYNAME_R -D_THREAD_SAFE -D_REENTRANT -DNOODBC -DWITH_STD_MALLOC -DFD_SETSIZE=4096 -DWITH_POLL -DWITH_NETFILTER\n+CFLAGS = -fPIC -fno-strict-aliasing -c -pthread -DWITHSPLICE -D_GNU_SOURCE -DGETHOSTBYNAME_R -D_THREAD_SAFE -D_REENTRANT -DNOODBC -DWITH_STD_MALLOC -DFD_SETSIZE=4096 -DWITH_POLL -DWITH_NETFILTER -DSYSTEM_PCRE\n COUT = -o \n LN = $(CC)\n DCFLAGS = \n-LDFLAGS = -fPIC -O2 -fno-strict-aliasing -pthread\n+LDFLAGS = -fno-strict-aliasing \n DLFLAGS = -shared\n DLSUFFICS = .ld.so\n # -lpthreads may be reuqired on some platforms instead of -pthreads\n@@ -35,7 +34,7 @@ MAKEFILE = Makefile.Linux\n #LIBS = -lcrypto -lssl -ldl \n LIBS = -ldl \n #PLUGINS = SSLPlugin StringsPlugin TrafficPlugin PCREPlugin TransparentPlugin PamAuth\n-PLUGINS = StringsPlugin TrafficPlugin PCREPlugin TransparentPlugin\n+PLUGINS = SSLPlugin StringsPlugin TrafficPlugin PCREPlugin TransparentPlugin\n \n include Makefile.inc\n \nIndex: 3proxy/src/sockmap.c\n===================================================================\n--- 3proxy.orig/src/sockmap.c\n+++ 3proxy/src/sockmap.c\n@@ -19,10 +19,11 @@ char logbuf[1024];\n #define log(X)\n #endif\n \n+#define _FILE_OFFSET_BITS 64\n #ifdef WITHSPLICE\n \n #include <fcntl.h>\n-ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);\n+ssize_t splice(int fd_in, off_t *off_in, int fd_out, off_t *off_out, size_t len, unsigned int flags);\n #ifndef SPLICE_F_MOVE\n #define SPLICE_F_MOVE           0x01\n #endif\n"
  },
  {
    "path": "packages/net/3proxy/patches/0002-use-pcre-library.patch",
    "content": "--- a/src/plugins/PCREPlugin/Makefile.inc\n+++ b/src/plugins/PCREPlugin/Makefile.inc\n@@ -57,6 +57,12 @@ pcre_xclass$(OBJSUFFICS): pcre_xclass.c\n pcre_plugin$(OBJSUFFICS): pcre_plugin.c\n \t$(CC) $(DCFLAGS) $(CFLAGS) pcre_plugin.c\n \n-$(BUILDDIR)PCREPlugin$(DLSUFFICS): pcre_compile$(OBJSUFFICS) pcre_config$(OBJSUFFICS) pcre_dfa_exec$(OBJSUFFICS) pcre_exec$(OBJSUFFICS) pcre_fullinfo$(OBJSUFFICS) pcre_get$(OBJSUFFICS) pcre_globals$(OBJSUFFICS) pcre_newline$(OBJSUFFICS) pcre_ord2utf8$(OBJSUFFICS) pcre_refcount$(OBJSUFFICS) pcre_study$(OBJSUFFICS) pcre_tables$(OBJSUFFICS) pcre_valid_utf8$(OBJSUFFICS) pcre_version$(OBJSUFFICS) pcre_xclass$(OBJSUFFICS) pcre_plugin$(OBJSUFFICS) pcre_maketables$(OBJSUFFICS) pcre_ucd$(OBJSUFFICS) pcre_table$(OBJSUFFICS)\n-\t$(LN) $(LNOUT)../../$(BUILDDIR)PCREPlugin$(DLSUFFICS) $(LDFLAGS) $(DLFLAGS) pcre_compile$(OBJSUFFICS) pcre_config$(OBJSUFFICS) pcre_dfa_exec$(OBJSUFFICS) pcre_exec$(OBJSUFFICS) pcre_fullinfo$(OBJSUFFICS) pcre_get$(OBJSUFFICS) pcre_globals$(OBJSUFFICS) pcre_newline$(OBJSUFFICS) pcre_ord2utf8$(OBJSUFFICS) pcre_refcount$(OBJSUFFICS) pcre_study$(OBJSUFFICS) pcre_tables$(OBJSUFFICS) pcre_valid_utf8$(OBJSUFFICS) pcre_version$(OBJSUFFICS) pcre_xclass$(OBJSUFFICS) pcre_plugin$(OBJSUFFICS) pcre_maketables$(OBJSUFFICS) pcre_ucd$(OBJSUFFICS) pcre_table$(OBJSUFFICS)\n \n+#ifdef SYSTEM_PCRE\n+LDFLAGS += -lpcre2-8\n+#else\n+PCRE_OBJS += pcre_compile$(OBJSUFFICS) pcre_config$(OBJSUFFICS) pcre_dfa_exec$(OBJSUFFICS) pcre_exec$(OBJSUFFICS) pcre_fullinfo$(OBJSUFFICS) pcre_get$(OBJSUFFICS) pcre_globals$(OBJSUFFICS) pcre_newline$(OBJSUFFICS) pcre_ord2utf8$(OBJSUFFICS) pcre_refcount$(OBJSUFFICS) pcre_study$(OBJSUFFICS) pcre_tables$(OBJSUFFICS) pcre_valid_utf8$(OBJSUFFICS) pcre_version$(OBJSUFFICS) pcre_xclass$(OBJSUFFICS) pcre_plugin$(OBJSUFFICS) pcre_maketables$(OBJSUFFICS) pcre_ucd$(OBJSUFFICS) pcre_table$(OBJSUFFICS)\n+#endif\n+\n+$(BUILDDIR)PCREPlugin$(DLSUFFICS): $(PCRE_OBJS) pcre_plugin$(OBJSUFFICS)\n+\t$(LN) $(LNOUT)../../$(BUILDDIR)PCREPlugin$(DLSUFFICS) $(LDFLAGS) $(DLFLAGS) $^\n"
  },
  {
    "path": "packages/net/accel-ppp/LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "packages/net/accel-ppp/Makefile",
    "content": "#B\n#\n# Copyright (C) 2013-2015 OpenWrt.org\n#\n# This is free software, licensed under the GNU General Public License v2.\n# See /LICENSE for more information.\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=accel-ppp\nPKG_RELEASE:=20250110~master1\nPKG_VERSION:=1.13.0\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL=https://github.com/accel-ppp/accel-ppp.git\nPKG_SOURCE_VERSION:=b6938e83144ff4f5495195dbb60d59e1e7511840\n\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\nPKG_LICENSE:=GPL-2.0\n\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)\n\n#PKG_INSTALL:=1\n#PKG_CONFIG_DEPENDS:=CONFIG_PACKAGE_accel-ppp_$(BUILD_VARIANT)_ext_cer_id\n\ninclude $(INCLUDE_DIR)/package.mk\ninclude $(INCLUDE_DIR)/cmake.mk\ninclude $(INCLUDE_DIR)/kernel.mk\n\ndefine Package/$(PKG_NAME)\n  SECTION:=net\n  CATEGORY:=Network\n  TITLE:=accel-ppp VPN server\n  DEPENDS:=+libpcre2 +libopenssl +libpthread +librt +libatomic +libucontext\nendef\n\nCMAKE_OPTIONS += \\\n\t-DKDIR=$(LINUX_DIR) \\\n\t-DCMAKE_INSTALL_PREFIX=/usr \\\n\t-DCMAKE_BUILD_TYPE=Release \\\n\t-DLOG_PGSQL=FALSE \\\n\t-DSHAPER=TRUE \\\n\t-DRADIUS=FALSE \\\n\t-DNETSNMP=FALSE \\\n\t-DLOG_FILE=TRUE \\\n\t-DLIB_SUFFIX= \\\n\nifeq ($(ARCH),$(filter $(ARCH), x86_64 i386))\n  CMAKE_OPTIONS += \\\n\t-DBUILD_IPOE_DRIVER=TRUE \\\n\t-DBUILD_VLAN_MON_DRIVER=TRUE\nendif\n\n\nCMAKE_BINARY_DIR := $(PKG_BUILD_DIR)/build\nTARGET_CFLAGS += -I$(STAGING_DIR)/opt/include -D_GNU_SOURCE -g -O2 -Wno-incompatible-pointer-types\nTARGET_LDFLAGS += -lpthread -latomic -lucontext\n\ndefine KernelPackage/$(PKG_NAME)-ipoe\n  SUBMENU:= Accel-PPP kernel modules\n  TITLE:=Accel-PPP IPoE kernel module\n  DEPENDS:=+accel-ppp\n  FILES:=$(CMAKE_BINARY_DIR)/drivers/ipoe/driver/ipoe.ko\n  AUTOLOAD:=$(call AutoProbe,ipoe)\nendef\n\ndefine KernelPackage/$(PKG_NAME)-vlan-mon\n  SUBMENU:=Accel-PPP kernel modules\n  TITLE:=Accel-PPP vlan-mon kernel module\n  DEPENDS:=+accel-ppp\n  FILES:=$(CMAKE_BINARY_DIR)/drivers/vlan_mon/driver/vlan_mon.ko\n  AUTOLOAD:=$(call AutoProbe,vlan_mon)\nendef\n\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) \\\n\t\t$(1)/usr/sbin \\\n\t\t$(1)/usr/bin \\\n\t\t$(1)/usr/lib \\\n\t\t$(1)/usr/lib/accel-ppp \\\n\t\t$(1)/usr/share/accel-ppp \\\n\t\t$(1)/usr/share/accel-ppp/radius \\\n\t\t$(1)/usr/share/accel-ppp/l2tp \\\n\t\t$(1)/etc/init.d \\\n\t\t$(1)/etc/accel-ppp\n\n\t$(INSTALL_BIN) \\\n\t\t$(PKG_INSTALL_DIR)/usr/sbin/accel-pppd \\\n\t\t$(1)/usr/sbin/\n\n\t$(INSTALL_BIN) \\\n\t\t$(PKG_INSTALL_DIR)/usr/bin/accel-cmd \\\n\t\t$(1)/usr/bin/\n\n\t$(INSTALL_BIN) \\\n\t\t$(PKG_INSTALL_DIR)/usr/lib/accel-ppp/*.so \\\n\t\t$(1)/usr/lib/accel-ppp/\n\n\t$(INSTALL_DATA) \\\n\t\t$(PKG_INSTALL_DIR)/usr/share/accel-ppp/l2tp/dictionary.* \\\n\t\t$(1)/usr/share/accel-ppp/l2tp/\n\n\t$(INSTALL_BIN) \\\n\t\tfiles/accel-ppp.init \\\n\t\t$(1)/etc/init.d/accel-ppp\n\n\t$(INSTALL_DATA) \\\n\t\tfiles/accel-ppp.conf \\\n\t\t$(1)/etc/accel-ppp\n\nendef\n\ndefine Package/$(PKG_NAME)/postinst\nfor l in $$(ls -1 /usr/lib/accel-ppp/); do\n\tln -s /usr/lib/accel-ppp/$$l /usr/lib/\ndone\nendef\n\ndefine Package/$(PKG_NAME)/prerm\nfor l in $$(ls -1 /usr/lib/accel-ppp/); do\n\trm -f /usr/lib/$$l\ndone\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\nifeq ($(ARCH),$(filter $(ARCH), x86_64 i386))\n  $(eval $(call KernelPackage,$(PKG_NAME)-ipoe))\n  $(eval $(call KernelPackage,$(PKG_NAME)-vlan-mon))\nendif\n"
  },
  {
    "path": "packages/net/accel-ppp/README.md",
    "content": "# openwrt-accel-ppp\nACCEL-PPP is a high performance VPN server application for linux.\nOpenWrt package\n"
  },
  {
    "path": "packages/net/accel-ppp/files/accel-ppp.conf",
    "content": "[modules]\nconnlimit\nlog_file\n#log_syslog\n#log_tcp\n#log_pgsql\n\npptp\n# L2TP not Work!\n#l2tp\nsstp\n#pppoe\n#ipoe\n\nauth_mschap_v2\n#auth_mschap_v1\n#auth_chap_md5\n#auth_pap\n\nchap-secrets\n\nippool\n\npppd_compat\n\n#shaper\n#net-snmp\n#logwtmp\n\n#ipv6_nd\n#ipv6_dhcp\n#ipv6pool\n\n[core]\nlog-error=/tmp/accel-ppp-core.log\nthread-count=4\n\n[common]\n#single-session=replace\n#single-session-ignore-case=0\n#sid-case=upper\n#sid-source=seq\n#max-sessions=1000\n#max-starting=0\n#check-ip=0\n#nl-snd-buffer=32768\n#nl-rcv-buffer=1048576\n\n[ppp]\nverbose=1\nmin-mtu=1280\nmtu=1400\nmru=1400\naccomp=deny\npcomp=deny\n#ccp=0\nmppe=require\nipv4=require\nipv6=deny\nipv6-intf-id=0:0:0:1\nipv6-peer-intf-id=0:0:0:2\nipv6-accept-peer-intf-id=1\nlcp-echo-interval=20\nlcp-echo-failure=3\nlcp-echo-timeout=120\nunit-cache=1\n#unit-preallocate=1\n\n[auth]\n#timeout=5\n#interval=0\n#max-failure=3\n#any-login=0\n#noauth=0\n#challenge-name=accel-ppp\n\n[pptp]\nverbose=1\n#echo-interval=30\nip-pool=pool3\n#ipv6-pool=pptp\n#ipv6-pool-delegate=pptp\nifname=pptp%d\n\n[pppoe]\nverbose=1\n#ac-name=xxx\n#service-name=yyy\n#pado-delay=0\n#pado-delay=0,100:100,200:200,-1:500\ncalled-sid=mac\n#tr101=1\n#padi-limit=0\n#ip-pool=pppoe\n#ipv6-pool=pppoe\n#ipv6-pool-delegate=pppoe\n#ifname=pppoe%d\n#sid-uppercase=0\n#vlan-mon=eth0,10-200\n#vlan-timeout=60\n#vlan-name=%I.%N\n#interface=eth1,padi-limit=1000\ninterface=eth0\n\n[l2tp]\nverbose=1\n#dictionary=/usr/local/share/accel-ppp/l2tp/dictionary\n#hello-interval=60\n#timeout=60\n#rtimeout=1\n#rtimeout-cap=16\n#retransmit=5\n#recv-window=16\n#host-name=accel-ppp\n#dir300_quirk=0\n#secret=\n#dataseq=allow\n#reorder-timeout=0\n#ip-pool=l2tp\n#ipv6-pool=l2tp\n#ipv6-pool-delegate=l2tp\n#ifname=l2tp%d\n\n[sstp]\nverbose=2\n#cert-hash-proto=sha1,sha256\n#cert-hash-sha1=\n#cert-hash-sha256=\n#accept=ssl,proxy\n#ssl-protocol=tls1,tls1.1,tls1.2,tls1.3\n#ssl-dhparam=/etc/ssl/dhparam.pem\n#ssl-ecdh-curve=prime256v1\n#ssl-ciphers=DEFAULT\n#ssl-prefer-server-ciphers=0\n#ssl-ca-file=/etc/ssl/sstp-ca.crt\n#ssl-pemfile=/etc/ssl/sstp-cert.pem\n#ssl-keyfile=/etc/ssl/sstp-key.pem\n#host-name=domain.tld\n#http-error=allow\n#timeout=60\n#hello-interval=60\n#ip-pool=sstp\n#ipv6-pool=sstp\n#ipv6-pool-delegate=sstp\nifname=sstp%d\n\n[ipoe]\nverbose=1\nusername=ifname\n#password=username\nlease-time=600\n#renew-time=300\n#rebind-time=525\nmax-lease-time=3600\n#unit-cache=1000\n#l4-redirect-table=4\n#l4-redirect-ipset=l4\n#l4-redirect-on-reject=300\n#l4-redirect-ip-pool=pool1\nshared=0\nifcfg=1\nmode=L2\nstart=dhcpv4\n#start=up\n#ip-unnumbered=1\n#proxy-arp=0\n#nat=0\n#proto=100\n#relay=10.10.10.10\n#vendor=Custom\n#weight=0\n#attr-dhcp-client-ip=DHCP-Client-IP-Address\n#attr-dhcp-router-ip=DHCP-Router-IP-Address\n#attr-dhcp-mask=DHCP-Mask\n#attr-dhcp-lease-time=DHCP-Lease-Time\n#attr-dhcp-renew-time=DHCP-Renewal-Time\n#attr-dhcp-rebind-time=DHCP-Rebinding-Time\n#attr-dhcp-opt82=DHCP-Option82\n#attr-dhcp-opt82-remote-id=DHCP-Agent-Remote-Id\n#attr-dhcp-opt82-circuit-id=DHCP-Agent-Circuit-Id\n#attr-l4-redirect=L4-Redirect\n#attr-l4-redirect-table=4\n#attr-l4-redirect-ipset=l4-redirect\n#lua-file=/etc/accel-ppp.lua\n#offer-delay=0,100:100,200:200,-1:1000\n#vlan-mon=eth0,10-200\n#vlan-timeout=60\n#vlan-name=%I.%N\n#ip-pool=ipoe\n#ipv6-pool=ipoe\n#ipv6-pool-delegate=ipoe\n#idle-timeout=0\n#session-timeout=0\n#soft-terminate=0\n#check-mac-change=1\n#calling-sid=mac\n#local-net=192.168.0.0/16\ninterface=eth0\n\n[dns]\n#dns1=172.16.0.1\n#dns2=172.16.1.1\n\n[wins]\n#wins1=172.16.0.1\n#wins2=172.16.1.1\n\n[radius]\n#dictionary=/usr/local/share/accel-ppp/radius/dictionary\nnas-identifier=accel-ppp\nnas-ip-address=127.0.0.1\ngw-ip-address=192.168.100.1\nserver=127.0.0.1,testing123,auth-port=1812,acct-port=1813,req-limit=50,fail-timeout=0,max-fail=10,weight=1\ndae-server=127.0.0.1:3799,testing123\nverbose=1\n#timeout=3\n#max-try=3\n#acct-timeout=120\n#acct-delay-time=0\n#acct-delay-start=0\n#acct-on=0\n#acct-interim-interval=0\n#acct-interim-jitter=0\n#default-realm=\n#strip-realm=0\n#attr-tunnel-type=My-Tunnel-Type\n#nas-port-id-in-req=1\n\n[client-ip-range]\n0.0.0.0/0\n\n[ip-pool]\ngw-ip-address=192.168.0.1\n#vendor=Cisco\n#attr=Cisco-AVPair\nattr=Framed-Pool\n192.168.0.2-255\n192.168.1.1-255,name=pool1\n192.168.2.1-255,name=pool2\n192.168.3.1-255,name=pool3\n192.168.4.1-255,name=pool4,next=pool1\n192.168.4.0/24\n\n[log]\nlog-file=/tmp/accel-ppp.log\nlog-emerg=/tmpaccel-ppp-emerg.log\nlog-fail-file=/tmp/accel-ppp-auth-fail.log\n#log-debug=/dev/stdout\n#syslog=accel-pppd,daemon\n#log-tcp=127.0.0.1:3000\ncopy=1\n#color=1\n#per-user-dir=per_user\n#per-session-dir=per_session\n#per-session=1\nlevel=7\n\n[log-pgsql]\nconninfo=user=log\nlog-table=log\n\n[pppd-compat]\nverbose=1\n#ip-pre-up=/etc/ppp/ip-pre-up\nip-up=/etc/ppp/ip-up\nip-down=/etc/ppp/ip-down\n#ip-change=/etc/ppp/ip-change\nradattr-prefix=/var/run/radattr\n#fork-limit=16\n\n[chap-secrets]\ngw-ip-address=192.168.100.1\n#chap-secrets=/etc/ppp/chap-secrets\n#encrypted=0\n#username-hash=md5\n\n[shaper]\n#attr=Filter-Id\n#down-burst-factor=0.1\n#up-burst-factor=1.0\n#latency=50\n#mpu=0\n#mtu=0\n#r2q=10\n#quantum=1500\n#moderate-quantum=1\n#cburst=1534\n#ifb=ifb0\nup-limiter=police\ndown-limiter=tbf\n#leaf-qdisc=sfq perturb 10\n#leaf-qdisc=fq_codel [limit PACKETS] [flows NUMBER] [target TIME] [interval TIME] [quantum BYTES] [[no]ecn]\n#rate-multiplier=1\n#fwmark=1\n#rate-limit=2048/1024\nverbose=1\n\n[cli]\nverbose=1\ntelnet=127.0.0.1:2000\ntcp=127.0.0.1:2001\n#password=123\n#sessions-columns=ifname,username,ip,ip6,ip6-dp,type,state,uptime,uptime-raw,calling-sid,called-sid,sid,comp,inbound-if,service-name,rx-bytes,tx-bytes,rx-bytes-raw,tx-bytes-raw,rx-pkts,tx-pkts,netns,vrf\n\n[snmp]\nmaster=0\nagent-name=accel-ppp\n\n[connlimit]\nlimit=10/min\nburst=3\ntimeout=60\n\n[ipv6-pool]\n#gw-ip6-address=fc00:0:1::1\n#vendor=\n#attr-prefix=Delegated-IPv6-Prefix-Pool\n#attr-address=Stateful-IPv6-Address-Pool\nfc00:0:1::/48,64\nfc00:0:2::/48,64,name=pool1\nfc00:0:3::/48,64,name=pool2,next=pool1\ndelegate=fc00:1::/36,48\ndelegate=fc00:2::/36,48,name=pool3\ndelegate=fc00:3::/36,48,name=pool4,next=pool3\n\n[ipv6-dns]\n#fc00:1::1\n#fc00:1::2\n#fc00:1::3\n#dnssl=suffix1.local.net\n#dnssl=suffix2.local.net.\n\n[ipv6-dhcp]\nverbose=1\npref-lifetime=604800\nvalid-lifetime=2592000\nroute-via-gw=1\n"
  },
  {
    "path": "packages/net/accel-ppp/files/accel-ppp.init",
    "content": "#!/bin/sh /etc/rc.common\n\nSTART=35\nSTOP=85\nUSE_PROCD=1\n\nstart_service() {\n\tprocd_open_instance\n\tprocd_set_param command /usr/sbin/accel-pppd -d -c /etc/accel-ppp/accel-ppp.conf -p /var/run/accel-pppd.pid\n\tprocd_set_param respawn\n\tprocd_close_instance\n}\n\nstop_service(){\n\tkillall -9 accel-pppd >/dev/null 2>&1\n\trm -rf /var/run/accel-pppd.pid\n}\n\nreload_service() {\n\tprocd_send_signal accel-ppp\n}\n\nservice_triggers()\n{\n\tprocd_add_reload_trigger accel-ppp\n}\n"
  },
  {
    "path": "packages/net/accel-ppp/patches/0001-build-with-musl.patch",
    "content": "Index: accel-ppp-1.13.0/accel-pppd/ctrl/ipoe/arp.c\n===================================================================\n--- accel-ppp-1.13.0.orig/accel-pppd/ctrl/ipoe/arp.c\n+++ accel-ppp-1.13.0/accel-pppd/ctrl/ipoe/arp.c\n@@ -14,9 +14,9 @@\n #include <sys/socket.h>\n #include <sys/ioctl.h>\n #ifdef HAVE_GOOD_IFARP\n-#include <linux/if_arp.h>\n+#include <net/if_arp.h>\n #endif\n-#include <linux/if_packet.h>\n+#include <netpacket/packet.h>\n \n #include \"list.h\"\n #include \"triton.h\"\nIndex: accel-ppp-1.13.0/accel-pppd/ctrl/ipoe/ipoe.c\n===================================================================\n--- accel-ppp-1.13.0.orig/accel-pppd/ctrl/ipoe/ipoe.c\n+++ accel-ppp-1.13.0/accel-pppd/ctrl/ipoe/ipoe.c\n@@ -16,7 +16,7 @@\n #include <sys/ioctl.h>\n #include <linux/if.h>\n #ifdef HAVE_GOOD_IFARP\n-#include <linux/if_arp.h>\n+#include <net/if_arp.h>\n #endif\n #include <linux/route.h>\n \nIndex: accel-ppp-1.13.0/accel-pppd/ctrl/pppoe/pppoe.c\n===================================================================\n--- accel-ppp-1.13.0.orig/accel-pppd/ctrl/pppoe/pppoe.c\n+++ accel-ppp-1.13.0/accel-pppd/ctrl/pppoe/pppoe.c\n@@ -8,6 +8,7 @@\n #include <fcntl.h>\n #include <sys/socket.h>\n #include <sys/ioctl.h>\n+#include <linux/sockios.h>\n #include <net/ethernet.h>\n #include <netpacket/packet.h>\n #include <arpa/inet.h>\nIndex: accel-ppp-1.13.0/accel-pppd/include/ap_session.h\n===================================================================\n--- accel-ppp-1.13.0.orig/accel-pppd/include/ap_session.h\n+++ accel-ppp-1.13.0/accel-pppd/include/ap_session.h\n@@ -5,6 +5,7 @@\n \n #include \"triton.h\"\n #include \"ap_net.h\"\n+#include \"pthread.h\"\n \n //#define AP_SESSIONID_LEN 16\n #define AP_IFNAME_LEN 16\n@@ -124,9 +125,9 @@ struct ap_session\n \n struct ap_session_stat\n {\n-\tunsigned int active;\n-\tunsigned int starting;\n-\tunsigned int finishing;\n+\tuint32_t active;\n+\tuint32_t starting;\n+\tuint32_t finishing;\n };\n \n \nIndex: accel-ppp-1.13.0/accel-pppd/ppp/ppp.c\n===================================================================\n--- accel-ppp-1.13.0.orig/accel-pppd/ppp/ppp.c\n+++ accel-ppp-1.13.0/accel-pppd/ppp/ppp.c\n@@ -10,6 +10,7 @@\n #include <signal.h>\n #include <sys/socket.h>\n #include <sys/ioctl.h>\n+#include <linux/sockios.h>\n #include \"linux_ppp.h\"\n \n #include \"crypto.h\"\nIndex: accel-ppp-1.13.0/accel-pppd/session_backup.c\n===================================================================\n--- accel-ppp-1.13.0.orig/accel-pppd/session_backup.c\n+++ accel-ppp-1.13.0/accel-pppd/session_backup.c\n@@ -4,7 +4,7 @@\n #include <errno.h>\n #include <unistd.h>\n #include <netinet/in.h>\n-\n+#include \"pthread.h\"\n #include \"events.h\"\n #include \"triton.h\"\n #include \"log.h\"\n"
  },
  {
    "path": "packages/net/cellled/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=cellled\nPKG_VERSION:=0.0.1\nPKG_RELEASE:=1\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  PKGARCH:=all\n  TITLE:=LED cellular signal signal strength\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tIndicate LED cellular signal signal strength.\nendef\n\ndefine Package/$(PKG_NAME)/conffiles\n\t/etc/config/cellled\nendef\n\n\ndefine Build/Prepare\nendef\n\ndefine Build/Configure\nendef\n\ndefine Build/Compile\nendef\n\n\ndefine Package/$(PKG_NAME)/install\n\t$(CP) ./root/* $(1)/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n\n"
  },
  {
    "path": "packages/net/cellled/root/etc/config/cellled",
    "content": "\nconfig device\n\toption data_type 'qmi'\n\toption device_qmi '/dev/cdc-wdm0'\n\toption timeout '5'\n\nconfig rssi_led\n\toption led 'blue:sys'\n\toption rssi_min '20'\n\toption rssi_max '39'\n\nconfig rssi_led\n        option led 'blue:wlan0'\n        option rssi_min '40'\n        option rssi_max '59'\n\nconfig rssi_led\n        option led 'blue:wlan1'\n        option rssi_min '60'\n        option rssi_max '79'\n\nconfig rssi_led\n        option led 'blue:net'\n        option rssi_min '80'\n        option rssi_max '100'\n"
  },
  {
    "path": "packages/net/cellled/root/etc/init.d/cellled",
    "content": "#!/bin/sh /etc/rc.common\n# Copyright (c) 2011-2015 OpenWrt.org\n\nSTART=99\n\nstart() {\n\tif [ ! -f \"/var/run/cellled.pid\" ]; then\n\t\t/usr/bin/cellled >/dev/null 2>&1 &\n\t\tPID=$(pgrep -f /usr/bin/cellled)\n\t\tif [ $PID ]; then\n\t\t\techo $PID > /var/run/cellled.pid\n\t\tfi\n\tfi\n\treturn 0\n}\n\nstop() {\n\trm -f /var/run/cellled.pid\n\tkill $(pgrep -f /usr/bin/cellled) >/dev/null 2>&1\n\t/etc/init.d/led start\n}\n\nrestart(){\n\tstop\n\tsleep 1\n\tstart\n}\n\nboot() {\n\t(sleep 5 && start >/dev/null 2>&1) &\n\treturn 0\n}\n"
  },
  {
    "path": "packages/net/cellled/root/usr/bin/cellled",
    "content": "#!/bin/sh\nwhile true :; do\n\t/usr/share/cellled.sh >/dev/null 2>&1\ndone\n"
  },
  {
    "path": "packages/net/cellled/root/usr/share/cellled.sh",
    "content": "#!/bin/sh\n\n# get data\nget_data(){\n\tDATATYPE=$(uci -q get cellled.@device[0].data_type)\n\tcase $DATATYPE in\n\t\tmm)\n\t\t\tPORT=$(uci -q get cellled.@device[0].device_mm)\n\t\t\tRSSI=$(mmcli -m ${PORT} -J | jsonfilter -e '@[\"modem\"][\"generic\"][\"signal-quality\"][\"value\"]')\n\t\t;;\n\t\tqmi)\n\t\t\tPORT=$(uci -q get cellled.@device[0].device_qmi)\n\t\t\tQMICTL=\"qmicli -p -d $PORT\"\n\t\t\tRSSIDB=$($QMICTL --nas-get-signal-info | awk -F [\\'\\:\\ ] '/RSSI:/{print $4}')\n\t\t\tRSSI=$(echo $RSSIDB |awk '{printf \"%d\\n\", (100*(1-(-51 - $1)/(-50 - -113)))}')\n\t\t;;\n\t\tuqmi)\n\t\t\tPORT=$(uci -q get cellled.@device[0].device_qmi)\n\t\t\tRSSIDB=$(uqmi -d ${PORT} --get-signal-info | jsonfilter -e '@[\"rssi\"]')\n\t\t\tRSSI=$(echo $RSSIDB |awk '{printf \"%d\\n\", (100*(1-(-51 - $1)/(-51 - -113)))}')\n\t\t;;\n\t\tserial)\n\t\t\tPORT=$(uci -q get cellled.@device[0].device)\n\t\t\tRSSI=$(gcom -d ${PORT} -s /etc/gcom/getstrength.gcom | awk -F [:,] '/CSQ:/{printf \"%.0f\\n\", $2*100/31}')\n\t\t;;\n\tesac\n}\n\n# get rssi levels\nget_led_level(){\n        RSSI_MIN=$(uci -q get cellled.@rssi_led[$n].rssi_min)\n        RSSI_MAX=$(uci -q get cellled.@rssi_led[$n].rssi_max)\n}\n\n# set rgb light\nset_led_rgb(){\n        if [ $RSSI -ge $RSSI_MIN -a $RSSI -le $RSSI_MAX ]; then\n              \tLED_ON=$(echo $RSSI_MIN $RSSI_MAX $RSSI | awk '{printf \"%.0f\\n\", (255/($2-$1)*($3-$1))}')\n\t\tSTATE=true\n\telse\n\t\tLED_ON=0\n\t\tSTATE=false\n\tfi\n}\n\n# set linear light\nset_led_linear(){\n        if [ $RSSI -ge $RSSI_MIN -a $RSSI -lt $RSSI_MAX ]; then\n                STATE=true\n        elif [ $RSSI -ge $RSSI_MAX ]; then\n                STATE=true\n        elif [ $RSSI -lt $RSSI_MIN ]; then\n                STATE=false\n        else\n                STATE=false\n        fi\n}\n\n# set linear led level\nif_linear(){\n\tLED=$(uci -q get cellled.@rssi_led[$n].led)\n\tget_led_level\n\tset_led_linear\n\tif [ $STATE = true -a $LED ]; then\n\t\tLED_ON=255\n\t\techo $LED_ON > /sys/class/leds/$LED/brightness\n\telse\n\t\tLED_ON=0\n\t\techo $LED_ON > /sys/class/leds/$LED/brightness\n\tfi\n\n}\n\n# set grb led level\nif_rgb(){\n\tPWM=$(uci -q get cellled.@device[0].pwm_mode)\n\tLED_R=$(uci -q get cellled.@device[0].red_led)\n\tLED_G=$(uci -q get cellled.@device[0].green_led)\n\tLED_B=$(uci -q get cellled.@device[0].blue_led)\n\tcase $TYPE in\n\t\tpoor)\n\t\t\tget_led_level\n\t\t\tset_led_rgb\n\t\t\tif [ $STATE = true ]; then\n\t\t\t\tR=255\n\t\t\t\tG=0\n\t\t\t\tif [ \"$PWM\" = \"1\" ]; then\n\t\t\t\t\tB=$((255-$LED_ON))\n\t\t\t\telse\n\t\t\t\t\tB=255\n\t\t\t\tfi\n\t\t\tfi\n\t\t;;\n\t\tbad)\n\t\t\tget_led_level\n\t\t\tset_led_rgb\n\t\t\tif [ $STATE = true ]; then\n\t\t\t\tR=255\n\t\t\t\tif [ \"$PWM\" = \"1\" ]; then\n\t\t\t\t\tG=$LED_ON\n\t\t\t\telse\n\t\t\t\t\tG=0\n\t\t\t\tfi\n\t\t\t\tB=0\n\t\t\tfi\n\t\t;;\n\t\tfair)\n\t\t\tget_led_level\n\t\t\tset_led_rgb\n\t\t\tif [ $STATE = true ]; then\n\t\t\t\tif [ \"$PWM\" = \"1\" ]; then\n\t\t\t\t\tR=$((255-$LED_ON))\n\t\t\t\telse\n\t\t\t\t\tR=255\n\t\t\t\tfi\n\t\t\t\tG=255\n\t\t\t\tB=0\n\t\t\tfi\n\t\t;;\n\t\tgood|best|excellend)\n\t\t\tget_led_level\n\t\t\tset_led_rgb\n\t\t\tif [ $STATE = true ]; then\n\t\t\t\tR=0\n\t\t\t\tG=255\n\t\t\t\tB=0\n\t\t\tfi\n\t\t;;\n\tesac\n\tif [ ! $R ]; then R=0; fi\n\tif [ ! $G ]; then G=0; fi\n\tif [ ! $B ]; then B=0; fi\n}\n\n# select type led (rgb or linear)\nselect_led(){\n\tif [ \"$RGB_LED\" = \"1\" ]; then\n\t\tTYPE=$(uci -q get cellled.@rssi_led[$n].type)\n\t\tif_rgb\n\telse\n\t\tif_linear\n\tfi\n}\n\n# get param\nget_param(){\n\tRSSI=$RSSI\n\tif [ $RSSI -lt 0 ]; then\n\t\tRSSI=0\n\telif [ $RSSI -gt 100 ]; then\n\t\tRSSI=100\n\tfi\n\tLED_ON=0\n\tRGB_LED=$(uci -q get cellled.@device[0].rgb_led)\n\tSECTIONS=$(uci show cellled | awk -F [\\]\\[\\@=] '/=rssi_led/{print $3}')\n\tTIMEOUT=$(uci -q get cellled.@device[0].timeout)\n\tif [ -z $RSSI ]; then\n        \texit 2\n\tfi\n}\n\nget_data\nget_param\nfor n in $SECTIONS; do\n\tselect_led\ndone\nif [ \"$RGB_LED\" = \"1\" -a $LED_R -a $LED_G -a $LED_B ]; then\n\t#echo \"${LED_R}=${R} ${LED_G}=${G} ${LED_B}=${B}\"\n        echo $R > /sys/class/leds/$LED_R/brightness\n        echo $G > /sys/class/leds/$LED_G/brightness\n        echo $B > /sys/class/leds/$LED_B/brightness\nfi\n\nsleep $TIMEOUT\n"
  },
  {
    "path": "packages/net/ethstatus/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=ethstatus\nPKG_VERSION:=0.4.9\nPKG_RELEASE:=1\n\nPKG_SOURCE:=$(PKG_NAME)_$(PKG_VERSION).tar.xz\nPKG_SOURCE_URL:=http://deb.debian.org/debian/pool/main/e/ethstatus/\nPKG_HASH:=4a85051f9d704c88e770577f18caaf4b97583e622a919dfdc87e0bd6b8166722\n\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)\n\nPKG_MAINTAINER:=Paul Spooren <mail@aparcar.org>\nPKG_LICENSE:=GPL-2.0-only\nPKG_LICENSE_FILES:=COPYING\n\nPKG_BUILD_PARALLEL:=1\n\ninclude $(INCLUDE_DIR)/package.mk\ninclude $(INCLUDE_DIR)/nls.mk\n\ndefine Package/$(PKG_NAME)\n  SECTION:=utils\n  CATEGORY:=Utilities\n  TITLE:=console-based ethernet statistics monitor\n  URL:=https://packages.debian.org/unstable/ethstatus\n  DEPENDS:=+libncurses\nendef\n\ndefine Package/$(PKG_NAME)/description\nEthstatus is a console-based monitoring utility for\ndisplaying statistical data of the ethernet interface\non a quantity basis. It is similar to iptraf but is meant\nto run as a permanent console task to monitor the network\nload.\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/net/ethstatus/patches/0001-version.patch",
    "content": "--- ethstatus-0.4.9.orig/ethstatus.h\n+++ ethstatus-0.4.9/ethstatus.h\n@@ -1,4 +1,4 @@\n-#define VERSION \"0.4.8\"\n+#define VERSION \"0.4.9\"\n #define AUTHOR \"Gabriel Montenegro / Christoph Haas / Marcio Souza\"\n \n #define LED_RX    1\n"
  },
  {
    "path": "packages/net/ifmetric/Makefile",
    "content": "include $(TOPDIR)/rules.mk\r\n\r\nPKG_NAME:=ifmetric\r\nPKG_VERSION:=0.3.0\r\nPKG_RELEASE:=1\r\n\r\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\r\nPKG_LICENSE:=LICENSE\r\nPKG_LICENSE_FILES:=COPYING\r\n\r\nPKG_SOURCE_PROTO:=git\r\nPKG_SOURCE_URL:=https://github.com/mshuler/ifmetric.git\r\nPKG_SOURCE_VERSION:=5c779caf7665f53995b26104124af17f6e49e552\r\n\r\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\r\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\r\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\r\n\r\nPKG_INSTALL:=1\r\nPKG_BUILD_PARALLEL:=1\r\n\r\nPKG_FIXUP:=autoreconf\r\n\r\ninclude $(INCLUDE_DIR)/package.mk\r\n\r\ndefine Package/$(PKG_NAME)\r\n  SECTION:=net\r\n  CATEGORY:=Network\r\n  TITLE:=Tool for setting the metrics of all IPv4 routes\r\n  URL:=http://0pointer.de/lennart/projects/ifmetric/\r\nendef\r\n\r\ndefine Package/$(PKG_NAME)/description\r\n\tifmetric is a Linux tool for setting the metrics of all IPv4 routes\r\n\tattached to a given network interface at once. This may be used to\r\n\tchange the priority of routing IPv4 traffic over the interface. Lower\r\n\tmetrics correlate with higher priorities. and min/max network usage.\r\nendef\r\n\r\nCONFIGURE_ARGS+= \\\r\n\t--disable-lynx\r\n\r\ndefine Package/$(PKG_NAME)/install\r\n\t$(INSTALL_DIR) $(1)/usr/bin\r\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/src/$(PKG_NAME) $(1)/usr/bin/\r\nendef\r\n\r\n$(eval $(call BuildPackage,$(PKG_NAME)))"
  },
  {
    "path": "packages/net/ifmetric/patches/0001-nlrequest.c_packet-too-small_fix",
    "content": "Description: Fix \"NETLINK: Packet too small or truncated!\" error.\nAuthor: Benedek László <benedekl@gmail.com>\nBug-Debian: http://bugs.debian.org/514197\nIndex: ifmetric/src/nlrequest.c\n===================================================================\n--- ifmetric.orig/src/nlrequest.c\t2013-09-05 14:10:42.000000000 -0500\n+++ ifmetric/src/nlrequest.c\t2013-09-05 14:19:06.573420862 -0500\n@@ -44,7 +44,7 @@\n \n     for (;;) {\n         int bytes;\n-        char replybuf[2048];\n+        char replybuf[4096];\n         struct nlmsghdr *p = (struct nlmsghdr *) replybuf;\n         \n         if ((bytes = recv(s, &replybuf, sizeof(replybuf), 0)) < 0) {\n"
  },
  {
    "path": "packages/net/maxminddb-dump-country/Makefile",
    "content": "include $(TOPDIR)/rules.mk\r\n\r\nPKG_NAME:=maxminddb-dump-country\r\nPKG_VERSION:=1\r\nPKG_RELEASE:=1\r\n\r\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\r\nPKG_LICENSE:=LICENSE\r\nPKG_LICENSE_FILES:=COPYING\r\n\r\nPKG_SOURCE_PROTO:=git\r\nPKG_SOURCE_URL:=https://github.com/vel21ripn/maxminddb-dump-country.git\r\nPKG_SOURCE_VERSION:=0f4c8aa54e30c65ee73b2d08c2a44a2f0ad1acd0\r\n\r\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\r\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\r\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\r\n\r\n#PKG_INSTALL:=1\r\n#PKG_BUILD_PARALLEL:=1\r\n\r\n#PKG_FIXUP:=autoreconf\r\n#PKG_BUILD_DEPENDS:=libmaxminddb/host\r\n\r\ninclude $(INCLUDE_DIR)/package.mk\r\n\r\ndefine Package/$(PKG_NAME)\r\n  SECTION:=net\r\n  CATEGORY:=Network\r\n  DEPENDS:= +libmaxminddb\r\n  TITLE:=Tool for convert maxmindDB to xt_geoip\r\n  URL:=https://github.com/vel21ripn/maxminddb-dump-country\r\nendef\r\n\r\ndefine Package/$(PKG_NAME)/description\r\n\tDump maxmind DB for linux iptables xt_geoip\r\n\tThe program is designed to convert the GeoLite2-Country.mmdb file into a dataset for xtables-addons/xt_geoip.\r\n\tIt is also possible to get files GeoLite2-Country-Blocks-IPv4.csv, GeoLite2-Country-Blocks-IPv6.csv, GeoLite2-Country-Locations-en.csv\r\nendef\r\n\r\ndefine Build/Compile\r\n\t$(TARGET_CC) $(TARGET_CFLAGS) -Wall -lmaxminddb \\\r\n\t\t-o $(PKG_BUILD_DIR)/xt_geoip_build_maxmind $(PKG_BUILD_DIR)/xt_geoip_build_maxmind.c \r\nendef\r\n\r\ndefine Package/$(PKG_NAME)/install\r\n\t$(INSTALL_DIR) $(1)/usr/bin\r\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/xt_geoip_build_maxmind $(1)/usr/bin/\r\nendef\r\n\r\n$(eval $(call BuildPackage,$(PKG_NAME)))\r\n"
  },
  {
    "path": "packages/net/modeminfo/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=modeminfo\nPKG_VERSION:=0.4.6\nPKG_RELEASE:=2\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)/Default\n  PKGARCH:=all\n  SECTION:=net\n  CATEGORY:=Network\n  SUBMENU:=Telephony\nendef\n\ndefine Package/$(PKG_NAME)\n  $(call Package/$(PKG_NAME)/Default)\n  DEPENDS:=+comgt\n  TITLE:=Signal information for 3G/LTE dongle\nendef\n\ndefine Package/$(PKG_NAME)-telegram\n  $(call Package/$(PKG_NAME)/Default)\n  DEPENDS:=+modeminfo +telegrambot\n  TITLE:= Plugin for telegrambot\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tSignal information for 3G/LTE dongle\nendef\n\ndefine Package/$(PKG_NAME)/conffiles\n/etc/config/modeminfo\nendef\n\ndefine SerialPlugin\n  define Package/$(PKG_NAME)-serial-$(1)\n    $$(call Package/$(PKG_NAME)/Default)\n    DEPENDS:= +modeminfo $(2) +atinout\n    TITLE:=$(3) serial addon for modeminfo\n  endef\n  define Package/$(PKG_NAME)-serial-$(1)/install\n\t$(INSTALL_DIR) $$(1)/usr/share/$(PKG_NAME)/scripts\n\t$(CP) ./root/usr/share/$(PKG_NAME)/scripts/$(4)* $$(1)/usr/share/$(PKG_NAME)/scripts\n  endef\n  $$(eval $$(call BuildPackage,$(PKG_NAME)-serial-$(1)))\nendef\n\ndefine Build/Prepare\nendef\n\ndefine Build/Configure\nendef\n\ndefine Build/Compile\nendef\n\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_DIR) $(1)/usr/share/modeminfo\n\t$(INSTALL_DIR) $(1)/usr/share/modeminfo/scripts\n\t$(INSTALL_DIR) $(1)/etc/config\n\t$(CP) ./root/usr/bin/* $(1)/usr/bin\n\t$(CP) ./root/usr/share/modeminfo/modeminfo $(1)/usr/share/modeminfo/\n\t$(CP) ./root/usr/share/modeminfo/scripts/*.gcom  $(1)/usr/share/modeminfo/scripts/\n\t$(CP) ./root/usr/share/modeminfo/scripts/ch_to_band  $(1)/usr/share/modeminfo/scripts/\n\t$(CP) ./root/usr/share/modeminfo/modem.list  $(1)/usr/share/modeminfo/\n\t$(CP) ./root/usr/share/modeminfo/scripts/modeminfo  $(1)/usr/share/modeminfo/scripts/\n\t$(CP) ./root/usr/share/modeminfo/scripts/GENERIC* $(1)/usr/share/modeminfo/scripts/\n\t$(CP) ./root/etc/config/* $(1)/etc/config\nendef\n\ndefine Package/$(PKG_NAME)-telegram/install\n\t$(INSTALL_DIR) $(1)/usr/lib/telegrambot/plugins\n\t$(CP) ./root/usr/lib/telegrambot/plugins/* $(1)/usr/lib/telegrambot/plugins\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n$(eval $(call SerialPlugin,simcom,+kmod-usb-serial-option +kmod-usb-acm,SIMCOM SIM7600/SIM7906/SIM7912/A7600/A7906,SIMCOM))\n$(eval $(call SerialPlugin,huawei,+kmod-usb-serial-option,HUAWEI,HUAWEI))\n$(eval $(call SerialPlugin,quectel,+kmod-usb-serial-option,QUECTEL,QUECTEL))\n$(eval $(call SerialPlugin,fibocom,+kmod-usb-serial-option,FIBOCOM NL668/678/FM150/FM190,FIBOCOM))\n$(eval $(call SerialPlugin,telit,+kmod-usb-serial-option,TELIT LN940/T77W678/HP4220,QUALCOMM))\n$(eval $(call SerialPlugin,xmm,+kmod-usb-serial-option +kmod-usb-acm,FIBOCOM L850/L860/FM350 Intel XMM chips,INTEL))\n#$(eval $(call SerialPlugin,mikrotik,+kmod-usb-acm,MikroTik R11e-LTE/R11e-LTE6,MIKROTIK))\n$(eval $(call SerialPlugin,meig,+kmod-usb-serial-option,MEIGLink SLM750-R2/SLM820/SLM828,MEIGLINK))\n$(eval $(call SerialPlugin,sierra,+kmod-usb-serial-qualcomm,Sierra Wireless EM7455,SIERRA))\n#$(eval $(call SerialPlugin,styx,+kmod-usb-serial-option,STYX MG8224,STYX))\n$(eval $(call SerialPlugin,dell,+kmod-usb-serial-option,Dell DW5821e,DELL))\n#$(eval $(call SerialPlugin,gosun,+kmod-usb-serial-option,Gosuncnwelink GM510,GOSUN))\n#$(eval $(call SerialPlugin,tw,+kmod-usb-serial-option,ThinkWill ML7820+,THINKWILL))\n#$(eval $(call SerialPlugin,yuge,+kmod-usb-serial-option,Yuge CLM920,YUGE))\n#$(eval $(call SerialPlugin,zte,+kmod-usb-serial-option,ZTE MF823/MF823D,ZTE))\n$(eval $(call SerialPlugin,thales,+kmod-usb-serial-option,Thales MV31-W T99W175,THALES))\n$(eval $(call BuildPackage,$(PKG_NAME)-telegram))\n"
  },
  {
    "path": "packages/net/modeminfo/root/etc/config/modeminfo",
    "content": "\nconfig general\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/bin/modeminfo",
    "content": "#!/bin/sh\n\n. /usr/share/libubox/jshn.sh\n\njson_load \"$(/usr/share/modeminfo/modeminfo)\"\njson_dump\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/lib/telegrambot/plugins/modeminfo.sh",
    "content": "#!/bin/sh\nDATA=$(/usr/share/modeminfo/modeminfo)\nSECTIONS=$(echo $(uci show modeminfo | awk -F [\\]\\[\\@=] '/=modeminfo/{print $3}'))\n\n# load channel to band converter\n. /usr/share/modeminfo/scripts/ch_to_band\n\nget_data(){\n\tjsonfilter -s \"$DATA\" -e \"MODE=$['modem'][$s]['mode']\" \\\n\t\t-e \"DEVICE=$['modem'][$s]['device']\" \\\n\t\t-e \"CH=$['modem'][$s]['arfcn']\" \\\n\t\t-e \"COPS=$['modem'][$s]['cops']\" \\\n\t\t-e \"PERS=$['modem'][$s]['csq_per']\" \\\n\t\t-e \"CA=$['modem'][$s]['lteca']\" \\\n\t\t-e \"RSSI=$['modem'][$s]['rssi']\" \\\n\t\t-e \"RSRP=$['modem'][$s]['rsrp']\" \\\n\t\t-e \"RSRQ=$['modem'][$s]['rsrq']\" \\\n\t\t-e \"SINR=$['modem'][$s]['sinr']\" \\\n\t\t-e \"ENBID=$['modem'][$s]['enbid']\" \\\n\t\t-e \"PCI=$['modem'][$s]['pci']\" \\\n\t\t-e \"CELL=$['modem'][$s]['cell']\" \\\n\t\t-e \"DIST=$['modem'][$s]['distance']\" \\\n\t\t-e \"SB=$['modem'][$s]['scc']\"\n}\n\nfor s in ${SECTIONS}; do\n\teval $(echo \"$(get_data)\" | sed -e 's/export//g')\n\tch_to_band $CH\n\techo -ne \"*Modem $(($s+1))*\\n\"\n\techo -ne \"*Device:* $DEVICE\\n*Operator:* $COPS 📶${PERS}% $MODE\\n\"\n\tcase $MODE in\n\t\tLTE)\n\t\t\tif [ $CA -ge 1 ]; then\n\t\t\t\tCA=\"$(($CA+1))xCA\"\n\t\t\t\tBAND=\"${SC}${SB}\"\n\t\t\t\techo -ne \"*CA:* $CA BAND ($BAND)\\n\"\n\t\t\telse\n\t\t\t\techo -ne \"*BAND:* $SC\\n\"\n\t\t\tfi\n\t\t\techo -ne \"*RSSI/RSRP:* ${RSSI}dBm/${RSRP}dBm\\n*RSRQ/SINR:* ${RSRQ}dB/${SINR}dB\\n*eNBID-Cell/PCI:* ${ENBID}-${CELL}/${PCI}\"\n\t\t\tif [ \"$DIST\" ] && [ \"$DIST\" != \"0.00\" ]; then\n\t\t\t\techo -ne \" ~${DIST}km\\n\"\n\t\t\telse\n\t\t\t\techo -ne \"\\n\"\n\t\t\tfi\n\t\t;;\n\t\tUMT*|WCD*|*HSP*|*HUS*)\n\t\t\techo -ne \"*BAND:* $SC\\n\"\n\t\t\techo -ne \"*RSSI/ECIO:* ${RSSI}dBm/${SINR}dB\\n*LAC/CID:* ${LAC}/${CID}\"\n\t\t;;\n\t\t*)\n\t\t\techo -ne \"*BAND:* $SC\\n\"\n\t\t\techo -ne \"*RSSI:*  ${RSSI}dBm\\n*LAC/CID:* ${LAC}/${CID}\"\n\t\t;;\n\tesac\n\t\t\t\n\techo -e \"\\n\"\ndone\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/modem.list",
    "content": "# Modem support list\n# Format: VIDPID;FAMILY\n# Intel XMM modems\n2cb70007;INTEL\n8087095a;INTEL\n# Fibocom modems\n2cb70104;FIBOCOM\n2cb70105;FIBOCOM\n2cb70109;FIBOCOM\n2cb7010a;FIBOCOM\n# Huawei modems\n12d11c25;HUAWEI\n12d115c1;HUAWEI\n12d11506;HUAWEI\n# DELL modems\n413c81d7;DELL\n413c81e4;DELL\n1bc71910;DELL\n1bc71911;DELL\n# Quectel modems\n05c69215;QUECTEL\n2c7c0125;QUECTEL\n2c7c0121;QUECTEL\n2c7c6026;QUECTEL\n2c7c0620;QUECTEL\n2c7c6000;QUECTEL\n2c7c0901;QUECTEL\n2c7c0306;QUECTEL\n2c7c0512;QUECTEL\n# SimCOM modems\n1e0e9001;SIMCOM\n1e0e9002;SIMCOM\n1e0e9003;SIMCOM\n1e0e9004;SIMCOM\n1e0e9005;SIMCOM\n1e0e9006;SIMCOM\n1e0e9007;SIMCOM\n1e0e9011;SIMCOM\n1e0e9016;SIMCOM\n1e0e9018;SIMCOM\n1e0e9019;SIMCOM\n# Telit LN940 LT4220\n1bc71900;QUALCOMM\n1bc71901;QUALCOMM\n# Yuge CLM920\n12864e3c;YUGE\n# ThinkWill ML7820\n19d20532;THINKWILL\n# MEIG SLM820\n2dee4d22;MEIGLINK\n2dee4d07;MEIGLINK\n2dee4d02;MEIGLINK\n# GOCUNCN Welink GM510\n305a1415;GOSUN\n# Sierra EM7455\n11999078;SIERRA\n11999079;SIERRA\n11999071;SIERRA\n119990e2;SIERRA\n119990e3;SIERRA\n# Fibocom FM350\n0e8d7126;INTEL_FM350\n0e8d7127;INTEL_FM350\n# Mikrotik modems\n2cd20001;MIKROTIK\n2cd20004;MIKROTIK\n# ZTE modems\n19d21404;ZTE\n19d21424;ZTE\n# Thales T99W175\n05c69025;THALES\n05c690d5;THALES\n1e2d00b7;THALES\n1e2d00b3;THALES\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/modeminfo",
    "content": "#!/bin/sh\n\ntrap 'cleanup >/dev/null 2>&1; exit 0' INT TERM\nSECTIONS=$(echo $(uci show modeminfo | awk -F [\\]\\[\\@=] '/=modeminfo/{print $3}'))\n\nget_vars(){\n\tfor v in device device_qmi qmi_trap decimail qmi_mode;  do\n\t\teval $v=$(uci -q get modeminfo.@modeminfo[${s}].${v} 2>/dev/null)\n\tdone\n}\n\n\ncleanup(){\n\tCURRENT_PID=$$\n\tps ww | grep \"/bin/sh.*modeminfo\" | grep -v grep | awk '{print $1}' | grep -v \"^$CURRENT_PID\\$\" | xargs -r kill -9 >/dev/null 2>&1\n\tps ww | grep \"atinout\" | grep -v grep | awk '{print $1}' | xargs -r kill -9 >/dev/null 2>&1\n}\n\n\nfunction json_status() {\n\tcat << EOF\n\t{\n\t\t\"device\": \"$DEVICE\",\n\t\t\"cops\": \"$COPS\",\n\t\t\"mode\": \"$MODE\",\n\t\t\"csq_per\": \"$CSQ_PER\",\n\t\t\"lac\": \"$LAC\",\n\t\t\"cid\": \"$CID\",\n\t\t\"rssi\": \"$CSQ_RSSI\",\n\t\t\"sinr\": \"$SINR\",\n\t\t\"rsrp\": \"$RSRP\",\n\t\t\"rsrq\": \"$RSRQ\",\n\t\t\"imei\": \"$IMEI\",\n\t\t\"reg\": \"$REGST\",\n\t\t\"csq_col\": \"$CSQ_COL\",\n\t\t\"arfcn\": \"$EARFCN\",\n\t\t\"chiptemp\": \"$CHIPTEMP\",\n\t\t\"firmware\": \"$FW\",\n\t\t\"bwdl\": \"$BWDL\",\n\t\t\"lteca\": \"$LTE_CA\",\n\t\t\"enbid\": \"$ENBID\",\n\t\t\"distance\": \"$DISTANCE\",\n\t\t\"cell\": \"$CELL\",\n\t\t\"scc\": \"$SCC\",\n\t\t\"bwca\": \"$BWCA\",\n\t\t\"iccid\": \"$ICCID\",\n\t\t\"imsi\": \"$IMSI\",\n\t\t\"pci\": \"$PCI\"\nEOF\n}\n\n\n\nfunction if_null() {\n        for var in DEVICE COPS MODE CSQ_PER LAC CSQ_RSSI SINR RSRP \\\n                        RSRQ IMEI REGST EARFCN CHIPTEMP FW BWDL ICCID IMSI; do\n                if [ \"x$(eval echo \\${$var})\" = \"x\" ]; then\n                        eval $var='--'\n                fi\n        done\n}\n\n\nqmi_device(){\n\t[ -n \"$qmi_mode\" ] || SCRIPT=/usr/share/modeminfo/scripts/modeminfo\n\t[ -r $SCRIPT ] && {\n\t\t. $SCRIPT\n\t} || {\n\t\texit 0\n\t}\n}\n\n\nargs(){\n    echo \"$#\"\n}\n\nARGSLEN=$(args $SECTIONS)\n\necho \"{ \\\"modem\\\" : [\"\n\nfor s in $SECTIONS; do\n\tm=$((${s} + 1))\n\tget_vars\n\tif [ $qmi_mode ]; then\n\t\tif [ \"$s\" ]; then\n\t\t\t/usr/bin/qminfo $device_qmi --json --${qmi_trap} \n\t\tfi\n\t\tif [ \"$ARGSLEN\" != \"${m}\" ]; then\n\t\t\techo \",\"\n\t\tfi\n\telse\n\t\tif [ \"$s\" ]; then\n\t\t\tqmi_device\n\t\t\tget_device_info\n\t\t\tget_data_in >/dev/null 2>&1\n\t\t\tif_null\n\t\t\tjson_status\n\t\tfi\n\t\tif [ \"$ARGSLEN\" = \"${m}\" ]; then\n\t\t\techo \"\t}\"\n\t\telse\n\t\t\techo \"\t},\"\n\t\tfi\n\t\t# flush modem variables\n\t\tunset BWCA SCC SC BWDx BWPx BWCx BWDL LTE_CA \\\n        \t        DEVICE COPS MODE CSQ_PER LAC CSQ_RSSI \\\n                \tSINR RSRP IMEI REGST EARFCN CHIPTEMP FW \\\n\t                ICCID IMSI RSRQ DISTANCE\n\tfi\ndone\n\necho \"]}\"\n\nDELAY=$(uci get modeminfo.@general[0].delay >/dev/null 2>&1)\n\nkill -9 $(pgrep atinout) >/dev/null 2>&1\n[ $DELAY ] || DELAY=0 && sleep $DELAY\n\nexit 0\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/DELL",
    "content": "modem_data(){\n        # hwinfo\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\tset -- $(echo \"$O\" | awk '\n\t\tBEGIN{IGNORECASE=1} $0 ~ /DELL|Telit/ {manufacturer=$1; getline model; getline fw}\n\t\t/^ICCID:/{iccid=$2; getline imei; getline imsi}\n\t\t/^PA:|^xo_therm_buf:/ {sub(/C/, \"\", $2); split($0,arr,\":\"); if (arr[2] !=\"\") temp = arr[2]; else temp=$2 }\n\t\tEND {print manufacturer \"|\" model \"|\" fw \"|\" iccid \"|\" imei \"|\" imsi \"|\" temp }')\n\tDEVICE=\"${1} ${2}\"; MANUF=${1}; MODEL=${2}; FW=${3}; ICCID=${4}; IMEI=${5}; IMSI=${6}; CHIPTEMP=${7}\n\tIFS=\"$OIFS\"; unset OIFS\n\tgeneric_data\n\t# get signal info\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\tset -- $(echo \"$O\" | awk -v mode=\"$MODE\" '\n\t\tBEGIN {\n\t\t\tlac=\"\"; cid=\"\"; earfcn=\"\"; pci=\"\"; bw=\"\"; bwc=\"\"; rsrp=\"\"; rssi=\"\"; rsrq=\"\"; sinr=\"\"; rscp=\"\"; csq=\"\"; lte_ca=0\n\t\t\tmap[3]=1; map[5]=2; map[10]=3; map[15]=4; map[20]=5\n\t\t}\n\t\t# LTE Carrier Aggregation\n\t\t/SCC[0-9]+ info:/ {lte_ca++}\n    \t\t# LTE \n\t\tmode == \"LTE\" {\n\t\t\tif (/\\+CEREG/) { gsub(/\"/, \"\"); split($0, arr, \",\"); lac = arr[3]; cid = arr[5]}\n\t\t\tif (/EARFCN\\(DL\\/UL\\):/ && earfcn == \"\") {split($0, arr, /[ \\/]/); earfcn = arr[3]}\n\t\t\tif (/PCI\\)/ && pci == \"\") {gsub(\"\\)\",\"\",$0); gsub(\"\\(\",\" \",$0); pci=$NF}\n\t\t\tif (/^RSRP:/ && rsrp == \"\") {sub(/dBm/, \"\", $2); rsrp = sprintf(\"%.0f\", $2)}\n\t\t\tif (/^RSSI:/ && rssi == \"\") {sub(/dBm/, \"\", $2); rssi = sprintf(\"%.0f\", $2)}\n\t\t\tif (/^RSRQ:/ && rsrq == \"\") {sub(/dB/, \"\", $2); rsrq = sprintf(\"%.0f\", $2)}\n\t\t\tif (/^RS-SINR:/ && sinr == \"\") {sub(/dB/, \"\", $2); sinr = sprintf(\"%.0f\", $2)}\n\t\t\tif (/^RS-SNR:/ && sinr == \"\") {sub(/dB/, \"\", $2);sinr = sprintf(\"%.0f\", $2)}\n\t\t\tif (/PCC info/ && bwc == \"\") { bwc = sprintf(\"%.0f\", $8); bw=map[bwc] }\n\t\t\tcell = sprintf(\"%d\", \"0x\" substr(cid, length(cid) - 1, 2)) \n\t\t\tenbid = sprintf(\"%d\", \"0x\" substr(cid, 1, length(cid) - 2))\n\t\t}\n    \n\t\t# Other mode (3G)\n\t\tmode != \"LTE\" {\n\t\t\tif (/\\+CREG/) {gsub(/\"/, \"\");split($0, arr, \",\");lac = arr[3]; cid = arr[4]}\n\t\t\tif (/Active channel/ && earfcn == \"\") {split($0, arr, /:/); gsub(/ /, \"\", arr[2]); earfcn = arr[2]}\n\t\t\tif (/QCSQ/ && csq == \"\") { split($0, arr, /[:,]/); rscp = arr[2]; sinr = arr[3]; rssi = arr[6] }\n\t\t\n\t\t}\n    \n\t\tEND {print lac \"|\" cid \"|\" earfcn \"|\" rssi \"|\" sinr \"|\" pci \"|\" bw \"|\" bwc \"|\" rsrp \"|\" rsrq \"|\" rscp \"|\" csq \"|\" lte_ca \"|\" cell \"|\" enbid}')\n\tLAC=${1}; CID=${2}; EARFCN=${3}; CSQ_RSSI=${4}; SINR=${5}; PCI=${6}; BWDL=${7}; BWDx=${8}; RSRP=${9}; RSRQ=${10}; LTE_CA=${13}\n\tCELL=${14}; ENBID=${15}\n\tIFS=\"$OIFS\"; unset OIFS\n\tif [ $LTE_CA -ge 1 ]; then\n\t\tOIFS=\"$IFS\"; IFS=\"|\"\n\t\tset -- $(echo \"$O\" | awk -v bwc=${BWDx:-0} '\n\t\t\t/^SCC\\d info:/ {for(i=1; i<=NF; i++) { if($i ~ /LTE_B/) { gsub(/[^0-9]/, \"\", $i); scc = scc \"+\" $i}} bwca_raw += sprintf(\"%.0f\", $8)\n\t\t\tbwca=bwca_raw+bwc}\n\t\t\tEND { print scc \"|\" bwca }')\n\t\tIFS=\"$OIFS\"; unset BWDx OIFS\n\t\tSCC=${1}; BWCA=${2}\n\tfi\n\n        if [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n                LAC=$(printf %d 0x$LAC)\n                CID=$(printf %d 0x$CID)\n        fi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/DELL.at",
    "content": "ATE0\nAT+CSQ\nAT+COPS=3,0; +COPS?\nAT+CREG=2; +CREG?\nAT+CGREG=2; +CGREG?\nAT+CEREG=2; +CEREG?\nAT$QCSQ\nAT^DEBUG?\nAT^CA_INFO?\nAT+TEMP?\nAT^TEMP?\nAT+CGMI; +CGMM; +CGMR; +ICCID; +CGSN; +CIMI\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/DIS_SIMCOM_A7XXX",
    "content": "function modem_data(){\n\tgeneric_data\n\tDATA=$(echo \"$O\" | grep -Ev '^$|^OK|^AT' | sort)\n\tMANUF=$(echo \"$DATA\" | awk 'c&&! --c;/ZRSSI/{c=4}')\n\tMODEL=$(echo \"$DATA\" | awk 'c&&! --c;/ZRSSI/{c=3}')\n\tFW=$(echo \"$DATA\" | awk '/CGMR/{print $2}')\n\tDEVICE=\"$MODEL\"\n\tTECH=$(echo \"$DATA\" | awk -F [,] '/\\+COPS/{print $NF}')\n\tICCID=$(echo \"$DATA\" | awk '/ICCID:/{print $2}')\n\tIMSI=$(echo \"$DATA\" | awk 'c&&! --c;/ZRSSI/{c=1}')\n\tIMEI=$(echo \"$DATA\" | awk 'c&&! --c;/ZRSSI/{c=2}')\n\tREGST=$(echo \"$DATA\" | awk -F[,] '/\\'$CREG'/ {print $2}')\n\tCHIPTEMP=$(echo \"$DATA\" | awk '/\\+CPMUTEMP/{printf \"%.0f\\n\", ($2/1000)}')\n\tEARFCN=$(echo \"$DATA\" | awk -F[,\\ ] '/^\\+CPSI/ {print $9}')\n\n\tcase $TECH in\n\t\t0|1) MODE=GSM ;;\n\t\t2) MODE=WCDMA ;;\n\t\t3) MODE=EDGE ;;\n\t\t4) MODE=HSDPA ;;\n\t\t5) MODE=HSUPA ;;\n\t\t6) MODE=HSDPA,HSUPA ;;\n\t\t7|9) MODE=LTE ;;\n\t\t8) MODE=HSPA+ ;;\n\tesac\n\t\n\tcase $TECH in\n\t\t7|9)\n\t\t\tPCI=$(echo \"$DATA\" | awk -F[,\\ ] '/^\\+CPSI/ {print $7}')\n\t\t\tBWDL=$(echo \"$DATA\" |awk -F[,\\ ] '/^\\+CPSI/ {print $10}')\n\t\t\tLAC=$(echo \"$DATA\" | awk -F [:,] '/\\+CEREG/{gsub(\"\\\"\",\"\");print $4}')\n\t\t\tCID=$(echo \"$DATA\" | awk -F [:,] '/\\+CEREG/{gsub(\"\\\"\",\"\");print $5}')\n\t\t\tENBx=$(echo $CID | sed -e 's/..$//')\n\t\t\tENBID=$(printf %d 0x$ENBx)\n\t\t\tCELL=$(printf %d 0x${CID: -2})\n\t\t\tCSQ_RSSI=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{print $4}')\n\t\t\tRSRQ=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{printf \"%.0f\\n\", $3}')\n\t\t\tRSRP=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{printf \"%.0f\\n\", $2}')\n\t\t\tSINR=$(echo \"$DATA\" | awk -F[,\\ ] '/^\\+CPSI/ {printf \"%.0f\\n\", $15}')\n\t\t\t# Carrier Aggregation\n\t\t\tSCx1=$(echo \"$DATA\" | awk -F [\\:\\;] '/ZCAINFO/{print $3}')\n\t\t\tSCx2=$(echo \"$DATA\" | awk -F [\\:\\;] '/ZCAINFO/{print $4}')\n\t\t\tSCC1=$(echo $SCx1 | awk -F [,] '{print $2}')\n\t\t\tcase $BWDL in\n\t\t\t\t1) BWCx=3 ;;\n\t\t\t\t2) BWCx=5 ;;\n\t\t\t\t3) BWCx=10 ;;\n\t\t\t\t4) BWCx=15 ;;\n\t\t\t\t5) BWCx=20 ;;\n\t\t\tesac\n\t\t\tif [ $SCC1 -ne 0 ]; then\n\t\t\t\tLTE_CA=1\n\t\t\t\tfor a in \"$SCx1 $SCx2\"; do\n\t\t\t\t\tSCH=$(echo $a | awk -F[,] '{print $3}')\n\t\t\t\t\tSCC=\"$SCC+$SCH\"\n\t\t\t\t\tSBW=$(echo $a | awk -F[,] '{print $5}')\n\t\t\t\t\tBWCx=$(($BWCx+$SBW))\n\t\t\t\tdone\n\t\t\t\tBWCA=$((BWCx+BWCA))\n\t\t\tfi\n\t\t;;\n\t\t2|4|6|8)\n\t\t\tLAC=$(echo \"$DATA\" | awk -F [:,] '/\\+CGREG/{gsub(\"\\\"\",\"\");print $4}')\n\t\t\tCID=$(echo \"$DATA\" | awk -F [:,] '/\\+CGREG/{gsub(\"\\\"\",\"\");print $5}')\n\t\t\tCSQ_RSSI=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{print $2}')\n\t\t\tSINR=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{printf \"%.0f\\n\", $3}')\n\t\t;;\n\tesac\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n                LAC=$(printf %d 0x$LAC)\n                CID=$(printf %d 0x$CID)\n        fi\t\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/DIS_SIMCOM_A7XXX.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?\nAT+CREG=2; +CREG?\nAT+CGREG=2; +CGREG?\nAT+CEREG=2; +CEREG?; +CSQ\nAT+CPSI?\nAT+ZRSSI\nAT+CGMI; +CGMM; +CGMR; +CIMI; +CGSN; +CICCID\nAT+CPMUTEMP\nAT+ZCAINFO?\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/FIBOCOM",
    "content": "# Fibocom modems NL668/NL678/NL952/FM150/FM190\n\nfunction modem_data(){\n\tgeneric_data\n\tOIFS=\"$IFS\"; IFS=\"|\" # Get hw and sim info\n\t# FIX regstate\n\tREGST=$(echo \"$O\" | awk -F',' '/(\\+CREG|\\+CGREG|\\+CEREG)/ {if (NF >= 5 && $NF ~ /^[0-9]$|^1[0-3]$/) {print $2; exit}}')\n\tset -- $(echo \"$O\" | awk -F'[:,]' '\n\t\t/\\+CGMI/ {gsub(\"\\\"\",\"\"); manuf=$2}\n\t\t/\\+CGMM/ {gsub(\"\\\"\",\"\"); model=$2}\n\t\t/\\+CGMR/ {gsub(\"\\\"\",\"\"); fw=$2}\n\t\t/\\+CIMI/ {gsub(\"\\\"\",\"\"); imsi=$2}\n\t\t/\\+CGSN/ {gsub(\"\\\"\",\"\"); imei=$2}\n\t\t/ICCID:/ {gsub(\"\\r\",\"\"); iccid=$2}\n\t\t/\\+MTSM/ {temp = sprintf(\"%0.f\\n\", $2)}\n\t\tEND { print manuf \"|\" model \"|\" fw \"|\" imsi \"|\" imei \"|\" iccid \"|\" temp }')\n\tDEVICE=\"${1} ${2}\"; MANUF=\"${1}\"; MODEL=\"${2}\"; FW=\"${3}\"; IMSI=\"${4}\"; IMEI=\"${5}\"; ICCID=\"${6}\" CHIPTEMP=${7}\n\tIFS=\"$OIFS\"; unset OIFS\n\tOIFS=\"$IFS\"; IFS=\"|\" # Get Signal info\n\tset -- $(echo \"$O\" | awk -v model=$MODEL -v mode=$MODE '\n\t\tBEGIN { map[15]=1; map[25]=2; map[50]=3; map[75]=4; map[100]=5 }\n\t\tmode ~ /LTE/ { # LTE mode\n\t\t\tif (/^\\PCC:/ && model~/952|FM/) {split($0, ca, \",\"); pch = ca[3]; pcica = ca[2] }\n                        if (/^\\SCC\\d/ && model~/952|FM/) {lte_ca++}\n\t\t\tif (/^\\+GTCAINFO: 1/) { lte_ca++; split($0, ca, \",\"); pch = ca[5]; pcica = ca[4] }\n\t\t\tif (/\\+GTCCINFO:/) { getline; getline; split($0, f, \",\")\n\t\t\t\tlac = (model~/952|FM/) ? sprintf(\"%d\",\"0x\"f[5]) : f[5]\n\t\t\t\tcid = (model~/952|FM/) ? sprintf(\"%d\",\"0x\"f[6]) : f[6]\n\t\t\t\tearfcn = (model~/952|FM/) ? sprintf(\"%d\",\"0x\"f[7]) : f[7]\n\t\t\t\tpci = (model~/952|FM/) ? sprintf(\"%d\",\"0x\"f[8]) : f[8]\n\t\t\t\trsrq = sprintf(\"%.0f\", -20 + (f[14]/2)); rsrp = sprintf(\"%.0f\", f[13] - 140)\n\t\t\t\tsinr = sprintf(\"%.0f\", f[11]/4 + 5); bwc = f[10]; bw=map[bwc]}\n\t\t}\n\t\tmode !~ /LTE/ { # 3G mode\n\t\t\tif (/CNTI/) { split($0, f, \",\")\n\t\t\t\ttech = f[2] }\n\t\t\tif (/\\+GTCCINFO:/) { getline; getline; split($0, f, \",\")\n\t\t\t\tlac = (model~/952|FM/) ? sprintf(\"%d\",\"0x\"f[5]) : f[5]\n\t\t\t\tcid = (model~/952|FM/) ? sprintf(\"%d\",\"0x\"f[6]) : f[6]\n\t\t\t\tearfcn = (model~/952|FM/) ? sprintf(\"%d\",\"0x\"f[7]) : f[7]\n\t\t\t\tsinr = (model~/952|FM/) ? sprintf(\"%.0f\", -24+(f[12]*24/255)) : sprintf(\"%.0f\", -24+(f[14]*24/255))}\n\t\t}\n\n\t\tEND { print  earfcn \"|\" lac \"|\" cid \"|\" tech \"|\" rsrq \"|\" rsrp \"|\" sinr \"|\" pci \"|\" bwc \"|\" bw \"|\" lte_ca \"|\" pcica \"|\" pch }\n\t\t\n\t')\n\t# Define variables\n\tEARFCN=${1}; LAC=${2}; CID=${3}; SINR=${7}\n\tIFS=\"$OIFS\"; unset OIFS\n\n\tif [ \"$MODE\" = \"LTE\" ]; then\n\t\tRSRQ=${5}; RSRP=${6}; PCI=${8}; BWDx=${9}; BWDL=${10}; PCICA=${12}; PCH=${13}; LTE_CA=${11}\n\t\tENBx=$(echo $CID | sed -e 's/..$//')\n\t\tCELL=$(printf %d 0x${CID: -2})\n\t\tENBID=$(printf %d 0x$ENBx)\n\n\t\tif [ \"$EARFCN\" = \"$PCH\" ] && [ \"$PCI\" = \"$PCICA\" ]; then\n\t\t\tOIFS=\"$IFS\"; IFS=\"|\" # Get LTE-A bandwith and CA bands\n\t\t\tset -- $(echo \"$O\" | awk -F'[,]' -v model=\"$MODEL\" -v mode=\"$MODE\" -v bwp=\"${BWDx:-0}\" '\n\t\t\t\tBEGIN { scc = \"\"; sum = 0; bwp_num = (bwp == 15 ? 1.3 : bwp/5) }\n\t\t\t\tfunction earfcn_to_band(channel) {\n\t\t\t\t\tif (channel <= 1) return \"\"\n\t\t\t\t\t\tcmd = \"/usr/share/modeminfo/scripts/ch_to_band \" mode \" \" channel\n\t\t\t\t\t\tcmd | getline band\n\t\t\t\t\t\tclose(cmd)\n\t\t\t\t\t\treturn band\n\t\t\t\t\t}\n\t\t\t\t{\n\t\t\t\tif (model ~ /FM|952/ && /^SCC[0-9]+:/) {\n\t\t\t\t\tchan_field = 5; bw_field = 6\n\t\t\t\t} else if (/\\+GTCAINFO: 2/) {\n\t\t\t\t\tchan_field = 4; bw_field = NF\n\t\t\t\t} else {\n\t\t\t\t\tnext\n\t\t\t\t}\n\t\t\t        if ($chan_field > 1) {\n\t\t\t\t\tband = earfcn_to_band($chan_field)\n\t\t\t\t\tif (band != \"\") scc = scc \"+\" band\n\t\t\t\t\tsum += $bw_field/5; bwca = sum + bwp_num\n\t\t\t\t}\n\t\t\t\t} END { print scc \"|\" bwca }\n\t\t\t')\n\t\t\tSCC=${1}; BWCA=${2}\n\t\t\tIFS=\"$OIFS\"; unset OIFS\n\t\tfi\n\telse\n\t\tTECH=${4}\n\t\t[ \"x$TECH\" != \"x\" ] && MODE=\"$TECH\"\n\tfi\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n\t\tLAC=$(printf %d 0x$LAC); CID=$(printf %d 0x$CID)\n\tfi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/FIBOCOM.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?; +CREG?\nAT+CEREG=2; +CEREG?\nAT+CGREG=2; +CGREG?; +CSQ\nAT+GTCCINFO?; +GTCAINFO?\nAT*CNTI=0\nAT+MTSM=1\nAT+CGMI?\nAT+CGMM?\nAT+CGMR?\nAT+CIMI?\nAT+ICCID\nAT+CGSN?\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/GENERIC",
    "content": "# Generic modem data\nmodem_data(){\n\tREGST=$(echo \"$O\" | awk -F[,] '/\\+CREG/ {print $2}')\n\tgeneric_data\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/GENERIC.at",
    "content": "ATE0\nAT+CSQ\nAT+COPS=3,0; +COPS?\nAT+CREG=2; +CREG?\nAT+CGREG=2; +CGREG?\nAT+CGMI; +CGMM; +CGMR; +ICCID; +CGSN; +CIMI\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/GOSUN",
    "content": "function modem_data(){\n\tDATA=$(echo \"$O\" | grep -Ev '^OK')\n\tMANUF=$(echo \"$DATA\" | awk 'c&&! --c;/ZCHIPTEMP/{c=1}')\n\tMODEL=$(echo \"$DATA\" | awk 'c&&! --c;/ZCHIPTEMP/{c=2}')\n\tFW=$(echo \"$DATA\" | awk 'c&&! --c;/ZCHIPTEMP/{c=3}')\n\tDEVICE=\"$MANUF $MODEL\"\n\tTECH=$(echo \"$DATA\" | awk -F [,] '/\\+COPS/{print $NF}')\n\tICCID=$(echo \"$DATA\" | awk '/ZGETICCID/{print $2}')\n\tIMSI=$(echo \"$DATA\" | awk 'c&&! --c;/ZGETICCID/{c=1}')\n\tIMEI=$(echo \"$DATA\" | awk 'c&&! --c;/ZGETICCID/{c=2}')\n\tCHIPTEMP=$(echo \"$DATA\" | awk '/\\+ZCHIPTEMP/{print $2}')\n\n\tcase $TECH in\n\t\t0|1) MODE=GSM ;;\n\t\t2) MODE=WCDMA ;;\n\t\t3) MODE=EDGE ;;\n\t\t4) MODE=HSDPA ;;\n\t\t5) MODE=HSUPA ;;\n\t\t6) MODE=HSDPA,HSUPA ;;\n\t\t7) MODE=LTE ;;\n\t\t8) MODE=DC-HSPA+ ;;\n\tesac\n\n\tcase $TECH in\n\t\t7)\n\t\t\tPCI=$(echo \"$DATA\" | awk -F [:,] '/\\+ZCDS/{print $10}')\n\t\t\tEEMGINFO=$(echo \"$O\" | awk -F[':'] '/^\\+EEMLTESVC:/ {print $2}')\n\t\t\tEARFCN=$(echo $EEMGINFO | awk -F [,] '{print $6}')\n\t\t\tLAC=$(echo $EEMGINFO | awk -F [,] '{printf \"%x\\n\", toupper($4)}')\n\t\t\tCID=$(echo $EEMGINFO | awk -F [,] '{printf \"%x\\n\", toupper($21)}')\n\t\t\tENBx=$(echo $CID | sed -e 's/..$//')\n\t\t\tENBID=$(printf %d 0x$ENBx)\n\t\t\tCELL=$(printf %d 0x${CID: -2})\n\t\t\tPCI=$(echo $EEMGINFO | awk -F [,] '{print $5}')\n\t\t\tRSRQ=$(echo $EEMGINFO | awk -F[,] '{printf \"%.0f\\n\", -19.5+$11/2}')\n\t\t\tRSRP=$(echo $EEMGINFO | awk -F[,] '{print -140+$10'})\n\t\t\tSINR=$(echo $EEMGINFO | awk -F[','] '{printf \"%.0f\\n\", $12/4+5}')\n\t\t\tBWDL=$(echo $EEMGINFO | awk -F[','] '{print $9}')\n\t\t;;\n\t\t2|4|5|6|8)\n\t\t\tEEMGINFO=$(echo \"$O\" | awk -F[':'] '/^\\+EEMUMTSSVC/ {print $2}')\n\t\t\tEARFCN=$(echo $EEMGINFO | awk -F [,] '{print $20}')\n\t\t\tSINR=$(echo $EEMGINFO | awk -F [,] '{printf \"%.0f\\n\", -24+$6/2}')\n\t\t\tLAC=$(echo $EEMGINFO | awk -F [,] '{printf \"%x\\n\", toupper($16)}')\n\t\t\tCID=$(echo $EEMGINFO | awk -F [,] '{printf \"%x\\n\", toupper($17)}')\n\t\t\t\n\t\t;;\n\tesac\n\tif [ $(uci -q get modeminfo.@generic[0].decimail) -eq 1 ]; then\n                LAC=$(printf %d 0x$LAC)\n                CID=$(printf %d 0x$CID)\n        fi\t\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/GOSUN.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?; +CREG?; +CGREG?; +CGREG?; +CEREG?; +CSQ\nAT+EEMGINFO?\nAT+ZCHIPTEMP?\nAT+CGMI; +CGMM; +CGMR; +ZGETICCID; +CIMI ;+CGSN\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/HUAWEI",
    "content": "# Huawei modems\nfunction modem_data(){\n\t# Get hw and sim info\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\tset -- $(echo \"$O\" | awk -F [:] -v fam=$FAMILY '\n\t\tBEGIN { IGNORECASE=1 }\n\t\t$0 ~ fam { gsub(\",\", \"\")\n\t\t\tmanuf = $1 $2\n\t\t\tif ((getline line) > 0) model = line\n\t\t\tif ((getline line) > 0) fw = line\n\t\t}\n\t\t/ICCID/ { split($0, parts, /:[[:space:]]*/)\n\t\t\ticcid = parts[2]\n\t\t\tif ((getline line) > 0) imsi = line\n\t\t\tif ((getline line) > 0) imei = line\n\t\t}\n\t\t/^\\^CHIPTEMP/ {n=split($0, t, \",\")\n\t\t\ttemp=sprintf(\"%.0f\", t[n]/10)\n\t\t}\n\t\tEND { print model \"|\" fw \"|\" iccid \"|\" imsi \"|\" imei \"|\" temp \"|\" manuf}\n\t')\n\t\n\tMANUF=${7}; MODEL=${1}; FW=${2}; ICCID=${3}; IMSI=${4}; IMEI=${5}; CHIPTEMP=${6}\n\tIFS=\"$OIFS\"; unset OIFS\n\tDEVICE=\"$MANUF $MODEL\"\n\n\t# get signal info\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\tset -- $(echo \"$O\" | awk -v mode=\"$MODE\" '\n\t\tBEGIN {\n\t\t\ttmap[1]=\"GSM\"; tmap[2]=\"GPRS\"; tmap[3]=\"EDGE\"; tmap[4]=\"UMTS\"; tmap[5]=\"HSDPA\"; tmap[6]=\"HSUPA\"\n\t\t\ttmap[7]=\"HSPA\"; tmap[9]=tmap[17]=tmap[18]=\"HSPA+\"\n\t\t\tbmap[1400]=0; bmap[3000]=1; bmap[5000]=2; bmap[10000]=3; bmap[15000]=4; bmap[20000]=5\n\t\t}\n\t\t/^\\^SYSINFOEX/{ gsub(\"\\\"\",\"\"); n=split($0, m, \",\")\n\t\t\tmode=m[n]; tech=m[6]\n\t\t\tif (length(mode) == 0) mode=tmap[tech]\n\t\t}\n\t\t/^\\+CGREG/ {gsub(\"\\\"\",\"\");split($0, r, \",\"); gsub(\"\\\"\",\"\")\n\t\t\tlac=r[3]; cid=r[4]\n\t\t\tcell = sprintf(\"%d\", \"0x\" substr(cid, length(cid) - 1, 2))\n\t\t\tenbid = sprintf(\"%d\", \"0x\" substr(cid, 1, length(cid) - 2))\n\t\t}\n\t\t/^\\^HFREQINFO/{split($0, r, \",\")\n\t\t\tearfcn=r[4]\n\t\t}\n\t\t/^\\^MONSC/{ split($0, r, \",\")\n\t\t\tpci=r[6]\n\t\t\tif (earfcn !~ \"\") earfcn=r[5]\n\t\t}\n\t\t/^\\^HCSQ:/{split($0, r, \",\")\n\t\t\trssi=-121+r[2]\n\t\t\tif (mode ~ /LTE/)sinr=sprintf(\"%.0f\", -20+r[4]/5); rsrp=-141+r[2]; rsrq=-20+r[5]/2\n\t\t\tif (mode ~ /WCDMA|UMTS|3G|HS/) sinr=sprintf(\"%.0f\",-32+a[4]); rscp=-121+a[3]\n\t\t}\n\t\t/^\\^HFREQINFO/ { split($0, r, \",\")\n\t\t\tbwc=r[6]/1000; bw=bmap[r[6]]\n\t\t}\n\t\tEND {print mode \"|\" lac \"|\" cid \"|\" earfcn \"|\" rssi \"|\" sinr \"|\" rsrp \"|\" rsrq \"|\" pci \"|\" cell \"|\" enbid \"|\" bw }\n\t')\n\tMODE=${1}; LAC=${2}; CID=${3}; EARFCN=${4}; CSQ_RSSI=${5}; SINR=${6}; RSRP=${7}; RSRQ=${8}; PCI=${9}; CELL=${10}; ENBID=${11} BWDL=${12}\n\tIFS=\"$OIFS\"; unset OIFS\n\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n                LAC=$(printf %d 0x$LAC)\n                CID=$(printf %d 0x$CID)\n        fi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/HUAWEI.at",
    "content": "ATE0\nAT+COPS=3,0 \nAT+COPS?; +CREG?; +CSQ\nAT+CGREG=2\nAT+CGREG?; ^MONSC\nAT^HFREQINFO?\nAT^SYSINFOEX\nAT^LTERSRP?\nAT^CSNR?\nAT^HCSQ?\nAT^CHIPTEMP?\nAT^SYSCFGEX?\nAT+CGMI; +CGMM; +CGMR; ^ICCID?; +CIMI; +CGSN\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/INTEL",
    "content": "# Parser DATA form XMM and T7xx modems\n# copyright by Konstantine Shevlakov (koshev-msk) 2026\n\nfunction modem_data(){\n\tgeneric_data\n\t# hwdata\n\tOIFS=\"$IFS\";IFS=\"|\"\n\tset -- $(echo \"$O\" | awk '\n\t\t/\\+GTPKGVER:/{gsub(\"\\\"\",\"\");fw=$2}\n\t\t/CGMI:/ {gsub(\"\\\"|\\r\",\"\",$0); split($0,a,\":\"); manuf=a[2]}\n\t\t/GMM:/ {gsub(\"\\\"|\\r\",\"\",$0); split($0,a,/[:,]/);  model=a[2]}\n\t\t/CCID:/ {iccid=$2; getline imsi; getline imei}\n\t\t/\\+MTSM:/ {split($0,a,\":\"); temp=a[2]}\n\t\tEND { print manuf \"|\" model \"|\" fw \"|\" iccid \"|\" imsi \"|\" imei \"|\" temp }\n\t')\n\tDEVICE=\"$(echo ${1} ${2})\"; FW=${3}; ICCID=${4}; IMSI=${5}; IMEI=${6}; CHIPTEMP=${7}\n\tIFS=\"$OIFS\"; unset OIFS\n\t# signal data\n\tOIFS=\"$IFS\";IFS=\"|\"\n\tset -- $(echo \"$O\" | awk -v mode=$MODE '\n\t\t# define maps and variables\n\t\tBEGIN {\n\t\t\tmap[1]=15; map[2]=25; map[3]=50; map[4]=75; map[5]=100\n\t\t\tcsq_range[\"black\"] = \"0:0\"; csq_range[\"red\"] = \"1:10\"\n\t\t\tcsq_range[\"orange\"] = \"11:20\"; csq_range[\"green\"] = \"21:50\"\n\t\t\trsrp = bwc = csq = rssi = csq_per = 0; csq_col = \"black\";\n\t\t}\n\t\t# text color percentage\n\t\tfunction get_csq_color(csq_raw_val) {\n\t\t\tif (csq_raw_val == \"\") return \"black\"\n\t\t\tcsq_num = csq_raw_val + 0\n\t\t\tfor (color in csq_range) {\n\t\t\t\tsplit(csq_range[color], limits, \":\")\n\t\t\t\tmin = limits[1] + 0; max = limits[2] + 0\n\t\t\t\tif (csq_num >= min && csq_num <= max) {return color}\n\t\t\t} return \"black\"\n\t\t}\n\t\t# lte mode\n\t\tmode ~ /LTE/ {\n\t\t\tif (/\\+CEREG/){gsub(\"\\\"\",\"\");split($0,a,\",\");reg=a[2];lac=a[3]; cid=a[4]}\n\t\t\tif (/\\+XLEC:/){gsub(\"\\r\",\"\");split($0,a,/[:,]/);lte_ca=a[3]-1;bw=a[4];bwc=map[bw]}\n\t\t\tif (/\\+RSRP:/){split($0,a,/[:,]/);pci=a[2];earfcn=a[3];rsrp=sprintf(\"%.0f\",a[4])}\n\t\t\tif (/\\+RSRQ:/){split($0,a,/[:,]/);rsrq=sprintf(\"%.0f\",a[4])}\n\t\t\tif (/\\+XMCI: 4/){gsub(/\"/,\"\");split($0,a,/[:,]/)\n\t\t\t\tpci=sprintf(\"%d\", a[7]);sinr=sprintf(\"%.0f\", a[13]/4+5)\n\t\t\t\tdistance=sprintf(\"%.2f\", (a[14]*78/1000))\n\t\t\t}\n\t\t\t# RSSI not correct sending via modem. Calc manually.\n\t\t\tcsq_raw=sprintf(\"%.0f\", ((rsrp+10*log(12*bwc)/log(10))+113)/2)\n\t\t\tcsq = (csq_raw > 50) ? 50 : csq = csq_raw\n\t\t\tcsq_col = get_csq_color(csq)\n\t\t\tcsq_per = (csq > 30) ? 100 : sprintf(\"%.0f\", csq*100/31); rssi=(2*csq-113)\n\t\t}\n\t\t# 3g mode\n\t\tmode !~ /LTE/{\n\t\t\tif (/\\+CREG/){gsub(\"\\\"\",\"\");split($0,a,\",\");reg=a[2];lac=a[3];cid=a[4]}\n\t\t\tif (/\\+RSCP:/){split($0,a,\",\");earfcn=a[2]}\n\t\t\tif (/\\+XMCI: 2/) {n=split($0,a,\",\");sinr_raw=a[n-1]; rssi_raw=a[n-2]; sinr = sprintf(\"%.0f\", (sinr_raw*24)/24-50); rssi = (rssi_raw*64/64-110)\n\t\t\t\tcsq=rssi_raw/2; csq_col = get_csq_color(csq); csq_per = (csq > 30) ? 100 : sprintf(\"%.0f\", csq*100/31)}\n\t\t\trsrp=\"\"\n\t\t}\n\t\tEND {print reg \"|\" earfcn \"|\" lac \"|\" cid \"|\" rssi \"|\" sinr \"|\" rsrp \"|\" rsrq \"|\" bw \"|\" bwc \"|\" pci \"|\" lte_ca \"|\" csq_col \"|\" csq_per \"|\" distance}\n\t')\n\tREGST=${1}; EARFCN=${2}; LAC=${3}; CID=${4}; CSQ_RSSI=${5}; SINR=${6}; RSRP=${7}; RSRQ=${8}; BWDL=${9}; BWCx=${10}\n\tPCI=${11}; LTE_CA=${12}; CSQ_COL=${13}; CSQ_PER=${14}; DISTANCE=${15}\n\tIFS=\"$OIFS\"; unset OIFS\n\tif [ $MODE = LTE ]; then\n\t\tENBx=$(echo $CID | sed -e 's/..$//')\n                CELL=$(printf %d 0x${CID: -2})\n                ENBID=$(printf %d 0x$ENBx)\n\t\tOIFS=\"$IFS\";IFS=\"|\"\n\t\tset -- $(echo \"$O\"| awk -F [:,] -v lte_ca=$LTE_CA -v mode=\"$MODE\" '\n\t\t\t# define maps and variables\n\t\t\tBEGIN { map[1]=3; map[2]=5; map[3]=10; map[4]=15; map[5]=20; scc = \"\" }\n\t\t\tfunction earfcn_to_band(channel, cmd, band) {\n\t\t\t\tif (channel == \"\" || channel == \"0\") return \"N/A\"\n\t\t\t\tcmd = \"/usr/share/modeminfo/scripts/ch_to_band \" mode \" \" channel\n\t\t\t\tcmd | getline band\n\t\t\t\tclose(cmd)\n\t\t\t\treturn (band != \"\" ? band : \"\")\n\t\t\t}\n\t\t\t# find secondary CA bands\n\t\t\t/\\+RSRP:/ {\n\t\t\t\tfor (i = 1; i <= lte_ca; i++) {\n\t\t\t\t\tfield_num = 6 + 3 * (i - 1)\n\t\t\t\t\tif (field_num <= NF) {\n\t\t\t\t\t\tearfcn = $field_num; band = earfcn_to_band(earfcn)\n\t\t\t\t\t\tif (band != \"\") {\n\t\t\t\t\t\t\tscc = (scc == \"\") ? \"+\" band : scc \"+\" band\n\t\t\t\t                }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t# bandwith summ CA\n\t\t\t/\\+XLEC/ {\n\t\t\t\tgsub(\"\\r\", \"\", $0); bwca = 0\n\t\t\t        if (lte_ca >= 1 && NF >= 5) {\n\t\t\t\t\tval1 = $4 + 0; val2 = $5 + 0\n\t\t\t\t\tif (val1 in map) bwca += map[val1]\n\t\t\t\t\tif (val2 in map) bwca += map[val2]\n\t\t\t\t}\n\t\t\t\tfor (i = 2; i <= lte_ca; i++) {\n\t\t\t\t\tfield_num = 4 + i  # 6, 7, 8, ...\n\t\t\t\t\tif (field_num <= NF) {\n\t\t\t\t\t\tval = $(field_num) + 0\n\t\t\t\t\t\tif (val in map) {\n\t\t\t\t\t\t\tbwca += map[val]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tEND { print scc \"|\" bwca }\n\t\t')\n\t\tSCC=${1}; BWCA=${2}\n\t\tIFS=\"$OIFS\"; unset OIFS\n\tfi\n\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n\t\tLAC=$(printf %d 0x$LAC)\n\t\tCID=$(printf %d 0x$CID)\n\tfi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/INTEL.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?\nAT+CEREG=2; +CEREG?\nAT+CREG=2; +CREG?; +CSQ?\nAT+XCESQ?; +RSRP?; +RSRQ?; +RSCP?\nAT+XLEC?\nAT+MTSM=1\nAT+XMCI=1\nAT+GTPKGVER?; +CGMI; +GMM?; +CCID; +CIMI; +CGSN\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/INTEL_FM350",
    "content": "# Fibocom FM350 parse data\n#\n\nfunction ch_to_band(){\n\t/usr/share/modeminfo/scripts/ch_to_band $1 $2\n}\n\nfunction modem_data(){\n\tgeneric_data\n\n\t# get hw and sim data\n\tIFS=\"$IFS\";IFS=\"|\"\n\tset -- $(echo \"$O\" | awk -F [:] '\n\t\t{ gsub(/\"/, \"\"); sub(/^[[:space:]]+/, \"\", $2);sub(/[[:space:]]+$/, \"\", $2)\n\t\t\tif (/GTPKGVER/) { fw = $2 }\n\t\t\tif (/CGMI/) { manuf = $2 }\n\t\t\tif (/CGMM/) { model = $2 }\n\t\t\tif (/CGMR/) { version = $2 }\n\t\t\tif (/CIMI/) { imsi = $2 }\n\t\t\tif (/ICCID/) { iccid = $2 }\n\t\t\tif (/CGSN/) { imei = $2 }\n\t\t\tif (/GTSENRDTEMP/) {n=split($0,t,\",\"); temp=sprintf(\"%.0f\", t[n]/1000)}\n\t\t}\n\t\tEND { print manuf \"|\" model \"|\" fw \"|\" iccid \"|\" imsi \"|\" imei \"|\" temp }\n\t')\n\n\tMANUF=${1}; MODEL=${2}; FW=${3}; ICCID=${4}; IMSI=${5}; IMEI=${6}; CHIPTEMP=${7}\n\tIFS=\"$OIFS\"; unset OIFS\n\tDEVICE=\"$MANUF $MODEL\"\n\n\t# Get signal info\n\tIFS=\"$IFS\";IFS=\"|\"\n\tset -- $(echo \"$O\" | awk -v mode=\"$MODE\" -F [,] '\n\t\tBEGIN {\n\t\t\tbmap[15]=1; bmap[25]=2; bmap[50]=3; bmap[75]=4; bmap[100]=5\n\t\t\tnmap[15]=3; nmap[25]=5; nmap[50]=10; nmap[75]=15; nmap[100]=20\n\t\t\tcsq_range[\"black\"] = \"0:0\"; csq_range[\"red\"] = \"1:10\"\n\t\t\tcsq_range[\"orange\"] = \"11:20\"; csq_range[\"green\"] = \"21:50\"\n\t\t}\n\n\t\t# text color percentage\n\t\tfunction get_csq_color(csq_raw_val) {\n\t\t\tif (csq_raw_val == \"\") return \"black\"\n\t\t\tcsq_num = csq_raw_val + 0\n\t\t\tfor (color in csq_range) {\n\t\t\t\tsplit(csq_range[color], limits, \":\")\n\t\t\t\tmin = limits[1] + 0; max = limits[2] + 0\n\t\t\t\tif (csq_num >= min && csq_num <= max) {return color}\n\t\t\t} return \"black\"\n\t\t}\n\n\t\tmode ~ /LTE/ { gsub(\"\\\"\",\"\"); gsub(/[[:space:]]+/, \"\")\n\t\t\tif (/\\+CEREG/){ gsub(\"\\\"\",\"\")\n\t\t\t\treg=$2; lac=$3; cid=$4\n\t\t\t}\n\t\t\tif (/^\\+CESQ/){split ($1, r, \":\")\n\t\t\t\trssi=r[2]-110; csq_raw = sprintf(\"%.0f\", (rssi + 113)/2); csq=(csq_raw>30) ? 30 : csq_raw\n\t\t\t\tcsq_col=get_csq_color(csq); csq_per=sprintf(\"%.0f\", csq*100/31)\n\t\t\t}\n\t\t\tif(/\\+GTCCINFO:/){ getline; earfcn=$7; rsrq=sprintf(\"%.0f\", -20+($14/2)); rsrp=sprintf(\"%.0f\", $13-140)\n\t\t\t\tsinr=sprintf(\"%.0f\",($11/4)+5); pci=$8; bw=bmap[$10]; bwc=nmap[$10]\n\t\t\t}\n\t\t\tif (/SCC/){lte_ca++}\n\t\t\tcell = sprintf(\"%d\", \"0x\" substr(cid, length(cid) - 1, 2))\n\t\t\tenbid = sprintf(\"%d\", \"0x\" substr(cid, 1, length(cid) - 2))\n\t\t}\n\t\tmode !~ /LTE/{\n\t\t\tif (/\\+CGREG/){ gsub(\"\\\"\",\"\")\n\t\t\t\treg=$2; lac=$3; cid=$4\n\t\t\t}\n\t\t\tif (/^\\+CESQ/){\n\t\t\t\trssi=sprintf(\"%.0f\",((95*$3)/96-120)); csq_raw = sprintf(\"%.0f\", (rssi + 113)/2); csq=(csq_raw>30) ? 30 : csq_raw\n\t\t\t\tcsq_col=get_csq_color(csq); csq_per=sprintf(\"%.0f\", csq*100/31); sinr=sprintf(\"%.0f\",(24*$4)/49-24)\n\t\t\t}\n\t\t\tif(/\\+GTCCINFO:/){ getline; earfcn=$7 }\n\t\t}\n\t\tEND { print reg \"|\" lac \"|\" cid \"|\" earfcn \"|\" rssi \"|\" sinr \"|\" rsrp \"|\" rsrq \"|\" pci \"|\" bw \"|\" bwc \"|\" lte_ca \"|\" csq_col \"|\"  csq_per \"|\" enbid \"|\" cell }\n\t')\n\n\tREGST=${1}; LAC=${2}; CID=${3}; EARFCN=${4}; CSQ_RSSI=${5}; SINR=${6}; RSRP=${7}; RSRQ=${8}; PCI=${9}; BWDL=${10}; BWDx=${11}\n\tLTE_CA=${12}; CSQ_COL=${13}; CSQ_PER=${14}; ENBID=${15}; CELL=${16}\n\tIFS=\"$OIFS\"; unset OIFS\n\t# Calc LTE CA\n\tif [ \"$LTE_CA\" -ge \"1\" ]; then\n\t\tIFS=\"$IFS\";IFS=\"|\"\n\t\tset -- $(echo \"$O\" | awk -v mode=$MODE -v bwcx=$BWDx -F [,] '\n\t\t\tBEGIN { sum = 0 }\n\t\t\tfunction earfcn_to_band(channel, cmd, band) {\n\t\t\t\tif (channel == \"\" || channel == \"0\") return \"N/A\"\n\t\t\t\tcmd = \"/usr/share/modeminfo/scripts/ch_to_band \" mode \" \" channel\n\t\t\t\tcmd | getline band\n\t\t\t\tclose(cmd)\n\t\t\t\treturn (band != \"\" ? band : \"\")\n\t\t\t}\n\t\t\t/SCC/{ scc = scc \"+\" earfcn_to_band($5); scc_ca += $6/5; bwca=bwcx+scc_ca }\n\t\t\tEND { print scc \"|\" bwca }\n\t\t')\n\t\tSCC=${1}; BWCA=${2}\n\t\tIFS=\"$OIFS\"; unset OIFS\n\tfi\n\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n\t\tLAC=$(printf %d 0x$LAC)\n\t\tCID=$(printf %d 0x$CID)\n\tfi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/INTEL_FM350.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?\nAT+CEREG=2; +CEREG?\nAT+CGREG=2; +CGREG?; +CESQ\nAT+GTCCINFO?; +GTCAINFO?\nAT+GTSENRDTEMP=1\nAT+GTPKGVER?; +CGMI?; +CGMM?; +CGMR?; +CIMI?; +ICCID; +CGSN?\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/MEIGLINK",
    "content": "# Meiglink modems\n\nfunction modem_data(){\n\tgeneric_data\n\t# hwinfo\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\tset -- $(echo \"$O\" | awk '\n\t/^\\+CGMI:/ {manuf = $2\" \"$3}; /^\\+CGMM:/ {model = $2}; /^\\+CGMR:/ {fw = $2}; /^\\+TEMP:.*cpuss-usr/ {gsub(\"\\\"\",\"\"); split($2,t,\",\"); temp = t[2]}\n        /^[0-9]{15}$/ {\n            if (!imsi) imsi = $0; else if (!imei) imei = $0\n        } /^ICCID:/ { iccid = $2; sub(/\\r$/, \"\", iccid)\n        } END { print manuf \"|\" model \"|\" fw \"|\" imsi \"|\" imei \"|\" iccid \"|\" temp } ')\n\tDEVICE=\"${1} ${2}\"; FW=${3}; IMSI=${4}; IMEI=${5}; ICCID=${6}; CHIPTEMP=${7}\n\tIFS=\"$OIFS\"; unset OIFS\n\n\t# signal info\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\t \n\tset -- $(echo \"$O\" | awk -F'[\\,]' '\n\t\t/^\\$QCSQ/ {split(substr($0, index($0,\":\")+1), a, \",\"); qcsq = a[1]; ecio = $2}\n\t\t/^\\+CSQ:/ {split($1,b,\":\\ \"); csq = b[2]}\n\t\t/^\\^CELLINFO:/ {\n\t\t\tsplit($1, p, \":\"); mode = p[2]\n\t\t\tif (mode == \"LTE\") {\n\t\t\t\tlte_ca++\n\t\t\t\tlac_h = sprintf(\"%X\", $9); cid_h = sprintf(\"%X\", $5); sinr = sprintf(\"%.0f\", $17/5 - 20)\n\t\t\t\trfcn = $12; rssi = $14; rsrp = $15; rsrq = $16; pci = $6; bw = ($11==15 ? 3 : $11/5)\n\t\t\t\tbwc_map[20] = 5; bwc_map[15] = 4; bwc_map[10] = 3; bwc_map[5] = 2; bwc_map[1.3] = 1\n\t\t\t\tbwc = bwc_map[bw]\n\t\t\t\tprint mode \"|\" rfcn \"|\" lac_h \"|\" cid_h \"|\" rssi \"|\" sinr \"|\" rsrp \"|\" rsrq \"|\" \\\n\t\t\t\t\tpci \"|\" bw \"|\" bwc \"|\" qcsq \"|\" csq \"|\" lte_ca\n\t\t\t} else if (mode ~ /WCDMA|UMTS/) {\n\t\t\t\tlac_h = sprintf(\"%X\", $6); cid_h = sprintf(\"%X\", $7)\n\t\t\t\trfcn = $9; rssi = qcsq; sinr = ecio\n\t\t\t\tprint mode \"|\" rfcn \"|\" lac_h \"|\" cid_h \"|\" rssi \"|\" sinr\n\t\t\t} else {\n\t\t\t\tlac_h = sprintf(\"%X\", $6); cid_h = sprintf(\"%X\", $7)\n\t\t\t\trfcn = $9; rssi = qcsq\n\t\t\t\tprint mode \"|\" rfcn \"|\" lac_h \"|\" cid_h \"|\" rssi\n\t\t\t}\n\t\t\texit\n\t\t} END {if (!mode) print \"NO_CARRIER\"}') \n\t\n\tMODE=\"${1}\" EARFCN=${2}; LAC=${3}; CID=${4}; CSQ_RSSI=${5}\n\tIFS=\"$OIFS\"; unset OIFS\n\tcase $MODE in\n\t\t*LTE*)\n\t\t\tSINR=${6}; RSRP=${7}; RSRQ=${8}; PCI=${9}; BWC=${10}; BWDL=${11}; LTE_CA=${14}\n\t\t\tCELL=$(printf %d 0x${CID: -2})\n\t\t\tENBx=$(echo $CID | awk '{print $1}' | sed -e 's/..$//' )\n\t\t\tENBID=$(printf %d 0x$ENBx)\n\t\t\tLTE_CA=$(echo \"$O\" | awk '/\\^CELLINFO: / {print $0}' | wc -l)\n\t\t\tif [ $LTE_CA -gt 0 ]; then\n\t\t\t\tLTE_CA=1; OIFS=\"$IFS\"; IFS=\"|\"\n\t\t\t\tset -- $(echo \"$O\" | awk -v bwdl=${BWC:-0} -F [,] '\n\t\t\t\t\t/CELLINFO: \"SCC\"/{bands = bands \"+\" $7; sum+=$6/5; bwca=sum+bwdl}\n\t\t\t\t\tEND{print bands \"|\" bwca}\n\t\t\t\t')\n\t\t\t\tBWCA=${2}; SCC=${1}\n\t\t\t\tIFS=\"$OIFS\"; unset OIFS BWC\n\t\t\tfi\n\t\t;;\n\t\tWCDMA|UMTS) SINR=${6} ;;\n\tesac\n\tunset OIFS\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ] ; then\n\t\tLAC=$(printf \"%d\" 0x${LAC})\n\t\tCID=$(printf \"%d\" 0x${CID})\n\tfi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/MEIGLINK.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?; +CREG?; +CGREG=2; +CGREG?; +CEREG?; +CSQ\nAT$QCSQ\nAT^CELLINFO=1; ^CELLINFO=3\nAT+TEMP\nAT+CGMI; +CGMM; +CGMR; +CIMI; +ICCID; +CGSN\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/MIKROTIK",
    "content": "# MikroTik R11e-LTE, R11e-LTE6 modem (plat.ver.\"OpenWrt-18.06.7\" router: MikroTik RBM33\")\n# NEED REWRITE FUNCTION for atinout compatible!!!\nfunction modem_data() {\n\tO=$(echo \"$O\" | grep -Ev '^$|^OK|^AT' | sort)\t\n\tTECH=$(echo \"$O\" | awk -F[,] '/^\\+COPS/ {print $4}')\n\tFW=$(echo \"$O\" | awk '/^\\+CGMR/{gsub(\"\\\"|\\r\",\"\",$0); print $2}')\n\tREGST=$(echo \"$O\" | awk -F[,] '/\\+CREG/{print $2}')\n\tEEMGSTATE=-1\n\tcase \"$TECH\" in\n\t\t0*|1*) # GSM, GSM Compact\n\t\t\tMODE=\"GSM\"\n\t\t\tEEMGSTATE=0\n\t\t;;   \n\t\t2*) # UTRAN\n\t\t\tMODE=\"UMTS\" \n\t\t\tEEMGSTATE=0\n\t\t;;    \n\t\t3*) # GSM w/EGPRS\n\t\t\tMODE=\"EDGE\"\n\t\t\tEEMGSTATE=0\n\t\t;;    \n\t\t4*|5*) # UTRAN w/HSDPA, UTRAN w/HSUPA\n\t\t\tMODE=\"HSDPA\"\n\t\t\tEEMGSTATE=1\n\t\t;;\n\t\t6*) # UTRAN w/HSDPA and HSUPA\n\t\t\tMODE=\"HSUPA\"\n\t\t\tEEMGSTATE=1\n\t\t;;    \n\t\t7*) # E-UTRAN\n\t\t\tMODE=\"LTE\"\n\t\t\tEEMGSTATE=2\n\t\t;;      \n\t\t8*) # UTRAN HSPA+\n\t\t\tMODE=\"HSPA\"\n\t\t\tEEMGSTATE=2\n\t\t;;    \n\t\t *) \n\t\t\tMODE=\"--\"\n\t\t;;\n\tesac\n\tDEVx=\"$(echo \"$O\" | awk -F [:,] '/CGMI:|GMM:/{gsub(\"\\\"|\\r\",\"\",$0);print substr($2,2);}')\"\n\tDEVICE=$(echo $DEVx)\n\tIMEI=$(echo \"$O\" | awk -F [:,] '/\\+CGSN:/{gsub(\" \",\"\", $2); print $2}')\n\t# Need Chenges\n\tIMSI=$(echo \"$O\" | awk -F [:,] '/\\+CIMI/{gsub(\" \",\"\", $2); print $2}')\n\tICCID=$(echo \"$O\" | awk -F [:,] '/\\+ICCID/{gsub(\" \",\"\", $2); print $2}')\n\tCSQ_RSSI=$(echo \"$O\" | awk -F[':,'] '/^\\+CESQ:/ {print $2}')\n\tECIO=$(echo \"$O\" | awk -F[':,'] '/^\\+CESQ:/ {print $5}')\n\t# If less than 11 -> error\n\tif [ \"x$CSQ_RSSI\" != \"x\" ]; then\n\t\tCSQ_RSSI=$(echo $CSQ_RSSI | awk '{if((110 - $1) > 11) print 110 - $1; else print \"\";}')\n\tfi\n\tif [ \"x$ECIO\" != \"x\" ]; then\n\t\tECIO=$(echo $ECIO | awk '($1 < 255) {print 25 - $1 * 0.5}')\n\tfi\n\t# LTE Engineering Mode\n\tif [ $EEMGSTATE -eq 2 ]; then\n\t\tEEMGINFO=$(echo \"$O\" | awk -F[':'] '/^\\+EEMLTESVC:/ {print $2}')\n\t\tif [ \"x$EEMGINFO\" != \"x\" ]; then\n\t\t\tBW=$(echo $EEMGINFO | awk -F[','] '{print $8}')\n\t\t\tBWDL=$(echo $EEMGINFO | awk -F[','] '{print $9}')\n\t\t\tPCI=$(echo $EEMGINFO | awk -F [,] '{print $5}')\n\t\t\tRSRP=$(echo $EEMGINFO | awk -F[','] '{print -140+$11}')\n\t\t\tRSRQ=$(echo $EEMGINFO | awk -F[','] '{printf \"%.0f\\n\", -19.5+$11/2}')\n\t\t\tSINR=$(echo $EEMGINFO | awk -F[','] '{printf \"%.0f\\n\", ($13/4+5)}')\n\t\t\tCOPS_MCC=$(echo $EEMGINFO | awk -F[','] '{print $1}')\n\t\t\tCOPS_MNC=$(echo $EEMGINFO | awk -F[','] '{print $3}')\n\t\t\tLAC=$(echo \"$O\" | awk -F [,] '/^\\+CREG/{print toupper($3)}')\n\t\t\tCID=$(echo \"$O\" | awk -F [,] '/^\\+CREG/{print toupper($4)}')\n\t\t\tENBx=$(echo $CID | sed -e 's/..$//')\n\t\t\tENBID=$(printf %d 0x$ENBx)\n\t\t\tCELL=$(printf %d 0x${CID: -2})\n\t\t\tEARFCN=$(echo $EEMGINFO | awk -F[','] '{print $6}')\n\t\tfi\n\t# UMTS Engineering Mode\n\telif [ $EEMGSTATE -eq 1 ]; then\n\t\tCOPS_NUM=$(echo \"$O\" | awk -F[\":\"] '/^\\+EEMUMTSSV:/ {print $2}')\n\t\tif [ \"x$EEMGINFO\" != \"x\" ]; then\n\t\t\tCOPS_MCC=$(echo $EEMGINFO | awk -F[','] '{print $11}')\n\t\t\tCOPS_MNC=$(echo $EEMGINFO | awk -F[','] '{print $12}')\n\t\tfi\n\t# GSM Engineering Mode\n\telif [ $EEMGSTATE -eq 0 ]; then\n\t\tCOPS_NUM=$(echo \"$O\" | awk -F[\":\"] '/^\\+EEMGINFOSVC:/ {print $2}')\n\t\tif [ \"x$EEMGINFO\" != \"x\" ]; then\n\t\t\tCOPS_MCC=$(echo $EEMGINFO | awk -F[','] '{print $1}')\n\t\t\tCOPS_MNC=$(echo $EEMGINFO | awk -F[','] '{print $2}')\n\t\tfi\n\tfi\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n                LAC=$(printf %d 0x$LAC)\n                CID=$(printf %d 0x$CID)\n        fi\n\tCOPS=$(awk -F[\\;] '/'$COPS_NUM'/ {print $2}' $RES/mccmnc.dat)\n\tif [ \"x$COPS\" = \"x\" ]; then\n\t\tCOPS=\"$COPS_MCC $COPS_MNC\"\n\tfi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/MIKROTIK.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?\nAT+CREG=2; +CREG?\nAT+CGREG=2; +CGREG?\nAT+CEREG=2; +CEREG?\nAT+CESQ\nAT+EEMOPT=1\nAT+EEMGINFO?\nAT+CGMI; +CGMM; +CGMR; +CGSN; +CIMI\nAT+ICCID\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/QUALCOMM",
    "content": "# Qualcomm Modems (FoxConn T77W678, Telit LN940, HP LT4220)\nfunction modem_data(){\n        generic_data\n\t# hwinfo and siminfo\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\tset -- $(echo \"$O\" | awk -v fam=\"$FAMILY\" '\n\t\t($0 ~ fam){manuf =$0; getline model; getline fw}\n\t\t/ICCID:/{split($0,i,\":\");gsub(\"\\r\",\"\"); iccid=i[2]; getline imsi; getline imei}\n\t\t/tsens_tz_sensor0:/{split($0,i,\":\");temp=i[2]}\t\n\t\tEND{print manuf \"|\" model \"|\" fw \"|\" iccid \"|\" imsi \"|\" imei \"|\" temp}\n\t')\n\tMANUF=${1}; MODEL=${2}; FW=${3}; ICCID=${4}; IMSI=${5} IMEI=${6}; CHIPTEMP=${7}\n\tIFS=\"$OIFS\"; unset OIFS\n        DEVICE=\"$MANUF $MODEL\"\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\t# signal\n\tset -- $(echo \"$O\" | awk -v mode=\"$MODE\" '\n\t\tBEGIN { map[3]=1; map[5]=2; map[10]=3; map[15]=4; map[20]=5 }\n\t\tmode ~ /LTE/ { # LTE mode\n\t\t\tif (/\\+CEREG/){gsub(\"\\\"\",\"\"); split($0,i,\",\"); reg=i[2]; lac=i[3]; cid=i[5]}\n\t\t\tif (/RFSTS:/){ getline; split($0,i,\",\"); earfcn=i[2]; rssi=sprintf(\"%.0f\",i[4]); rsrp=sprintf(\"%.0f\",i[3])\n\t\t\t\tsinr=sprintf(\"%.0f\",i[16]/4+5); rsrq=sprintf(\"%.0f\",i[5]); pci=i[11]\n\t\t\t}\n\t\t\tif (/SCC/){lte_ca++}\n\t\t\tif (/PCC info/){bwc=sprintf(\"%.0f\",$(NF-1)); bw=map[bwc]}\n\t\t\tcell = sprintf(\"%d\", \"0x\" substr(cid, length(cid) - 1, 2))\n\t\t\tenbid = sprintf(\"%d\", \"0x\" substr(cid, 1, length(cid) - 2))\n\t\t}\n\t\tmode !~ /LTE/ { # 3G mode\n\t\t\tif (/\\+CREG/){gsub(\"\\\"\",\"\"); split($0,i,\",\"); reg=i[2]; lac=i[3]; cid=i[4]}\n\t\t\tif (/RFSTS:/){ getline; split($0,i,\",\"); earfcn=i[2]; rssi=sprintf(\"%.0f\",i[6]); sinr=sprintf(\"%.0f\",i[4])}\n\t\t}\n\t\tEND { print reg \"|\" earfcn \"|\" lac \"|\" cid \"|\" rssi \"|\" sinr \"|\" rsrp \"|\" rsrq \"|\" pci \"|\" bw \"|\" bwc \"|\" lte_ca \"|\" enbid \"|\" cell}\n\t')\n\tEARFCN=${2}; LAC=${3}; CID=${4}; CSQ_RSSI=${5}; SINR=${6}; RSRP=${7}; RSRQ=${8}; PCI=${9}; BWDL=${10}; BWDx=${11}; LTE_CA=${12}; ENBID=${13}; CELL=${14}\n\tIFS=\"$OIFS\"; unset OIFS\n\t# LTE CA calc\n\tif [ $LTE_CA -ge 1 ]; then\n\t\tOIFS=\"$IFS\"; IFS=\"|\"\n\t\tset -- $(echo \"$O\" | awk -v bwc=${BWDx:-0} '\n\t\t\t/^SCC\\d info:/ {for(i=1; i<=NF; i++) { if($i ~ /LTE_B/) { gsub(/[^0-9]/, \"\", $i); scc = scc \"+\" $i}}\n\t\t\t\tbwca_raw += sprintf(\"%.0f\", $8); bwca=bwca_raw+bwc }\n\t\t\tEND { print scc \"|\" bwca }')\n\t\t\tSCC=${1}; BWCA=${2}\n\t\t\tIFS=\"$OIFS\"; unset OIFS BWDx\n        fi\n\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n\t\tLAC=$(printf %d 0x$LAC)\n\t\tCID=$(printf %d 0x$CID)\n\tfi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/QUALCOMM.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?\nAT+CREG=2; +CREG?\nAT+CGREG=2; +CGREG?\nAT+CEREG=2; +CEREG?\nAT+CSQ\nAT^RFSTS?\nAT^CA_INFO?\nAT+CGMI; +CGMM; +CGMR; +ICCID; +CIMI; +CGSN\nAT+TEMP\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/QUECTEL",
    "content": "# Quectel modems\nfunction modem_data(){\n\tgeneric_data\n\tO=$(echo \"$O\" | grep -Ev '^$|OK')\n\t# hwinfo\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\tset -- $(echo \"$O\" | awk -v fam=\"$FAMILY\" '\n\t\tBEGIN { IGNORECASE=1; temp_found = 0 }\n\t\t!temp_found && /\\+QTEMP:/ {\n\t\t    sub(/.*\\+QTEMP:[^0-9]*/, \"\")\n\t\t    temp = $0+0\n\t\t    temp_found = 1\n\t\t}\n\t\t/ICCID:/ {\n\t\t\tgsub(/\"/, \"\", $0)\n\t\t\tsplit($0, parts, /:[[:space:]]*/)\n\t\t\tif (parts[2] != \"\") {\n\t\t\t\ticcid = parts[2]\n\t\t\t}\n\t\t}\n\t\t$0 ~ fam && manuf == \"\" {\n\t\t    manuf = $1\n\t\t\tif ((getline line) > 0) {\n\t\t\t        model = line\n\t\t\n\t\t\tif ((getline line) > 0) fw = line\n\t\t\tif ((getline line) > 0) imsi = line\n\t\t\tif ((getline line) > 0) imei = line\n\t\t}\n\t}\n\tEND {\n\tif (!temp_found) temp = \"N/A\"\n\t\tprint manuf \"|\" model \"|\" fw \"|\" iccid \"|\" imsi \"|\" imei \"|\" temp\n\t}')\n\tMANUF=${1}; MDL=${2}; FW=${3} ICCID=${4}; IMSI=${5}; IMEI=${6} CHIPTEMP=${7}\n\tIFS=\"$OIFS\"; unset OIFS\n\tDEVICE=\"$MANUF $MDL\"\n\t# signal info\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\tset -- $(echo \"$O\" | awk -v mode=\"$MODE\" -v model=\"$MDL\" -F \",\" '\n\t\t$0 ~ /^\\+QENG/ {\n\t\t\tif (mode == \"\") { mode = gsub(\"\\\"\",\"\"); mode=$3 }\n\t\t\tif (mode ~ /LTE/) {\n\t\t\t\tlac=$13; cid=$7; earfcn=$9; sinr=$(NF-1); rsrp=$(NF-4); rsrq=$(NF-3)\n\t\t\t\tbw=$11; bwc=bw*5-5; pci=$8\n\t\t\t\tif (model !~/EC200/){ # EC200 send incorrect RSSI value, use value from AT+CSQ\n\t\t\t\t\trssi=$(NF-2)\n\t\t\t\t}\n\t\t\t\tcell = sprintf(\"%d\", \"0x\" substr(cid, length(cid) - 1, 2)) \n\t\t\t\tenbid = sprintf(\"%d\", \"0x\" substr(cid, 1, length(cid) - 2))\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlac=$6; cid=$7; earfcn=$8; sinr=$13\n\t\t\t\tif (model !~/EC200/){ # EC200 send incorrect RSSI value, use value from AT+CSQ\n\t\t\t\t\trssi=$11; sinr=$12\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t/^\\+QCAINFO/{lte_ca++}\n\t\tEND { print lac \"|\" cid \"|\" earfcn \"|\" rssi \"|\" sinr \"|\" rsrp \"|\" rsrq \"|\" bw \"|\" bwc \"|\" pci \"|\" lte_ca \"|\" enbid \"|\" cell}\n\t')\n\tLAC=${1}; CID=${2}; EARFCN=${3}; [ \"${4}\" != \"\" ] && CSQ_RSSI=${4}; SINR=${5}; RSRP=${6} RSRQ=${7}\n\tBWDL=${8}; BWDx=${9}; PCI=${10}; LTE_CA=${11}; ENBID=${12}; CELL=${13}\n\tIFS=\"$OIFS\"; unset OIFS\n\tif [ $LTE_CA -ge 2 ]; then\n\t\tOIFS=\"$IFS\"; IFS=\"|\"\n\t\tset -- $(echo \"$O\" | awk -v bwdl=$BWDL -F [,] '/^\\+QCAINFO.*\"scc\"/{for(i=1;i<=NF;i++) if($i~/BAND/){gsub(/[^0-9]/,\"\",$i); scc = scc\"+\"$i}\n\t\t\t{bwcx+=$3/5; bwca=bwcx + (bwdl==1 ? 3 : bwdl*5 - 5)}\n\t\t} END{print scc \"|\" bwca }')\n\t\tSCC=${1}; BWCA=${2}\n\t\tIFS=\"$OIFS\"; unset OIFS\n\tfi\n\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n\t\tLAC=$(printf %d 0x$LAC)\n\t\tCID=$(printf %d 0x$CID)\n\tfi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/QUECTEL.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?; +CREG?; +CGREG?; +CEREG?; +CSQ\nAT+QENG=\"servingcell\"\nAT+QCAINFO\nAT+QTEMP\nAT+ICCID\nAT+CGMI; +CGMM; +CGMR\nAT+CIMI\nAT+CGSN\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/SIERRA",
    "content": "modem_data(){\n\t# hwdata\n\tOIFS=\"$IFS\"\n\tIFS=\"|\"\n\tset -- $(echo \"$O\" | awk -v family=$FAMILY '\n\t\tBEGIN{IGNORECASE=1} $0 ~ family {manuf=$0; getline model; getline fw}\n\t\t/^ICCID:/{iccid=$2; getline imsi; getline imei}\n\t\t/Temperature:/{temp=$5}\n\t\tEND { print manuf \"|\" model \"|\" fw \"|\" iccid \"|\" imsi \"|\" imei \"|\" temp}')\n\n\tMANUF=${1}; MODEL=${2}; FW=${3}; ICCID=${4}; IMSI=${5}; IMEI=${6}; CHIPTEMP=${7}\n\tIFS=\"$OIFS\"\n\tunset OIFS\n        DEVICE=\"$MANUF $MODEL\"\n\t# Sierra cell data\n\tTECH=$(echo \"$O\" | awk -F[,\\ ] '/^\\*CNTI/ {print $3}' | sed 's|/|,|g')\n\tOIFS=\"$IFS\"\n\tIFS=\"|\"\n\tset -- $(echo \"$O\" | awk -v tech=$TECH -v model=$MODEL '\n\t\tBEGIN { map[3]=1; map[5]=2; map[10]=3; map[15]=4; map[20]=5 }\n\t\ttech ~ \"LTE\" {\n\t\t\tif (/^\\+CEREG/ && model ~ /73|74/) {gsub(\"\\\"\",\"\");split($0,a,\",\"); lac=a[3]; cid=a[5]}\n\t\t\tif (/^\\+CEREG/ && model !~ /73|74/) {gsub(\"\\\"\",\"\");split($0,a,\",\"); lac=a[3]; cid=a[4]}\n\t\t\tif (/LTE bw/) {bwc = $6; bw = map[bwc]}\n\t\t\tif (/LTE Rx chan:/) { earfcn=$4 }\n\t\t\tif (/LTE CA state:/) { ca = $4; lte_ca = (ca ~ /INACTIVE|NOT/) ? 0 : 1 }\n\t\t\tif (/PCC RxM RSSI:/){rssi=$4; rsrp=$7}\n\t\t\tif (/RSRQ/) {rsrq = sprintf(\"%.0f\",$3); getline; sinr = sprintf(\"%.0f\",$3);exit}\n\t\t}\n\t\ttech !~ \"LTE\" {\n\t\t\tif (/^\\+CGREG/) {gsub(\"\\\"\",\"\");split($0,a,\",\"); lac=a[3]; cid=a[4]}\n\t\t\tif (/channel:/) {earfcn=$3}\n\t\t\tif (/QCSQ/) {split($0,a,\",|:\"); rssi=a[2]; sinr=a[3]}\n\t\t}\n\t\tEND { print earfcn \"|\" lac \"|\" cid \"|\" rssi \"|\" sinr \"|\" rsrp \"|\" rsrq \"|\" bwc \"|\" bw \"|\" lte_ca}')\n\n\tEARFCN=${1}; LAC=${2}; CID=${3}; CSQ_RSSI=${4}; SINR=${5}; RSRP=${6}; RSRQ=${7}; BWDx=${8}; BWDL=${9}; LTE_CA=${10}\n\tunset OIFS\n\tif [ \"x$TECH\" != \"x\" ]; then\n\t\tMODE=\"$TECH\"\n\t\tcase $MODE in\n\t\t\tNONE) MODE=LTE ;;\n\t\tesac\n\tfi\n\n\tif [ $MODE = \"LTE\" ]; then\n\t\tENBx=$(echo $CID | sed -e 's/..$//')\n\t\tCELL=$(printf %d 0x${CID: -2})\n\t\tENBID=$(printf %d 0x$ENBx)\n\t\tPCI=$(echo \"$O\" | awk -F [:,] '/QCRSRQ:/{print $2}')\n\t\tif [ $LTE_CA -ge 1 ]; then\n\t\t\tSCC=$(echo \"$O\" | awk -F [:] '/LTE Scell band/{gsub(\"B\",\"\");printf \"%s\", \"+\"$NF} END{print \"\"}')\n\t\t\t[ $SCC ] || SCC=$(echo \"$O\" | awk '/LTE SCC[1-5] band:/{gsub(\"B\", \"\"); printf \"%s\", \"+\"$NF} END{print \"\"}')\n\t\t\tcase $MODEL in\n\t\t\t\t*73*|*74*) BWCA=$(echo \"$O\" | awk  -v bwdx=$BWDx -F [:] '/LTE Scell bw:/{sum += $2} END{print sum + bwdx}') ;;\n\t\t\t\t*) BWCA=$(echo \"$O\" | awk -v bwdx=$BWDx '/LTE SCC[1-9] bw/{sum += $5} END{print sum + bwdx}') ;;\n\t\t\tesac\n\t\tfi\n\tfi\n\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n\t\tLAC=$(printf %d 0x$LAC)\n\t\tCID=$(printf %d 0x$CID)\n\tfi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/SIERRA.at",
    "content": "ATE0\nAT+COPS=3,0;+COPS?\nAT+CREG=2;+CREG?\nAT+CGREG=2;+CGREG?\nAT+CEREG=2;+CEREG?\nAT+CSQ\nAT*CNTI=0\nAT!GSTATUS?\nAT$QCRSRQ?\nAT$QCSQ\nAT+CGMI; +CGMM; +CGMR; +ICCID; +CIMI; +CGSN\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/SIMCOM",
    "content": "# SimCom SIM7600E-H\nmodem_data(){\n\t# get hardware info\n\n\tset -- $(echo \"$O\" | awk -v fam=\"$FAMILY\" '\n\t\tBEGIN {cnt=0; OFS=\"\\n\"}\n\t\t$0 ~ fam {cnt++; if(cnt==1) manuf=$0; if(cnt==2) model=$0}\n\t\t/^\\+CGMR:/ {split($0,a,\":\"); fw=a[2]; getline; if($0~/^[0-9]{15}$/) imsi=$0}\n\t\t/^\\+ICCID:/ {split($0,a,\":\"); iccid=a[2]; getline; if($0~/^[0-9]{15}$/) imei=$0}\n\t\t/^\\+CPMUTEMP/ {split($0,a,\" \"); temp=a[2]}\n\t\t/^\\+CNSMOD/ {split($0, a, \"[, ]\"); tech = a[3]\n\t\tmodes[1] = \"GSM\"; modes[2] = \"GPRS\"; modes[3] = \"EDGE\";\tmodes[4] = \"UMTS\"; modes[5] = \"HSDPA\"\n\t\tmodes[6] = \"HSUPA\"; modes[7] = \"HSPA\"; modes[8] = \"LTE\"\n\t\ttech_first = substr(tech, 1, 1)\n\t\tmode = (tech_first in modes) ? modes[tech_first] : \"--\" }\n\t\tEND {print manuf, model, fw, iccid, imsi, imei, temp, tech, mode}')\n\n\tDEVICE=\"${1} ${2} ${3}\"; FW=\"${4}\"; ICCID=\"${5}\"; IMSI=\"${6}\"; IMEI=\"${7}\"; CHIPTEMP=${8}; TECH=${9}; MODE=\"${10}\"\n\n\t# get signal info\n\tset -- $(echo \"$O\" | awk -F[,\\ ] -v tech=\"$TECH\" '\n\t\t/^\\+CPSI:/ && tech ~ /^8/ { # LTE Mode\n\t\tearfcn = $9; csq_rssi = sprintf(\"%.0f\", $14/10)\n\t\trsrp = sprintf(\"%.0f\", $13/10)\n\t\trsrq = sprintf(\"%.0f\", $12/10)\n\t\tsinr = sprintf(\"%.0f\", ($15*2)-20)\n\t\tpci = $7; bwdl = $10; bwul = $11\n\t\t} /^\\+CPSI:/ && tech ~ /^[4-7]/ { # 3G Mode\n\t\tearfcn = $8; sinr = sprintf(\"%.0f\", (-$10 < -24) ? -24 : (-$10 > 0) ? 0 : -$10)\n\t\t} /^\\+CPSI:/ && tech ~ /^[1-3]/ { # 2G Mode\n\t\tearfcn = $7\n\t\t} /^\\+CEREG:/ && tech ~ /^[4-8]/ { # LTE/3G Mode\n\t\tlac = $4; cid = $5\n\t\t} /^\\+CREG:/ && tech ~ /^[1-3]/ { # 2G Mode\n\t\tlac = $3; cid = $4\n\t\t} END {print earfcn, lac, cid, csq_rssi, sinr, rsrp, rsrq, pci, bwdl, bwul}')\n\tEARFCN=${1}; LAC=${2}; CID=${3}; CSQ_RSSI=${4}\n\tcase \"$TECH\" in \n\t\t8*)\n\t\t\tSINR=${5}; RSRP=${6}; RSRQ=${7}; PCI=${8}; BWDL=${9}\n\t\t\tENBx=$(echo $CID | sed -e 's/..$//')\n\t\t\tCELL=$(printf %d 0x${CID: -2})\n\t\t\tENBID=$(printf %d 0x$ENBx)\n\t\t\tLTE_CA=$(echo \"$O\" | grep -i CA_Scell | wc -l)\n                        if [ $LTE_CA -gt 0 ]; then\n\t\t\t\tLTE_CA=1\n\t\t\t\tSCC=$(echo \"$O\" | awk -F'[,]' -v mode=\"$MODE\" '\n\t\t\t\t\t/CA_Scell/ && $3 > 1 {\n\t\t\t\t\tcmd = \"/usr/share/modeminfo/scripts/ch_to_band \" mode \" \" $3\n\t\t\t\t\tcmd | getline band\n\t\t\t\t\tclose(cmd)\n\t\t\t\t\tprintf \"%s%s\", (++n?\"+\":\"\"), band }')\n\n\t\t\t\tBWCA=$(echo \"$O\" | awk -F '[:,]' -v bwdl=\"$BWDL\" '\n\t\t\t\t\tBEGIN { ca_count = 0; bwca = 0\n\t\t\t\t\t# Convert value to bandwith\n\t\t\t\t\tbw_conv[1] = 3; bw_conv[2] = 5; bw_conv[3] = 10; bw_conv[4] = 15; bw_conv[5] = 20\n\t\t\t\t\tmain_bw = (bwdl in bw_conv) ? bw_conv[bwdl] : 0\n\t\t\t\t\t} /CA_Scell/ { ca_count++; bw = $6\n\t\t\t\t\tif (bw in bw_conv) {\n\t\t\t\t\tbwca += bw_conv[bw] }\n\t\t\t\t\t} END {\thas_ca = (ca_count > 0) ? 1 : 0; total_bw = has_ca ? (bwca + main_bw) : 0\n\t\t\t\t\tprint has_ca, total_bw }')\n\t\t\tfi\n\t\t;;\n\t\t4*|5*|6*|7*) SINR=${5} ;;\n\tesac\n        LAC_NUM=$(printf %d 0x$LAC)\n        CID_NUM=$(printf %d 0x$CID)\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n\t\tLAC=$LAC_NUM\n\t\tCID=$CID_NUM\n\tfi\n\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/SIMCOM.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?\nAT+CREG=2; +CREG?\nAT+CGREG=2; +CGREG?\nAT+CEREG=2; +CEREG?; +CSQ\nAT+CNSMOD?; +CPSI?; +CPMUTEMP\nAT+CMGRMI=4\nAT+CGMI; +CGMM; +CGMR; +CIMI; +ICCID; +CGSN\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/SKELETON",
    "content": "# Expample parser file\n# define function \"modem_data\"\n# optionally include function \"generic_data\"\n# finction generic data declare next variables:\n# MODE=\"LTE\"\t- form: AT+COPS: 0,1,\"Provider GSM\",7\t\tlast arg\n# CSQ=24\t- form: AT+CSQ: 24,99\t\t\t\tsecond arg\n# CSQ_RSSI\t- calculate form: CSQ/31*100 in dBm\n# CSQ_PER\t- calculate form: CSQ*2-113 in dB\n# COPS\t\t- form: AT+COPS: 0,1,\"Provider GSM\",7\t\ttrid arg\n# LAC\t\t- form: AT+CREG: CREG: 2,1,\"ABCD\",\"ABCDEF0\"\ttrid arg\n# TAC\t\t- form: AT+CREG: CREG: 2,1,\"ABCD\",\"ABCDEF0\"\tfour arg\n# incoming other data define variable \"O\"\nfunction modem_data() {\n\tgeneric_gata\n#parse next variables:\n#hardware modem and sim information\n\tMANUF=\"Example Manufacturer\"\t\t# Modem Manufacrurer. E.g Huawei\n\tMODEL=\"Example Model\"\t\t\t# Modem model. E.g ME909\n\tFW=\"Firmware modem version\"\n\tICCID=\"12345678901234567890\"\t\t# 20 digit Integrated Circuit Card Identifier\n\tIMSI=\"123456789012345\"\t\t\t# 15 digit International Mobile Subscriber Identity\n\tIMEI=\"123456789012345\"\t\t\t# 15 digit International Mobile Equipment Identity\n\tCHIPTEMP=\"50\"\t\t\t\t# Chip Themperature integer celsius\n\n#for LTE modem signal\n\tEARFCN=3200\t\t\t\t# E-UTRA Absolute Radio Frequency Channel Number.In LTE\n\tRSRP=-96\t\t\t\t# Reference Signal Received Power in dBm\n\tRSRQ=-11\t\t\t\t# Reference Signal Received Quality in dB\n\tSINR=13\t\t\t\t\t# Signal to Interference + Noise Ratio in dB\n\tBWDL=3\t\t\t\t\t# Bandwith Download channel: 1 - 3MHz; 2 - 5MHz; 3 - 10MHz; 4 - 15MHz; 5 - 20MHz\n\tPCI=345\t\t\t\t\t# Physical Cell ID\n\n#for LTE-A (LTE+) signal\n\tSCC=+7+1\t\t\t\t# Lte CA Dowmload bands\n\tBWCA=60\t\t\t\t\t# LTE CA Summ download Bandwith: BWDL+(SCC1+SCCN) in MHz\n# Calc next data\n\tCELL=$(printf %d 0x${CID: -2})\t\t# a unique number identifying a specific cell tower (base station) in a mobile network\n\tENBID=$(printf %d 0x$ENBx)\t\t# a type of cellular base station used in LTE networks\n\n# for 3G/UMTS signal\n\tEARFCN\t\t\t\t\t# UTRA Absolute Radio Frequency Channel Number.In 3G/UMTS/WCDMA\n\tSINR\t\t\t\t\t# Energy per Chip to Interference/Noise ratio in 3G networks\n# for 2G/EDGE\n\tEARFCN\t\t\t\t\t# Absolute Radio Frequency Channel Number in 2G/GSM\n# calc next variables for decimal show LAC/TAC option \n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n\t\tLAC=$(printf %d 0x$LAC)\t\t# decimal LAC (ABCD) 43981\n\t\tCID=$(printf %d 0x$CID)\t\t# decimal CID (ABCDEF0) 180150000\n\tfi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/SKELETON.at",
    "content": "# A custom at commants respone modem to parse\nATE0\nAT+CSQ\nAT+COPS=3,0; +COPS?\nAT+CREG=2; +CREG?\nAT+CGREG=2; +CGREG?\nAT+CEREG?\nAT+CGMI; +CGMM; +CGMR; +ICCID; +CGSN; +CIMI\n# Proprietary commands for parse\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/STYX",
    "content": "# STYX modems\nfunction modem_data(){\n    generic_data\n    # hwinfo\n    MANUF=$(echo \"$O\" | grep -i $FAMILY)\n    MODEL=$(echo \"$O\" | grep -A1 -i $FAMILY | tail -1)\n    DEVICE=\"$MANUF $MODEL\"\n    FW=$(echo \"$O\" | grep -A2 -i $FAMILY | tail -1)\n    ICCID=$(echo \"$O\" | awk -F [:] '/ICCID:/{gsub(\"\\r\",\"\");print $2}')\n    IMSI=$(echo \"$O\" | grep -A3 -i $FAMILY | tail -1)\n    IMEI=$(echo \"$O\" | grep -A1 -i $ICCID | tail -1)\n    # signal info\n    if [ \"$MODE\" = \"LTE\" ]; then\n        EARFCN=$(echo \"$O\" | awk -F: '/DL CHANNEL:/ {print $2}')\n    \tRSRP=$(echo \"$O\" | awk -F: '/RSRP:/ {print $2}')\n    \tSINR=$(echo \"$O\" | awk -F: '/SINR:/ {printf $2/5-20}')\n    \tCID=$(echo \"$O\"  | awk -F: '/CELL ID:/ {print $2}')\n    \tLAC=$(echo \"$O\" | awk -F[,\\ ] '/\\+CREG:/{print $5}')\n    \tENBx=$(echo $CID | sed -e 's/..$//')\n    \tCELL=$(printf %d 0x${CID: -2})\n    \tENBID=$(printf %d 0x$ENBx)\n\n    \tRSRQ=$(echo \"$O\" | awk -F: '/RSRQ:/ {print $2}')\n    \tBWUL=$(echo \"$O\" | awk -F: '/UL CHANNEL:/ {print $2}')\n    \tBWDL=$(echo \"$O\" | awk -F: '/DL CHANNEL:/ {print $2}')\n    elif [ \"$MODE\" = \"WCDMA\" ]; then\n    \tLAC=$(echo \"$O\" | awk -F[,\\ ] '/\\+CREG:/{print $5}')\n    \tCID=$(echo \"$O\"  | awk -F: '/CELL ID:/ {print $2}')\n    \tEARFCN=$(echo \"$O\" | awk -F: '/DL CHANNEL:/ {print $2}')\n    \tECIO=$(echo \"$O\" | awk -F: '/ECIO:/ {print $2}')\n    \tSINR=$ECIO\n    elif [ \"$MODE\" = \"HDR\" ]; then\n    \tLAC=$(echo \"$O\" | awk -F[,\\ ] '/\\+CREG:/{print $5}')\n    \tCID=$(echo \"$O\"  | awk -F: '/SECTOR ID:/ {print $2}')\n    \tSINR=$(echo \"$O\" | awk -F: '/SINR:/ {printf $2/5-20}')\n    elif [ \"$MODE\" = \"TDSCDMA\" ]; then\n    \tLAC=$(echo \"$O\" | awk -F[,\\ ] '/\\+CREG:/{print $5}')\n    \tCID=$(echo \"$O\"  | awk -F: '/CELL ID:/ {print $2}')\n    elif [ \"$MODE\" = \"GSM\" ]; then\n    \tLAC=$(echo \"$O\" | awk -F[,\\ ] '/\\+CREG:/{print $5}')\n    \tCID=$(echo \"$O\"  | awk -F: '/CELL ID:/ {print $2}')\n    \tECIO=$(echo \"$O\" | awk -F: '/ECIO:/ {print $2}')\n    \tSINR=$ECIO\n    else\n    \tLAC=$(echo \"$O\" | awk -F[,\\ ] '/\\+CREG:/{print $5}')\n    \tCID=$(echo \"$O\"  | awk -F: '/BASIC ID:/ {print $2}')\n    \tECIO=$(echo \"$O\" | awk -F: '/ECIO:/ {print $2}')\n    \tSINR=$ECIO\n    fi\n\n    # TEMP -> according to documentation rev.1.0\n    # the device does not provide temperature information.\n\n    LAC_NUM=$(printf %d 0x$LAC)\n    CID_NUM=$(printf %d 0x$CID)\n    if [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n    \tLAC=$LAC_NUM\n    \tCID=$CID_NUM\n    fi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/STYX.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?; +CREG=2; +CREG?; +CGREG?; +CEREG?; +CSQ\nAT+PLMCELLINF?\nAT+CGMI; +CGMM; +CGMR; +CIMI; +ICCID; +CGSN\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/THALES",
    "content": "modem_data(){\n        # hwinfo\n\tO=$(echo \"$O\" | grep -Ev '^OK|^$|^NO|^ERR')\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\tset -- $(echo \"$O\" | awk '\n\t\t/^\\+CEREG/{getline manuf; getline model; getline fw}\n\t\t/ICCID/{iccid=$2;getline imei; getline imsi}\n\t\t/^TSENS|xo_therm_buf:/{gsub(\"C\", \"\"); temp=$2}\n\t\tEND {print manuf \"|\" model \"|\" fw \"|\" iccid \"|\" imei \"|\" imsi \"|\" temp}\n\t')\n\tMANUF=${1}; MODEL=${2}; FW=${3}; ICCID=${4}; IMEI=${5}; IMSI=${6}; CHIPTEMP=${7}\n\tIFS=$OIFS; unset OIFS\n        DEVICE=\"$MANUF $MODEL\"\n\t# signal info\n\tgeneric_data\n\tOIFS=\"$IFS\"; IFS=\"|\"\n\tset -- $(echo \"$O\" | awk -v mode=\"$MODE\" '\n\tBEGIN { map[3]=1; map[5]=2; map[10]=3; map[15]=4; map[20]=5 }\n\tmode ~ /LTE/ { # LTE mode\n\t\tif (/\\+CEREG/) { gsub(\"\\\"\",\"\"); split($0,a,\",\"); lac=a[3]; cid=a[4]}\n\t\tif (/pcell:/) { f=1\n\t\t\tfor (i=1; i<=NF; i++) {\n\t\t\t\tif ($i ~ /lte_band_width:/) { split($i, b, \":\"); bwc_raw = b[2]; sub(\"MHz\", \"\", bwc_raw)\n\t\t\t\tbwc = sprintf(\"%.0f\", bwc_raw);bw = map[bwc]\n\t\t\t}\n\t\t}; next }\n\t\tif (/^scell:/) { f=0; next }\n\t\tif (f && /channel:/) { split($0,a,\" \"); earfcn=substr(a[1],9)+0; pci=substr(a[2],5)+0 }\n\t\tif (f && /lte_rsrp:/) { split($0,a,\",\"); rsrp=sprintf(\"%.0f\", substr(a[1],10)+0); rsrq=sprintf(\"%.0f\", substr(a[2],6)+0)}\n\t\tif (f && /lte_snr:/) { split($0,a,\",\"); sinr=sprintf(\"%.0f\", substr(a[2],10)+0) }\n\t\tif (/^SCC[0-9]+ info:/) { lte_ca++ }\n\t\tif (/\\+$QCSQ/) { split($0,a,\",\"); rssi=a[5] }\n\t}\n\tmode !~ /LTE/{ # 3G mode\n\t\tif (/\\+CREG/) { gsub(\"\\\"\",\"\"); split($0,a,\",\"); lac=a[3]; cid=a[4]}\n\t\tif (/channel:/) {split($0,a,\":\"); earfcn=a[2]}\n\t\tif (/\\+$QCSQ/) { split($0,a,\",\"); rssi=a[5] }\n\t\tif (/cell1:/) { split($0,a,\",\"); sinr=sprintf(\"%.0f\", substr(a[3],6)) }\n\t}\n\tEND { print earfcn \"|\" lac \"|\" cid \"|\" rssi \"|\" sinr \"|\" rsrp \"|\" rsrq \"|\" pci \"|\" bw \"|\" bwc \"|\" lte_ca }\n\t')\n\tEARFCN=${1}; LAC=${2}; CID=${3}; RSSI=${4}; SINR=${5}; RSRP=${6}; RSRQ=${7}; PCI=${8}; BWDL=${9}; BWDx=${10}; LTE_CA=${11}\n\tIFS=$OIFS; unset OIFS\n\tif [ \"$MODE\" = \"LTE\" ]; then\n\t\tENBx=$(echo $CID | sed -e 's/..$//')\n\t\tCELL=$(printf %d 0x${CID: -2})\n\t\tENBID=$(printf %d 0x$ENBx)\n\t\t# CA calc\n\t\tif [ $LTE_CA -ge 1 ]; then\n\t\t\tOIFS=\"$IFS\"; IFS=\"|\"\n\t\t\tset -- $(echo \"$O\" | awk -v bwc=${BWDx:-0} '\n\t\t\t\t/^SCC\\d info:/ {for(i=1; i<=NF; i++) { if($i ~ /LTE_B/) { gsub(/[^0-9]/, \"\", $i); scc = scc \"+\" $i}}\n\t\t\t\t\tbwca_raw += sprintf(\"%.0f\", $8); bwca=bwca_raw+bwc}\n\t\t\tEND { print scc \"|\" bwca }')\n\t\t\tIFS=\"$OIFS\"; unset BWDx OIFS\n\t\t\tSCC=${1}; BWCA=${2}\n                fi\n\tfi\n\n        if [ $(uci -q get modeminfo.@general[0].decimail) -eq 1 ]; then\n                LAC=$(printf %d 0x$LAC)\n                CID=$(printf %d 0x$CID)\n        fi\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/THALES.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?\nAT+CSQ\nAT+CREG=2; +CREG?\nAT+CGREG=2; +CGREG?\nAT+CEREG=2; +CEREG?\nAT+CGMI; +CGMM; +CGMR\nAT^DEBUG?\nAT+ICCID; +CGSN; +CIMI\nAT$QCSQ\nAT^TEMP?\nAT^CA_INFO?\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/THINKWILL",
    "content": "function modem_data(){\n\tgeneric_data\n\tDATA=$(echo \"$O\" | grep -Ev '^$|^OK|^AT' | sort)\n\tMANUF=$(echo \"$DATA\" | awk '/CGMI/{print $2}')\n\tMODEL=$(echo \"$DATA\" | awk '/CGMM/{print $2}')\n\tFW=$(echo \"$DATA\" | awk '/CGMR/{print $2}')\n\tDEVICE=\"$MANUF $MODEL\"\n\tTECH=$(echo \"$DATA\" | awk -F [,] '/\\+COPS/{print $(NF-1)}')\n\tICCID=$(echo \"$DATA\" | awk '/ICCID:/{print $2}')\n\tIMSI=$(echo \"$DATA\" | awk '/CIMI/{print $2}')\n\tIMEI=$(echo \"$DATA\" | awk '/CGSN/{print $2}')\n\tREGST=$(echo \"$DATA\" | awk -F[,] '/\\'$CREG'/ {print $2}')\n\tCHIPTEMP=$(echo \"$DATA\" | awk '/\\+GETPATEMP/{printf \"%.0f\\n\", ($2/100)}')\n\tEARFCN=$(echo \"$DATA\" | awk -F [:,] '/\\+ZEMSCIQ/{print $9}')\n\n\tcase $TECH in\n\t\t0|1) MODE=GSM ;;\n\t\t2) MODE=WCDMA ;;\n\t\t3) MODE=EDGE ;;\n\t\t4) MODE=HSDPA ;;\n\t\t5) MODE=HSUPA ;;\n\t\t6) MODE=HSDPA,HSUPA ;;\n\t\t7) MODE=LTE ;;\n\t\t8) MODE=HSPA+ ;;\n\tesac\n\t\n\tcase $TECH in\n\t\t7)\n\t\t\tPCI=$(echo \"$DATA\" | awk -F [:,] '/\\+ZEMSCIQ/{print $8}')\n\t\t\tBWDL=$(echo \"$DATA\" | awk -F[,] '/\\+ZEMSCIQ/{print $13}')\n\t\t\tLAC=$(echo \"$DATA\" | awk -F [:,] '/\\+CEREG/{gsub(\"\\\"\",\"\");print $4}')\n\t\t\tCID=$(echo \"$DATA\" | awk -F [:,] '/\\+CEREG/{gsub(\"\\\"\",\"\");print $5}')\n\t\t\tENBx=$(echo $CID | sed -e 's/..$//')\n\t\t\tENBID=$(printf %d 0x$ENBx)\n\t\t\tCELL=$(printf %d 0x${CID: -2})\n\t\t\tCSQ_RSSI=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{print $2}')\n\t\t\tRSRQ=$(echo \"$DATA\" | awk -F [:,] '/\\+ZEMSCIQ/{printf \"%.0f\\n\", ($12/2-20)}')\n\t\t\tRSRP=$(echo \"$DATA\" | awk -F [:,] '/\\+ZEMSCIQ/{print ($11-140)}')\n\t\t\tSINR=$(echo \"$DATA\" | awk -F [:,] '/\\+ZSINR/{printf \"%.0f\\n\", $2}')\n\t\t\tCHIPTEMP=$((85-$CHIPTEMP))\n\t\t;;\n\t\t2|4|5|6|8)\n\t\t\t#LAC=$(echo \"$DATA\" | awk -F [:,] '/\\+CGREG/{gsub(\"\\\"\",\"\");print $4}')\n\t\t\t#CID=$(echo \"$DATA\" | awk -F [:,] '/\\+CGREG/{gsub(\"\\\"\",\"\");print $5}')\n\t\t\tSINR=$(echo \"$DATA\" | awk -F [:,] '/\\+ZEMSCIQ/{printf \"%.0f\\n\", (($NF/2)-20)}')\n\t\t;;\n\t\t*)\n\t\t\tLAC=$(echo \"$DATA\" | awk -F [:,] '/\\+CGREG/{gsub(\"\\\"\",\"\");print $4}')\n\t\t\tCID=$(echo \"$DATA\" | awk -F [:,] '/\\+CGREG/{gsub(\"\\\"\",\"\");print $5}')\n\t\t\tEARFCN=$(echo \"$DATA\" | awk -F [:,] '/\\+ZEMSCIQ/{print $9}')\n\t\t;;\n\t\t\t\n\tesac\n\tif [ $(uci -q get modeminfo.@general[0].decimail) -eq 1 ]; then\n                LAC=$(printf %d 0x$LAC)\n                CID=$(printf %d 0x$CID)\n        fi\t\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/THINKWILL.at",
    "content": "ATE1\nAT+COPS=3,0\nAT+COPS?\nAT+CREG=2  \nAT+CGREG=2 \nAT+CEREG=2\nAT+CREG?\nAT+CGREG?\nAT+CEREG?\nAT+CSQ\nAT+ZRSSI\nAT+ZSINR\nAT+ZEMSCIQ=1\nAT+CGMI\nAT+CGMM\nAT+CGMR\nAT+CIMI\nAT+ICCID\nAT+CGSN\nAT+GETPATEMP?\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/YUGE",
    "content": "function modem_data(){\n\tDATA=$(echo \"$O\" | grep -Ev '^OK')\n\tMANUF=$(echo \"$DATA\" | awk 'c&&! --c;/ICCID/{c=1}')\n\tMODEL=$(echo \"$DATA\" | awk 'c&&! --c;/ICCID/{c=2}')\n\tFW=$(echo \"$DATA\" | awk 'c&&! --c;/ICCID/{c=3}')\n\tDEVICE=\"$MANUF $MODEL\"\n\tTECH=$(echo \"$DATA\" | awk -F [,] '/\\+COPS/{print $NF}')\n\tICCID=$(echo \"$DATA\" | awk '/ICCID/{print $2}')\n\tIMSI=$(echo \"$DATA\" | awk 'c&&! --c;/ICCID/{c=5}')\n\tIMEI=$(echo \"$DATA\" | awk 'c&&! --c;/ICCID/{c=4}')\n\t# ToDO\n\t#CHIPTEMP=$(echo \"$DATA\" | awk '/\\+ZCHIPTEMP/{print $2}')\n\n\tcase $TECH in\n\t\t0|1) MODE=GSM ;;\n\t\t2) MODE=WCDMA ;;\n\t\t3) MODE=EDGE ;;\n\t\t4) MODE=HSDPA ;;\n\t\t5) MODE=HSUPA ;;\n\t\t6) MODE=HSDPA,HSUPA ;;\n\t\t7) MODE=LTE ;;\n\t\t8) MODE=DC-HSPA+ ;;\n\tesac\n\n\tcase $TECH in\n\t\t7)\n\t\t\tPCI=$(echo \"$DATA\" | awk -F [:,] '/\\+ZCDS/{print $10}')\n\t\t\tEEMGINFO=$(echo \"$O\" | awk -F[':'] '/^\\+EEMLTESVC:/ {print $2}')\n\t\t\tEARFCN=$(echo $EEMGINFO | awk -F [,] '{print $6}')\n\t\t\tLAC=$(echo $EEMGINFO | awk -F [,] '{printf \"%x\\n\", toupper($4)}')\n\t\t\tCID=$(echo $EEMGINFO | awk -F [,] '{printf \"%x\\n\", toupper($21)}')\n\t\t\tENBx=$(echo $CID | sed -e 's/..$//')\n\t\t\tENBID=$(printf %d 0x$ENBx)\n\t\t\tCELL=$(printf %d 0x${CID: -2})\n\t\t\tPCI=$(echo $EEMGINFO | awk -F [,] '{print $5}')\n\t\t\tRSRQ=$(echo $EEMGINFO | awk -F[,] '{printf \"%.0f\\n\", -19.5+$11/2}')\n\t\t\tRSRP=$(echo $EEMGINFO | awk -F[,] '{print -140+$10'})\n\t\t\tSINR=$(echo $EEMGINFO | awk -F[','] '{printf \"%.0f\\n\", $12/4+5}')\n\t\t\tBWDL=$(echo $EEMGINFO | awk -F[','] '{print $9}')\n\t\t;;\n\t\t2|4|5|6|8)\n\t\t\tEEMGINFO=$(echo \"$O\" | awk -F[':'] '/^\\+EEMUMTSSVC/ {print $2}')\n\t\t\tEARFCN=$(echo $EEMGINFO | awk -F [,] '{print $20}')\n\t\t\tSINR=$(echo $EEMGINFO | awk -F [,] '{printf \"%.0f\\n\", -24+$6/2}')\n\t\t\tLAC=$(echo $EEMGINFO | awk -F [,] '{printf \"%x\\n\", toupper($16)}')\n\t\t\tCID=$(echo $EEMGINFO | awk -F [,] '{printf \"%x\\n\", toupper($17)}')\n\t\t\t\n\t\t;;\n\tesac\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n                LAC=$(printf %d 0x$LAC)\n                CID=$(printf %d 0x$CID)\n        fi\t\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/YUGE.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?; +CREG?; +CGREG?; +CGREG?; +CEREG?; +CSQ\nAT+EEMGINFO?\nAT+CHIPTEMP?\nAT+ICCID; +CGMI; +CGMM; +CGMR; +CIMI ;+CGSN\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/ZTE",
    "content": "function modem_data(){\n\tgeneric_data\n\tDATA=$(echo \"$O\" | grep -Ev '^$|^OK' | sort)\n\tMANUF=$(echo \"$DATA\" | awk 'c&&! --c;/ICCID/{c=2}')\n\tMODEL=$(echo \"$DATA\" | awk 'c&&! --c;/ICCID/{c=1}')\n\tFW=$(echo \"$DATA\" | awk 'c&&! --c;/ZRSSI/{c=3}')\n\tDEVICE=\"$MANUF $MODEL\"\n\tTECH=$(echo \"$DATA\" | awk -F [,] '/\\+COPS/{print $NF}')\n\tICCID=$(echo \"$DATA\" | awk '/ICCID:/{print $2}')\n\tIMSI=$(echo \"$DATA\" | awk 'c&&! --c;/ZRSSI/{c=1}')\n\tIMEI=$(echo \"$DATA\" | awk 'c&&! --c;/ZRSSI/{c=2}')\n\n\tcase $TECH in\n\t\t0|1) MODE=GSM ;;\n\t\t2) MODE=WCDMA ;;\n\t\t3) MODE=EDGE ;;\n\t\t4) MODE=HSDPA ;;\n\t\t5) MODE=HSUPA ;;\n\t\t6) MODE=HSDPA,HSUPA ;;\n\t\t7) MODE=LTE ;;\n\t\t8) MODE=DC-HSPA+ ;;\n\tesac\n\t\n\tcase $TECH in\n\t\t7)\n\t\t\tPCI=$(echo \"$DATA\" | awk -F [:,] '/\\+ZCELLINFO/{print $3}')\n\t\t\tEARFCN=$(echo \"$DATA\" | awk -F [:,] '/\\+ZCELLINFO/{print $5}')\n\t\t\tLAC=$(echo \"$DATA\" | awk -F [:,] '/\\+CEREG/{gsub(\"\\\"\",\"\");print $4}')\n\t\t\tCID=$(echo \"$DATA\" | awk -F [:,] '/\\+CEREG/{gsub(\"\\\"\",\"\");print $6}')\n\t\t\tENBx=$(echo $CID | sed -e 's/..$//')\n\t\t\tENBID=$(printf %d 0x$ENBx)\n\t\t\tCELL=$(printf %d 0x${CID: -2})\n\t\t\tCSQ_RSSI=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{print $4}')\n\t\t\tRSRQ=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{print $3}')\n\t\t\tRSRP=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{print $2}')\n\t\t\tSINR=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{printf \"%.0f\\n\", $5}')\n\t\t;;\n\t\t2|4|5|6|8)\n\t\t\tLAC=$(echo \"$DATA\" | awk -F [:,] '/\\+CGREG/{gsub(\"\\\"\",\"\");print $4}')\n\t\t\tCID=$(echo \"$DATA\" | awk -F [:,] '/\\+CGREG/{gsub(\"\\\"\",\"\");print $5}')\n\t\t\tEARFCN=$(echo \"$DATA\" | awk -F [:,] '/\\+ZCELLINFO/{print $4}')\n\t\t\tSINR=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{printf \"%.0f\\n\", $3}')\n\t\t;;\n\t\t0|1|3)\n\t\t\tCSQ_RSSI=$(echo \"$DATA\" | awk -F [:,] '/\\+ZRSSI/{print $2}')\n\t\t\tLAC=$(echo \"$DATA\" | awk -F [:,] '/\\+CGREG/{gsub(\"\\\"\",\"\");print $4}')\n\t\t\tCID=$(echo \"$DATA\" | awk -F [:,] '/\\+CGREG/{gsub(\"\\\"\",\"\");print $5}')\n\t\t\tEARFCN=$(echo \"$DATA\" | awk -F [:,] '/\\+ZCELLINFO/{print $4}')\n\t\t;;\n\t\t\t\n\tesac\n\tif [ $(uci -q get modeminfo.@general[0].decimail) = \"1\" ]; then\n                LAC=$(printf %d 0x$LAC)\n                CID=$(printf %d 0x$CID)\n        fi\t\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/ZTE.at",
    "content": "ATE0\nAT+COPS=3,0; +COPS?\nAT+CREG=2; +CREG?\nAT+CGREG=2; +CGREG?\nAT+CEREG=2; +CEREG?\nAT+CSQ; +ZPAS?; +ZRSSI; +ZCELLINFO?\nAT+CGMI; +CGMM; +CGMR; +CIMI; +ICCID; +CGSN\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/ch_to_band",
    "content": "#!/usr/bin/awk -f\n\nfunction find_band(value, bands) {\n    for (i in bands) {\n        split(bands[i], parts, /[-:]/)\n        if (value >= parts[1] && value <= parts[2]) {\n            return parts[3]\n        }\n    }\n    return \"\"\n}\n\nBEGIN {\n    if (ARGC < 3) {\n        print \"Usage: \" ARGV[0] \" <MODE> <CHANNEL>\"\n        exit 1\n    }\n\n    mode = ARGV[1]\n    value = ARGV[2]\n    ARGC = 1\n\n    # LTE Bands\n    lte_bands[0] = \"0-599:1\"\n    lte_bands[1] = \"600-1199:2\"\n    lte_bands[2] = \"1200-1949:3\"\n    lte_bands[3] = \"1950-2399:4\"\n    lte_bands[4] = \"2400-2469:5\"\n    lte_bands[5] = \"2750-3449:7\"\n    lte_bands[6] = \"3450-3799:8\"\n    lte_bands[7] = \"6150-6449:20\"\n    lte_bands[8] = \"9210-9659:28\"\n    lte_bands[9] = \"9870-9919:31\"\n    lte_bands[10] = \"37750-38249:38\"\n    lte_bands[11] = \"38650-39649:40\"\n    lte_bands[12] = \"39650-41589:41\"\n\n    # Other Bands\n    other_bands[0] = \"1-124:GSM900\"\n    other_bands[1] = \"512-885:DCS1800\"\n    other_bands[2] = \"955-1023:DCS900\"\n    other_bands[3] = \"2937-3088:UMTS900\"\n    other_bands[4] = \"10562-10838:IMT2100\"\n\n    if (mode ~ /^LTE/) {\n        result = find_band(value, lte_bands)\n        print (result != \"\" ? result : 0)\n    } else {\n        result = find_band(value, other_bands)\n        print (result != \"\" ? result : \"\")\n    }\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/modeminfo",
    "content": "# (c) 2010-2016 Cezary Jackiewicz <cezary@eko.one.pl>\n# (c) 2020-2025 modified by Konstantine Shevlyakov  <shevlakov@132lan.ru>\n# (c) 2021-2025 modified by Vladislav Kadulin  <spanky@yandex.ru>\n\n\nRES=\"/usr/share/modeminfo\"\n#GSCRIPT=\"$RES/scripts/${FAMILY}.txt\"\n\nfunction get_device_info() {\n\tDEVPORT=$(uci -q get modeminfo.@modeminfo[${s}].device)\n\tcase $DEVPORT in\n\t\t!*tty*) get_port ;;\n\tesac\n\tmodem_family\n\tGSCRIPT=\"$RES/scripts/${FAMILY}.at\"\n\tO=$(atinout $GSCRIPT $DEVPORT - |grep -v '^$')\n}\n\nfunction get_port() {\n\tdevices=\"$(ls /dev/ttyUSB* /dev/ttyACM* /dev/ttyHS* 2>/dev/null | sort -r)\"\n\t\tfor d in $devices; do\n\t\t\tDEVPORT=$d gcom -s $RES/scripts/probeport.gcom > /dev/null 2>&1\n\t\t\tif [ $? = 0 ]; then\n\t\t\t\tuci set modeminfo.@modeminfo[${s}].device=\"$d\"\n\t\t\t\tuci commit modeminfo\n\t\t\t\tbreak\n\t\t\tfi\n\t\tdone\n\tDEVPORT=$(uci -q get modeminfo.@modeminfo[${s}].device)\n\tO=$(atinout $GSCRIPT $DEVPORT - |grep -v '^$')\n}\n\nfunction modem_family() {\n    FAMILY=$(uci -q get modeminfo.@modeminfo[${s}].device | awk -F/ -v res=\"$RES\" '\n        function read_file(path) {\n            value = \"\"\n            if ((getline value < path) > 0) {\n                close(path)\n                return value\n            }\n            return \"\"\n        }\n        \n        {\n            comport = $NF\n            if (comport ~ /ttyUSB|ttyHS/) {\n                device_path = \"/sys/class/tty/\" comport \"/device/../..\"\n            } else if (comport ~ /ttyACM/) {\n                device_path = \"/sys/class/tty/\" comport \"/device/..\"\n            } else {\n                print \"GENERIC\"\n                exit\n            }\n            \n            vid = read_file(device_path \"/idVendor\")\n            pid = read_file(device_path \"/idProduct\")\n            \n            if (vid == \"\" || pid == \"\") {\n                print \"GENERIC\"\n                exit\n            }\n            \n            vidpid = vid pid\n            modem_list = res \"/modem.list\"\n            \n           # Read modem.list file\n            while ((getline line < modem_list) > 0) {\n                if (line ~ /^#/ || line == \"\") continue\n                split(line, parts, \";\")\n                if (parts[1] == vidpid) {\n                    close(modem_list)\n                    print toupper(parts[2])\n                    exit\n                }\n            }\n            close(modem_list)\n            print \"GENERIC\"\n        }\n    ')\n}\n\n# get CSQ\nfunction get_csq(){\n    eval $(echo \"$O\" | awk -F[:,] '\n        /^\\+CSQ/ {\n            csq = $2\n            if (csq == \"\" || csq < 0 || csq > 31) {\n                print \"CSQ=-1; CSQ_PER=0; CSQ_COL=\\\"black\\\"; CSQ_RSSI=0\"\n                exit\n            }\n            \n            # Percentage calc\n            csq_per = int(csq * 100 / 31)\n            \n            # Color\n            if (csq >= 20) csq_col = \"green\"\n            else if (csq >= 15) csq_col = \"orange\" \n            else if (csq >= 10) csq_col = \"red\"\n            else csq_col = \"red\"\n            \n            # RSSI calc\n            csq_rssi = 2 * csq - 113\n            \n            printf \"CSQ=%d; CSQ_PER=%d; CSQ_COL=\\\"%s\\\"; CSQ_RSSI=%d\", csq, csq_per, csq_col, csq_rssi\n            exit\n        }\n        \n        END {\n            if (!csq) print \"CSQ=-1; CSQ_PER=0; CSQ_COL=\\\"black\\\"; CSQ_RSSI=0\"\n        }\n    ')\n}\n\n# Get MCC or MNC \nfunction get_cops() {\n\tCOPS=$(echo \"$O\" | awk -F[\\\"] '/^\\+COPS:\\s?.,[0-9]+/ {print $2}' | awk '{for(i=NF;i>=1;i--){w=tolower($i);if(!s[w]++)r=r?$i\" \"r:$i} print r}')\t\n}\n\n# Get Registration data\nfunction get_reg_data(){\n\tREGST=$(echo \"$O\" | awk -F',' '/\\+CREG|\\+CGREG|\\+CEREG/ {print $2; exit}')\n}\n\nfunction generic_data(){\n    MODE=$(echo \"$O\" | awk -F, '\n        /^\\+COPS/ {\n            modes[\"2\"] = \"UMTS\"\n            modes[\"0\"] = modes[\"3\"] = \"EDGE\"\n            modes[\"4\"] = \"HSDPA\"\n            modes[\"5\"] = \"HSUPA\"\n            modes[\"6\"] = \"HSPA\"\n            modes[\"7\"] = modes[\"13\"] = \"LTE\"\n            \n            prefix = substr($4, 1, 2)\n            print (prefix in modes ? modes[prefix] : \"--\")\n        }')\n}\n\n# name device via mmcli utility\nmmcli_name(){\n        MMCLI=$(uci -q get modeminfo.@modeminfo[${s}].mmcli_name)\n        if [ \"$MMCLI\" = \"1\" ]; then\n                if [ -x /usr/bin/mmcli ]; then\n                        MODEM=$(mmcli -L | awk '{print $1}' | awk -F [/] '{print $NF}')\n                        for mm in $MODEM; do\n                                MMCLIMEI=$(mmcli -m $mm -J | jsonfilter -e '@[\"modem\"].*[\"imei\"]')\n                                IMEI=$(echo $IMEI | tr -d ' ')\n                                if [ \"$IMEI\" = \"$MMCLIMEI\" ]; then\n                                        MANUF=$(mmcli -m $mm -J | jsonfilter -e '@[\"modem\"].*[\"manufacturer\"]' | awk '{gsub(\"\\r\", \"\"); print $0}')\n                                        MODEL=$(mmcli -m $mm -J | jsonfilter -e '@[\"modem\"].*[\"model\"]' | awk '{gsub(\"\\r\", \"\"); print $0}')\n                                        if [ -n \"$(echo \"$MODEL\" | grep \"${MANUF}\")\" ]; then\n                                                DEVICE=\"$MODEL\"\n                                        else\n                                                DEVICE=\"$MANUF $MODEL\"\n                                        fi\n                                fi\n                        done\n                fi\n        fi\n}\n\nfunction get_data_in(){\n\tmodem_family\n\tget_reg_data\n\tget_cops\n\tget_csq\n\tif [ -f \"$RES/scripts/${FAMILY}\" ]; then\n\t\t. $RES/scripts/$FAMILY\n\t\tmodem_data\n\telse\n\t\tgeneric_data\n\tfi\n\tmmcli_name\n}\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/modeminfo/scripts/probeport.gcom",
    "content": "let $d = $env(\"DEVPORT\")\nif $d = \"\" goto timeout\n\nopen com $d\nset com 57600n81\nset senddelay 0.05\nwaitquiet 2 0.5\nsend \"AT^m\"\nwaitfor 2 \"OK\"\nif % = -1 goto timeout\nexit 0\n\n:timeout\n  exit 1\n"
  },
  {
    "path": "packages/net/modeminfo/root/usr/share/snmpmodem.sh",
    "content": "#!/bin/sh\n\n# Simple SNMP Script for modeminfo\n# Exapmle configuration OpenWrt snmpd package\n# file: /etc/config/snmpd\n# config exec\n#        option name     modem\t\t\t\t# Example name\n#        option prog     /usr/share/snmpmodem.sh\t# This script\n#        option args     0\t\t\t\t# Modem in system\n#        option miboid   1.3.6.1.2.1.25.1.9\t\t# Example OID\n\nif [ ! $1 ]; then\n\texit 0\nfi\n\nmodeminfo | jsonfilter -e \"@['modem'][$1]['device']\" \\\n\t-e \"@['modem'][$1]['cops']\" \\\n\t-e \"@['modem'][$1]['rssi']\" \\\n\t-e \"@['modem'][$1]['rsrp']\" \\\n\t-e \"@['modem'][$1]['rsrq']\" \\\n\t-e \"@['modem'][$1]['sinr']\"\n\n"
  },
  {
    "path": "packages/net/mrtg/Makefile",
    "content": "#\n# Copyright (C) 2011-2012 OpenWrt.org\n# Copyright (C) 2011 Paolo Pisati <p.pisati@gmail.com>\n#\n# This is free software, licensed under the GNU General Public License v2.\n# See /LICENSE for more information.\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=mrtg\nPKG_VERSION:=2.17.10\nPKG_RELEASE:=1\n\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\nPKG_SOURCE_URL:=http://oss.oetiker.ch/mrtg/pub/\nPKG_MD5SUM:=ab1c14acc9af4221f459707339f361b3\n\nPKG_INSTALL:=1\nPKG_FIXUP:=autoreconf\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/mrtg\n  SECTION:=net\n  CATEGORY:=Network\n  DEPENDS:=+libgd +libpng +zlib +perl +perlbase-essential +perlbase-findbin \\\n    +perlbase-cwd +perlbase-config +perlbase-file +perlbase-getopt \\\n    +perlbase-math +perlbase-integer +perlbase-xsloader +perlbase-pod \\\n    +perlbase-universal +perlbase-socket +perlbase-posix +perlbase-autoloader \\\n    +perlbase-fcntl +perlbase-tie +perlbase-io +perlbase-symbol \\\n    +perlbase-selectsaver +perlbase-errno +perlbase-scalar +perlbase-list \\\n\t+perlbase-base +perlbase-storable\n  TITLE:=The Multi Router Traffic Grapher\n  URL:=http://oss.oetiker.ch/mrtg/index.en.html\nendef\n\ndefine Package/mrtg/description\n  The Multi Router Traffic Grapher, or just simply MRTG, is free software for\n  monitoring and measuring the traffic load on network links. It allows the\n  user to see traffic load on a network over time in graphical form. It was\n  originally developed by Tobias Oetiker and Dave Rand to monitor router\n  traffic, but has developed into a tool that can create graphs and statistics\n  for almost anything.\nendef\n\ndefine Package/mrtg/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(CP) $(PKG_INSTALL_DIR)/usr/bin/* $(1)/usr/bin/\n\t$(INSTALL_DIR) $(1)/usr/lib\n\t$(CP) $(PKG_INSTALL_DIR)/usr/lib/* $(1)/usr/lib/\n\t$(INSTALL_DIR) $(1)/usr/share\n\t$(CP) $(PKG_INSTALL_DIR)/usr/share/* $(1)/usr/share/\nendef\n\n$(eval $(call BuildPackage,mrtg))\n"
  },
  {
    "path": "packages/net/mrtg/README.md",
    "content": "# mrtg-openwrt\nPackaging MRTG with OpenWrt SDK\n\nHow-to compile:\n```\ncd feeds/package/net\ngit clone https://github.com/koshev-msk/mrtg-openwrt.git\ncd ../../..\n./scripts feeds update -a; ./scripts/feeds install -a\nmake -j $(($(nproc)+1)) package/feeds/packages/mrtg-openwrt/compile\n```\n"
  },
  {
    "path": "packages/net/mrtg/patches/0001-disable-longlong-fmt.patch",
    "content": "--- mrtg-2.17.10.orig/configure.ac\n+++ mrtg-2.17.10/configure.ac\n@@ -20,46 +20,6 @@ fi\n \n AC_PATH_PROGS(NROFF, groff nroff)\n \n-dnl Check for long long type (64 bit rateup)\n-AC_CHECK_HEADERS(inttypes.h)\n-AC_CHECK_TYPE(unsigned long long)\n-AC_CHECK_TYPE(long long)\n-AC_CHECK_FUNCS(strtoll)\n-\n-# Taken from libIDL-0.8.2\n-AC_MSG_CHECKING([for printf long long format specifier])\n-AC_CACHE_VAL(mr_cv_long_long_format_specifier,[\n-    for format in ll l q I64; do\n-        AC_RUN_IFELSE([AC_LANG_SOURCE([[#include <stdio.h>  \n-            int main()\n-            {\n-                long long b, a = -0x3AFAFAFAFAFAFAFALL;\n-                char buffer[1000];\n-                sprintf (buffer, \"%${format}u\", a);\n-                sscanf (buffer, \"%${format}u\", &b);\n-                exit (b!=a);\n-            }\n-            ]])],[mr_cv_long_long_format_specifier=\"%${format}d\"\n-            mr_cv_long_long_format=\"${format}d\"\n-            break],[],[])\n-        done])\n-AC_MSG_RESULT($mr_cv_long_long_format_specifier)\n-AC_DEFINE_UNQUOTED(LLD, \"$mr_cv_long_long_format_specifier\",\n-    [How to print a long long])\n-AC_DEFINE_UNQUOTED(LLD_FORMAT, \"$mr_cv_long_long_format\",\n-    [long long format without % specifier])\n-\n-AH_BOTTOM(\n-#ifndef HAVE_STRTOLL     \n-long long int strtoll(const char *str, char **ptr, int base)\n-{\n-        long long int ll;\n-        sscanf(str, LLD, &ll);\n-        return ll;\n-}\n-#endif\n-)\n-\n dnl Checks for libraries.\n AC_CHECK_LIB(m, pow, [ MATHLIBS=\"-lm\" ])\n \n"
  },
  {
    "path": "packages/net/mrtg/patches/0002-fix-perl-path.patch",
    "content": "--- mrtg-2.17.10.orig/Makefile.in\n+++ mrtg-2.17.10/Makefile.in\n@@ -37,7 +37,7 @@ bin/rateup.o: $(top_srcdir)/src/rateup.c\n \t$(CC) $(CPPFLAGS) $(CFLAGS) -c $(top_srcdir)/src/rateup.c -o bin/rateup.o\n \n subst:\n-\t$(PERL) -0777 -p -i~ -e \"s'^#!\\s*/\\S*perl'#! $(PERL)'\" $(top_srcdir)/bin/cfgmaker $(top_srcdir)/bin/indexmaker $(top_srcdir)/bin/mrtg\n+\t$(PERL) -0777 -p -i~ -e \"s'^#!\\s*/\\S*perl'#! /usr/bin/perl'\" $(top_srcdir)/bin/cfgmaker $(top_srcdir)/bin/indexmaker $(top_srcdir)/bin/mrtg\n \t$(PERL) -0777 -p -i~ -e 's@GRAPHFMT=\"...\";@GRAPHFMT=\"$(GDFORM_EXT)\";@' $(top_srcdir)/bin/mrtg $(top_srcdir)/bin/indexmaker\n \n clean:\n"
  },
  {
    "path": "packages/net/mwan3-ledhelper/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=mwan3-ledhelper\nPKG_VERSION:=0.0.1\nPKG_RELEASE:=1\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  PKGARCH:=all\n  TITLE:=MWAN3 Led Helper\n  DEPENDS:=+mwan3\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tHotplug script to indicate led for mwan3 link states\nendef\n\ndefine Build/Prepare\nendef\n\ndefine Build/Configure\nendef\n\ndefine Build/Compile\nendef\n\n\ndefine Package/$(PKG_NAME)/install\n\t$(CP) ./root/* $(1)/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n\n"
  },
  {
    "path": "packages/net/mwan3-ledhelper/root/etc/config/mwan3_led",
    "content": "config led 'modem'\n        option iface 'modem' # mwan3 tracking interface\n        option led_on 'blue:wan'  # flash led on online state (offlline is disable)\n\toption led_off 'red:wan'  # flash led on offline state (online is disable)\n"
  },
  {
    "path": "packages/net/mwan3-ledhelper/root/etc/hotplug.d/iface/17-mwan3-ledhelper",
    "content": "#!/bin/sh\n\nmwan3_led_trigger() {\n        local INTERFACE=\"$1\"\n        local ACTION=\"$2\"\n\n        # LED state function\n        set_led() {\n                local led_name=\"$1\"\n                local value=\"$2\"\n                local led_path=\"/sys/class/leds/${led_name}/brightness\"\n\n                # Set state LED\n                echo \"$value\" > \"$led_path\" 2>/dev/null\n        }\n\n        # Get config values\n        handle_led_config() {\n                local iface led_on led_off\n\n                config_get iface \"$1\" iface\n                config_get led_on \"$1\" led_on\n                config_get led_off \"$1\" led_off\n\n                # Check interface\n                [ \"$iface\" != \"$INTERFACE\" ] && return\n\n                case \"$ACTION\" in\n                        connected)\n                                # Selected LED led_on\n                                set_led \"$led_on\" 255\n                                # if defined led_off\n                                [ -n \"$led_off\" ] && set_led \"$led_off\" 0\n                        ;;\n                        disconnected)\n\t                        if [ -n \"$led_off\" ]; then\n        \t                        # if defined led_off\n                \t                set_led \"$led_off\" 255\n\t                        fi\n        \t                # Off flashing LED led_on\n                \t        set_led \"$led_on\" 0\n                        ;;\n\t\t\tifdown)\n\t\t\t\tset_led \"$led_on\" 0\n\t\t\t\tif [ -n \"$led_off\" ]; then\n\t\t\t\t\tset_led \"$led_off\" 0\n\t\t\t\tfi\n\t\t\t;;\n                esac\n        }\n\n        # get config\n        config_load mwan3_led\n        config_foreach handle_led_config led\n}\n\n[ -f \"/etc/config/mwan3_led\" ] && {\n\t. /lib/functions.sh\n\t. /lib/mwan3/mwan3.sh\n\tinitscript=/etc/init.d/mwan3\n\t. /lib/functions/procd.sh\n\n\t[ \"$MWAN3_SHUTDOWN\" != 1 ] && procd_lock\n\n\t[ \"$MWAN3_SHUTDOWN\" != 1 ] && ! /etc/init.d/mwan3 running && {\n\t\texit 0\n\t}\n\n\tconfig_load mwan3\n\n\tconfig_get_bool enabled \"$INTERFACE\" enabled 0\n\t[ \"${enabled}\" -eq 1 ] || {\n\t\texit 0\n\t}\n\tmwan3_led_trigger $INTERFACE $ACTION\n}\n\nexit 0\n"
  },
  {
    "path": "packages/net/mwan3-ledhelper/root/usr/share/mwan3_led_config.sh",
    "content": "#!/bin/sh\n\nusage() {\n\techo \"Usage: $0 -i <interface1> [interface2 ...] -l <led1> [led2 ...] [-o <led_off1> [led_off2 ...]]\"\n\texit 1\n}\n\n# Parse arguments\nparse_args() {\n\twhile [ $# -gt 0 ]; do\n\t\tcase \"$1\" in\n\t\t\t-i)\n\t\t\t\tshift\n\t\t\t\twhile [ $# -gt 0 ] && [ \"$(echo \"$1\" | cut -c1)\" != \"-\" ]; do\n\t\t\t\t\tinterfaces=\"$interfaces $1\"\n\t\t\t\t\tshift\n\t\t\t\tdone\n\t\t\t;;\n\t\t\t-l)\n\t\t\t\tshift\n\t\t\t\twhile [ $# -gt 0 ] && [ \"$(echo \"$1\" | cut -c1)\" != \"-\" ]; do\n\t\t\t\t\tleds_on=\"$leds_on $1\"\n\t\t\t\t\tshift\n\t\t\t\tdone\n\t\t\t;;\n\t\t\t-o)\n\t\t\t\tshift\n\t\t\t\twhile [ $# -gt 0 ] && [ \"$(echo \"$1\" | cut -c1)\" != \"-\" ]; do\n\t\t\t\t\tleds_off=\"$leds_off $1\"\n\t\t\t\t\tshift\n\t\t\t\tdone\n\t\t\t;;\n\t\t\t*)\n\t\t\t\tusage\n\t\t\t;;\n\t\tesac\n\tdone\n}\n\n# Validate and adjust parameters\ncheck_counts() {\n\t# Count interfaces and LEDs\n\tcount_int=$(echo \"$interfaces\" | awk '{print NF}')\n\tcount_on=$(echo \"$leds_on\" | awk '{print NF}')\n\n\tif [ \"$count_int\" -eq 0 ]; then\n\t\techo \"Error: at least one interface must be specified\"\n\t\tusage\n\tfi\n\n\tif [ \"$count_on\" -eq 0 ]; then\n\t\techo \"Error: at least one LED must be specified with -l\"\n\t\tusage\n\tfi\n\n\t# If more LEDs than interfaces, limit LEDs to match interfaces\n\tif [ \"$count_on\" -gt \"$count_int\" ]; then\n\t\tleds_on=$(echo \"$leds_on\" | awk -v n=\"$count_int\" '{for(i=1;i<=n;i++) printf \"%s \", $i; print \"\"}')\n\t\tcount_on=\"$count_int\"\n\tfi\n\n\t# If more off LEDs than interfaces, limit off LEDs to match interfaces\n\tif [ -n \"$leds_off\" ]; then\n\t\tcount_off=$(echo \"$leds_off\" | awk '{print NF}')\n\t\tif [ \"$count_off\" -gt \"$count_int\" ]; then\n\t\t\tleds_off=$(echo \"$leds_off\" | awk -v n=\"$count_int\" '{for(i=1;i<=n;i++) printf \"%s \", $i; print \"\"}')\n\t\tfi\n\tfi\n}\n\n# Generate configuration\ngenerate_config() {\n\techo \"$interfaces\" | awk -v leds_on=\"$leds_on\" -v leds_off=\"$leds_off\" '\n\tBEGIN {\n\t\tsplit(leds_on, on_arr, \" \")\n\t\tsplit(leds_off, off_arr, \" \")\n\t}\n\t{\n\t\tfor (i = 1; i <= NF; i++) {\n\t\t\tif (leds_on != \"\" && i <= length(on_arr) || leds_off != \"\" && i <= length(off_arr)) {\n\t\t\t\tprint \"config led '\\''\" $i \"'\\''\"\n\t\t\t\tprint \"\toption iface '\\''\" $i \"'\\''\"\n\t\t\t\tif (leds_on != \"\" && i <= length(on_arr)) {\n\t\t\t\t\tprint \"\toption led_on '\\''\" on_arr[i] \"'\\''\"\n\t\t\t\t}\n\t\t\t\tif (leds_off != \"\" && i <= length(off_arr)) {\n\t\t\t\t\tprint \"\toption led_off '\\''\" off_arr[i] \"'\\''\"\n\t\t\t\t}\n\t\t\t}\n\t\t\tprint \"\"\n\t\t}\n\t}'\n}\n\n# Main program\nmain() {\n\tif [ $# -eq 0 ]; then\n\t\tusage\n\tfi\n\n\tparse_args \"$@\"\n\tcheck_counts\n\tgenerate_config\n}\n\nmain \"$@\"\n\n"
  },
  {
    "path": "packages/net/n2n/Makefile",
    "content": "#\n# Copyright (C) 2021 - ntop.org and contributors\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=n2n\nPKG_VERSION:=3.1.1\nPKG_RELEASE:=1\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\n\n# These are defaults for compiling without any environmental overrides\n# (eg, the github action calculates the correct overrides for each build)\nPKG_SOURCE_URL:=https://github.com/ntop/n2n\nPKG_SOURCE_VERSION:=dev\nPKG_MIRROR_HASH:=skip\n\n# Apply overrides from the build environment\nifdef N2N_PKG_SOURCE_URL\n    PKG_SOURCE_URL:=$(N2N_PKG_SOURCE_URL)\nendif\nifdef N2N_PKG_SOURCE_VERSION\n    PKG_SOURCE_VERSION:=$(N2N_PKG_SOURCE_VERSION)\nendif\nifdef N2N_PKG_VERSION\n    PKG_VERSION:=$(N2N_PKG_VERSION)\nendif\n\nPKG_MAINTAINER:=Emanuele Faranda <faranda@ntop.org>\nPKG_LICENSE:=GPL3\n\n# autogen fix\nPKG_FIXUP:=autoreconf\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)/default\n  SECTION:=net\n  CATEGORY:=Network\n  TITLE:=N2N Peer-to-peer VPN\n  URL:=http://www.ntop.org/n2n\n  SUBMENU:=VPN\n  DEPENDS+=+libcap\nendef\n\ndefine Package/n2n-edge\n  $(call Package/n2n/Default)\n  TITLE+= client (edge node)\n  DEPENDS+=+kmod-tun\nendef\n\ndefine Package/n2n-supernode\n  $(call Package/n2n/Default)\n  TITLE+= server (supernode)\nendef\n\ndefine Package/$(PKG_NAME)-tools\n  $(call Package/n2n/Default)\n  TITLE+= tools\nendef\n\ndefine Package/$(PKG_NAME)-tests\n  $(call Package/n2n/Default)\n  TITLE+= benchmark\nendef\n\ndefine Package/$(PKG_NAME)-edge/description\nThe client node for the N2N infrastructure\nendef\n\ndefine Package/$(PKG_NAME)-supernode/description\nThe supernode for the N2N infrastructure\nendef\n\ndefine Package/$(PKG_NAME)-tools/description\nPeer-to-peer N2N VPN, keygen, keyfile, sertificate creation tools\nendef\n\ndefine Package/$(PKG_NAME)-tests/description\nPeer-to-peer N2N VPN  benchmarks\nendef\n\n\ndefine Build/Configure\n\t( cd $(PKG_BUILD_DIR); \\\n\t./autogen.sh; \\\n\t./configure )\nendef\n\ndefine Package/n2n-edge/conffiles\n/etc/n2n/edge.conf\nendef\n\ndefine Package/n2n-edge/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/edge $(1)/usr/bin/\n\t$(INSTALL_DIR) $(1)/etc/init.d\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/packages/openwrt/etc/init.d/edge $(1)/etc/init.d/edge\n\t$(INSTALL_DIR) $(1)/etc/n2n\n\t$(INSTALL_CONF) $(PKG_BUILD_DIR)/packages/etc/n2n/edge.conf.sample $(1)/etc/n2n/edge.conf\nendef\n\ndefine Package/n2n-supernode/conffiles\n/etc/n2n/supernode.conf\nendef\n\ndefine Package/n2n-supernode/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/supernode $(1)/usr/bin/\n\t$(INSTALL_DIR) $(1)/etc/init.d\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/packages/openwrt/etc/init.d/supernode $(1)/etc/init.d/supernode\n\t$(INSTALL_DIR) $(1)/etc/n2n\n\t$(INSTALL_CONF) $(PKG_BUILD_DIR)/packages/etc/n2n/supernode.conf.sample $(1)/etc/n2n/supernode.conf\nendef\n\ndefine Package/$(PKG_NAME)-tools/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/tools/n2n-benchmark \\\n\t\t$(PKG_BUILD_DIR)/tools/n2n-decode \\\n\t\t$(PKG_BUILD_DIR)/tools/n2n-keygen \\\n\t\t$(PKG_BUILD_DIR)/tools/n2n-portfwd \\\n\t\t$(PKG_BUILD_DIR)/tools/n2n-route \\\n\t\t$(1)/usr/bin/\nendef\n\ndefine Package/$(PKG_NAME)-tests/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/tools/tests-auth \\\n\t\t$(PKG_BUILD_DIR)/tools/tests-compress \\\n\t\t$(PKG_BUILD_DIR)/tools/tests-elliptic \\\n\t\t$(PKG_BUILD_DIR)/tools/tests-hashing \\\n\t\t$(PKG_BUILD_DIR)/tools/tests-transform \\\n\t\t$(PKG_BUILD_DIR)/tools/tests-wire \\\n\t\t$(1)/usr/bin/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)-supernode))\n$(eval $(call BuildPackage,$(PKG_NAME)-edge))\n$(eval $(call BuildPackage,$(PKG_NAME)-tools))\n$(eval $(call BuildPackage,$(PKG_NAME)-tests))\n"
  },
  {
    "path": "packages/net/nat64/Makefile",
    "content": "include $(TOPDIR)/rules.mk\ninclude $(INCLUDE_DIR)/kernel.mk\n\nPKG_NAME:=nat64\nPKG_SOURCE_URL:=https://github.com/primis/nat64.git\nPKG_SOURCE_DATE:=2021-06-30\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_VERSION:=1ba5895ff5f227fe9efd2d3765c4cdde6ac36899\n\nPKG_MAINTAINER:=Hans Dedecker <dedeckeh@gmail.com>\nPKG_LICENSE:=GPL-2.0\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine KernelPackage/nat64\n  DEPENDS:=@IPV6\n  TITLE:=Stateless NAT64 translation kernel module\n  SECTION:=kernel\n  SUBMENU:=Network Support\n  FILES:=$(PKG_BUILD_DIR)/nat64.ko\n  AUTOLOAD:=$(call AutoLoad,33,nat64)\nendef\n\ninclude $(INCLUDE_DIR)/kernel-defaults.mk\n\ndefine Build/Configure\n\t$(call Build/Configure/Default)\nendef\n\ndefine Build/Compile\n\t$(KERNEL_MAKE) M=\"$(PKG_BUILD_DIR)/\" \\\n\t\tMODFLAGS=\"-DMODULE -mlong-calls\" \\\n\t\tEXTRA_CFLAGS=\"-DNAT64_VERSION=\\\\\\\"$(PKG_SOURCE_VERSION)\\\\\\\"\" \\\n\t\tmodules\nendef\n\n$(eval $(call KernelPackage,nat64))\n"
  },
  {
    "path": "packages/net/nat64/patches/01-fix-checksum-packet.patch",
    "content": "--- a/nat64_factory.h\n+++ b/nat64_factory.h\n@@ -90,7 +90,7 @@ static inline void factory_translate_ip4\n \t\tbreak;\n \tcase IPPROTO_ICMPV6:\n \t\tdst->csum = csum_partial(skb_transport_header(dst), dst->len - sizeof(struct ipv6hdr), 0);\n-\t\ticmp_hdr(dst)->checksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, payload_len, nexthdr, dst->csum);\n+\t\ticmp_hdr(dst)->checksum = csum_partial(dst, payload_len, 0);\n \t\tbreak;\n \t}\n \n"
  },
  {
    "path": "packages/net/natflow/Makefile",
    "content": "#\n# Copyright (C) 2017-2019 Chen Minqiang <ptpt52@gmail.com>\n#\n# This is free software, licensed under the GNU General Public License v2.\n# See /LICENSE for more information.\n#\n\ninclude $(TOPDIR)/rules.mk\ninclude $(INCLUDE_DIR)/kernel.mk\n\nPKG_NAME:=natflow\nPKG_VERSION:=20260313\nPKG_RELEASE:=1\n\nPKG_SOURCE:=$(PKG_VERSION).tar.xz\nPKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_VERSION:=fa81c57c83d46df878a7954b8ccf6b601d8f0587\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)\nPKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>\nPKG_LICENSE:=GPL-2.0\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine KernelPackage/natflow\n  CATEGORY:=X\n  SUBMENU:=Fast Forward Stacks\n  TITLE:=natflow kernel driver\n  KCONFIG:= \\\n\tCONFIG_NETFILTER_INGRESS=y\n  FILES:=$(PKG_BUILD_DIR)/natflow.ko\n  AUTOLOAD:=$(call AutoLoad,96,natflow)\n  DEPENDS:= +kmod-ipt-conntrack +kmod-ipt-nat +kmod-ipt-ipset\nendef\n\ndefine KernelPackage/natflow/description\n  fast nat forward kmod\nendef\n\ninclude $(INCLUDE_DIR)/kernel-defaults.mk\n\ndefine Build/Compile/natflow\n\t+$(MAKE) $(PKG_JOBS) -C \"$(LINUX_DIR)\" \\\n\t\t$(KERNEL_MAKE_FLAGS) \\\n\t\tM=\"$(PKG_BUILD_DIR)\" \\\n\t\tmodules\nendef\n\ndefine Build/Compile\n\t$(call Build/Compile/natflow)\nendef\n\ndefine Package/natflow-boot\n  CATEGORY:=X\n  SUBMENU:=Fast Forward Stacks\n  TITLE:=natflow boot init scripts\n  PKGARCH:=all\n  DEPENDS:= +kmod-natflow\nendef\n\ndefine Package/natflow-boot/install\n\t$(INSTALL_DIR) $(1)/etc/init.d\n\t$(INSTALL_BIN) ./files/natflow-boot.init $(1)/etc/init.d/natflow-boot\nendef\n\n$(eval $(call KernelPackage,natflow))\n$(eval $(call BuildPackage,natflow-boot))\n"
  },
  {
    "path": "packages/net/natflow/files/natflow-boot.init",
    "content": "#!/bin/sh /etc/rc.common\n# Copyright (C) 2006-2011 OpenWrt.org\n\nSTART=95\n\nstart() {\n\techo debug=3 >/dev/natflow_ctl\n\techo disabled=0 >/dev/natflow_ctl\n}\n\nstop() {\n\techo disabled=1 >/dev/natflow_ctl\n}\n"
  },
  {
    "path": "packages/net/ndpi-netfilter/LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "packages/net/ndpi-netfilter/Makefile",
    "content": "#\n# Based on package from https://github.com/Ysurac/openmptcprouter-feeds/blob/master/ndpi-netfilter2\n# Copyright (C) 2018-2022 Ycarus (Yannick Chabanois) <ycarus@zugaina.org> for OpenMPTCProuter\n# (C) 2023 modified by Konstantine Shevlakov <shevlakov@132lan.ru> OpenWrt modemfeed project\n#\n# This is free software, licensed under the GNU General Public License v2.\n# See /LICENSE for more information.\n#\n\ninclude $(TOPDIR)/rules.mk\ninclude $(INCLUDE_DIR)/kernel.mk\n\nPKG_NAME:=ndpi-netfilter\nPKG_REV:=4c1a23b0539ec15296029b149c53f5946d8b7280\nPKG_REV_SHORT=$(shell echo $(PKG_REV) | cut -c1-6)\nPKG_VERSION:=2026.03.30-flow_info-4~$(PKG_REV_SHORT)\nPKG_RELEASE:=3\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.bz2\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)\nPKG_SOURCE_URL:=https://github.com/vel21ripn/nDPI.git\nPKG_SOURCE_VERSION:=$(PKG_REV)\n\nPKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)\nPKG_BUILD_DEPENDS:=libpcap\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/iptables-mod-ndpi\n  SUBMENU:=Firewall\n  SECTION:=net\n  CATEGORY:=Network\n  TITLE:=ndpi successor of nDPI\n  URL:=http://www.ntop.org/products/ndpi/\n  DEPENDS:=+iptables +iptables-mod-conntrack-extra +kmod-ipt-ndpi +libpcap\n  MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\nendef\n\ndefine Package/iptables-mod-ndpi/description\n\tnDPI is a ntop-maintained superset of the popular OpenDPI library\nendef\n\nMAKE_PATH:=ndpi-netfilter\n\nINCLUDE_SRC += -I$(PKG_BUILD_DIR)/src/include/ -I$(PKG_BUILD_DIR)/src/lib/third_party/include/\nCFLAGS += -g -O2 -fPIC -DPIC -DNDPI_LIB_COMPILATION\n\nMAKE_FLAGS += \\\n\tKERNEL_DIR=\"$(LINUX_DIR)\" \\\n\tMODULES_DIR=\"$(TARGET_MODULES_DIR)\" \\\n\tARCH=\"$(LINUX_KARCH)\" \\\n\tKERNEL_RELEASE=\"$(KERNEL_PATCHVER)\" \\\n\tCROSS_COMPILE=\"$(TARGET_CROSS)\" \\\n\tCC=\"$(TARGET_CC)\" \\\n\tEXTRA_CFLAGS=\"-D__KERNEL__ -DMODULE $(INCLUDE_SRC) -Wno-error -Wno-unused-function -Wno-implicit-fallthrough -Wno-implicit-function-declaration\"\n\ndefine Build/Configure\n\t(cd $(PKG_BUILD_DIR); \\\n\t\t./autogen.sh; \\\n\t\t./configure --with-pic --host=$(GNU_TARGET_NAME) \\\n\t\tCFLAGS=\"$(TARGET_CFLAGS) -fPIC -DPIC\" \\\n\t\tCPPFLAGS=\"-I$(STAGING_DIR)/usr/include\" \\\n\t\tLDFLAGS=\"-L$(STAGING_DIR)/usr/lib\" \\\n\t\tLIBS=\"-lpcap\")\nendef\n\ndefine Build/Compile\n\t$(MAKE) $(MAKE_FLAGS) -C $(PKG_BUILD_DIR)/ndpi-netfilter\nendef\n\ndefine Package/iptables-mod-ndpi/install\n\t$(INSTALL_DIR) $(1)/usr/lib/iptables\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/ndpi-netfilter/ipt/libxt_ndpi.so $(1)/usr/lib/iptables\nendef\n\ndefine KernelPackage/ipt-ndpi\n  SUBMENU:=Netfilter Extensions\n  TITLE:= nDPI net netfilter module\n  DEPENDS:=+kmod-nf-conntrack +kmod-nf-conntrack-netlink +kmod-ipt-compat-xtables +libpcap\n  FILES:= $(PKG_BUILD_DIR)/ndpi-netfilter/src/xt_ndpi.ko\n  KCONFIG:=\\\n\tCONFIG_NF_CONNTRACK=y \\\n\tCONFIG_NF_CONNTRACK_LABELS=y \\\n\tCONFIG_NETFILTER_XT_MATCH_CONNLABEL=y\n  AUTOLOAD:=$(call AutoProbe,xt_ndpi)\nendef\n\n$(eval $(call BuildPackage,iptables-mod-ndpi))\n$(eval $(call KernelPackage,ipt-ndpi))\n"
  },
  {
    "path": "packages/net/ndpi-netfilter/README.md",
    "content": "# ndpi-netfilter\nopenwrt ndpi-netfilter2 packet\n"
  },
  {
    "path": "packages/net/ndpi-netfilter/patches/0001-outline-atomics-skbuff-check.patch",
    "content": "--- a/ndpi-netfilter/src/Makefile\n+++ b/ndpi-netfilter/src/Makefile\n@@ -4,15 +4,21 @@ NDPI_PRO := ${NDPI_SRC}/lib/protocols\n ccflags-y += -I${src}/${NDPI_SRC}/include -I${src}/${NDPI_SRC}/lib -I${src}/../libre -I${src}/${NDPI_SRC}/lib/third_party/include \n ccflags-y += -DHAVE_CONFIG_H -DNDPI_LIB_COMPILATION -DOPENDPI_NETFILTER_MODULE -DNDPI_DETECTION_SUPPORT_IPV6\n #ccflags-y += -DUSE_GLOBAL_CONTEXT\n-ccflags-y += -g -Wno-declaration-after-statement\n+ccflags-y += -g -Wno-declaration-after-statement -Werror=frame-larger-than=4096\n # Needed for pahole\n ccflags-y += -femit-struct-debug-detailed=any\n \n+ifeq ($(KERNEL_RELEASE),5.15)\n+ifeq ($(ARCH),arm64)\n+\tccflags-y += -mno-outline-atomics\n+endif\n+endif\n+\n ifndef $(KERNEL_DIR)\n KERNEL_DIR := /lib/modules/$(shell uname -r)/build\n endif\n \n-ifeq ($(shell test -f $(KERNEL_DIR)/source/include/linux/skbuff.h && grep -c userid $(KERNEL_DIR)/source/include/linux/skbuff.h),1)\n+ifeq ($(shell test -f $(KERNEL_DIR)/include/linux/skbuff.h && grep -qc userid $(KERNEL_DIR)/include/linux/skbuff.h),1)\n ccflags-y += -DUSE_HACK_USERID=1\n endif\n \n"
  },
  {
    "path": "packages/net/ndpi-netfilter/patches/0002-fix-build-murmurhash.patch",
    "content": "--- a/src/lib/third_party/src/hll/MurmurHash3.c\n+++ b/src/lib/third_party/src/hll/MurmurHash3.c\n@@ -49,16 +49,19 @@ u_int32_t MurmurHash(const void *key, u_\n     {\n     case 3:\n       k1 ^= (u_int32_t)tail[2] << 16;\n-      __fallthrough\n+      /* fallthrough */\n+      break;\n     case 2:\n       k1 ^= (u_int32_t)tail[1] << 8;\n-      __fallthrough\n+      /* fallthrough */\n+      break;\n     case 1:\n       k1 ^= tail[0];\n       k1 *= c1;\n       k1 = ROTL32(k1, 15);\n       k1 *= c2;\n       h1 ^= k1;\n+      break;\n     };\n \n   h1 ^= len;\n"
  },
  {
    "path": "packages/net/ndpi-netfilter/patches/0003-fix-xchg.patch",
    "content": "--- a/ndpi-netfilter/src/main.c\n+++ b/ndpi-netfilter/src/main.c\n@@ -80,6 +80,16 @@ extern ndpi_protocol_match host_match[];\n /* Only for debug! */\n //#define NDPI_IPPORT_DEBUG\n \n+/* Workaround for old arch: redefine xchg to simple assignment */\n+#if defined(CONFIG_MIPS) || defined(CONFIG_CACHE_FEROCEON_L2)\n+#undef xchg\n+#define xchg(ptr, x) ({\\\n+    typeof(*(ptr)) __old = *(ptr);\\\n+    *(ptr) = (x);\\\n+    __old;\\\n+})\n+#endif\n+\n \n #define COUNTER(a) atomic_inc((atomic_t *)&a)\n #define COUNTER_D(a) atomic_dec((atomic_t *)&a)\n"
  },
  {
    "path": "packages/net/ookla-speedtest/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=ookla-speedtest\nPKG_VERSION:=1.2.0\nPKG_RELEASE:=1\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  TITLE:=Speedtest from Ookla\n  SECTION:=net\n  CATEGORY:=Network\nendef\n\nifeq ($(ARCH),x86_64)\n  BIN_PATH:=./files/x86_64\nendif\nifeq ($(ARCH),i386)\n  BIN_PATH:=./files/i386\nendif\nifeq ($(ARCH),arm)\n  BIN_PATH:=./files/armhf\nendif\nifeq ($(ARCH),aarch64)\n  BIN_PATH:=./files/aarch64\nendif\n\ndefine Package/$(PKG_NAME)/description\n\tSpeedtest CLI brings the trusted technology and global server network behind Speedtest to the command line.\n\tBuilt for software developers, system administrators and computer enthusiasts alike,\n\tSpeedtest CLI is the first official Linux-native Speedtest application backed by Ookla®.\nendef\n\ndefine Build/Prepare\nendef\n\ndefine Build/Configure\nendef\n\ndefine Build/Compile\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(CP) $(BIN_PATH)/speedtest $(1)/usr/bin\nendef\n\nifeq ($(ARCH),$(filter $(ARCH), x86_64 i386 arm aarch64))\n  $(eval $(call BuildPackage,$(PKG_NAME))) \nendif\n"
  },
  {
    "path": "packages/net/openvpn-dns-hotplug/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=openvpn-dns-hotplug\nPKG_VERSION:=0.0.1\nPKG_RELEASE:=3\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  PKGARCH:=all\n  TITLE:=Openvpn DNS hotplug\n  DEPENDS:=+openvpn\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tHotplug script to get NS attribytes: DNS and domain search.\nendef\n\ndefine Build/Prepare\nendef\n\ndefine Build/Configure\nendef\n\ndefine Build/Compile\nendef\n\n\ndefine Package/$(PKG_NAME)/install\n\t$(CP) ./root/* $(1)/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n\n"
  },
  {
    "path": "packages/net/openvpn-dns-hotplug/root/etc/hotplug.d/openvpn/90-dns",
    "content": "#!/bin/sh\n# Get DNS, Domain Search via OpenVPN client hotplug script\n# Written by Konstantine Shevlakov at <shevlakov@132lan.ru> 2022\n# OpenWrt 21 file location: /etc/hotplug.d/openvpn\n\n\n# Get NameServers and Domain search\nget_ns(){\n\tDNS=$(env |  awk '/dhcp-option DNS/{gsub(\"'\\''\", \"\");print $NF}')\n\tDOMAIN=$(env |  awk '/dhcp-option DOMAIN/{gsub(\"'\\''\", \"\");print $NF}')\n}\n\n# Setting up NameServers and Domain\nset_ns(){\n\tif [ \"x$CFGIFACE\" != \"x\" ]; then\n\t\tfor ns in $DNS; do\n\t\t\tif [ \"x$ns\" != \"x\" ]; then\n\t\t\t\tuci -q add_list network.$CFGIFACE.dns=$ns\n\t\t\tfi\n\t\tdone\n\t\tfor dn in $DOMAIN; do\n\t\t\tif [ \"x$dn\" != \"x\" ]; then\n\t\t\t\tuci -q add_list network.$CFGIFACE.dns_search=$dn\n\t\t\tfi\n\t\tdone\n\tfi\n\tuci -q commit network\n}\n\n# Delete NameServers and Domain\nunset_ns(){\n\tif [ \"x$CFGIFACE\" != \"x\" ]; then\n\t\tuci -q delete network.$CFGIFACE.dns\n\t\tuci -q delete network.$CFGIFACE.dns_search\n\tfi\n\tuci -q commit network\n}\n\n\n# Get interface name in uci \nUCI_conf=$(uci show openvpn | awk -F [\\/=] '/config/{gsub(\"'\\''\", \"\");print $NF}')\nfor cfg in $UCI_conf; do\n\tif [ $config = $cfg ]; then\n\t\tCFGIFACE=$(uci show network | awk -F [.] -v device=$dev '$0 ~ device {print $2}')\n\t\tPROTO=$(ifstatus $CFGIFACE | jsonfilter -e '@[\"proto\"]')\n\t\tget_ns\n\tfi\ndone\n\n# Hotplug action\ncase $PROTO in \n\tnone)\n\t\t# Check peerdns option in uci interface section\n\t\tPEERDNS=$(uci -q get network.$CFGIFACE.dns)\n\t\tcase $ACTION in\n\t\t\tup)\n\t\t\t\tif [ \"x${PEERDNS}\" != \"x\" ]; then\n\t\t\t\t\texit 0\n\t\t\t\tfi\n\t\t\t\tif  [ \"$DNS\" ]; then\n\t\t\t\t\tunset_ns\n\t\t\t\t\tset_ns\n\t\t\t\t\treload_config\n\t\t\t\t\tlogger -t \"`basename $0`\" \"OpenVPN: adding DNS: $DNS from $INSTANCE\" \n\t\t\t\tfi\n\t\t\t;;\n\t\t\tdown)\n\t\t\t\tunset_ns\n\t\t\t\treload_config\n\t\t\t\tlogger -t \"`basename $0`\" \"OpenVPN: remove NS attributes from $INSTANCE\"\n\t\t\t;;\n\t\tesac\n\t;;\nesac\n"
  },
  {
    "path": "packages/net/pingcontrol/Makefile",
    "content": ":\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=pingcontrol\nPKG_VERSION:=1\nPKG_RELEASE:=4\nPKG_BUILD_DIR:=$(BUILD_DIR)/pingcontrol\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/pingcontrol\n  SECTION:=utils\n  CATEGORY:=Utilities\n  TITLE:=Network interface watchdog\n  PKGARCH:=all\nendef\n\ndefine Package/pingcontrol/description\n\t This package contains a utility for managing the interface using ping requests.\nendef\n\ndefine Build/Prepare\nendef\n\ndefine Build/Configure\nendef\n\ndefine Build/Compile\nendef\n\ndefine Package/pingcontrol/install\n\t$(INSTALL_DIR) $(1)/bin\n\t$(INSTALL_BIN) ./files/pingcontrol $(1)/bin/\n\t$(INSTALL_DIR) $(1)/etc/config\n\t$(CP) ./files/pingcontrol.config $(1)/etc/config/pingcontrol\n\t$(INSTALL_DIR) $(1)/etc/init.d\n\t$(INSTALL_BIN) ./files/pingcontrol.init $(1)/etc/init.d/pingcontrol\nendef\n\n$(eval $(call BuildPackage,pingcontrol))\n"
  },
  {
    "path": "packages/net/pingcontrol/files/pingcontrol",
    "content": "#!/bin/sh\n\n. /usr/share/libubox/jshn.sh\n\nINSTANCE=$1\nIFACE=$(uci -q get pingcontrol.$1.iface)\nCHECK_PERIOD=$(uci -q get pingcontrol.$1.check_period)\nTESTIP=$(uci -q get pingcontrol.$1.testip)\nSW_BEFORE_MODRES=$(uci -q get pingcontrol.$1.sw_before_modres)\nSW_BEFORE_SYSRES=$(uci -q get pingcontrol.$1.sw_before_sysres)\n\nPING_OK=$(uci -q get pingcontrol.$1.ping_ok)\nSILENT=$(uci -q get pingcontrol.$1.ping_silent)\nPING_LOST=$(uci -q get pingcontrol.$1.ping_lost)\nPING_RESTORED=$(uci -q get pingcontrol.$1.ping_restored)\nBEFORE_IFACE_DOWN=$(uci -q get pingcontrol.$1.before_iface_down)\nAFTER_IFACE_UP=$(uci -q get pingcontrol.$1.after_iface_up)\nBEFORE_REBOOT=$(uci -q get pingcontrol.$1.before_reboot)\n\nhost_up_count=0\nlost1=0\nlost2=0\nlost_flag=0\nIP_COUNT=$(echo $TESTIP | wc -w)\nlet SW_BEFORE_MODRES=SW_BEFORE_MODRES*IP_COUNT\nlet SW_BEFORE_SYSRES=SW_BEFORE_SYSRES*IP_COUNT\n\nubus call network.interface.$IFACE up\nwhile [[ -z \"$IFNAME\" ]]; do\n\tjson_load \"$(ubus call network.interface.$IFACE status)\"\n\tjson_get_var IFNAME l3_device\n\tsleep $CHECK_PERIOD\ndone\n\nwhile true; do\n\tfor IP in $TESTIP\n\tdo\n\t\t[ -n $IFNAME ] && {\n\t\t\t/bin/ping -w5 -c1 -s8 -I $IFNAME $IP &> /dev/null\n\t\t\tSTATUS=$?\n\t\t} || {\n\t\t\t/bin/ping -w5 -c1 -s8 $IP &> /dev/null\n\t\t\tSTATUS=$?\n\t\t}\n\n\t\tif [[ \"${STATUS}\" -eq \"0\" ]]; then\n\t\t\tlet host_up_count++\n\t\telse\n\t\t\tlogger -t pingcontrol \"$INSTANCE: Ping to host $IP lost\"\n\t\t\tif [[ -n \"$PING_LOST\" ]]; then\n\t\t\t\tlogger -t pingcontrol \"$INSTANCE: Execute $PING_LOST\"\n\t\t\t\teval $PING_LOST\n\t\t\tfi\n\t\t\tlet lost1++\n\t\t\tlet lost2++\n\t\t\tlost_flag=1\n\t\tfi\n\tdone\n\n\tif [[ \"$host_up_count\" -gt \"0\" ]]; then\n\t\t[ \"${SILENT}\" != \"1\" ] && {\n\t\t\tlogger -t pingcontrol \"$INSTANCE: Ping OK, reset counters\"\n\t\t}\n\t\tif [[ \"$lost_flag\" -eq \"1\" ]]; then\n\t\t\tif [[ -n \"$PING_RESTORED\" ]]; then\n\t\t\t\tlogger -t pingcontrol \"$INSTANCE: Execute $PING_RESTORED\"\n\t\t\t\teval $PING_RESTORED\n\t\t\tfi\n\t\telse\n\t\t\tif [[ -n \"$PING_OK\" ]]; then\n\t\t\t\t[ \"${SILENT}\" != \"1\" ] && {\n\t\t\t\t\tlogger -t pingcontrol \"$INSTANCE: Execute $PING_OK\"\n\t\t\t\t}\n\t\t\t\t\teval $PING_OK\n\t\t\tfi\n\t\tfi\n\t\thost_up_count=0\n\t\tlost1=0\n\t\tlost2=0\n\t\tlost_flag=0\n\tfi\n\n\tif [[ \"$lost1\" -gt \"0\" ]]; then\n\t\tif [[ \"$SW_BEFORE_MODRES\" -ne \"0\" ]]; then\n\t\t\tif [[ \"$lost1\" -ge \"$SW_BEFORE_MODRES\" ]]; then\n\t\t\t\tif [[ -n \"$BEFORE_IFACE_DOWN\" ]]; then\n\t\t\t\t\tlogger -t pingcontrol \"$INSTANCE: Execute $BEFORE_IFACE_DOWN\"\n\t\t\t\t\teval $BEFORE_IFACE_DOWN\n\t\t\t\tfi\n\t\t\t\tlogger -t pingcontrol \"$INSTANCE: Ifup/ifdown $IFACE\"\n\t\t\t\tubus call network.interface.$IFACE down\n\t\t\t\tsleep 1\n\t\t\t\tubus call network.interface.$IFACE up\n\t\t\t\tif [[ -n \"$AFTER_IFACE_UP\" ]]; then\n\t\t\t\t\tlogger -t pingcontrol \"$INSTANCE: Execute $AFTER_IFACE_UP\"\n\t\t\t\t\teval $AFTER_IFACE_UP\n\t\t\t\tfi\n\t\t\t\tlost1=0\n\t\t\tfi\n\t\tfi\n\t\tif [[ \"$SW_BEFORE_SYSRES\" -ne \"0\" ]]; then\n\t\t\tif [[ \"$lost2\" -ge \"$SW_BEFORE_SYSRES\" ]]; then\n\t\t\t\tif [[ -n \"$BEFORE_REBOOT\" ]]; then\n\t\t\t\t\tlogger -t pingcontrol \"$INSTANCE: Execute $BEFORE_REBOOT\"\n\t\t\t\t\teval $BEFORE_REBOOT\n\t\t\t\tfi\n\t\t\t\tlogger -t pingcontrol \"$INSTANCE: Reboot now!\"\n\t\t\t\treboot\n\t\t\tfi\n\t\tfi\n\tfi\n\n\tsleep $CHECK_PERIOD\ndone\n"
  },
  {
    "path": "packages/net/pingcontrol/files/pingcontrol.config",
    "content": "config pingcontrol 'pingcontrol'\n        option enabled '0'\n        option check_period '30'\n        option iface 'internet'\n        option testip '8.8.8.8'\n\toption ping_silent '1'\n        option sw_before_modres '3'\n        option sw_before_sysres '0'\n"
  },
  {
    "path": "packages/net/pingcontrol/files/pingcontrol.init",
    "content": "#!/bin/sh /etc/rc.common\n\nSTART=80\nUSE_PROCD=1\nPROG=/bin/pingcontrol\n\nvalidate_pingcontrol_section() {\n        uci_validate_section pingcontrol pingcontrol \"${1}\" \\\n                'enabled:bool:0' \\\n                'iface:string' \\\n                'check_period:uinteger' \\\n                'testip:list(host)' \\\n                'sw_before_modres:uinteger' \\\n                'sw_before_sysres:uinteger'\n}\n\npingcontrol_instance() {\n        local enabled iface check_period testip sw_before_modres sw_before_sysres\n\n        validate_pingcontrol_section \"$1\" || {\n                echo \"validation failed\"\n                return 1\n        }\n\n        [ \"$enabled\" = 0 ] && return\n\n        procd_open_instance\n        procd_set_param command \"$PROG\" \"$1\"\n\n        procd_set_param stdout 1\n        procd_set_param stderr 1\n        procd_set_param respawn\n        procd_close_instance\n}\n\nstart_service() {\n        config_load 'pingcontrol'\n        config_foreach pingcontrol_instance 'pingcontrol'\n}\n\nservice_triggers() {\n        procd_add_reload_trigger 'pingcontrol'\n        procd_add_validation validate_pingcontrol_section\n}\n\nreload_service() {\n        stop\n        start\n}\n"
  },
  {
    "path": "packages/net/rrm-nr-distributor/LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {description}\n    Copyright (C) {year}  {fullname}\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  {signature of Ty Coon}, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n\n"
  },
  {
    "path": "packages/net/rrm-nr-distributor/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=rrm-nr-distributor\nPKG_VERSION:=0.0.1\nPKG_RELEASE:=1\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  PKGARCH:=all\n  DEPENDS:=+umdns\n  TITLE:=802.11k Neighbor Report distributor daemon\nendef\n\ndefine Package/$(PKG_NAME)/description\n\t802.11k Neighbor Report distributor daemon\nendef\n\ndefine Build/Prepare\nendef\n\ndefine Build/Configure\nendef\n\ndefine Build/Compile\nendef\n\n\ndefine Package/$(PKG_NAME)/install\n\t$(CP) ./root/* $(1)/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/net/rrm-nr-distributor/README.md",
    "content": "802.11k Neighbor Report distributor daemon\n==========================================\n\nOriginal source: https://github.com/simonyiszk/openwrt-rrm-nr-distributor\n\n## Features\n\n- Multi-network support (different SSID for different networks)\n- STA dependent band steering by advertising the other BSSes of the same AP\n- Works out of the box after umdns is working\n- Not too much but enough logs\n\n## Installation\n\n- Configure all your OpenWRT devices for the same SSID for each layer 2 network\n- Install umdns and configure it (Pay attention to config the interface in the config file, setup your firewall, seccomp workaround etc. Finally ubus call umdns browse should show your other devices' dropbear)\n- Copy initscript to /etc/init.d/rrm_nr\n- Copy bin to /usr/bin/rrm_nr\n- Run /etc/init.d/rrm_nr enable and /etc/init.d/rrm_nr start commands\n- Check the syslog for the results\n\n## Known issues\n\n- SSIDs with '|' character are not supported at the moment\n- With large number of APs (>20) the full umdns update takes a few interations/minutes\n"
  },
  {
    "path": "packages/net/rrm-nr-distributor/example_wireless_config",
    "content": "\nconfig wifi-device 'radio0'\n        option type 'mac80211'\n        option hwmode '11a'\n        option path 'pci0000:00/0000:00:00.0'\n        option htmode 'VHT20'\n        option txpower '18'\n        option cell_density '0'\n        option beacon_int '50'\n        option distance '30'\n        option country 'HU'\n        option channel '60'\n        option log_level '1'\n\nconfig wifi-iface 'default_radio0'\n        option device 'radio0'\n        option mode 'ap'\n        option key 'intranetintranetintranet'\n        option ssid 'intranet'\n        option encryption 'psk2'\n        option ft_psk_generate_local '1'\n        option mobility_domain '4f4f'\n        option country_code 'HU'\n        option country3 '0x49'\n        option time_advertisement '2'\n        option ieee80211d '1'\n        option reassociation_deadline '1000'\n        option ieee80211r '1'\n        option network 'intranet'\n        option ieee80211k '1'\n        option ieee80211v '1'\n        option ft_over_ds '0'\n        option na_mcast_to_ucast '1'\n        option bss_transition '0'\n        option nasid 'ap-10.intranet'\n\nconfig wifi-device 'radio1'\n        option type 'mac80211'\n        option hwmode '11g'\n        option path 'platform/ahb/18100000.wmac'\n        option htmode 'HT20'\n        option txpower '16'\n        option cell_density '0'\n        option distance '30'\n        option beacon_int '50'\n        option country 'HU'\n        option channel '1'\n        option log_level '1'\n\nconfig wifi-iface 'default_radio1'\n        option device 'radio1'\n        option mode 'ap'\n        option key 'intranetintranetintranet'\n        option ssid 'intranet'\n        option encryption 'psk2'\n        option ft_psk_generate_local '1'\n        option mobility_domain '4f4f'\n        option country_code 'HU'\n        option country3 '0x49'\n        option time_advertisement '2'\n        option reassociation_deadline '1000'\n        option ieee80211r '1'\n        option network 'intranet'\n        option ieee80211k '1'\n        option ieee80211v '1'\n        option bss_transition '0'\n        option na_mcast_to_ucast '1'\n        option ft_over_ds '0'\n        option nasid 'ap-10.intranet'\n\nconfig wifi-iface 'wifinet3'\n        option device 'radio1'\n        option mode 'ap'\n        option ssid 'guest'\n        option encryption 'psk2'\n        option key 'Randompassword2'\n        option network 'guest'\n        option ft_psk_generate_local '1'\n        option country_code 'HU'\n        option country3 '0x49'\n        option time_advertisement '2'\n        option reassociation_deadline '1000'\n        option ieee80211r '1'\n        option ieee80211k '1'\n        option ieee80211v '1'\n        option bss_transition '0'\n        option na_mcast_to_ucast '1'\n        option ft_over_ds '0'\n        option nasid 'ap-10.guestnet'\n        option mobility_domain 'abab'\n\n\n"
  },
  {
    "path": "packages/net/rrm-nr-distributor/root/etc/init.d/rrm_nr",
    "content": "#!/bin/sh /etc/rc.common\n#file: /etc/init.d/rrm_nr\n## Based on: https://forum.openwrt.org/t/how-does-rrm-work/32635/68\n\nSTART=99\n\nNAME=rrm_nr\nUSE_PROCD=1\n\nstart_service() {\n\t#[ ! -f /etc/config/wireless ] && logger -t \"${NAME}\" -pdaemon.error \"/etc/config/wireless does not exist\" && exit 1\n\t#[ $(grep -q 'wifi-iface' /etc/config/wireless) ] && logger -t \"${NAME}\" -pdaemon.error \"/etc/config/wireless does not have any wifi-iface stanzas\" && exit 1\n\n\tlocal rrm_own\n\t#todo skip if nr is disabled for interface, skip disabled interface etc.\n\t#while [ \"$(ubus list hostapd.wlan* | wc -l)\" != \"$(grep 'wifi-iface' /etc/config/wireless | wc -l)\" ]; do\n\t#\tlogger -t \"${NAME}\" -pdaemon.info \"Waiting for all interfaces to initialize\"\n\t#\tsleep 30\n\t#done\n\n\tOIFS=$IFS\n\tIFS=$'\\x0a'\n\n\tfor value in $(ubus list hostapd.wlan*); do\n\t\trrm_own=\"${rrm_own}|$(/bin/ubus call ${value} rrm_nr_get_own | /usr/bin/jsonfilter -e '$.value')\"\n\tdone\n\n\tIFS=$OIFS\n\n\tprocd_open_instance\n\tprocd_set_param command /bin/sh \"/usr/bin/rrm_nr\"\n\t#https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=5247\n\tprocd_add_mdns \"rrm_nr\" \"udp\" \"5247\" \"${rrm_own:1}\" \n\tprocd_close_instance\n}\n\nboot(){\n\tsleep 30\n\tstart\n}\n\nservice_triggers()\n{\n\tprocd_add_reload_trigger wireless\n}\n"
  },
  {
    "path": "packages/net/rrm-nr-distributor/root/usr/bin/rrm_nr",
    "content": "#!/bin/sh\n#file: /usr/bin/rrm_nr\n#based on: https://forum.openwrt.org/t/how-does-rrm-work/32635/73\n\nNAME=rrm_nr\n\n# We wrap everything into this function to allow GC by ash, esp. for low-mem devices\nfunction _do_updates() {\n\tlocal rrm_nr_lists\n\n\tOIFS=$IFS\n\tIFS=$'\\n'\n\t# Discover neighbors and self\n\tubus call umdns update\n\tsleep 5\n\tfor wifi_iface in $(ubus list hostapd.wlan* | awk -F. '{ print $2; }'); do\n\t\tnet_ssid=$(iwinfo ${wifi_iface} info | head -n1 | cut -d\\\" -f2)\n\t\t[ -z \"${net_ssid}\" ] && logger -t \"rrm_nr\" -p daemon.error \"${wifi_iface}: does not have ssid according to iwinfo.\" && continue\n\t\t\n\t\t# Discover other nodes\n\t\trrm_nr_lists=\"\"\n\n\t\tfor other_iface in $(ubus list hostapd.wlan* | awk -F. '{ print $2; }'); do\n\t\t\t[ ${wifi_iface} = ${other_iface} ] && continue\n\n\t\t\t[ ${net_ssid} = $(iwinfo ${other_iface} info | head -n1 | cut -d\\\" -f2) ] && rrm_nr_lists=\"${rrm_nr_lists}\"$'\\n'\"$(/bin/ubus call hostapd.${other_iface} rrm_nr_get_own | /usr/bin/jsonfilter -e '$.value')\"\n\t\tdone\n\n\n\t\t# Sort at the end stabilizes the result, so we can compare it across runs\n\t\tfor discovered_node in $(ubus call umdns browse | jsonfilter -e '@[\"_rrm_nr._udp\"][*].txt' | tr '|' '\\n' | grep \"\\\"${net_ssid}\\\"\"); do #TODO replace tr with sed \"s= ]|[ = ]\\n[ =\"\n\t\t\trrm_nr_lists=\"${rrm_nr_lists}\"$'\\n'\"${discovered_node}\"\n\t\tdone\n\n\t\tnr_len=$(echo \"${rrm_nr_lists:1}\" | wc -l)\n\t\trrm_nr_lists=$(echo -n \"${rrm_nr_lists:1}\" | sort -u | tr '\\n' ',' )\n\t\trrm_nr_lists=\"[${rrm_nr_lists::-1}]\"\n\n\t\t#[[ \"${rrm_nr_lists}\" == \",\" ]] && logger -t \"rrm_nr\" -p daemon.error \"${wifi_iface}: no neighbors detected, nothing to do\" && continue TODO is logging necessary?\n\t\t#[[ \"${rrm_nr_lists}\" == \",\" ]] && continue\n\n\t\t#ubus call hostapd.${wifi_iface} bss_mgmt_enable '{\"neighbor_report\": true}' neighbor_report shoud be enabled in uci TODO skip interface if not set\n\t\tprev_rrm_list=\"[$(ubus call hostapd.${wifi_iface} rrm_nr_list | jsonfilter -e '@.list[@]' | sort -u | sed ':a;N;$!ba;s/\\n/,/g')]\"\n\t\tif [ \"$(echo -n ${prev_rrm_list} | sed 's: ::g')\" = \"$(echo -n ${rrm_nr_lists} | sed 's: ::g')\" ]; then\n\t\t\t# Setting a new list will cause the wifi to quickly cycle, which we do not want every 60s\n\t\t\tcontinue\n\t\tfi\n\t\tlogger -t \"rrm_nr\" -p daemon.info \"${wifi_iface}: Updating neighbor list (number of neighbors: ${nr_len}): ${rrm_nr_lists}\"\n\t\tubus call hostapd.${wifi_iface} rrm_nr_set \"{ \\\"list\\\": ${rrm_nr_lists} }\"\n\tdone\n\n\tIFS=$OIFS\n}\n\nwhile true; do\n\t_do_updates\n\n\tsleep 60\ndone\n\nexit 0\n"
  },
  {
    "path": "packages/net/simpleproxy/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=simpleproxy\nPKG_VERSION:=3.5\n\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\nPKG_LICENSE:=GPLv2\nPKG_LICENSE_FILES:=LICENSE\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL:=https://github.com/vzaliva/simpleproxy.git\nPKG_SOURCE_VERSION:=09def95891f8d62b96d6c04de1c688e1af7f2325\n\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  SUBMENU:=Web Servers/Proxies\n  SECTION:=net\n  CATEGORY:=Network\n  TITLE:=simpleproxy OpenWRT package\n  URL:=https://github.com/vzaliva/simpleproxy\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tSimpleproxy is a simple TCP proxy. It accepts connections on a local\n\tTCP port and forward them to anoter port on a remote host via TCP.\nendef\n\ndefine Package/$(PKG_NAME)/prepare\n\t./configure --prefix=/usr\nendef\n\ndefine Package/$(PKG_NAME)/prepare\n\t$(MAKE)\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_DIR) $(1)/etc\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/simpleproxy $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/sample.cfg $(1)/etc/simpleproxy.cfg\nendef\n\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n\n\n"
  },
  {
    "path": "packages/net/ssw/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=ssw\nPKG_VERSION:=0.0.2\nPKG_RELEASE:=3\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  PKGARCH:=all\n  DEPENDS:=+modemmanager +mwan3\n  TITLE:=SIM-card switcher and tracker\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tSIM-card tracker for HuastLink HC-G60 and ZBTLint ZBT-WE2806-A\n\tor similar CPE-routers with two SIM slots and one 4g/5G modem\nendef\n\n\ndefine Build/Prepare\nendef\n\ndefine Build/Configure\nendef\n\ndefine Build/Compile\nendef\n\n\ndefine Package/$(PKG_NAME)/install\n\t$(CP) ./root/* $(1)/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/net/ssw/root/etc/config/ssw",
    "content": "\nconfig modem 'modem'\n\toption gpio 'modem'\n\toption value '1'\n\nconfig sim 'sim'\n\toption gpio 'sim'\n\toption value '1'\n\nconfig failover 'failover'\n\n"
  },
  {
    "path": "packages/net/ssw/root/etc/init.d/ssw",
    "content": "#!/bin/sh /etc/rc.common\n\n# SIM slot switcher for HuastLink HC-G60 or ZBT-WE2806-A\n# by Konstantine Shevlakov at <shevlakov@132lan.ru> 2025\n\nSTART=99\nSTOP=10\nUSE_PROCD=1\n\nload_config_gpio(){\n\tlocal gpio value\n\tfor c in gpio value; do\n\t\tconfig_get $1_$c \"$1\" $c\n\t\texport $1_$c\n\tdone\n}\n\nload_config_failover(){\n\tlocal enable mode interval times_rsrp times_ping revert rsrp host\n\tfor c in enable mode interval times_rsrp times_ping revert rsrp host; do\n\t\tconfig_get $1_$c \"$1\" $c\n\t\texport $1_$c \n\tdone\n}\n\nreload_iface(){\n\t[ \"$iface\" ] &&  {\n\t\tfor i in $iface; do\n\t\t\tifup $i\n\t\tdone\n\t}\n}\n\nstart_service(){\n\tconfig_load ssw\n\tconfig_foreach load_config_gpio modem\n\tconfig_foreach load_config_gpio sim\n\tmodem_cur=$(cat /sys/class/gpio/$modem_gpio/value)\n\tsim_cur=$(cat /sys/class/gpio/$sim_gpio/value)\n\tiface=$(uci show network | awk -F [.] '/devices/{gsub(\"'\\''\",\"\");print $2}' | tail -1)\n\tif [ \"$modem_value\" = \"$modem_cur\" -a \"$sim_value\" = \"$sim_cur\" ]; then\n\t\tcontinue\n\telif [ \"$modem_value\" = \"$modem_cur\" -a \"$modem_value\" = \"1\" ]; then\n\t\tlogger -t \"SIM SW\" \"modem already enabled on gpio $modem_gpio. Restart it\"\n\t\techo \"0\" > /sys/class/gpio/$modem_gpio/value\n\t\tsleep 2\n\t\techo \"$sim_value\" > /sys/class/gpio/$sim_gpio/value\n\t\techo \"$modem_value\" > /sys/class/gpio/$modem_gpio/value\n\t\tif [ -x /etc/init.d/smstools3 ]; then\n\t\t\t/etc/init.d/smstools3 restart\n\t\tfi\n\t\tsleep 30 && reload_iface &\n\telse\n\t\tlogger -t \"SIM SW\" \"modem powered changed\"\n\t\techo \"$sim_value\" > /sys/class/gpio/$sim_gpio/value\n\t\techo \"$modem_value\" > /sys/class/gpio/$modem_gpio/value\n\t\tif [ -x /etc/init.d/smstools3 ]; then\n\t\t\t/etc/init.d/smstools3 restart\n\t\tfi\n\t\tsleep 30 && reload_iface &\n\tfi\n\t#config_foreach load_config_failover failover\n\tprocd_open_instance\n\tprocd_set_param command /usr/share/ssw_track.sh\n\tprocd_close_instance\n}\n\nreload_service(){\n        start\n}\n\nservice_triggers(){\n\tprocd_add_reload_trigger \"ssw\"\n}\n"
  },
  {
    "path": "packages/net/ssw/root/usr/share/ssw_track.sh",
    "content": "#!/bin/sh\n\n# SIM slot switcher for HuastLink HC-G60 or ZBT-WE2806-A\n# or similiar cpe OpenWrt-routers\n# by Konstantine Shevlakov at <shevlakov@132lan.ru> 2025\n\nNODE=\"SW SIM\"\n\n# config file stored in /etc/config/ssw\n# define gpios in /etc/config/system\n#\n# mandatory section names of gpio: modem and sim\n# example config file /etc/config/system for gpio_switch\n#\n# config gpio_switch 'modem'\n#        option name 'Modem pwr'\n#        option gpio_pin '512'\n#        option value '1'\n#\n# config gpio_switch 'sim'\n#        option name 'SIM switch'\n#        option gpio_pin 'sim'\n#        option value '1'\n#\n# if gpio not defined on dts and have integer number on gpioswitch then\n# option gpio must have value 'gpioN'. Example /etc/config/ssw:\n#\n# config modem 'modem'\n#        option gpio 'gpio512'\n#        option value '1'\n#\n# config sim 'sim'\n#        option gpio 'sim'\n#        option value '1'\n\n\n# Get Variables\nget_vars(){\n\tfor v in enable interval revert rsrp times_rsrp apn1 apn2; do\n\t\teval $v=$(uci -q get ssw.failover.${v} 2>/dev/nul)\n\tdone\n\tfor d in modem sim; do\n\t\tfor s in gpio value; do\n\t\t\teval \"${d}_${s}=$(uci -q get ssw.${d}.${s} 2>/dev/null)\"\n\t\tdone \n\tdone\n\t[ -n \"$interval\" ] || interval=60\n\t[ -n \"$times_rsrp\" ] || times_rsrp=5\n}\n\n# SIM Switch\nsw_sim(){\n\tcase $cur_sim in\n\t\t0) next_sim=1 ;;\n\t\t1) next_sim=0 ;;\n\tesac\n\n\techo \"0\" > /sys/class/gpio/$modem_gpio/value\n\techo \"$next_sim\" > /sys/class/gpio/$sim_gpio/value\n\n\tif [ \"$modem_value\" = \"1\" ]; then\n\t\tsleep 2\n\t\techo \"1\" > /sys/class/gpio/$modem_gpio/value\n\t\tif [ -x /etc/init.d/smstools3 ]; then\n\t\t\t/etc/init.d/smstools3 restart\n\t\tfi\n\tfi\n\tset > /tmp/apn.ssw\n}\n\n# Revert rule switch\nsw_rule(){\n\tif [ -f /tmp/ssw.vars ]; then\n\t\t. /tmp/ssw.vars\n\tfi\n\t# Revert SIM card to default slot\n\tif [ \"$cur_sim\" -ne \"$sim_value\" ]; then\n\t\tif [ \"$(date +%s)\" -gt \"$SWDATE\" ]; then\n\t\t\tlogger -t \"$NODE\" \"Revert to default SIM slot with $apn\"\n\t\t\tsw_sim\n\t\tfi\n\tfi\n}\n\n# Check interface state via mwan3\nmonitor_mwan3(){\n\tiface=$(uci show network | awk -F [.] '/devices/{print $2}')\n\t# Check link status\n\tif [ -r /tmp/run/mwan3/iface_state/$iface ]; then\n\t\tlink_status=$(cat /tmp/run/mwan3/iface_state/$iface | grep online | wc -l)\n\telse\n\t\t# Disable track link via mwan3\n\t\tlink_status=1\n\tfi\n}\n\n# RSRP average value by modemmanager. Enable singnal monitor!\nmonitor_rsrp(){\n\tdevice=\"$(uci show network | awk -F [=] '/devices/{gsub(\"'\\''\",\"\");print $2}')\"\n\tSIGNAL=\"$(mmcli -J -m $device --signal-get)\"\n\tCRSRP=$(echo \"$SIGNAL\" | jsonfilter -e '@[\"modem\"][*][\"lte\"][\"rsrp\"]' | awk '{printf \"%.0f\\n\", $1}')\n\tif ! [ $CRSRP ]; then\n\t\tCRSRP=$(echo \"$SIGNAL\" | jsonfilter -e '@[\"modem\"][*][\"5g\"][\"rsrp\"]' | awk '{printf \"%.0f\\n\", $1}')\n\tfi\n\n\tif [ $CRSRP -ne 0 ]; then\n\t\techo $CRSRP >> /tmp/ssw_rsrp.var\n\tfi\n\tif [ $cnt -eq $times_rsrp ]; then\n\t\tRSRP=$(awk '{sum+=$1} END { printf \"%.0f\\n\", sum/NR }' /tmp/ssw_rsrp.var)\n\t\tcat /dev/null > /tmp/ssw_rsrp.var\n\t\tif [ $RSRP -lt $rsrp ]; then\n\t\t\tmon_rsrp=0\n\t\telse\n\t\t\tmon_rsrp=1\n\t\tfi\n\tfi\n}\n\n# reload iface\nreload_iface(){\n\t[ \"$iface\" ] && {\n\t\tfor i in $iface; do\n\t\t\tifup $i\n\t\tdone\n\t}\n}\n\n# Stuff\ncnt=1\nwhile true; do\n\tget_vars\n\tsleep $interval\n\tif [ \"$enable\" = \"1\" ]; then\n\t\tcur_sim=$(cat /sys/class/gpio/$sim_gpio/value)\n\t\tmonitor_rsrp\n\t\tmonitor_mwan3\n\t\tif [ \"$cnt\" -eq \"$times_rsrp\" ]; then\n\t\t\tif [ \"$link_status\" = \"0\" -o \"$mon_rsrp\" = \"0\" ]; then\n\n\t\t\t\tif [ \"$sim_value\" -eq \"$cur_sim\" ]; then\n\t\t\t\t\tapn=$apn2\n\t\t\t\telse\n\t\t\t\t\tapn=$apn1\n\t\t\t\tfi\n\t\t\t\tiface=$(uci show network | awk -F [.] '/devices/{gsub(\"'\\''\",\"\");print $2}' | tail -1)\n\t\t\t\tif [ $RSRP ]; then\n\t\t\t\t\tif [ \"$mon_rsrp\" = \"0\" ]; then\n\t\t\t\t\t\tlogger -t \"$NODE\" \"Modem interface: $iface is average RSRP= ${RSRP} dBm. Min. value ${rsrp} dBm.\"\n\t\t\t\t\tfi\n\t\t\t\t\tif [ \"$link_status\" = \"0\" ]; then\n\t\t\t\t\t\tlogger -t \"$NODE\" \"Modem interface: $iface is loss connectivity\"\n\t\t\t\t\tfi\n\t\t\t\telse\n\t\t\t\t\tlogger -t \"$NODE\" \"WARNING: RSRP value not exist. Please check \\\"Resfesh signal\\\" option of modem interface!\"\n\t\t\t\tfi\n\t\t\t\tif [ \"${#apn}\" -gt \"0\" ]; then\n\t\t\t\t\tuci set network.$iface.apn=\"$apn\"\n\t\t\t\t\tlogger -t \"$NODE\" \"Switch SIM-card slot with APN: $apn\"\n\t\t\t\telse\n\t\t\t\t\tlogger -t \"$NODE\" \"WARNING: APN not defined. Switch SIM-card slot with default APN.\"\n\t\t\t\t\tuci set network.$iface.apn=\"internet\"\n\t\t\t\tfi\n\n\t\t\t\tuci commit network\n\t\t\t\treload_config network\n\n\t\t\t\tif [ \"$revert\" = \"1\" ]; then\n\t\t\t\t\tif [ \"$cur_sim\" -eq \"$sim_value\" ]; then \n\t\t\t\t\t\tFBT=${FBT:=$((($interval+$times_rsrp)*2))}\n\t\t\t\t\t\tFBT=$(($FBT*2))\n\t\t\t\t\t\tSWDATE=$((`date +%s`+$FBT))\n\t\t\t\t\t\techo \"FBT=$FBT\" > /tmp/ssw.vars\n\t\t\t\t\t\techo SWDATE=$SWDATE >> /tmp/ssw.vars\n\t\t\t\t\t\tlogger -t \"$NODE\" \"Back to default SIM-slot after $(date -d @${SWDATE})\"\n\t\t\t\t\tfi\n\t\t\t\tfi\n\t\t\t\tsw_sim && sleep 20 && reload_iface &\n\t\t\tfi\n\t\t\tif [ \"$revert\" = \"1\" ]; then\n\t\t\t\tsw_rule && sleep 20 && reload_iface &\n\t\t\tfi\n\t\t\tcnt=0\n\t\telse\n\t\t\tcnt=$(($cnt+1))\n\t\tfi\n\tfi\ndone\n"
  },
  {
    "path": "packages/net/telegrambot/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=telegrambot\nPKG_VERSION:=0.0.4\nPKG_RELEASE:=4\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  PKGARCH:=all\n  DEPENDS:=+curl\n  TITLE:=Openwrt Telegram Bot\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tTelegram bot for router with firmware Lede/Openwrt.\nendef\n\ndefine Package.$(PKG_NAME)/conffiles\n\t/etc/config/telegrambot\nendef\n\n\ndefine Build/Prepare\nendef\n\ndefine Build/Configure\nendef\n\ndefine Build/Compile\nendef\n\n\ndefine Package/$(PKG_NAME)/install\n\t$(CP) ./root/* $(1)/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/net/telegrambot/README.md",
    "content": "# Openwrt Telegram Bot\n\nTelegram bot for router with firmware Lede/Openwrt.\n\n## Implemented functions (Commands via Telegram message)\n\n  - */memory* return RAM info\n  - */clients* connected clients\n  - */wll_list* wifi clients\n  - */wifi_list* wifi info\n  - */reboot* reboot the device\n  - */wol <mac_address>* wake on lan over the Internet\n  - */wanip* WAN ip address\n  - */<script_name>* any scripts in the `plugins` directory\n\n## Prerequisites\n\nTelegram bot run under Lede or Openwrt firmware than the first prerequisite is to have Lede/Openwrt installed.\n\nSecond prerequisite is to have `curl` package installed. You can do this with command `opkg update && opkg install curl`.\n\n## Installation Steps\n\n### Step one:\n\n- Get your chat_id of Telegram. If you don't know what is your chat_id you can use bot @get_id_bot.\n\n- Get a bot token and start your bot. If you don't know how get it you can use bot @BotFather. Send him /newbot command , name of your new bot and a username. Get the returned string \"Use this token to access the HTTP API:\" \n\n### Step two:\n\nInstall Package  [telegrambot_0.0.3-1_all.ipk](http://openwrt.132lan.ru/packages/packages-19.07/mipsel_24kc/packages/telegrambot_0.0.3-1_all.ipk) of your Lede/Openwrt system.\n\n### Step three:\n\nSet your variables (bot token and chat id) in `telegrambot` file under `/etc/config/` dir.\n\n```sh\nuci set telegrambot.config.bot_token='[PUT YOUR BOT TOKEN HERE]'\nuci set telegrambot.config.chat_id='[PUT YOUR CHAT ID HERE]'\n\nuci commit telegrambot\n```\n\nStart `telegrambot` service with commands:\n\n```/etc/init.d/telegrambot restart```\n\nEnjoy your bot!\n"
  },
  {
    "path": "packages/net/telegrambot/root/etc/config/telegrambot",
    "content": "config telegram_bot 'config'\n\toption bot_token ''\n\toption chat_id ''\n\toption polling_time '1'\n\toption timeout '30'\n\toption plugins '/usr/lib/telegrambot/plugins'\n"
  },
  {
    "path": "packages/net/telegrambot/root/etc/init.d/telegrambot",
    "content": "#!/bin/sh /etc/rc.common\n# Copyright (c) 2011-2015 OpenWrt.org\n\nSTART=99\n\nstart() {\n\tif [ ! -f \"/var/run/telegrambot.pid\" ]; then\n\t\techo \"1\" > /var/run/telegrambot.pid\n\t\t/usr/lib/telegrambot/telegrambot >/dev/null 2>&1 &\n\tfi\n\treturn 0\n}\n\nstop() {\n\trm -f /var/run/telegrambot.pid\n\tkill $(pgrep -f telegrambot/telegrambot) >/dev/null 2>&1\n}\n\nrestart(){\n\tstop\n\tsleep 1\n\tstart\n}\n\nboot() {\n\t(sleep 5 && start >/dev/null 2>&1) &\n\treturn 0\n}\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/functions/get_mac.sh",
    "content": "#!/bin/sh\n\nPUREMAC=\"${1:0:8}\"\nLMAC=$(echo $PUREMAC | awk '{ print toupper($0) }')\nMAC=${LMAC//:/}\n\nmaclist=$(curl -s -k -L -X GET -H \"Content-Type: application/json; charset=utf-8\" \\\n\t-H \"Origin: https://regauth.standards.ieee.org\" \\\n\t-H \"Referer: https://regauth.standards.ieee.org/standards-ra-web/pub/view.html\" \\\n\t\"https://services13.ieee.org/RST/standards-ra-web/rest/assignments/?registry=MA-L&sortby=organization&sortorder=asc&text=${MAC}\")\n\nIDS=${MAC//-/}\nHEX=$(jsonfilter -s \"$maclist\" -e $.data.hits[*].assignmentNumberHex)\n\ni=0\nfor hexs in $HEX\ndo\n\tif [ \"$hexs\" == \"$IDS\" ]; then\n\t\tname=$(jsonfilter -s \"$maclist\" -e $.data.hits[$i].organizationName)\n\t\t#echo \"Identifier: $LMAC\"\n\t\techo \"Organization: $name\"\n\t\texit\n\tfi\n\ti=$((i+1))\ndone\necho \"Organization: Not Found!\""
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/functions/ping.sh",
    "content": "#!/bin/sh\n\nDEVICE=\"$1\"\nNL=\"$2\"\n\nping ${DEVICE} -w 1 -q &>/dev/null; [ $? == 0 ] && echo -en \"Up\" || echo -en \"Down\"\nif [ -z ${NL} ];then\n\techo -en \"\\n\"\nfi"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/ifconfig.sh",
    "content": "#!/bin/sh\n\nINTERFACE=\"$1\"\n\nif [ -n \"$INTERFACE\" ]; then\n\techo -en \"\\`\\`\\`$(ifconfig $INTERFACE)\\`\\`\\`\"\nelse\n\techo -en \"Usage: */ifconfig {interface}*\"\nfi\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/kernel.sh",
    "content": "#!/bin/sh\nuname -a\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/leases.sh",
    "content": "#!/bin/sh\necho -en \"Current DHCP leases:\\n\\`\\`\\`\\n \"\necho -en \"Exp date   |        HWaddr     |   IP\\n\"\nfile='/tmp/dhcp.leases'\nwhile IFS= read -r line <&3; do\n  echo -en \"$(date -d @$(printf '%s\\n' \"$line\" | awk '{print $1}') +'%d.%m %H:%M') | \"\n  printf '%s' \"$line\" | awk '{print $2\" | \"$3}'\ndone 3< $file\necho -en \"\\`\\`\\`\"\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/memory.sh",
    "content": "#!/bin/sh\necho -en \"\\`\\`\\` \\n\"\necho -en \"$(cat /proc/meminfo | sed -n '1,5p')\\`\\`\\`\"\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/netstat.sh",
    "content": "#!/bin/sh\n\nnetstat -ntu | grep -v \"127.0.0.1\" | grep -v \"::ffff:\" |grep \"ESTABLISHED\\|CLOSED\\|TIME_WAIT\" | sed -e 's/_/\\\\_/g' | awk '{print \"Protocolo: \" $1 \" \\nOrigem: \" $4 \"\\nDestino: \" $5 \"\\nState: \" $6 \"\\n\" } '\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/opkg.sh",
    "content": "#!/bin/sh\n\nACTION=$1\nPKG=\"$2 $3 $4 $5 $6\"\n\nupdate_(){\n\topkg update > /dev/null 2>&1\n\tMSG=\"List updated.\"\n}\n\nlist_(){\n\tPKGS=\"$(opkg list-upgradable)\"\n\tMSG=\"\\`\\`\\`\\n$PKGS\\`\\`\\`\"\n\tif [ \"$PKGS\" ]; then\n\t\tMSG=\"Packages to upgrade are:\\n${MSG}\"\n\telse\n\t\tMSG=\"Noting to upgrade\"\n\tfi\n}\n\nlistpkg_(){\n\tPKGS=\"$(opkg ${ACTION} ${PKG})\"\n\tif [ \"$PKGS\" ]; then\n\t\tMSG=\"\\`\\`\\`\\n$PKGS\\`\\`\\`\"\n\telse\n\t\tMSG=\"Package $2 not found.\"\n\tfi\n}\n\nupgrade_(){\n\tPKGS=\"$(opkg list-upgradable | awk '{print $1}')\"\n\topkg upgrade $(echo ${PKGS}) > /dev/null 2>&1\n\tMSG=\"Upgraded package(s):\\n\\`\\`\\`\\n${PKGS}\\`\\`\\`\"\n}\n\ninstall_(){\n\topkg ${ACTION} ${PKG} > /dev/null 2>&1\n\tMSG=\"Package(s): $PKG installed.\"\n}\n\nremove_(){\n\topkg ${ACTION} ${PKG} > /dev/null 2>&1\n\tMSG=\"Package(s): $PKG removed.\"\n}\n\ninstalled_(){\n\tPKGS=\"$(opkg list-installed | awk '{print $1}')\"\n\t#PKGS=\"Test TEst2 TEst3\"\n\tMSG=\"Installed package(s):\\n\\`\\`\\`\\n$(echo ${PKGS})\\`\\`\\`\"\n}\n\nhelp_(){\n\tMSG=\"Usage: */opkg command [argument]*\\n\\\n\tAviable opkg commands:\\n\\\n\t\\t\\tupdate - update packages list\\n\\\n\t\\t\\tlist pkgmane - show package\\n\\\n\t\\t\\tinstall pkgname - install packages (max 5 package names)\\n\\\n\t\\t\\tremove pkgname - remove packages (max 5 package names)\\n\\\n\t\\t\\tlist-installed [pkgname] - list installed packages [package]\\n\\\n\t\\t\\tlist-upgrade - list upgradabe packages\\n\\\n\t\\t\\trun-upgrade - run upgrade upgradable packages\\n\\\n\t\\t\\thelp - this help.\"\n}\n\ncase ${ACTION} in\n\tupdate)\n\t\tupdate_\n\t\techo -en \"$MSG\"\n\t;;\n\tlist)\n\t\tlistpkg_\n\t\techo -en \"$MSG\"\n\t;;\n\tlist-upgrade)\n\t\tlist_\n\t\techo -en \"$MSG\"\n\t;;\n\trun-upgrade)\n\t\tupgrade_\n\t\techo -en \"$MSG\"\n\t;;\n\tinstall)\n\t\tinstall_\n\t\techo -en \"$MSG\"\n\t;;\n\tremove)\n\t\tremove_\n\t\techo -en \"$MSG\"\n\t;;\n\tlist-installed)\n\t\tinstalled_\n\t\techo -en \"$MSG\"\n\t;;\n\t*)\n\t\thelp_\n\t\techo -en \"$MSG\"\n\t;;\nesac\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/ping.sh",
    "content": "#!/bin/sh\n\nDEVICE=\"$1\"\nNL=\"$2\"\n\nping ${DEVICE} -w 1 -q &>/dev/null; [ $? == 0 ] && echo -en \"Up\" || echo -en \"Down\"\nif [ -z ${NL} ];then\n\techo -en \"\\n\"\nfi"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/plugins.sh",
    "content": "#!/bin/sh\n\nIFS=\necho -ne \"Aviable commands are:\\n\"\n\ncd /usr/lib/telegrambot/plugins\n\nfor p in $(find . -type f -print -o -name . -o -prune |sed -e 's/.sh//g' -e 's/\\.//g'); do\n\techo -ne \"*$p*\\n\"\ndone\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/swports_list.sh",
    "content": "#!/bin/sh\n# <https://github.com/alexwbaule/telegramopenwrt/blob/master/plugins/swports_list>\n\nIFS=$'\\n'\nPORTS=$(swconfig dev switch0 show | grep link | grep -oE \"port:[[:digit:]][[:space:]]link:(up|down)([[:space:]]speed:[[:digit:]]+|$)\")\nfor port in $PORTS\ndo\n\tid=$(echo $port | grep -oE \"port:[[:digit:]]\" | cut -d ':' -f 2)\n\tif [ \"$id\" == \"0\" ]\n\t\tthen\n\t\techo \"CPU\"\n\telif [ \"$id\" == \"1\" ]\n\t\tthen\n\t\techo \"WAN\"\n\telif [ \"$id\" == \"2\" ]\n\t\tthen\n\t\techo \"LAN 1\"\n\telif [ \"$id\" == \"3\" ]\n\t\tthen\n\t\techo \"LAN 2\"\n\telif [ \"$id\" == \"4\" ]\n\t\tthen\n\t\techo \"LAN 3\"\n\telif [ \"$id\" == \"5\" ]\n\t\tthen\n\t\techo \"LAN 4\"\n\telif [ \"$id\" == \"6\" ]\n\t\tthen\n\t\techo \"EMPTY\"\n\tfi\n\techo -en \"${port// /\\n}\\n\\n\"\ndone"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/uci.sh",
    "content": "#!/bin/sh\n\nACTION=$1\nVARIABLE=$2\n\ncase ${ACTION} in\n\tshow) echo -en \"\\`\\`\\`$(uci show ${VARIABLE})\\`\\`\\`\" :;;\n\tget) echo -en \" \\`\\`\\`$(uci -q get ${VARIABLE})\\`\\`\\`\" :;;\n\t*) echo -en \" Usage: */uci {show|get} [section]*\" ;;\nesac\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/uptime.sh",
    "content": "#!/bin/sh\n\nuptime"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/wanip.sh",
    "content": "#ip r | awk '/default/{print \"interface \"$5\": \"$9}'\nIP=$(nslookup myip.opendns.com resolver1.opendns.com | awk '/^[[:space:]]*Address( 1)?: / { print $2 }')\necho -en \"[$IP](https://whois.ru/$IP)\"\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/wifi_list.sh",
    "content": "#!/bin/sh\n# <https://github.com/alexwbaule/telegramopenwrt/blob/master/plugins/wifi_list>\n\nold=$IFS\nIFS=$'\\n'\nTOTAL=0\n\necho \"*Wireless*\"\n\nfor rule in $(uci show wireless  | grep \"default_radio\" )\ndo\n\tline=$(echo ${rule} | awk -F \".\" '{print $3}')\n\tid=$(echo ${rule} | grep \".ssid\" | grep -oE \"\\[[[:digit:]]+\\]\" | awk '{gsub(\"\\\\[|]\",\"\");printf $1}')\n\tif [ \"$id\" != \"\" ]; then\n\t\techo \"rate: \"$(iwinfo wlan${id} info | grep -oE \"[[:digit:]]+\\.[[:digit:]]+ MBit\\/s\")\n\t\tawk 'BEGIN{x='$(iwinfo wlan${id} info | grep -oE \"[[:digit:]]+/[[:digit:]]+\")' * 100; printf \"quality: %.0f%%\\n\", x}'\n\tfi\n\tnew=${line//\\'/}\n\tnew2=${new//_/\\\\_}\n\tnew3=${new2//=/: }\n\techo ${new3//\\*/all}\ndone\nIFS=$old"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/wll_list.sh",
    "content": "#!/bin/sh\n# https://github.com/alexwbaule/telegramopenwrt/blob/master/plugins/wll_list\nfor wifi in wlan0 wlan1\ndo\n\tif [ \"${wifi}\" == \"wlan0\" ]; then\n\t\techo -en \"-----------------*${wifi} - 2.4 Ghz*-----------------------\\n\"\n\telse\n\t\techo -en \"-----------------*${wifi} - 5 Ghz*-------------------------\\n\"\n\tfi\n\tmacaddr=$(iw dev ${wifi} station dump | grep Station | grep -oE \"([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}\" | awk '{printf $0 \" \"}')\n\tfor lease in ${macaddr}\n\tdo\n\t\tline=$(cat /tmp/dhcp.leases | grep ${lease})\n\t\tif [ $? == 0 ]; then\n\t\t\techo \"${line}\" | awk '{gsub( \"*\",\"\\\\*\" ); gsub( \"_\",\"\\\\_\" ); printf \"Device: \" $4 \"\\nIP: \" $3 \"\\nMac: \" toupper($2) \"\\nState: \";system(\"./functions/get_ping.sh \"$4\" 1\");printf \"\\n\"}'\n\t\telse\n\t\t\tcat /proc/net/arp | grep ${lease} | awk '{gsub( \"_\",\"\\\\_\" ); printf \"IP: \" $1 \"\\nMac: \" toupper($4) \"\\nState: \";system(\"./functions/get_ping.sh \"$1\" 1\");printf \"\\n\"}'\n\t\tfi\n\t\t./functions/get_mac.sh \"${lease}\"\n\t\techo -en \"\\n\"\n\tdone\n\t#echo -en \"-----------------------------------------------------------\\n\"\ndone"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/plugins/wol.sh",
    "content": "#!/bin/sh\nWOLIFACE=$1\nHWADDR=$2\nif [ -z $WOLIFACE ]  && [ -z \"$HWADDR\" ]; then\n\techo -en \"Usage: */wol {interface} {hwadddress}*\"\nelse\n\t/usr/bin/etherwake -D -i \"$WOLIFACE\" \"$HWADDR\"\nfi\n"
  },
  {
    "path": "packages/net/telegrambot/root/usr/lib/telegrambot/telegrambot",
    "content": "#!/bin/sh\n\nPATH='/usr/bin:/usr/sbin:/bin:/sbin'\n\nfor v in bot_token chat_id server timeout polling_time enabled log_file plugins; do\n\teval $v=$(uci -q get telegrambot.@telegram_bot[0].${v} 2>/dev/nul)\ndone\n\nif [ $enabled -eq 0 ]; then\n\texit 0\nfi\n\nif [ -z \"$bot_token\" ]; then\n\techo \"token is empty\"\n\texit 2\nfi\n\nif [ -z \"$chat_id\" ]; then\n\techo \"chat_id is empty\"\n\texit 2\nfi\n\nif [ -z \"$server\" ]; then\n\tserver=\"https://api.telegram.org\"\nfi\n\nif [ -z \"$timeout\" ]; then\n\ttimeout=30\nfi\n\nif [ -z \"$polling_time\" ]; then\n\tpolling_time=1\nfi\n\napi=\"${server}/bot${bot_token}\"\n\nif [ ! -d \"/tmp/telegrambot\" ]; then\n  mkdir /tmp/telegrambot\nfi\nif [ \"x$plugins\" = \"x\" ]; then\n\tplugins=\"/usr/lib/telegrambot/plugins\"\nfi\n#telegram_log_file=/tmp/telegram-bot/telegram.log\nif [ \"x$log_file\" = \"x\" ]; then\n\tlogfile=/dev/null\nelse\n\tlog_file=${log_file}\nfi\n\noffset_file=/tmp/telegrambot/telegram_offset\n\ncommand_not_found=\"Sorry,\nI only recognize *commands*.\nCommands are words beginning with a slash.\nTry it !\nSend [/start](/start) to get my commands list.\"\n\ncommand_start=\"*/memory* return RAM info\n*/leases* current dhcp leases\n*/wll_list* wifi clients\n*/reboot* reboot the device\n*/wol <mac_address>* wake on lan over the Internet\n*/loc* get current location via modemmanager\n*/wanip* WAN ip address\n*/plugins* aviable commands\"\n\ncurl -k -s -X POST $api/sendMessage \\\n\t-d chat_id=$chat_id \\\n\t-d parse_mode=Markdown \\\n\t--data-urlencode text=\"$(uci -q get system.@system[0].hostname). TelegramBot started.\" &> $log_file\n\nif [ -f \"$offset_file\" ] ; then\n\toffset=$( cat $offset_file )\nelse\n\toffset=0\n\techo $offset > $offset_file\nfi\n\nreply_to_msg () {\n\tlocal msg_id=$1\n\tlocal origin=$2\n\teval local text=\"$3\"\n        curl -k -s -X POST $api/sendMessage \\\n\t\t-d reply_to_message_id=$msg_id \\\n\t\t-d chat_id=$origin \\\n\t\t-d parse_mode=Markdown \\\n\t\t--data-urlencode text=\"$text\" &> $log_file\n}\n\nreply_to_location() {\n        local msg_id=$1\n        local origin=$2\n        SLOT=$(mmcli -L | awk '{print $1}' | awk -F [\\/] '{print $NF}')\n        GPS=\"$(echo $(mmcli -m $SLOT  --location-get -J | jsonfilter -e '@[\"modem\"].*[\"gps\"].*'))\"\n        LAT=$(echo $GPS | awk '{print $2}')\n        LONG=$(echo $GPS | awk '{print $3}')\n        case $LAT in\n                *[0-9]*)\n                        curl -k -s -X POST $api/sendLocation \\\n                                -d reply_to_message_id=$msg_id \\\n                                -d chat_id=$origin \\\n                                -d parse_mode=Markdown \\\n                                -d latitude=$LAT \\\n                                -d longitude=$LONG &>$log_file\n                ;;\n                *)\n                        curl -k -s -X POST $api/sendMessage \\\n                                -d reply_to_message_id=$msg_id \\\n                                -d chat_id=$origin \\\n                                -d parse_mode=Markdown \\\n                                --data-urlencode text=\"Location not sended! Check your mmcli settings or GPS antenna.\" &>$log_file\n                ;;\n        esac\n}\n\nwhile [ true ]\ndo\n\tupdates=$(curl -s -k -X GET ${api}/getUpdates?offset=${offset}&timeout=${timeout})\n\tstatus=$(jsonfilter -s \"$updates\" -e $.ok)\n\tif [ $status = 'true' ]; then\n\t\tupdate_ids=$(jsonfilter -s \"$updates\" -e $.result[*].update_id)\n\t\tfor update_id in $update_ids\n\t\tdo\n\t\t\toffset=$((update_id+1))\n\t\t\techo $offset > $offset_file\n\t\t\torigin=$(jsonfilter -s \"$updates\"  -e \"$.result[@.update_id=$update_id].message.chat.id\")\n\t\t\tmsg_id=$(jsonfilter -s \"$updates\"  -e \"$.result[@.update_id=$update_id].message.message_id\")\n\t\t\tcommand=$(jsonfilter -s \"$updates\" -e \"$.result[@.update_id=$update_id].message.text\")\n\t\t\tis_a_cmd=$(jsonfilter -s \"$updates\" -e \"$.result[@.update_id=$update_id].message.entities[*].type\")\n\t\t\tquery_ans=$(jsonfilter -s \"$updates\" -e \"$.result[@.update_id=$update_id].callback_query.id\")\n\t\t\torigin_ans=$(jsonfilter -s \"$updates\"  -e \"$.result[@.update_id=$update_id].callback_query.message.chat.id\")\n\t\t\tif [[ \"$origin\" != \"$chat_id\" && \"$origin_ans\" != \"$chat_id\" ]];then\n\t\t\t\tcurl -k -s -X POST $api/sendMessage -d reply_to_message_id=$msg_id -d chat_id=$origin -d parse_mode=Markdown --data-urlencode \\\n\t\t\t\t\ttext=\"This is a Private bot. If you want to implement one for you, check this out https://github.com/ixiumu/openwrt-telegram-bot\" &> $log_file\n\t\t\t\tcurl -k -s -X POST $api/leaveChat -d chat_id=$origin  &> $telegram_log_file\n\t\t\telse\n\t\t\t\tif [ $(echo \"$is_a_cmd\" | grep \"bot_command\") != \"\" ]; then\n\t\t\t\t\t\n\t\t\t\t\tcmd=$(echo $command | awk '{print $1}')\n\t\t\t\t\tprms=$(echo $command | awk '{$1=\"\";print $0}' | sed 's/^[ \\t]*//g')\n\t\t\t\t\tparms=${prms//[\\\"\\&\\;\\\\\\>\\<\\|]/}\n\t\t\t\t\tDATE=`date +%Y-%m-%d_%H:%M:%S`\n\t\t\t\t\tRETURN_TEXT=\"\"\n\t\t\t\t\tif [ $send_hotname == 1 ]; then\n\t\t\t\t\t\tRETURN_TEXT=\"$hostname ${RETURN_TEXT}\"\n\t\t\t\t\tfi\n\t\t\t\t\t\n\t\t\t\t\tcase \"$cmd\" in\n\t\t\t\t\t\t(\"/start\"|\"/help\")\n\t\t\t\t\t\t\treply_to_msg $msg_id $origin \"\\${command_start}\"\n\t\t\t\t\t\t\t;;\n\t\t\t\t\t\t(\"/reboot\")\n                                                \t(echo \"Call $cmd\" | logger -t \"telegram_bot\" -p daemon.info)&\n\t                                                RETURN_TEXT=\"Command executed!\"\n\t\t\t\t\t\t\tUPTIME=$(awk '{printf \"%0.0f\\n\", $1}' /proc/uptime)\n\t\t\t\t\t\t\t# fix bootloop commands. Add timeout 3 min\n\t\t\t\t\t\t\t[ \"$UPTIME\" -ge \"180\" ] && {\n\t\t\t\t\t\t\t\treply_to_msg $msg_id $origin \"\\${RETURN_TEXT}\" && touch /etc/banner && sleep 5 && reboot\n\t\t\t\t\t\t\t} || {\n\t\t\t\t\t\t\t\tRETURN_TEXT=\"Command not accepted! Uptime ${UPTIME} sec is smaller 180s\"\n\t\t\t\t\t\t\t\treply_to_msg $msg_id $origin  \"\\${RETURN_TEXT}\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t;;\n\t\t\t\t\t\t(\"/wol\")\n\t\t\t\t\t\t\t(echo \"Call $cmd [$parms]\" | logger -t \"telegram_bot\" -p daemon.info)&\n\t\t\t\t\t\t\tRETURN_TEXT=\"$(${plugins}/wol.sh ${parms})\"\n\t\t\t\t\t\t\treply_to_msg $msg_id $origin \"\\${RETURN_TEXT}\"\n                                                        ;;\n\t\t\t\t\t\t(\"/loc\")\n                                                        reply_to_location $msg_id $origin\n\t\t\t\t\t\t\t;;\n\t\t\t\t\t\t(*)\n\t\t\t\t\t\t\tif [ -f \"${plugins}/${cmd}.sh\" ] ; then\n\t\t\t\t\t\t\t\t(echo \"Call $cmd [$parms]\" | logger -t \"telegram_bot\" -p daemon.info)&\n\t\t\t\t\t\t\t\tRETURN_TEXT=$(${plugins}/${cmd}.sh ${parms})\n\t\t\t\t\t\t\telse\n                                    \t\t        \tRETURN_TEXT=\"Command *${cmd}* not found!\"\n\t\t\t\t\t\t\tfi\n\n                                                        reply_to_msg $msg_id $origin \"\\${RETURN_TEXT}\"\n\t\t\t\t\t\t\t;;\n                                    \tesac\n\n\t\t\t\telse\n \t\t\t\t\treply_to_msg $msg_id $origin \"\\${command_not_found}\"\n\t\t\t\tfi\n\n\t\t\tfi\n\t\tdone\n\tfi\n\tsleep $polling_time\ndone&\n"
  },
  {
    "path": "packages/net/teleproxy/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=teleproxy\nPKG_VERSION:=4.12.2\nPKG_RELEASE:=1\n\nPKG_MAINTAINER:=Kosntantine Shevlakov <shevlakov@132lan.ru>\nPKG_LICENSE:=GPLv2\nPKG_LICENSE_FILES:=LICENSE\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL:=https://github.com/teleproxy/teleproxy.git\nPKG_SOURCE_VERSION:=960a11a8cf96fe3d3e9f3e553407e73ec97070f3\n\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\n\ninclude $(INCLUDE_DIR)/package.mk\n\nMAKE_VARS += EXTRA_VERSION=$(PKG_VERSION)\n\ndefine Package/$(PKG_NAME)\n  SUBMENU:=Web Servers/Proxies\n  SECTION:=net\n  CATEGORY:=Network\n  TITLE:=MTProxy OpenWRT package\n  DEPENDS:=+libopenssl +zlib\nendef\n\ndefine Package/$(PKG_NAME)/conffiles\n/etc/config/$(PKG_NAME)\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin \\\n\t\t\t$(1)/usr/share \\\n\t\t\t$(1)/etc/config \\\n\t\t\t$(1)/etc/init.d \\\n\t\t\t$(1)/etc/uci-defaults\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/objs/bin/* \\\n\t\t\t$(1)/usr/bin/\n\t$(INSTALL_BIN) ./files/$(PKG_NAME).init $(1)/etc/init.d/$(PKG_NAME)\n\t$(INSTALL_BIN) ./files/$(PKG_NAME).defaults  $(1)/etc/uci-defaults/$(PKG_NAME)\n\t$(INSTALL_BIN) ./files/$(PKG_NAME)-config-refresh.sh $(1)/usr/share/\n\t$(INSTALL_CONF) ./files/$(PKG_NAME).config $(1)/etc/config/$(PKG_NAME)\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/net/teleproxy/README.md",
    "content": "\n# MTPROTO proxy server\n\nteleproxy OpenWrt package\n\nexample config file\n\n```\nconfig teleproxy 'default'\n  option address '0.0.0.0'      # listen ipaddr or '::' with ipv6\n  option ipv6 '1'               # Use IPv6 proto\n  option port '8443'            # proxy port\n  option direct '1'             # connect directly to Telegram DCs instead of through ME relays\n  option aes_pwd 'file'         # sets custom secret.conf file\n  option socks 'addr:port'      # route upstream DC connections through SOCKS5 proxy\n  option socks_auth 'user:pass' # autentification on SOCKS5 upstream proxy\n  option extra '--extra args'   # exta args to run teleproxy. See \"teleproxy --help\".\n```\n"
  },
  {
    "path": "packages/net/teleproxy/files/teleproxy-config-refresh.sh",
    "content": "#!/bin/sh\n\n# teleproxy config updater\n# by koshev-msk 2026\n\nURL=https://core.telegram.org/\n\nwhich curl || echo \"Error. cURL not found.\"\n\n# Check proxy connectrion \nSOCKS_PROXY=$(uci -q get teleproxy.default.socks)\n[ -n $SOCKS_PROXY ] && {\n\t# Check autentification proxy\n\tSOCKS_AUTH=$(uci -q get teleproxy.default.socks_auth)\n\t[ -n $SOCKS_AUTH ] && {\n\t\tCURL=\"curl -x socks5://${SOCKS_AUTH}@${SOCKS_PROXY}\"\n\t} || {\n\t\tCURL=\"curl -x socks://${SOCKS_PROXY}\"\n\t}\n} || {\n\tCURL=\"curl\"\n}\n\n[ -d /etc/teleproxy ] || mkdir -p /etc/teleproxy\n\n# Download latest configs and check new sha256\n${CURL} -s --max-time 60 $URL/cidr.txt -o /tmp/cidr.txt && \\\n\tSHA256_CIDR_NEW=$(sha256sum /tmp/cidr.txt | awk '{print $1}') || SHA256_CIDR_NEW=\"failed\"\n${CURL} -s --max-time 60 $URL/getProxyConfig -o /tmp/proxy-multi.conf && \\\n\tSHA256_MULTI_NEW=$(sha256sum /tmp/proxy-multi.conf | awk '{print $1}') || SHA256_MULTI_NEW=\"failed\"\n${CURL} -s --max-time 60 $URL/getProxySecret -o /tmp/aes-secret && \\\n\tSHA256_AES_NEW=$(sha256sum /tmp/aes-secret | awk '{print $1}') || SHA256_AES_NEW=\"failed\"\n\n# Check old sha256 config\n[ -f /etc/teleproxy/cidr.txt ] && \\\n\tSHA256_CIDR_OLD=$(sha256sum /etc/teleproxy/cidr.txt | awk '{print $1}') || SHA256_CIDR_OLD=\"failed\"\n[ -f /etc/teleproxy/proxy-multi.conf ] && \\\n\tSHA256_MULTI_OLD=$(sha256sum /etc/teleproxy/proxy-multi.conf | awk '{print $1}') || SHA256_MULTI_OLD=\"failed\"\n[ -f /etc/teleproxy/aes-secret ] && \\\n\tSHA256_AES_OLD=$(sha256sum /etc/teleproxy/aes-secret | awk '{print $1}') || SHA256_MULTI_OLD=\"failed\"\n\n# Comapre hashes\n[ \"$SHA256_CIDR_NEW\" = \"$SHA256_CIDR_OLD\" ] || \\\n\tcp /tmp/cidr.txt /etc/teleproxy/cidr.txt && rm /tmp/cidr.txt\n[ \"$SHA256_MULTI_NEW\" = \"$SHA256_MULTI_OLD\" ] || \\\n\tcp /tmp/proxy-multi.conf /etc/teleproxy/proxy-multi.conf && rm /tmp/proxy-multi.conf\n[ \"$SHA256_AES_NEW\" = \"$SHA256_AES_OLD\" ] || \\\n\tcp /tmp/aes-secret /etc/teleproxy/aes-secret && rm /tmp/aes-secret\n"
  },
  {
    "path": "packages/net/teleproxy/files/teleproxy.config",
    "content": "config teleproxy 'default'\n\toption port '4843'\n\toption direct '1'\n"
  },
  {
    "path": "packages/net/teleproxy/files/teleproxy.defaults",
    "content": "#!/bin/sh\n# generate secret key\n[ -n \"$(uci -q get teleproxy.default.secret)\" ] || {\n\t#SECRET=$(dd if=/dev/urandom bs=16 count=1 2>/dev/null | hexdump -v -e '16/1 \"%02x\"')\n\tSECRET=$(/usr/bin/teleproxy generate-secret)\n\tuci set teleproxy.default.secret=\"$SECRET\"\n\tuci commit teleproxy\n}\n"
  },
  {
    "path": "packages/net/teleproxy/files/teleproxy.init",
    "content": "#!/bin/sh /etc/rc.common\n\nSTART=95\nNAME=teleproxy\nBIN=/usr/bin/${NAME}\nPID=/var/run/${NAME}.pid\n\nload_config(){\n\tfor v in ipv6 address port secret direct relay aes_pwd auth_socks socks extra; do\n\t\tconfig_get $v $1 $v\n\tdone\n}\n\n\nstart() {\n\tconfig_load teleproxy\n\tlocal ipv6 address port secret direct relay aes_pwd auth_socks socks extra\n\tconfig_foreach load_config\n\t[ $ipv6 ] && args=\"-6\"\n\t[ $address ] && args=\"$args --address $address\"\n\t[ $port ] && args=\"$args -H $port\"\n\t[ $secret ] && args=\"$args -S $secret\"\n\t[ $aes_pwd ] && args=\"$args --aes-pwd $aes_pwd\"\n\t[ $socks -a \"$direct\" -eq \"1\" ] && {\n\t\t[ $auth_socks ] && {\n\t\t\targs=\"$args --socks socks5://${auth_socks}@${socks}\"\n\t\t} || {\n                        args=\"$args --socks socks5://$socks\"\n\t\t}\n\t}\n\t[ \"$extra\" ] && args=\"$args $extra\"\n\t[ \"$direct\" -ne \"1\" ] && {\n\t\t[ $relay ] && args=\"$args $relay\"\n\t} || {\n\t\targs=\"$args --direct\"\n\t}\n        ($BIN -u daemon $args) & > /dev/null 2>&1\n}\n\nstop() {\n\tkillall teleproxy\n}\n"
  },
  {
    "path": "packages/net/teleproxy/patches/0001-makefile.patch",
    "content": "Index: teleproxy/Makefile\n===================================================================\n--- teleproxy.orig/Makefile\n+++ teleproxy/Makefile\n@@ -56,11 +56,11 @@ COMMON_CFLAGS += $(EXTRA_CFLAGS)\n COMMON_LDFLAGS += $(EXTRA_LDFLAGS)\n \n # Architecture-specific CFLAGS\n-ifeq ($(HOST_ARCH), x86_64)\n+ifeq ($(ARCH), x86_64)\n CFLAGS := $(COMMON_CFLAGS) -mpclmul -march=core2 -mfpmath=sse -mssse3 $(BITNESS_FLAGS)\n-else ifeq ($(HOST_ARCH), aarch64)\n-CFLAGS := $(COMMON_CFLAGS) $(BITNESS_FLAGS)\n-else ifeq ($(HOST_ARCH), arm64)\n+else ifeq ($(ARCH), mipsel)\n+CFLAGS := $(COMMON_CFLAGS) $(BITNESS_FLAGS) -march=mips32r2 -Wno-builtin-declaration-mismatch\n+else\n CFLAGS := $(COMMON_CFLAGS) $(BITNESS_FLAGS)\n endif\n \n"
  },
  {
    "path": "packages/net/teleproxy/patches/0002-fix-mips-variable.patch",
    "content": "Index: teleproxy/src/mtproto/mtproto-proxy-stats.c\n===================================================================\n--- teleproxy.orig/src/mtproto/mtproto-proxy-stats.c\n+++ teleproxy/src/mtproto/mtproto-proxy-stats.c\n@@ -602,9 +602,9 @@ void mtfront_prepare_stats (stats_buffer\n       if (_quota > 0) {\n         sb_printf (sb, \"secret_%s_quota\\t%lld\\n\", _lbl, _quota);\n       }\n-      int _mips = tcp_rpcs_get_ext_secret_max_ips (_i);\n-      if (_mips > 0) {\n-        sb_printf (sb, \"secret_%s_max_ips\\t%d\\n\", _lbl, _mips);\n+      int _mips_ips = tcp_rpcs_get_ext_secret_max_ips (_i);\n+      if (_mips_ips > 0) {\n+        sb_printf (sb, \"secret_%s_max_ips\\t%d\\n\", _lbl, _mips_ips);\n       }\n       int64_t _exp = tcp_rpcs_get_ext_secret_expires (_i);\n       if (_exp > 0) {\n"
  },
  {
    "path": "packages/net/teleproxy/patches/0003-rtds-alternate.patch",
    "content": "Index: teleproxy/src/common/rdtsc_32bit.h\n===================================================================\n--- /dev/null\n+++ teleproxy/src/common/rdtsc_32bit.h\n@@ -0,0 +1,14 @@\n+#ifndef RDTSC_EMUL_H\n+#define RDTSC_EMUL_H\n+\n+#if defined(__arm__) || defined(__mips__) || defined(__mipsel__)\n+#include <time.h>\n+\n+static inline unsigned long long rdtsc(void) {\n+    struct timespec ts;\n+    clock_gettime(CLOCK_MONOTONIC, &ts);\n+    return (unsigned long long)ts.tv_sec * 1000000000ULL + ts.tv_nsec;\n+}\n+#endif\n+\n+#endif\nIndex: teleproxy/src/engine/engine-rpc-common.c\n===================================================================\n--- teleproxy.orig/src/engine/engine-rpc-common.c\n+++ teleproxy/src/engine/engine-rpc-common.c\n@@ -51,6 +51,7 @@\n\n+#include \"common/rdtsc_32bit.h\" \n #include \"engine/engine.h\"\n #include \"engine/engine-rpc-common.h\"\n \n #include \"common/tl-parse.h\"\n \nIndex: teleproxy/src/jobs/jobs.c\n===================================================================\n--- teleproxy.orig/src/jobs/jobs.c\n+++ teleproxy/src/jobs/jobs.c\n@@ -53,6 +53,7 @@\n #include \"net/net-connections.h\"\n #include \"jobs/jobs.h\"\n #include \"common/common-stats.h\"\n+#include \"common/rdtsc_32bit.h\"\n \n //#include \"auto/engine/engine.h\"\n \nIndex: teleproxy/src/net/net-crypto-aes.c\n===================================================================\n--- teleproxy.orig/src/net/net-crypto-aes.c\n+++ teleproxy/src/net/net-crypto-aes.c\n@@ -48,6 +48,7 @@\n \n #include \"jobs/jobs.h\"\n #include \"common/common-stats.h\"\n+#include \"common/rdtsc_32bit.h\"\n \n #define MODULE crypto_aes\n\nIndex: teleproxy/src/common/precise-time.c\n===================================================================\n--- teleproxy.orig/src/common/precise-time.c\n+++ teleproxy/src/common/precise-time.c\n@@ -19,10 +19,11 @@\n */\n #include <assert.h>\n #include <sys/time.h>\n+\n #include <time.h>\n /* unistd.h defines _POSIX_TIMERS */\n #include <unistd.h>\n-\n+#include \"rdtsc_32bit.h\"\n #include \"precise-time.h\"\n\n __thread int now;\n\nIndex: teleproxy/src/engine/engine-rpc.c\n===================================================================\n--- teleproxy.orig/src/engine/engine-rpc.c\n+++ teleproxy/src/engine/engine-rpc.c\n@@ -25,6 +25,8 @@\n     Copyright 2015-2016 Telegram Messenger Inc\n               2015-2016 Vitaliy Valtman\n */\n+#include \"common/rdtsc_32bit.h\"\n+\n #include \"engine/engine-rpc.h\"\n #include \"common/tl-parse.h\"\n\n"
  },
  {
    "path": "packages/net/teleproxy/patches/0004-atomic-32bit-emu.patch",
    "content": "Index: teleproxy/src/common/atomic_32bit.c\n===================================================================\n--- /dev/null\n+++ teleproxy/src/common/atomic_32bit.c\n@@ -0,0 +1,73 @@\n+#if defined(__arm__) || defined(__mips__) || defined(__mipsel__)\n+\n+#include <pthread.h>\n+#include <stdint.h>\n+\n+static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER;\n+\n+/* 64-bit atomic operations emulation */\n+uint64_t __sync_fetch_and_add_8(uint64_t *ptr, uint64_t val) {\n+    pthread_mutex_lock(&atomic_mutex);\n+    uint64_t old = *ptr;\n+    *ptr += val;\n+    pthread_mutex_unlock(&atomic_mutex);\n+    return old;\n+}\n+\n+uint64_t __sync_fetch_and_sub_8(uint64_t *ptr, uint64_t val) {\n+    pthread_mutex_lock(&atomic_mutex);\n+    uint64_t old = *ptr;\n+    *ptr -= val;\n+    pthread_mutex_unlock(&atomic_mutex);\n+    return old;\n+}\n+\n+uint64_t __sync_fetch_and_or_8(uint64_t *ptr, uint64_t val) {\n+    pthread_mutex_lock(&atomic_mutex);\n+    uint64_t old = *ptr;\n+    *ptr |= val;\n+    pthread_mutex_unlock(&atomic_mutex);\n+    return old;\n+}\n+\n+uint64_t __sync_fetch_and_and_8(uint64_t *ptr, uint64_t val) {\n+    pthread_mutex_lock(&atomic_mutex);\n+    uint64_t old = *ptr;\n+    *ptr &= val;\n+    pthread_mutex_unlock(&atomic_mutex);\n+    return old;\n+}\n+\n+uint64_t __sync_add_and_fetch_8(uint64_t *ptr, uint64_t val) {\n+    pthread_mutex_lock(&atomic_mutex);\n+    *ptr += val;\n+    uint64_t newval = *ptr;\n+    pthread_mutex_unlock(&atomic_mutex);\n+    return newval;\n+}\n+\n+uint64_t __sync_sub_and_fetch_8(uint64_t *ptr, uint64_t val) {\n+    pthread_mutex_lock(&atomic_mutex);\n+    *ptr -= val;\n+    uint64_t newval = *ptr;\n+    pthread_mutex_unlock(&atomic_mutex);\n+    return newval;\n+}\n+\n+int __sync_bool_compare_and_swap_8(uint64_t *ptr, uint64_t oldval, uint64_t newval) {\n+    pthread_mutex_lock(&atomic_mutex);\n+    int ret = (*ptr == oldval);\n+    if (ret) *ptr = newval;\n+    pthread_mutex_unlock(&atomic_mutex);\n+    return ret;\n+}\n+\n+uint64_t __sync_val_compare_and_swap_8(uint64_t *ptr, uint64_t oldval, uint64_t newval) {\n+    pthread_mutex_lock(&atomic_mutex);\n+    uint64_t old = *ptr;\n+    if (old == oldval) *ptr = newval;\n+    pthread_mutex_unlock(&atomic_mutex);\n+    return old;\n+}\n+\n+#endif /* 32bits arch */\n\n"
  },
  {
    "path": "packages/net/teleproxy/patches/0005-makefile-atomic-emu.patch",
    "content": "Index: teleproxy/Makefile\n===================================================================\n--- teleproxy.orig/Makefile\n+++ teleproxy/Makefile\n@@ -92,6 +92,7 @@ DEPENDENCE_STRANGE\t:=\t$(subst ${OBJ}/,${\n DEPENDENCE_NORM\t:=\t$(subst ${OBJ}/,${DEP}/,$(patsubst %.o,%.d,${OBJECTS}))\n \n LIB_OBJS_NORMAL := \\\n+\t${OBJ}/src/common/atomic_32bit.o \\\n \t${OBJ}/src/common/crc32c.o \\\n \t${OBJ}/src/common/pid.o \\\n \t${OBJ}/src/common/sha1.o \\\n"
  },
  {
    "path": "packages/net/teleproxy/patches/0006-fix-mips-musl-sigrtmax.patch",
    "content": "Index: teleproxy/src/engine/engine.c\n===================================================================\n--- teleproxy.orig/src/engine/engine.c\n+++ teleproxy/src/engine/engine.c\n@@ -612,8 +612,15 @@ void engine_startup (engine_t *E, server\n   precise_now_diff = get_utime_monotonic () - get_double_time ();\n \n #ifdef __linux__\n-  assert (SIGRTMAX == OUR_SIGRTMAX);\n-  assert (SIGRTMAX - SIGRTMIN >= 20);\n+  #ifdef __mips__\n+    if (SIGRTMAX != OUR_SIGRTMAX) {\n+      vkprintf(0, \"MIPS: SIGRTMAX mismatch (libc=%d, expected=%d), continuing\\n\", \n+               SIGRTMAX, OUR_SIGRTMAX);\n+    }\n+  #else\n+    assert(SIGRTMAX == OUR_SIGRTMAX);\n+  #endif\n+  assert(SIGRTMAX - SIGRTMIN >= 20);\n #endif\n   \n   E->sfd = 0;\n"
  },
  {
    "path": "packages/net/torrserver/Makefile",
    "content": "# Makefile build Torrserver MatriX for OpenWrt SDK\n# add node repository in feeds.conf.default for build OpenWrt current branch: e.g 24.10\n# src-git node https://github.com/nxhack/openwrt-node-packages.git;openwrt-24.10\n# and optional for compress binary via upx\n# src-git upx https://github.com/kuoruan/openwrt-upx.git\n# Copyright (c) Konstantine Shevlakov 2025 at <shevlakov@132lan.ru>\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=torrserver\nPKG_VERSION:=137\nPKG_RELEASE:=1\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL:=https://github.com/YouROK/TorrServer.git\nPKG_SOURCE_VERSION:=19e4f4ef78ad92e68a19fafc561003ca33e0dea8\nPKG_MIRROR_HASH:=skip\n\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\nPKG_LICENSE:=MIT\nPKG_LICENSE_FILES:=LICENSE\n\nPKG_BUILD_DEPENDS:=golang/host node/host node-yarn/host upx/host\nPKG_BUILD_PARALLEL:=1\nPKG_BUILD_FLAGS:=no-mips16\n\ninclude $(INCLUDE_DIR)/package.mk\n\nGO_ARCH:=\nifeq ($(ARCH),aarch64)\n  GO_ARCH:=arm64\nelse ifeq ($(ARCH),arm)\n  GO_ARCH:=arm\nelse ifeq ($(ARCH),i386)\n  GO_ARCH:=386\nelse ifeq ($(ARCH),i686)\n  GO_ARCH:=386\nelse ifeq ($(ARCH),x86_64)\n  GO_ARCH:=amd64\nelse ifeq ($(ARCH),mips)\n  GO_ARCH:=mips\nelse ifeq ($(ARCH),mipsel)\n  GO_ARCH:=mipsle\nelse ifeq ($(ARCH),mips64)\n  GO_ARCH:=mips64\nelse ifeq ($(ARCH),mips64el)\n  GO_ARCH:=mips64le\nelse ifeq ($(ARCH),loongarch64)\n  GO_ARCH:=loong64\nelse ifeq ($(ARCH),riscv64)\n  GO_ARCH:=riscv64\nelse ifeq ($(ARCH),riscv32)\n  GO_ARCH:=riscv\nelse ifeq ($(ARCH),powerpc)\n  GO_ARCH:=ppc\nelse ifeq ($(ARCH),powerpc64)\n  GO_ARCH:=ppc64\nelse ifeq ($(ARCH),powerpc64le)\n  GO_ARCH:=ppc64le\nelse\n  GO_ARCH:=$(ARCH)\nendif\n\ndefine Package/$(PKG_NAME)\n  SECTION:=multimedia\n  CATEGORY:=Multimedia\n  TITLE:=TorrServer - stream torrent files\n  URL:=https://github.com/YouROK/TorrServer\n  DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle\nendef\n\ndefine Package/$(PKG_NAME)/description\n  TorrServer is a tool for streaming torrent files.\nendef\n\nGO_HOST:=$(STAGING_DIR_HOSTPKG)/bin/go\nUPX:=$(STAGING_DIR_HOST)/bin/upx\nUPX_AVAILABLE:=$(shell [ -x \"$(UPX)\" ] && echo yes || echo no)\nTARGET_CC:=$(TARGET_CROSS)gcc\nTARGET_CXX:=$(TARGET_CROSS)g++\n\n\nifeq ($(UPX_AVAILABLE),yes)\ndefine upxcompress\n\t@echo \"Compressing $(PKG_NAME) with UPX...\"\n\t$(UPX) --lzma --best $(1)\nendef\nelse\ndefine upxcompress\n\t@echo \"UPX not found at $(UPX)\"\n\t@echo \"Skipping compression of $(PKG_NAME)\"\nendef\nendif\n\ndefine Build/Prepare\n\t$(call Build/Prepare/Default)\n\n\tcd $(PKG_BUILD_DIR) && \\\n\tln -sf server/go.mod go.mod && \\\n\tln -sf server/go.sum go.sum\nendef\n\ndefine Build/Compile\n\t# Build Application WEB Interface\n\tcd $(PKG_BUILD_DIR) && \\\n\texport PATH=$(STAGING_DIR_HOSTPKG)/bin:$$PATH && \\\n\tNODE_OPTIONS=--openssl-legacy-provider \\\n\t$(GO_HOST) run gen_web.go\n\n\t# Prepare Build Application\n\tcd $(PKG_BUILD_DIR)/server && \\\n\t$(GO_HOST) clean -i -r -cache && \\\n\t$(GO_HOST) mod tidy\n\n\t# Build main Application\n\tcd $(PKG_BUILD_DIR)/server && \\\n\tGOOS=linux \\\n\tGOARCH=$(GO_ARCH) \\\n\t$(if $(filter arm,$(ARCH)),GOARM=$(if $(filter bcm53xx kirkwood,$(BOARD)),5,7)) \\\n\t$(if $(filter mips mipsel,$(ARCH)),GOMIPS=softfloat) \\\n\t$(if $(filter mips64 mips64el,$(ARCH)),GOMIPS64=softfloat) \\\n\tCGO_ENABLED=0 \\\n\t$(GO_HOST) build \\\n\t\t-trimpath \\\n\t\t-ldflags=\"-s -w -checklinkname=0 -extldflags=-static\" \\\n\t\t-tags=nosqlite \\\n\t\t-o $(PKG_INSTALL_DIR)/$(PKG_NAME) \\\n\t\t./cmd\n\t$(call upxcompress,$(PKG_INSTALL_DIR)/$(PKG_NAME))\nendef\n\ndefine Package/$(PKG_NAME)/install\t\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_INSTALL_DIR)/$(PKG_NAME) $(1)/usr/bin/\n\t\n\t$(INSTALL_DIR) $(1)/etc/config\n\t$(INSTALL_CONF) ./files/torrserver.config $(1)/etc/config/torrserver\n\t\n\t$(INSTALL_DIR) $(1)/usr/share/torrserver\n\t\n\t$(INSTALL_DIR) $(1)/etc/init.d\n\t$(INSTALL_BIN) ./files/torrserver.init $(1)/etc/init.d/torrserver\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/net/torrserver/files/torrserver.config",
    "content": "config torrserver\n\toption enabled '0'\n\toption port '8090'\n\toption ssl '0'\n\toption ssl_port '8091'\n\toption path '/usr/share/torrserver'\n"
  },
  {
    "path": "packages/net/torrserver/files/torrserver.init",
    "content": "#!/bin/sh /etc/rc.common\n# Simple proc.d init script by koshev-msk\n# <shevlakov@132lan.ru> 2025\n\nUSE_PROCD=1\nSTART=99\nSTOP=01\nNAME=torrserver\nBINDIR=/usr/bin\n\nstart_service() {\n\tconfig_load torrserver\n\tconfig_get enabled \"$CONFIG_SECTION\" enabled\n\tconfig_get port \"$CONFIG_SECTION\" port\n\tconfig_get address \"$CONFIG_SECTION\" address\n\tconfig_get ssl \"$CONFIG_SECTION\" ssl\n\tconfig_get ssl_port \"$CONFIG_SECTION\" ssl_port\n\tconfig_get ssl_key \"$CONFIG_SECTION\" ssl_key\n\tconfig_get ssl_cert \"$CONFIG_SECTION\" ssl_cert\n\tconfig_get path \"$CONFIG_SECTION\" path\n\t\n\t[ \"$enabled\" != \"1\" ] && return 1\n\t[ ! -d \"$path\" ] && mkdir -p $path\n\n\tprocd_open_instance\n\tprocd_set_param command ${BINDIR}/${NAME}\n\n        procd_set_param respawn 300 5 5\n        procd_set_param limits nofile=\"unlimited\"\n\n\t[ $address ] && procd_append_param command -i $address\n\tprocd_append_param command -d \"$path\"\n\tprocd_append_param command -p \"$port\"\n\t\n\t[ \"$ssl\" = \"1\" ] && {\n\t\tprocd_append_param command --ssl\n\t\tprocd_append_param command --sslport \"$ssl_port\"\n\t\t[ -f \"$ssl_key\" ] && procd_append_param command --sslkey \"$ssl_key\"\n\t\t[ -f \"$ssl_cert\" ] && procd_append_param command --sslcert \"$ssl_cert\"\n\t}\n\t\n\tprocd_set_param env GIN_MODE=release\n\tprocd_set_param stdout 1\n\tprocd_set_param pidfile /var/run/${NAME}.pid\n\tprocd_close_instance\n}\n"
  },
  {
    "path": "packages/net/totd/Makefile",
    "content": "#\n# Copyright (C) 2016 Nikil Mehta <nikil.mehta@gmail.com>\n#\n# This is free software, licensed under the GNU General Public License v2.\n# See /LICENSE for more information.\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=totd\nPKG_VERSION:=1.5.3\nPKG_RELEASE:=4\n\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\nPKG_LICENSE:=LICENSE\nPKG_LICENSE_FILES:=COPYING\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL:=https://github.com/fwdillema/totd.git\nPKG_SOURCE_VERSION:=0e395876d9348592c539c74e45745edc81fda0bb\n\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\n\n\nPKG_BUILD_PARALLEL:=1\n\nPKG_FIXUP:=autoreconf\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  SECTION:=net\n  CATEGORY:=Network\n  TITLE:=Trick or Treat Daemon (TOTD)\n  URL:=https://github.com/fwdillema/totd\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tTotd is a small DNS64 proxy nameserver that supports IPv6 only hosts/networks\n\tthat communicate with the IPv4 world using some translation mechanism. \nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin/\n\t$(CP) ./root/* $(1)/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/net/totd/root/etc/config/totd",
    "content": "config totd\n\toption port '10053'\n\tlist forwarder '8.8.8.8#53'\n\tlist prefix '3ffe:abcd:1234:9876::'\n\t\n\t\n"
  },
  {
    "path": "packages/net/totd/root/etc/init.d/totd",
    "content": "#!/bin/sh /etc/rc.common\n\nSTART=85\nSTOP=30\nUSE_PROCD=1\nPROG=/usr/bin/totd\n\nvalidate_totd_section(){\n\tuci_validate_section totd totd \"${1}\" \\\n\t\t'forwarder:list(string)' \\\n\t\t'prefix:list(ipaddr)' \\\n\t\t'port:uinteger' \\\n\t\t'interface:list(string)' \\\n\t\t'lookup:string' \\\n\t\t'stf_enable:bool'\n}\n\ntotd(){\n\tconfig_load 'totd'\n\tlocal forwarder prefix port interface lookup stf_enable\n\tvalidate_totd_section \"$1\" || {\n\t\tlogger -t \"totd\" \"validation failed\"\n\t\treturn 1\n\t}\n\n\ttest -d /var/etc/ || mkdir -p /var/etc\n\n        {\n                [ -n \"$forwarder\" ] && {\n\t\t\tfor fa in $forwarder; do\n\t\t\t\techo \"$fa\" | awk -F [#] '{print \"forwarder \"$1\" port \"$2}'\n\t\t\tdone\n\t\t}\n                [ -n \"$prefix\" ] && {\n\t\t\tfor pr in $prefix; do\n\t\t\t\techo \"prefix $pr\"\n\t\t\tdone\n\t\t}\n                [ -n \"$port\" ] && echo \"port $port\"\n                [ -n \"$interface\" ] && echo \"interfaces $interface\"\n                [ -n \"$lookup\" ] && echo \"$lookup reverse lookup\"\n                [ -n \"$stf_enable\" -a \"$stf_enable\" = \"1\" ] && echo \"stf\"\n        } > /var/etc/totd.conf\n\tprocd_open_instance\n        procd_append_param command $PROG -c /var/etc/totd.conf\n        procd_set_param stdout 1\n        procd_set_param stderr 1\n        procd_set_param respawn\n        procd_close_instance\n}\n\nstart_service(){\n\tconfig_load 'totd'\n\tconfig_foreach totd \"totd\"\n}\n\nstop_service(){\n\tkillall $PROG > /dev/null 2>&1\n}\n"
  },
  {
    "path": "packages/net/tun2socks/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=tun2socks\nPKG_VERSION:=2.6.0\nPKG_RELEASE:=1\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL:=https://github.com/xjasonlyu/tun2socks.git\nPKG_SOURCE_VERSION:=4127937ea7c450a5230b273f406c9410acec2be7\n\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlako@132lan.ru>\nPKG_LICENSE:=GPL-3.0\nPKG_LICENSE_FILES:=LICENSE\n\nPKG_BUILD_DEPENDS:=golang/host\nPKG_BUILD_PARALLEL:=1\nPKG_BUILD_FLAGS:=no-mips16\n\nGO_PKG=github.com/xjasonlyu/tun2socks\n\ninclude $(INCLUDE_DIR)/package.mk\ninclude $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk\n\nGO_MOD_ARGS:=\nGO_PKG_BUILD_VARS+= GO111MODULE=on\n\ndefine Package/$(PKG_NAME)\n  SECTION:=net\n  CATEGORY:=Network\n  TITLE:=tun to socks proxy redirector written on golang\n  URL:=https://github.com/xjasonlyu/tun2socks\n  DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle \\\n\t   +kmod-tun +ip-full +coreutils-base64\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))\n\t$(INSTALL_DIR) $(1)/usr/sbin\n\t$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/$(PKG_NAME) $(1)/usr/sbin/\n\t$(CP) ./root/* $(1)/\nendef\n\n$(eval $(call GoBinPackage,$(PKG_NAME)))\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/net/tun2socks/README.md",
    "content": "# OpenWrt netifd protocol handler for tun2socks go application\n\n# How-to configure tun2socks interface\nThe config stored in /etc/config/network. Example configuration:\n\n```\nconfig interface 't2s'\n\toption proto 't2s'\n\toption ipaddr '10.0.0.10'\n\toption netmask '255.255.0.0'\n\toption gateway '10.0.0.1' # optional\n\toption proxy 'socks5' # type proxy\n\toption host 'my.socks5.proxy:port' # upstream proxy-server\n\toption username 'username' # username \n\toption password 'password' # password\n\n```\n\n# How-to configure interface via LuCi\nbuild and install package luci-proto-tun2socks\n\n"
  },
  {
    "path": "packages/net/tun2socks/root/lib/netifd/proto/t2s.sh",
    "content": "#!/bin/sh\n\n. /lib/functions.sh\n. /lib/functions/network.sh\n. ../netifd-proto.sh\ninit_proto \"$@\"\n\nproto_t2s_init_config(){\n\tno_device=1\n\tavailable=1\n\tproto_config_add_string \"network\"\n\tproto_config_add_int \"ip_manual\"\n\tproto_config_add_string \"ipaddr\"\n\tproto_config_add_string \"netmask\"\n\tproto_config_add_string \"gateway\"\n\tproto_config_add_string \"host\"\n\tproto_config_add_string \"proxy\"\n\tproto_config_add_string \"encrypt\"\n\tproto_config_add_string \"loglevel\"\n\tproto_config_add_string \"obfs_host\"\n\tproto_config_add_string \"username\"\n\tproto_config_add_string \"password\"\n\tproto_config_add_string \"opts\"\n\tproto_config_add_string \"sockpath\"\n\tproto_config_add_int \"mtu\"\n\tproto_config_add_int \"fwmark\"\n\tproto_config_add_boolean \"socket\"\n\tproto_config_add_boolean \"base64enc\"\n\tproto_config_add_defaults\n}\n\ncheck_encrypt(){\n\tcase $encrypt in\n\t\t\"none\"|\\\n\t\t\"table\"|\\\n\t\t\"rc4\"|\\\n\t\t\"rc4-md5\"|\\\n\t\t\"aes-128-cfb\"|\\\n\t\t\"aes-192-cfb\"|\\\n\t\t\"aes-256-cfb\"|\\\n\t\t\"aes-128-ctr\"|\\\n\t\t\"aes-192-ctr\"|\\\n\t\t\"aes-256-ctr\"|\\\n\t\t\"aes-128-gcm\"|\\\n\t\t\"aes-192-gcm\"|\\\n\t\t\"aes-256-gcm\"|\\\n\t\t\"camellia-128-cfb\"|\\\n\t\t\"camellia-192-cfb\"|\\\n\t\t\"camellia-256-cfb\"|\\\n\t\t\"bf-cfb\"|\\\n\t\t\"salsa20\"|\\\n\t\t\"chacha20\"|\\\n\t\t\"chacha20-ietf\"|\\\n\t\t\"chacha20-ietf-poly1305\"|\\\n\t\t\"xchacha20-ietf-poly1305\")\n\t\t\t\tcontinue\n\t\t;;\n\t\t*)\n\t\t\tproto_notify_error \"$interface\" WRONG_ENCRYPT_METHOD\n\t\t\tproto_set_available \"$interface\" 0\n\t\t;;\n\tesac\n}\n\ngetaddr() {\n\tlocal network=\"${1:-10.0.0.0/8}\"\n\n\tawk -v network=\"$network\" '\n\tBEGIN {\n\t\tsrand()\n\t\tsplit(network, parts, \"/\")\n\t\tnetwork_ip = parts[1]\n\t\tprefix = parts[2] == \"\" ? 24 : int(parts[2])\n\n\t\tsplit(network_ip, octets, \".\")\n\t\to1 = int(octets[1])\n\t\to2 = int(octets[2])\n\t\to3 = int(octets[3])\n\t\to4 = int(octets[4])\n\n\t\tif (prefix <= 8) {\n\t\t\tr2 = int(rand() * 256)\n\t\t} else if (prefix <= 16) {\n\t\t\tbits = prefix - 8\n\t\t\tmax = bits > 0 ? 2^(8 - bits) - 1 : 0\n\t\t\tr2 = o2 + int(rand() * (max + 1))\n\t\t} else {\n\t\t\tr2 = o2\n\t\t}\n\n\t\tif (prefix <= 16) {\n\t\t\tr3 = int(rand() * 256)\n\t\t} else if (prefix <= 24) {\n\t\t\tbits = prefix - 16\n\t\t\tmax = bits > 0 ? 2^(8 - bits) - 1 : 0\n\t\t\tr3 = o3 + int(rand() * (max + 1))\n\t\t} else {\n\t\t\tr3 = o3\n\t\t}\n\n\t\tif (prefix <= 24) {\n\t\t\tbase4 = int(rand() * 64) * 4\n\t\t} else {\n\t\t\tbits = prefix - 24\n\t\t\tmax = bits > 0 ? 2^(8 - bits) - 1 : 0\n\t\t\tbase4 = o4 + (int(rand() * (max + 1)) * 4)\n\t\t\tbase4 = and(base4, 252)\n\t\t\tif (base4 > 252) base4 = 252\n\t\t}\n\n\t\tnet_ip = o1 \".\" r2 \".\" r3 \".\" base4\n\t\tgw_ip = o1 \".\" r2 \".\" r3 \".\" (base4 + 1)\n\t\tdev_ip = o1 \".\" r2 \".\" r3 \".\" (base4 + 2)\n\n\t\tprint \"GENIPADDR=\" dev_ip\n\t\tprint \"GENIPMASK=255.255.255.252\"\n\t\tprint \"GENIPNET=\" net_ip \"/30\"\n\t\tprint \"GENIPGW=\" gw_ip\n\t}\n\t'\n}\n\n\nproto_t2s_setup(){\n\tlocal interface=\"$1\"\n\tlocal network ifname ipaddr netmask gateway host proxy encrypt loglevel fwmark \n\tlocal ip_manual base64enc socket obfs_host port mtu\n\tlocal username password opts sockpath $PROTO_DEFAULT_OPTIONS\n\tjson_get_vars network ifname ipaddr netmask gateway host proxy encrypt loglevel fwmark\n\tjson_get_vars ip_manual base64enc socket obfs_host port mtu\n\tjson_get_vars username password opts sockpath $PROTO_DEFAULT_OPTIONS\n\tifname=$interface\n\t[ \"$metric\" = \"\" ] && metric=\"0\"\n\t[ \"$proxy\" = \"\" ] && proxy=socks5\n\t[ \"$loglevel\" = \"\" ] && loglevel=error\n\t[ \"$host\" ] && {\n\t\tcase \"$proxy\" in\n\t\t\thttp) ARGS=\"-proxy ${proxy}://${host}\" ;;\n\t\t\tsocks4)\n\t\t\t\t[ \"$username\" ] && {\n\t\t\t\t\tARGS=\"-proxy ${proxy}://${username}@${host}\"\n\t\t\t\t} || {\n\t\t\t\t\tARGS=\"-proxy ${proxy}://${host}\"\n\t\t\t\t}\n\t\t\t;;\n\t\t\tsocks5)\n\t\t\t\t[ \"$username\" -a \"$password\" ] && {\n\t\t\t\t\tARGS=\"-proxy ${proxy}://${username}:${password}@${host}\"\n\t\t\t\t} || {\n\t\t\t\t\tARGS=\"-proxy ${proxy}://${host}\"\n\t\t\t\t}\n\t\t\t;;\n\t\t\tss)\n\t\t\t\t#check_encrypt\n\t\t\t\t[ \"$encrypt\" -a \"$password\" ] && {\n\t\t\t\t\t[ \"$base64enc\" = \"1\" ] && {\n\t\t\t\t\t\tbase64gen=$(echo ${encrypt}:${password} | base64)\n\t\t\t\t\t\t[ \"$obfs_host\" ] && {\n\t\t\t\t\t\t\tARGS=\"-proxy ${proxy}://${base64gen}@${host}/\\<\\?obfs=http\\;obfs-host=$obfs_host\\>\"\n\t\t\t\t\t\t} || {\n\t\t\t\t\t\t\tARGS=\"-proxy ${proxy}://${base64gen}@${host}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t} || {\n\t\t\t\t\t\t[ \"$obfs_host\" ] && {\n\t\t\t\t\t\t\tARGS=\"-proxy ${proxy}://${encrypt}:${password}@${host}/\\<\\?obfs=http\\;obfs-host=$obfs_host\\>\"\n\t\t\t\t\t\t} || {\n\t\t\t\t\t\t\tARGS=\"-proxy ${proxy}://${encrypt}:${password}@${host}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} || {\n\t\t\t\t\tproto_notify_error \"$interface\" CONFIGURE_FAILED\n\t\t\t\t\tproto_set_available \"$interface\" 0\n\t\t\t\t}\n\t\t\t;;\n\t\t\trelay)\n\t\t\t\t[ \"$username\" -a \"$password\" ] && {\n\t\t\t\t\tARGS=\"-proxy ${proxy}://${encrypt}:${password}@${host}/\\<nodelay=false\\>\"\n\t\t\t\t} || {\n\t\t\t\t\tARGS=\"-proxy ${proxy}://${host}/\\<nodelay=false\\>\"\n\t\t\t\t}\n\t\t\t;;\n\t\tesac\n\t}\n\n\tcase $proxy in\n\t\tdirect|reject) ARGS=\"-proxy ${proxy}://\" ;;\n\t\tsocks5)\n\t\t\t[ \"$socket\" ] &&  {\n\t\t\t\t[ \"$sockpath\" ] && {\n\t\t\t\t\tARGS=\"-proxy ${proxy}://${sockpath}\"\n\t\t\t\t} || {\n\t\t\t\t\tproto_notify_error \"$interface\" CONFIGURE_FAILED\n\t\t\t\t\tproto_set_available \"$interface\" 0\n\t\t\t\t}\n\t\t\t}\n\t\t;;\n\tesac\n\n\t[ \"x${ARGS}\" = \"x\" ] && {\n\t\tproto_notify_error \"$interface\" CONFIGURE_FAILED\n\t\tproto_set_available \"$interface\" 0\n\t}\n\n\t[ \"$loglevel\" ] && ARGS=\"$ARGS -loglevel $loglevel\"\n\t[ \"$fwmark\" ] && ARGS=\"$ARGS -fwmark $fwmark\"\n\t[ \"$mtu\" ] && ARGS=\"$ARGS -mtu $mtu\"\n\t[ \"$opts\" ] && ARGS=\"$ARGS $opts\"\n\t! [ \"$ip_manual\" = \"1\" ] && {\n\t\teval $(getaddr $network)\n\t\tipaddr=$GENIPADDR\n\t\tnetmask=$GENIPMASK\n\t\tgateway=$GENIPGW\n\t\techo \"Assign $ipaddr mask $netmask gw $gateway\"\n\t}\n\n\tproto_init_update \"$interface\" 1\n\tproto_add_data\n\tproto_close_data\n\tip tuntap add mode tun dev $interface\n\tproto_set_keep 1\n\tproto_add_ipv4_address $ipaddr $netmask\n\t[ $gateway ] && {\n\t\tproto_add_ipv4_route \"0.0.0.0\" 0 $gateway $ipaddr\n\t}\n\tproto_add_data\n\tproto_close_data\n\tproto_send_update \"$interface\"\n\tproto_run_command \"$interface\" /usr/sbin/tun2socks \\\n\t\t-device \"$interface\" $ARGS\n}\n\nproto_t2s_teardown(){\n\tlocal interface=\"$1\"\n\tproto_kill_command \"$interface\"\n\tip tuntap del mode tun dev $interface\n}\n\n[ -n \"$INCLUDE_ONLY\" ] || add_protocol t2s\n"
  },
  {
    "path": "packages/net/xmm-modem/LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "packages/net/xmm-modem/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=xmm-modem\nPKG_VERSION:=0.1.6\nPKG_RELEASE:=1\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  PKGARCH:=all\n  DEPENDS:=+comgt +kmod-usb-acm +kmod-usb-serial-option \\\n\t+kmod-usb-net-cdc-ncm +kmod-usb-net-rndis\n  TITLE:=Fibocom L850/L860/FM350 protocol\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tSimple connect to cellular for Fibocom L850/L860/FM350 module.\n\tIntel XMM LTE-A 7360/7560 and MediaTek T700 chipsets\nendef\n\n\ndefine Build/Prepare\nendef\n\ndefine Build/Configure\nendef\n\ndefine Build/Compile\nendef\n\n\ndefine Package/$(PKG_NAME)/install\n\t$(CP) ./root/* $(1)/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/net/xmm-modem/README.md",
    "content": "# OpenWrt netifd scripts to configure connection Fibocom L860-GL/FM350-GL\nIntel XMM 7360/7650 LTE-A Pro and MediaTek T700 modems\n\n# How-to configure cellular connection\nThe config stored in /etc/config/network. Example configuration:\n```\nconfig interface 'wwan'\n\toption device '/dev/ttyACM0'\t# Device serial port\n\toption proto 'xmm'\n\toption pdp 'ip'\t\t\t# Connect IPV4/6 version (ip, ipv4v6, ipv6)\n\toption profile '0'\t\t# PDP context. (default: 1)\n\toption apn 'internet'\t\t# APN Cellular\n\toption delay '10'\t\t# Delay interface to connect.\n\toptiom auth 'auto'\t\t# Auth type (auto, pap or chap)\n\toption username 'username'\t# username \n\toption password 'password'\t# password\n```\n\n# How-to configure interface via LuCi\nbuild and install package luci-proto-xmm\n\n"
  },
  {
    "path": "packages/net/xmm-modem/root/etc/gcom/fm350-auth.gcom",
    "content": "opengt\n set com 115200n81\n set comecho off\n set senddelay 0.02\n waitquiet 0.2 0.2\n\n send \"AT+CGAUTH=\"\n send $env(\"CID\")\n send \",\"\n send $env(\"AUTH\")\n send \",\\\"\"\n send $env(\"USER\")\n send \"\\\",\\\"\"\n send $env(\"PASS\")\n send \"\\\"^m\"\n\n"
  },
  {
    "path": "packages/net/xmm-modem/root/etc/gcom/fm350-config.gcom",
    "content": "opengt\n set com 115200n81\n set comecho off\n set senddelay 0.02\n waitquiet 0.2 0.2\n\n send \"AT+CGPADDR=\"\n send $env(\"CID\")\n send \"^m\"\n get 1 \"\" $s\n print $s\n\n send \"AT+GTDNS=\"\n send $env(\"CID\")\n send \"^m\"\n get 1 \"\" $s\n print $s\n"
  },
  {
    "path": "packages/net/xmm-modem/root/etc/gcom/fm350-connect.gcom",
    "content": "opengt\n set com 115200n81\n set comecho off\n set senddelay 0.02\n waitquiet 0.2 0.2\n\n send \"AT+CMEE=2^m\"\n send \"AT+COPS=0^m\"\n send \"AT+CGPIAF=1,0,0,0^m\"\n send \"AT+CGDCONT=\"\n send $env(\"CID\")\n send \"^m\"\n send \"AT+CGDCONT=\"\n send $env(\"CID\")\n send \",\\\"\"\n send $env(\"PDP\")\n send \"\\\",\\\"\"\n send $env(\"APN\")\n send \"\\\"^m\"\n send \"AT+CGACT=1,\"\n send $env(\"CID\")\n send \"^m\"\n send \"AT+CGPADDR=\"\n send $env(\"CID\")\n send \"^m\"\n\n get 1 \"\" $s\n print $s\n"
  },
  {
    "path": "packages/net/xmm-modem/root/etc/gcom/probeport.gcom",
    "content": "let $d = $env(\"DEVPORT\")\nif $d = \"\" goto timeout\n\nopen com $d\nset com 57600n81\nset senddelay 0.05\nwaitquiet 2 0.5\nsend \"AT^m\"\nwaitfor 2 \"OK\"\nif % = -1 goto timeout\nif % = 0 goto continue\nexit 0\n\n:continue\nsend \"AT+CPIN?^m\"\nwaitfor 2 \"+CME ERROR:\"\nif % = 0 goto simerr\nexit 0\n\n:timeout\n  exit 1\n\n:simerr\n  exit 2\n\n"
  },
  {
    "path": "packages/net/xmm-modem/root/etc/gcom/xmm-auth.gcom",
    "content": "opengt\n set com 115200n81\n set comecho off\n set senddelay 0.02\n waitquiet 0.2 0.2\n\n send \"AT+XGAUTH=\"\n send $env(\"CID\")\n send \",\"\n send $env(\"AUTH\")\n send \",\\\"\"\n send $env(\"USER\")\n send \"\\\",\\\"\"\n send $env(\"PASS\")\n send \"\\\"^m\"\n"
  },
  {
    "path": "packages/net/xmm-modem/root/etc/gcom/xmm-config.gcom",
    "content": "opengt\n set com 115200n81\n set comecho off\n set senddelay 0.02\n waitquiet 0.2 0.2\n\n send \"AT+CGPADDR=\"\n send $env(\"CID\")\n send \"^m\"\n get 1 \"\" $s\n print $s\n\n send \"AT+XDNS?^m\"\n get 1 \"\" $s\n print $s\n"
  },
  {
    "path": "packages/net/xmm-modem/root/etc/gcom/xmm-connect.gcom",
    "content": "opengt\n set com 115200n81\n set comecho off\n set senddelay 0.02\n waitquiet 0.2 0.2\n\n send \"AT+CMEE=2^m\"\n send \"AT+COPS=0^m\"\n send \"AT+CGPIAF=1,0,0,0^m\"\n send \"AT+XDNS=\"\n send $env(\"CID\")\n send \",1^m\"\n send \"AT+CGDCONT=\"\n send $env(\"CID\")\n send \"^m\"\n send \"AT+CGDCONT=\"\n send $env(\"CID\")\n send \",\\\"\"\n send $env(\"PDP\")\n send \"\\\",\\\"\"\n send $env(\"APN\")\n send \"\\\"^m\"\n send \"AT+CGACT=1,\"\n send $env(\"CID\")\n send \"^m\"\n send \"AT+CGPADDR=\"\n send $env(\"CID\")\n send \"^m\"\n\n send \"AT+XDATACHANNEL=1,1,\\\"/USBCDC/0\\\",\\\"/USBHS/NCM/0\\\",2,\"\n send $env(\"CID\")\n send \"^m\"\n send \"AT+CGDATA=\\\"M-RAW_IP\\\",\"\n send $env(\"CID\")\n send \"^m\"\n get 1 \"\" $s\n print $s\n"
  },
  {
    "path": "packages/net/xmm-modem/root/etc/gcom/xmm-disconnect.gcom",
    "content": "opengt\n set com 115200n81\n set comecho off\n set senddelay 0.02\n waitquiet 0.2 0.2\n\n send \"AT+CGACT=0,\"\n send $env(\"CID\")\n send \"^m\"\n send \"AT+XDATACHANNEL=0^m\"\n"
  },
  {
    "path": "packages/net/xmm-modem/root/etc/hotplug.d/usb/01_xmm.sh",
    "content": "#!/bin/sh\n. /lib/functions.sh\n. /lib/netifd/netifd-proto.sh\n\n\nfind_xmm_iface() {\n\tlocal cfg=\"$1\"\n\tlocal proto section\n\n\tconfig_get proto \"$cfg\" proto\n\t[ \"$proto\" = xmm ] || return 0\n\n\tlocal dev=$(uci_get network \"$cfg\" device)\n\tlocal devname=$(basename $dev)\n\n\tif ! [ -e \"/sys/${DEVPATH}\" ]; then\n\t\tif ! [ -e \"/sys/class/tty/${devname}\" ]; then\n\t\t\tif [ \"$ACTION\" = remove ]; then\n\t\t\t\tproto_set_available \"$cfg\" 0\n\t\t\tfi\n\t\tfi\n\tfi\n\tif [ -e \"/sys/${DEVPATH}\" ]; then\n\t\tif [ -e \"/sys/class/tty/${devname}\" ]; then\n\t\t\tif [ \"$ACTION\" = add ]; then\n\t\t\t\tproto_set_available \"$cfg\" 1\n\t\t\tfi\n\t\tfi\n\tfi\n}\n\n[ \"$ACTION\" = add ] || [ \"$ACTION\" = remove ] || exit 0\n\nconfig_load network\nconfig_foreach find_xmm_iface interface\n"
  },
  {
    "path": "packages/net/xmm-modem/root/lib/netifd/proto/xmm.sh",
    "content": "#!/bin/sh\n\n# protocol handler for INTEL-like modem\n# XMM7530 XMM7650 T700 chipsents\n# by Konstatntine Shevlakov 2024-2025 <shevlakov@132lan.ru>\n\n. /lib/functions.sh\n. /lib/functions/network.sh\n. ../netifd-proto.sh\ninit_proto \"$@\"\n\n\nvalid_ip4(){\n\t/bin/ipcalc.sh \"${1}/${ip4mask}\" > /dev/null 2>&1\n}\n\n\nproto_xmm_init_config() {\n\tno_device=1\n\tavailable=1\n\tproto_config_add_string \"device:device\"\n\tproto_config_add_string \"apn\"\n\tproto_config_add_string \"pdp\"\n\tproto_config_add_int \"delay\"\n\tproto_config_add_string \"pincode\"\n\tproto_config_add_string \"username\"\n\tproto_config_add_string \"password\"\n\tproto_config_add_string \"auth\"\n\tproto_config_add_int \"profile\"\n\tproto_config_add_int \"maxfail\"\n\tproto_config_add_defaults\n}\n\nproto_xmm_setup() {\n\tlocal interface=\"$1\"\n\tlocal devname devpath hwaddr ip4addr ip4mask dns1 dns2 defroute lladdr\n\tlocal name ifname proto extendprefix auth username password\n\tlocal device ifname auth username password apn pdp profile maxfail pincode delay $PROTO_DEFAULT_OPTIONS\n\tjson_get_vars device ifname auth username password apn pdp profile maxfail pincode delay $PROTO_DEFAULT_OPTIONS\n\n\t[ \"$profile\" = \"\" ] && profile=\"1\"\n\t[ \"$metric\" = \"\" ] && metric=\"0\"\n\t[ \"$delay\" = \"\" ] && delay=\"5\"\n\t[ \"$maxfail\" = \"\" ] && maxfail=\"5\"\n\tsleep $delay\n\t[ -z $ifname ] && {\n\t\tdevname=$(basename $device)\n\t\tdevpath=\"$(readlink -f /sys/class/tty/$devname/device)\"\n\t\tcase \"$devname\" in\n\t\t\t*ttyACM*|*ttyUSB*)\n\t\t\t\tif [ -r $(readlink -f /sys/class/tty/$devname/device/../idVendor) ]; then\n\t\t\t\t\tVID=$(cat $(readlink -f /sys/class/tty/$devname/device/../idVendor))\n\t\t\t\t\tPID=$(cat $(readlink -f /sys/class/tty/$devname/device/../idProduct))\n\t\t\t\telse\n\t\t\t\t\tVID=$(cat $(readlink -f /sys/class/tty/$devname/device/../../idVendor))\n\t\t\t\t\tPID=$(cat $(readlink -f /sys/class/tty/$devname/device/../../idProduct))\n\t\t\t\tfi\n\t\t\t\tVIDPID=$VID$PID\n\t\t\t\tcase $VIDPID in\n\t\t\t\t\t8087095a)\n\t\t\t\t\t\tPREFIX=\"xmm\"\n\t\t\t\t\t\thwaddr=\"$(ls -1 $devpath/../*/net/*/*address*)\"\n\t\t\t\t\t\tXMMDNS=\"XDNS\"\n\t\t\t\t\t;;\n\t\t\t\t\t0e8d7126|0e8d7127)\n\t\t\t\t\t\tPREFIX=\"fm350\"\n\t\t\t\t\t\thwaddr=\"$(ls -1 $devpath/../../*/net/*/*address*)\"\n\t\t\t\t\t\tXMMDNS=\"GTDNS\"\n\t\t\t\t\t;;\n\t\t\t\t\t*)\n\t\t\t\t\t\techo \"Modem not supported!\"\n\t\t\t\t\t\tproto_notify_error \"$interface\" NO_DEVICE_SUPPORT\n\t\t\t\t\t\tproto_set_available \"$interface\" 0\n\t\t\t\t\t\treturn 1\n\t\t\t\t\t;;\n\t\t\t\tesac\n\t\t\t;;\n\t\t\t*)\n\t\t\t\techo \"AT port not valid!\"\n\t\t\t\tproto_notify_error \"$interface\" NO_PORT_FOUND\n\t\t\t;;\n\t\tesac\n\t\techo \"Setup $PREFIX interface $interface with port ${device}\"\n\n\t\t[ \"${devpath}x\" != \"x\" ] && {\n\t\t\techo \"Found path $devpath\"\n\t\t\tfor h in $hwaddr; do\n\t\t\t\tif [ \"$(cat ${h})\" = \"00:00:11:12:13:14\" ]; then\n\t\t\t\t\tifname=$(echo ${h} | awk -F [\\/] '{print $(NF-1)}')\n\t\t\t\tfi\n\t\t\tdone\n\t\t} || {\n\t\t\techo \"Device path not found!\"\n\t\t\tproto_notify_error \"$interface\" NO_DEVICE_FOUND\n\t\t\treturn 1\n\t\t}\n\t}\n\n\t[ -n \"$ifname\" ] && {\n\t\techo \"Found interface $ifname\"\n\t} || {\n\t\techo \"The interface could not be found.\"\n\t\tproto_notify_error \"$interface\" NO_IFACE\n\t\tproto_set_available \"$interface\" 0\n\t\treturn 1\n\t}\n\n\t# probes for AT port and SIM-card\n\tfor p in $(seq 1 $maxfail); do\n\t\tDEVPORT=$device gcom -s /etc/gcom/probeport.gcom\n\t\tDEVERR=$?\n\t\t[ \"$DEVERR\" = \"0\" ] && break\n\t\tif [ \"$p\" -eq \"$maxfail\" ]; then\n\t\t\tcase $DEVERR in\n\t\t\t\t1)\n\t\t\t\t\techo \"AT port not answer!\"\n\t\t\t\t\tproto_notify_error \"$interface\" NO_PORT_ANSWER\n\t\t\t\t\tproto_set_available \"$interface\" 0\n\t\t\t\t\treturn 1\n\t\t\t\t;;\n\t\t\t\t2)\n\t\t\t\t\techo \"SIM-card not insert!\"\n\t\t\t\t\tproto_notify_error \"$interface\" NO_SIM_CARD\n\t\t\t\t\tproto_set_available \"$interface\" 0\n\t\t\t\t\treturn 1\n\t\t\t\t;;\n\t\t\tesac\n\t\tfi\n\t\n\t\tsleep 3\n\tdone\n\n\tif [ -n \"$pincode\" ]; then\n\t\tPINCODE=\"$pincode\" gcom -d \"$device\" -s /etc/gcom/setpin.gcom || {\n\t\t\tproto_notify_error \"$interface\" PIN_FAILED\n\t\t\tproto_block_restart \"$interface\"\n\t\t\treturn 1\n\t\t}\n\tfi\n\n\tpdp=$(echo $pdp | awk '{print toupper($0)}')\n\t[ \"$pdp\" = \"IP\" -o \"$pdp\" = \"IPV6\" -o \"$pdp\" = \"IPV4V6\" ] || pdp=\"IP\"\n\techo \"Setting up $ifname\"\n\t[ -n \"$username\" ] && [ -n \"$password\" ] && {\n\t\techo \"Using auth type is: $auth\"\n\t\tcase $auth in\n\t\t\tpap) AUTH=1 ;;\n\t\t\tchap) AUTH=2 ;;\n\t\t\t*) AUTH=0 ;;\n\t\tesac\n\t\tCID=$profile AUTH=$AUTH USER=\"$username\" PASS=\"$password\" gcom -d \"$device\" -s /etc/gcom/${PREFIX}-auth.gcom >/dev/null 2>&1\n\t}\n\n\tCID=$profile APN=$apn PDP=$pdp  gcom -d $device -s /etc/gcom/${PREFIX}-connect.gcom >/dev/null 2>&1\n\tproto_init_update \"$ifname\" 1\n\tproto_add_data\n\tproto_close_data\n\tDATA=$(CID=$profile gcom -d $device -s /etc/gcom/${PREFIX}-config.gcom)\n\tip4addr=$(echo \"$DATA\" | awk -F [,] '/^\\+CGPADDR/{gsub(\"\\r|\\\"\", \"\"); print $2}') >/dev/null 2>&1\n\tlladdr=$(echo \"$DATA\" | awk -F [,] '/^\\+CGPADDR/{gsub(\"\\r|\\\"\", \"\"); print $3}') >/dev/null 2>&1\n\tns=$(echo \"$DATA\" | awk -F [,] '/^\\+'$XMMDNS': /{gsub(\"\\r|\\\"\",\"\"); gsub(\"0.0.0.0\",\"\"); print $2\" \"$3}' | sed 's/^[[:space:]]//g' | uniq)\n\n\tcase $ip4addr in\n\t\t*FE80*)\n\t\t\tlladdr=$ip4addr\n\t\t\tip4addr=\"\"\n\t\t;;\n\t\t*)\n\t\t\tip4mask=24\n\t\t\tdefroute=$(echo $ip4addr | awk -F [.] '{print $1\".\"$2\".\"$3\".1\"}')\n\t\t;;\n\tesac\n\n\tfor n in $(echo $ns); do\n\t\t$(valid_ip4 $n) && {\n\t\t\t[ ! \"$(echo $dns1 | grep $n)\" ] && {\n\t\t\t\tdns1=\"$dns1 $n\"\n\t\t\t}\n\t\t}\n\tdone\n\n\tproto_set_keep 1\n\tip link set dev $ifname arp off\n\techo \"PDP type is: $pdp\"\n\t[ \"$pdp\" = \"IP\" -o \"$pdp\" = \"IPV4V6\" ] && {\n\t\t$(valid_ip4 $ip4addr) && [ \"$ip4addr\" != \"0.0.0.0\" ] && {\n\t\t\techo \"Set IPv4 address: ${ip4addr}/${ip4mask}\"\n\t\t\tproto_add_ipv4_address $ip4addr $ip4mask\n\t\t\tproto_add_ipv4_route \"0.0.0.0\" 0 $defroute $ip4addr\n\t\t} || {\n\t\t\techo \"Failed to configure interface\"\n\t\t\tproto_notify_error \"$interface\" CONFIGURE_FAILED\n\t\t\treturn 1\n\t\t}\n\t\t[ -n \"$dns1\" ] && {\n\t\t\tproto_add_dns_server \"$dns1\"\n\t\t\techo \"Using IPv4 DNS: $dns1\"\n\t\t}\n\t}\n\n\tproto_add_data\n\tproto_close_data\n\tproto_send_update \"$interface\"\n\n\t[ \"$pdp\" = \"IPV6\" -o \"$pdp\" = \"IPV4V6\" ] && {\n\t\tjson_init\n\t\tjson_add_string name \"${interface}_6\"\n\t\tjson_add_string ifname \"@$interface\"\n\t\tjson_add_string proto \"dhcpv6\"\n\t\tjson_add_string extendprefix 1\n\t\tproto_add_dynamic_defaults\n\t\tjson_close_object\n\t\tubus call network add_dynamic \"$(json_dump)\"\n\t}\n}\n\nproto_xmm_teardown() {\n\tlocal interface=\"$1\"\n\tlocal device profile\n\tjson_get_vars device profile\n\t[ \"$profile\" = \"\" ] && profile=\"1\"\n\tCID=$profile gcom -d $device -s /etc/gcom/xmm-disconnect.gcom >/dev/null 2>&1\n\techo \"Modem $device disconnected\"\n\tproto_kill_command \"$interface\"\n}\n\n[ -n \"$INCLUDE_ONLY\" ] || add_protocol xmm\n\n\n"
  },
  {
    "path": "packages/net/xt-tls/Makefile",
    "content": "include $(TOPDIR)/rules.mk\ninclude $(INCLUDE_DIR)/kernel.mk\n\n# Name, version and release number\n# The name and version of your package are used to define the variable to point to the build directory of your package: $(PKG_BUILD_DIR)\nPKG_NAME:=xt_tls\nPKG_VERSION:=0.3.5\nPKG_RELEASE:=1\n\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\nPKG_SOURCE_URL:=https://github.com/Lochnair/xt_tls.git\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_SUBDIR=$(PKG_NAME)-$(PKG_VERSION)\nPKG_SOURCE_VERSION:=692e50651ea024dc6ceddc764ae3391e93a99b67\n\nPKG_MAINTAINER:=Nils Andreas Svee <me@lochnair.net>\nPKG_LICENSE=GPL-3.0\nPKG_LICENSE_FILES=LICENSE\n\ninclude $(INCLUDE_DIR)/package.mk\n\n# Package definition; instructs on how and where our package will appear in the overall configuration menu ('make menuconfig')\ndefine Package/iptables-mod-tls\n\tSECTION:=net\n\tCATEGORY:=Network\n\tTITLE:=iptables library for filtering TLS connections\n\tDEPENDS:=libxtables\nendef\n\n# Package description; a more verbose description on what our package does\ndefine Package/iptables-mod-tls/description\n\tiptables module for filtering TLS connections\nendef\n\ndefine KernelPackage/ipt-tls\n\tSUBMENU:=Other modules\n\tTITLE:=iptables module for filtering TLS connections\n\tFILES:=$(PKG_BUILD_DIR)/src/xt_tls.ko\n\tDEPENDS:=kmod-nf-ipt\nendef\n\ndefine KernelPackage/ipt-tls/description\n\tA iptables module for filtering TLS connections.\nendef\n\nMAKE_FLAGS += \\\n\t      KDIR=$(LINUX_DIR) \\\n\t      ARCH=$(LINUX_KARCH) \\\n\t      M=\"$(PKG_BUILD_DIR)/src\"\n\n# Package install instructions; create a directory inside the package to hold our executable, and then copy the executable we built previously into the folder\ndefine Package/iptables-mod-tls/install\n\t$(INSTALL_DIR) $(1)/usr/lib/iptables\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/ipt/libxt_tls.so $(1)/usr/lib/iptables\nendef\n\n# This command is always the last, it uses the definitions and variables we give above in order to get the job done\n$(eval $(call BuildPackage,iptables-mod-tls))\n$(eval $(call KernelPackage,ipt-tls))\n"
  },
  {
    "path": "packages/net/xtables-wgobfs/Makefile",
    "content": "#\n# Copyright (C) 2022 Wei Chen <weichen302@gmail.com>\n#\n# This is free software, licensed under the GNU General Public License v2.\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=xtables-wgobfs\nPKG_VERSION:=0.4.1\nPKG_RELEASE:=1\n\nPKG_BUILD_DIR:=$(BUILD_DIR)/xt_wgobfs-$(PKG_VERSION)\nPKG_SOURCE:=xt_wgobfs-$(PKG_VERSION).tar.xz\nPKG_SOURCE_URL:= https://github.com/infinet/xt_wgobfs/releases/download/v$(PKG_VERSION)/\nPKG_HASH:=5ea0e36ac96816e558132fdaa49746af53ab0f71c690baac6ca29e5561efe584\n\nPKG_LICENSE:=GPL-2.0\nPKG_LICENSE_FILES:=COPYING\n\nPKG_BUILD_DEPENDS:=iptables\nPKG_BUILD_PARALLEL:=1\nPKG_INSTALL:=1\nPKG_MAINTAINER:=Wei Chen <weichen302@gmail.com>\n\ninclude $(INCLUDE_DIR)/kernel.mk\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/xtables-wgobfs\n\tSECTION:=net\n\tCATEGORY:=Network\n\tSUBMENU:=Firewall\n\tTITLE:=iptables WireGuard obfuscation extension\n\tURL:=https://github.com/infinet/xt_wgobfs\n\tMAINTAINER:=Wei Chen <weichen302@gmail.com>\n\tDEPENDS:= +iptables +kmod-ipt-core +libxtables\nendef\n\ndefine Package/xtables-wgobfs/description\n\tAn iptables extension for WireGuard obfuscation\nendef\n\n# uses GNU configure\nCONFIGURE_ARGS+= \\\n\t--with-kbuild=\"$(LINUX_DIR)\" \\\n\t--with-xtlibdir=\"/usr/lib/iptables\" \\\n\ndefine Build/Compile\n\t+$(MAKE) $(PKG_JOBS) -C $(PKG_BUILD_DIR) \\\n\t\tARCH=\"$(LINUX_KARCH)\" \\\n\t\tCROSS_COMPILE=\"$(TARGET_CROSS)\" \\\n\t\tDESTDIR=\"$(PKG_INSTALL_DIR)\" \\\n\t\tDEPMOD=\"/bin/true\" \\\n\t\tall\nendef\n\ndefine Build/Install\n\tmkdir -p $(PKG_INSTALL_DIR)/usr/lib/iptables/\n\t$(MAKE) -C $(PKG_BUILD_DIR) \\\n\t\tARCH=\"$(LINUX_KARCH)\" \\\n\t\tCROSS_COMPILE=\"$(TARGET_CROSS)\" \\\n\t\tDESTDIR=\"$(PKG_INSTALL_DIR)\" \\\n\t\tDEPMOD=\"/bin/true\" \\\n\t\tinstall\nendef\n\ndefine Package/iptables-mod-wgobfs\n\t$(call Package/xtables-wgobfs)\n\tCATEGORY:=Network\n\tTITLE:=WireGuard obfuscation iptables extension\n\tDEPENDS:=+iptables +kmod-ipt-wgobfs\nendef\n\ndefine Package/iptables-mod-wgobfs/install\n\t$(INSTALL_DIR) $(1)/usr/lib/iptables\n\t$(CP) \\\n\t\t$(PKG_INSTALL_DIR)/usr/lib/iptables/libxt_WGOBFS.so \\\n\t\t$(1)/usr/lib/iptables/\nendef\n\ndefine KernelPackage/ipt-wgobfs\n\tSUBMENU:=Netfilter Extensions\n\tTITLE:=WireGuard obfuscation netfilter module\n\tDEPENDS:=+kmod-ipt-core\n\tFILES:=$(PKG_BUILD_DIR)/src/xt_WGOBFS.$(LINUX_KMOD_SUFFIX)\n\tAUTOLOAD:=$(call AutoProbe,xt_WGOBFS)\nendef\n\n$(eval $(call BuildPackage,iptables-mod-wgobfs))\n$(eval $(call KernelPackage,ipt-wgobfs))\n"
  },
  {
    "path": "packages/telephony/asterisk-chan-quectel/Makefile",
    "content": "#\n# Copyright (C) 2017 - 2018 OpenWrt.org\n#\n# This is free software, licensed under the GNU General Public License v2.\n# See /LICENSE for more information.\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=asterisk-chan-quectel\nPKG_VERSION:=2.1-20220526\nPKG_RELEASE:=1\n\n#PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz\nPKG_SOURCE_URL:=https://github.com/IchthysMaranatha/asterisk-chan-quectel.git\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)\nPKG_SOURCE_VERSION:=f9828978a608be6c32282f2637863a45f00c3d25\nPKG_SOURCE_PROTO:=git\n\nPKG_FIXUP:=autoreconf\n\nPKG_LICENSE:=GPL-2.0\nPKG_LICENSE_FILES:=COPYRIGHT.txt LICENSE.txt\nPKG_MAINTAINER:=Jiri Slachta <jiri@slachta.eu>\n\ndefine Build/Prepare\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(BUILD_VARIANT)/$(PKG_NAME)-$(PKG_VERSION)\n$(CP) ./src/* $(PKG_BUILD_DIR)/\nendef\n\n\n\nMODULES_DIR:=/usr/lib/asterisk/modules\n\ninclude $(INCLUDE_DIR)/package.mk\n# asterisk-chan-quectel needs iconv\ninclude $(INCLUDE_DIR)/nls.mk\n\ndefine Package/$(PKG_NAME)\n  SUBMENU:=Telephony\n  SECTION:=net\n  CATEGORY:=Network\n  URL:=https://github.com/IchthysMaranatha/asterisk-chan-quectel\n  DEPENDS:=asterisk $(ICONV_DEPENDS) +kmod-usb-acm +kmod-usb-serial +kmod-usb-serial-option +libusb-1.0 +alsa-lib\n  TITLE:=Asterisk Mobile Telephony Module\nendef\n\ndefine Package/$(PKG_NAME)/description\n Asterisk channel driver for mobile telephony.\nendef\n\n\nCONFIGURE_ARGS+= \\\n\t--with-asterisk=$(STAGING_DIR)/usr/include \\\n\t\t--with-astversion=20 \\\n\t\t--with-iconv=$(ICONV_PREFIX)/include\n\nTARGET_CFLAGS+= \\\n\t-I$(CHAN_quectel_AST_HEADERS)\n\nMAKE_FLAGS+=LD=\"$(TARGET_CC)\"\n\nCONFIGURE_VARS += \\\n\tDESTDIR=\"$(MODULES_DIR)\" \\\n\tac_cv_type_size_t=yes \\\n\tac_cv_type_ssize_t=yes\n\n\ndefine Package/$(PKG_NAME)/conffiles\n\t/etc/asterisk/quectel.conf\nendef\n\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/etc/asterisk\n\t$(INSTALL_DATA) $(PKG_BUILD_DIR)/etc/quectel.conf $(1)/etc/asterisk\n\t$(INSTALL_DIR) $(1)$(MODULES_DIR)\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/chan_quectel.so $(1)$(MODULES_DIR)\nendef\n\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/telephony/asterisk-chan-quectel/patches/100-openwrtIconvMysql.patch",
    "content": "--- a/configure.ac\r\n+++ b/configure.ac\r\n@@ -83,8 +83,9 @@\r\n \r\n dnl Checks for libraries.\r\n dnl AC_CHECK_LIB([pthread], [pthread_create])  # should use ast_pthread_join everywhere?\r\n-AC_SEARCH_LIBS([iconv], [c iconv],,AC_MSG_ERROR([iconv library missing]))\r\n-AC_CHECK_LIB([sqlite3], [sqlite3_open],,AC_MSG_ERROR([sqlite3 library missing]))\r\n+dnl AC_SEARCH_LIBS([iconv], [c iconv],,AC_MSG_ERROR([iconv library missing]))\r\n+AC_CHECK_LIB([iconv], [libiconv])\r\n+\r\n \r\n dnl Checks for header files.\r\n AC_CHECK_HEADERS([fcntl.h stdlib.h string.h sys/time.h termios.h])\r\n@@ -109,8 +110,8 @@\r\n )\r\n \r\n AC_HEADER_FIND([asterisk.h], $with_asterisk)\r\n-AC_HEADER_FIND([iconv.h], $with_iconv)\r\n-AC_CHECK_HEADER([sqlite3.h],,AC_MSG_ERROR([sqlite3.h header file missing]))\r\n+dnl AC_HEADER_FIND([iconv.h], $with_iconv)\r\n+dnl AC_CHECK_HEADER([sqlite3.h],,AC_MSG_ERROR([sqlite3.h header file missing]))\r\n \r\n AC_DEFINE([ICONV_CONST],[], [Define to const if you has iconv() const declaration of input buffer])\r\n AC_MSG_CHECKING([for iconv use const inbuf])\r\n@@ -224,8 +225,6 @@\r\n if test \"x$enable_debug\" = \"xyes\" ; then\r\n   CFLAGS=\"$CFLAGS -O0 -g\"\r\n   AC_DEFINE([__DEBUG__], [1], [Build with debugging])\r\n-else\r\n-  CFLAGS=\"$CFLAGS -O6\"\r\n fi\r\n \r\n dnl Asterisk header files use lots of old style declarations, ignore those.\r\n"
  },
  {
    "path": "packages/telephony/atinout/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=atinout\nPKG_VERSION=0.9.1\n\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\nPKG_LICENSE:=GPLv2\nPKG_LICENSE_FILES:=LICENSE\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL:=https://github.com/beralt/atinout.git\nPKG_SOURCE_VERSION:=4013e8db4cd140c1df24bb90f929efeb9b61b238\n\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n\tSECTION:=net\n\tCATEGORY:=Network\n\tSUBMENU:=Telephony\n\tTITLE:=Send AT commands to a modem\n\tURL:=http://atinout.sourceforge.net/\n\tMAINTAINER:=Adrian Guenter <a@gntr.me>\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tAtinout is a program that will execute AT commands in sequence and\n\tcapture the response from the modem.\nendef\n\n\nCONFIGURE_VARS += \\\n  CC=\"$(TARGET_CC)\" \\\n  CXX=\"$(TARGET_CC) +.c++\" \\\n  CFLAGS=\"$(TARGET_CFLAGS) -Wall -DVERSION=\\\"\\\\\\\"$(PKG_VERSION)\\\\\\\"\\\"\" \\\n  LDFLAGS=\"$(TARGET_LDFLAGS)\"\n\ndefine Build/Configure\n\t$(call Build/Configure/Default,--with-linux-headers=$(LINUX_DIR))\nendef\n\ndefine Build/Compile\n\t@echo -e \"\\n=== Build/Compile ===\"\n\t$(CONFIGURE_VARS) $(MAKE) -C $(PKG_BUILD_DIR) \\\n\t\tall \\\n\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin/$(PKG_NAME)\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/telephony/hl-atcmd/Makefile",
    "content": "include $(TOPDIR)/rules.mk\n\nPKG_NAME:=hl-atcmd\nPKG_VERSION=1\n\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\nPKG_LICENSE:=GPLv2\nPKG_LICENSE_FILES:=LICENSE\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL:=https://github.com/nhonchu/HL-ATcmd.git\nPKG_SOURCE_VERSION:=7bbd452be2dee32056d49c666fc00a4051840b89\n\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n\tSECTION:=net\n\tCATEGORY:=Network\n\tSUBMENU:=Telephony\n\tTITLE:=Simple tool a send AT command\n\tURL:=https://github.com/nhonchu\n\tMAINTAINER:=Nhon Chu <nhon.chu@gmail.com>\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tThis simple command line tool for sending AT command to a serial device.\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin/$(PKG_NAME)\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/telephony/imei_generator/Makefile",
    "content": "#\n# Copyright (C) 2011-2014 OpenWrt.org\n#\n# This is free software, licensed under the GNU General Public License v2.\n# See /LICENSE for more information.\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=imei_generator\nPKG_RELEASE:=1\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  SECTION:=utils\n  CATEGORY:=Utilities\n  TITLE:=Utility to generate randomly IMEI\n  MAINTAINER:=Egor Grushko\nendef\n\ndefine Package/$(PKG_NAME)/description\n This package contains generate custom IMEI \nendef\n\ndefine Build/Compile\n\t$(TARGET_CC) $(TARGET_CFLAGS) -Wall \\\n\t\t-o $(PKG_BUILD_DIR)/$(PKG_NAME) $(PKG_BUILD_DIR)/$(PKG_NAME).c\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/telephony/imei_generator/src/imei_generator.c",
    "content": "/*-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\\\r\n*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\r\n* Copyright (C) 2016 Egor Grushko\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\r\n*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\r\n* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\t|\r\n*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\r\n* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\r\n*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\r\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\r\n* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\r\n*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\r\n* ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/\r\n\r\n#include <stdio.h>\r\n#include <string.h>\r\n#include <stdlib.h>\r\n#include <time.h>\r\n#include <ctype.h>\r\n\r\nvoid gen_imei(int imei[]) {\r\n    int pos;\r\n    int len = 15;\r\n\r\n    srand(time(NULL));\r\n\r\n    static const char *rbi[] = { \"01\", \"10\", \"30\", \"33\", \"35\", \"44\", \"45\", \"49\", \"50\", \"51\", \"52\", \"53\", \"54\", \"86\", \"91\", \"98\", \"99\" };\r\n    static size_t rbi_count = sizeof(rbi) / sizeof(const char*);\r\n\r\n    const char *arr = rbi[rand() % rbi_count];\r\n\r\n    imei[0] = arr[0] - '0';\r\n    imei[1] = arr[1] - '0';\r\n\r\n    pos = 2;\r\n\r\n    while (pos < len - 1)\r\n    {\r\n        imei[pos++] = rand() % 10;\r\n    }\r\n\r\n\r\n}\r\n\r\nvoid calc_check(int imei[]) {\r\n    int pos;\r\n    int len_offset = 0;\r\n    int len = 15;\r\n    int sum = 0;\r\n    int t = 0;\r\n    int final_digit;\r\n\r\n    len_offset = (len + 1) % 2;\r\n\r\n    for (pos = 0; pos < len - 1; pos++)\r\n    {\r\n        if (((pos + len_offset) % 2) == 1)\r\n        {\r\n            t = imei[pos] * 2;\r\n            if (t > 9)\r\n            {\r\n                t -= 9;\r\n            }\r\n            sum += t;\r\n        }\r\n        else\r\n        {\r\n            sum += imei[pos];\r\n        }\r\n    }\r\n\r\n    final_digit = (10 - (sum % 10)) % 10;\r\n    imei[len - 1] = final_digit;\r\n}\r\n\r\nvoid printUsage() {\r\n    printf(\"Usage: imei_generator [-n] [-m XXXXXXXX]\");\r\n}\r\n\r\nint main(int argc, const char *argv[]) {\r\n    int nvitem = 0;\r\n    int manual = 0;\r\n    int i;\r\n    int imei[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };\r\n    const size_t imei_length = sizeof(imei) / sizeof(int);\r\n\r\n    for (i = 1; i < argc; i++)\r\n    {\r\n        if (strcmp(argv[i], \"-n\") == 0)\r\n        {\r\n            nvitem = 1;\r\n        }\r\n        else if (strcmp(argv[i], \"-m\") == 0)\r\n        {\r\n            manual = 1;\r\n            i++;\r\n\r\n            if (strlen(argv[i]) == 8)\r\n            {\r\n                manual = i;\r\n            }\r\n        }\r\n        else\r\n        {\r\n            printUsage();\r\n            return -1;\r\n        }\r\n    }\r\n\r\n    gen_imei(imei);\r\n\r\n    if (manual)\r\n    {\r\n        for (i = 0; i < 8; i++)\r\n        {\r\n            if (isdigit(argv[manual][i]))\r\n            {\r\n                imei[i] = argv[manual][i] - '0';\r\n            }\r\n            else\r\n            {\r\n                printUsage();\r\n                return -1;\r\n            }\r\n        }\r\n    }\r\n\r\n    calc_check(imei);\r\n\r\n    for (i = 0; i < imei_length; i++)\r\n    {\r\n        if (nvitem)\r\n        {\r\n            printf(\"0%i \", imei[i]);\r\n        }\r\n        else\r\n        {\r\n            printf(\"%i\", imei[i]);\r\n        }\r\n    }\r\n\r\n    if (nvitem)\r\n    {\r\n        printf(\"00\");\r\n    }\r\n\r\n    return 0;\r\n}\r\n"
  },
  {
    "path": "packages/telephony/qtools/Makefile",
    "content": "#\n# Copyright (C) 2016 Nikil Mehta <nikil.mehta@gmail.com>\n#\n# This is free software, licensed under the GNU General Public License v2.\n# See /LICENSE for more information.\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=qtools\nPKG_VERSION:=0.0.1\nPKG_RELEASE:=1~cross\n\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\nPKG_LICENSE:=LICENSE\nPKG_LICENSE_FILES:=COPYING\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL:=https://github.com/intelfx/qtools.git\nPKG_SOURCE_VERSION:=c35166595dd252f23fd6de9b58a94612a1d12627\n\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\n\n\nPKG_INSTALL:=1\nPKG_BUILD_PARALLEL:=1\n\nPKG_FIXUP:=autoreconf\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  SECTION:=telephony\n  CATEGORY:=Utilities\n  TITLE:=Tools modems based on the Qualcom chipset\n  URL:=https://github.com/forth32/qtools\n  DEPENDS:=+libreadline +libncurses\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tA set of tools for working with flash modems based on the Qualcom chipset The set consists of a package of utilities and a set of patched loaders.\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_DIR) $(1)/usr/share\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/mibibsplit \\\n\t\t\t$(PKG_BUILD_DIR)/qbadblock \\\n\t\t\t$(PKG_BUILD_DIR)/qblinfo \\\n\t\t\t$(PKG_BUILD_DIR)/qcommand \\\n\t\t\t$(PKG_BUILD_DIR)/qdload \\\n\t\t\t$(PKG_BUILD_DIR)/qefs \\\n\t\t\t$(PKG_BUILD_DIR)/qflashparm \\\n\t\t\t$(PKG_BUILD_DIR)/qident \\\n\t\t\t$(PKG_BUILD_DIR)/qnvram \\\n\t\t\t$(PKG_BUILD_DIR)/qrflash \\\n\t\t\t$(PKG_BUILD_DIR)/qrmem \\\n\t\t\t$(PKG_BUILD_DIR)/qwdirect \\\n\t\t\t$(PKG_BUILD_DIR)/qwflash $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/*.sh $(1)/usr/share\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/telephony/qtools/README.md",
    "content": "# qtools OpenWrt package\nTools modems based on the Qualcomm chipset\n"
  },
  {
    "path": "packages/telephony/qtools/patches/0001-makefile-install.patch",
    "content": "--- qtools.orig/Makefile\n+++ qtools/Makefile\n@@ -52,3 +52,11 @@ qbadblock:   qbadblock.o $(OBJS)  ptable\n qflashparm:  qflashparm.o $(OBJS)\n \n qterminal qcommand: override LDLIBS+=-lreadline -lncurses\n+\n+install: all\n+\tmkdir -p $(DESTDIR)$(PREFIX)/bin\n+\tcp *.sh mibibsplit qbadblock qblinfo \\\n+\t\tqcommand qdload qefs qflashparm \\\n+\t\tqident qnvram qrflash qrmem \\\n+\t\tqwdirect qwflash $(DESTDIR)$(PREFIX)/bin\n+\n"
  },
  {
    "path": "packages/telephony/qtools/patches/0002-fix-qbadblock-int.patch",
    "content": "--- a/qbadblock.c\n+++ b/qbadblock.c\n@@ -56,7 +56,7 @@ printf(\"\\r * Всего дефектны\n //* Сканирование на ошибки ECC\n //* flag=0 - все ошибки, 1 - только корректируемые\n //********************************************************8 \n-void ecc_scan(start,len,flag) {\n+void ecc_scan(unsigned int start, unsigned int len, int flag) {\n   \n int blk;\n int errcount=0;\n"
  },
  {
    "path": "packages/telephony/ttymux/Makefile",
    "content": "#\n# Copyright (C) 2013-2015 OpenWrt.org\n#\n# This is free software, licensed under the GNU General Public License v2.\n# See /LICENSE for more information.\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=ttymux\nPKG_VERSION:=1\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL=https://github.com/wd5gnr/SerialMux.git\nPKG_SOURCE_VERSION:=553d6e5b0d96773e8a7aaeb30d5bd1a1d1e3c8b9\n\nPKG_MAINTAINER:=Konstatnine Shevlakov <shevlakov@132lan.ru>\nPKG_LICENSE:=GPL-2.0\n\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)\n\nMAKE_PATH:=linux\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  SECTION:=Telephony\n  SECTION:=net\n  CATEGORY:=Network\n  TITLE:=Serial port multiplexer\n  DEPENDS:=+libpthread +libstdcpp\n  URL:=https://github.com/wd5gnr/SerialMux\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tIs a serial multiplexer and demultiplexer protocol for Linux and a microcontroller.\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/linux/ttymux $(1)/usr/bin\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/telephony/ttymux/patches/0001-makefile.patch",
    "content": "--- /dev/null\n+++ ttymux-1/linux/Makefile\n@@ -0,0 +1,9 @@\n+build:\n+\t$(CXX) -o ttymux ttymux.cpp -lpthread\n+\n+clean:\n+\trm -f ttymux\n+\t\t\n+install:\n+\tmkdir -p $(DESTDIR)$(PREFIX)/bin\n+\tcp ttymux $(DESTDIR)$(PREFIX)/bin\n"
  },
  {
    "path": "packages/utils/flash-fox/Makefile",
    "content": "include $(TOPDIR)/rules.mk\r\n\r\nPKG_NAME:=flash_fox\r\nPKG_VERSION:=1\r\nPKG_RELEASE:=1\r\n\r\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\r\nPKG_LICENSE:=LICENSE\r\nPKG_LICENSE_FILES:=COPYING\r\n\r\nPKG_SOURCE_PROTO:=git\r\nPKG_SOURCE_URL:=https://github.com/andrewz1/flash_fox.git\r\nPKG_SOURCE_VERSION:=417257e1d95724380097a72380b89e36f75e3598\r\n\r\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\r\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\r\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\r\n\r\n\r\ninclude $(INCLUDE_DIR)/package.mk\r\n\r\ndefine Package/$(PKG_NAME)\r\n  SECTION:=utils\r\n  CATEGORY:=Utilities\r\n  MAINTAINER := Andrey Safonov <andrey.safonov@gmail.com>\r\n  TITLE:=Tool for flashing Qualcomm T99W175 modems\r\n  URL:=https://github.com/andrewz1/flash_fox/\r\n  DEPENDS:=+libusb-1.0 +zlib\r\nendef\r\n\r\ndefine Package/$(PKG_NAME)/install\r\n\t$(INSTALL_DIR) $(1)/usr/bin\r\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin/\r\nendef\r\n\r\n$(eval $(call BuildPackage,$(PKG_NAME)))\r\n"
  },
  {
    "path": "packages/utils/qmbimat/Makefile",
    "content": "include $(TOPDIR)/rules.mk\r\n\r\nPKG_NAME:=qmbimat\r\nPKG_VERSION:=0.0.1\r\nPKG_RELEASE:=2\r\n\r\nPKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>\r\nPKG_LICENSE:=LICENSE\r\nPKG_LICENSE_FILES:=COPYING\r\n\r\nPKG_SOURCE_PROTO:=git\r\nPKG_SOURCE_URL:=https://github.com/quectel-official/QMbimAT.git\r\nPKG_SOURCE_VERSION:=7c665521a732acbe7722ed19c91011163649f899\r\n\r\nPKG_SOURCE_SUBDIR:=$(PKG_NAME)\r\nPKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz\r\nPKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)\r\n\r\ninclude $(INCLUDE_DIR)/package.mk\r\n\r\ndefine Package/$(PKG_NAME)\r\n  SECTION:=utils\r\n  CATEGORY:=Network\r\n  TITLE:=Tool for send AT-command via MBIM interface\r\n  URL:=https://github.com/quectel-official/QMbimAT\r\nendef\r\n\r\ndefine Package/$(PKG_NAME)/description\r\n\tTool for send AT-command via MBIM interface\r\nendef\r\n\r\ndefine Package/$(PKG_NAME)/install\r\n\t$(INSTALL_DIR) $(1)/usr/bin\r\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin/\r\nendef\r\n\r\n$(eval $(call BuildPackage,$(PKG_NAME)))\r\n"
  },
  {
    "path": "packages/utils/qmbimat/patches/0001-cc.patch",
    "content": "--- a/Makefile\n+++ b/Makefile\n@@ -16,14 +16,14 @@ OBJS = $(SOURCES:%.c=%.o)\n all: $(PROG)\n \n $(PROG): $(OBJS) $(MYLIBS)\n-\tg++ $(LDFLAGS) $(OBJS) $(LIBS)  -o $(PROG)\n+\t$(CXX) $(LDFLAGS) $(OBJS) $(LIBS)  -o $(PROG)\n \trm -rf ./src/*.o \n \n %.o:%.c\n-\tgcc $(CFLAGS) -c -o $@ $<\n+\t$(CC) $(CFLAGS) -c -o $@ $<\n \n %.a: \n-\tmake -C $(dir $@)\n+\t$(MAKE) -C $(dir $@)\n \n clean:\n \t# make clean -C $(SUBDIR)\n"
  },
  {
    "path": "packages/utils/qmbimat/patches/0002-qmbimat_more_fixes.patch",
    "content": "diff -ruN QMbimAT-main/src/main.c QMbimAT-main-fixed/src/main.c\n--- QMbimAT-main/src/main.c\t2023-09-05 19:22:17.000000000 +0000\n+++ QMbimAT-main-fixed/src/main.c\t2026-05-15 07:14:30.636615920 +0000\n@@ -54,7 +54,7 @@\n         return 0;\n     }\n \n-    while (-1 != (opt = getopt_long(argc, argv, \"a:d:vh\", longopts, NULL)))\n+    while (-1 != (opt = getopt_long(argc, argv, \"a:d:vhD\", longopts, NULL)))\n     {\n         switch (opt)\n         {\n@@ -143,13 +143,10 @@\n \n int main(int argc, char *argv[])\n {\n-    if (argc >= 5)\n+    if (argc <= 1)\n     {\n-        return debug_tool(argc, argv );\n-    }\n-    else \n-    {\n-        debug_tool_usage(argv[0]);\n+        debug_tool_usage();\n         return -1;\n     }\n+    return debug_tool(argc, argv);\n }\ndiff -ruN QMbimAT-main/src/mbim_ctx.c QMbimAT-main-fixed/src/mbim_ctx.c\n--- QMbimAT-main/src/mbim_ctx.c\t2023-09-05 19:22:17.000000000 +0000\n+++ QMbimAT-main-fixed/src/mbim_ctx.c\t2026-05-15 06:58:01.288358305 +0000\n@@ -129,14 +129,37 @@\n     }\n     else if (mbim_pRequest && le32toh(mbim_pRequest->TransactionId) == le32toh(pResponse->TransactionId))\n     {\n-        mbim_pResponse = mbim_alloc(le32toh(pResponse->MessageLength) + 1);\n-        if (mbim_pResponse)\n-            memcpy(mbim_pResponse, pResponse, le32toh(pResponse->MessageLength));\n-        pthread_cond_signal(&mbim_command_cond);\n+        uint32_t resp_type = le32toh(pResponse->MessageType);\n+        uint32_t req_type  = le32toh(mbim_pRequest->MessageType);\n+\n+        /* Accept matching response types only:\n+         *   MBIM_OPEN_MSG        -> MBIM_OPEN_DONE\n+         *   MBIM_CLOSE_MSG       -> MBIM_CLOSE_DONE\n+         *   MBIM_COMMAND_MSG     -> MBIM_COMMAND_DONE\n+         * Also accept MBIM_FUNCTION_ERROR_MSG for any request.\n+         */\n+        int is_expected = (resp_type == MBIM_FUNCTION_ERROR_MSG) ||\n+                          (req_type == MBIM_OPEN_MSG    && resp_type == MBIM_OPEN_DONE)  ||\n+                          (req_type == MBIM_CLOSE_MSG   && resp_type == MBIM_CLOSE_DONE) ||\n+                          (req_type == MBIM_COMMAND_MSG && resp_type == MBIM_COMMAND_DONE);\n+\n+        if (is_expected)\n+        {\n+            mbim_pResponse = mbim_alloc(le32toh(pResponse->MessageLength) + 1);\n+            if (mbim_pResponse)\n+                memcpy(mbim_pResponse, pResponse, le32toh(pResponse->MessageLength));\n+            pthread_cond_signal(&mbim_command_cond);\n+        }\n+        else\n+        {\n+            mbim_debug(\"info: ignoring MessageType=0x%x for TransactionId=%u (waiting for response to req type=0x%x)\",\n+                       resp_type, le32toh(pResponse->TransactionId), req_type);\n+        }\n     }\n     else if (le32toh(pResponse->MessageType) == MBIM_INDICATE_STATUS_MSG)\n     {\n         MBIM_INDICATE_STATUS_MSG_T *pIndMsg = (MBIM_INDICATE_STATUS_MSG_T *)pResponse;\n+        (void)pIndMsg;\n     }\n \n     pthread_mutex_unlock(&mbim_command_mutex);\n@@ -207,7 +230,7 @@\n     mbim_debug(\"%s is created\", __func__);\n     (void)param;\n \n-    while (mbim_fd > 0)\n+    while (mbim_fd >= 0)\n     {\n         struct pollfd pollfds[] = {{mbim_fd, POLLIN, 0}, {control_pipe[0], POLLIN, 0}};\n         int ne, ret, nevents = 2;\n@@ -247,21 +270,22 @@\n                     mbim_debug(\"%s read=%d errno: %d (%s)\", __func__, (int)nreads, errno, strerror(errno));\n                     break;\n                 }\n-                else if (nreads < pResponse->MessageLength)\n+                else if (nreads < (ssize_t)le32toh(pResponse->MessageLength))\n                 {\n-                    mbim_debug(\"error: %s read=%d MessageLength=%u\", __func__, (int)nreads, pResponse->MessageLength);\n+                    mbim_debug(\"error: %s read=%d MessageLength=%u\", __func__, (int)nreads, le32toh(pResponse->MessageLength));\n                     break;\n                 }\n-                else if (nreads >= pResponse->MessageLength)\n+                else if (nreads >= (ssize_t)le32toh(pResponse->MessageLength))\n                 {\n                     while (nreads > 0)\n                     {\n-                        mbim_debug(\"%s read=%d MessageLength=%u\", __func__, (int)nreads, pResponse->MessageLength);\n+                        uint32_t msg_len = le32toh(pResponse->MessageLength);\n+                        mbim_debug(\"%s read=%d MessageLength=%u\", __func__, (int)nreads, msg_len);\n \n                         // coverity[tainted_data:FALSE]\n-                        mbim_recv_command(pResponse, pResponse->MessageLength);\n-                        nreads -= pResponse->MessageLength;\n-                        pResponse = (MBIM_MESSAGE_HEADER *)((char *)pResponse + pResponse->MessageLength);\n+                        mbim_recv_command(pResponse, msg_len);\n+                        nreads -= msg_len;\n+                        pResponse = (MBIM_MESSAGE_HEADER *)((char *)pResponse + msg_len);\n                     }\n                 }\n             }\n@@ -350,7 +374,7 @@\n     if (control_pipe[0] == -1)\n         return;\n \n-    if (!use_mbim_proxy && mbim_fd)\n+    if (!use_mbim_proxy && mbim_fd != -1)\n     {\n         mbim_CLOSE();\n     }\n@@ -387,7 +411,7 @@\n     if (mbim_fd != -1)\n         return 0;\n \n-    fp = popen(\"ps -e |  grep mbim-proxy\", \"r\");\n+    fp = popen(\"cat /proc/[0-9]*/comm 2>/dev/null | grep -m1 mbim-proxy\", \"r\");\n     if (fp != NULL)\n     {\n         // coverity[check_return]\ndiff -ruN QMbimAT-main/src/mbim_protocol.c QMbimAT-main-fixed/src/mbim_protocol.c\n--- QMbimAT-main/src/mbim_protocol.c\t2023-09-05 19:22:17.000000000 +0000\n+++ QMbimAT-main-fixed/src/mbim_protocol.c\t2026-05-15 07:25:01.090234223 +0000\n@@ -161,6 +161,8 @@\n     return &uuid;\n }\n \n+static const UUID_T *str2uuid_pub(const char *str) { return str2uuid(str); }\n+\n static uint32_t TransactionId(void)\n {\n     static uint32_t tid = 0;\n@@ -295,7 +297,7 @@\n         return NULL;\n \n     pOpen->MessageHeader.MessageType = htole32(MBIM_OPEN_MSG);\n-    pOpen->MessageHeader.MessageLength = htole32(sizeof(MBIM_COMMAND_MSG_T));\n+    pOpen->MessageHeader.MessageLength = htole32(sizeof(MBIM_OPEN_MSG_T));\n     pOpen->MessageHeader.TransactionId = htole32(TransactionId());\n     pOpen->MaxControlTransfer = htole32(4096);\n     return (MBIM_MESSAGE_HEADER*)pOpen;\n@@ -309,7 +311,7 @@\n         return NULL;\n \n     pOpen->MessageHeader.MessageType = htole32(MBIM_CLOSE_MSG);\n-    pOpen->MessageHeader.MessageLength = htole32(sizeof(MBIM_COMMAND_MSG_T));\n+    pOpen->MessageHeader.MessageLength = htole32(sizeof(MBIM_CLOSE_MSG_T));\n     pOpen->MessageHeader.TransactionId = htole32(TransactionId());\n \n     return (MBIM_MESSAGE_HEADER*)pOpen;\n@@ -361,9 +363,10 @@\n     {\n         cfg = (MBIM_LIBQMI_PROXY_CONFIG_T *)((MBIM_COMMAND_MSG_T *)pRequest)->InformationBuffer;\n \n-        cfg->DevicePathOffset = sizeof(*cfg);\n-        cfg->DevicePathSize = char2wchar((const uint8_t *)dev, strlen(dev), cfg->DataBuffer, strlen(dev) * 2);\n-        cfg->Timeout = 15;\n+        uint32_t wchar_len = (uint32_t)char2wchar((const uint8_t *)dev, strlen(dev), cfg->DataBuffer, strlen(dev) * 2);\n+        cfg->DevicePathOffset = htole32(sizeof(*cfg));\n+        cfg->DevicePathSize   = htole32(wchar_len);\n+        cfg->Timeout          = htole32(15);\n     }\n \n     err = mbim_send_command(pRequest, &pCmdDone);\n@@ -555,6 +558,101 @@\n     return err;\n }\n \n+\n+/* AT-over-MBIM service candidates, tried in order */\n+typedef struct {\n+    const char *uuid;\n+    uint32_t    cid;\n+    const char *description;\n+} AT_SERVICE_CANDIDATE;\n+\n+static const AT_SERVICE_CANDIDATE at_service_candidates[] = {\n+    /* Most Quectel LTE/5G modules: EC2x, EP06, EM06, EG06, EG12, EG18,\n+       EM12, EM160, RG500, RM500, RM502, RM520, etc. */\n+    { uuid_ext_qmux, 1, \"EXT_QMUX/CID=1 (EC2x/EP06/EM06/EG-series/RM5xx)\" },\n+    /* Older fallback: EM060 and similar SDX55-based modules */\n+    { uuid_qdu,      8, \"QDU/CID=8 (EM060)\" },\n+    /* Sentinel */\n+    { NULL, 0, NULL }\n+};\n+\n+static const char  *s_at_uuid = NULL;\n+static uint32_t     s_at_cid  = 0;\n+\n+/* Query Device Services and pick the first UUID/CID pair the modem supports */\n+static int mbim_detect_at_service(void)\n+{\n+    MBIM_MESSAGE_HEADER  *pRequest  = NULL;\n+    MBIM_COMMAND_DONE_T  *pCmdDone  = NULL;\n+    int err;\n+    unsigned int i, s;\n+\n+    if (s_at_uuid)\n+        return 0; /* already detected */\n+\n+    pRequest = mbim_compose_command(UUID_BASIC_CONNECT,\n+                                    MBIM_CID_DEVICE_SERVICES,\n+                                    MBIM_CID_CMD_TYPE_QUERY, NULL, 0);\n+    err = mbim_send_command(pRequest, &pCmdDone);\n+    if (err || !pCmdDone) {\n+        mbim_free(pRequest);\n+        mbim_free(pCmdDone);\n+        /* Fall back to first candidate without detection */\n+        s_at_uuid = at_service_candidates[0].uuid;\n+        s_at_cid  = at_service_candidates[0].cid;\n+        mbim_debug(\"detect_at_service: query failed, defaulting to %s\",\n+                   at_service_candidates[0].description);\n+        return 0;\n+    }\n+\n+    if (le32toh(pCmdDone->InformationBufferLength) >= sizeof(MBIM_DEVICE_SERVICES_INFO_T)) {\n+        MBIM_DEVICE_SERVICES_INFO_T *info =\n+            (MBIM_DEVICE_SERVICES_INFO_T *)pCmdDone->InformationBuffer;\n+        uint32_t svc_count = le32toh(info->DeviceServicesCount);\n+\n+        /* Walk candidate list; pick first one found in device services */\n+        for (i = 0; at_service_candidates[i].uuid && !s_at_uuid; i++) {\n+            const UUID_T *want = (const UUID_T *)str2uuid_pub(at_service_candidates[i].uuid);\n+            for (s = 0; s < svc_count && !s_at_uuid; s++) {\n+                uint32_t offset = le32toh(info->DeviceServicesRefList[s].offset);\n+                uint32_t size   = le32toh(info->DeviceServicesRefList[s].size);\n+                if (offset + size > le32toh(pCmdDone->InformationBufferLength))\n+                    continue;\n+                MBIM_DEVICE_SERVICE_ELEMENT_T *elem =\n+                    (MBIM_DEVICE_SERVICE_ELEMENT_T *)\n+                    ((uint8_t *)info + offset);\n+                if (memcmp(elem->DeviceServiceId.uuid, want->uuid, 16) == 0) {\n+                    /* Check the required CID is in the CID list */\n+                    uint32_t cid_count = le32toh(elem->CidCount);\n+                    uint32_t c;\n+                    for (c = 0; c < cid_count; c++) {\n+                        if (le32toh(elem->CidList[c]) == at_service_candidates[i].cid) {\n+                            s_at_uuid = at_service_candidates[i].uuid;\n+                            s_at_cid  = at_service_candidates[i].cid;\n+                            mbim_debug(\"detect_at_service: found %s\",\n+                                       at_service_candidates[i].description);\n+                            break;\n+                        }\n+                    }\n+                }\n+            }\n+        }\n+    }\n+\n+    mbim_free(pRequest);\n+    mbim_free(pCmdDone);\n+\n+    if (!s_at_uuid) {\n+        /* Nothing matched — fall back to first candidate */\n+        s_at_uuid = at_service_candidates[0].uuid;\n+        s_at_cid  = at_service_candidates[0].cid;\n+        mbim_debug(\"detect_at_service: no match, defaulting to %s\",\n+                   at_service_candidates[0].description);\n+    }\n+\n+    return 0;\n+}\n+\n static char s_atc_response[8192];\n int mbim_send_at_command(const char *atc_req, char **pp_atc_rsp)\n {\n@@ -566,7 +664,20 @@\n     if (pp_atc_rsp)\n         *pp_atc_rsp = NULL;\n     printf(\"Send > %s\\n\", atc_req);\n-    pRequest = mbim_compose_command(uuid_qdu, 8,\n+    /* Add \\r terminator if not already present (required by AT command standard) */\n+    char at_buf[133];\n+    if (atc_len > 0 && atc_req[atc_len - 1] != '\\r') {\n+        if (atc_len + 1 >= sizeof(at_buf)) {\n+            return -EINVAL;\n+        }\n+        memcpy(at_buf, atc_req, atc_len);\n+        at_buf[atc_len++] = '\\r';\n+        at_buf[atc_len] = '\\0';\n+        atc_req = at_buf;\n+    }\n+    mbim_detect_at_service();\n+    printf(\"Using AT service: uuid=%s cid=%u\\n\", s_at_uuid, s_at_cid);\n+    pRequest = mbim_compose_command(s_at_uuid, s_at_cid,\n                                     MBIM_CID_CMD_TYPE_SET, NULL, 4 + atc_len);\n     if (pRequest)\n     {\n@@ -580,14 +691,20 @@\n \n     if (le32toh(pCmdDone->InformationBufferLength))\n     {\n-        unsigned int i = 0;\n+        uint32_t rsp_len = le32toh(pCmdDone->InformationBufferLength);\n \n-        strncpy(s_atc_response, (char *)&pCmdDone->InformationBuffer[4], pCmdDone->InformationBufferLength - 4);\n-        s_atc_response[pCmdDone->InformationBufferLength - 4] = 0;\n-\n-        printf(\"Recv < %s\", s_atc_response);\n-        if (pp_atc_rsp)\n-            *pp_atc_rsp = s_atc_response;\n+        if (rsp_len > 4)\n+        {\n+            uint32_t data_len = rsp_len - 4;\n+            if (data_len >= sizeof(s_atc_response))\n+                data_len = sizeof(s_atc_response) - 1;\n+            memcpy(s_atc_response, (char *)&pCmdDone->InformationBuffer[4], data_len);\n+            s_atc_response[data_len] = '\\0';\n+\n+            printf(\"Recv < %s\", s_atc_response);\n+            if (pp_atc_rsp)\n+                *pp_atc_rsp = s_atc_response;\n+        }\n     }\n \n     // out:\n"
  },
  {
    "path": "packages/utils/qminfo/Makefile",
    "content": "#\n# Copyright (C) 2011-2014 OpenWrt.org\n#\n# This is free software, licensed under the GNU General Public License v2.\n# See /LICENSE for more information.\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=qminfo\nPKG_VERSION=0.0.2\nPKG_RELEASE:=1\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/$(PKG_NAME)\n  SECTION:=utils\n  CATEGORY:=Utilities\n  TITLE:=QMI modeminfo\n  MAINTAINER:=Konstantine Shevlakov\n  DEPENDS:= +libqmi\nendef\n\ndefine Package/$(PKG_NAME)/description\n\tQMI info\nendef\n\ndefine Package/$(PKG_NAME)/install\n\t$(INSTALL_DIR) $(1)/usr/bin\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin/\nendef\n\n$(eval $(call BuildPackage,$(PKG_NAME)))\n"
  },
  {
    "path": "packages/utils/qminfo/src/Makefile",
    "content": "# ─────────────────────────────────────────────────────────────────\n#  Makefile for qminfo\n#\n#  Build:\n#    make\n#\n#  Cross Build:\n#    make ARCH=aarch64\n#    make ARCH=armv7\n#    make ARCH=armv5\n#    make ARCH=mipsle\n#\n#  С sysroot (Buildroot/Yocto/OpenWRT):\n#    make ARCH=mipsle SYSROOT=/path/to/staging\n# ─────────────────────────────────────────────────────────────────\n\nARCH    ?=\nSYSROOT ?=\n\nifeq ($(ARCH),aarch64)\n    CROSS_TRIPLE := aarch64-linux-gnu\n    ARCH_CFLAGS  :=\nelse ifeq ($(ARCH),armv7)\n    CROSS_TRIPLE := arm-linux-gnueabihf\n    ARCH_CFLAGS  := -march=armv7-a -mfpu=vfpv3 -mfloat-abi=hard\nelse ifeq ($(ARCH),armv5)\n    CROSS_TRIPLE := arm-linux-gnueabi\n    ARCH_CFLAGS  := -march=armv5te -mfloat-abi=soft\nelse ifeq ($(ARCH),mipsle)\n    CROSS_TRIPLE := mipsel-linux-gnu\n    ARCH_CFLAGS  := -march=mips32 -mabi=32 -EL\nelse\n    CROSS_TRIPLE :=\n    ARCH_CFLAGS  :=\nendif\n\nifdef CROSS_TRIPLE\n    CC    := $(CROSS_TRIPLE)-gcc\n    STRIP := $(CROSS_TRIPLE)-strip\nelse\n    CC    := gcc\n    STRIP := strip\nendif\n\nTARGET := qminfo\nSRC    := qminfo.c\nOBJ    := $(SRC:.c=.o)\n\n# pkg-config\nPKG_QMI  := qmi-glib\nPKG_MBIM := mbim-glib\n\nifdef SYSROOT\n    PKG_CONFIG := PKG_CONFIG_SYSROOT_DIR=$(SYSROOT) \\\n                  PKG_CONFIG_LIBDIR=$(SYSROOT)/usr/lib/pkgconfig:$(SYSROOT)/usr/share/pkgconfig \\\n                  PKG_CONFIG_PATH= \\\n                  pkg-config\n    SYSROOT_FLAGS := --sysroot=$(SYSROOT)\nelse ifdef CROSS_TRIPLE\n    PKG_CONFIG := PKG_CONFIG_PATH=/usr/lib/$(CROSS_TRIPLE)/pkgconfig \\\n                  pkg-config\n    SYSROOT_FLAGS :=\nelse\n    PKG_CONFIG    := pkg-config\n    SYSROOT_FLAGS :=\nendif\n\n# if mbim-glib — link it (need for QMI over MBIM)\nMBIM_LIBS := $(shell $(PKG_CONFIG) --libs $(PKG_MBIM) 2>/dev/null)\nMBIM_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PKG_MBIM) 2>/dev/null)\n\nCFLAGS := $(shell $(PKG_CONFIG) --cflags $(PKG_QMI)) \\\n          $(MBIM_CFLAGS) \\\n          $(ARCH_CFLAGS) \\\n          $(SYSROOT_FLAGS) \\\n          -Wall -Wextra -O2 -pipe\n\nLIBS   := $(shell $(PKG_CONFIG) --libs $(PKG_QMI)) \\\n          $(MBIM_LIBS) \\\n          -lm\n\nPREFIX := /usr/local\nBINDIR := $(PREFIX)/bin\n\n.PHONY: all clean install uninstall run json run-mbim json-mbim strip help check-pkg\n\nall: check-pkg $(TARGET)\n\ncheck-pkg:\n\t@$(PKG_CONFIG) --exists $(PKG_QMI) || \\\n\t\t{ echo \"Error: $(PKG_QMI) not found. Install libqmi-glib-dev\"; exit 1; }\n\t@$(PKG_CONFIG) --exists $(PKG_MBIM) && \\\n\t\techo \"  mbim-glib: found (QMI over MBIM supported)\" || \\\n\t\techo \"  mbim-glib: not found (QMI over MBIM disabled)\"\n\n$(OBJ): $(SRC)\n\t$(CC) $(CFLAGS) -c -o $@ $<\n\n$(TARGET): $(OBJ)\n\t$(CC) $(CFLAGS) -o $@ $^ $(LIBS)\n\nstrip: $(TARGET)\n\t$(STRIP) --strip-all $(TARGET)\n\ninstall: $(TARGET)\n\tinstall -d $(DESTDIR)$(BINDIR)\n\tinstall -m 755 $(TARGET) $(DESTDIR)$(BINDIR)/$(TARGET)\n\nuninstall:\n\trm -f $(DESTDIR)$(BINDIR)/$(TARGET)\n\nclean:\n\trm -f $(OBJ) $(TARGET)\n"
  },
  {
    "path": "packages/utils/qminfo/src/qminfo.c",
    "content": "/*\n * qminfo.c\n *\n * simple monitor for QMI modems with json output\n *       by Konstantine Shevlakov (koshev-msk)\n *\t\t GPLv3 License. Copyrihgt (c) 2026\n *\n * compile:\n *   gcc $(pkg-config --cflags --libs qmi-glib) -lm -o qminfo qminfo.c\n *\n * run:\n *   sudo ./qminfo /dev/cdc-wdm0              # auto-detect (QMI first)\n *   sudo ./qminfo /dev/cdc-wdm0 --json       # JSON output\n *   sudo ./qminfo /dev/cdc-wdm0 --qmi        # force native QMI\n *   sudo ./qminfo /dev/cdc-wdm0 --mbim       # force QMI over MBIM\n *   sudo ./qminfo /dev/cdc-wdm0 --mbim --json\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <math.h>\n#include <unistd.h>\n#include <libqmi-glib.h>\n\n/* ═══════════════════════════════════════════════════════════════\n * main data\n * ═══════════════════════════════════════════════════════════════ */\ntypedef struct {\n    char    imei[32];\n    char    model[128];\n    char    firmware[128];\n    char    iccid[32];\n    char    imsi[32];\n    gint    chip_temp;\n\n    gint    reg_state;\n    char    cops[64];\n    char    mode[32];\n    guint32 lac;\n    guint64 cid;\n\n    gint8   rssi;\n    gint16  rsrp;       /* *0.1 dBm */\n    gint8   rsrq;       /* *0.1 dB  */\n    gint16  sinr;       /* *0.1 dB  */\n    gint    csq;        /* 0-31, -1 if not exist */\n\n    guint16 arfcn;\n    guint16 pci;\n    guint32 enb_id;\n    guint8  cell_sector;\n    guint32 ta;\n    guint8  bw_dl;      /* index 0-5 */\n\n    gint    lte_ca;\n    char    scc_bands[128];\n\tchar    scc_bands_json[128];\n    guint32 bw_ca_total; /* kHz */\n\n    gboolean has_rsrp;\n    gboolean has_rsrq;\n    gboolean has_sinr;\n    gboolean has_ta;\n    gboolean has_bw_dl;\n    gboolean has_pci;\n    gboolean has_arfcn;\n    gboolean has_ca;\n    gboolean has_temp;\n    gboolean has_iccid;\n    gboolean has_imsi;\n    gboolean has_imei;\n    gboolean has_model;\n    gboolean has_firmware;\n    gboolean has_csq;\n    gboolean ta_valid;\n    \n    gboolean is_umts;   /* UMTS flag */\n} ModemInfo;\n\n/* ═══════════════════════════════════════════════════════════════\n * main state\n * ═══════════════════════════════════════════════════════════════ */\nstatic GMainLoop    *loop      = NULL;\nstatic QmiDevice    *device    = NULL;\nstatic QmiClientNas *nas       = NULL;\nstatic QmiClientDms *dms       = NULL;\nstatic ModemInfo     info;\nstatic gboolean      json_mode = FALSE;\nstatic const char   *dev_path  = NULL;\nstatic gint          pending   = 0;\ntypedef enum {\n    MODE_AUTO,   /* try QMI first, fallback to MBIM */\n    MODE_QMI,    /* force native QMI */\n    MODE_MBIM,   /* force QMI over MBIM */\n} DeviceMode;\n\nstatic DeviceMode    device_mode = MODE_AUTO;\nstatic gint          open_retries = 0;  /* retry counter for auto mode */\n\n/* ═══════════════════════════════════════════════════════════════\n * helpers\n * ═══════════════════════════════════════════════════════════════ */\n\nstatic guint32 bw_index_to_khz(guint8 bw)\n{\n    static const guint32 t[] = { 1400, 3000, 5000, 10000, 15000, 20000 };\n    return (bw < 6) ? t[bw] : 0;\n}\n\nstatic int csq_to_percent(int csq)\n{\n    if (csq < 0 || csq > 31) return -1;\n    return (csq * 100) / 31;\n}\n\nstatic float ta_to_km(guint32 ta)\n{\n    /* QMI TA μsec.\n     * d = c * t / 2 = 299792458 * (ta * 10^-6) / 2 / 1000 (in km) */\n    float d = (299792458.0f * (float)ta * 1e-6f) / 2.0f / 1000.0f;\n    return roundf(d * 100.0f) / 100.0f;\n}\n\nstatic const char *radio_to_mode(QmiNasRadioInterface iface)\n{\n    switch (iface) {\n    case QMI_NAS_RADIO_INTERFACE_GSM:         return \"GSM\";\n    case QMI_NAS_RADIO_INTERFACE_UMTS:        return \"UMTS\";\n    case QMI_NAS_RADIO_INTERFACE_CDMA_1X:     return \"CDMA\";\n    case QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO: return \"EVDO\";\n    case QMI_NAS_RADIO_INTERFACE_LTE:         return \"LTE\";\n    case QMI_NAS_RADIO_INTERFACE_5GNR:        return \"5G NR\";\n    default:                                   return \"Unknown\";\n    }\n}\n\nstatic const char *reg_state_to_string(QmiNasRegistrationState state)\n{\n    switch (state) {\n    case QMI_NAS_REGISTRATION_STATE_NOT_REGISTERED:          return \"Not Registered\";\n    case QMI_NAS_REGISTRATION_STATE_REGISTERED:              return \"Registered\";\n    case QMI_NAS_REGISTRATION_STATE_NOT_REGISTERED_SEARCHING: return \"Searching\";\n    case QMI_NAS_REGISTRATION_STATE_REGISTRATION_DENIED:     return \"Denied\";\n    case QMI_NAS_REGISTRATION_STATE_UNKNOWN:                 return \"Unknown\";\n    default:                                                  return \"Unknown\";\n    }\n}\n\nstatic guint active_band_to_lte_band(QmiNasActiveBand band)\n{\n    /* QmiNasActiveBand enum to LTE band */\n    switch (band) {\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_1:   return 1;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_2:   return 2;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_3:   return 3;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_4:   return 4;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_5:   return 5;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_6:   return 6;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_7:   return 7;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_8:   return 8;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_9:   return 9;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_10:  return 10;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_11:  return 11;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_12:  return 12;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_13:  return 13;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_14:  return 14;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_17:  return 17;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_18:  return 18;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_19:  return 19;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_20:  return 20;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_21:  return 21;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_23:  return 23;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_24:  return 24;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_25:  return 25;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_26:  return 26;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_27:  return 27;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_28:  return 28;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_29:  return 29;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_30:  return 30;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_31:  return 31;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_32:  return 32;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_33:  return 33;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_34:  return 34;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_35:  return 35;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_36:  return 36;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_37:  return 37;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_38:  return 38;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_39:  return 39;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_40:  return 40;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_41:  return 41;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_42:  return 42;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_43:  return 43;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_46:  return 46;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_47:  return 47;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_48:  return 48;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_66:  return 66;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_71:  return 71;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_125: return 125;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_126: return 126;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_127: return 127;\n    case QMI_NAS_ACTIVE_BAND_EUTRAN_250: return 250;\n    default:                             return 0;\n    }\n}\n\nstatic void jstr(char *dst, size_t sz, const char *src)\n{\n    if (!src) { dst[0] = '\\0'; return; }\n    size_t j = 0;\n    for (size_t i = 0; src[i] && j + 2 < sz; i++) {\n        if (src[i] == '\"' || src[i] == '\\\\') dst[j++] = '\\\\';\n        dst[j++] = src[i];\n    }\n    dst[j] = '\\0';\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * Read USB ID from sysfs\n * ═══════════════════════════════════════════════════════════════ */\nstatic void read_usb_id(const char *dev_name, char *manufacturer, size_t man_size, char *product, size_t prod_size)\n{\n    char path[512];\n    \n    manufacturer[0] = '\\0';\n    product[0] = '\\0';\n    \n    if (!dev_name || !dev_name[0]) return;\n    \n    /* Read manufacturer */\n    snprintf(path, sizeof(path), \"/sys/class/usbmisc/%s/../../../manufacturer\", dev_name);\n    \n    FILE *f = fopen(path, \"r\");\n    if (f) {\n        if (fgets(manufacturer, man_size, f)) {\n            size_t l = strlen(manufacturer);\n            if (l > 0 && manufacturer[l-1] == '\\n')\n                manufacturer[l-1] = '\\0';\n        }\n        fclose(f);\n    }\n    \n    /* Read product */\n    snprintf(path, sizeof(path), \"/sys/class/usbmisc/%s/../../../product\", dev_name);\n    f = fopen(path, \"r\");\n    if (f) {\n        if (fgets(product, prod_size, f)) {\n            size_t l = strlen(product);\n            if (l > 0 && product[l-1] == '\\n')\n                product[l-1] = '\\0';\n        }\n        fclose(f);\n    }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * release DMS -> release NAS > close device -> quit loop\n * ═══════════════════════════════════════════════════════════════ */\n\nstatic void on_device_closed(QmiDevice *dev, GAsyncResult *res, gpointer ud)\n{\n    (void)dev; (void)ud;\n    GError *err = NULL;\n    qmi_device_close_finish(device, res, &err);\n    g_clear_error(&err);\n    g_main_loop_quit(loop);\n}\n\nstatic void do_close_device(void)\n{\n    qmi_device_close_async(device, 5, NULL,\n        (GAsyncReadyCallback)on_device_closed, NULL);\n}\n\nstatic void on_nas_released(QmiDevice *dev, GAsyncResult *res, gpointer ud)\n{\n    (void)dev; (void)ud;\n    GError *err = NULL;\n    qmi_device_release_client_finish(device, res, &err);\n    g_clear_error(&err);\n\tif (nas){\n\t\tg_object_unref(nas);\n\t\tnas = NULL;\n\t}\n    do_close_device();\n}\n\nstatic void on_dms_released(QmiDevice *dev, GAsyncResult *res, gpointer ud)\n{\n    (void)dev; (void)ud;\n    GError *err = NULL;\n    qmi_device_release_client_finish(device, res, &err);\n    g_clear_error(&err);\n\tif (dms){\n\t\tg_object_unref(dms);\n\t\tdms = NULL;\n\t}\n\n    if (nas) {\n        qmi_device_release_client(device, QMI_CLIENT(nas),\n            QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID,\n            5, NULL,\n            (GAsyncReadyCallback)on_nas_released, NULL);\n    } else {\n        do_close_device();\n    }\n}\n\nstatic void do_release_clients(void)\n{\n    if (dms) {\n        qmi_device_release_client(device, QMI_CLIENT(dms),\n            QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID,\n            5, NULL,\n            (GAsyncReadyCallback)on_dms_released, NULL);\n    } else if (nas) {\n        qmi_device_release_client(device, QMI_CLIENT(nas),\n            QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID,\n            5, NULL,\n            (GAsyncReadyCallback)on_nas_released, NULL);\n    } else {\n        do_close_device();\n    }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * Results\n * ═══════════════════════════════════════════════════════════════ */\nstatic void emit_results(void)\n{\n    int   csq_pct = info.has_csq ? csq_to_percent(info.csq) : -1;\n    float dist_km = -1.0f;\n    const char* siname;\n\n    if (info.has_ta && info.ta_valid && info.ta > 0 && info.ta != 0xFFFFFFFF)\n        dist_km = ta_to_km(info.ta);\n\n    if (info.cid > 0) {\n        info.enb_id      = (guint32)(info.cid >> 8);\n        info.cell_sector = (guint8)(info.cid & 0xFF);\n    }\n\n    if (!json_mode) {\n        siname = info.is_umts ? \"EcIO\" : \"SINR\";\n        printf(\"\\n=================================================\\n\");\n        printf(\"  ModemInfo-QMI — %s\\n\", dev_path);\n        printf(\"=================================================\\n\");\n        printf(\"  Model      : %s\\n\",  info.has_model    ? info.model    : \"N/A\");\n        printf(\"  Firmware   : %s\\n\",  info.has_firmware ? info.firmware : \"N/A\");\n        printf(\"  IMEI       : %s\\n\",  info.has_imei     ? info.imei     : \"N/A\");\n        if (info.has_iccid) printf(\"  ICCID      : %s\\n\", info.iccid);\n        if (info.has_imsi)  printf(\"  IMSI       : %s\\n\", info.imsi);\n        if (info.has_temp)  printf(\"  Temp       : %d C\\n\", info.chip_temp);\n        printf(\"-------------------------------------------------\\n\");\n        printf(\"  Operator   : %s\\n\",  info.cops[0] ? info.cops : \"N/A\");\n        printf(\"  Mode       : %s\\n\",  info.mode[0] ? info.mode : \"N/A\");\n        printf(\"  Reg.State  : %s\\n\",  reg_state_to_string((QmiNasRegistrationState)info.reg_state));\n        printf(\"  LAC/TAC    : %u\\n\",  info.lac);\n        printf(\"  CID        : %llu\\n\",(unsigned long long)info.cid);\n        printf(\"  eNB ID     : %u\\n\",  info.enb_id);\n        printf(\"  Sector     : %u\\n\",  info.cell_sector);\n        if (info.has_arfcn) printf(\"  RF Chan.   : %u\\n\",  info.arfcn);\n        if (info.has_pci)   printf(\"  PCI        : %u\\n\",  info.pci);\n        if (dist_km >= 0)   printf(\"  Distance   : ~%.2f km (TA=%u)\\n\", dist_km, info.ta);\n\t\tif (info.has_bw_dl && strcmp(info.mode, \"LTE\") == 0) \n\t\t\tprintf(\"  BW DL      : %.1f MHz\\n\", bw_index_to_khz(info.bw_dl) / 1000.0f);\n        printf(\"-------------------------------------------------\\n\");\n\tif (info.has_csq)  printf(\"  Strength   : %d%% (CSQ %d)\\n\", csq_pct, info.csq);\n        printf(\"  RSSI       : %d dBm\\n\", info.rssi);\n        if (info.has_rsrp) printf(\"  RSRP       : %d dBm\\n\", (int)roundf(info.rsrp));\n        if (info.has_rsrq) printf(\"  RSRQ       : %d dB\\n\",  (int)roundf(info.rsrq));\n        if (info.has_sinr) printf(\"  %s       : %d dB\\n\", siname, (int)roundf(info.sinr / 10.0f));\n        if (info.has_ca) {\n            printf(\"  LTE-A SCC  : %d — %s\\n\", info.lte_ca, info.scc_bands);\n            printf(\"  BW CA      : %.1f MHz\\n\",   info.bw_ca_total / 1000.0f);\n        }\n        printf(\"=================================================\\n\");\n        return;\n    }\n\n    /* ── JSON ── */\n    char b_model[256], b_cops[128], b_fw[256];\n    char b_imei[64], b_iccid[64], b_imsi[64], b_scc[128];\n    jstr(b_model, sizeof(b_model), info.has_model    ? info.model    : \"\");\n    jstr(b_cops,  sizeof(b_cops),  info.cops[0]      ? info.cops     : \"\");\n    jstr(b_fw,    sizeof(b_fw),    info.has_firmware ? info.firmware : \"\");\n    jstr(b_imei,  sizeof(b_imei),  info.has_imei     ? info.imei     : \"\");\n    jstr(b_iccid, sizeof(b_iccid), info.has_iccid    ? info.iccid    : \"\");\n    jstr(b_imsi,  sizeof(b_imsi),  info.has_imsi     ? info.imsi     : \"\");\n    jstr(b_scc,   sizeof(b_scc),   info.has_ca       ? info.scc_bands_json : \"\");\n\n    const char *csq_col = \"\";\n    if (info.has_csq && csq_pct >= 0)\n        csq_col = (info.csq > 20) ? \"green\" : (info.csq > 10) ? \"orange\" : \"red\";\n\n    char csq_per_str[16] = \"\";\n    if (csq_pct >= 0) snprintf(csq_per_str, sizeof(csq_per_str), \"%d\", csq_pct);\n\n    printf(\"{\\n\");\n    printf(\"  \\\"device\\\"   : \\\"%s\\\",\\n\",  b_model);\n    printf(\"  \\\"cops\\\"     : \\\"%s\\\",\\n\",  b_cops);\n    printf(\"  \\\"mode\\\"     : \\\"%s\\\",\\n\",  info.mode[0] ? info.mode : \"\");\n    printf(\"  \\\"csq_per\\\"  : \\\"%s\\\",\\n\",  csq_per_str);\n    printf(\"  \\\"lac\\\"      : \\\"%u\\\",\\n\",  info.lac);\n    printf(\"  \\\"cid\\\"      : \\\"%llu\\\",\\n\",(unsigned long long)info.cid);\n    printf(\"  \\\"rssi\\\"     : \\\"%d\\\",\\n\",  info.rssi);\n    if (info.has_sinr)\n        printf(\"  \\\"sinr\\\"     : \\\"%d\\\",\\n\", (int)roundf(info.sinr / 10.0f));\n    else\n        printf(\"  \\\"sinr\\\"     : \\\"\\\",\\n\");\n    if (info.has_rsrp)\n        printf(\"  \\\"rsrp\\\"     : \\\"%d\\\",\\n\", (int)roundf(info.rsrp));\n    else\n        printf(\"  \\\"rsrp\\\"     : \\\"\\\",\\n\");\n    if (info.has_rsrq)\n        printf(\"  \\\"rsrq\\\"     : \\\"%d\\\",\\n\", (int)roundf(info.rsrq));\n    else\n        printf(\"  \\\"rsrq\\\"     : \\\"\\\",\\n\");\n    printf(\"  \\\"imei\\\"     : \\\"%s\\\",\\n\",  b_imei);\n    printf(\"  \\\"reg\\\"      : \\\"%d\\\",\\n\",  info.reg_state);\n    printf(\"  \\\"csq_col\\\"  : \\\"%s\\\",\\n\",  csq_col);\n    if (info.has_arfcn)\n        printf(\"  \\\"arfcn\\\"    : \\\"%u\\\",\\n\", info.arfcn);\n    else\n        printf(\"  \\\"arfcn\\\"    : \\\"\\\",\\n\");\n    if (info.has_temp)\n        printf(\"  \\\"chiptemp\\\" : \\\"%d\\\",\\n\", info.chip_temp);\n    else\n        printf(\"  \\\"chiptemp\\\" : \\\"\\\",\\n\");\n    printf(\"  \\\"firmware\\\" : \\\"%s\\\",\\n\",  b_fw);\n\tif (info.has_bw_dl && strcmp(info.mode, \"LTE\") == 0)\n\t\tprintf(\"  \\\"bwdl\\\"     : \\\"%u\\\",\\n\",  info.has_bw_dl ? (guint32)info.bw_dl : 0);\n\telse\n\t\tprintf(\"  \\\"bwdl\\\"     : \\\"\\\",\\n\");\n    printf(\"  \\\"lteca\\\"    : \\\"%d\\\",\\n\",  info.has_ca ? info.lte_ca : 0);\n    printf(\"  \\\"enbid\\\"    : \\\"%u\\\",\\n\",  info.enb_id);\n    if (dist_km >= 0.0f)\n        printf(\"  \\\"distance\\\" : \\\"%.2f\\\",\\n\", dist_km);\n    else\n        printf(\"  \\\"distance\\\" : \\\"\\\",\\n\");\n    printf(\"  \\\"cell\\\"     : \\\"%u\\\",\\n\",  info.cell_sector);\n    printf(\"  \\\"scc\\\"      : \\\"%s\\\",\\n\",  b_scc);\n    printf(\"  \\\"bwca\\\"     : \\\"%u\\\",\\n\",  info.has_ca ? info.bw_ca_total / 1000 : 0);\n    printf(\"  \\\"iccid\\\"    : \\\"%s\\\",\\n\",  b_iccid);\n    printf(\"  \\\"imsi\\\"     : \\\"%s\\\",\\n\",  b_imsi);\n    if (info.has_pci)\n        printf(\"  \\\"pci\\\"      : \\\"%u\\\"\\n\", info.pci);\n    else\n        printf(\"  \\\"pci\\\"      : \\\"\\\"\\n\");\n    printf(\"}\\n\");\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * check_done: release all queries\n * ═══════════════════════════════════════════════════════════════ */\nstatic void check_done(void)\n{\n    pending--;\n    if (pending <= 0) {\n        emit_results();\n        do_release_clients();\n    }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * NAS — Get Signal Strength (CSQ)\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_signal_strength(QmiClientNas *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageNasGetSignalStrengthOutput *out =\n        qmi_client_nas_get_signal_strength_finish(nas, res, &err);\n\n    if (out && qmi_message_nas_get_signal_strength_output_get_result(out, NULL)) {\n        gint8 rssi = 0;\n        QmiNasRadioInterface iface = QMI_NAS_RADIO_INTERFACE_NONE;\n        if (qmi_message_nas_get_signal_strength_output_get_signal_strength(\n                out, &rssi, &iface, NULL)) {\n            int csq = (rssi + 113) / 2;\n            if (csq < 0)  csq = 0;\n            if (csq > 31) csq = 31;\n            info.csq     = csq;\n            info.has_csq = TRUE;\n        }\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_nas_get_signal_strength_output_unref(out);\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * NAS — Get Cell Location Info\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_cell_location(QmiClientNas *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageNasGetCellLocationInfoOutput *out =\n        qmi_client_nas_get_cell_location_info_finish(nas, res, &err);\n\n    if (out && qmi_message_nas_get_cell_location_info_output_get_result(out, NULL)) {\n\n        /* 5G NR ARFCN */\n        guint32 nr_arfcn = 0;\n        if (qmi_message_nas_get_cell_location_info_output_get_nr5g_arfcn(\n                out, &nr_arfcn, NULL) && nr_arfcn) {\n            info.arfcn     = (guint16)nr_arfcn;\n            info.has_arfcn = TRUE;\n        }\n\n        /* LTE intra-frequency v2: EARFCN=guint16, PCI=guint16 */\n        guint16 earfcn = 0, pci16 = 0;\n        GArray *cells  = NULL;\n        if (qmi_message_nas_get_cell_location_info_output_get_intrafrequency_lte_info_v2(\n                out,\n                NULL, NULL, NULL, NULL,\n                &earfcn,\n                &pci16,\n                NULL, NULL, NULL, NULL,\n                &cells,\n                NULL)) {\n            if (earfcn) {\n                info.arfcn     = earfcn;\n                info.has_arfcn = TRUE;\n            }\n            info.pci     = pci16;\n            info.has_pci = TRUE;\n        }\n\n        /* TA */\n        guint32 ta32 = 0xFFFFFFFF;\n        if (qmi_message_nas_get_cell_location_info_output_get_lte_info_timing_advance(\n                out, &ta32, NULL)) {\n            info.ta       = ta32;\n            info.has_ta   = TRUE;\n            info.ta_valid = (ta32 != 0xFFFFFFFF && ta32 != 0x7FFFFFFF && ta32 < 1000);\n        }\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_nas_get_cell_location_info_output_unref(out);\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * NAS — Get LTE phy CA Info\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_lte_ca(QmiClientNas *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageNasGetLteCphyCaInfoOutput *out =\n        qmi_client_nas_get_lte_cphy_ca_info_finish(nas, res, &err);\n\n    if (out && qmi_message_nas_get_lte_cphy_ca_info_output_get_result(out, NULL)) {\n        guint16           pcc_pci  = 0;\n        guint16           pcc_rx   = 0;\n        QmiNasDLBandwidth pcc_bw   = (QmiNasDLBandwidth)0;\n        QmiNasActiveBand  pcc_band = (QmiNasActiveBand)0;\n        if (qmi_message_nas_get_lte_cphy_ca_info_output_get_phy_ca_agg_pcell_info(\n                out, &pcc_pci, &pcc_rx, &pcc_bw, &pcc_band, NULL)) {\n            guint8 bw = (guint8)pcc_bw;\n            if (bw < 6) {\n                info.bw_dl     = bw;\n                info.has_bw_dl = TRUE;\n            }\n        }\n\n        /* SCC — probe scell_info (secondary cell) */\n        {\n            guint16           scc_pci   = 0;\n            guint16           scc_rx    = 0;\n            QmiNasDLBandwidth scc_bw    = (QmiNasDLBandwidth)0;\n            QmiNasActiveBand  scc_band  = (QmiNasActiveBand)0;\n            QmiNasScellState  scc_state = QMI_NAS_SCELL_STATE_DECONFIGURED;\n\n            if (qmi_message_nas_get_lte_cphy_ca_info_output_get_phy_ca_agg_scell_info(\n                    out, &scc_pci, &scc_rx, &scc_bw, &scc_band, &scc_state, NULL)\n                    && scc_state == QMI_NAS_SCELL_STATE_ACTIVATED) {\n                info.has_ca       = TRUE;\n                info.lte_ca       = 1;\n                info.scc_bands[0] = '\\0';\n\t\t\t\tinfo.scc_bands_json[0] = '\\0';\n                guint32 total     = info.has_bw_dl ? bw_index_to_khz(info.bw_dl) : 0;\n                char tmp[16];\n                \n                /* For text mode: B40 */\n                snprintf(tmp, sizeof(tmp), \"B%u\", active_band_to_lte_band(scc_band));\n                g_strlcat(info.scc_bands, tmp, sizeof(info.scc_bands));\n\t\t\t\t/* For json mode: +40 */\n\t\t\t\tsnprintf(tmp, sizeof(tmp), \"+%u\", active_band_to_lte_band(scc_band));\n                g_strlcat(info.scc_bands_json, tmp, sizeof(info.scc_bands_json));\n                \n                total += bw_index_to_khz((guint8)scc_bw);\n                info.bw_ca_total = total;\n            } else {\n                /* Fallback: massive secondary cells */\n                GArray *scc_arr = NULL;\n                if (qmi_message_nas_get_lte_cphy_ca_info_output_get_phy_ca_agg_secondary_cells(\n                        out, &scc_arr, NULL) && scc_arr && scc_arr->len > 0) {\n                    info.has_ca       = TRUE;\n                    info.lte_ca       = (gint)scc_arr->len;\n                    info.scc_bands[0] = '\\0';\n\t\t\t\t\tinfo.scc_bands_json[0] = '\\0';\n                    guint32 total     = info.has_bw_dl ? bw_index_to_khz(info.bw_dl) : 0;\n                    \n                    for (guint i = 0; i < scc_arr->len; i++) {\n                        QmiMessageNasGetLteCphyCaInfoOutputPhyCaAggSecondaryCellsSsc *el =\n                            &g_array_index(scc_arr,\n                                QmiMessageNasGetLteCphyCaInfoOutputPhyCaAggSecondaryCellsSsc, i);\n                        char tmp[16];\n                        \n                        if (i > 0) g_strlcat(info.scc_bands, \",\", sizeof(info.scc_bands));\n\t\t\t\t\t\t/* For text mode: B40 */\n                        snprintf(tmp, sizeof(tmp), \"B%u\", active_band_to_lte_band(el->lte_band));\n                        g_strlcat(info.scc_bands, tmp, sizeof(info.scc_bands));\n\t\t\t\t\t\t/* For json mode: +40 */\n                        snprintf(tmp, sizeof(tmp), \"+%u\", active_band_to_lte_band(el->lte_band));\n                        g_strlcat(info.scc_bands_json, tmp, sizeof(info.scc_bands_json));\t\t\t\t\t\t\n                        \n                        total += bw_index_to_khz((guint8)el->dl_bandwidth);\n                    }\n                    info.bw_ca_total = total;\n                }\n            }\n        }\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_nas_get_lte_cphy_ca_info_output_unref(out);\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * NAS — Get Signal Info (RSRP, RSRQ, SINR)\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_signal_info(QmiClientNas *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageNasGetSignalInfoOutput *out =\n        qmi_client_nas_get_signal_info_finish(nas, res, &err);\n\n    if (out && qmi_message_nas_get_signal_info_output_get_result(out, NULL)) {\n        /* LTE: rssi=gint8, rsrq=gint8, rsrp=gint16, snr=gint16 (все *0.1) */\n        gint8  lrssi = 0, lrsrq = 0;\n        gint16 lrsrp = 0, lsnr  = 0;\n        if (qmi_message_nas_get_signal_info_output_get_lte_signal_strength(\n                out, &lrssi, &lrsrq, &lrsrp, &lsnr, NULL)) {\n            info.rssi     = lrssi;\n            info.rsrq     = lrsrq;\n            info.rsrp     = lrsrp;\n            info.sinr     = lsnr;\n            info.has_rsrp = TRUE;\n            info.has_rsrq = TRUE;\n            info.has_sinr = TRUE;\n            info.is_umts  = FALSE;\n        }\n\n        /* WCDMA: rssi=gint8, ecio=gint16 */\n        gint8  wrssi = 0;\n        gint16 wecio = 0;\n        if (qmi_message_nas_get_signal_info_output_get_wcdma_signal_strength(\n                out, &wrssi, &wecio, NULL)) {\n            info.rssi     = wrssi;\n            info.sinr     = wecio;\n            info.has_sinr = TRUE;\n            info.is_umts  = TRUE;\n        }\n\n        /* GSM */\n        gint8 grssi = 0;\n        if (!info.has_rsrp && !info.has_sinr &&\n            qmi_message_nas_get_signal_info_output_get_gsm_signal_strength(\n                out, &grssi, NULL)) {\n            info.rssi = grssi;\n            info.is_umts = FALSE;\n        }\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_nas_get_signal_info_output_unref(out);\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * NAS — Get RF Band Information (для ARFCN в 3G/UMTS)\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_rf_band_info(QmiClientNas *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageNasGetRfBandInformationOutput *out =\n        qmi_client_nas_get_rf_band_information_finish(nas, res, &err);\n\n    if (out && qmi_message_nas_get_rf_band_information_output_get_result(out, NULL)) {\n        GArray *list = NULL;\n        \n        /* get main list band information */\n        if (qmi_message_nas_get_rf_band_information_output_get_list(\n                out, &list, NULL) && list && list->len > 0) {\n            \n            QmiMessageNasGetRfBandInformationOutputListElement *el =\n                &g_array_index(list, QmiMessageNasGetRfBandInformationOutputListElement, 0);\n            \n            /* if non LTE/5GNR (UMTS/GSM) */\n            if (el->radio_interface != QMI_NAS_RADIO_INTERFACE_LTE && \n                el->radio_interface != QMI_NAS_RADIO_INTERFACE_5GNR &&\n                el->active_channel != 0) {\n                \n                /* ARFCN non LTE */\n                if (!info.has_arfcn) {\n                    info.arfcn = el->active_channel;\n                    info.has_arfcn = TRUE;\n                }\n            }\n        }\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_nas_get_rf_band_information_output_unref(out);\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * NAS — Get Serving System\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_serving_system(QmiClientNas *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageNasGetServingSystemOutput *out =\n        qmi_client_nas_get_serving_system_finish(nas, res, &err);\n\n    if (out && qmi_message_nas_get_serving_system_output_get_result(out, NULL)) {\n        QmiNasRegistrationState reg = 0;\n        QmiNasAttachState cs = 0, ps = 0;\n        QmiNasNetworkType net = 0;\n        GArray *ifaces = NULL;\n\n        if (qmi_message_nas_get_serving_system_output_get_serving_system(\n                out, &reg, &cs, &ps, &net, &ifaces, NULL)) {\n            info.reg_state = (gint)reg;\n            if (ifaces && ifaces->len > 0) {\n                QmiNasRadioInterface pri =\n                    g_array_index(ifaces, QmiNasRadioInterface, 0);\n                strncpy(info.mode, radio_to_mode(pri), sizeof(info.mode) - 1);\n            }\n        }\n\n        const gchar *opname = NULL;\n        guint16 mcc = 0, mnc = 0;\n        if (qmi_message_nas_get_serving_system_output_get_current_plmn(\n                out, &mcc, &mnc, &opname, NULL) && opname)\n            strncpy(info.cops, opname, sizeof(info.cops) - 1);\n\n        /* LTE TAC */\n        guint16 tac_tmp = 0;\n        if (qmi_message_nas_get_serving_system_output_get_lte_tac(\n                out, &tac_tmp, NULL) && tac_tmp != 0xFFFF && tac_tmp != 0xFFFE) {\n            info.lac = tac_tmp;\n        } else {\n            /* Fallback: LAC 3GPP */\n            guint16 lac_tmp = 0;\n            if (qmi_message_nas_get_serving_system_output_get_lac_3gpp(\n                    out, &lac_tmp, NULL) && lac_tmp != 0xFFFE && lac_tmp != 0xFFFF) {\n                info.lac = lac_tmp;\n            }\n        }\n\n        guint32 cid32 = 0;\n        if (qmi_message_nas_get_serving_system_output_get_cid_3gpp(out, &cid32, NULL)\n                && cid32 != 0xFFFFFFFF)\n            info.cid = cid32;\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_nas_get_serving_system_output_unref(out);\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * DMS — Get IMSI\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_imsi(QmiClientDms *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageDmsUimGetImsiOutput *out =\n        qmi_client_dms_uim_get_imsi_finish(dms, res, &err);\n\n    if (out && qmi_message_dms_uim_get_imsi_output_get_result(out, NULL)) {\n        const gchar *v = NULL;\n        if (qmi_message_dms_uim_get_imsi_output_get_imsi(out, &v, NULL) && v) {\n            strncpy(info.imsi, v, sizeof(info.imsi) - 1);\n            info.has_imsi = TRUE;\n        }\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_dms_uim_get_imsi_output_unref(out);\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * DMS — Get ICCID\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_iccid(QmiClientDms *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageDmsUimGetIccidOutput *out =\n        qmi_client_dms_uim_get_iccid_finish(dms, res, &err);\n\n    if (out && qmi_message_dms_uim_get_iccid_output_get_result(out, NULL)) {\n        const gchar *v = NULL;\n        if (qmi_message_dms_uim_get_iccid_output_get_iccid(out, &v, NULL) && v) {\n            strncpy(info.iccid, v, sizeof(info.iccid) - 1);\n            info.has_iccid = TRUE;\n        }\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_dms_uim_get_iccid_output_unref(out);\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * DMS — Chip Temperature (sysfs)\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_temp(QmiClientDms *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageDmsGetPowerStateOutput *out =\n        qmi_client_dms_get_power_state_finish(dms, res, &err);\n    g_clear_error(&err);\n    if (out) qmi_message_dms_get_power_state_output_unref(out);\n\n    static const char *paths[] = {\n        \"/sys/class/thermal/thermal_zone0/temp\",\n        \"/sys/class/thermal/thermal_zone1/temp\",\n        \"/sys/class/thermal/thermal_zone2/temp\",\n        NULL\n    };\n    for (int i = 0; paths[i]; i++) {\n        FILE *f = fopen(paths[i], \"r\");\n        if (!f) continue;\n        int val = 0;\n        if (fscanf(f, \"%d\", &val) == 1 && val > 0) {\n            info.chip_temp = (val > 1000) ? val / 1000 : val;\n            info.has_temp  = TRUE;\n            fclose(f);\n            break;\n        }\n        fclose(f);\n    }\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * DMS — Get Firmware Version\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_firmware(QmiClientDms *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageDmsGetSoftwareVersionOutput *out =\n        qmi_client_dms_get_software_version_finish(dms, res, &err);\n\n    if (out && qmi_message_dms_get_software_version_output_get_result(out, NULL)) {\n        const gchar *v = NULL;\n        if (qmi_message_dms_get_software_version_output_get_version(out, &v, NULL) && v) {\n            strncpy(info.firmware, v, sizeof(info.firmware) - 1);\n            info.has_firmware = TRUE;\n        }\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_dms_get_software_version_output_unref(out);\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * run processes if DMS and NAS ready. Model recieved\n * ═══════════════════════════════════════════════════════════════ */\n/* forward declaration */\nstatic void on_manufacturer(QmiClientDms *client, GAsyncResult *res, gpointer ud);\n\nstatic void fire_all_requests(void)\n{\n    pending = 11;\n\n    qmi_client_dms_get_manufacturer(dms, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_manufacturer, NULL);\n    qmi_client_dms_get_software_version(dms, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_firmware, NULL);\n    qmi_client_dms_uim_get_iccid(dms, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_iccid, NULL);\n    qmi_client_dms_uim_get_imsi(dms, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_imsi, NULL);\n    qmi_client_dms_get_power_state(dms, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_temp, NULL);\n\n    qmi_client_nas_get_serving_system(nas, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_serving_system, NULL);\n    qmi_client_nas_get_signal_info(nas, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_signal_info, NULL);\n    qmi_client_nas_get_signal_strength(nas, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_signal_strength, NULL);\n    qmi_client_nas_get_cell_location_info(nas, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_cell_location, NULL);\n    qmi_client_nas_get_lte_cphy_ca_info(nas, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_lte_ca, NULL);\n    qmi_client_nas_get_rf_band_information(nas, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_rf_band_info, NULL);\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * DMS — Get Manufacturer (merge of USB ID)\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_manufacturer(QmiClientDms *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageDmsGetManufacturerOutput *out =\n        qmi_client_dms_get_manufacturer_finish(dms, res, &err);\n\n    /* if no USB data use libQMI  */\n    if (!info.has_model && out && qmi_message_dms_get_manufacturer_output_get_result(out, NULL)) {\n        const gchar *v = NULL;\n        if (qmi_message_dms_get_manufacturer_output_get_manufacturer(out, &v, NULL) && v) {\n            /* Skip Qualcomm name */\n            if (g_ascii_strncasecmp(v, \"QUALCOMM\", 8) != 0) {\n                snprintf(info.model, sizeof(info.model), \"%s\", v);\n                info.has_model = TRUE;\n            }\n        }\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_dms_get_manufacturer_output_unref(out);\n    check_done();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * DMS — Get Model: save model in USB ID and libQMI\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_model(QmiClientDms *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageDmsGetModelOutput *out =\n        qmi_client_dms_get_model_finish(dms, res, &err);\n\n    if (out && qmi_message_dms_get_model_output_get_result(out, NULL)) {\n        const gchar *v = NULL;\n        if (qmi_message_dms_get_model_output_get_model(out, &v, NULL) && v)\n            strncpy(info.model, v, sizeof(info.model) - 1);\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_dms_get_model_output_unref(out);\n\n    /* Read USB ID manufacturer and product */\n    char usb_manufacturer[128] = {0};\n    char usb_product[128] = {0};\n    \n    const char *dev_name = strrchr(dev_path, '/');\n    if (dev_name)\n        dev_name++;\n    else\n        dev_name = dev_path;\n    \n    read_usb_id(dev_name, usb_manufacturer, sizeof(usb_manufacturer), \n                usb_product, sizeof(usb_product));\n    \n    /* Former model at USB ID */\n    if (usb_manufacturer[0] && usb_product[0]) {\n        /* Check duplicate words manufacturer and product */\n        if (g_ascii_strncasecmp(usb_product, usb_manufacturer, strlen(usb_manufacturer)) == 0) {\n            /*  Only product, if manufacturer exist */\n            snprintf(info.model, sizeof(info.model), \"%s\", usb_product);\n        } else {\n            snprintf(info.model, sizeof(info.model), \"%s %s\", \n                    usb_manufacturer, usb_product);\n        }\n        info.has_model = TRUE;\n    } else if (usb_product[0]) {\n        g_strlcpy(info.model, usb_product, sizeof(info.model));\n        info.has_model = TRUE;\n    } else if (usb_manufacturer[0]) {\n        g_strlcpy(info.model, usb_manufacturer, sizeof(info.model));\n        info.has_model = TRUE;\n    }\n\n    fire_all_requests();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * DMS — Get IDs (IMEI)\n * ═══════════════════════════════════════════════════════════════ */\nstatic void on_ids(QmiClientDms *client, GAsyncResult *res, gpointer ud)\n{\n    (void)client; (void)ud;\n    GError *err = NULL;\n    QmiMessageDmsGetIdsOutput *out =\n        qmi_client_dms_get_ids_finish(dms, res, &err);\n\n    if (out && qmi_message_dms_get_ids_output_get_result(out, NULL)) {\n        const gchar *v = NULL;\n        if (qmi_message_dms_get_ids_output_get_imei(out, &v, NULL) && v) {\n            strncpy(info.imei, v, sizeof(info.imei) - 1);\n            info.has_imei = TRUE;\n        }\n    }\n    g_clear_error(&err);\n    if (out) qmi_message_dms_get_ids_output_unref(out);\n\n    qmi_client_dms_get_model(dms, NULL, 10, NULL,\n        (GAsyncReadyCallback)on_model, NULL);\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * Open clients\n * ═══════════════════════════════════════════════════════════════ */\n/* clients_ready: timer — if all (DMS+NAS) open */\nstatic gint clients_ready = 0;\n\nstatic void start_dms_chain(void)\n{\n    /* run DMS if all clients ready */\n    clients_ready++;\n    if (clients_ready == 2)\n        qmi_client_dms_get_ids(dms, NULL, 10, NULL,\n            (GAsyncReadyCallback)on_ids, NULL);\n}\n\nstatic void on_nas_client(QmiDevice *dev, GAsyncResult *res, gpointer ud)\n{\n    (void)dev; (void)ud;\n    GError *err = NULL;\n    QmiClient *c = qmi_device_allocate_client_finish(device, res, &err);\n    if (!c) {\n        fprintf(stderr, \"NAS client error: %s\\n\", err ? err->message : \"?\");\n        g_clear_error(&err);\n        g_main_loop_quit(loop);\n        return;\n    }\n    nas = QMI_CLIENT_NAS(c);\n    start_dms_chain();\n}\n\nstatic void on_dms_client(QmiDevice *dev, GAsyncResult *res, gpointer ud)\n{\n    (void)dev; (void)ud;\n    GError *err = NULL;\n    QmiClient *c = qmi_device_allocate_client_finish(device, res, &err);\n    if (!c) {\n        fprintf(stderr, \"DMS client error: %s\\n\", err ? err->message : \"?\");\n        g_clear_error(&err);\n        g_main_loop_quit(loop);\n        return;\n    }\n    dms = QMI_CLIENT_DMS(c);\n\n    /* open NAS */\n    qmi_device_allocate_client(device, QMI_SERVICE_NAS,\n        QMI_CID_NONE, 10, NULL,\n        (GAsyncReadyCallback)on_nas_client, NULL);\n\n    start_dms_chain();\n}\n\nstatic void try_open_device(void);  /* forward */\n\nstatic void on_device_open(QmiDevice *dev, GAsyncResult *res, gpointer ud)\n{\n    (void)dev; (void)ud;\n    GError *err = NULL;\n    if (!qmi_device_open_finish(device, res, &err)) {\n        if (device_mode == MODE_AUTO && open_retries == 0) {\n            /* QMI failed in auto mode — retry with MBIM */\n            g_clear_error(&err);\n            open_retries++;\n            try_open_device();\n            return;\n        }\n        fprintf(stderr, \"Open error: %s\\n\", err ? err->message : \"?\");\n        g_clear_error(&err);\n        g_main_loop_quit(loop);\n        return;\n    }\n\n    /* Restore normal log handler after successful open in auto mode */\n    if (device_mode == MODE_AUTO)\n        g_log_set_handler(\"Qmi\",\n            G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL,\n            g_log_default_handler, NULL);\n\n    qmi_device_allocate_client(device, QMI_SERVICE_DMS,\n        QMI_CID_NONE, 10, NULL,\n        (GAsyncReadyCallback)on_dms_client, NULL);\n}\n\n/* Open device with flags depending on mode / retry state */\nstatic void try_open_device(void)\n{\n    QmiDeviceOpenFlags flags = QMI_DEVICE_OPEN_FLAGS_PROXY;\n    gboolean mbim;\n\n    switch (device_mode) {\n    case MODE_MBIM:\n        mbim = TRUE;\n        break;\n    case MODE_QMI:\n        mbim = FALSE;\n        break;\n    case MODE_AUTO:\n    default:\n        /* First attempt: QMI; after retry: MBIM */\n        mbim = (open_retries > 0);\n        break;\n    }\n\n    if (mbim) {\n        flags |= QMI_DEVICE_OPEN_FLAGS_MBIM;\n        //fprintf(stderr, \"Opening as QMI over MBIM\\n\");\n    } else {\n        flags |= QMI_DEVICE_OPEN_FLAGS_NET_802_3 |\n                 QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER;\n    }\n\n    qmi_device_open(device, flags, 15, NULL,\n        (GAsyncReadyCallback)on_device_open, NULL);\n}\n\nstatic void on_device_new(GObject *src, GAsyncResult *res, gpointer ud)\n{\n    (void)src; (void)ud;\n    GError *err = NULL;\n    device = qmi_device_new_finish(res, &err);\n    if (!device) {\n        fprintf(stderr, \"Device error: %s\\n\", err ? err->message : \"?\");\n        g_clear_error(&err);\n        g_main_loop_quit(loop);\n        return;\n    }\n    try_open_device();\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * Silent log handler — suppresses GLib/QMI warnings\n * ═══════════════════════════════════════════════════════════════ */\nstatic void log_silent(const gchar    *domain,\n                       GLogLevelFlags  level,\n                       const gchar    *message,\n                       gpointer        user_data)\n{\n    (void)domain; (void)level; (void)message; (void)user_data;\n    /* intentionally empty */\n}\n\n/* ═══════════════════════════════════════════════════════════════\n * main\n * ═══════════════════════════════════════════════════════════════ */\nint main(int argc, char *argv[])\n{\n    if (argc < 2) {\n        fprintf(stderr, \"Usage: %s <device> [--json] [--qmi|--mbim]\\n\", argv[0]);\n        fprintf(stderr, \"  --json   JSON output\\n\");\n        fprintf(stderr, \"  --qmi    force native QMI mode\\n\");\n        fprintf(stderr, \"  --mbim   force QMI over MBIM mode\\n\");\n        fprintf(stderr, \"  (default: auto-detect, try QMI first)\\n\");\n        fprintf(stderr, \"Example: %s /dev/cdc-wdm0 --json\\n\", argv[0]);\n        fprintf(stderr, \"         %s /dev/cdc-wdm0 --mbim --json\\n\", argv[0]);\n        return EXIT_FAILURE;\n    }\n\n    dev_path    = argv[1];\n    json_mode   = FALSE;\n    device_mode = MODE_AUTO;\n\n    for (int i = 2; i < argc; i++) {\n        if (strcmp(argv[i], \"--json\") == 0)\n            json_mode = TRUE;\n        else if (strcmp(argv[i], \"--mbim\") == 0)\n            device_mode = MODE_MBIM;\n        else if (strcmp(argv[i], \"--qmi\") == 0)\n            device_mode = MODE_QMI;\n    }\n\n    memset(&info, 0, sizeof(info));\n    info.chip_temp = -999;\n    info.csq       = -1;\n    info.is_umts   = FALSE;\n\n    /* In auto-detect mode, suppress QMI warnings that are expected\n     * when probing QMI on an MBIM-only device:\n     *   \"requested QMI mode but unexpected transport type found\"\n     *   \"Cannot read from istream: connection broken\" */\n    if (device_mode == MODE_AUTO) {\n        g_log_set_handler(\"Qmi\",\n            G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL,\n            log_silent, NULL);\n    }\n\n    loop = g_main_loop_new(NULL, FALSE);\n\n    GFile *f = g_file_new_for_path(dev_path);\n    qmi_device_new(f, NULL, (GAsyncReadyCallback)on_device_new, NULL);\n    g_object_unref(f);\n\n    g_main_loop_run(loop);\n\n    /* destroy sesion do_release_clients() */\n    if (device) {\n        g_object_unref(device);\n        device = NULL;\n    }\n    g_main_loop_unref(loop);\n\n    return EXIT_SUCCESS;\n}\n"
  }
]