Skip to content

feat(integrations): add ArchiveTeam Warrior widget#5784

Open
darbyjack wants to merge 3 commits into
homarr-labs:devfrom
darbyjack:feat/archive-team-warrior-widget
Open

feat(integrations): add ArchiveTeam Warrior widget#5784
darbyjack wants to merge 3 commits into
homarr-labs:devfrom
darbyjack:feat/archive-team-warrior-widget

Conversation

@darbyjack
Copy link
Copy Markdown


Homarr

Thank you for your contribution. Please ensure that your pull request meets the following pull request:

  • Builds without warnings or errors (pnpm build, autofix with pnpm format:fix)
  • Pull request targets dev branch
  • Commits follow the conventional commits guideline
  • No shorthand variable names are used (eg. x, y, i or any abbrevation)
  • Documentation is up to date. Create a pull request here.

Summary

Adds a read-only ArchiveTeam Warrior integration and widget.

The widget connects to the Warrior web interface and reads the Seesaw/SockJS status stream to display:

  • current Warrior status
  • selected/current project
  • running, completed, failed, and canceled item counts
  • current download/upload bandwidth
  • broadcast message when available

This first iteration intentionally does not expose control actions such as stopping the Warrior, changing projects, or modifying settings.

Testing

  • Tested against a live ArchiveTeam Warrior instance
  • Confirmed live status and bandwidth updates
  • pnpm lint
  • pnpm format:fix
  • pnpm typecheck
  • pnpm build

@darbyjack darbyjack requested a review from a team as a code owner May 24, 2026 14:54
@dokploy-homarr-labs
Copy link
Copy Markdown

🚨 Preview Deployment Blocked - Security Protection

Your pull request was blocked from triggering preview deployments

Why was this blocked?

  • User: darbyjack
  • Repository: homarr
  • Permission Level: read
  • Required Level: write, maintain, or admin

How to resolve this:

Option 1: Get Collaborator Access (Recommended)
Ask a repository maintainer to invite you as a collaborator with write permissions or higher.

Option 2: Request Permission Override
Ask a repository administrator to disable security validation for this specific application if appropriate.

For Repository Administrators:

To disable this security check (⚠️ not recommended for public repositories):
Enter to preview settings and disable the security check.


This security measure protects against malicious code execution in preview deployments. Only trusted collaborators should have the ability to trigger deployments.

🛡️ Learn more about this security feature

This protection prevents unauthorized users from:

  • Executing malicious code on the deployment server
  • Accessing environment variables and secrets
  • Potentially compromising the infrastructure

Preview deployments are powerful but require trust. Only users with repository write access can trigger them.

@darbyjack darbyjack force-pushed the feat/archive-team-warrior-widget branch from 56226e1 to dde277e Compare May 24, 2026 14:55
Comment thread packages/definitions/src/integration.ts Outdated
archiveTeamWarrior: {
name: "ArchiveTeam Warrior",
secretKinds: [[], ["username", "password"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/archivebox.svg",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really yours? If you have a more specific logo, you can add it via https://dashboardicons.com

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not maintain the project, I'm just a user of it. I did find their logo on that site and I will swap it over.

Comment on lines +11 to +69
const seesawFrameSchema = z.object({
event_name: z.string(),
message: z.unknown(),
});

const warriorStatusMessageSchema = z.object({
status: z.string(),
});

const warriorBroadcastMessageSchema = z.object({
message: z.string().nullable(),
});

const warriorProjectSelectedMessageSchema = z.object({
project: z.string().nullable(),
});

const warriorProjectSchema = z.object({
project_id: z.number().optional(),
title: z.string().optional(),
project_html: z.string().optional(),
utc_deadline: z.string().nullable().optional(),
});

const warriorItemSchema = z.object({
id: z.string(),
name: z.string(),
status: z.string().nullable().optional(),
project: z.string().nullable().optional(),
start_time: z.number().optional(),
});

const projectRefreshMessageSchema = z
.object({
project: warriorProjectSchema.optional(),
status: z.string().optional(),
items: z.array(warriorItemSchema).optional(),
})
.nullable();

const itemStatusMessageSchema = z.object({
item_id: z.string(),
});

const pipelineStartItemMessageSchema = z.object({
item: warriorItemSchema,
});

const bandwidthMessageSchema = z.object({
received: z.number().optional(),
sent: z.number().optional(),
receiving: z.number().optional(),
sending: z.number().optional(),
session_id: z.string().optional(),
});

type WarriorProject = z.infer<typeof warriorProjectSchema>;
type WarriorItem = z.infer<typeof warriorItemSchema>;
type SeesawFrame = z.infer<typeof seesawFrameSchema>;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put this in a separate file?

const frames = this.parseSockJsMessage(event.data);

for (const frame of frames) {
switch (frame.event_name) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be put in a separate function to reduce cognitive complexity

const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getStatusAsync();
},
cacheDuration: dayjs.duration(10, "seconds"),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 seconds is very short, does it really need to be that short?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely not. I'll update it to every minute.

<Stack gap={0}>
<Text fw={700} lineClamp={1}>
ArchiveTeam Warrior
</Text>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We mostly remove the names of widgets now from them.
Can you remove this one too?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure! First time here, thanks for letting me know.

Comment on lines +108 to +116
const formatBytesPerSecond = (value?: number) => {
if (!value) return "0 KB/s";

if (value >= 1024 * 1024) {
return `${(value / 1024 / 1024).toFixed(1)} MB/s`;
}

return `${Math.round(value / 1024)} KB/s`;
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a method for this. Please remove and use the existing one.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do, thank you.

@manuel-rw
Copy link
Copy Markdown
Member

I think we can still improve the spacing and padding of the widget.
Would you like me to give some suggestions or do you want to try yourself?

@darbyjack
Copy link
Copy Markdown
Author

I think we can still improve the spacing and padding of the widget. Would you like me to give some suggestions or do you want to try yourself?

I'm more than happy to give it a shot. I'd definitely appreciate your input as well :)

@darbyjack
Copy link
Copy Markdown
Author

Thanks for the review. I pushed updates addressing the requested changes:

  • Removed the duplicated widget title from the widget body
  • Updated the widget layout/spacing and changed the metric grid to be less cramped
  • Replaced the local bandwidth formatter with Homarr's existing helper
  • Moved the ArchiveTeam Warrior websocket schemas into a dedicated schema file
  • Split websocket frame handling into smaller methods to reduce cognitive complexity
  • Increased the request-handler cache duration from 10 seconds to 1 minute
  • Updated to the ArchiveTeam Warrior icon from https://dashboardicons.com/

@darbyjack darbyjack requested a review from manuel-rw May 24, 2026 19:20
@manuel-rw
Copy link
Copy Markdown
Member

Did you resolve the comments from my review? If yes, please always mark them as "resolved".
I'll review again.... 🕐

const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getStatusAsync();
},
cacheDuration: dayjs.duration(1, "minute"),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 1 minute really needed here? 5 minutes should be enough too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants