merge console frontend release
This commit is contained in:
commit
34231e29ed
212
.github/workflows/build-images.yml
vendored
212
.github/workflows/build-images.yml
vendored
@ -1,72 +1,11 @@
|
||||
name: Build Dashboard Images
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
push_images:
|
||||
description: "Push service images instead of local builds"
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
dockerhub_namespace:
|
||||
description: "Docker Hub namespace (user/org)"
|
||||
type: string
|
||||
|
||||
skip_security:
|
||||
description: "Skip security scans and signing"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
node_builder_image:
|
||||
type: string
|
||||
default: "node:22-bookworm"
|
||||
|
||||
node_runtime_image:
|
||||
type: string
|
||||
default: "node:22-slim"
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
push_images:
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
dockerhub_namespace:
|
||||
description: "Docker Hub namespace (user/org)"
|
||||
type: string
|
||||
default: "cloudneutral"
|
||||
|
||||
skip_security:
|
||||
description: "Skip security scans and signing"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
node_builder_image:
|
||||
type: string
|
||||
default: "node:22-bookworm"
|
||||
|
||||
node_runtime_image:
|
||||
type: string
|
||||
default: "node:22-slim"
|
||||
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
name: Build Multi-Arch Images
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
# ✅ 不硬编码:默认推到 ghcr.io/<当前仓库 owner>/...
|
||||
ORG: ${{ github.repository_owner }}
|
||||
|
||||
SKIP_SECURITY: ${{ inputs.skip_security || github.event.inputs.skip_security || 'false' }}
|
||||
|
||||
NODE_BUILDER_IMAGE: ${{ inputs.node_builder_image || github.event.inputs.node_builder_image || 'node:22-bookworm' }}
|
||||
NODE_RUNTIME_IMAGE: ${{ inputs.node_runtime_image || github.event.inputs.node_runtime_image || 'node:22-slim' }}
|
||||
|
||||
PUSH_IMAGES: ${{ github.event_name == 'push'
|
||||
|| (github.event_name == 'workflow_call' && inputs.push_images)
|
||||
|| (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true') }}
|
||||
@ -86,7 +25,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
|
||||
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
|
||||
- uses: ./.github/actions/docker-login
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@ -98,15 +37,15 @@ jobs:
|
||||
with:
|
||||
image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}
|
||||
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
|
||||
- uses: ./.github/actions/docker-setup-qemu
|
||||
- uses: ./.github/actions/docker-setup-buildx
|
||||
|
||||
- name: Clone knowledge content
|
||||
run: git clone https://github.com/Cloud-Neutral-Workshop/knowledge.git knowledge
|
||||
|
||||
- name: Build Service Image (per-arch)
|
||||
id: build
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8
|
||||
uses: ./.github/actions/docker-build-push
|
||||
with:
|
||||
context: ${{ matrix.service.workdir }}
|
||||
file: ${{ matrix.service.dockerfile }}
|
||||
@ -131,144 +70,3 @@ jobs:
|
||||
with:
|
||||
name: digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}
|
||||
path: digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}.txt
|
||||
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: ${{ (github.event_name == 'push' || inputs.push_images == true || github.event.inputs.push_images == 'true') && !((inputs.skip_security == true) || (github.event.inputs.skip_security == 'true')) }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- { platform: linux/amd64, artifact: linux-amd64 }
|
||||
- { platform: linux/arm64, artifact: linux-arm64 }
|
||||
service:
|
||||
- { name: dashboard }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}
|
||||
|
||||
- name: Load image digest
|
||||
env:
|
||||
DIGEST_FILE: digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}.txt
|
||||
run: bash .github/scripts/build-images/load-image-digest.sh
|
||||
|
||||
- name: Set image ref
|
||||
env:
|
||||
SERVICE_NAME: ${{ matrix.service.name }}
|
||||
IMAGE_SHA: ${{ github.sha }}
|
||||
IMAGE_ARTIFACT: ${{ matrix.arch.artifact }}
|
||||
run: bash .github/scripts/build-images/set-image-ref.sh
|
||||
|
||||
- uses: anchore/sbom-action@v0
|
||||
with:
|
||||
image: ${{ env.IMG }}
|
||||
output-file: sbom.spdx.json
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sbom-${{ matrix.service.name }}-${{ matrix.arch.artifact }}
|
||||
path: sbom.spdx.json
|
||||
|
||||
- uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
image-ref: ${{ env.IMG }}
|
||||
severity: HIGH,CRITICAL
|
||||
exit-code: '1'
|
||||
|
||||
- uses: sigstore/cosign-installer@v3
|
||||
with:
|
||||
cosign-release: 'v2.4.1'
|
||||
|
||||
- name: Cosign Sign Image
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: "true"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cosign sign --yes "${{ env.IMG }}"
|
||||
|
||||
push:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
- security
|
||||
if: ${{ needs.build.result == 'success' && (github.event_name == 'push' || inputs.push_images == true || github.event.inputs.push_images == 'true') && ((inputs.skip_security == true) || (github.event.inputs.skip_security == 'true') || (needs.security.result == 'success')) }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
registry:
|
||||
- ghcr.io
|
||||
- docker.io
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: digest-dashboard-linux-amd64
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: digest-dashboard-linux-arm64
|
||||
|
||||
- name: Load digests
|
||||
env:
|
||||
AMD_DIGEST_FILE: digest-dashboard-linux-amd64.txt
|
||||
ARM_DIGEST_FILE: digest-dashboard-linux-arm64.txt
|
||||
run: bash .github/scripts/build-images/load-manifest-digests.sh
|
||||
|
||||
- name: Generate Auto Tags
|
||||
id: meta
|
||||
uses: ./.github/actions/auto-tag
|
||||
with:
|
||||
image: ${{ env.REGISTRY }}/${{ env.ORG }}/dashboard
|
||||
|
||||
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
|
||||
if: matrix.registry == 'ghcr.io'
|
||||
with:
|
||||
registry: ${{ matrix.registry }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create & Push Multi-Arch Manifests (GHCR)
|
||||
if: matrix.registry == 'ghcr.io'
|
||||
env:
|
||||
IMAGE_SHA: ${{ github.sha }}
|
||||
TAGS_CSV: ${{ steps.meta.outputs.tags }}
|
||||
run: bash .github/scripts/build-images/create-ghcr-manifest.sh
|
||||
|
||||
- name: Clone knowledge content
|
||||
if: matrix.registry == 'ghcr.io'
|
||||
run: git clone https://github.com/Cloud-Neutral-Workshop/knowledge.git knowledge
|
||||
|
||||
- name: Validate blog content mount
|
||||
if: matrix.registry == 'ghcr.io'
|
||||
env:
|
||||
IMAGE: ${{ env.REGISTRY }}/${{ env.ORG }}/dashboard@${{ env.MANIFEST_DIGEST }}
|
||||
KNOWLEDGE_CONTENT_DIR: ${{ github.workspace }}/knowledge/content
|
||||
run: bash .github/scripts/build-images/validate-blog-content-mount.sh
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: matrix.registry == 'docker.io'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Copy Multi-Arch Image to Docker Hub (skopeo)
|
||||
if: matrix.registry == 'docker.io'
|
||||
env:
|
||||
TARGET_NS: ${{ inputs.dockerhub_namespace || github.event.inputs.dockerhub_namespace || 'cloudneutral' }}
|
||||
GHCR_USERNAME: ${{ github.actor }}
|
||||
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
run: bash .github/scripts/build-images/copy-image-to-dockerhub.sh
|
||||
|
||||
2
.github/workflows/check-image.yaml
vendored
2
.github/workflows/check-image.yaml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Authenticate to GHCR
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
|
||||
uses: ./.github/actions/docker-login
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@ -1,58 +1,22 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import { Suspense } from "react";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
import { XWorkmateLoading } from "@/app/xworkmate/XWorkmateLoading";
|
||||
import { XWorkmateWorkspacePage } from "@/components/xworkmate/XWorkmateWorkspacePage";
|
||||
import {
|
||||
buildXWorkmateScopeKey,
|
||||
toXWorkmateIntegrationDefaults,
|
||||
} from "@/lib/xworkmate/types";
|
||||
import { XWorkmateWorkspaceRoute } from "@/components/xworkmate/XWorkmateWorkspaceRoute";
|
||||
import { getConsoleIntegrationDefaults } from "@/server/consoleIntegrations";
|
||||
import { getXWorkmateSessionContext } from "@/server/xworkmate/profile";
|
||||
|
||||
export const metadata = {
|
||||
title: "XWorkmate",
|
||||
description: "Online XWorkmate workspace powered by OpenClaw gateway",
|
||||
};
|
||||
|
||||
export default async function XWorkmatePage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams?: Promise<{ prompt?: string; sessionKey?: string }>;
|
||||
}) {
|
||||
const requestHeaders = await headers();
|
||||
const requestHost = requestHeaders.get("host");
|
||||
const { user, profile } = await getXWorkmateSessionContext(requestHost);
|
||||
const defaults =
|
||||
profile ? toXWorkmateIntegrationDefaults(profile) : getConsoleIntegrationDefaults();
|
||||
const scopeKey = buildXWorkmateScopeKey(
|
||||
profile,
|
||||
user?.id ?? user?.uuid ?? null,
|
||||
requestHost,
|
||||
);
|
||||
const resolvedSearchParams = searchParams ? await searchParams : undefined;
|
||||
const initialPrompt =
|
||||
typeof resolvedSearchParams?.prompt === "string"
|
||||
? resolvedSearchParams.prompt
|
||||
: "";
|
||||
const initialSessionKey =
|
||||
typeof resolvedSearchParams?.sessionKey === "string"
|
||||
? resolvedSearchParams.sessionKey
|
||||
: "";
|
||||
|
||||
export default function XWorkmatePage() {
|
||||
const defaults = getConsoleIntegrationDefaults();
|
||||
return (
|
||||
<div className="h-[calc(100vh-var(--app-shell-nav-offset))] w-full">
|
||||
<Suspense fallback={<XWorkmateLoading />}>
|
||||
<XWorkmateWorkspacePage
|
||||
defaults={defaults}
|
||||
profile={profile}
|
||||
initialPrompt={initialPrompt}
|
||||
initialSessionKey={initialSessionKey}
|
||||
requestHost={requestHost ?? undefined}
|
||||
scopeKey={scopeKey}
|
||||
/>
|
||||
<XWorkmateWorkspaceRoute defaults={defaults} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
|
||||
100
src/components/xworkmate/XWorkmateWorkspaceRoute.tsx
Normal file
100
src/components/xworkmate/XWorkmateWorkspaceRoute.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
import type { IntegrationDefaults } from "@/lib/openclaw/types";
|
||||
import { useUserStore } from "@/lib/userStore";
|
||||
import { normalizeXWorkmateHost } from "@/lib/xworkmate/host";
|
||||
import {
|
||||
buildXWorkmateScopeKey,
|
||||
toXWorkmateIntegrationDefaults,
|
||||
type XWorkmateProfileResponse,
|
||||
} from "@/lib/xworkmate/types";
|
||||
import { XWorkmateWorkspacePage } from "@/components/xworkmate/XWorkmateWorkspacePage";
|
||||
|
||||
type XWorkmateWorkspaceRouteProps = {
|
||||
defaults: IntegrationDefaults;
|
||||
};
|
||||
|
||||
async function fetchProfile(): Promise<XWorkmateProfileResponse | null> {
|
||||
const response = await fetch("/api/xworkmate/profile", {
|
||||
credentials: "include",
|
||||
cache: "no-store",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`xworkmate_profile_failed:${response.status}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as XWorkmateProfileResponse;
|
||||
}
|
||||
|
||||
export function XWorkmateWorkspaceRoute({
|
||||
defaults,
|
||||
}: XWorkmateWorkspaceRouteProps): React.ReactNode {
|
||||
const searchParams = useSearchParams();
|
||||
const sessionUser = useUserStore((state) => state.user);
|
||||
const [profile, setProfile] = useState<XWorkmateProfileResponse | null>(null);
|
||||
|
||||
const requestHost = useMemo(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return normalizeXWorkmateHost(window.location.host);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function loadProfile() {
|
||||
try {
|
||||
const nextProfile = await fetchProfile();
|
||||
if (!cancelled) {
|
||||
setProfile(nextProfile);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load xworkmate profile", error);
|
||||
if (!cancelled) {
|
||||
setProfile(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadProfile();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const resolvedDefaults = profile
|
||||
? toXWorkmateIntegrationDefaults(profile)
|
||||
: defaults;
|
||||
const scopeKey = buildXWorkmateScopeKey(
|
||||
profile,
|
||||
sessionUser?.id ?? sessionUser?.uuid ?? null,
|
||||
requestHost,
|
||||
);
|
||||
const initialPrompt = searchParams.get("prompt") ?? "";
|
||||
const initialSessionKey = searchParams.get("sessionKey") ?? "";
|
||||
|
||||
return (
|
||||
<XWorkmateWorkspacePage
|
||||
defaults={resolvedDefaults}
|
||||
profile={profile}
|
||||
initialPrompt={initialPrompt}
|
||||
initialSessionKey={initialSessionKey}
|
||||
requestHost={requestHost}
|
||||
scopeKey={scopeKey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user