mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 11:45:13 +08:00
🎨 Sync 2026-06-24 20:46:52
This commit is contained in:
1 parent
668e1f1cb8
commit
7e9cb31284
22 files changed
+642
-145
No files matched your search
+2
-2
@@ -6,8 +6,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=gowebdav
|
||||
PKG_VERSION:=5.11.10
|
||||
PKG_RELEASE:=13
|
||||
PKG_VERSION:=5.11.11
|
||||
PKG_RELEASE:=14
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
|
||||
|
||||
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.4)
|
||||
PKG_RELEASE:=10
|
||||
PKG_RELEASE:=11
|
||||
|
||||
LUCI_TITLE:=LuCI support for EasyTier
|
||||
LUCI_DEPENDS:=+kmod-tun +easytier +luci-compat
|
||||
|
||||
@@ -118,16 +118,36 @@ local translate = i18n.translate
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.log-wiki-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
margin-right: 8px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.log-wiki-link img {
|
||||
display: block;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
.log-select {
|
||||
padding: 8px 14px;
|
||||
box-sizing: border-box;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
height: 34px;
|
||||
padding: 6px 24px 6px 10px;
|
||||
border: 1px solid var(--log-border);
|
||||
background: var(--log-bg);
|
||||
color: var(--log-text);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
flex: 0 0 auto;
|
||||
max-width: 136px;
|
||||
}
|
||||
|
||||
.log-select:hover, .log-select:focus {
|
||||
@@ -138,6 +158,12 @@ local translate = i18n.translate
|
||||
}
|
||||
|
||||
.log-btn {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
height: 34px;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--log-border);
|
||||
background: var(--log-bg);
|
||||
@@ -148,6 +174,10 @@ local translate = i18n.translate
|
||||
font-weight: 500;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
flex: 0 0 auto;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
.log-btn:hover {
|
||||
@@ -162,15 +192,65 @@ local translate = i18n.translate
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.log-btn-primary {
|
||||
background: var(--tab-active);
|
||||
color: white;
|
||||
border-color: var(--tab-active);
|
||||
.log-switch {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 34px;
|
||||
padding: 0 4px;
|
||||
color: var(--log-text);
|
||||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
.log-btn-primary:hover {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 4px 12px rgba(33,150,243,0.3);
|
||||
.log-switch input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.log-switch-track {
|
||||
position: relative;
|
||||
width: 42px;
|
||||
height: 22px;
|
||||
border-radius: 999px;
|
||||
background: var(--log-border);
|
||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.18);
|
||||
transition: background 0.2s ease, box-shadow 0.2s ease;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.log-switch-track::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.28);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.log-switch input:checked + .log-switch-track {
|
||||
background: var(--tab-active);
|
||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.12), 0 0 0 2px rgba(33,150,243,0.16);
|
||||
}
|
||||
|
||||
.log-switch input:checked + .log-switch-track::after {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.log-switch-text {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
@@ -256,28 +336,129 @@ local translate = i18n.translate
|
||||
.log-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 12px 16px;
|
||||
padding: 10px 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.log-tabs {
|
||||
width: 100%;
|
||||
justify-content: space-around;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.log-tab {
|
||||
padding: 7px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.log-controls {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.log-wiki-link {
|
||||
height: 30px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.log-wiki-link img {
|
||||
max-width: 96px;
|
||||
}
|
||||
|
||||
.log-select {
|
||||
height: 28px;
|
||||
max-width: 96px;
|
||||
padding: 3px 18px 3px 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
height: 400px;
|
||||
height: 60vh;
|
||||
min-height: 320px;
|
||||
font-size: 12px;
|
||||
padding: 16px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.log-btn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
flex: 0 0 auto;
|
||||
height: 28px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
line-height: 1.1;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
.log-switch {
|
||||
height: 28px;
|
||||
gap: 5px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.log-switch-track {
|
||||
width: 34px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.log-switch-track::after {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.log-switch input:checked + .log-switch-track::after {
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
.log-switch-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.log-footer {
|
||||
padding: 6px 12px;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.log-container {
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.log-controls {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.log-wiki-link img {
|
||||
max-width: 82px;
|
||||
}
|
||||
|
||||
.log-select {
|
||||
max-width: 88px;
|
||||
}
|
||||
|
||||
.log-btn {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.log-switch-text {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -291,11 +472,16 @@ local translate = i18n.translate
|
||||
<button class="log-tab" data-log="web" onclick="switchLog('web')">
|
||||
<%=translate("web log")%>
|
||||
</button>
|
||||
<a class="log-wiki-link" href="https://deepwiki.com/EasyTier/EasyTier" target="_blank">
|
||||
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
|
||||
</a>
|
||||
</div>
|
||||
<div class="log-controls">
|
||||
<a href="https://deepwiki.com/EasyTier/EasyTier" target="_blank" style="display:inline-block;height:36px;line-height:36px;margin-right:8px;">
|
||||
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki" style="vertical-align:middle;">
|
||||
</a>
|
||||
<label class="log-switch" id="auto_refresh_switch" title="<%=translate("Auto Refresh")%>">
|
||||
<span class="log-switch-text"><%=translate("Auto Refresh")%></span>
|
||||
<input type="checkbox" id="auto_refresh_toggle" checked onchange="toggleAutoRefresh(true)">
|
||||
<span class="log-switch-track" aria-hidden="true"></span>
|
||||
</label>
|
||||
<select id="log_level_filter" class="log-select" onchange="filterLogLevel()">
|
||||
<option value="all"><%=translate("All Levels")%></option>
|
||||
<option value="INFO">INFO</option>
|
||||
@@ -304,9 +490,6 @@ local translate = i18n.translate
|
||||
<option value="DEBUG">DEBUG</option>
|
||||
<option value="TRACE">TRACE</option>
|
||||
</select>
|
||||
<button class="log-btn log-btn-primary" id="auto_refresh_btn" onclick="toggleAutoRefresh()">
|
||||
<%=translate("Stop Auto Refresh")%>
|
||||
</button>
|
||||
<button class="log-btn" onclick="clearCurrentLog()"><%=translate("Clear Logs")%></button>
|
||||
<button class="log-btn" onclick="downloadCurrentLog()"><%=translate("Download Log")%></button>
|
||||
</div>
|
||||
@@ -521,18 +704,20 @@ function startPolling() {
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function toggleAutoRefresh() {
|
||||
autoRefreshEnabled = !autoRefreshEnabled;
|
||||
var btn = document.getElementById('auto_refresh_btn');
|
||||
function toggleAutoRefresh(refreshNow) {
|
||||
var toggle = document.getElementById('auto_refresh_toggle');
|
||||
var wrapper = document.getElementById('auto_refresh_switch');
|
||||
autoRefreshEnabled = toggle ? toggle.checked : !autoRefreshEnabled;
|
||||
|
||||
if (autoRefreshEnabled) {
|
||||
btn.textContent = '<%=translate("Stop Auto Refresh")%>';
|
||||
btn.classList.add('log-btn-primary');
|
||||
if (wrapper) wrapper.title = '<%=translate("Stop Auto Refresh")%>';
|
||||
if (refreshNow) refreshLog();
|
||||
} else {
|
||||
btn.textContent = '<%=translate("Start Auto Refresh")%>';
|
||||
btn.classList.remove('log-btn-primary');
|
||||
if (wrapper) wrapper.title = '<%=translate("Start Auto Refresh")%>';
|
||||
}
|
||||
}
|
||||
|
||||
toggleAutoRefresh(false);
|
||||
refreshLog();
|
||||
startPolling();
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ local uci = require "luci.model.uci".cursor()
|
||||
--info: #2196F3;
|
||||
--warning: #FF9800;
|
||||
--hover-bg: #f5f5f5;
|
||||
--field-width: 520px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@@ -33,17 +34,17 @@ local uci = require "luci.model.uci".cursor()
|
||||
|
||||
.web-console-container {
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
padding: 0 20px;
|
||||
margin: 12px auto;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.web-card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 16px;
|
||||
padding: 28px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
border-radius: 10px;
|
||||
padding: 18px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
@@ -52,47 +53,47 @@ local uci = require "luci.model.uci".cursor()
|
||||
}
|
||||
|
||||
.web-card h3 {
|
||||
margin: 0 0 24px 0;
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-bottom: 3px solid var(--info);
|
||||
padding-bottom: 14px;
|
||||
border-bottom: 2px solid var(--info);
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.web-card h3::before {
|
||||
content: "⚙️";
|
||||
font-size: 24px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 6px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.form-group .description {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
margin-top: 4px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
padding-left: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
@@ -108,13 +109,17 @@ local uci = require "luci.model.uci".cursor()
|
||||
}
|
||||
|
||||
.form-control {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
max-width: var(--field-width);
|
||||
height: 32px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--card-bg);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
transition: all 0.2s ease;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
@@ -122,7 +127,7 @@ local uci = require "luci.model.uci".cursor()
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--info);
|
||||
box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);
|
||||
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
|
||||
}
|
||||
|
||||
.form-control:disabled {
|
||||
@@ -132,13 +137,17 @@ local uci = require "luci.model.uci".cursor()
|
||||
}
|
||||
|
||||
.checkbox-wrapper {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
padding: 12px 16px;
|
||||
width: 100%;
|
||||
max-width: var(--field-width);
|
||||
padding: 8px 10px;
|
||||
background: var(--hover-bg);
|
||||
border-radius: 8px;
|
||||
border-radius: 6px;
|
||||
transition: background 0.2s;
|
||||
position: relative;
|
||||
min-height: 26px;
|
||||
min-height: 20px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.checkbox-wrapper:hover {
|
||||
@@ -150,8 +159,11 @@ local uci = require "luci.model.uci".cursor()
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: block;
|
||||
line-height: 26px;
|
||||
padding-right: 60px;
|
||||
line-height: 20px;
|
||||
padding-right: 48px;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
.checkbox-wrapper label::before {
|
||||
@@ -161,10 +173,10 @@ local uci = require "luci.model.uci".cursor()
|
||||
/* iOS风格开关 */
|
||||
.switch {
|
||||
position: absolute;
|
||||
width: 48px;
|
||||
height: 26px;
|
||||
right: 16px;
|
||||
top: 12px;
|
||||
width: 38px;
|
||||
height: 20px;
|
||||
right: 10px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
@@ -179,21 +191,21 @@ local uci = require "luci.model.uci".cursor()
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 48px;
|
||||
height: 26px;
|
||||
width: 38px;
|
||||
height: 20px;
|
||||
background-color: #ccc;
|
||||
-webkit-transition: 0.3s;
|
||||
transition: 0.3s;
|
||||
border-radius: 26px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
background-color: white;
|
||||
-webkit-transition: 0.3s;
|
||||
transition: 0.3s;
|
||||
@@ -206,9 +218,9 @@ input:checked + .slider {
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(22px);
|
||||
-ms-transform: translateX(22px);
|
||||
transform: translateX(22px);
|
||||
-webkit-transform: translateX(18px);
|
||||
-ms-transform: translateX(18px);
|
||||
transform: translateX(18px);
|
||||
}
|
||||
|
||||
input:disabled + .slider {
|
||||
@@ -217,16 +229,16 @@ input:disabled + .slider {
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 28px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
@@ -259,17 +271,17 @@ input:disabled + .slider {
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 32px;
|
||||
padding-top: 24px;
|
||||
border-top: 2px dashed var(--border-color);
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px dashed var(--border-color);
|
||||
}
|
||||
|
||||
.iframe-container {
|
||||
width: 100%;
|
||||
height: 800px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: var(--card-bg);
|
||||
box-shadow: inset 0 2px 8px rgba(0,0,0,0.05);
|
||||
@@ -284,18 +296,18 @@ input:disabled + .slider {
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
gap: 5px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 14px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.status-badge::before {
|
||||
content: "";
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
@@ -345,9 +357,9 @@ input:disabled + .slider {
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 14px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
@@ -356,7 +368,7 @@ input:disabled + .slider {
|
||||
-ms-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
z-index: 10000;
|
||||
min-width: 320px;
|
||||
min-width: 260px;
|
||||
max-width: 500px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
|
||||
@@ -408,39 +420,39 @@ input:disabled + .slider {
|
||||
|
||||
.modal-content {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
padding: 18px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modal-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.modal-btn {
|
||||
padding: 10px 20px;
|
||||
padding: 7px 14px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
@@ -458,6 +470,84 @@ input:disabled + .slider {
|
||||
background: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.web-console-container {
|
||||
margin: 8px auto;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.web-card {
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.web-card h3 {
|
||||
font-size: 15px;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.web-card h3::before {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
height: 30px;
|
||||
padding: 5px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.checkbox-wrapper {
|
||||
padding: 7px 8px;
|
||||
}
|
||||
|
||||
.checkbox-wrapper label {
|
||||
font-size: 12px;
|
||||
padding-right: 44px;
|
||||
white-space: normal;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.switch {
|
||||
right: 8px;
|
||||
top: 7px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 7px 12px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
padding-top: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.alert {
|
||||
min-width: 220px;
|
||||
max-width: calc(100vw - 24px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="web-console-container">
|
||||
@@ -479,7 +569,7 @@ input:disabled + .slider {
|
||||
<label for="web_db_path"><%:Database File Path%></label>
|
||||
<div style="display:flex;gap:8px;align-items:flex-start;flex-wrap:wrap;">
|
||||
<input type="text" class="form-control" id="web_db_path" name="web_db_path" placeholder="/etc/easytier/et.db" style="flex:1;min-width:200px;">
|
||||
<button type="button" class="btn" onclick="showResetDbModal()" style="background:var(--error);color:white;padding:12px 16px;white-space:nowrap;">
|
||||
<button type="button" class="btn" onclick="showResetDbModal()" style="background:var(--error);color:white;padding:8px 12px;white-space:nowrap;">
|
||||
<%:Reset Database%>
|
||||
</button>
|
||||
</div>
|
||||
@@ -602,7 +692,7 @@ input:disabled + .slider {
|
||||
</div>
|
||||
|
||||
<!-- OIDC单点登录配置 -->
|
||||
<div style="margin-top:32px;padding-top:24px;border-top:2px dashed var(--border-color);">
|
||||
<div style="margin-top:20px;padding-top:16px;border-top:1px dashed var(--border-color);">
|
||||
<div class="form-group">
|
||||
<div class="checkbox-wrapper">
|
||||
<label for="web_oidc_enabled"><%:Enable OIDC Single Sign-On%></label>
|
||||
@@ -676,7 +766,7 @@ input:disabled + .slider {
|
||||
</div>
|
||||
|
||||
<!-- Webhook高级配置 -->
|
||||
<div style="margin-top:32px;padding-top:24px;border-top:2px dashed var(--border-color);">
|
||||
<div style="margin-top:20px;padding-top:16px;border-top:1px dashed var(--border-color);">
|
||||
<div class="form-group">
|
||||
<div class="checkbox-wrapper">
|
||||
<label for="web_webhook_enabled"><%:Enable Webhook Integration%></label>
|
||||
@@ -745,12 +835,12 @@ input:disabled + .slider {
|
||||
<div class="modal-content">
|
||||
<div class="modal-title" style="color:var(--error);">⚠️ <%:Warning%></div>
|
||||
<div class="modal-message">
|
||||
<div style="color:var(--error);font-weight:600;margin-bottom:12px;">
|
||||
<div style="color:var(--error);font-weight:600;margin-bottom:8px;">
|
||||
<%:Are you sure you want to reset the database?%>
|
||||
</div>
|
||||
<div style="font-size:13px;line-height:1.6;color:var(--text-secondary);">
|
||||
<div style="font-size:12px;line-height:1.5;color:var(--text-secondary);">
|
||||
<%:This will delete all database files and cannot be recovered%>:<br>
|
||||
<code id="reset_db_files" style="display:block;margin-top:8px;padding:8px;background:var(--hover-bg);border-radius:4px;word-break:break-all;font-size:12px;"></code>
|
||||
<code id="reset_db_files" style="display:block;margin-top:6px;padding:6px;background:var(--hover-bg);border-radius:4px;word-break:break-all;font-size:11px;"></code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-buttons">
|
||||
@@ -765,7 +855,7 @@ input:disabled + .slider {
|
||||
<div class="modal-content">
|
||||
<div class="modal-title"><%:Database Reset Successful%></div>
|
||||
<div class="modal-message">
|
||||
<pre id="restart_web_message" style="white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.6;"></pre>
|
||||
<pre id="restart_web_message" style="white-space:pre-wrap;word-break:break-all;font-size:12px;line-height:1.5;"></pre>
|
||||
</div>
|
||||
<div class="modal-buttons">
|
||||
<button class="modal-btn modal-btn-secondary" onclick="closeRestartModal()"><%:Later%></button>
|
||||
|
||||
@@ -466,10 +466,10 @@ start_et() {
|
||||
fi
|
||||
if [ ! -z "$proxy_network" ] ; then
|
||||
if [[ "$(grep "list proxy_network" /etc/config/easytier | awk '{print $3}' | wc -l ) " -eq 1 ]]; then
|
||||
core_cmd="${core_cmd} -n $proxy_network"
|
||||
core_cmd="${core_cmd} -n '$proxy_network'"
|
||||
else
|
||||
for proxy_networks in $(cat /etc/config/easytier | grep 'list proxy_network' | awk -F 'list proxy_network' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
|
||||
core_cmd="${core_cmd} -n $proxy_networks"
|
||||
core_cmd="${core_cmd} -n '$proxy_networks'"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -4,7 +4,7 @@ LUCI_TITLE:=Information dashboard for 3G/LTE dongle
|
||||
LUCI_DEPENDS:=+modeminfo
|
||||
PKG_LICENSE:=GPLv3
|
||||
PKG_VERSION:=0.4.8
|
||||
PKG_RELEASE:=10
|
||||
PKG_RELEASE:=11
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
|
||||
@@ -434,8 +434,8 @@ return view.extend({
|
||||
|
||||
updateSignalBar(
|
||||
'sinr', i, modem.sinr, ' dB',
|
||||
netmode === 'LTE' ? 'sinr' : 'ecio',
|
||||
netmode === 'LTE' ? -20 : -24
|
||||
/LTE|NR/.test(netmode) ? 'sinr' : 'ecio',
|
||||
/LTE|NR/.test(netmode) ? -20 : -24
|
||||
);
|
||||
|
||||
updateSignalBar('rsrp', i, modem.rsrp, ' dBm', 'rsrp', -140);
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.6.2
|
||||
PKG_RELEASE:=165
|
||||
PKG_RELEASE:=166
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -49,6 +49,12 @@ o.rewrite_option = o.option
|
||||
o = s:option(Value, _n("realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
|
||||
@@ -351,6 +351,12 @@ end
|
||||
|
||||
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
|
||||
@@ -375,6 +375,12 @@ if singbox_tags:find("with_quic") then
|
||||
|
||||
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
|
||||
@@ -36,6 +36,12 @@ o:depends({ [_n("custom")] = false })
|
||||
o = s:option(Value, _n("realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
|
||||
@@ -130,6 +130,12 @@ o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
|
||||
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
|
||||
@@ -163,6 +163,12 @@ if singbox_tags:find("with_quic") then
|
||||
|
||||
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
|
||||
@@ -1605,12 +1605,20 @@ function vps_domain_exclude(domain)
|
||||
end
|
||||
|
||||
function parse_realm_uri(uri)
|
||||
if type(uri) ~= "string" then return nil end
|
||||
uri = trim(uri)
|
||||
if uri == "" then return nil end
|
||||
-- realm[+http]://token@server/realm_id?query
|
||||
local scheme, token, server_url, realm_id, query = trim(uri):match("^(realm%+http|realm)://([^@]+)@([^/]+)/([^?]*)%??(.*)$")
|
||||
if not scheme or not token or not server_url or not realm_id then return nil end
|
||||
local scheme = (uri:match("^realm%+http://") and "realm+http") or (uri:match("^realm://") and "realm")
|
||||
if not scheme then return nil end
|
||||
uri = uri:gsub("^realm%+http://", ""):gsub("^realm://", "")
|
||||
local token, server_url, realm_id, query = uri:match("^([^@]+)@([^/]+)/([^?]*)%??(.*)$")
|
||||
if not token or not server_url or not realm_id then return nil end
|
||||
realm_id = realm_id:gsub("/+$", "")
|
||||
local address, port = server_url:match("^%[?([^%]]+)%]?:?(%d*)$")
|
||||
local address, port = server_url:match("^%[([^%]]+)%]:(%d+)$") --ipv6:port
|
||||
if not address then
|
||||
address, port = server_url:match("^([^:]+):(%d+)$") --ipv4[domain]:port
|
||||
end
|
||||
address = address or server_url:match("^%[([^%]]+)%]$") or server_url
|
||||
port = tonumber(port) or (scheme == "realm+http" and 80 or 443)
|
||||
local realm = {
|
||||
scheme = scheme,
|
||||
@@ -1622,9 +1630,9 @@ function parse_realm_uri(uri)
|
||||
}
|
||||
-- 解析 query 中的 stun=
|
||||
local stun_servers
|
||||
for value in (query or ""):gmatch("[?&]?[Ss][Tt][Uu][Nn]=([^&]+)") do
|
||||
if not stun_servers then stun_servers = {} end
|
||||
stun_servers[#stun_servers + 1] = value
|
||||
for v in (query or ""):gmatch("[Ss][Tt][Uu][Nn]=([^&]+)") do
|
||||
stun_servers = stun_servers or {}
|
||||
stun_servers[#stun_servers + 1] = v
|
||||
end
|
||||
realm.stun_servers = stun_servers
|
||||
return realm
|
||||
|
||||
@@ -2167,3 +2167,6 @@ msgstr "并发隧道连接数"
|
||||
|
||||
msgid "Processing, please wait…"
|
||||
msgstr "正在处理,请稍候…"
|
||||
|
||||
msgid "Invalid Realm URL."
|
||||
msgstr "Realm URL 不正确。"
|
||||
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-run
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=5
|
||||
PKG_RELEASE:=6
|
||||
|
||||
LUCI_TITLE:=LuCI support for running installers and packages
|
||||
LUCI_DESCRIPTION:=Upload and execute .run, .sh, .ipk and .apk files from LuCI.
|
||||
@@ -16,6 +16,8 @@ define Package/luci-app-run/postinst
|
||||
#!/bin/sh
|
||||
# 设置版本号到临时文件,用于前端资源缓存刷新
|
||||
echo "$(PKG_VERSION)-$(PKG_RELEASE)" > /tmp/luci-app-run.version
|
||||
# 重启 rpcd 服务以加载新插件
|
||||
/etc/init.d/rpcd restart >/dev/null 2>&1 || true
|
||||
exit 0
|
||||
endef
|
||||
|
||||
|
||||
@@ -23,7 +23,12 @@
|
||||
"running": "安装器正在运行。",
|
||||
"last_file": "上一次安装包:%s",
|
||||
"err_no_opkg": "您的系统不支持 ipk 包安装,请选择 apk 包。",
|
||||
"err_no_apk": "您的系统不支持 apk 包安装,请选择 ipk 包。"
|
||||
"err_no_apk": "您的系统不支持 apk 包安装,请选择 ipk 包。",
|
||||
"script_args": "脚本参数(如 -q -h)",
|
||||
"args_hint": "您可以输入脚本参数或留空",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定",
|
||||
"auto_cleaned": "执行完毕,已自动清理临时文件。"
|
||||
},
|
||||
"en": {
|
||||
"title": "Run Installer",
|
||||
@@ -49,6 +54,11 @@
|
||||
"running": "Installer is running.",
|
||||
"last_file": "Last installer: %s",
|
||||
"err_no_opkg": "Your system does not support ipk package installation. Please use .apk packages instead.",
|
||||
"err_no_apk": "Your system does not support apk package installation. Please use .ipk packages instead."
|
||||
"err_no_apk": "Your system does not support apk package installation. Please use .ipk packages instead.",
|
||||
"script_args": "Script arguments (e.g., -q -h)",
|
||||
"args_hint": "You can enter script arguments or leave it blank",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "OK",
|
||||
"auto_cleaned": "Execution complete, temporary files cleaned automatically."
|
||||
}
|
||||
}
|
||||
@@ -48,12 +48,50 @@ function getLang() {
|
||||
|
||||
function _(key) {
|
||||
if (!I18N)
|
||||
return key;
|
||||
return getDefaultText(key);
|
||||
|
||||
var lang = getLang();
|
||||
var str = I18N[lang]?.[key] || I18N.zh[key] || key;
|
||||
var str = (I18N[lang] && I18N[lang][key]) || (I18N.zh && I18N.zh[key]) || getDefaultText(key) || key;
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
return str.format.apply(str, args);
|
||||
if (args.length > 0) {
|
||||
return str.format.apply(str, args);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function getDefaultText(key) {
|
||||
var defaults = {
|
||||
'title': 'Run安装器',
|
||||
'desc': '在路由器上上传并执行脚本或安装包,注意架构务必匹配。',
|
||||
'drop_tip': '拖入文件,或从电脑选择。',
|
||||
'choose_file': '选择 .run 或 .sh 文件',
|
||||
'choose_ipk': '选择 .ipk 包',
|
||||
'choose_apk': '选择 .apk 包',
|
||||
'execute': '执行',
|
||||
'clean_up': '清理',
|
||||
'upload_title': '上传文件',
|
||||
'log_title': '执行日志',
|
||||
'clean_done': '临时文件与日志已清理。',
|
||||
'only_supported': '仅支持 .run、.sh、.ipk 和 .apk 文件。',
|
||||
'prepare_upload': '准备上传:%s (%s)',
|
||||
'upload_failed': '上传失败。',
|
||||
'uploading': '正在上传 %s:%d%%',
|
||||
'upload_err': '上传请求失败。',
|
||||
'upload_invalid': '上传返回格式无效。',
|
||||
'upload_done': '上传完成:%s (%s)',
|
||||
'starting': '正在启动安装器...',
|
||||
'started': '安装器已启动,PID %d。',
|
||||
'running': '安装器正在运行。',
|
||||
'last_file': '上一次安装包:%s',
|
||||
'err_no_opkg': '您的系统不支持 ipk 包安装,请选择 apk 包。',
|
||||
'err_no_apk': '您的系统不支持 apk 包安装,请选择 ipk 包。',
|
||||
'script_args': '脚本参数(如 -q -h)',
|
||||
'args_hint': '您可以输入脚本参数或留空',
|
||||
'cancel': '取消',
|
||||
'confirm': '确定',
|
||||
'auto_cleaned': '执行完毕,已自动清理临时文件。'
|
||||
};
|
||||
return defaults[key] || null;
|
||||
}
|
||||
|
||||
var uploadStart = rpc.declare({
|
||||
@@ -77,7 +115,7 @@ var uploadFinish = rpc.declare({
|
||||
var runInstaller = rpc.declare({
|
||||
object: 'luci-app-run',
|
||||
method: 'run',
|
||||
params: ['id']
|
||||
params: ['id', 'args']
|
||||
});
|
||||
|
||||
var getStatus = rpc.declare({
|
||||
@@ -134,6 +172,7 @@ return view.extend({
|
||||
|
||||
logOffset: 0,
|
||||
currentUploadId: null,
|
||||
currentFileType: null,
|
||||
appVersion: 'unknown',
|
||||
capabilities: { opkg: 1, apk: 1 },
|
||||
|
||||
@@ -257,7 +296,18 @@ return view.extend({
|
||||
style: 'min-width:140px;margin-left:15px;background:#7B1FA2!important;background-color:#7B1FA2!important;background-image:none!important;color:#fff!important;border-color:#7B1FA2!important;box-shadow:none!important;text-shadow:none!important;opacity:1!important',
|
||||
click: function (ev) {
|
||||
ev.preventDefault();
|
||||
self.startRun(runButton, state);
|
||||
|
||||
if (self.currentFileType === '.sh') {
|
||||
self.showArgsDialog(function (args) {
|
||||
if (args !== null) {
|
||||
log.textContent = '';
|
||||
self.startRun(runButton, state, args.trim());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.textContent = '';
|
||||
self.startRun(runButton, state, '');
|
||||
}
|
||||
}
|
||||
}, [_('execute')]);
|
||||
|
||||
@@ -272,6 +322,8 @@ return view.extend({
|
||||
|
||||
self.currentUploadId = null;
|
||||
self.logOffset = 0;
|
||||
self.prevRunning = false;
|
||||
self.autoCleanType = null;
|
||||
runButton.disabled = true;
|
||||
log.textContent = '';
|
||||
progress.style.display = 'none';
|
||||
@@ -358,6 +410,10 @@ return view.extend({
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
// 记录文件类型
|
||||
var ext = file.name.match(/\.(run|sh|ipk|apk)$/i);
|
||||
self.currentFileType = ext ? ext[0].toLowerCase() : null;
|
||||
|
||||
progress.style.display = '';
|
||||
progress.value = 0;
|
||||
runButton.disabled = true;
|
||||
@@ -414,15 +470,16 @@ return view.extend({
|
||||
});
|
||||
},
|
||||
|
||||
startRun: function (runButton, state) {
|
||||
startRun: function (runButton, state, args) {
|
||||
var self = this;
|
||||
|
||||
if (!this.currentUploadId) return;
|
||||
|
||||
runButton.disabled = true;
|
||||
state.textContent = _('starting');
|
||||
self.autoCleanType = null;
|
||||
|
||||
return runInstaller(this.currentUploadId).then(function (res) {
|
||||
return runInstaller(this.currentUploadId, args || '').then(function (res) {
|
||||
if (res && res.error) {
|
||||
var errorMsg = res.error;
|
||||
if (res.error === 'ERR_NO_OPKG') {
|
||||
@@ -434,6 +491,10 @@ return view.extend({
|
||||
}
|
||||
|
||||
self.logOffset = 0;
|
||||
self.prevRunning = true;
|
||||
if (self.currentFileType === '.sh' || self.currentFileType === '.run') {
|
||||
self.autoCleanType = self.currentFileType;
|
||||
}
|
||||
state.textContent = _('started', res.pid);
|
||||
}).catch(function (err) {
|
||||
runButton.disabled = false;
|
||||
@@ -441,6 +502,47 @@ return view.extend({
|
||||
});
|
||||
},
|
||||
|
||||
showArgsDialog: function (callback) {
|
||||
var dialog = E('div', {
|
||||
style: 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9999'
|
||||
}, [
|
||||
E('div', {
|
||||
style: 'background:#fff;border-radius:8px;padding:20px;width:400px;box-shadow:0 4px 20px rgba(0,0,0,0.3)'
|
||||
}, [
|
||||
E('input', {
|
||||
type: 'text',
|
||||
placeholder: _('args_hint'),
|
||||
style: 'width:100%;padding:10px;margin-bottom:15px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;font-size:14px',
|
||||
id: 'run-args-dialog-input'
|
||||
}),
|
||||
E('div', {
|
||||
style: 'display:flex;justify-content:flex-end;gap:10px'
|
||||
}, [
|
||||
E('button', {
|
||||
class: 'cbi-button cbi-button-reset',
|
||||
style: 'padding:8px 20px;text-transform:none',
|
||||
click: function () {
|
||||
document.body.removeChild(dialog);
|
||||
callback(null);
|
||||
}
|
||||
}, [_('cancel')]),
|
||||
E('button', {
|
||||
class: 'cbi-button cbi-button-action',
|
||||
style: 'padding:8px 20px;text-transform:none',
|
||||
click: function () {
|
||||
var args = document.getElementById('run-args-dialog-input').value;
|
||||
document.body.removeChild(dialog);
|
||||
callback(args);
|
||||
}
|
||||
}, [_('confirm')])
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
document.body.appendChild(dialog);
|
||||
document.getElementById('run-args-dialog-input').focus();
|
||||
},
|
||||
|
||||
refreshLog: function (log, state) {
|
||||
var self = this;
|
||||
|
||||
@@ -454,8 +556,24 @@ return view.extend({
|
||||
|
||||
self.logOffset = res.offset || self.logOffset;
|
||||
|
||||
if (res.running)
|
||||
if (res.running) {
|
||||
state.textContent = _('running');
|
||||
self.prevRunning = true;
|
||||
} else if (self.prevRunning) {
|
||||
// Installer just finished
|
||||
self.prevRunning = false;
|
||||
|
||||
// Auto-cleanup for .sh and .run files
|
||||
if (self.autoCleanType) {
|
||||
self.autoCleanType = null;
|
||||
cleanup().then(function () {
|
||||
self.currentUploadId = null;
|
||||
state.textContent = _('auto_cleaned');
|
||||
}).catch(function () {
|
||||
state.textContent = _('clean_done');
|
||||
});
|
||||
}
|
||||
}
|
||||
}).catch(function () { });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"luci-app-run": {
|
||||
"description": "LuCI app run service",
|
||||
"script": "/usr/libexec/rpcd/luci-app-run"
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,28 @@ read_state() {
|
||||
|
||||
is_running() {
|
||||
local pid="$1"
|
||||
[ -n "$pid" ] && [ -d "/proc/$pid" ]
|
||||
[ -n "$pid" ] || return 1
|
||||
|
||||
[ -d "/proc/$pid" ] || return 1
|
||||
|
||||
local cmdline
|
||||
cmdline="$(cat "/proc/$pid/cmdline" 2>/dev/null | tr '\0' ' ')"
|
||||
|
||||
echo "$cmdline" | grep -q "luci-app-run" && return 0
|
||||
|
||||
# Check for .run files (must be followed by space or end of string)
|
||||
case "$cmdline" in
|
||||
*".run "*) return 0 ;;
|
||||
*".run") return 0 ;;
|
||||
esac
|
||||
|
||||
# Check for .sh files (must be followed by space or end of string)
|
||||
case "$cmdline" in
|
||||
*".sh "*) return 0 ;;
|
||||
*".sh") return 0 ;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
method_list() {
|
||||
@@ -108,7 +129,8 @@ method_list() {
|
||||
"id": "String"
|
||||
},
|
||||
"run": {
|
||||
"id": "String"
|
||||
"id": "String",
|
||||
"args": "String"
|
||||
},
|
||||
"status": {},
|
||||
"read_log": {
|
||||
@@ -217,6 +239,7 @@ upload_finish() {
|
||||
run_installer() {
|
||||
load_input
|
||||
get_string id
|
||||
get_string args
|
||||
|
||||
valid_id "$id" || { json_error "Invalid upload id"; return; }
|
||||
|
||||
@@ -227,6 +250,11 @@ run_installer() {
|
||||
file="$dir/$name"
|
||||
|
||||
read_state
|
||||
if [ -n "$PID" ] && ! is_running "$PID"; then
|
||||
rm -f "$STATE_FILE"
|
||||
unset PID
|
||||
fi
|
||||
|
||||
if is_running "$PID"; then
|
||||
json_error "Another installer is already running"
|
||||
return
|
||||
@@ -279,8 +307,8 @@ run_installer() {
|
||||
(
|
||||
cd "$work" || exit 1
|
||||
printf 'luci-app-run: started %s\n' "$now"
|
||||
printf 'luci-app-run: executing %s\n\n' "$file"
|
||||
"$file"
|
||||
printf 'luci-app-run: executing %s %s\n\n' "$file" "$args"
|
||||
"$file" $args
|
||||
rc=$?
|
||||
printf '\nluci-app-run: exited with status %s at %s\n' "$rc" "$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
exit "$rc"
|
||||
@@ -356,8 +384,19 @@ cleanup() {
|
||||
return
|
||||
fi
|
||||
|
||||
rm -rf "$UPLOAD_DIR" "$RUN_DIR" "$STATE_FILE"
|
||||
mkdir -p "$UPLOAD_DIR" "$RUN_DIR"
|
||||
rm -rf "$UPLOAD_DIR"
|
||||
mkdir -p "$UPLOAD_DIR"
|
||||
|
||||
# Keep state file but clear PID and FILE, preserve LOG path
|
||||
if [ -n "$LOG" ]; then
|
||||
echo "PID=" > "$STATE_FILE"
|
||||
echo "FILE=" >> "$STATE_FILE"
|
||||
echo "LOG=$LOG" >> "$STATE_FILE"
|
||||
echo "STARTED=" >> "$STATE_FILE"
|
||||
else
|
||||
> "$STATE_FILE"
|
||||
fi
|
||||
|
||||
json_ok "Cleaned"
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=fastfetch
|
||||
PKG_VERSION:=2.64.2
|
||||
PKG_RELEASE:=9
|
||||
PKG_VERSION:=2.65.1
|
||||
PKG_RELEASE:=10
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/fastfetch-cli/fastfetch/tar.gz/$(PKG_VERSION)?
|
||||
|
||||
Reference in new issue
Block a user