Skip to content

Commit bcc198a

Browse files
committed
Add app policy privacy summary
1 parent cc340cd commit bcc198a

2 files changed

Lines changed: 85 additions & 24 deletions

File tree

webui/src/components/pages/AppsPage.vue

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import PageHeader from "@/components/ui/PageHeader.vue";
88
import { useActionLock } from "@/composables/useActionLock";
99
import { useMagicNet } from "@/composables/useMagicNet";
1010
import { copyText } from "@/utils";
11-
import { buildAppPolicySummary, isValidPackageName, recommendedBypass } from "./appPolicyInsights";
11+
import { buildAppPolicySummary, formatAppPolicyFullReport, formatAppPolicySafeReport, isValidPackageName, recommendedBypass } from "./appPolicyInsights";
1212
1313
const { state, runCli, refreshApps, refreshPackages, shellQuote } = useMagicNet();
1414
const { isRunning, withAction } = useActionLock();
1515
const removedBypass = ref<string[]>([]);
1616
const pendingAppAction = ref<PendingAppAction | null>(null);
1717
const appReportCopied = ref(false);
18+
const safeReportCopied = ref(false);
1819
1920
type PendingAppAction = {
2021
key: string;
@@ -182,28 +183,23 @@ async function searchPackages(): Promise<void> {
182183
}
183184
184185
async function copyAppPolicyReport(): Promise<void> {
185-
const report = [
186-
"MagicNet app policy",
187-
"privacy_note=contains package names from app policy lists",
188-
`mode=${state.appPolicy.mode}`,
189-
`proxy_count=${state.appPolicy.proxy.length}`,
190-
`bypass_count=${state.appPolicy.bypass.length}`,
191-
`summary=${policySummary.value.summary}`,
192-
`conflict_count=${policySummary.value.conflicts.length}`,
193-
`current_list_proxy=${policySummary.value.installedProxy.length}`,
194-
`current_list_bypass=${policySummary.value.installedBypass.length}`,
195-
"",
196-
"[insights]",
197-
...policySummary.value.items.map((item) => `${item.label}=${item.value} (${item.tone})`),
198-
"",
199-
"[proxy]",
200-
...state.appPolicy.proxy,
201-
"",
202-
"[bypass]",
203-
...state.appPolicy.bypass
204-
].join("\n").trim();
205-
appReportCopied.value = await copyText(report);
206-
state.output = appReportCopied.value ? "应用策略快照已复制。" : "剪贴板不可用,应用策略快照未复制。";
186+
appReportCopied.value = await copyText(formatAppPolicyFullReport({
187+
mode: state.appPolicy.mode,
188+
proxy: state.appPolicy.proxy,
189+
bypass: state.appPolicy.bypass,
190+
summary: policySummary.value
191+
}));
192+
state.output = appReportCopied.value ? "应用策略完整快照已复制。" : "剪贴板不可用,应用策略快照未复制。";
193+
}
194+
195+
async function copyAppPolicySafeReport(): Promise<void> {
196+
safeReportCopied.value = await copyText(formatAppPolicySafeReport({
197+
mode: state.appPolicy.mode,
198+
proxy: state.appPolicy.proxy,
199+
bypass: state.appPolicy.bypass,
200+
summary: policySummary.value
201+
}));
202+
state.output = safeReportCopied.value ? "应用策略隐私摘要已复制。" : "剪贴板不可用,应用策略摘要未复制。";
207203
}
208204
209205
async function applyRecommendedBypass(): Promise<void> {
@@ -283,7 +279,8 @@ onMounted(() => {
283279
<div class="flex flex-wrap gap-2">
284280
<Button variant="outline" :loading="isRunning('refresh-apps')" @click="withAction('refresh-apps', () => refreshApps())"><RefreshCw :size="17" />读取名单</Button>
285281
<Button variant="outline" :loading="isRunning('search-packages')" @click="searchPackages"><ListFilter :size="17" />重新读取应用</Button>
286-
<Button variant="outline" :loading="isRunning('copy-app-policy-report')" @click="withAction('copy-app-policy-report', copyAppPolicyReport)"><Copy :size="17" />{{ appReportCopied ? '已复制快照' : '复制快照' }}</Button>
282+
<Button variant="outline" :loading="isRunning('copy-app-policy-report')" @click="withAction('copy-app-policy-report', copyAppPolicyReport)"><Copy :size="17" />{{ appReportCopied ? '已复制快照' : '复制完整快照' }}</Button>
283+
<Button variant="outline" :loading="isRunning('copy-app-policy-safe-report')" @click="withAction('copy-app-policy-safe-report', copyAppPolicySafeReport)"><Copy :size="17" />{{ safeReportCopied ? '已复制摘要' : '复制隐私摘要' }}</Button>
287284
<Button :loading="isRunning('apply-recommended-bypass')" :disabled="availableRecommendedBypass.length === 0" @click="requestRecommendedBypass"><ShieldCheck :size="17" />应用推荐名单</Button>
288285
</div>
289286
</PageHeader>

webui/src/components/pages/appPolicyInsights.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ export type AppPolicySummary = {
1414
installedBypass: string[];
1515
};
1616

17+
export type AppPolicySafeReportInput = {
18+
mode: AppPolicyMode;
19+
proxy: string[];
20+
bypass: string[];
21+
summary: AppPolicySummary;
22+
};
23+
1724
export const recommendedBypass = [
1825
"com.eg.android.AlipayGphone",
1926
"com.tencent.mm",
@@ -121,6 +128,63 @@ export function buildAppPolicySummary(
121128
};
122129
}
123130

131+
export function formatAppPolicySafeReport(input: AppPolicySafeReportInput): string {
132+
return [
133+
"MagicNet app policy",
134+
"privacy_note=package names omitted; fingerprints are weak change markers, not privacy proof",
135+
`mode=${input.mode}`,
136+
`proxy_count=${input.proxy.length}`,
137+
`bypass_count=${input.bypass.length}`,
138+
`summary=${input.summary.summary}`,
139+
`conflict_count=${input.summary.conflicts.length}`,
140+
`current_list_proxy=${input.summary.installedProxy.length}`,
141+
`current_list_bypass=${input.summary.installedBypass.length}`,
142+
`proxy_fingerprint=${fingerprintList(input.proxy)}`,
143+
`bypass_fingerprint=${fingerprintList(input.bypass)}`,
144+
`conflict_fingerprint=${fingerprintList(input.summary.conflicts)}`,
145+
"",
146+
"[insights]",
147+
...input.summary.items.map((item) => `${item.label}=${item.value} (${item.tone})`)
148+
].join("\n").trim();
149+
}
150+
151+
export function formatAppPolicyFullReport(input: AppPolicySafeReportInput): string {
152+
return [
153+
"MagicNet app policy",
154+
"privacy_note=contains package names from app policy lists",
155+
`mode=${input.mode}`,
156+
`proxy_count=${input.proxy.length}`,
157+
`bypass_count=${input.bypass.length}`,
158+
`summary=${input.summary.summary}`,
159+
`conflict_count=${input.summary.conflicts.length}`,
160+
`current_list_proxy=${input.summary.installedProxy.length}`,
161+
`current_list_bypass=${input.summary.installedBypass.length}`,
162+
"",
163+
"[insights]",
164+
...input.summary.items.map((item) => `${item.label}=${item.value} (${item.tone})`),
165+
"",
166+
"[proxy]",
167+
...input.proxy,
168+
"",
169+
"[bypass]",
170+
...input.bypass
171+
].join("\n").trim();
172+
}
173+
124174
function insight(label: string, value: string, tone: AppPolicyInsight["tone"]): AppPolicyInsight {
125175
return { label, value, tone };
126176
}
177+
178+
function fingerprintList(values: string[]): string {
179+
if (!values.length) return "none";
180+
return fnv32(values.slice().sort().join("\n")).toString(16).padStart(8, "0");
181+
}
182+
183+
function fnv32(value: string): number {
184+
let hash = 0x811c9dc5;
185+
for (let index = 0; index < value.length; index += 1) {
186+
hash ^= value.charCodeAt(index);
187+
hash = Math.imul(hash, 0x01000193) >>> 0;
188+
}
189+
return hash;
190+
}

0 commit comments

Comments
 (0)