feat: show instance deployment time in system info

This commit is contained in:
tianma-if committed 2026-10-02 16:29:32 +08:00
1 parent 88eccaa058
commit 0e546ec834
15 files changed
+66 -4

No files matched your search

+5 -1
View File
@@ -12,6 +12,8 @@ export type Bindings = {
EDGE_EVER_CONTAINER_IMAGE?: string;
EDGE_EVER_DEPLOYMENT_TRIGGER?: string;
EDGE_EVER_DEPLOYMENT_METHOD?: string;
/** Creation time of the active deployment version, supplied by a runtime adapter. */
deploymentVersionCreatedAt?: string;
EDGE_EVER_AUTH_PASSWORD?: string;
EDGE_EVER_AUTH_PASSWORD_HASH?: string;
EDGE_EVER_AUTH_PASSWORD_FALLBACK?: string;
@@ -31,7 +33,9 @@ export type Bindings = {
EDGE_EVER_ALLOW_UNAUTHENTICATED?: string;
};
export type WorkerBindings = Omit<Bindings, "storage" | "publicNetworkFetch"> & CloudflareStorageBindings;
export type WorkerBindings = Omit<Bindings, "storage" | "publicNetworkFetch" | "deploymentVersionCreatedAt"> & CloudflareStorageBindings & {
CF_VERSION_METADATA?: { timestamp?: string };
};
export type AuthContext = {
kind: "user" | "agent";
+3 -1
View File
@@ -46,7 +46,7 @@ import {
} from "./backup-service";
import { sha256, sha256Bytes } from "./hash-utils";
import { INSTANCE_BUILD_ID } from "./instance-build";
import { resolveInstanceDeploymentMetadata } from "./instance-deployment";
import { resolveDeploymentVersionCreatedAt, resolveInstanceDeploymentMetadata } from "./instance-deployment";
import type {
DatabaseAdapter,
PreparedStatementAdapter,
@@ -233,6 +233,7 @@ app.get("/api/health", async (c) => {
authMode,
build: INSTANCE_BUILD_ID.slice(0, 12),
deployment: resolveInstanceDeploymentMetadata(c.env),
deploymentVersionCreatedAt: resolveDeploymentVersionCreatedAt(c.env),
migration: await getAppliedMigration(c.env),
storage: {
database: c.env.storage.diagnostics.database,
@@ -441,6 +442,7 @@ const worker = {
return fetchEdgeEverApp(request, {
...env,
storage: createCloudflareStorageAdapter(env),
deploymentVersionCreatedAt: env.CF_VERSION_METADATA?.timestamp,
// workerd's default Internet egress checks resolved addresses against its public-only network policy.
publicNetworkFetch: (url, init) => fetch(url, init),
}, ctx);
@@ -0,0 +1,9 @@
import { expect, test } from "bun:test";
import { resolveDeploymentVersionCreatedAt } from "./instance-deployment";
test("normalizes a runtime-supplied deployment version timestamp and rejects missing or invalid values", () => {
expect(resolveDeploymentVersionCreatedAt({ deploymentVersionCreatedAt: "2026-10-02T08:00:00+08:00" }))
.toBe("2026-10-02T00:00:00.000Z");
expect(resolveDeploymentVersionCreatedAt({})).toBeNull();
expect(resolveDeploymentVersionCreatedAt({ deploymentVersionCreatedAt: "unknown" })).toBeNull();
});
+8
View File
@@ -21,3 +21,11 @@ export const resolveInstanceDeploymentMetadata = (
trigger: resolveDeploymentTrigger(environment.EDGE_EVER_DEPLOYMENT_TRIGGER?.trim() || bundledTrigger),
method: resolveDeploymentMethod(environment.EDGE_EVER_DEPLOYMENT_METHOD?.trim() || bundledMethod),
});
export const resolveDeploymentVersionCreatedAt = (
environment: Pick<Bindings, "deploymentVersionCreatedAt">,
): string | null => {
const timestamp = environment.deploymentVersionCreatedAt;
if (!timestamp || !Number.isFinite(Date.parse(timestamp))) return null;
return new Date(timestamp).toISOString();
};
@@ -41,6 +41,7 @@ struct InstanceHealth: Codable, Equatable, Sendable {
var containerImageSource: String?
var authMode: String?
var build: String?
var deploymentVersionCreatedAt: String?
var migration: String?
var storage: InstanceStorageDiagnostics?
var objectStorageProvider: String?
@@ -661,6 +661,10 @@ struct SettingsView: View {
value: instanceVersion.map { "v\($0.replacingOccurrences(of: "^v", with: "", options: .regularExpression))" } ?? unknownSystemInfoValue
),
SystemInfoItem(label: env.preferences.t("实例构建", en: "Instance build"), value: instanceHealth?.build ?? unknownSystemInfoValue),
SystemInfoItem(
label: env.preferences.t("实例部署时间", en: "Instance deployment time", ja: "インスタンスのデプロイ時刻"),
value: deploymentVersionTimeLabel(instanceHealth?.deploymentVersionCreatedAt)
),
SystemInfoItem(label: env.preferences.t("数据库版本", en: "Database version"), value: instanceHealth?.migration ?? unknownSystemInfoValue),
SystemInfoItem(label: env.preferences.t("数据库后端", en: "Database backend"), value: databaseBackendLabel(instanceHealth?.storage?.database)),
SystemInfoItem(label: env.preferences.t("新上传对象存储", en: "New upload object storage"), value: objectStorageLabel(instanceHealth)),
@@ -708,6 +712,18 @@ struct SettingsView: View {
env.preferences.t("未知", en: "Unknown")
}
private func deploymentVersionTimeLabel(_ timestamp: String?) -> String {
guard let timestamp else { return unknownSystemInfoValue }
let parser = ISO8601DateFormatter()
parser.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let parsed = parser.date(from: timestamp) ?? ISO8601DateFormatter().date(from: timestamp)
guard let parsed else { return unknownSystemInfoValue }
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter.string(from: parsed)
}
private func databaseBackendLabel(_ backend: String?) -> String {
switch backend {
case "d1": return "D1"
@@ -547,6 +547,7 @@ const getMobileSystemInfoText = (localePreference: MobileLocaleMode) =>
followSystem: "Follow system",
installMode: "Mode",
instanceBuild: "Instance build",
deploymentVersionTime: "Instance deployment time",
instanceConnection: "Instance connection",
instanceUrl: "Instance URL",
instanceVersion: "Instance version",
@@ -596,6 +597,7 @@ const getMobileSystemInfoText = (localePreference: MobileLocaleMode) =>
followSystem: "跟随系统",
installMode: "安装形态",
instanceBuild: "实例构建",
deploymentVersionTime: "实例部署时间",
instanceConnection: "实例连接",
instanceUrl: "实例地址",
instanceVersion: "实例版本",
@@ -758,6 +760,12 @@ const getMobileSystemInfoGroups = (
{ label: copy.databaseBackend, value: getMobileDatabaseBackend(instance?.health.storage?.database, copy.unknown) },
{ label: copy.deploymentPlatform, value: getMobileDeploymentPlatform(instance?.health.runtime, english) },
{ label: copy.instanceBuild, mono: true, value: instance?.health.build || copy.unknown, wide: true },
{
label: copy.deploymentVersionTime,
value: instance?.health.deploymentVersionCreatedAt && Number.isFinite(Date.parse(instance.health.deploymentVersionCreatedAt))
? new Intl.DateTimeFormat(english ? "en-US" : "zh-CN", { dateStyle: "medium", timeStyle: "short" }).format(new Date(instance.health.deploymentVersionCreatedAt))
: copy.unknown,
},
{ fullWidth: true, label: copy.newUploadObjectStorage, value: getMobileObjectStorage(instance?.health, english, copy.unknown) },
...(instance?.health.objectStorageProvider === "s3"
? [{ fullWidth: true, label: copy.existingAttachments, value: copy.existingAttachmentsOriginalStorage }]
@@ -32,7 +32,7 @@ export type SystemInfoItem = {
localOnly?: boolean;
};
type InstanceSystemDiagnostics = Pick<InstanceHealth, "build" | "containerImageSource" | "deployment" | "migration" | "objectStorageProvider" | "storage"> & {
type InstanceSystemDiagnostics = Pick<InstanceHealth, "build" | "containerImageSource" | "deployment" | "deploymentVersionCreatedAt" | "migration" | "objectStorageProvider" | "storage"> & {
runtime?: string | null;
};
@@ -162,6 +162,12 @@ const getWebSystemInfoGroups = (
mono: true,
},
{ label: t("systemInfo.instanceBuild"), value: diagnostics.instance?.build ?? t("systemInfo.unknown"), mono: true },
{
label: t("systemInfo.deploymentVersionTime"),
value: diagnostics.instance?.deploymentVersionCreatedAt && Number.isFinite(Date.parse(diagnostics.instance.deploymentVersionCreatedAt))
? new Intl.DateTimeFormat(language, { dateStyle: "medium", timeStyle: "short" }).format(new Date(diagnostics.instance.deploymentVersionCreatedAt))
: t("systemInfo.unknown"),
},
{ label: t("systemInfo.databaseMigration"), value: diagnostics.instance?.migration ?? t("systemInfo.unknown"), mono: true },
{
label: t("systemInfo.databaseBackend"),
+1
View File
@@ -134,6 +134,7 @@ export type InstanceHealth = {
authMode?: string | null;
build?: string | null;
deployment?: DeploymentMetadata | null;
deploymentVersionCreatedAt?: string | null;
migration?: string | null;
storage?: {
database?: "d1" | "sqlite" | string | null;
+1
View File
@@ -1481,6 +1481,7 @@ export const enUS = {
instanceUrl: "Instance URL",
instanceVersion: "Instance version",
instanceBuild: "Instance build",
deploymentVersionTime: "Instance deployment time",
databaseMigration: "Database version",
databaseBackend: "Database backend",
databaseBackends: {
+1
View File
@@ -1481,6 +1481,7 @@ export const ja = {
instanceUrl: "インスタンス URL",
instanceVersion: "インスタンスバージョン",
instanceBuild: "インスタンスビルド",
deploymentVersionTime: "インスタンスのデプロイ時刻",
databaseMigration: "データベースバージョン",
databaseBackend: "データベースバックエンド",
databaseBackends: {
+1
View File
@@ -1481,6 +1481,7 @@ export const zhCN = {
instanceUrl: "实例地址",
instanceVersion: "实例版本",
instanceBuild: "实例构建",
deploymentVersionTime: "实例部署时间",
databaseMigration: "数据库版本",
databaseBackend: "数据库后端",
databaseBackends: {
+1 -1
View File
@@ -13,7 +13,7 @@ const REPOSITORY_WRANGLER_VALUES = {
bucket_name: "edgeever-resources",
preview_bucket_name: "edgeever-resources-preview",
};
const REPOSITORY_WRANGLER_SHA256 = "d49ed530169a8597d2c17d5c6b99e03e371c8710124bbce4e526cf2b3a720f7a";
const REPOSITORY_WRANGLER_SHA256 = "7817c91538634a2aa2eebaae990ea9d11870d92486f522939f2e02c8eee30c09";
const configValue = (config, key) => {
const match = config.match(new RegExp(`^${key}\\s*=\\s*(?:"([^"]*)"|(true|false))`, "m"));
+1
View File
@@ -227,6 +227,7 @@ describe("Cloudflare deployment entrypoints", () => {
test("rejects instance-specific values in the repository Wrangler config", () => {
const repositoryConfig = readRepositoryFile("wrangler.toml");
expect(repositoryConfig).toContain('[version_metadata]\nbinding = "CF_VERSION_METADATA"');
const instanceConfigs = [
repositoryConfig.replace('name = "edgeever"', 'name = "my-notes"'),
repositoryConfig.replace("workers_dev = true", "workers_dev = false"),
+3
View File
@@ -34,3 +34,6 @@ preview_bucket_name = "edgeever-resources-preview"
[observability]
enabled = true
[version_metadata]
binding = "CF_VERSION_METADATA"