summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-mod-system
diff options
context:
space:
mode:
authorDaniel F. Dickinson <cshored@thecshore.com>2018-08-03 12:36:51 -0400
committerJo-Philipp Wich <jo@mein.io>2018-09-19 20:08:19 +0200
commit58d97b5e271bc0d7507eab5b9bd2902181864e02 (patch)
tree80e250346ad33c79b3f821daf7b7d9be90d99240 /modules/luci-mod-system
parent6ec0353201435e0d0d7d32820d8ba600b4ca7b5b (diff)
modules: Split luci-mod-full
Move some common elements to luci-base, and otherwise make three packages out of status, system, and network. They were mostly separated already, but there were some shared elements between status and network that are now in luci-base. Signed-off-by: Daniel F. Dickinson <cshored@thecshore.com>
Diffstat (limited to 'modules/luci-mod-system')
-rw-r--r--modules/luci-mod-system/Makefile17
-rw-r--r--modules/luci-mod-system/luasrc/controller/admin/system.lua469
-rw-r--r--modules/luci-mod-system/luasrc/model/cbi/admin_system/admin.lua122
-rw-r--r--modules/luci-mod-system/luasrc/model/cbi/admin_system/backupfiles.lua80
-rw-r--r--modules/luci-mod-system/luasrc/model/cbi/admin_system/crontab.lua32
-rw-r--r--modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab.lua270
-rw-r--r--modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/mount.lua151
-rw-r--r--modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/swap.lua54
-rw-r--r--modules/luci-mod-system/luasrc/model/cbi/admin_system/ipkg.lua64
-rw-r--r--modules/luci-mod-system/luasrc/model/cbi/admin_system/leds.lua158
-rw-r--r--modules/luci-mod-system/luasrc/model/cbi/admin_system/startup.lua97
-rw-r--r--modules/luci-mod-system/luasrc/model/cbi/admin_system/system.lua224
-rw-r--r--modules/luci-mod-system/luasrc/view/admin_system/applyreboot.htm53
-rw-r--r--modules/luci-mod-system/luasrc/view/admin_system/backupfiles.htm10
-rw-r--r--modules/luci-mod-system/luasrc/view/admin_system/clock_status.htm36
-rw-r--r--modules/luci-mod-system/luasrc/view/admin_system/flashops.htm137
-rw-r--r--modules/luci-mod-system/luasrc/view/admin_system/ipkg.htm10
-rw-r--r--modules/luci-mod-system/luasrc/view/admin_system/packages.htm213
-rw-r--r--modules/luci-mod-system/luasrc/view/admin_system/reboot.htm62
-rw-r--r--modules/luci-mod-system/luasrc/view/admin_system/upgrade.htm65
20 files changed, 2324 insertions, 0 deletions
diff --git a/modules/luci-mod-system/Makefile b/modules/luci-mod-system/Makefile
new file mode 100644
index 000000000..a6d5a7a45
--- /dev/null
+++ b/modules/luci-mod-system/Makefile
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI Administration - Global System Settings
+LUCI_DEPENDS:=+luci-base
+
+PKG_LICENSE:=Apache-2.0
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
+
diff --git a/modules/luci-mod-system/luasrc/controller/admin/system.lua b/modules/luci-mod-system/luasrc/controller/admin/system.lua
new file mode 100644
index 000000000..4e83769ee
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/controller/admin/system.lua
@@ -0,0 +1,469 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Copyright 2008-2011 Jo-Philipp Wich <jow@openwrt.org>
+-- Licensed to the public under the Apache License 2.0.
+
+module("luci.controller.admin.system", package.seeall)
+
+function index()
+ local fs = require "nixio.fs"
+
+ entry({"admin", "system", "system"}, cbi("admin_system/system"), _("System"), 1)
+ entry({"admin", "system", "clock_status"}, post_on({ set = true }, "action_clock_status"))
+
+ entry({"admin", "system", "admin"}, cbi("admin_system/admin"), _("Administration"), 2)
+
+ if fs.access("/bin/opkg") then
+ entry({"admin", "system", "packages"}, post_on({ exec = "1" }, "action_packages"), _("Software"), 10)
+ entry({"admin", "system", "packages", "ipkg"}, form("admin_system/ipkg"))
+ end
+
+ entry({"admin", "system", "startup"}, form("admin_system/startup"), _("Startup"), 45)
+ entry({"admin", "system", "crontab"}, form("admin_system/crontab"), _("Scheduled Tasks"), 46)
+
+ if fs.access("/sbin/block") and fs.access("/etc/config/fstab") then
+ entry({"admin", "system", "fstab"}, cbi("admin_system/fstab"), _("Mount Points"), 50)
+ entry({"admin", "system", "fstab", "mount"}, cbi("admin_system/fstab/mount"), nil).leaf = true
+ entry({"admin", "system", "fstab", "swap"}, cbi("admin_system/fstab/swap"), nil).leaf = true
+ end
+
+ local nodes, number = fs.glob("/sys/class/leds/*")
+ if number > 0 then
+ entry({"admin", "system", "leds"}, cbi("admin_system/leds"), _("<abbr title=\"Light Emitting Diode\">LED</abbr> Configuration"), 60)
+ end
+
+ entry({"admin", "system", "flashops"}, call("action_flashops"), _("Backup / Flash Firmware"), 70)
+ entry({"admin", "system", "flashops", "reset"}, post("action_reset"))
+ entry({"admin", "system", "flashops", "backup"}, post("action_backup"))
+ entry({"admin", "system", "flashops", "backupmtdblock"}, post("action_backupmtdblock"))
+ entry({"admin", "system", "flashops", "backupfiles"}, form("admin_system/backupfiles"))
+
+ -- call() instead of post() due to upload handling!
+ entry({"admin", "system", "flashops", "restore"}, call("action_restore"))
+ entry({"admin", "system", "flashops", "sysupgrade"}, call("action_sysupgrade"))
+
+ entry({"admin", "system", "reboot"}, template("admin_system/reboot"), _("Reboot"), 90)
+ entry({"admin", "system", "reboot", "call"}, post("action_reboot"))
+end
+
+function action_clock_status()
+ local set = tonumber(luci.http.formvalue("set"))
+ if set ~= nil and set > 0 then
+ local date = os.date("*t", set)
+ if date then
+ luci.sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d'" %{
+ date.year, date.month, date.day, date.hour, date.min, date.sec
+ })
+ luci.sys.call("/etc/init.d/sysfixtime restart")
+ end
+ end
+
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({ timestring = os.date("%c") })
+end
+
+function action_packages()
+ local fs = require "nixio.fs"
+ local ipkg = require "luci.model.ipkg"
+ local submit = (luci.http.formvalue("exec") == "1")
+ local update, upgrade
+ local changes = false
+ local install = { }
+ local remove = { }
+ local stdout = { "" }
+ local stderr = { "" }
+ local out, err
+
+ -- Display
+ local display = luci.http.formvalue("display") or "available"
+
+ -- Letter
+ local letter = string.byte(luci.http.formvalue("letter") or "A", 1)
+ letter = (letter == 35 or (letter >= 65 and letter <= 90)) and letter or 65
+
+ -- Search query
+ local query = luci.http.formvalue("query")
+ query = (query ~= '') and query or nil
+
+
+ -- Modifying actions
+ if submit then
+ -- Packets to be installed
+ local ninst = luci.http.formvalue("install")
+ local uinst = nil
+
+ -- Install from URL
+ local url = luci.http.formvalue("url")
+ if url and url ~= '' then
+ uinst = url
+ end
+
+ -- Do install
+ if ninst then
+ install[ninst], out, err = ipkg.install(ninst)
+ stdout[#stdout+1] = out
+ stderr[#stderr+1] = err
+ changes = true
+ end
+
+ if uinst then
+ local pkg
+ for pkg in luci.util.imatch(uinst) do
+ install[uinst], out, err = ipkg.install(pkg)
+ stdout[#stdout+1] = out
+ stderr[#stderr+1] = err
+ changes = true
+ end
+ end
+
+ -- Remove packets
+ local rem = luci.http.formvalue("remove")
+ if rem then
+ remove[rem], out, err = ipkg.remove(rem)
+ stdout[#stdout+1] = out
+ stderr[#stderr+1] = err
+ changes = true
+ end
+
+
+ -- Update all packets
+ update = luci.http.formvalue("update")
+ if update then
+ update, out, err = ipkg.update()
+ stdout[#stdout+1] = out
+ stderr[#stderr+1] = err
+ end
+
+
+ -- Upgrade all packets
+ upgrade = luci.http.formvalue("upgrade")
+ if upgrade then
+ upgrade, out, err = ipkg.upgrade()
+ stdout[#stdout+1] = out
+ stderr[#stderr+1] = err
+ end
+ end
+
+
+ -- List state
+ local no_lists = true
+ local old_lists = false
+ if fs.access("/var/opkg-lists/") then
+ local list
+ for list in fs.dir("/var/opkg-lists/") do
+ no_lists = false
+ if (fs.stat("/var/opkg-lists/"..list, "mtime") or 0) < (os.time() - (24 * 60 * 60)) then
+ old_lists = true
+ break
+ end
+ end
+ end
+
+
+ luci.template.render("admin_system/packages", {
+ display = display,
+ letter = letter,
+ query = query,
+ install = install,
+ remove = remove,
+ update = update,
+ upgrade = upgrade,
+ no_lists = no_lists,
+ old_lists = old_lists,
+ stdout = table.concat(stdout, ""),
+ stderr = table.concat(stderr, "")
+ })
+
+ -- Remove index cache
+ if changes then
+ fs.unlink("/tmp/luci-indexcache")
+ end
+end
+
+local function image_supported(image)
+ return (os.execute("sysupgrade -T %q >/dev/null" % image) == 0)
+end
+
+local function image_checksum(image)
+ return (luci.sys.exec("md5sum %q" % image):match("^([^%s]+)"))
+end
+
+local function image_sha256_checksum(image)
+ return (luci.sys.exec("sha256sum %q" % image):match("^([^%s]+)"))
+end
+
+local function supports_sysupgrade()
+ return nixio.fs.access("/lib/upgrade/platform.sh")
+end
+
+local function supports_reset()
+ return (os.execute([[grep -sq "^overlayfs:/overlay / overlay " /proc/mounts]]) == 0)
+end
+
+local function storage_size()
+ local size = 0
+ if nixio.fs.access("/proc/mtd") then
+ for l in io.lines("/proc/mtd") do
+ local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
+ if n == "linux" or n == "firmware" then
+ size = tonumber(s, 16)
+ break
+ end
+ end
+ elseif nixio.fs.access("/proc/partitions") then
+ for l in io.lines("/proc/partitions") do
+ local x, y, b, n = l:match('^%s*(%d+)%s+(%d+)%s+([^%s]+)%s+([^%s]+)')
+ if b and n and not n:match('[0-9]') then
+ size = tonumber(b) * 1024
+ break
+ end
+ end
+ end
+ return size
+end
+
+
+function action_flashops()
+ --
+ -- Overview
+ --
+ luci.template.render("admin_system/flashops", {
+ reset_avail = supports_reset(),
+ upgrade_avail = supports_sysupgrade()
+ })
+end
+
+function action_sysupgrade()
+ local fs = require "nixio.fs"
+ local http = require "luci.http"
+ local image_tmp = "/tmp/firmware.img"
+
+ local fp
+ http.setfilehandler(
+ function(meta, chunk, eof)
+ if not fp and meta and meta.name == "image" then
+ fp = io.open(image_tmp, "w")
+ end
+ if fp and chunk then
+ fp:write(chunk)
+ end
+ if fp and eof then
+ fp:close()
+ end
+ end
+ )
+
+ if not luci.dispatcher.test_post_security() then
+ fs.unlink(image_tmp)
+ return
+ end
+
+ --
+ -- Cancel firmware flash
+ --
+ if http.formvalue("cancel") then
+ fs.unlink(image_tmp)
+ http.redirect(luci.dispatcher.build_url('admin/system/flashops'))
+ return
+ end
+
+ --
+ -- Initiate firmware flash
+ --
+ local step = tonumber(http.formvalue("step")) or 1
+ if step == 1 then
+ local force = http.formvalue("force")
+ if image_supported(image_tmp) or force then
+ luci.template.render("admin_system/upgrade", {
+ checksum = image_checksum(image_tmp),
+ sha256ch = image_sha256_checksum(image_tmp),
+ storage = storage_size(),
+ size = (fs.stat(image_tmp, "size") or 0),
+ keep = (not not http.formvalue("keep")),
+ force = (not not http.formvalue("force"))
+ })
+ else
+ fs.unlink(image_tmp)
+ luci.template.render("admin_system/flashops", {
+ reset_avail = supports_reset(),
+ upgrade_avail = supports_sysupgrade(),
+ image_invalid = true
+ })
+ end
+
+ --
+ -- Start sysupgrade flash
+ --
+ elseif step == 2 then
+ local keep = (http.formvalue("keep") == "1") and "" or "-n"
+ local force = (http.formvalue("force") == "1") and "-F" or ""
+ luci.template.render("admin_system/applyreboot", {
+ title = luci.i18n.translate("Flashing..."),
+ msg = luci.i18n.translate("The system is flashing now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a few minutes before you try to reconnect. It might be necessary to renew the address of your computer to reach the device again, depending on your settings."),
+ addr = (#keep > 0) and (#force > 0) and "192.168.1.1" or nil
+ })
+ fork_exec("sleep 1; killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %s %q" %{ keep, force, image_tmp })
+ end
+end
+
+function action_backup()
+ local reader = ltn12_popen("sysupgrade --create-backup - 2>/dev/null")
+
+ luci.http.header(
+ 'Content-Disposition', 'attachment; filename="backup-%s-%s.tar.gz"' %{
+ luci.sys.hostname(),
+ os.date("%Y-%m-%d")
+ })
+
+ luci.http.prepare_content("application/x-targz")
+ luci.ltn12.pump.all(reader, luci.http.write)
+end
+
+function action_backupmtdblock()
+ local http = require "luci.http"
+ local mv = http.formvalue("mtdblockname")
+ local m, s, n = mv:match('^([^%s]+)/([^%s]+)/([^%s]+)')
+
+ local reader = ltn12_popen("dd if=/dev/mtd%s conv=fsync,notrunc 2>/dev/null" % n)
+
+ luci.http.header(
+ 'Content-Disposition', 'attachment; filename="backup-%s-%s-%s.bin"' %{
+ luci.sys.hostname(), m,
+ os.date("%Y-%m-%d")
+ })
+
+ luci.http.prepare_content("application/octet-stream")
+ luci.ltn12.pump.all(reader, luci.http.write)
+end
+
+function action_restore()
+ local fs = require "nixio.fs"
+ local http = require "luci.http"
+ local archive_tmp = "/tmp/restore.tar.gz"
+
+ local fp
+ http.setfilehandler(
+ function(meta, chunk, eof)
+ if not fp and meta and meta.name == "archive" then
+ fp = io.open(archive_tmp, "w")
+ end
+ if fp and chunk then
+ fp:write(chunk)
+ end
+ if fp and eof then
+ fp:close()
+ end
+ end
+ )
+
+ if not luci.dispatcher.test_post_security() then
+ fs.unlink(archive_tmp)
+ return
+ end
+
+ local upload = http.formvalue("archive")
+ if upload and #upload > 0 then
+ if os.execute("gunzip -t %q >/dev/null 2>&1" % archive_tmp) == 0 then
+ luci.template.render("admin_system/applyreboot")
+ os.execute("tar -C / -xzf %q >/dev/null 2>&1" % archive_tmp)
+ luci.sys.reboot()
+ else
+ luci.template.render("admin_system/flashops", {
+ reset_avail = supports_reset(),
+ upgrade_avail = supports_sysupgrade(),
+ backup_invalid = true
+ })
+ end
+ return
+ end
+
+ http.redirect(luci.dispatcher.build_url('admin/system/flashops'))
+end
+
+function action_reset()
+ if supports_reset() then
+ luci.template.render("admin_system/applyreboot", {
+ title = luci.i18n.translate("Erasing..."),
+ msg = luci.i18n.translate("The system is erasing the configuration partition now and will reboot itself when finished."),
+ addr = "192.168.1.1"
+ })
+
+ fork_exec("sleep 1; killall dropbear uhttpd; sleep 1; jffs2reset -y && reboot")
+ return
+ end
+
+ http.redirect(luci.dispatcher.build_url('admin/system/flashops'))
+end
+
+function action_passwd()
+ local p1 = luci.http.formvalue("pwd1")
+ local p2 = luci.http.formvalue("pwd2")
+ local stat = nil
+
+ if p1 or p2 then
+ if p1 == p2 then
+ stat = luci.sys.user.setpasswd("root", p1)
+ else
+ stat = 10
+ end
+ end
+
+ luci.template.render("admin_system/passwd", {stat=stat})
+end
+
+function action_reboot()
+ luci.sys.reboot()
+end
+
+function fork_exec(command)
+ local pid = nixio.fork()
+ if pid > 0 then
+ return
+ elseif pid == 0 then
+ -- change to root dir
+ nixio.chdir("/")
+
+ -- patch stdin, out, err to /dev/null
+ local null = nixio.open("/dev/null", "w+")
+ if null then
+ nixio.dup(null, nixio.stderr)
+ nixio.dup(null, nixio.stdout)
+ nixio.dup(null, nixio.stdin)
+ if null:fileno() > 2 then
+ null:close()
+ end
+ end
+
+ -- replace with target command
+ nixio.exec("/bin/sh", "-c", command)
+ end
+end
+
+function ltn12_popen(command)
+
+ local fdi, fdo = nixio.pipe()
+ local pid = nixio.fork()
+
+ if pid > 0 then
+ fdo:close()
+ local close
+ return function()
+ local buffer = fdi:read(2048)
+ local wpid, stat = nixio.waitpid(pid, "nohang")
+ if not close and wpid and stat == "exited" then
+ close = true
+ end
+
+ if buffer and #buffer > 0 then
+ return buffer
+ elseif close then
+ fdi:close()
+ return nil
+ end
+ end
+ elseif pid == 0 then
+ nixio.dup(fdo, nixio.stdout)
+ fdi:close()
+ fdo:close()
+ nixio.exec("/bin/sh", "-c", command)
+ end
+end
diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/admin.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/admin.lua
new file mode 100644
index 000000000..6c1c1235c
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/model/cbi/admin_system/admin.lua
@@ -0,0 +1,122 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org>
+-- Licensed to the public under the Apache License 2.0.
+
+local fs = require "nixio.fs"
+
+m = Map("system", translate("Router Password"),
+ translate("Changes the administrator password for accessing the device"))
+
+s = m:section(TypedSection, "_dummy", "")
+s.addremove = false
+s.anonymous = true
+
+pw1 = s:option(Value, "pw1", translate("Password"))
+pw1.password = true
+
+pw2 = s:option(Value, "pw2", translate("Confirmation"))
+pw2.password = true
+
+function s.cfgsections()
+ return { "_pass" }
+end
+
+function m.parse(map)
+ local v1 = pw1:formvalue("_pass")
+ local v2 = pw2:formvalue("_pass")
+
+ if v1 and v2 and #v1 > 0 and #v2 > 0 then
+ if v1 == v2 then
+ if luci.sys.user.setpasswd(luci.dispatcher.context.authuser, v1) == 0 then
+ m.message = translate("Password successfully changed!")
+ else
+ m.message = translate("Unknown Error, password not changed!")
+ end
+ else
+ m.message = translate("Given password confirmation did not match, password not changed!")
+ end
+ end
+
+ Map.parse(map)
+end
+
+
+if fs.access("/etc/config/dropbear") then
+
+m2 = Map("dropbear", translate("SSH Access"),
+ translate("Dropbear offers <abbr title=\"Secure Shell\">SSH</abbr> network shell access and an integrated <abbr title=\"Secure Copy\">SCP</abbr> server"))
+
+s = m2:section(TypedSection, "dropbear", translate("Dropbear Instance"))
+s.anonymous = true
+s.addremove = true
+
+
+ni = s:option(Value, "Interface", translate("Interface"),
+ translate("Listen only on the given interface or, if unspecified, on all"))
+
+ni.template = "cbi/network_netlist"
+ni.nocreate = true
+ni.unspecified = true
+
+
+pt = s:option(Value, "Port", translate("Port"),
+ translate("Specifies the listening port of this <em>Dropbear</em> instance"))
+
+pt.datatype = "port"
+pt.default = 22
+
+
+pa = s:option(Flag, "PasswordAuth", translate("Password authentication"),
+ translate("Allow <abbr title=\"Secure Shell\">SSH</abbr> password authentication"))
+
+pa.enabled = "on"
+pa.disabled = "off"
+pa.default = pa.enabled
+pa.rmempty = false
+
+
+ra = s:option(Flag, "RootPasswordAuth", translate("Allow root logins with password"),
+ translate("Allow the <em>root</em> user to login with password"))
+
+ra.enabled = "on"
+ra.disabled = "off"
+ra.default = ra.enabled
+
+
+gp = s:option(Flag, "GatewayPorts", translate("Gateway ports"),
+ translate("Allow remote hosts to connect to local SSH forwarded ports"))
+
+gp.enabled = "on"
+gp.disabled = "off"
+gp.default = gp.disabled
+
+
+s2 = m2:section(TypedSection, "_dummy", translate("SSH-Keys"),
+ translate("Here you can paste public SSH-Keys (one per line) for SSH public-key authentication."))
+s2.addremove = false
+s2.anonymous = true
+s2.template = "cbi/tblsection"
+
+function s2.cfgsections()
+ return { "_keys" }
+end
+
+keys = s2:option(TextValue, "_data", "")
+keys.wrap = "off"
+keys.rows = 3
+
+function keys.cfgvalue()
+ return fs.readfile("/etc/dropbear/authorized_keys") or ""
+end
+
+function keys.write(self, section, value)
+ return fs.writefile("/etc/dropbear/authorized_keys", value:gsub("\r\n", "\n"))
+end
+
+function keys.remove(self, section, value)
+ return fs.writefile("/etc/dropbear/authorized_keys", "")
+end
+
+end
+
+return m, m2
diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/backupfiles.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/backupfiles.lua
new file mode 100644
index 000000000..ee2401e93
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/model/cbi/admin_system/backupfiles.lua
@@ -0,0 +1,80 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org>
+-- Licensed to the public under the Apache License 2.0.
+
+if luci.http.formvalue("cbid.luci.1._list") then
+ luci.http.redirect(luci.dispatcher.build_url("admin/system/flashops/backupfiles") .. "?display=list")
+elseif luci.http.formvalue("cbid.luci.1._edit") then
+ luci.http.redirect(luci.dispatcher.build_url("admin/system/flashops/backupfiles") .. "?display=edit")
+ return
+end
+
+m = SimpleForm("luci", translate("Backup file list"))
+m:append(Template("admin_system/backupfiles"))
+
+if luci.http.formvalue("display") ~= "list" then
+ f = m:section(SimpleSection, nil, translate("This is a list of shell glob patterns for matching files and directories to include during sysupgrade. Modified files in /etc/config/ and certain other configurations are automatically preserved."))
+
+ l = f:option(Button, "_list", translate("Show current backup file list"))
+ l.inputtitle = translate("Open list...")
+ l.inputstyle = "apply"
+
+ c = f:option(TextValue, "_custom")
+ c.rmempty = false
+ c.cols = 70
+ c.rows = 30
+
+ c.cfgvalue = function(self, section)
+ return nixio.fs.readfile("/etc/sysupgrade.conf")
+ end
+
+ c.write = function(self, section, value)
+ value = value:gsub("\r\n?", "\n")
+ return nixio.fs.writefile("/etc/sysupgrade.conf", value)
+ end
+else
+ m.submit = false
+ m.reset = false
+
+ f = m:section(SimpleSection, nil, translate("Below is the determined list of files to backup. It consists of changed configuration files marked by opkg, essential base files and the user defined backup patterns."))
+
+ l = f:option(Button, "_edit", translate("Back to configuration"))
+ l.inputtitle = translate("Close list...")
+ l.inputstyle = "link"
+
+
+ d = f:option(DummyValue, "_detected")
+ d.rawhtml = true
+ d.cfgvalue = function(s)
+ local list = io.popen(
+ "( find $(sed -ne '/^[[:space:]]*$/d; /^#/d; p' /etc/sysupgrade.conf " ..
+ "/lib/upgrade/keep.d/* 2>/dev/null) -type f 2>/dev/null; " ..
+ "opkg list-changed-conffiles ) | sort -u"
+ )
+
+ if list then
+ local files = { "<ul>" }
+
+ while true do
+ local ln = list:read("*l")
+ if not ln then
+ break
+ else
+ files[#files+1] = "<li>"
+ files[#files+1] = luci.util.pcdata(ln)
+ files[#files+1] = "</li>"
+ end
+ end
+
+ list:close()
+ files[#files+1] = "</ul>"
+
+ return table.concat(files, "")
+ end
+
+ return "<em>" .. translate("No files found") .. "</em>"
+ end
+
+end
+
+return m
diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/crontab.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/crontab.lua
new file mode 100644
index 000000000..016a6199a
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/model/cbi/admin_system/crontab.lua
@@ -0,0 +1,32 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Copyright 2008-2013 Jo-Philipp Wich <jow@openwrt.org>
+-- Licensed to the public under the Apache License 2.0.
+
+local fs = require "nixio.fs"
+local cronfile = "/etc/crontabs/root"
+
+f = SimpleForm("crontab", translate("Scheduled Tasks"),
+ translate("This is the system crontab in which scheduled tasks can be defined.") ..
+ translate("<br/>Note: you need to manually restart the cron service if the " ..
+ "crontab file was empty before editing."))
+
+t = f:field(TextValue, "crons")
+t.rmempty = true
+t.rows = 10
+function t.cfgvalue()
+ return fs.readfile(cronfile) or ""
+end
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ if data.crons then
+ fs.writefile(cronfile, data.crons:gsub("\r\n", "\n"))
+ luci.sys.call("/usr/bin/crontab %q" % cronfile)
+ else
+ fs.writefile(cronfile, "")
+ end
+ end
+ return true
+end
+
+return f
diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab.lua
new file mode 100644
index 000000000..3ce5351bf
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab.lua
@@ -0,0 +1,270 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Licensed to the public under the Apache License 2.0.
+
+require("luci.tools.webadmin")
+
+local fs = require "nixio.fs"
+local util = require "nixio.util"
+local tp = require "luci.template.parser"
+
+local block = io.popen("block info", "r")
+local ln, dev, devices = nil, nil, {}
+
+repeat
+ ln = block:read("*l")
+ dev = ln and ln:match("^/dev/(.-):")
+
+ if dev then
+ local e, s, key, val = { }
+
+ for key, val in ln:gmatch([[(%w+)="(.-)"]]) do
+ e[key:lower()] = val
+ devices[val] = e
+ end
+
+ s = tonumber((fs.readfile("/sys/class/block/%s/size" % dev)))
+
+ e.dev = "/dev/%s" % dev
+ e.size = s and math.floor(s / 2048)
+
+ devices[e.dev] = e
+ end
+until not ln
+
+block:close()
+
+m = Map("fstab", translate("Mount Points"))
+s = m:section(TypedSection, "global", translate("Global Settings"))
+s.addremove = false
+s.anonymous = true
+
+detect = s:option(Button, "block_detect", translate("Generate Config"), translate("Find all currently attached filesystems and swap and replace configuration with defaults based on what was detected"))
+detect.inputstyle = "reload"
+
+detect.write = function(self, section)
+ luci.sys.call("block detect >/etc/config/fstab")
+ luci.http.redirect(luci.dispatcher.build_url("admin/system", "fstab"))
+end
+
+o = s:option(Flag, "anon_swap", translate("Anonymous Swap"), translate("Mount swap not specifically configured"))
+o.default = o.disabled
+o.rmempty = false
+
+o = s:option(Flag, "anon_mount", translate("Anonymous Mount"), translate("Mount filesystems not specifically configured"))
+o.default = o.disabled
+o.rmempty = false
+
+o = s:option(Flag, "auto_swap", translate("Automount Swap"), translate("Automatically mount swap on hotplug"))
+o.default = o.enabled
+o.rmempty = false
+
+o = s:option(Flag, "auto_mount", translate("Automount Filesystem"), translate("Automatically mount filesystems on hotplug"))
+o.default = o.enabled
+o.rmempty = false
+
+o = s:option(Flag, "check_fs", translate("Check filesystems before mount"), translate("Automatically check filesystem for errors before mounting"))
+o.default = o.disabled
+o.rmempty = false
+
+local mounts = luci.sys.mounts()
+local non_system_mounts = {}
+for rawmount, val in pairs(mounts) do
+ if (string.find(val.mountpoint, "/tmp/.jail") == nil) then
+ repeat
+ val.umount = false
+ if (val.mountpoint == "/") then
+ break
+ elseif (val.mountpoint == "/overlay") then
+ break
+ elseif (val.mountpoint == "/rom") then
+ break
+ elseif (val.mountpoint == "/tmp") then
+ break
+ elseif (val.mountpoint == "/tmp/shm") then
+ break
+ elseif (val.mountpoint == "/tmp/upgrade") then
+ break
+ elseif (val.mountpoint == "/dev") then
+ break
+ end
+ val.umount = true
+ until true
+ non_system_mounts[rawmount] = val
+ end
+end
+
+v = m:section(Table, non_system_mounts, translate("Mounted file systems"))
+
+fs = v:option(DummyValue, "fs", translate("Filesystem"))
+
+mp = v:option(DummyValue, "mountpoint", translate("Mount Point"))
+
+avail = v:option(DummyValue, "avail", translate("Available"))
+function avail.cfgvalue(self, section)
+ return luci.tools.webadmin.byte_format(
+ ( tonumber(mounts[section].available) or 0 ) * 1024
+ ) .. " / " .. luci.tools.webadmin.byte_format(
+ ( tonumber(mounts[section].blocks) or 0 ) * 1024
+ )
+end
+
+used = v:option(DummyValue, "used", translate("Used"))
+function used.cfgvalue(self, section)
+ return ( mounts[section].percent or "0%" ) .. " (" ..
+ luci.tools.webadmin.byte_format(
+ ( tonumber(mounts[section].used) or 0 ) * 1024
+ ) .. ")"
+end
+
+unmount = v:option(Button, "unmount", translate("Unmount"))
+unmount.render = function(self, section, scope)
+ if non_system_mounts[section].umount then
+ self.title = translate("Unmount")
+ self.inputstyle = "remove"
+ Button.render(self, section, scope)
+ end
+end
+
+unmount.write = function(self, section)
+ if non_system_mounts[section].umount then
+ luci.sys.call("/bin/umount '%s'" % luci.util.shellstartsqescape(non_system_mounts[section].mountpoint))
+ return luci.http.redirect(luci.dispatcher.build_url("admin/system", "fstab"))
+ end
+end
+
+mount = m:section(TypedSection, "mount", translate("Mount Points"), translate("Mount Points define at which point a memory device will be attached to the filesystem"))
+mount.anonymous = true
+mount.addremove = true
+mount.template = "cbi/tblsection"
+mount.extedit = luci.dispatcher.build_url("admin/system/fstab/mount/%s")
+
+mount.create = function(...)
+ local sid = TypedSection.create(...)
+ if sid then
+ luci.http.redirect(mount.extedit % sid)
+ return
+ end
+end
+
+
+mount:option(Flag, "enabled", translate("Enabled")).rmempty = false
+
+dev = mount:option(DummyValue, "device", translate("Device"))
+dev.rawhtml = true
+dev.cfgvalue = function(self, section)
+ local v, e
+
+ v = m.uci:get("fstab", section, "uuid")
+ e = v and devices[v:lower()]
+ if v and e and e.size then
+ return "UUID: %s (%s, %d MB)" %{ tp.pcdata(v), e.dev, e.size }
+ elseif v and e then
+ return "UUID: %s (%s)" %{ tp.pcdata(v), e.dev }
+ elseif v then
+ return "UUID: %s (<em>%s</em>)" %{ tp.pcdata(v), translate("not present") }
+ end
+
+ v = m.uci:get("fstab", section, "label")
+ e = v and devices[v]
+ if v and e and e.size then
+ return "Label: %s (%s, %d MB)" %{ tp.pcdata(v), e.dev, e.size }
+ elseif v and e then
+ return "Label: %s (%s)" %{ tp.pcdata(v), e.dev }
+ elseif v then
+ return "Label: %s (<em>%s</em>)" %{ tp.pcdata(v), translate("not present") }
+ end
+
+ v = Value.cfgvalue(self, section) or "?"
+ e = v and devices[v]
+ if v and e and e.size then
+ return "%s (%d MB)" %{ tp.pcdata(v), e.size }
+ elseif v and e then
+ return tp.pcdata(v)
+ elseif v then
+ return "%s (<em>%s</em>)" %{ tp.pcdata(v), translate("not present") }
+ end
+end
+
+mp = mount:option(DummyValue, "target", translate("Mount Point"))
+mp.cfgvalue = function(self, section)
+ if m.uci:get("fstab", section, "is_rootfs") == "1" then
+ return "/overlay"
+ else
+ return Value.cfgvalue(self, section) or "?"
+ end
+end
+
+fs = mount:option(DummyValue, "fstype", translate("Filesystem"))
+fs.cfgvalue = function(self, section)
+ local v, e
+
+ v = m.uci:get("fstab", section, "uuid")
+ v = v and v:lower() or m.uci:get("fstab", section, "label")
+ v = v or m.uci:get("fstab", section, "device")
+
+ e = v and devices[v]
+
+ return e and e.type or m.uci:get("fstab", section, "fstype") or "?"
+end
+
+op = mount:option(DummyValue, "options", translate("Options"))
+op.cfgvalue = function(self, section)
+ return Value.cfgvalue(self, section) or "defaults"
+end
+
+rf = mount:option(DummyValue, "is_rootfs", translate("Root"))
+rf.cfgvalue = function(self, section)
+ local target = m.uci:get("fstab", section, "target")
+ if target == "/" then
+ return translate("yes")
+ elseif target == "/overlay" then
+ return translate("overlay")
+ else
+ return translate("no")
+ end
+end
+
+ck = mount:option(DummyValue, "enabled_fsck", translate("Check"))
+ck.cfgvalue = function(self, section)
+ return Value.cfgvalue(self, section) == "1"
+ and translate("yes") or translate("no")
+end
+
+
+swap = m:section(TypedSection, "swap", "SWAP", translate("If your physical memory is insufficient unused data can be temporarily swapped to a swap-device resulting in a higher amount of usable <abbr title=\"Random Access Memory\">RAM</abbr>. Be aware that swapping data is a very slow process as the swap-device cannot be accessed with the high datarates of the <abbr title=\"Random Access Memory\">RAM</abbr>."))
+swap.anonymous = true
+swap.addremove = true
+swap.template = "cbi/tblsection"
+swap.extedit = luci.dispatcher.build_url("admin/system/fstab/swap/%s")
+
+swap.create = function(...)
+ local sid = TypedSection.create(...)
+ if sid then
+ luci.http.redirect(swap.extedit % sid)
+ return
+ end
+end
+
+
+swap:option(Flag, "enabled", translate("Enabled")).rmempty = false
+
+dev = swap:option(DummyValue, "device", translate("Device"))
+dev.cfgvalue = function(self, section)
+ local v
+
+ v = m.uci:get("fstab", section, "uuid")
+ if v then return "UUID: %s" % v end
+
+ v = m.uci:get("fstab", section, "label")
+ if v then return "Label: %s" % v end
+
+ v = Value.cfgvalue(self, section) or "?"
+ e = v and devices[v]
+ if v and e and e.size then
+ return "%s (%s MB)" % {v, e.size}
+ else
+ return v
+ end
+end
+
+return m
diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/mount.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/mount.lua
new file mode 100644
index 000000000..a85872afa
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/mount.lua
@@ -0,0 +1,151 @@
+-- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
+-- Licensed to the public under the Apache License 2.0.
+
+local fs = require "nixio.fs"
+local util = require "nixio.util"
+
+local has_fscheck = fs.access("/usr/sbin/e2fsck")
+
+local block = io.popen("block info", "r")
+local ln, dev, devices = nil, nil, {}
+
+repeat
+ ln = block:read("*l")
+ dev = ln and ln:match("^/dev/(.-):")
+
+ if dev then
+ local e, s, key, val = { }
+
+ for key, val in ln:gmatch([[(%w+)="(.-)"]]) do
+ e[key:lower()] = val
+ end
+
+ s = tonumber((fs.readfile("/sys/class/block/%s/size" % dev)))
+
+ e.dev = "/dev/%s" % dev
+ e.size = s and math.floor(s / 2048)
+
+ devices[#devices+1] = e
+ end
+until not ln
+
+block:close()
+
+
+m = Map("fstab", translate("Mount Points - Mount Entry"))
+m.redirect = luci.dispatcher.build_url("admin/system/fstab")
+
+if not arg[1] or m.uci:get("fstab", arg[1]) ~= "mount" then
+ luci.http.redirect(m.redirect)
+ return
+end
+
+
+
+mount = m:section(NamedSection, arg[1], "mount", translate("Mount Entry"))
+mount.anonymous = true
+mount.addremove = false
+
+mount:tab("general", translate("General Settings"))
+mount:tab("advanced", translate("Advanced Settings"))
+
+
+mount:taboption("general", Flag, "enabled", translate("Enable this mount")).rmempty = false
+
+
+o = mount:taboption("general", Value, "uuid", translate("UUID"),
+ translate("If specified, mount the device by its UUID instead of a fixed device node"))
+
+o:value("", translate("-- match by uuid --"))
+
+for i, d in ipairs(devices) do
+ if d.uuid and d.size then
+ o:value(d.uuid, "%s (%s, %d MB)" %{ d.uuid, d.dev, d.size })
+ elseif d.uuid then
+ o:value(d.uuid, "%s (%s)" %{ d.uuid, d.dev })
+ end
+end
+
+
+o = mount:taboption("general", Value, "label", translate("Label"),
+ translate("If specified, mount the device by the partition label instead of a fixed device node"))
+
+o:value("", translate("-- match by label --"))
+
+o:depends("uuid", "")
+
+for i, d in ipairs(devices) do
+ if d.label and d.size then
+ o:value(d.label, "%s (%s, %d MB)" %{ d.label, d.dev, d.size })
+ elseif d.label then
+ o:value(d.label, "%s (%s)" %{ d.label, d.dev })
+ end
+end
+
+
+o = mount:taboption("general", Value, "device", translate("Device"),
+ translate("The device file of the memory or partition (<abbr title=\"for example\">e.g.</abbr> <code>/dev/sda1</code>)"))
+
+o:value("", translate("-- match by device --"))
+
+o:depends({ uuid = "", label = "" })
+
+for i, d in ipairs(devices) do
+ if d.size then
+ o:value(d.dev, "%s (%d MB)" %{ d.dev, d.size })
+ else
+ o:value(d.dev)
+ end
+end
+
+
+o = mount:taboption("general", Value, "target", translate("Mount point"),
+ translate("Specifies the directory the device is attached to"))
+
+o:value("/", translate("Use as root filesystem (/)"))
+o:value("/overlay", translate("Use as external overlay (/overlay)"))
+
+
+o = mount:taboption("general", DummyValue, "__notice", translate("Root preparation"))
+o:depends("target", "/")
+o.rawhtml = true
+o.default = [[
+<p>%s</p><pre>mkdir -p /tmp/introot
+mkdir -p /tmp/extroot
+mount --bind / /tmp/introot
+mount /dev/sda1 /tmp/extroot
+tar -C /tmp/introot -cvf - . | tar -C /tmp/extroot -xf -
+umount /tmp/introot
+umount /tmp/extroot</pre>
+]] %{
+ translate("Make sure to clone the root filesystem using something like the commands below:"),
+
+}
+
+
+o = mount:taboption("advanced", Value, "fstype", translate("Filesystem"),
+ translate("The filesystem that was used to format the memory (<abbr title=\"for example\">e.g.</abbr> <samp><abbr title=\"Third Extended Filesystem\">ext3</abbr></samp>)"))
+
+o:value("", "auto")
+
+local fs
+for fs in io.lines("/proc/filesystems") do
+ fs = fs:match("%S+")
+ if fs ~= "nodev" then
+ o:value(fs)
+ end
+end
+
+
+o = mount:taboption("advanced", Value, "options", translate("Mount options"),
+ translate("See \"mount\" manpage for details"))
+
+o.placeholder = "defaults"
+
+
+if has_fscheck then
+ o = mount:taboption("advanced", Flag, "enabled_fsck", translate("Run filesystem check"),
+ translate("Run a filesystem check before mounting the device"))
+end
+
+return m
diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/swap.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/swap.lua
new file mode 100644
index 000000000..82468d5fc
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/swap.lua
@@ -0,0 +1,54 @@
+-- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
+-- Licensed to the public under the Apache License 2.0.
+
+local fs = require "nixio.fs"
+local util = require "nixio.util"
+
+local devices = {}
+util.consume((fs.glob("/dev/sd*")), devices)
+util.consume((fs.glob("/dev/hd*")), devices)
+util.consume((fs.glob("/dev/scd*")), devices)
+util.consume((fs.glob("/dev/mmc*")), devices)
+
+local size = {}
+for i, dev in ipairs(devices) do
+ local s = tonumber((fs.readfile("/sys/class/block/%s/size" % dev:sub(6))))
+ size[dev] = s and math.floor(s / 2048)
+end
+
+
+m = Map("fstab", translate("Mount Points - Swap Entry"))
+m.redirect = luci.dispatcher.build_url("admin/system/fstab")
+
+if not arg[1] or m.uci:get("fstab", arg[1]) ~= "swap" then
+ luci.http.redirect(m.redirect)
+ return
+end
+
+
+mount = m:section(NamedSection, arg[1], "swap", translate("Swap Entry"))
+mount.anonymous = true
+mount.addremove = false
+
+mount:tab("general", translate("General Settings"))
+mount:tab("advanced", translate("Advanced Settings"))
+
+
+mount:taboption("general", Flag, "enabled", translate("Enable this swap")).rmempty = false
+
+
+o = mount:taboption("general", Value, "device", translate("Device"),
+ translate("The device file of the memory or partition (<abbr title=\"for example\">e.g.</abbr> <code>/dev/sda1</code>)"))
+
+for i, d in ipairs(devices) do
+ o:value(d, size[d] and "%s (%s MB)" % {d, size[d]})
+end
+
+o = mount:taboption("advanced", Value, "uuid", translate("UUID"),
+ translate("If specified, mount the device by its UUID instead of a fixed device node"))
+
+o = mount:taboption("advanced", Value, "label", translate("Label"),
+ translate("If specified, mount the device by the partition label instead of a fixed device node"))
+
+
+return m
diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/ipkg.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/ipkg.lua
new file mode 100644
index 000000000..7c6d7e1c6
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/model/cbi/admin_system/ipkg.lua
@@ -0,0 +1,64 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Copyright 2008-2011 Jo-Philipp Wich <jow@openwrt.org>
+-- Licensed to the public under the Apache License 2.0.
+
+local ipkgfile = "/etc/opkg.conf"
+local distfeeds = "/etc/opkg/distfeeds.conf"
+local customfeeds = "/etc/opkg/customfeeds.conf"
+
+f = SimpleForm("ipkgconf", translate("OPKG-Configuration"), translate("General options for opkg"))
+
+f:append(Template("admin_system/ipkg"))
+
+t = f:field(TextValue, "lines")
+t.wrap = "off"
+t.rows = 10
+function t.cfgvalue()
+ return nixio.fs.readfile(ipkgfile) or ""
+end
+
+function t.write(self, section, data)
+ return nixio.fs.writefile(ipkgfile, data:gsub("\r\n", "\n"))
+end
+
+function f.handle(self, state, data)
+ return true
+end
+
+g = SimpleForm("distfeedconf", translate("Distribution feeds"),
+ translate("Build/distribution specific feed definitions. This file will NOT be preserved in any sysupgrade."))
+
+d = g:field(TextValue, "lines2")
+d.wrap = "off"
+d.rows = 10
+function d.cfgvalue()
+ return nixio.fs.readfile(distfeeds) or ""
+end
+
+function d.write(self, section, data)
+ return nixio.fs.writefile(distfeeds, data:gsub("\r\n", "\n"))
+end
+
+function g.handle(self, state, data)
+ return true
+end
+
+h = SimpleForm("customfeedconf", translate("Custom feeds"),
+ translate("Custom feed definitions, e.g. private feeds. This file can be preserved in a sysupgrade."))
+
+c = h:field(TextValue, "lines3")
+c.wrap = "off"
+c.rows = 10
+function c.cfgvalue()
+ return nixio.fs.readfile(customfeeds) or ""
+end
+
+function c.write(self, section, data)
+ return nixio.fs.writefile(customfeeds, data:gsub("\r\n", "\n"))
+end
+
+function h.handle(self, state, data)
+ return true
+end
+
+return f, g, h
diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/leds.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/leds.lua
new file mode 100644
index 000000000..2ea044e16
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/model/cbi/admin_system/leds.lua
@@ -0,0 +1,158 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Licensed to the public under the Apache License 2.0.
+
+m = Map("system", translate("<abbr title=\"Light Emitting Diode\">LED</abbr> Configuration"), translate("Customizes the behaviour of the device <abbr title=\"Light Emitting Diode\">LED</abbr>s if possible."))
+
+local sysfs_path = "/sys/class/leds/"
+local leds = {}
+
+local fs = require "nixio.fs"
+local nu = require "nixio.util"
+local util = require "luci.util"
+
+if fs.access(sysfs_path) then
+ leds = nu.consume((fs.dir(sysfs_path)))
+end
+
+if #leds == 0 then
+ return m
+end
+
+
+s = m:section(TypedSection, "led", "")
+s.anonymous = true
+s.addremove = true
+
+function s.parse(self, ...)
+ TypedSection.parse(self, ...)
+ os.execute("/etc/init.d/led enable")
+end
+
+
+s:option(Value, "name", translate("Name"))
+
+
+sysfs = s:option(ListValue, "sysfs", translate("<abbr title=\"Light Emitting Diode\">LED</abbr> Name"))
+for k, v in ipairs(leds) do
+ sysfs:value(v)
+end
+
+s:option(Flag, "default", translate("Default state")).rmempty = false
+
+
+trigger = s:option(ListValue, "trigger", translate("Trigger"))
+
+local triggers = fs.readfile(sysfs_path .. leds[1] .. "/trigger")
+for t in triggers:gmatch("[%w-]+") do
+ trigger:value(t, translate(t:gsub("-", "")))
+end
+
+
+delayon = s:option(Value, "delayon", translate ("On-State Delay"))
+delayon:depends("trigger", "timer")
+
+delayoff = s:option(Value, "delayoff", translate ("Off-State Delay"))
+delayoff:depends("trigger", "timer")
+
+
+dev = s:option(ListValue, "_net_dev", translate("Device"))
+dev.rmempty = true
+dev:value("")
+dev:depends("trigger", "netdev")
+
+function dev.cfgvalue(self, section)
+ return m.uci:get("system", section, "dev")
+end
+
+function dev.write(self, section, value)
+ m.uci:set("system", section, "dev", value)
+end
+
+function dev.remove(self, section)
+ local t = trigger:formvalue(section)
+ if t ~= "netdev" and t ~= "usbdev" then
+ m.uci:delete("system", section, "dev")
+ end
+end
+
+for k, v in pairs(luci.sys.net.devices()) do
+ if v ~= "lo" then
+ dev:value(v)
+ end
+end
+
+
+mode = s:option(MultiValue, "mode", translate("Trigger Mode"))
+mode.rmempty = true
+mode:depends("trigger", "netdev")
+mode:value("link", translate("Link On"))
+mode:value("tx", translate("Transmit"))
+mode:value("rx", translate("Receive"))
+
+
+usbdev = s:option(ListValue, "_usb_dev", translate("USB Device"))
+usbdev:depends("trigger", "usbdev")
+usbdev.rmempty = true
+usbdev:value("")
+
+function usbdev.cfgvalue(self, section)
+ return m.uci:get("system", section, "dev")
+end
+
+function usbdev.write(self, section, value)
+ m.uci:set("system", section, "dev", value)
+end
+
+function usbdev.remove(self, section)
+ local t = trigger:formvalue(section)
+ if t ~= "netdev" and t ~= "usbdev" then
+ m.uci:delete("system", section, "dev")
+ end
+end
+
+
+usbport = s:option(MultiValue, "port", translate("USB Ports"))
+usbport:depends("trigger", "usbport")
+usbport.rmempty = true
+usbport.widget = "checkbox"
+usbport.cast = "table"
+usbport.size = 1
+
+function usbport.valuelist(self, section)
+ local port, ports = nil, {}
+ for port in util.imatch(m.uci:get("system", section, "port")) do
+ local b, n = port:match("^usb(%d+)-port(%d+)$")
+ if not (b and n) then
+ b, n = port:match("^(%d+)-(%d+)$")
+ end
+ if b and n then
+ ports[#ports+1] = "usb%u-port%u" %{ tonumber(b), tonumber(n) }
+ end
+ end
+ return ports
+end
+
+function usbport.validate(self, value)
+ return type(value) == "string" and { value } or value
+end
+
+
+for p in nixio.fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer") do
+ local id = p:match("%d+-%d+")
+ local mf = nixio.fs.readfile("/sys/bus/usb/devices/" .. id .. "/manufacturer") or "?"
+ local pr = nixio.fs.readfile("/sys/bus/usb/devices/" .. id .. "/product") or "?"
+ usbdev:value(id, "%s (%s - %s)" %{ id, mf, pr })
+end
+
+for p in nixio.fs.glob("/sys/bus/usb/devices/*/usb[0-9]*-port[0-9]*") do
+ local bus, port = p:match("usb(%d+)-port(%d+)")
+ if bus and port then
+ usbport:value("usb%u-port%u" %{ tonumber(bus), tonumber(port) },
+ "Hub %u, Port %u" %{ tonumber(bus), tonumber(port) })
+ end
+end
+
+port_mask = s:option(Value, "port_mask", translate ("Switch Port Mask"))
+port_mask:depends("trigger", "switch0")
+
+return m
diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/startup.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/startup.lua
new file mode 100644
index 000000000..9e19ac50a
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/model/cbi/admin_system/startup.lua
@@ -0,0 +1,97 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Copyright 2010-2012 Jo-Philipp Wich <jow@openwrt.org>
+-- Copyright 2010 Manuel Munz <freifunk at somakoma dot de>
+-- Licensed to the public under the Apache License 2.0.
+
+local fs = require "nixio.fs"
+local sys = require "luci.sys"
+
+local inits = { }
+
+for _, name in ipairs(sys.init.names()) do
+ local index = sys.init.index(name)
+ local enabled = sys.init.enabled(name)
+
+ if index < 255 then
+ inits["%02i.%s" % { index, name }] = {
+ name = name,
+ index = tostring(index),
+ enabled = enabled
+ }
+ end
+end
+
+
+m = SimpleForm("initmgr", translate("Initscripts"), translate("You can enable or disable installed init scripts here. Changes will applied after a device reboot.<br /><strong>Warning: If you disable essential init scripts like \"network\", your device might become inaccessible!</strong>"))
+m.reset = false
+m.submit = false
+
+
+s = m:section(Table, inits)
+
+i = s:option(DummyValue, "index", translate("Start priority"))
+n = s:option(DummyValue, "name", translate("Initscript"))
+
+
+e = s:option(Button, "endisable", translate("Enable/Disable"))
+
+e.render = function(self, section, scope)
+ if inits[section].enabled then
+ self.title = translate("Enabled")
+ self.inputstyle = "save"
+ else
+ self.title = translate("Disabled")
+ self.inputstyle = "reset"
+ end
+
+ Button.render(self, section, scope)
+end
+
+e.write = function(self, section)
+ if inits[section].enabled then
+ inits[section].enabled = false
+ return sys.init.disable(inits[section].name)
+ else
+ inits[section].enabled = true
+ return sys.init.enable(inits[section].name)
+ end
+end
+
+
+start = s:option(Button, "start", translate("Start"))
+start.inputstyle = "apply"
+start.write = function(self, section)
+ sys.call("/etc/init.d/%s %s >/dev/null" %{ inits[section].name, self.option })
+end
+
+restart = s:option(Button, "restart", translate("Restart"))
+restart.inputstyle = "reload"
+restart.write = start.write
+
+stop = s:option(Button, "stop", translate("Stop"))
+stop.inputstyle = "remove"
+stop.write = start.write
+
+
+
+f = SimpleForm("rc", translate("Local Startup"),
+ translate("This is the content of /etc/rc.local. Insert your own commands here (in front of 'exit 0') to execute them at the end of the boot process."))
+
+t = f:field(TextValue, "rcs")
+t.rmempty = true
+t.rows = 20
+
+function t.cfgvalue()
+ return fs.readfile("/etc/rc.local") or ""
+end
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ if data.rcs then
+ fs.writefile("/etc/rc.local", data.rcs:gsub("\r\n", "\n"))
+ end
+ end
+ return true
+end
+
+return m, f
diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/system.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/system.lua
new file mode 100644
index 000000000..c7fdfcddb
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/model/cbi/admin_system/system.lua
@@ -0,0 +1,224 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org>
+-- Licensed to the public under the Apache License 2.0.
+
+local sys = require "luci.sys"
+local zones = require "luci.sys.zoneinfo"
+local fs = require "nixio.fs"
+local conf = require "luci.config"
+
+local m, s, o
+local has_ntpd = fs.access("/usr/sbin/ntpd")
+
+m = Map("system", translate("System"), translate("Here you can configure the basic aspects of your device like its hostname or the timezone."))
+m:chain("luci")
+
+
+s = m:section(TypedSection, "system", translate("System Properties"))
+s.anonymous = true
+s.addremove = false
+
+s:tab("general", translate("General Settings"))
+s:tab("logging", translate("Logging"))
+s:tab("language", translate("Language and Style"))
+
+
+--
+-- System Properties
+--
+
+o = s:taboption("general", DummyValue, "_systime", translate("Local Time"))
+o.template = "admin_system/clock_status"
+
+
+o = s:taboption("general", Value, "hostname", translate("Hostname"))
+o.datatype = "hostname"
+
+function o.write(self, section, value)
+ Value.write(self, section, value)
+ sys.hostname(value)
+end
+
+
+o = s:taboption("general", ListValue, "zonename", translate("Timezone"))
+o:value("UTC")
+
+for i, zone in ipairs(zones.TZ) do
+ o:value(zone[1])
+end
+
+function o.write(self, section, value)
+ local function lookup_zone(title)
+ for _, zone in ipairs(zones.TZ) do
+ if zone[1] == title then return zone[2] end
+ end
+ end
+
+ AbstractValue.write(self, section, value)
+ local timezone = lookup_zone(value) or "GMT0"
+ self.map.uci:set("system", section, "timezone", timezone)
+ fs.writefile("/etc/TZ", timezone .. "\n")
+end
+
+
+--
+-- Logging
+--
+
+o = s:taboption("logging", Value, "log_size", translate("System log buffer size"), "kiB")
+o.optional = true
+o.placeholder = 16
+o.datatype = "uinteger"
+
+o = s:taboption("logging", Value, "log_ip", translate("External system log server"))
+o.optional = true
+o.placeholder = "0.0.0.0"
+o.datatype = "ip4addr"
+
+o = s:taboption("logging", Value, "log_port", translate("External system log server port"))
+o.optional = true
+o.placeholder = 514
+o.datatype = "port"
+
+o = s:taboption("logging", ListValue, "log_proto", translate("External system log server protocol"))
+o:value("udp", "UDP")
+o:value("tcp", "TCP")
+
+o = s:taboption("logging", Value, "log_file", translate("Write system log to file"))
+o.optional = true
+o.placeholder = "/tmp/system.log"
+
+o = s:taboption("logging", ListValue, "conloglevel", translate("Log output level"))
+o:value(8, translate("Debug"))
+o:value(7, translate("Info"))
+o:value(6, translate("Notice"))
+o:value(5, translate("Warning"))
+o:value(4, translate("Error"))
+o:value(3, translate("Critical"))
+o:value(2, translate("Alert"))
+o:value(1, translate("Emergency"))
+
+o = s:taboption("logging", ListValue, "cronloglevel", translate("Cron Log Level"))
+o.default = 8
+o:value(5, translate("Debug"))
+o:value(8, translate("Normal"))
+o:value(9, translate("Warning"))
+
+
+--
+-- Langauge & Style
+--
+
+o = s:taboption("language", ListValue, "_lang", translate("Language"))
+o:value("auto")
+
+local i18ndir = luci.i18n.i18ndir .. "base."
+for k, v in luci.util.kspairs(conf.languages) do
+ local file = i18ndir .. k:gsub("_", "-")
+ if k:sub(1, 1) ~= "." and fs.access(file .. ".lmo") then
+ o:value(k, v)
+ end
+end
+
+function o.cfgvalue(...)
+ return m.uci:get("luci", "main", "lang")
+end
+
+function o.write(self, section, value)
+ m.uci:set("luci", "main", "lang", value)
+end
+
+
+o = s:taboption("language", ListValue, "_mediaurlbase", translate("Design"))
+for k, v in pairs(conf.themes) do
+ if k:sub(1, 1) ~= "." then
+ o:value(v, k)
+ end
+end
+
+function o.cfgvalue(...)
+ return m.uci:get("luci", "main", "mediaurlbase")
+end
+
+function o.write(self, section, value)
+ m.uci:set("luci", "main", "mediaurlbase", value)
+end
+
+
+--
+-- NTP
+--
+
+if has_ntpd then
+
+ -- timeserver setup was requested, create section and reload page
+ if m:formvalue("cbid.system._timeserver._enable") then
+ m.uci:section("system", "timeserver", "ntp",
+ {
+ server = { "0.openwrt.pool.ntp.org", "1.openwrt.pool.ntp.org", "2.openwrt.pool.ntp.org", "3.openwrt.pool.ntp.org" }
+ }
+ )
+
+ m.uci:save("system")
+ luci.http.redirect(luci.dispatcher.build_url("admin/system", arg[1]))
+ return
+ end
+
+ local has_section = false
+ m.uci:foreach("system", "timeserver",
+ function(s)
+ has_section = true
+ return false
+ end)
+
+ if not has_section then
+
+ s = m:section(TypedSection, "timeserver", translate("Time Synchronization"))
+ s.anonymous = true
+ s.cfgsections = function() return { "_timeserver" } end
+
+ x = s:option(Button, "_enable")
+ x.title = translate("Time Synchronization is not configured yet.")
+ x.inputtitle = translate("Set up Time Synchronization")
+ x.inputstyle = "apply"
+
+ else
+
+ s = m:section(TypedSection, "timeserver", translate("Time Synchronization"))
+ s.anonymous = true
+ s.addremove = false
+
+ o = s:option(Flag, "enable", translate("Enable NTP client"))
+ o.rmempty = false
+
+ function o.cfgvalue(self)
+ return sys.init.enabled("sysntpd")
+ and self.enabled or self.disabled
+ end
+
+ function o.write(self, section, value)
+ if value == self.enabled then
+ sys.init.enable("sysntpd")
+ sys.call("env -i /etc/init.d/sysntpd start >/dev/null")
+ else
+ sys.call("env -i /etc/init.d/sysntpd stop >/dev/null")
+ sys.init.disable("sysntpd")
+ end
+ end
+
+
+ o = s:option(Flag, "enable_server", translate("Provide NTP server"))
+ o:depends("enable", "1")
+
+
+ o = s:option(DynamicList, "server", translate("NTP server candidates"))
+ o.datatype = "host(0)"
+ o:depends("enable", "1")
+
+ -- retain server list even if disabled
+ function o.remove() end
+
+ end
+end
+
+return m
diff --git a/modules/luci-mod-system/luasrc/view/admin_system/applyreboot.htm b/modules/luci-mod-system/luasrc/view/admin_system/applyreboot.htm
new file mode 100644
index 000000000..e235bd467
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/view/admin_system/applyreboot.htm
@@ -0,0 +1,53 @@
+<%#
+ Copyright 2008 Steven Barth <steven@midlink.org>
+ Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<html>
+ <head>
+ <title><%=luci.sys.hostname()%> - <%= title or translate("Rebooting...") %></title>
+ <link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />
+ <script type="text/javascript" src="<%=resource%>/xhr.js"></script>
+ <script type="text/javascript">//<![CDATA[
+ var interval = window.setInterval(function() {
+ var img = new Image();
+ var target = ('https:' == document.location.protocol ? 'https://' : 'http://') + <%=addr and "'%s'" % addr or "window.location.host"%>;
+
+ img.onload = function() {
+ window.clearInterval(interval);
+ window.location.replace(target);
+ };
+
+ img.src = target + '<%=resource%>/icons/loading.gif?' + Math.random();
+
+ }, 5000);
+ //]]></script>
+ </head>
+ <body>
+ <header>
+ <div class="fill">
+ <div class="container">
+ <p class="brand"><%=luci.sys.hostname() or "?"%></p>
+ </div>
+ </div>
+ </header>
+ &#160;
+ <div class="main">
+ <div id="maincontainer">
+ <div id="maincontent" class="container">
+ <h2 name="content" id="applyreboot-container" ><%:System%> - <%= title or translate("Rebooting...") %></h2>
+ <div class="cbi-section" id="applyreboot-section">
+ <div>
+ <%= msg or translate("Changes applied.") %>
+ </div>
+ <div>
+ <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" />
+ <%:Waiting for changes to be applied...%>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/modules/luci-mod-system/luasrc/view/admin_system/backupfiles.htm b/modules/luci-mod-system/luasrc/view/admin_system/backupfiles.htm
new file mode 100644
index 000000000..c1f3361ae
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/view/admin_system/backupfiles.htm
@@ -0,0 +1,10 @@
+<%#
+ Copyright 2008 Steven Barth <steven@midlink.org>
+ Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<ul class="cbi-tabmenu">
+ <li class="cbi-tab-disabled"><a href="<%=url("admin/system/flashops")%>"><%:Actions%></a></li>
+ <li class="cbi-tab"><a href="#"><%:Configuration%></a></li>
+</ul>
diff --git a/modules/luci-mod-system/luasrc/view/admin_system/clock_status.htm b/modules/luci-mod-system/luasrc/view/admin_system/clock_status.htm
new file mode 100644
index 000000000..37d8ae0e8
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/view/admin_system/clock_status.htm
@@ -0,0 +1,36 @@
+<%+cbi/valueheader%>
+
+<script type="text/javascript">//<![CDATA[
+ XHR.poll(5, '<%=url('admin/system/clock_status')%>', null,
+ function(x, rv)
+ {
+ var s = document.getElementById('<%=self.option%>-clock-status');
+ if (s)
+ {
+ s.innerHTML = rv.timestring || '?';
+ }
+ }
+ );
+
+ function sync_clock(btn)
+ {
+ btn.disabled = true;
+ btn.value = '<%:Synchronizing...%>';
+
+ (new XHR()).post('<%=url('admin/system/clock_status')%>',
+ { token: '<%=token%>', set: Math.floor((new Date()).getTime() / 1000) },
+ function()
+ {
+ btn.disabled = false;
+ btn.value = '<%:Sync with browser%>';
+ }
+ );
+
+ return false;
+ }
+//]]></script>
+
+<span id="<%=self.option%>-clock-status"><em><%:Collecting data...%></em></span>
+<input type="button" class="cbi-button cbi-button-apply" value="<%:Sync with browser%>" onclick="return sync_clock(this)" />
+
+<%+cbi/valuefooter%>
diff --git a/modules/luci-mod-system/luasrc/view/admin_system/flashops.htm b/modules/luci-mod-system/luasrc/view/admin_system/flashops.htm
new file mode 100644
index 000000000..8204d38e3
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/view/admin_system/flashops.htm
@@ -0,0 +1,137 @@
+<%#
+ Copyright 2008 Steven Barth <steven@midlink.org>
+ Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<%+header%>
+
+<h2 name="content"><%:Flash operations%></h2>
+
+<ul class="cbi-tabmenu">
+ <li class="cbi-tab"><a href="#"><%:Actions%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=url('admin/system/flashops/backupfiles')%>"><%:Configuration%></a></li>
+</ul>
+
+<div class="cbi-section">
+ <h3><%:Backup%></h3>
+ <div class="cbi-section-descr"><%:Click "Generate archive" to download a tar archive of the current configuration files.%></div>
+ <div class="cbi-section-node">
+ <form class="inline" method="post" action="<%=url('admin/system/flashops/backup')%>">
+ <input type="hidden" name="token" value="<%=token%>" />
+ <div class="cbi-value<% if not reset_avail then %> cbi-value-last<% end %>">
+ <label class="cbi-value-title" for="image"><%:Download backup%></label>
+ <div class="cbi-value-field">
+ <input class="cbi-button cbi-button-action important" type="submit" name="backup" value="<%:Generate archive%>" />
+ </div>
+ </div>
+ </form>
+ </div>
+
+ <h3><%:Restore%></h3>
+ <div class="cbi-section-descr"><%:To restore configuration files, you can upload a previously generated backup archive here. To reset the firmware to its initial state, click "Perform reset" (only possible with squashfs images).%></div>
+ <div class="cbi-section-node">
+ <% if reset_avail then %>
+ <form class="inline" method="post" action="<%=url('admin/system/flashops/reset')%>">
+ <input type="hidden" name="token" value="<%=token%>" />
+ <div class="cbi-value cbi-value-last">
+ <label class="cbi-value-title"><%:Reset to defaults%></label>
+ <div class="cbi-value-field">
+ <input onclick="return confirm('<%:Really reset all changes?%>')" class="cbi-button cbi-button-reset" type="submit" name="reset" value="<%:Perform reset%>" />
+ </div>
+ </div>
+ </form>
+ <% end %>
+ <form class="inline" method="post" action="<%=url('admin/system/flashops/restore')%>" enctype="multipart/form-data">
+ <div class="cbi-value cbi-value-last">
+ <label class="cbi-value-title" for="archive"><%:Restore backup%></label>
+ <div class="cbi-value-field">
+ <input type="hidden" name="token" value="<%=token%>" />
+ <input type="file" name="archive" id="archive" />
+ <input type="submit" class="cbi-button cbi-button-action important" name="restore" value="<%:Upload archive...%>" />
+ <% if reset_avail then %>
+ <div class="cbi-value-description"><%:Custom files (certificates, scripts) may remain on the system. To prevent this, perform a factory-reset first.%></div>
+ <% end %>
+ </div>
+ </div>
+ </form>
+ <% if backup_invalid then %>
+ <div class="cbi-section-error"><%:The backup archive does not appear to be a valid gzip file.%></div>
+ <% end %>
+ </div>
+
+ <% local mtds = require("luci.sys").mtds(); if #mtds > 0 then -%>
+ <h3><%:Save mtdblock contents%></h3>
+ <div class="cbi-section-descr"><%:Click "Save mtdblock" to download specified mtdblock file. (NOTE: THIS FEATURE IS FOR PROFESSIONALS! )%></div>
+ <div class="cbi-section-node">
+ <form class="inline" method="post" action="<%=url('admin/system/flashops/backupmtdblock')%>">
+ <input type="hidden" name="token" value="<%=token%>" />
+ <div class="cbi-value">
+ <label class="cbi-value-title" for="mtdblockname"><%:Choose mtdblock%></label>
+ <div class="cbi-value-field">
+ <select class="cbi-input-select" data-update="change" name="mtdblockname" id="mtdblockname">
+ <% for i, key in ipairs(mtds) do
+ if key and key.name ~= "rootfs_data" then -%>
+ <option<%=
+ attr("id", "mtdblockname-" .. key.name) ..
+ attr("value", key.name .. '/'.. key.size .. '/' .. i - 1) ..
+ attr("data-index", i) ..
+ ifattr(key.name == "linux" or key.name == "firmware", "selected", "selected")
+ %>><%=pcdata(key.name)%></option>
+ <% end
+ end -%>
+ </select>
+ </div>
+ </div>
+ <div class="cbi-value cbi-value-last<% if reset_avail then %> cbi-value-error<% end %>">
+ <label class="cbi-value-title" for="image"><%:Download mtdblock%></label>
+ <div class="cbi-value-field">
+ <input type="submit" class="cbi-button cbi-button-action important" value="<%:Save mtdblock%>" />
+ </div>
+ </div>
+ </form>
+ </div>
+ <% end %>
+
+</div>
+
+<div class="cbi-section">
+ <h3><%:Flash new firmware image%></h3>
+ <% if upgrade_avail then %>
+ <form method="post" action="<%=url('admin/system/flashops/sysupgrade')%>" enctype="multipart/form-data">
+ <input type="hidden" name="token" value="<%=token%>" />
+ <div class="cbi-section-descr"><%:Upload a sysupgrade-compatible image here to replace the running firmware. Check "Keep settings" to retain the current configuration (requires a compatible firmware image).%></div>
+ <div class="cbi-section-node">
+ <div class="cbi-value">
+ <label class="cbi-value-title" for="keep"><%:Keep settings%></label>
+ <div class="cbi-value-field">
+ <input type="checkbox" name="keep" id="keep" checked="checked" />
+ </div>
+ </div>
+ <% if image_invalid then %>
+ <div class="cbi-value">
+ <label class="cbi-value-title" for="force"><%:Force upgrade%></label>
+ <div class="cbi-value-field">
+ <input type="checkbox" name="force" id="force" />
+ </div>
+ <div class="cbi-section-error">
+ <%:The uploaded image file does not contain a supported format. Make sure that you choose the generic image format for your platform. %>
+ <%:Select 'Force upgrade' to flash the image even if the image format check fails. Use only if you are sure that the firmware is correct and meant for your device! %>
+ </div>
+ </div>
+ <% end %>
+ <div class="cbi-value cbi-value-last<% if image_invalid then %> cbi-value-error<% end %>">
+ <label class="cbi-value-title" for="image"><%:Image%></label>
+ <div class="cbi-value-field">
+ <input type="file" name="image" id="image" />
+ <input type="submit" class="cbi-button cbi-button-action important" value="<%:Flash image...%>" />
+ </div>
+ </div>
+ </div>
+ </form>
+ <% else %>
+ <div class="cbi-section-descr"><%:Sorry, there is no sysupgrade support present; a new firmware image must be flashed manually. Please refer to the wiki for device specific install instructions.%></div>
+ <% end %>
+</div>
+
+<%+footer%>
diff --git a/modules/luci-mod-system/luasrc/view/admin_system/ipkg.htm b/modules/luci-mod-system/luasrc/view/admin_system/ipkg.htm
new file mode 100644
index 000000000..a7ff4e50b
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/view/admin_system/ipkg.htm
@@ -0,0 +1,10 @@
+<%#
+ Copyright 2008 Steven Barth <steven@midlink.org>
+ Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<ul class="cbi-tabmenu">
+ <li class="cbi-tab-disabled"><a href="<%=url("admin/system/packages")%>"><%:Actions%></a></li>
+ <li class="cbi-tab"><a href="#"><%:Configuration%></a></li>
+</ul>
diff --git a/modules/luci-mod-system/luasrc/view/admin_system/packages.htm b/modules/luci-mod-system/luasrc/view/admin_system/packages.htm
new file mode 100644
index 000000000..280eabb8e
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/view/admin_system/packages.htm
@@ -0,0 +1,213 @@
+<%#
+ Copyright 2008 Steven Barth <steven@midlink.org>
+ Copyright 2008-2010 Jo-Philipp Wich <jow@openwrt.org>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<%-
+local opkg = require "luci.model.ipkg"
+local fs = require "nixio.fs"
+local wa = require "luci.tools.webadmin"
+local rowcnt = 1
+
+function rowstyle()
+ rowcnt = rowcnt + 1
+ return (rowcnt % 2) + 1
+end
+
+local fstat = fs.statvfs(opkg.overlay_root())
+local space_total = fstat and fstat.blocks or 0
+local space_free = fstat and fstat.bfree or 0
+local space_used = space_total - space_free
+
+local used_perc = math.floor(0.5 + ((space_total > 0) and ((100 / space_total) * space_used) or 100))
+local free_byte = space_free * fstat.frsize
+
+local filter = { }
+
+
+local opkg_list = luci.model.ipkg.list_all
+local querypat
+if query and #query > 0 then
+ querypat = '*%s*' % query
+ opkg_list = luci.model.ipkg.find
+end
+
+local letterpat
+if letter == 35 then
+ letterpat = "[^a-z]*"
+else
+ letterpat = string.char(letter, 42) -- 'A' '*'
+end
+
+-%>
+
+<%+header%>
+
+
+<h2 name="content"><%:Software%></h2>
+
+<div class="cbi-map">
+
+ <ul class="cbi-tabmenu">
+ <li class="cbi-tab"><a href="#"><%:Actions%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=REQUEST_URI%>/ipkg"><%:Configuration%></a></li>
+ </ul>
+
+ <form method="post" action="<%=REQUEST_URI%>">
+ <input type="hidden" name="exec" value="1" />
+ <input type="hidden" name="token" value="<%=token%>" />
+
+ <div class="cbi-section">
+ <div class="cbi-section-node">
+ <% if (install and next(install)) or (remove and next(remove)) or update or upgrade then %>
+ <div class="cbi-value">
+ <% if #stdout > 0 then %><pre><%=pcdata(stdout)%></pre><% end %>
+ <% if #stderr > 0 then %><pre class="error"><%=pcdata(stderr)%></pre><% end %>
+ </div>
+ <% end %>
+
+ <% if querypat then %>
+ <div class="cbi-value">
+ <%:Displaying only packages containing%> <strong>"<%=pcdata(query)%>"</strong>
+ <input type="button" onclick="location.href='?display=<%=luci.http.urlencode(display)%>'" href="#" class="cbi-button cbi-button-reset" style="margin-left:1em" value="<%:Reset%>" />
+ <br style="clear:both" />
+ </div>
+ <% end %>
+
+ <% if no_lists or old_lists then %>
+ <div class="cbi-value">
+ <% if old_lists then %>
+ <%:Package lists are older than 24 hours%>
+ <% else %>
+ <%:No package lists available%>
+ <% end %>
+ <input type="submit" name="update" href="#" class="cbi-button cbi-button-apply" style="margin-left:3em" value="<%:Update lists%>" />
+ </div>
+ <% end %>
+
+ <div class="cbi-value cbi-value-last">
+ <%:Free space%>: <strong><%=(100-used_perc)%>%</strong> (<strong><%=wa.byte_format(free_byte)%></strong>)
+ <div style="margin:3px 0; width:300px; height:10px; border:1px solid #000000; background-color:#80C080">
+ <div style="background-color:#F08080; border-right:1px solid #000000; height:100%; width:<%=used_perc%>%">&#160;</div>
+ </div>
+ </div>
+ </div>
+
+ <br />
+
+ <div class="cbi-section-node">
+ <input type="hidden" name="display" value="<%=pcdata(display)%>" />
+
+ <div class="cbi-value">
+ <label class="cbi-value-title"><%:Download and install package%>:</label>
+ <div class="cbi-value-field">
+ <span><input type="text" name="url" size="30" value="" /></span>
+ <input class="cbi-button cbi-button-save" type="submit" name="go" value="<%:OK%>" />
+ </div>
+ </div>
+
+ <div class="cbi-value cbi-value-last">
+ <label class="cbi-value-title"><%:Filter%>:</label>
+ <div class="cbi-value-field">
+ <span><input type="text" name="query" size="20" value="<%=pcdata(query)%>" /></span>
+ <input type="submit" class="cbi-button cbi-button-action" name="search" value="<%:Find package%>" />
+ </div>
+ </div>
+ </div>
+ </div>
+ </form>
+
+
+ <h3><%:Status%></h3>
+
+
+ <ul class="cbi-tabmenu">
+ <li class="cbi-tab<% if display ~= "available" then %>-disabled<% end %>"><a href="?display=available&amp;query=<%=pcdata(query)%>"><%:Available packages%><% if query then %> (<%=pcdata(query)%>)<% end %></a></li>
+ <li class="cbi-tab<% if display ~= "installed" then %>-disabled<% end %>"><a href="?display=installed&amp;query=<%=pcdata(query)%>"><%:Installed packages%><% if query then %> (<%=pcdata(query)%>)<% end %></a></li>
+ </ul>
+
+ <% if display ~= "available" then %>
+ <div class="cbi-section">
+ <div class="cbi-section-node">
+ <div class="table">
+ <div class="tr cbi-section-table-titles">
+ <div class="th left"><%:Package name%></div>
+ <div class="th left"><%:Version%></div>
+ <div class="th cbi-section-actions">&#160;</div>
+ </div>
+ <% local empty = true; luci.model.ipkg.list_installed(querypat, function(n, v, s, d) empty = false; filter[n] = true %>
+ <div class="tr cbi-rowstyle-<%=rowstyle()%>">
+ <div class="td left"><%=luci.util.pcdata(n)%></div>
+ <div class="td left"><%=luci.util.pcdata(v)%></div>
+ <div class="td cbi-section-actions">
+ <form method="post" class="inline" action="<%=REQUEST_URI%>">
+ <input type="hidden" name="exec" value="1" />
+ <input type="hidden" name="token" value="<%=token%>" />
+ <input type="hidden" name="remove" value="<%=pcdata(n)%>" />
+ <input class="cbi-button cbi-button-remove" type="submit" onclick="window.confirm('<%:Remove%> &quot;<%=luci.util.pcdata(n)%>&quot; ?') &#38;&#38; this.parentNode.submit(); return false" value="<%:Remove%>" />
+ </form>
+ </div>
+ </div>
+ <% end) %>
+ <% if empty then %>
+ <div class="tr cbi-section-table-row">
+ <div class="td left">&#160;</div>
+ <div class="td left"><em><%:none%></em></div>
+ <div class="td left"><em><%:none%></em></div>
+ </div>
+ <% end %>
+ </div>
+ </div>
+ </div>
+ <% else %>
+ <div class="cbi-section">
+ <% if not querypat then %>
+ <ul class="cbi-tabmenu" style="flex-wrap:wrap">
+ <% local i; for i = 65, 90 do %>
+ <li class="cbi-tab<% if letter ~= i then %>-disabled<% end %>"><a href="?display=available&amp;letter=<%=string.char(i)%>"><%=string.char(i)%></a></li>
+ <% end %>
+ <li class="cbi-tab<% if letter ~= 35 then %>-disabled<% end %>"><a href="?display=available&amp;letter=%23">#</a></li>
+ </ul>
+ <% end %>
+ <div class="cbi-section-node cbi-section-node-tabbed">
+ <div class="table">
+ <div class="tr cbi-section-table-titles">
+ <div class="th col-2 left"><%:Package name%></div>
+ <div class="th col-2 left"><%:Version%></div>
+ <div class="th col-1 center"><%:Size (.ipk)%></div>
+ <div class="th col-10 left"><%:Description%></div>
+ <div class="th cbi-section-actions">&#160;</div>
+ </div>
+ <% local empty = true; opkg_list(querypat or letterpat, function(n, v, s, d) if filter[n] then return end; empty = false %>
+ <div class="tr cbi-rowstyle-<%=rowstyle()%>">
+ <div class="td col-2 left"><%=luci.util.pcdata(n)%></div>
+ <div class="td col-2 left"><%=luci.util.pcdata(v)%></div>
+ <div class="td col-1 center"><%=luci.util.pcdata(s)%></div>
+ <div class="td col-10 left"><%=luci.util.pcdata(d)%></div>
+ <div class="td cbi-section-actions">
+ <form method="post" class="inline" action="<%=REQUEST_URI%>">
+ <input type="hidden" name="exec" value="1" />
+ <input type="hidden" name="token" value="<%=token%>" />
+ <input type="hidden" name="install" value="<%=pcdata(n)%>" />
+ <input class="cbi-button cbi-button-apply" type="submit" onclick="window.confirm('<%:Install%> &quot;<%=luci.util.pcdata(n)%>&quot; ?') &#38;&#38; this.parentNode.submit(); return false" value="<%:Install%>" />
+ </form>
+ </div>
+ </div>
+ <% end) %>
+ <% if empty then %>
+ <div class="tr">
+ <div class="td left">&#160;</div>
+ <div class="td left"><em><%:none%></em></div>
+ <div class="td left"><em><%:none%></em></div>
+ <div class="td right"><em><%:none%></em></div>
+ <div class="td left"><em><%:none%></em></div>
+ </div>
+ <% end %>
+ </div>
+ </div>
+ </div>
+ <% end %>
+</div>
+
+<%+footer%>
diff --git a/modules/luci-mod-system/luasrc/view/admin_system/reboot.htm b/modules/luci-mod-system/luasrc/view/admin_system/reboot.htm
new file mode 100644
index 000000000..d23664ada
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/view/admin_system/reboot.htm
@@ -0,0 +1,62 @@
+<%#
+ Copyright 2008 Steven Barth <steven@midlink.org>
+ Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<%+header%>
+
+<h2 name="content"><%:Reboot%></h2>
+
+<p><%:Reboots the operating system of your device%></p>
+
+<%- local c = require("luci.model.uci").cursor():changes(); if c and next(c) then -%>
+ <p class="alert-message warning"><%:Warning: There are unsaved changes that will get lost on reboot!%></p>
+<%- end -%>
+
+<hr />
+
+<input class="cbi-button cbi-button-action important" type="button" value="<%:Perform reboot%>" onclick="reboot(this)" />
+
+<p class="alert-message notice reboot-message" style="display:none">
+ <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" />
+ <span><%:Device is rebooting...%></span>
+</p>
+
+<script type="text/javascript">//<![CDATA[
+ var tries = 0,
+ message = document.querySelector('p.reboot-message'),
+ label = message.querySelector('span');
+
+ function ok() {
+ window.location = '<%=url("admin")%>';
+ }
+
+ function check() {
+ window.setTimeout(ping, 5000);
+ }
+
+ function ping() {
+ var img = document.createElement('img');
+
+ img.onload = ok;
+ img.onerror = check;
+ img.src = '<%=resource%>/icons/loading.gif?' + Math.random();
+
+ if (tries++ >= 30) {
+ message.classList.remove('notice');
+ message.classList.add('warning');
+ label.innerHTML = '<%:Device unreachable! Still waiting for device...%>';
+ }
+ }
+
+ function reboot(button) {
+ button.style.display = 'none';
+ message.style.display = '';
+ label.innerHTML = '<%:Waiting for device...%>';
+
+ (new XHR()).post('<%=url("admin/system/reboot/call")%>', { token: '<%=token%>' }, check);
+ }
+//]]></script>
+
+<%+footer%>
diff --git a/modules/luci-mod-system/luasrc/view/admin_system/upgrade.htm b/modules/luci-mod-system/luasrc/view/admin_system/upgrade.htm
new file mode 100644
index 000000000..597ddfd6b
--- /dev/null
+++ b/modules/luci-mod-system/luasrc/view/admin_system/upgrade.htm
@@ -0,0 +1,65 @@
+<%#
+ Copyright 2008 Steven Barth <steven@midlink.org>
+ Copyright 2008-2009 Jo-Philipp Wich <jow@openwrt.org>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<%+header%>
+
+<h2 name="content"><%:Flash Firmware%> - <%:Verify%></h2>
+<p>
+ <%_ The flash image was uploaded.
+ Below is the checksum and file size listed,
+ compare them with the original file to ensure data integrity.<br />
+ Click "Proceed" below to start the flash procedure. %>
+
+ <% if storage > 0 and size > storage then %>
+ <br /><br />
+ <div class="error"><%:It appears that you are trying to
+ flash an image that does not fit into the flash memory, please verify
+ the image file! %></div>
+ <% end %>
+
+</p>
+
+<div class="cbi-section">
+ <ul>
+ <li><%:Checksum%><br />
+ <%:MD5%>: <code><%=checksum%></code><br />
+ <%:SHA256%>: <code><%=sha256ch%></code></li>
+ <li><%:Size%>: <%
+ local w = require "luci.tools.webadmin"
+ write(w.byte_format(size))
+
+ if storage > 0 then
+ write(luci.i18n.translatef(
+ " (%s available)",
+ w.byte_format(storage)
+ ))
+ end
+ %></li>
+ <li><% if keep then %>
+ <%:Configuration files will be kept%>
+ <% else %>
+ <%:Caution: Configuration files will be erased%>
+ <% end %></li>
+ <% if force then %>
+ <li>
+ <%:Caution: System upgrade will be forced%>
+ </li>
+ <% end %>
+ </ul>
+</div>
+
+<div class="cbi-page-actions right">
+ <form class="inline" action="<%=REQUEST_URI%>" method="post">
+ <input type="hidden" name="token" value="<%=token%>" />
+ <input type="hidden" name="step" value="2" />
+ <input type="hidden" name="keep" value="<%=keep and "1" or ""%>" />
+ <input type="hidden" name="force" value="<%=force and "1" or ""%>" />
+ <input class="cbi-button cbi-button-reset" name="cancel" type="submit" value="<%:Cancel%>" />
+ <input class="cbi-button cbi-button-apply" type="submit" value="<%:Proceed%>" />
+ </form>
+</div>
+
+<%+footer%>