update 2026-07-28 14:21:36

This commit is contained in:
action
2026-07-28 14:21:36 +08:00
parent ce54ee1bb0
commit 9c289a7240
44 changed files with 19 additions and 474 deletions
+2 -2
View File
@@ -6,9 +6,9 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for linkease
LUCI_DEPENDS:=+luci-lib-linkeasefile
LUCI_DEPENDS:=+linkease
LUCI_PKGARCH:=all
PKG_VERSION:=2.1.70-r4
PKG_VERSION:=2.1.70-r3
# PKG_RELEASE MUST be empty for luci.mk
PKG_RELEASE:=
LUCI_MINIFY_CSS:=0
@@ -0,0 +1 @@
import{ab as L,i as v,ac as M}from"./index.js";var N=/\s/;function $(n){for(var r=n.length;r--&&N.test(n.charAt(r)););return r}var B=/^\s+/;function R(n){return n&&n.slice(0,$(n)+1).replace(B,"")}var k=0/0,F=/^[-+]0x[0-9a-f]+$/i,_=/^0b[01]+$/i,j=/^0o[0-7]+$/i,D=parseInt;function S(n){if(typeof n=="number")return n;if(L(n))return k;if(v(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=v(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=R(n);var t=_.test(n);return t||j.test(n)?D(n.slice(2),t?2:8):F.test(n)?k:+n}var H=function(){return M.Date.now()};const h=H;var P="Expected a function",U=Math.max,X=Math.min;function z(n,r,t){var u,c,l,s,i,f,o=0,b=!1,d=!1,T=!0;if(typeof n!="function")throw new TypeError(P);r=S(r)||0,v(t)&&(b=!!t.leading,d="maxWait"in t,l=d?U(S(t.maxWait)||0,r):l,T="trailing"in t?!!t.trailing:T);function x(e){var a=u,m=c;return u=c=void 0,o=e,s=n.apply(m,a),s}function W(e){return o=e,i=setTimeout(g,r),b?x(e):s}function O(e){var a=e-f,m=e-o,E=r-a;return d?X(E,l-m):E}function p(e){var a=e-f,m=e-o;return f===void 0||a>=r||a<0||d&&m>=l}function g(){var e=h();if(p(e))return y(e);i=setTimeout(g,O(e))}function y(e){return i=void 0,T&&u?x(e):(u=c=void 0,s)}function A(){i!==void 0&&clearTimeout(i),o=0,u=f=c=i=void 0}function C(){return i===void 0?s:y(h())}function I(){var e=h(),a=p(e);if(u=arguments,c=this,f=e,a){if(i===void 0)return W(f);if(d)return clearTimeout(i),i=setTimeout(g,r),x(f)}return i===void 0&&(i=setTimeout(g,r)),s}return I.cancel=A,I.flush=C,I}export{z as d};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{e as t,r as a,F as _,y as s,S as o,q as n,a7 as c}from"./index.js";const r={class:"layout-conatiner"},d={class:"layout-f"},l={class:"layout-r"},i=t({__name:"index",setup(u){return a(!0),(e,p)=>(n(),_("div",r,[s("div",d,[o(e.$slots,"f",{},void 0,!0)]),s("div",l,[o(e.$slots,"r",{},void 0,!0)])]))}});const v=c(i,[["__scopeId","data-v-cbf72d2b"]]);export{v as l};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
var g=(e,t,s)=>new Promise((a,l)=>{var c=i=>{try{n(s.next(i))}catch(d){l(d)}},u=i=>{try{n(s.throw(i))}catch(d){l(d)}},n=i=>i.done?a(i.value):Promise.resolve(i.value).then(c,u);n((s=s.apply(e,t)).next())});import{a8 as h,a9 as y,aa as f}from"./index.js";const o=e=>["linkease-file_web",e].join("/"),r={model:{get(){return localStorage.getItem(o("model"))=="true"},set(e){localStorage.setItem(o("model"),e)}},order:{get(){const e=localStorage.getItem(o("order"));return e||"date"},set(e){localStorage.setItem(o("order"),e)}},orderBy:{get(){return localStorage.getItem(o("orderBy"))||"asc"},set(e){localStorage.setItem(o("orderBy"),e)}},filter:{get(){return localStorage.getItem(o("filter"))||"default"},set(e){localStorage.setItem(o("filter"),e)}},copy:{get(){let e={type:"copy",values:[]};try{const t=localStorage.getItem(o("copy"));t&&(e=JSON.parse(t))}catch(t){}return e},set(e){const t=JSON.stringify(e);localStorage.setItem(o("copy"),t)}}},m=h("app",{state:()=>({keys:0,config:{model:r.model.get(),order:r.order.get(),orderBy:r.orderBy.get(),filter:r.filter.get()},menus:[],entries:[],checknoxEntries:[],previewImage:{enable:!1,values:[]},copy:r.copy.get(),task:0}),getters:{isCopy(){var e,t;return((t=(e=this.copy)==null?void 0:e.values)==null?void 0:t.length)>0}},actions:{reload(){this.keys++},taskIncr(e){e===void 0?this.task=0:this.task+=e},getEntries(){return g(this,null,function*(){try{const e=yield y.File.Basic.List.POST({path:"/local"});if(e.data){const{success:t,result:s,error:a}=e.data;t==0&&(this.menus=s.entries||[]),a&&f.Warning(a)}}catch(e){console.log(e),f.Error(`${e}`)}})},setConfigModel(){this.config.model=!this.config.model,r.model.set(`${this.config.model}`)},setConfigOrder(e){this.config.order=e,r.order.set(e)},setConfigOrderBy(e){this.config.orderBy=e,r.orderBy.set(e)},setConfigFilter(e){this.config.filter=e,r.filter.set(e)},setCopy(e){this.copy.type=e.type,this.copy.values=e.values||[],r.copy.set(e)}}}),S=h("editor",{state:()=>({entries:[],editableTabs:[],editableTabsValue:""}),actions:{addTab(e){for(let t=0;t<this.editableTabs.length;t++)if(this.editableTabs[t].rootPath===e.rootPath){this.editableTabsValue=e.rootPath||"";return}this.editableTabs.push(e),this.editableTabsValue=e.rootPath||""},removeTab(e){const t=this.editableTabs;let s=this.editableTabsValue;s===e&&t.forEach((a,l)=>{if(a.rootPath===e){const c=t[l+1]||t[l-1];c&&(s=c.rootPath||"")}}),this.editableTabsValue=s,this.editableTabs=t.filter(a=>a.rootPath!==e)}}});export{S as a,r as s,m as u};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
var u=(c,d,n)=>new Promise((p,r)=>{var s=a=>{try{i(n.next(a))}catch(e){r(e)}},m=a=>{try{i(n.throw(a))}catch(e){r(e)}},i=a=>a.done?p(a.value):Promise.resolve(a.value).then(s,m);i((n=n.apply(c,d)).next())});import{e as S,r as x,aA as D,az as g,t as B,x as y,A as l,a2 as w,q as _,y as o,P as F,bH as I,F as f,O as C,I as b,v as V,a3 as k,bI as q,aM as A,B as N,a9 as P,a4 as T,C as L,a5 as M,a6 as O,aB as h,a7 as z}from"./index.js";import{u as G}from"./chunk.6424c2fc.js";const v=c=>(M("data-v-61900a83"),c=c(),O(),c),H=["onSubmit"],U={class:"form-item"},W=v(()=>o("div",{class:"form-item_label form-required"}," \u6570\u636E\u76EE\u5F55 ",-1)),$={class:"form-item_content"},j=v(()=>o("option",{value:"",selected:""},"\u9009\u62E9\u6570\u636E\u76EE\u5F55",-1)),J=["value"],K={class:"form-btns"},Q=v(()=>o("div",{class:"auto"},null,-1)),R=S({__name:"index",setup(c){return u(this,null,function*(){let d,n;const p=G(),r=x(!1),s=D({dataPath:""}),m=()=>u(this,null,function*(){try{const a=yield P.LocalDevice.Data.GET();if(a.data){const{success:e,result:t}=a.data;t!=null&&t.dataPath&&(s.dataPath=t.dataPath)}}catch(a){}});[d,n]=g(()=>m()),yield d,n();const i=()=>u(this,null,function*(){if(!(s.dataPath==""||s.dataPath==null)){r.value=!0;try{const a={dataPath:s.dataPath},e=yield P.LocalDevice.Data.POST(a);if(e.data){const{success:t,error:E}=e.data;E&&h.Warning(E),t===0&&h.Success("\u4FDD\u5B58\u6210\u529F")}}catch(a){h.Error(`${a}`)}finally{r.value=!1}}});return(a,e)=>(_(),B(l(w),{id:"page"},{default:y(()=>[o("form",{onSubmit:N(i,["prevent"])},[o("div",U,[W,o("div",$,[F(o("select",{placeholder:"\u9009\u62E9\u6570\u636E\u76EE\u5F55","onUpdate:modelValue":e[0]||(e[0]=t=>s.dataPath=t),required:""},[j,(_(!0),f(b,null,C(l(p).menus,t=>(_(),f(b,null,[t.name!="root"?(_(),f("option",{key:0,value:"/local/"+t.name},T(t.name),9,J)):L("",!0)],64))),256))],512),[[I,s.dataPath,void 0,{trim:!0}]])])]),o("div",K,[Q,V(l(A),{type:"primary","native-type":"submit",icon:l(q),loading:r.value,disabled:r.value},{default:y(()=>[k(" \u4FDD\u5B58 ")]),_:1},8,["icon","loading","disabled"])])],40,H)]),_:1}))})}});const aa=z(R,[["__scopeId","data-v-61900a83"]]);export{aa as default};
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -9,6 +9,7 @@ function index()
entry({"admin", "services", "linkease_status"}, call("linkease_status"))
entry({"admin", "services", "linkease", "file"}, call("linkease_file_template")).leaf = true
end
@@ -26,3 +27,13 @@ function linkease_status()
luci.http.write_json(status)
end
function get_params(name)
local data = {
prefix=luci.dispatcher.build_url(unpack({"admin", "services", "linkease", name})),
}
return data
end
function linkease_file_template()
luci.template.render("linkease/file", get_params("file"))
end
@@ -0,0 +1,191 @@
-- Copyright 2022 xiaobao <xiaobao@linkease.com>
-- Licensed to the public under the MIT License
local http = require "luci.http"
local nixio = require "nixio"
local ltn12 = require "luci.ltn12"
local table = require "table"
local util = require "luci.util"
module("luci.controller.linkease_backend", package.seeall)
local BLOCKSIZE = 2048
local LINKEASE_UNIX = "/var/run/linkease.sock"
function index()
entry({"linkease"}, call("linkease_backend")).leaf=true
end
local function sink_socket(sock, io_err)
if sock then
return function(chunk, err)
if not chunk then
return 1
else
return sock:send(chunk)
end
end
else
return ltn12.sink.error(io_err or "unable to send socket")
end
end
local function session_retrieve(sid, allowed_users)
local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
if type(sdat) == "table" and
type(sdat.values) == "table" and
type(sdat.values.token) == "string" and
(not allowed_users or
util.contains(allowed_users, sdat.values.username))
then
return sid, sdat.values
end
return nil, nil
end
local function get_session()
local sid
local key
local sdat
for _, key in ipairs({"sysauth_https", "sysauth_http", "sysauth"}) do
sid = http.getcookie(key)
if sid then
sid, sdat = session_retrieve(sid, nil)
if sid and sdat then
return sid, sdat
end
end
end
return nil, nil
end
local function chunksource(sock, buffer)
buffer = buffer or ""
return function()
local output
local _, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
while not count and #buffer <= 1024 do
local newblock, code = sock:recv(1024 - #buffer)
if not newblock then
return nil, code
end
buffer = buffer .. newblock
_, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
end
count = tonumber(count, 16)
if not count then
return nil, -1, "invalid encoding"
elseif count == 0 then
return nil
elseif count + 2 <= #buffer - endp then
output = buffer:sub(endp+1, endp+count)
buffer = buffer:sub(endp+count+3)
return output
else
output = buffer:sub(endp+1, endp+count)
buffer = ""
if count - #output > 0 then
local remain, code = sock:recvall(count-#output)
if not remain then
return nil, code
end
output = output .. remain
count, code = sock:recvall(2)
else
count, code = sock:recvall(count+2-#buffer+endp)
end
if not count then
return nil, code
end
return output
end
end
end
function linkease_backend()
local sock = nixio.socket("unix", "stream")
if sock:connect(LINKEASE_UNIX) ~= true then
http.status(500, "connect failed")
return
end
local input = {}
input[#input+1] = http.getenv("REQUEST_METHOD") .. " " .. http.getenv("REQUEST_URI") .. " HTTP/1.1"
local req = http.context.request
local start = "HTTP_"
local start_len = string.len(start)
local ctype = http.getenv("CONTENT_TYPE")
if ctype then
input[#input+1] = "Content-Type: " .. ctype
end
for k, v in pairs(req.message.env) do
if string.sub(k, 1, start_len) == start and not string.find(k, "FORWARDED") then
input[#input+1] = string.sub(k, start_len+1, string.len(k)) .. ": " .. v
end
end
local sid, sdat = get_session()
if sdat ~= nil then
input[#input+1] = "X-Forwarded-Sid: " .. sid
input[#input+1] = "X-Forwarded-Token: " .. sdat.token
end
-- input[#input+1] = "X-Forwarded-For: " .. http.getenv("REMOTE_HOST") ..":".. http.getenv("REMOTE_PORT")
local num = tonumber(http.getenv("CONTENT_LENGTH")) or 0
input[#input+1] = "Content-Length: " .. tostring(num)
input[#input+1] = "\r\n"
local source = ltn12.source.cat(ltn12.source.string(table.concat(input, "\r\n")), http.source())
local ret = ltn12.pump.all(source, sink_socket(sock, "write sock error"))
if ret ~= 1 then
sock:close()
http.status(500, "proxy error")
return
end
local linesrc = sock:linesource()
local line, code, error = linesrc()
if not line then
sock:close()
http.status(500, "response parse failed")
return
end
local protocol, status, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
if not protocol then
sock:close()
http.status(500, "response protocol error")
return
end
num = tonumber(status) or 0
http.status(num, msg)
local allow_ranges = http.getenv("SERVER_SOFTWARE") ~= "uhttpd"
local chunked = 0
line = linesrc()
while line and line ~= "" do
local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
if key and key ~= "Status" then
if key == "Transfer-Encoding" and val == "chunked" then
chunked = 1
end
if key ~= "Connection" and key ~= "Transfer-Encoding" and ( allow_ranges or (key ~= "Content-Length" and key ~= "Accept-Ranges") ) then
http.header(key, val)
end
end
line = linesrc()
end
if not line then
sock:close()
http.status(500, "parse header failed")
return
end
local body_buffer = linesrc(true)
if chunked == 1 then
ltn12.pump.all(chunksource(sock, body_buffer), http.write)
else
local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), sock:blocksource())
ltn12.pump.all(body_source, http.write)
end
sock:close()
end
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0" /> -->
<meta name="viewport" content="width=1380" />
<title>易有云文件管理</title>
<script>
(function () {
var pathe_prefix = "<%=prefix%>"
window.path_base = pathe_prefix
window.ver = "<%# PKG_VERSION %>"
})();
</script>
<script type="module" crossorigin src="/luci-static/linkeasefile/index.js<%# ?v=PKG_VERSION %>"></script>
<link rel="stylesheet" href="/luci-static/linkeasefile/style.css<%# ?v=PKG_VERSION %>">
</head>
<body>
<div id="app"></div>
</body>
</html>