feat: show instance deployment time in system info
This commit is contained in:
1 parent
88eccaa058
commit
0e546ec834
15 files changed
+66
-4
No files matched your search
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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"),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -1481,6 +1481,7 @@ export const ja = {
|
||||
instanceUrl: "インスタンス URL",
|
||||
instanceVersion: "インスタンスバージョン",
|
||||
instanceBuild: "インスタンスビルド",
|
||||
deploymentVersionTime: "インスタンスのデプロイ時刻",
|
||||
databaseMigration: "データベースバージョン",
|
||||
databaseBackend: "データベースバックエンド",
|
||||
databaseBackends: {
|
||||
|
||||
@@ -1481,6 +1481,7 @@ export const zhCN = {
|
||||
instanceUrl: "实例地址",
|
||||
instanceVersion: "实例版本",
|
||||
instanceBuild: "实例构建",
|
||||
deploymentVersionTime: "实例部署时间",
|
||||
databaseMigration: "数据库版本",
|
||||
databaseBackend: "数据库后端",
|
||||
databaseBackends: {
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -34,3 +34,6 @@ preview_bucket_name = "edgeever-resources-preview"
|
||||
|
||||
[observability]
|
||||
enabled = true
|
||||
|
||||
[version_metadata]
|
||||
binding = "CF_VERSION_METADATA"
|
||||
Reference in new issue
Block a user