Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions data/locales/boards.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ en:
boards:
title: Camera boards
heading: Your board is probably here
lede: 'Camera and recorder boards with photos, UART pinouts, specifications and, where we have them, factory flash dumps and U-Boot consoles. Match the PCB in your hand, then install OpenIPC on its SoC.'
lede: 'Camera and recorder boards with photos, pinouts, specifications and, where we have them, factory flash dumps and U-Boot consoles. Match the PCB in your hand, then install OpenIPC on its SoC.'
report:
title: "Send us a board"
eyebrow: "Board catalogue"
Expand Down Expand Up @@ -125,11 +125,14 @@ en:
one: known maker
other: known makers
stats_pinouts:
one: with UART pinout
other: with UART pinout
one: with a pinout
other: with a pinout
stats_dumps:
one: factory flash dump
other: factory flash dumps
stats_private_dumps:
one: more kept private, for maintainers
other: more kept private, for maintainers
stats_need_pinout:
one: still needs a pinout
other: still need a pinout
Expand Down
11 changes: 8 additions & 3 deletions data/locales/boards.ru.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ru:
boards:
title: Платы камер
heading: Ваша плата, скорее всего, уже здесь
lede: 'Платы камер и регистраторов: фотографии, распиновка UART, характеристики, а где они есть — заводские дампы флеш-памяти и консоль U-Boot. Найдите плату, которая у вас в руках, и установите OpenIPC на её SoC.'
lede: 'Платы камер и регистраторов: фотографии, распиновки, характеристики, а где они есть — заводские дампы флеш-памяти и консоль U-Boot. Найдите плату, которая у вас в руках, и установите OpenIPC на её SoC.'
report:
title: "Прислать плату"
eyebrow: "Каталог плат"
Expand Down Expand Up @@ -129,13 +129,18 @@ ru:
many: известных производителей
other: известного производителя
stats_pinouts:
one: с распиновкой UART
other: с распиновкой UART
one: с распиновкой
other: с распиновкой
stats_dumps:
one: заводской дамп флеш-памяти
few: заводских дампа флеш-памяти
many: заводских дампов флеш-памяти
other: заводского дампа флеш-памяти
stats_private_dumps:
one: ещё закрыт, у мейнтейнеров
few: ещё закрыты, у мейнтейнеров
many: ещё закрыты, у мейнтейнеров
other: ещё закрыты, у мейнтейнеров
stats_need_pinout:
one: ещё ждёт распиновку
other: ещё ждут распиновку
Expand Down
9 changes: 6 additions & 3 deletions data/locales/boards.zh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ zh:
boards:
title: 摄像头主板
heading: 你的主板很可能就在这里
lede: '摄像机和录像机主板:照片、UART 引脚图、规格参数,以及(如有)出厂闪存转储和 U-Boot 控制台输出。先找到与你手中 PCB 相同的主板,再在它的 SoC 上安装 OpenIPC。'
lede: '摄像机和录像机主板:照片、引脚图、规格参数,以及(如有)出厂闪存转储和 U-Boot 控制台输出。先找到与你手中 PCB 相同的主板,再在它的 SoC 上安装 OpenIPC。'
report:
title: "提交电路板"
eyebrow: "电路板目录"
Expand Down Expand Up @@ -117,11 +117,14 @@ zh:
one: 家已知厂商
other: 家已知厂商
stats_pinouts:
one: 块有 UART 引脚图
other: 块有 UART 引脚图
one: 块有引脚图
other: 块有引脚图
stats_dumps:
one: 份出厂闪存转储
other: 份出厂闪存转储
stats_private_dumps:
one: 份未公开,由维护者保管
other: 份未公开,由维护者保管
stats_need_pinout:
one: 块仍缺引脚图
other: 块仍缺引脚图
Expand Down
3 changes: 2 additions & 1 deletion frontend/apps/site/src/components/boards/Boards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export default function Boards({ locale, socs, board = false }: { locale: Locale
const current = everywhere?.key === searchKey ? everywhere.value : null;
const counts = useMemo(() => (current ? countByScope(filterHits(current.hits, kept)) : null), [current, kept]);

const s = stats(all);
const s = stats(all, data.state === 'ok' ? data.value.totals : undefined);
const filters = [view.maker, view.soc, view.sensor, view.missing, view.line, view.source, view.ready || null, view.kind].filter(Boolean).length;
const filtered = filters > 0 || !!view.q;
const card = { socs, names, sources, t, href, onOpen: openModel, catalogue: all };
Expand Down Expand Up @@ -339,6 +339,7 @@ export default function Boards({ locale, socs, board = false }: { locale: Locale
[s.boards, 'stats_boards'], ...(s.devices > 0 ? [[s.devices, 'stats_devices'] as const] : []),
[s.makers, 'stats_makers'], [s.pinouts, 'stats_pinouts'],
[s.dumps, 'stats_dumps'],
...(s.privateDumps > 0 ? [[s.privateDumps, 'stats_private_dumps'] as const] : []),
] as const).map(([n, key]) => (
<div key={key} class="flex flex-col-reverse text-sm text-body-secondary">
<dt>{t(key, { count: n })}</dt>
Expand Down
8 changes: 6 additions & 2 deletions frontend/apps/site/src/i18n/boards.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -813,8 +813,12 @@
"other": "still need a pinout"
},
"stats_pinouts": {
"one": "with UART pinout",
"other": "with UART pinout"
"one": "with a pinout",
"other": "with a pinout"
},
"stats_private_dumps": {
"one": "more kept private, for maintainers",
"other": "more kept private, for maintainers"
},
"successor_xiongmai": "Xiongmai's product line continues under the JFTech brand: the same module codes and firmware from the same download servers. What Xiongmai listed up to 2023 comes from its archived catalogue at xiongmaitech.com; the current line comes from",
"tag_discontinued": "Discontinued",
Expand Down
10 changes: 8 additions & 2 deletions frontend/apps/site/src/i18n/boards.ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -878,8 +878,14 @@
"other": "ещё ждут распиновку"
},
"stats_pinouts": {
"one": "с распиновкой UART",
"other": "с распиновкой UART"
"one": "с распиновкой",
"other": "с распиновкой"
},
"stats_private_dumps": {
"few": "ещё закрыты, у мейнтейнеров",
"many": "ещё закрыты, у мейнтейнеров",
"one": "ещё закрыт, у мейнтейнеров",
"other": "ещё закрыты, у мейнтейнеров"
},
"successor_xiongmai": "Линейка Xiongmai продолжается под брендом JFTech: те же коды модулей и прошивки с тех же серверов загрузки. Выпущенное Xiongmai до 2023 года взято из архива каталога xiongmaitech.com, текущая линейка — с сайта",
"tag_discontinued": "Снят с производства",
Expand Down
8 changes: 6 additions & 2 deletions frontend/apps/site/src/i18n/boards.zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -739,8 +739,12 @@
"other": "块仍缺引脚图"
},
"stats_pinouts": {
"one": "块有 UART 引脚图",
"other": "块有 UART 引脚图"
"one": "块有引脚图",
"other": "块有引脚图"
},
"stats_private_dumps": {
"one": "份未公开,由维护者保管",
"other": "份未公开,由维护者保管"
},
"successor_xiongmai": "雄迈的产品线以 JFTech(杰峰)品牌延续:相同的模组型号,固件来自相同的下载服务器。雄迈 2023 年以前发布的产品来自 xiongmaitech.com 的归档目录,当前产品线来自",
"tag_discontinued": "已停产",
Expand Down
2 changes: 1 addition & 1 deletion frontend/apps/site/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
},
"boards": {
"heading": "Your board is probably here",
"lede": "Camera and recorder boards with photos, UART pinouts, specifications and, where we have them, factory flash dumps and U-Boot consoles. Match the PCB in your hand, then install OpenIPC on its SoC.",
"lede": "Camera and recorder boards with photos, pinouts, specifications and, where we have them, factory flash dumps and U-Boot consoles. Match the PCB in your hand, then install OpenIPC on its SoC.",
"title": "Camera boards"
},
"brand": {
Expand Down
2 changes: 1 addition & 1 deletion frontend/apps/site/src/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
},
"boards": {
"heading": "Ваша плата, скорее всего, уже здесь",
"lede": "Платы камер и регистраторов: фотографии, распиновка UART, характеристики, а где они есть — заводские дампы флеш-памяти и консоль U-Boot. Найдите плату, которая у вас в руках, и установите OpenIPC на её SoC.",
"lede": "Платы камер и регистраторов: фотографии, распиновки, характеристики, а где они есть — заводские дампы флеш-памяти и консоль U-Boot. Найдите плату, которая у вас в руках, и установите OpenIPC на её SoC.",
"title": "Платы камер"
},
"brand": {
Expand Down
2 changes: 1 addition & 1 deletion frontend/apps/site/src/i18n/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
},
"boards": {
"heading": "你的主板很可能就在这里",
"lede": "摄像机和录像机主板:照片、UART 引脚图、规格参数,以及(如有)出厂闪存转储和 U-Boot 控制台输出。先找到与你手中 PCB 相同的主板,再在它的 SoC 上安装 OpenIPC。",
"lede": "摄像机和录像机主板:照片、引脚图、规格参数,以及(如有)出厂闪存转储和 U-Boot 控制台输出。先找到与你手中 PCB 相同的主板,再在它的 SoC 上安装 OpenIPC。",
"title": "摄像头主板"
},
"brand": {
Expand Down
10 changes: 9 additions & 1 deletion frontend/apps/site/src/lib/boards/boards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,19 @@ describe('the layout', () => {

describe('stats', () => {
test('counts boards, named makers, pinouts and dumps', () => {
expect(stats(ALL)).toEqual({ boards: 4, devices: 0, makers: 1, pinouts: 1, dumps: 3, needPinout: 3 });
expect(stats(ALL)).toEqual({ boards: 4, devices: 0, makers: 1, pinouts: 1, dumps: 3, privateDumps: 0, needPinout: 3 });
const withNvr = [...ALL, ...entries({ ...FILE, manufacturers: [{ id: 'xiongmai', name: 'Xiongmai', aliases: [], website: null,
models: [model('nvr', { kind: 'recorder' })] }] })];
expect(stats(withNvr)).toMatchObject({ boards: 4, devices: 1 });
});

test('counts the dumps owner reports keep private apart from the served ones', () => {
const withPrivate = [...ALL, ...entries({ ...FILE, manufacturers: [{ id: 'xiongmai', name: 'Xiongmai', aliases: [], website: null,
models: [model('kept', { coverage: cov({ flash_dumps: 1, private_dumps: 4 }) })] }] })];
expect(stats(withPrivate)).toMatchObject({ dumps: 4, privateDumps: 4 });
// A report on two boards is a unit on each: the API's totals count its dumps once.
expect(stats(withPrivate, { flash_dumps: 3, private_dumps: 2 })).toMatchObject({ dumps: 3, privateDumps: 2 });
});
});

describe('who sent a unit', () => {
Expand Down
14 changes: 11 additions & 3 deletions frontend/apps/site/src/lib/boards/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function entries(file: BoardsFile): Entry[] {
export const COVERAGE = ['photos', 'pinout', 'flash_dump', 'uboot_env', 'boot_log', 'document'] as const;
export type CoverageKey = (typeof COVERAGE)[number];

const COUNTS: Record<CoverageKey, keyof Model['coverage']> = {
const COUNTS: Record<CoverageKey, Exclude<keyof Model['coverage'], 'private_dumps'>> = {
photos: 'photos',
pinout: 'pinouts',
flash_dump: 'flash_dumps',
Expand Down Expand Up @@ -331,16 +331,24 @@ export interface Stats {
makers: number;
pinouts: number;
dumps: number;
/** Dumps owner reports sent that are not served (kept private). */
privateDumps: number;
needPinout: number;
}

export function stats(all: Entry[]): Stats {
/**
* The catalogue's numbers. The dumps come from the API's totals when it
* sends them: a report published on two boards is a unit on each, and adding
* the boards' coverage would count its dumps twice.
*/
export function stats(all: Entry[], totals?: BoardsFile['totals']): Stats {
return {
boards: all.filter((m) => kindOf(m) === 'board').length,
devices: all.filter((m) => kindOf(m) !== 'board').length,
makers: new Set(all.filter((m) => m.maker.id !== 'unknown').map((m) => m.maker.id)).size,
pinouts: all.filter((m) => has(m, 'pinout')).length,
dumps: all.reduce((n, m) => n + m.coverage.flash_dumps, 0),
dumps: totals?.flash_dumps ?? all.reduce((n, m) => n + m.coverage.flash_dumps, 0),
privateDumps: totals?.private_dumps ?? all.reduce((n, m) => n + (m.coverage.private_dumps ?? 0), 0),
needPinout: all.filter((m) => !has(m, 'pinout')).length,
};
}
Expand Down
4 changes: 4 additions & 0 deletions frontend/apps/site/src/lib/boards/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export interface Coverage {
photos: number;
pinouts: number;
flash_dumps: number;
/** Owner reports' dumps of the board that are not served: one exists, a maintainer has it. */
private_dumps?: number;
uboot_envs: number;
boot_logs: number;
documents: number;
Expand Down Expand Up @@ -112,6 +114,8 @@ export interface BoardsFile {
files_prefix: string;
sources: Source[];
manufacturers: Manufacturer[];
/** What the tree holds, each thing once: a report on two boards is a unit on each. */
totals?: { flash_dumps: number; private_dumps: number };
}

/** One source's say about a board: GET /api/v1/boards/models/{id}. */
Expand Down
12 changes: 10 additions & 2 deletions service/cmd/openipc/club.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ func newClub(bg context.Context, cfg *config.Config, log *slog.Logger, pool *pgx

// refreshReportUnits lists every published owner report's text and photos
// on the boards it was linked to, as contributed units: searchable and
// counted like any board's files. It runs at start and after each review;
// counted like any board's files -- and a dump, listed when its sender
// shared it, counted as private when not. It runs at start and after each review;
// a failure is logged, and the next run catches up.
func refreshReportUnits(ctx context.Context, cfg *config.Config, log *slog.Logger, pool *pgxpool.Pool) {
texts, err := (&reports.Store{DB: pool}).PublishedTexts(ctx)
Expand All @@ -83,8 +84,15 @@ func refreshReportUnits(ctx context.Context, cfg *config.Config, log *slog.Logge
// sure it is that the report is this board's.
c := boards.Contribution{Unit: t.Model + "-" + t.Report, Model: t.Model, By: by, Note: t.Note,
Evidence: []string{cfg.ClubSiteURL + boards.ReceiptMark + t.Report + "&board=" + t.Model},
LinkCertainty: t.Certainty, LinkEvidence: t.Evidence}
LinkCertainty: t.Certainty, LinkEvidence: t.Evidence, PrivateDumps: t.PrivateDumps}
for _, f := range t.Files {
if f.Kind == "backup" {
// A dump shared publicly is listed where its report serves
// it, uncached: made private, it is gone at once, which a
// copy under /board-files/ (cached for a month) would not be.
c.Files = append(c.Files, boards.ContributedFile{Kind: "flash_dump", File: f.BoardName(), URL: f.URL, Bytes: f.Bytes, SHA256: f.SHA256})
continue
}
kind := kinds[f.Kind]
if f.Kind == "photo" {
kind = boards.ReportPhotoKind(f.Name)
Expand Down
Loading
Loading