feat(frontend/A): 嵌入方案A前端构建产物并简化构建流程 - 用户管理增强UI(批量/CSV导入) + 登录审计页面 - 修正 inventory/loginaudit.go List 加 WithUser() 加载 edges.user - 修正 version.json version=4.14.0 匹配 BackendVersion - Dockerfile.ci 删除 Stage1,直接 embed application/statics/assets.zip - .gitignore 放行 assets.zip,忽略前端临时目录 frontend-temp/ - 新增 scripts/gitea_actions.mjs 辅助 Actions status/dispatch
Build and Deploy / build-and-deploy (push) Canceled after 0s
Build and Deploy / build-and-deploy (push) Canceled after 0s
This commit is contained in:
+3
-1
@@ -7,7 +7,6 @@
|
|||||||
*.db
|
*.db
|
||||||
*.bin
|
*.bin
|
||||||
/release/
|
/release/
|
||||||
application/statics/assets.zip
|
|
||||||
master_v4
|
master_v4
|
||||||
slave_v4
|
slave_v4
|
||||||
__debug*
|
__debug*
|
||||||
@@ -39,4 +38,7 @@ tmp/
|
|||||||
cloudreve
|
cloudreve
|
||||||
.DS_Store
|
.DS_Store
|
||||||
assets/
|
assets/
|
||||||
|
frontend-temp/
|
||||||
|
_zipstage/
|
||||||
|
*.mjs.tmp
|
||||||
|
|
||||||
|
|||||||
+2
-32
@@ -1,38 +1,9 @@
|
|||||||
# syntax=docker/dockerfile:1.6
|
# syntax=docker/dockerfile:1.6
|
||||||
|
|
||||||
ARG FRONTEND_MIRROR=https://gitclone.com/github.com/cloudreve/frontend.git
|
|
||||||
ARG GO_PROXY=https://goproxy.cn,direct
|
ARG GO_PROXY=https://goproxy.cn,direct
|
||||||
ARG NPM_REGISTRY=https://registry.npmmirror.com
|
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# Stage 1: Frontend build
|
# Stage 1: Backend build (frontend pre-built locally -> application/statics/assets.zip)
|
||||||
# ========================================
|
|
||||||
FROM node:20-alpine AS frontend-build
|
|
||||||
ARG FRONTEND_MIRROR
|
|
||||||
ARG NPM_REGISTRY
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
RUN apk add --no-cache git zip tzdata
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/tmp/git-cache \
|
|
||||||
git clone --depth 1 ${FRONTEND_MIRROR} assets
|
|
||||||
|
|
||||||
WORKDIR /build/assets
|
|
||||||
RUN npm config set registry ${NPM_REGISTRY} && \
|
|
||||||
yarn config set registry ${NPM_REGISTRY}
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \
|
|
||||||
--mount=type=cache,target=/build/assets/node_modules \
|
|
||||||
yarn install --network-timeout 1000000 --frozen-lockfile || yarn install --network-timeout 1000000
|
|
||||||
|
|
||||||
RUN export NODE_OPTIONS="--max-old-space-size=8192" && yarn run build
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
RUN mkdir -p application/statics && \
|
|
||||||
cd assets/build && zip -r - . > /build/application/statics/assets.zip
|
|
||||||
|
|
||||||
# ========================================
|
|
||||||
# Stage 2: Backend build
|
|
||||||
# ========================================
|
# ========================================
|
||||||
FROM golang:1.23-alpine AS backend-build
|
FROM golang:1.23-alpine AS backend-build
|
||||||
ARG GO_PROXY
|
ARG GO_PROXY
|
||||||
@@ -50,7 +21,6 @@ RUN --mount=type=cache,target=/go/pkg/mod \
|
|||||||
go mod download
|
go mod download
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=frontend-build /build/application/statics/assets.zip ./application/statics/assets.zip
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
--mount=type=cache,target=/root/.cache/go-build \
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
@@ -61,7 +31,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \
|
|||||||
go build -trimpath -ldflags "-s -w -X 'github.com/cloudreve/Cloudreve/v4/pkg/conf.BackendVersion=byw-${BUILD_DATE:-dev}'" -o /cloudreve .
|
go build -trimpath -ldflags "-s -w -X 'github.com/cloudreve/Cloudreve/v4/pkg/conf.BackendVersion=byw-${BUILD_DATE:-dev}'" -o /cloudreve .
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# Stage 3: Runtime image
|
# Stage 2: Runtime image
|
||||||
# ========================================
|
# ========================================
|
||||||
FROM alpine:latest AS runtime
|
FROM alpine:latest AS runtime
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -167,6 +167,7 @@ func (c *loginAuditClient) List(ctx context.Context, args *LoginAuditListArgs) (
|
|||||||
|
|
||||||
items, err := q.
|
items, err := q.
|
||||||
Order(ent.Desc(loginaudit.FieldCreatedAt)).
|
Order(ent.Desc(loginaudit.FieldCreatedAt)).
|
||||||
|
WithUser().
|
||||||
Limit(pageSize).
|
Limit(pageSize).
|
||||||
Offset((page - 1) * pageSize).
|
Offset((page - 1) * pageSize).
|
||||||
All(ctx)
|
All(ctx)
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import https from "node:https";
|
||||||
|
import process from "node:process";
|
||||||
|
|
||||||
|
const token = process.env.GITEA_TOKEN;
|
||||||
|
const owner = "Henry_Du";
|
||||||
|
const repo = "BYWCloudreve";
|
||||||
|
const apiBase = "https://gitea.fse-media.group/api/v1";
|
||||||
|
|
||||||
|
const agent = new https.Agent({ rejectUnauthorized: false });
|
||||||
|
|
||||||
|
function request(method, path, body) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = new URL(apiBase + path);
|
||||||
|
const data = body ? JSON.stringify(body) : null;
|
||||||
|
const req = https.request(
|
||||||
|
{
|
||||||
|
method,
|
||||||
|
hostname: url.hostname,
|
||||||
|
port: url.port || 443,
|
||||||
|
path: url.pathname + url.search,
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${token}`,
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...(data ? { "Content-Length": Buffer.byteLength(data) } : {}),
|
||||||
|
},
|
||||||
|
agent,
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
let chunks = "";
|
||||||
|
res.setEncoding("utf8");
|
||||||
|
res.on("data", (c) => (chunks += c));
|
||||||
|
res.on("end", () => {
|
||||||
|
const ok = res.statusCode >= 200 && res.statusCode < 300;
|
||||||
|
let parsed = chunks;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(chunks);
|
||||||
|
} catch {
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
if (ok) resolve(parsed);
|
||||||
|
else reject(new Error(`HTTP ${res.statusCode}: ${typeof parsed === "string" ? parsed : JSON.stringify(parsed)}`));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
req.on("error", reject);
|
||||||
|
if (data) req.write(data);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
if (!token) {
|
||||||
|
console.error("GITEA_TOKEN is missing");
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
const action = process.argv[2] || "status";
|
||||||
|
if (action === "status") {
|
||||||
|
const runs = await request("GET", `/repos/${owner}/${repo}/actions/runs?limit=5`);
|
||||||
|
console.log(JSON.stringify(runs, null, 2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action === "dispatch") {
|
||||||
|
const workflows = await request("GET", `/repos/${owner}/${repo}/actions/workflows`);
|
||||||
|
const wf = (workflows.workflows || []).find((w) => /build-deploy/.test(w.path) || /build/.test(w.name.toLowerCase()));
|
||||||
|
if (!wf) {
|
||||||
|
console.error("No workflow found, workflows available:", workflows.workflows?.map((w) => w.path));
|
||||||
|
process.exit(3);
|
||||||
|
}
|
||||||
|
console.log("Dispatching workflow:", wf.path, "id:", wf.id);
|
||||||
|
await request("POST", `/repos/${owner}/${repo}/actions/workflows/${wf.id}/dispatches`, {
|
||||||
|
ref: "main",
|
||||||
|
inputs: { logLevel: "info" },
|
||||||
|
});
|
||||||
|
console.log("Dispatched OK");
|
||||||
|
const runs = await request("GET", `/repos/${owner}/${repo}/actions/runs?limit=3`);
|
||||||
|
console.log(JSON.stringify(runs, null, 2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error("Unknown action:", action);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user