luci-mod-admin-full: restructure and fix backup, restore and sysuprade (#517)
Do not use standard post security checking for actions that require file upload since reading the token value will trigger parsing of the http message body before the file upload handler has been set, which causes LuCI to buffer the entire request body in memory. In order to simplify the code and logic flow, split action_flashops() into separate handlers for reset, backup, restore and sysupgrade. Let the backup restore and sysupgrade handlers use the new test_post_security() method in luci.dispatcher to perform token checking *after* setting the upload handler. Signed-off-by: Jo-Philipp Wich <jow@openwrt.org>
This commit is contained in:
parent
d32c685039
commit
94ab57f48c
3 changed files with 202 additions and 160 deletions
|
@ -31,9 +31,15 @@ function index()
|
||||||
entry({"admin", "system", "leds"}, cbi("admin_system/leds"), _("<abbr title=\"Light Emitting Diode\">LED</abbr> Configuration"), 60)
|
entry({"admin", "system", "leds"}, cbi("admin_system/leds"), _("<abbr title=\"Light Emitting Diode\">LED</abbr> Configuration"), 60)
|
||||||
end
|
end
|
||||||
|
|
||||||
entry({"admin", "system", "flashops"}, post_on({ exec = "1" }, "action_flashops"), _("Backup / Flash Firmware"), 70)
|
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", "backupfiles"}, form("admin_system/backupfiles"))
|
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"}, template("admin_system/reboot"), _("Reboot"), 90)
|
||||||
entry({"admin", "system", "reboot", "call"}, post("action_reboot"))
|
entry({"admin", "system", "reboot", "call"}, post("action_reboot"))
|
||||||
end
|
end
|
||||||
|
@ -171,154 +177,187 @@ function action_packages()
|
||||||
end
|
end
|
||||||
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 supports_sysupgrade()
|
||||||
|
return nixio.fs.access("/lib/upgrade/platform.sh")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function supports_reset()
|
||||||
|
return (os.execute([[grep -sq '"rootfs_data"' /proc/mtd]]) == 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()
|
function action_flashops()
|
||||||
local http = require "luci.http"
|
|
||||||
local sys = require "luci.sys"
|
|
||||||
local fs = require "nixio.fs"
|
|
||||||
|
|
||||||
local submit = (http.formvalue("exec") == "1")
|
|
||||||
|
|
||||||
local upgrade_avail = fs.access("/lib/upgrade/platform.sh")
|
|
||||||
local reset_avail = os.execute([[grep '"rootfs_data"' /proc/mtd >/dev/null 2>&1]]) == 0
|
|
||||||
|
|
||||||
local restore_cmd = "tar -xzC/ >/dev/null 2>&1"
|
|
||||||
local backup_cmd = "sysupgrade --create-backup - 2>/dev/null"
|
|
||||||
local image_tmp = "/tmp/firmware.img"
|
|
||||||
|
|
||||||
local function image_supported()
|
|
||||||
return (os.execute("sysupgrade -T %q >/dev/null" % image_tmp) == 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function image_checksum()
|
|
||||||
return (luci.sys.exec("md5sum %q" % image_tmp):match("^([^%s]+)"))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function storage_size()
|
|
||||||
local size = 0
|
|
||||||
if 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 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
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Handle modifying actions
|
|
||||||
--
|
|
||||||
if submit then
|
|
||||||
|
|
||||||
local fp
|
|
||||||
http.setfilehandler(
|
|
||||||
function(meta, chunk, eof)
|
|
||||||
if not fp then
|
|
||||||
if meta and meta.name == "image" then
|
|
||||||
fp = io.open(image_tmp, "w")
|
|
||||||
else
|
|
||||||
fp = io.popen(restore_cmd, "w")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if chunk then
|
|
||||||
fp:write(chunk)
|
|
||||||
end
|
|
||||||
if eof then
|
|
||||||
fp:close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
|
|
||||||
if http.formvalue("backup") then
|
|
||||||
--
|
|
||||||
-- Assemble file list, generate backup
|
|
||||||
--
|
|
||||||
local reader = ltn12_popen(backup_cmd)
|
|
||||||
http.header('Content-Disposition', 'attachment; filename="backup-%s-%s.tar.gz"' % {
|
|
||||||
luci.sys.hostname(), os.date("%Y-%m-%d")})
|
|
||||||
http.prepare_content("application/x-targz")
|
|
||||||
luci.ltn12.pump.all(reader, http.write)
|
|
||||||
return
|
|
||||||
|
|
||||||
elseif http.formvalue("restore") then
|
|
||||||
--
|
|
||||||
-- Unpack received .tar.gz
|
|
||||||
--
|
|
||||||
local upload = http.formvalue("archive")
|
|
||||||
if upload and #upload > 0 then
|
|
||||||
luci.template.render("admin_system/applyreboot")
|
|
||||||
luci.sys.reboot()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif http.formvalue("image") or http.formvalue("step") then
|
|
||||||
--
|
|
||||||
-- Initiate firmware flash
|
|
||||||
--
|
|
||||||
local step = tonumber(http.formvalue("step") or 1)
|
|
||||||
if step == 1 then
|
|
||||||
if image_supported() then
|
|
||||||
luci.template.render("admin_system/upgrade", {
|
|
||||||
checksum = image_checksum(),
|
|
||||||
storage = storage_size(),
|
|
||||||
size = (fs.stat(image_tmp, "size") or 0),
|
|
||||||
keep = (not not http.formvalue("keep"))
|
|
||||||
})
|
|
||||||
else
|
|
||||||
fs.unlink(image_tmp)
|
|
||||||
luci.template.render("admin_system/flashops", {
|
|
||||||
reset_avail = reset_avail,
|
|
||||||
upgrade_avail = upgrade_avail,
|
|
||||||
image_invalid = true
|
|
||||||
})
|
|
||||||
end
|
|
||||||
return
|
|
||||||
--
|
|
||||||
-- Start sysupgrade flash
|
|
||||||
--
|
|
||||||
elseif step == 2 then
|
|
||||||
local keep = (http.formvalue("keep") == "1") and "" or "-n"
|
|
||||||
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 "192.168.1.1" or nil
|
|
||||||
})
|
|
||||||
fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp })
|
|
||||||
return
|
|
||||||
end
|
|
||||||
elseif reset_avail and http.formvalue("reset") then
|
|
||||||
--
|
|
||||||
-- Reset system
|
|
||||||
--
|
|
||||||
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("killall dropbear uhttpd; sleep 1; mtd -r erase rootfs_data")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Overview
|
-- Overview
|
||||||
--
|
--
|
||||||
luci.template.render("admin_system/flashops", {
|
luci.template.render("admin_system/flashops", {
|
||||||
reset_avail = reset_avail,
|
reset_avail = supports_reset(),
|
||||||
upgrade_avail = upgrade_avail
|
upgrade_avail = supports_sysupgrade()
|
||||||
})
|
})
|
||||||
end
|
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
|
||||||
|
if image_supported(image_tmp) then
|
||||||
|
luci.template.render("admin_system/upgrade", {
|
||||||
|
checksum = image_checksum(image_tmp),
|
||||||
|
storage = storage_size(),
|
||||||
|
size = (fs.stat(image_tmp, "size") or 0),
|
||||||
|
keep = (not not http.formvalue("keep"))
|
||||||
|
})
|
||||||
|
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"
|
||||||
|
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 "192.168.1.1" or nil
|
||||||
|
})
|
||||||
|
fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, 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_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
|
||||||
|
luci.template.render("admin_system/applyreboot")
|
||||||
|
os.execute("tar -C / -xzf %q >/dev/null 2>&1" % archive_tmp)
|
||||||
|
luci.sys.reboot()
|
||||||
|
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("killall dropbear uhttpd; sleep 1; mtd -r erase rootfs_data")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
http.redirect(luci.dispatcher.build_url('admin/system/flashops'))
|
||||||
|
end
|
||||||
|
|
||||||
function action_passwd()
|
function action_passwd()
|
||||||
local p1 = luci.http.formvalue("pwd1")
|
local p1 = luci.http.formvalue("pwd1")
|
||||||
local p2 = luci.http.formvalue("pwd2")
|
local p2 = luci.http.formvalue("pwd2")
|
||||||
|
|
|
@ -17,38 +17,43 @@
|
||||||
|
|
||||||
<fieldset class="cbi-section">
|
<fieldset class="cbi-section">
|
||||||
<legend><%:Backup / Restore%></legend>
|
<legend><%:Backup / Restore%></legend>
|
||||||
<form method="post" action="<%=url('admin/system/flashops')%>" enctype="multipart/form-data">
|
<div class="cbi-section-descr"><%:Click "Generate archive" to download a tar archive of the current configuration files. To reset the firmware to its initial state, click "Perform reset" (only possible with squashfs images).%></div>
|
||||||
<input type="hidden" name="exec" value="1" />
|
<div class="cbi-section-node">
|
||||||
<input type="hidden" name="token" value="<%=token%>" />
|
<form class="inline" method="post" action="<%=url('admin/system/flashops/backup')%>">
|
||||||
<div class="cbi-section-descr"><%:Click "Generate archive" to download a tar archive of the current configuration files. To reset the firmware to its initial state, click "Perform reset" (only possible with squashfs images).%></div>
|
<input type="hidden" name="token" value="<%=token%>" />
|
||||||
<div class="cbi-section-node">
|
|
||||||
<div class="cbi-value<% if not reset_avail then %> cbi-value-last<% end %>">
|
<div class="cbi-value<% if not reset_avail then %> cbi-value-last<% end %>">
|
||||||
<label class="cbi-value-title" for="image"><%:Download backup%>:</label>
|
<label class="cbi-value-title" for="image"><%:Download backup%>:</label>
|
||||||
<div class="cbi-value-field">
|
<div class="cbi-value-field">
|
||||||
<input class="cbi-button cbi-button-apply" type="submit" name="backup" value="<%:Generate archive%>" />
|
<input class="cbi-button cbi-button-apply" type="submit" name="backup" value="<%:Generate archive%>" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if reset_avail then %>
|
</form>
|
||||||
|
<% 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">
|
<div class="cbi-value cbi-value-last">
|
||||||
<label class="cbi-value-title"><%:Reset to defaults%>:</label>
|
<label class="cbi-value-title"><%:Reset to defaults%>:</label>
|
||||||
<div class="cbi-value-field">
|
<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%>" />
|
<input onclick="return confirm('<%:Really reset all changes?%>')" class="cbi-button cbi-button-reset" type="submit" name="reset" value="<%:Perform reset%>" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
</form>
|
||||||
</div>
|
<% end %>
|
||||||
<br />
|
</div>
|
||||||
<div class="cbi-section-descr"><%:To restore configuration files, you can upload a previously generated backup archive here.%></div>
|
<br />
|
||||||
<div class="cbi-section-node">
|
<div class="cbi-section-descr"><%:To restore configuration files, you can upload a previously generated backup archive here.%></div>
|
||||||
|
<div class="cbi-section-node">
|
||||||
|
<form class="inline" method="post" action="<%=url('admin/system/flashops/restore')%>" enctype="multipart/form-data">
|
||||||
<div class="cbi-value cbi-value-last">
|
<div class="cbi-value cbi-value-last">
|
||||||
<label class="cbi-value-title" for="archive"><%:Restore backup%>:</label>
|
<label class="cbi-value-title" for="archive"><%:Restore backup%>:</label>
|
||||||
<div class="cbi-value-field">
|
<div class="cbi-value-field">
|
||||||
|
<input type="hidden" name="token" value="<%=token%>" />
|
||||||
<input type="file" name="archive" id="archive" />
|
<input type="file" name="archive" id="archive" />
|
||||||
<input type="submit" class="cbi-button cbi-input-apply" name="restore" value="<%:Upload archive...%>" />
|
<input type="submit" class="cbi-button cbi-input-apply" name="restore" value="<%:Upload archive...%>" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
@ -56,8 +61,7 @@
|
||||||
<fieldset class="cbi-section">
|
<fieldset class="cbi-section">
|
||||||
<legend><%:Flash new firmware image%></legend>
|
<legend><%:Flash new firmware image%></legend>
|
||||||
<% if upgrade_avail then %>
|
<% if upgrade_avail then %>
|
||||||
<form method="post" action="<%=url('admin/system/flashops')%>" enctype="multipart/form-data">
|
<form method="post" action="<%=url('admin/system/flashops/sysupgrade')%>" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="exec" value="1" />
|
|
||||||
<input type="hidden" name="token" value="<%=token%>" />
|
<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 an OpenWrt compatible firmware image).%></div>
|
<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 an OpenWrt compatible firmware image).%></div>
|
||||||
<div class="cbi-section-node">
|
<div class="cbi-section-node">
|
||||||
|
|
|
@ -45,12 +45,11 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="cbi-page-actions right">
|
<div class="cbi-page-actions right">
|
||||||
<form style="display:inline" action="<%=REQUEST_URI%>" method="post">
|
<form class="inline" action="<%=REQUEST_URI%>" method="post">
|
||||||
<input class="cbi-button cbi-button-reset" type="submit" value="<%:Cancel%>" />
|
<input type="hidden" name="token" value="<%=token%>" />
|
||||||
</form>
|
|
||||||
<form style="display:inline" action="<%=REQUEST_URI%>" method="post">
|
|
||||||
<input type="hidden" name="step" value="2" />
|
<input type="hidden" name="step" value="2" />
|
||||||
<input type="hidden" name="keep" value="<%=keep and "1" or ""%>" />
|
<input type="hidden" name="keep" value="<%=keep 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%>" />
|
<input class="cbi-button cbi-button-apply" type="submit" value="<%:Proceed%>" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue