Merge pull request #818 from Cucumberrbob/feat/angular-control-flow

migrate to new Angular Control Flow syntax
This commit is contained in:
Roger Far 2025-05-18 11:13:50 -06:00 committed by GitHub
commit 772816ced0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 899 additions and 760 deletions

View file

@ -84,11 +84,15 @@
</div>
</div>
</div>
<p class="help" *ngIf="provider === 'AllDebrid'">
When downloading with AllDebrid it cannot be guaranteed that only files above this limit will be download as
some files are grouped together in 1 large archive.
</p>
<p class="help" *ngIf="downloadAction === 2">This setting does not apply to manually selected files.</p>
@if (provider === "AllDebrid") {
<p class="help">
When downloading with AllDebrid it cannot be guaranteed that only files above this limit will be download as
some files are grouped together in 1 large archive.
</p>
}
@if (downloadAction === 2) {
<p class="help">This setting does not apply to manually selected files.</p>
}
</div>
<div class="field">
@ -104,8 +108,12 @@
Select only the files that are matching this regular expression. Only use this setting OR the Exclude files
setting, not both.
</p>
<p class="help" *ngIf="downloadAction === 2">This setting does not apply to manually selected files.</p>
<p class="help is-danger" *ngIf="includeRegexError">{{ includeRegexError }}</p>
@if (downloadAction === 2) {
<p class="help">This setting does not apply to manually selected files.</p>
}
@if (includeRegexError) {
<p class="help is-danger">{{ includeRegexError }}</p>
}
</div>
<div class="field">
@ -121,8 +129,12 @@
Ignore files that are matching this regular expression. Only use this setting OR the Include files setting, not
both.
</p>
<p class="help" *ngIf="downloadAction === 2">This setting does not apply to manually selected files.</p>
<p class="help is-danger" *ngIf="excludeRegexError">{{ excludeRegexError }}</p>
@if (downloadAction === 2) {
<p class="help">This setting does not apply to manually selected files.</p>
}
@if (excludeRegexError) {
<p class="help is-danger">{{ excludeRegexError }}</p>
}
</div>
<div class="field">
@ -130,8 +142,10 @@
<div class="control select is-fullwidth">
<select [(ngModel)]="finishedAction">
<option [ngValue]="0">Do nothing</option>
<option [ngValue]="1" *ngIf="downloadClient !== 2">Remove torrent from provider and client</option>
<option [ngValue]="2" *ngIf="downloadClient !== 2">Remove torrent from provider</option>
@if (downloadClient !== 2) {
<option [ngValue]="1">Remove torrent from provider and client</option>
<option [ngValue]="2">Remove torrent from provider</option>
}
<option [ngValue]="3">Remove torrent from client</option>
</select>
</div>
@ -196,7 +210,9 @@
<div class="field">
<div class="control">
<div class="notification is-danger is-light" *ngIf="error">{{ error }}</div>
@if (error) {
<div class="notification is-danger is-light">{{ error }}</div>
}
</div>
</div>

View file

@ -41,9 +41,11 @@
Login
</button>
</div>
<div class="notification is-danger is-light" *ngIf="error?.length > 0">
{{ error }}
</div>
@if (error?.length > 0) {
<div class="notification is-danger is-light">
{{ error }}
</div>
}
</form>
</div>
</div>

View file

@ -38,13 +38,18 @@
</span>
<span>Settings</span>
</a>
<a class="navbar-item" *ngIf="profile" href="{{ providerLink }}" target="_blank" rel="noopener">
<span class="icon">
<i class="fas fa-euro-sign" aria-hidden="true"></i>
</span>
<span *ngIf="profile.expiration">Premium Status: {{ profile.expiration | date }}</span>
<span *ngIf="!profile.expiration" class="no-premium">Not premium</span>
</a>
@if (profile) {
<a class="navbar-item" href="{{ providerLink }}" target="_blank" rel="noopener">
<span class="icon">
<i class="fas fa-euro-sign" aria-hidden="true"></i>
</span>
@if (profile.expiration) {
<span>Premium Status: {{ profile.expiration | date }}</span>
} @else {
<span class="no-premium">Not premium</span>
}
</a>
}
</div>
<div class="navbar-end">
@ -61,19 +66,20 @@
</div>
</div>
</nav>
<div
class="notification"
[ngClass]="profile.isInsecure ? 'is-danger' : 'is-warning'"
*ngIf="
profile &&
(!profile.disableUpdateNotification || profile.isInsecure) &&
profile.latestVersion &&
profile.currentVersion !== profile.latestVersion
"
>
<span *ngIf="profile.isInsecure"> Your current version is insecure. </span>
<span>
Version {{ profile.latestVersion }} of RealDebrid Client was found. You are currently on version
{{ profile.currentVersion }}.
</span>
</div>
@if (
profile &&
(!profile.disableUpdateNotification || profile.isInsecure) &&
profile.latestVersion &&
profile.currentVersion !== profile.latestVersion
) {
<div class="notification" [ngClass]="profile.isInsecure ? 'is-danger' : 'is-warning'">
@if (profile.isInsecure) {
<span> Your current version is insecure. </span>
}
<span>
Version {{ profile.latestVersion }} of RealDebrid Client was found. You are currently on version
{{ profile.currentVersion }}.
</span>
</div>
}

View file

@ -19,13 +19,17 @@
<div class="field">
<div class="control">
<div class="notification is-danger is-light" *ngIf="error !== null">Error saving: {{ error }}</div>
@if (error !== null) {
<div class="notification is-danger is-light">Error saving: {{ error }}</div>
}
</div>
</div>
<div class="field">
<div class="control">
<div class="notification is-success is-light" *ngIf="success">Your profile has been updated</div>
@if (success) {
<div class="notification is-success is-light">Your profile has been updated</div>
}
</div>
</div>

View file

@ -1,186 +1,219 @@
<div class="tabs">
<ul>
<li [ngClass]="{ 'is-active': activeTab === i }" (click)="activeTab = i" *ngFor="let tab of tabs; let i = index">
<a>{{ tab.displayName }}</a>
</li>
@for (tab of tabs; track tab.key; let i = $index) {
<li [ngClass]="{ 'is-active': activeTab === i }" (click)="activeTab = i">
<a>{{ tab.displayName }}</a>
</li>
}
<li [ngClass]="{ 'is-active': activeTab === 99 }" (click)="activeTab = 99">
<a>Speed Tests</a>
</li>
</ul>
</div>
<div *ngFor="let tab of tabs; let i = index" [hidden]="activeTab !== i">
<h3>{{ tab.description }}</h3>
<ng-container *ngFor="let setting of tab.settings">
<h3 *ngIf="setting.type === 'Object'" class="title is-3" style="margin-top: 1.2rem">{{ setting.displayName }}</h3>
@for (tab of tabs; track tab.key; let i = $index) {
<div [hidden]="activeTab !== i">
<h3>{{ tab.description }}</h3>
@for (setting of tab.settings; track setting.key) {
@if (setting.type === "Object") {
<h3 class="title is-3" style="margin-top: 1.2rem">{{ setting.displayName }}</h3>
}
<div class="field">
@if (setting.type !== "Boolean" && setting.type !== "Object") {
<label class="label">{{ setting.displayName }}</label>
}
@switch (setting.type) {
@case ("String") {
<div class="control">
<input class="input" type="text" [(ngModel)]="setting.value" />
</div>
}
@case ("Int32") {
<div class="control">
<input class="input" type="number" [(ngModel)]="setting.value" />
</div>
}
@case ("Boolean") {
<label class="checkbox">
<input type="checkbox" [(ngModel)]="setting.value" />
{{ setting.displayName }}
</label>
}
@case ("Enum") {
<div class="control select is-fullwidth">
<select [(ngModel)]="setting.value">
@for (kvp of setting.enumValues | keyvalue; track kvp) {
<option [value]="kvp.key">{{ kvp.value }}</option>
}
</select>
</div>
}
@case ("Object") {}
@default {
<div class="control">Invalid setting type {{ setting.type }}</div>
}
}
<p class="help" [innerHtml]="setting.description | nl2br"></p>
@if (setting.key === "DownloadClient:Aria2cSecret") {
<button
class="button is-warning"
(click)="testAria2cConnection()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
>
Test aria2 connection
</button>
@if (testAria2cConnectionError) {
<div class="notification is-danger is-light" style="margin-top: 1rem">
Could connect to Aria2 client<br />
{{ testAria2cConnectionError }}
</div>
}
@if (testAria2cConnectionSuccess) {
<div class="notification is-success is-light" style="margin-top: 1rem">
Found Aria2 client version {{ testAria2cConnectionSuccess }}
</div>
}
}
</div>
}
</div>
}
@if (activeTab === 99) {
<div>
<div class="field">
<label class="label" *ngIf="setting.type !== 'Boolean' && setting.type !== 'Object'">{{
setting.displayName
}}</label>
<ng-container [ngSwitch]="setting.type">
<div class="control" *ngSwitchCase="'String'">
<input class="input" type="text" [(ngModel)]="setting.value" />
</div>
<div class="control" *ngSwitchCase="'Int32'">
<input class="input" type="number" [(ngModel)]="setting.value" />
</div>
<label class="checkbox" *ngSwitchCase="'Boolean'">
<input type="checkbox" [(ngModel)]="setting.value" />
{{ setting.displayName }}
</label>
<div class="control select is-fullwidth" *ngSwitchCase="'Enum'">
<select [(ngModel)]="setting.value">
<option [value]="kvp.key" *ngFor="let kvp of setting.enumValues | keyvalue">{{ kvp.value }}</option>
</select>
</div>
<ng-container *ngSwitchCase="'Object'"></ng-container>
<div class="control" *ngSwitchDefault>Invalid setting type {{ setting.type }}</div>
</ng-container>
<p class="help" [innerHtml]="setting.description | nl2br"></p>
<ng-container *ngIf="setting.key === 'DownloadClient:Aria2cSecret'">
<button
class="button is-warning"
(click)="testAria2cConnection()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
>
Test aria2 connection
</button>
<div class="notification is-danger is-light" style="margin-top: 1rem" *ngIf="testAria2cConnectionError">
Could connect to Aria2 client<br />
{{ testAria2cConnectionError }}
</div>
<div class="notification is-success is-light" style="margin-top: 1rem" *ngIf="testAria2cConnectionSuccess">
Found Aria2 client version {{ testAria2cConnectionSuccess }}
</div>
</ng-container>
</div>
</ng-container>
</div>
<div *ngIf="activeTab === 99">
<div class="field">
<label class="label">Test download path permissions</label>
<div class="control">
<button
class="button is-warning"
(click)="testDownloadPath()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
*ngIf="!testPathError && !testPathSuccess"
>
Test permissions
</button>
<div class="notification is-danger is-light" *ngIf="testPathError">
Could not test your download path<br />
{{ testPathError }}
<label class="label">Test download path permissions</label>
<div class="control">
@if (!testPathError && !testPathSuccess) {
<button
class="button is-warning"
(click)="testDownloadPath()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
>
Test permissions
</button>
}
@if (testPathError) {
<div class="notification is-danger is-light">
Could not test your download path<br />
{{ testPathError }}
</div>
}
@if (testPathSuccess) {
<div class="notification is-success is-light">Your download path looks good!</div>
}
</div>
<div class="notification is-success is-light" *ngIf="testPathSuccess">Your download path looks good!</div>
<div class="help">This will check if the download folder has write permissions.</div>
</div>
<div class="help">This will check if the download folder has write permissions.</div>
</div>
<div class="field">
<label class="label">Test Real-Debrid download speed</label>
<div class="control">
<button
class="button is-warning"
(click)="testDownloadSpeed()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
*ngIf="!testDownloadSpeedError && !testDownloadSpeedSuccess"
>
Test download speed
</button>
<div class="notification is-danger is-light" *ngIf="testDownloadSpeedError">
Could not test your download speed<br />
{{ testDownloadSpeedError }}
<div class="field">
<label class="label">Test Real-Debrid download speed</label>
<div class="control">
@if (testDownloadSpeedError) {
<div class="notification is-danger is-light">
Could not test your download speed<br />
{{ testDownloadSpeedError }}
</div>
} @else if (testDownloadSpeedSuccess) {
<div class="notification is-success is-light">Download speed {{ testDownloadSpeedSuccess | filesize }}/s</div>
} @else {
<button
class="button is-warning"
(click)="testDownloadSpeed()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
>
Test download speed
</button>
}
</div>
<div class="notification is-success is-light" *ngIf="testDownloadSpeedSuccess">
Download speed {{ testDownloadSpeedSuccess | filesize }}/s
<div class="help">
This will attempt to download a 10GB file from Real-Debrid. When 50MB has been downloaded the test will stop.
</div>
</div>
<div class="help">
This will attempt to download a 10GB file from Real-Debrid. When 50MB has been downloaded the test will stop.
<div class="field">
<label class="label">Test download folder write speed</label>
<div class="control">
@if (testWriteSpeedError) {
<div class="notification is-danger is-light">
Could not test your download speed<br />
{{ testWriteSpeedError }}
</div>
} @else if (testWriteSpeedSuccess) {
<div class="notification is-success is-light">Write speed {{ testWriteSpeedSuccess | filesize }}/s</div>
} @else {
<button
class="button is-warning"
(click)="testWriteSpeed()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
>
Test write speed
</button>
}
</div>
<div class="help">This will write a small file to your download folder to see how fast it can write to it.</div>
</div>
</div>
<div class="field">
<label class="label">Test download folder write speed</label>
<div class="control">
<button
class="button is-warning"
(click)="testWriteSpeed()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
*ngIf="!testWriteSpeedError && !testWriteSpeedSuccess"
>
Test write speed
</button>
<div class="notification is-danger is-light" *ngIf="testWriteSpeedError">
Could not test your download speed<br />
{{ testWriteSpeedError }}
</div>
<div class="notification is-success is-light" *ngIf="testWriteSpeedSuccess">
Write speed {{ testWriteSpeedSuccess | filesize }}/s
</div>
</div>
<div class="help">This will write a small file to your download folder to see how fast it can write to it.</div>
</div>
</div>
}
<div class="field">
<div class="control">
<div class="notification is-danger is-light" *ngIf="error?.length > 0">Error saving settings: {{ error }}</div>
</div>
</div>
<div class="field" *ngIf="activeTab === 0">
<label class="label">Register client as magnet link handler</label>
<div class="control">
<button
class="button is-info"
type="button"
(click)="registerMagnetHandler()"
[disabled]="!canRegisterMagnetHandler"
>
<span>
{{ canRegisterMagnetHandler ? "Register Handler" : "Unavailable in This Browser" }}
</span>
</button>
</div>
<p class="help">
@if (canRegisterMagnetHandler) {
This will attempt to register the client as your browser's default handler for magnet links and automatically open
them in the new torrent screen for downloading.
} @else {
Magnet link registration is unavailable. Your
<a href="https://caniuse.com/mdn-api_navigator_registerprotocolhandler" target="_blank" rel="noopener noreferrer">
browser may not support this feature
</a>
, or the client is not being served in a
<a
href="https://developer.mozilla.org/en-US/docs/Glossary/Secure_Context/"
target="_blank"
rel="noopener noreferrer"
>
secure context
</a>
.
@if (error?.length > 0) {
<div class="notification is-danger is-light">Error saving settings: {{ error }}</div>
}
</p>
</div>
<div class="field" *ngIf="activeTab < 99">
<div class="control">
<button class="button is-success" (click)="ok()" [disabled]="saving" [ngClass]="{ 'is-loading': saving }">
Save Settings
</button>
</div>
</div>
@if (activeTab === 0) {
<div class="field">
<label class="label">Register client as magnet link handler</label>
<div class="control">
<button
class="button is-info"
type="button"
(click)="registerMagnetHandler()"
[disabled]="!canRegisterMagnetHandler"
>
<span>
{{ canRegisterMagnetHandler ? "Register Handler" : "Unavailable in This Browser" }}
</span>
</button>
</div>
<p class="help">
@if (canRegisterMagnetHandler) {
This will attempt to register the client as your browser's default handler for magnet links and automatically
open them in the new torrent screen for downloading.
} @else {
Magnet link registration is unavailable. Your
<a
href="https://caniuse.com/mdn-api_navigator_registerprotocolhandler"
target="_blank"
rel="noopener noreferrer"
>
browser may not support this feature
</a>
, or the client is not being served in a
<a
href="https://developer.mozilla.org/en-US/docs/Glossary/Secure_Context/"
target="_blank"
rel="noopener noreferrer"
>
secure context
</a>
.
}
</p>
</div>
}
@if (activeTab < 99) {
<div class="field">
<div class="control">
<button class="button is-success" (click)="ok()" [disabled]="saving" [ngClass]="{ 'is-loading': saving }">
Save Settings
</button>
</div>
</div>
}

View file

@ -4,145 +4,165 @@
<div class="columns is-centered">
<div class="column is-5-tablet is-5-desktop is-5-widescreen">
<img src="assets/logo.png" />
<div class="box" *ngIf="step === 1">
<div class="notification is-primary">
Welcome to Real-Debrid Client. Please create your account by entering a username and password.
@if (step === 1) {
<div class="box">
<div class="notification is-primary">
Welcome to Real-Debrid Client. Please create your account by entering a username and password.
</div>
<form (ngSubmit)="setup()">
<div class="field">
<label for="" class="label">Username</label>
<div class="control has-icons-left">
<input type="text" placeholder="" class="input" name="userName" [(ngModel)]="userName" required />
<span class="icon is-small is-left">
<i class="fa fa-envelope"></i>
</span>
</div>
</div>
<div class="field">
<label for="" class="label" name="password">Password</label>
<div class="control has-icons-left">
<input type="password" class="input" required name="password" [(ngModel)]="password" />
<span class="icon is-small is-left">
<i class="fa fa-lock"></i>
</span>
</div>
</div>
<div class="field">
<button class="button is-success" [ngClass]="{ 'is-loading': working }" [disabled]="working">
Setup
</button>
</div>
@if (error?.length > 0) {
<div class="notification is-danger is-light">
{{ error }}
</div>
}
</form>
</div>
<form (ngSubmit)="setup()">
}
@if (step === 2) {
<div class="box">
<div class="notification is-primary">
To be able to use the Real-Debrid Client you need a premium subscription to download torrents.
<br /><br />
Not premium yet? You have the choice of 5 providers:
<br />
<a href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener"
>Use this link to sign up to Real-Debrid.</a
>
<br />
<a href="https://alldebrid.com/?uid=2v91l&lang=en" target="_blank" rel="noopener"
>Use this link to sign up to AllDebrid.</a
>
<br />
<a href="https://www.premiumize.me/" target="_blank" rel="noopener"
>Use this link to sign up to Premiumize.</a
>
<br />
<a href="https://torbox.app/" target="_blank" rel="noopener">Use this link to sign up to TorBox.</a>
<br />
<a href="https://debrid-link.com/" target="_blank" rel="noopener"
>Use this link to sign up to DebridLink.</a
>
<small>(Referal links)</small>
</div>
<div class="field">
<label for="" class="label">Username</label>
<div class="control has-icons-left">
<input type="text" placeholder="" class="input" name="userName" [(ngModel)]="userName" required />
<span class="icon is-small is-left">
<i class="fa fa-envelope"></i>
</span>
<label class="label">Provider</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="provider">
<option [ngValue]="0">Real-Debrid</option>
<option [ngValue]="1">AllDebrid</option>
<option [ngValue]="2">Premiumize</option>
<option [ngValue]="3">TorBox</option>
<option [ngValue]="4">DebridLink</option>
</select>
</div>
</div>
<div class="field">
<label for="" class="label" name="password">Password</label>
<div class="control has-icons-left">
<input type="password" class="input" required name="password" [(ngModel)]="password" />
<span class="icon is-small is-left">
<i class="fa fa-lock"></i>
</span>
<label for="" class="label">API Private Token</label>
<div class="control">
<input type="text" placeholder="" class="input" name="token" [(ngModel)]="token" required />
</div>
@if (provider === 0) {
<p class="help">
You can find your API key here:
<a href="https://real-debrid.com/apitoken" target="_blank" rel="noopener"
>https://real-debrid.com/apitoken</a
>.
</p>
}
@if (provider === 1) {
<p class="help">
You can find your API key here:
<a href="https://alldebrid.com/apikeys/" target="_blank" rel="noopener"
>https://alldebrid.com/apikeys/</a
>.
</p>
}
@if (provider === 2) {
<p class="help">
You can find your API key here:
<a href="https://www.premiumize.me/account" target="_blank" rel="noopener"
>https://www.premiumize.me/account</a
>.
</p>
}
@if (provider === 3) {
<p class="help">
You can find your API key here:
<a href="https://torbox.app/settings" target="_blank" rel="noopener">https://torbox.app/settings</a
>.
</p>
}
@if (provider === 4) {
<p class="help">
You can find your API key here:
<a href="https://debrid-link.com/webapp/apikey" target="_blank" rel="noopener"
>https://debrid-link.com/webapp/apikey</a
>.
</p>
}
</div>
<div class="field">
<button class="button is-success" [ngClass]="{ 'is-loading': working }" [disabled]="working">
<button
class="button is-success"
(click)="setToken()"
[ngClass]="{ 'is-loading': working }"
[disabled]="working"
>
Setup
</button>
</div>
<div class="notification is-danger is-light" *ngIf="error?.length > 0">
{{ error }}
</div>
</form>
</div>
<div class="box" *ngIf="step === 2">
<div class="notification is-primary">
To be able to use the Real-Debrid Client you need a premium subscription to download torrents.
<br /><br />
Not premium yet? You have the choice of 5 providers:
<br />
<a href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener"
>Use this link to sign up to Real-Debrid.</a
>
<br />
<a href="https://alldebrid.com/?uid=2v91l&lang=en" target="_blank" rel="noopener"
>Use this link to sign up to AllDebrid.</a
>
<br />
<a href="https://www.premiumize.me/" target="_blank" rel="noopener"
>Use this link to sign up to Premiumize.</a
>
<br />
<a href="https://torbox.app/" target="_blank" rel="noopener">Use this link to sign up to TorBox.</a>
<br />
<a href="https://debrid-link.com/" target="_blank" rel="noopener"
>Use this link to sign up to DebridLink.</a
>
<small>(Referal links)</small>
@if (error?.length > 0) {
<div class="notification is-danger is-light">
{{ error }}
</div>
}
</div>
<div class="field">
<label class="label">Provider</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="provider">
<option [ngValue]="0">Real-Debrid</option>
<option [ngValue]="1">AllDebrid</option>
<option [ngValue]="2">Premiumize</option>
<option [ngValue]="3">TorBox</option>
<option [ngValue]="4">DebridLink</option>
</select>
}
@if (step === 3) {
<div class="box">
<div class="notification is-primary">
You are now connected to Real-Debrid! To setup Radarr or Sonarr, please following the instructions here:
<a href="https://github.com/rogerfar/rdt-client/" target="_blank" rel="noopener"
>https://github.com/rogerfar/rdt-client/</a
>.
</div>
<div class="field">
<button
class="button is-success"
(click)="close()"
[ngClass]="{ 'is-loading': working }"
[disabled]="working"
>
OK
</button>
</div>
</div>
<div class="field">
<label for="" class="label">API Private Token</label>
<div class="control">
<input type="text" placeholder="" class="input" name="token" [(ngModel)]="token" required />
</div>
<p class="help" *ngIf="provider === 0">
You can find your API key here:
<a href="https://real-debrid.com/apitoken" target="_blank" rel="noopener"
>https://real-debrid.com/apitoken</a
>.
</p>
<p class="help" *ngIf="provider === 1">
You can find your API key here:
<a href="https://alldebrid.com/apikeys/" target="_blank" rel="noopener"
>https://alldebrid.com/apikeys/</a
>.
</p>
<p class="help" *ngIf="provider === 2">
You can find your API key here:
<a href="https://www.premiumize.me/account" target="_blank" rel="noopener"
>https://www.premiumize.me/account</a
>.
</p>
<p class="help" *ngIf="provider === 3">
You can find your API key here:
<a href="https://torbox.app/settings" target="_blank" rel="noopener">https://torbox.app/settings</a>.
</p>
<p class="help" *ngIf="provider === 4">
You can find your API key here:
<a href="https://debrid-link.com/webapp/apikey" target="_blank" rel="noopener"
>https://debrid-link.com/webapp/apikey</a
>.
</p>
</div>
<div class="field">
<button
class="button is-success"
(click)="setToken()"
[ngClass]="{ 'is-loading': working }"
[disabled]="working"
>
Setup
</button>
</div>
<div class="notification is-danger is-light" *ngIf="error?.length > 0">
{{ error }}
</div>
</div>
<div class="box" *ngIf="step === 3">
<div class="notification is-primary">
You are now connected to Real-Debrid! To setup Radarr or Sonarr, please following the instructions here:
<a href="https://github.com/rogerfar/rdt-client/" target="_blank" rel="noopener"
>https://github.com/rogerfar/rdt-client/</a
>.
</div>
<div class="field">
<button
class="button is-success"
(click)="close()"
[ngClass]="{ 'is-loading': working }"
[disabled]="working"
>
OK
</button>
</div>
</div>
}
</div>
</div>
</div>

View file

@ -1,7 +1,9 @@
<div class="notification is-danger is-light" *ngIf="error && error.length > 0">
An error has occured: {{ error }}<br />
Please refresh the screen after fixing this error.
</div>
@if (error && error.length > 0) {
<div class="notification is-danger is-light">
An error has occurred: {{ error }}<br />
Please refresh the screen after fixing this error.
</div>
}
<div class="table-container">
<table class="table is-fullwidth is-hoverable">
<thead>
@ -25,72 +27,59 @@
</tr>
</thead>
<tbody>
<tr *ngFor="let torrent of torrents | sort: sortProperty : sortDirection; trackBy: trackByMethod">
<td>
<input
type="checkbox"
(click)="toggleSelect(torrent.torrentId)"
[checked]="selectedTorrents.contains(torrent.torrentId)"
/>
</td>
<td (click)="openTorrent(torrent.torrentId)" class="break-all">
{{ torrent.rdName }}
</td>
<td>
{{ torrent.category }}
</td>
<td>
{{ torrent.priority }}
</td>
<td>
{{ torrent.rdSeeders }}
</td>
<td>
{{ torrent.files.length | number }}
</td>
<td>
{{ torrent.downloads.length | number }}
</td>
<td>
{{ torrent.rdSize | filesize }}
</td>
<td>
{{ torrent.added | date: "medium" }}
</td>
<td>
{{ torrent | status }}
</td>
</tr>
@for (torrent of torrents | sort: sortProperty : sortDirection; track torrent.torrentId) {
<tr>
<td>
<input
type="checkbox"
(click)="toggleSelect(torrent.torrentId)"
[checked]="selectedTorrents.contains(torrent.torrentId)"
/>
</td>
<td (click)="openTorrent(torrent.torrentId)" class="break-all">
{{ torrent.rdName }}
</td>
<td>
{{ torrent.category }}
</td>
<td>
{{ torrent.priority }}
</td>
<td>
{{ torrent.rdSeeders }}
</td>
<td>
{{ torrent.files.length | number }}
</td>
<td>
{{ torrent.downloads.length | number }}
</td>
<td>
{{ torrent.rdSize | filesize }}
</td>
<td>
{{ torrent.added | date: "medium" }}
</td>
<td>
{{ torrent | status }}
</td>
</tr>
}
</tbody>
</table>
<div class="flex-container">
<button
class="button is-danger"
(click)="showDeleteModal()"
[disabled]="selectedTorrents.length === 0"
*ngIf="torrents.length > 0"
>
Delete Selected
</button>
<button
class="button is-primary"
(click)="showRetryModal()"
[disabled]="selectedTorrents.length === 0"
*ngIf="torrents.length > 0"
>
Retry Selected
</button>
<button
class="button is-primary"
(click)="changeSettingsModal()"
[disabled]="selectedTorrents.length === 0"
*ngIf="torrents.length > 0"
>
Change Settings
</button>
@if (torrents.length > 0) {
<button class="button is-danger" (click)="showDeleteModal()" [disabled]="selectedTorrents.length === 0">
Delete Selected
</button>
<button class="button is-primary" (click)="showRetryModal()" [disabled]="selectedTorrents.length === 0">
Retry Selected
</button>
<button class="button is-primary" (click)="changeSettingsModal()" [disabled]="selectedTorrents.length === 0">
Change Settings
</button>
}
</div>
</div>
@ -131,9 +120,9 @@
<div class="notification is-primary">
Deleting a torrent from Real-Debrid will automatically delete it here too.
</div>
<div class="notification is-danger is-light" *ngIf="deleteError?.length > 0">
Error deleting torrent: {{ deleteError }}
</div>
@if (deleteError?.length > 0) {
<div class="notification is-danger is-light">Error deleting torrent: {{ deleteError }}</div>
}
</section>
<footer class="modal-card-foot">
<button
@ -164,9 +153,9 @@
This action will delete all the torrent data + all local downloads. Then it will re-add the original magnet link
or torrent file to the debrid provider.
</p>
<div class="notification is-danger is-light" *ngIf="retryError?.length > 0">
Error retrying torrent: {{ retryError }}
</div>
@if (retryError?.length > 0) {
<div class="notification is-danger is-light">Error retrying torrent: {{ retryError }}</div>
}
</section>
<footer class="modal-card-foot">
<button
@ -296,9 +285,9 @@
</p>
</div>
<div class="notification is-danger is-light" *ngIf="changeSettingsError?.length > 0">
Error changing settings: {{ changeSettingsError }}
</div>
@if (changeSettingsError?.length > 0) {
<div class="notification is-danger is-light">Error changing settings: {{ changeSettingsError }}</div>
}
</section>
<footer class="modal-card-foot">
<button

View file

@ -71,10 +71,6 @@ export class TorrentTableComponent implements OnInit {
this.router.navigate([`/torrent/${torrentId}`]);
}
public trackByMethod(index: number, el: Torrent): string {
return el.torrentId;
}
public toggleDeleteSelectAll(event: any) {
this.selectedTorrents = [];

View file

@ -12,353 +12,426 @@
</ul>
</div>
<div *ngIf="torrent === null || torrent === undefined">
<div class="fa-3x">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
<div *ngIf="torrent !== null && torrent !== undefined">
<div class="flex-container" *ngIf="activeTab === 0">
<div style="flex: 1 1 0">
<div class="field is-grouped">
<div class="control">
<button class="button is-danger" (click)="showDeleteModal()">Delete Torrent</button>
</div>
<div class="control">
<button class="button is-primary" (click)="showRetryModal()">Retry Torrent</button>
</div>
<div class="control">
<button class="button is-light" (click)="showUpdateSettingsModal()">Change Settings</button>
</div>
</div>
<div class="field">
<label class="label">Status</label>
{{ torrent | status }}
</div>
<div class="field">
<label class="label">Retry count</label>
{{ torrent.retryCount }} / {{ torrent.torrentRetryAttempts }}
</div>
<div class="field">
<label class="label">Hash</label>
{{ torrent.hash }}
</div>
<div class="field">
<label class="label">Priority</label>
{{ torrent.priority || "" }}
</div>
<div class="field">
<label class="label">Category</label>
{{ torrent.category || "(no category set)" }}
</div>
<div class="field">
<label class="label">Downloader</label>
<ng-container [ngSwitch]="torrent.downloadClient">
<ng-container *ngSwitchCase="0">Internal Downloader</ng-container>
<ng-container *ngSwitchCase="1">Bezadd</ng-container>
<ng-container *ngSwitchCase="2">Aria2c</ng-container>
<ng-container *ngSwitchCase="3">Symlink Downloader</ng-container>
<ng-component *ngSwitchCase="4">Synology DownloadStation</ng-component>
</ng-container>
</div>
<div class="field">
<label class="label">Post Download Action</label>
<ng-container [ngSwitch]="torrent.hostDownloadAction">
<ng-container *ngSwitchCase="0">Download all files to host</ng-container>
<ng-container *ngSwitchCase="1">Don't download files to host</ng-container>
</ng-container>
</div>
<div class="field">
<label class="label">Post Torrent Download Action</label>
<ng-container [ngSwitch]="torrent.downloadAction">
<ng-container *ngSwitchCase="0">Download all files above a certain size</ng-container>
<ng-container *ngSwitchCase="1"
>Download all available files on Real-Debrid above a certain size</ng-container
>
<ng-container *ngSwitchCase="2">Pick files I want to download</ng-container>
</ng-container>
</div>
<div class="field">
<label class="label">Finished action</label>
<ng-container [ngSwitch]="torrent.finishedAction">
<ng-container *ngSwitchCase="0">Do nothing</ng-container>
<ng-container *ngSwitchCase="1">Remove torrent from Real-Debrid and Real-Debrid Client</ng-container>
<ng-container *ngSwitchCase="2">Remove torrent from Real-Debrid</ng-container>
<ng-container *ngSwitchCase="3">Remove torrent from client</ng-container>
</ng-container>
</div>
<div class="field">
<label class="label">Minimum file size to download</label>
{{ torrent.downloadMinSize }}MB
</div>
<div class="field">
<label class="label">Include files</label>
{{ torrent.includeRegex }}
</div>
<div class="field">
<label class="label">Exclude files</label>
{{ torrent.excludeRegex }}
</div>
<div class="field" *ngIf="!torrent.isFile">
<label class="label">Magnet</label>
<span [cdkCopyToClipboard]="torrent.fileOrMagnet" (click)="copied = true" *ngIf="!copied"
>Click to copy magnet link to clipboard</span
>
<span *ngIf="copied">Link copied to clipboard!</span>
</div>
<div class="field" *ngIf="torrent.isFile">
<label class="label">Torrent file</label>
<span (click)="download()">Click to download torrent file</span>
</div>
<div class="field">
<label class="label">Added on</label>
{{ torrent.added | date: "fullDate" }} {{ torrent.added | date: "mediumTime" }}
</div>
<div class="field">
<label class="label">Files selected on</label>
<ng-container *ngIf="torrent.filesSelected === null">(no files selected yet) </ng-container>
<ng-container *ngIf="torrent.filesSelected !== null">
{{ torrent.filesSelected | date: "fullDate" }} {{ torrent.filesSelected | date: "mediumTime" }}
</ng-container>
</div>
<div class="field">
<label class="label">Completed on</label>
<ng-container *ngIf="torrent.completed === null">(not completed yet) </ng-container>
<ng-container *ngIf="torrent.completed !== null">
{{ torrent.completed | date: "fullDate" }} {{ torrent.completed | date: "mediumTime" }}
</ng-container>
</div>
</div>
<div style="flex: 1 1 0">
<div class="field">
<label class="label">Real-Debrid ID</label>
{{ torrent.rdId }}
</div>
<div class="field">
<label class="label">Real-Debrid Name</label>
{{ torrent.rdName }}
</div>
<div class="field">
<label class="label">Real-Debrid Size</label>
{{ torrent.rdSize | filesize }}
</div>
<div class="field">
<label class="label">Real-Debrid Host</label>
{{ torrent.rdHost }}
</div>
<div class="field">
<label class="label">Real-Debrid Split</label>
{{ torrent.rdSplit }}
</div>
<div class="field">
<label class="label">Real-Debrid Progress</label>
{{ torrent.rdProgress || 0 }}%
</div>
<div class="field">
<label class="label">Real-Debrid Status</label>
<ng-container [ngSwitch]="torrent.rdStatus">
<ng-container *ngSwitchCase="0">Processing</ng-container>
<ng-container *ngSwitchCase="1">Waiting For File Selection</ng-container>
<ng-container *ngSwitchCase="2">Downloading</ng-container>
<ng-container *ngSwitchCase="3">Finished</ng-container>
<ng-container *ngSwitchCase="99">Error</ng-container>
</ng-container>
({{ torrent.rdStatusRaw }})
</div>
<div class="field">
<label class="label">Real-Debrid Added</label>
{{ torrent.rdAdded | date: "fullDate" }} {{ torrent.rdAdded | date: "mediumTime" }}
</div>
<div class="field">
<label class="label">Real-Debrid Ended</label>
{{ torrent.rdEnded | date: "fullDate" }} {{ torrent.rdEnded | date: "mediumTime" }}
</div>
<div class="field">
<label class="label">Real-Debrid Speed</label>
{{ torrent.rdSpeed || 0 }}
</div>
<div class="field">
<label class="label">Real-Debrid Seeders</label>
{{ torrent.rdSeeders || "0" }}
</div>
@if (torrent === null || torrent === undefined) {
<div>
<div class="fa-3x">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
<div *ngIf="activeTab === 1">
<div class="field">
<table class="table is-fullwidth">
<thead>
<tr>
<th>ID</th>
<th>Path</th>
<th>Size</th>
<th>Selected</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let file of torrent.files">
<td>
{{ file.id }}
</td>
<td>
{{ file.path }}
</td>
<td>
{{ file.bytes | filesize }}
</td>
<td>
<i class="fas fa-check" *ngIf="file.selected" style="color: green"></i>
<i class="fas fa-times" *ngIf="!file.selected" style="color: red"></i>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div *ngIf="activeTab === 2">
<div class="field">
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th style="width: 35px"></th>
<th>Link</th>
<th>Size</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let download of torrent.downloads">
<tr (click)="downloadExpanded[download.downloadId] = !downloadExpanded[download.downloadId]">
<td style="width: 35px">
<i class="fas fa-caret-right" *ngIf="!downloadExpanded[download.downloadId]"></i>
<i class="fas fa-caret-down" *ngIf="downloadExpanded[download.downloadId]"></i>
</td>
<td>
<ng-container *ngIf="download.link">
{{ download.link | decodeURI }}
</ng-container>
<ng-container *ngIf="!download.link">
{{ download.path }}
</ng-container>
</td>
<td>
{{ download.bytesTotal | filesize }}
</td>
<td>
{{ download | downloadStatus }}
</td>
</tr>
<tr *ngIf="downloadExpanded[download.downloadId]" class="separator">
<td style="width: 35px"></td>
<td colspan="5">
<div class="flex-container">
<div style="flex: 1 1 0">
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" (click)="showDownloadRetryModal(download.downloadId)">
Retry Download
</button>
</div>
</div>
<div class="field" *ngIf="download.error">
<label class="label">Error</label>
{{ download.error }}
</div>
<div class="field">
<label class="label">Real-Debrid Unrestricted Link</label>
<a href="{{ download.link }}" target="_blank" *ngIf="download.link">
{{ download.link | decodeURI }}</a
>
</div>
<div class="field">
<label class="label">Real-Debrid Link</label>
} @else {
<div>
@if (activeTab === 0) {
<div class="flex-container">
<div style="flex: 1 1 0">
<div class="field is-grouped">
<div class="control">
<button class="button is-danger" (click)="showDeleteModal()">Delete Torrent</button>
</div>
<div class="control">
<button class="button is-primary" (click)="showRetryModal()">Retry Torrent</button>
</div>
<div class="control">
<button class="button is-light" (click)="showUpdateSettingsModal()">Change Settings</button>
</div>
</div>
<div class="field">
<label class="label">Status</label>
{{ torrent | status }}
</div>
<div class="field">
<label class="label">Retry count</label>
{{ torrent.retryCount }} / {{ torrent.torrentRetryAttempts }}
</div>
<div class="field">
<label class="label">Hash</label>
{{ torrent.hash }}
</div>
<div class="field">
<label class="label">Priority</label>
{{ torrent.priority || "" }}
</div>
<div class="field">
<label class="label">Category</label>
{{ torrent.category || "(no category set)" }}
</div>
<div class="field">
<label class="label">Downloader</label>
@switch (torrent.downloadClient) {
@case (0) {
Internal Downloader
}
@case (1) {
Bezadd
}
@case (2) {
Aria2c
}
@case (3) {
Symlink Downloader
}
@case (4) {
Synology DownloadStation
}
}
</div>
<div class="field">
<label class="label">Post Download Action</label>
@switch (torrent.hostDownloadAction) {
@case (0) {
Download all files to host
}
@case (1) {
Don't download files to host
}
}
</div>
<div class="field">
<label class="label">Post Torrent Download Action</label>
@switch (torrent.downloadAction) {
@case (0) {
Download all files above a certain size
}
@case (1) {
Download all available files on Real-Debrid above a certain size
}
@case (2) {
Pick files I want to download
}
}
</div>
<div class="field">
<label class="label">Finished action</label>
@switch (torrent.finishedAction) {
@case (0) {
Do nothing
}
@case (1) {
Remove torrent from Real-Debrid and Real-Debrid Client
}
@case (2) {
Remove torrent from Real-Debrid
}
@case (3) {
Remove torrent from client
}
}
</div>
<div class="field">
<label class="label">Minimum file size to download</label>
{{ torrent.downloadMinSize }}MB
</div>
<div class="field">
<label class="label">Include files</label>
{{ torrent.includeRegex }}
</div>
<div class="field">
<label class="label">Exclude files</label>
{{ torrent.excludeRegex }}
</div>
@if (!torrent.isFile) {
<div class="field">
<label class="label">Magnet</label>
@if (!copied) {
<span [cdkCopyToClipboard]="torrent.fileOrMagnet" (click)="copied = true">
Click to copy magnet link to clipboard
</span>
} @else {
<span>Link copied to clipboard!</span>
}
</div>
} @else {
<div class="field">
<label class="label">Torrent file</label>
<span (click)="download()">Click to download torrent file</span>
</div>
}
<div class="field">
<label class="label">Added on</label>
{{ torrent.added | date: "fullDate" }} {{ torrent.added | date: "mediumTime" }}
</div>
<div class="field">
<label class="label">Files selected on</label>
@if (torrent.filesSelected === null) {
(no files selected yet)
} @else {
{{ torrent.filesSelected | date: "fullDate" }} {{ torrent.filesSelected | date: "mediumTime" }}
}
</div>
<div class="field">
<label class="label">Completed on</label>
@if (torrent.completed === null) {
(not completed yet)
} @else {
{{ torrent.completed | date: "fullDate" }} {{ torrent.completed | date: "mediumTime" }}
}
</div>
</div>
<div style="flex: 1 1 0">
<div class="field">
<label class="label">Real-Debrid ID</label>
{{ torrent.rdId }}
</div>
<div class="field">
<label class="label">Real-Debrid Name</label>
{{ torrent.rdName }}
</div>
<div class="field">
<label class="label">Real-Debrid Size</label>
{{ torrent.rdSize | filesize }}
</div>
<div class="field">
<label class="label">Real-Debrid Host</label>
{{ torrent.rdHost }}
</div>
<div class="field">
<label class="label">Real-Debrid Split</label>
{{ torrent.rdSplit }}
</div>
<div class="field">
<label class="label">Real-Debrid Progress</label>
{{ torrent.rdProgress || 0 }}%
</div>
<div class="field">
<label class="label">Real-Debrid Status</label>
@switch (torrent.rdStatus) {
@case (0) {
Processing
}
@case (1) {
Waiting For File Selection
}
@case (2) {
Downloading
}
@case (3) {
Finished
}
@case (99) {
Error
}
}
({{ torrent.rdStatusRaw }})
</div>
<div class="field">
<label class="label">Real-Debrid Added</label>
{{ torrent.rdAdded | date: "fullDate" }} {{ torrent.rdAdded | date: "mediumTime" }}
</div>
<div class="field">
<label class="label">Real-Debrid Ended</label>
{{ torrent.rdEnded | date: "fullDate" }} {{ torrent.rdEnded | date: "mediumTime" }}
</div>
<div class="field">
<label class="label">Real-Debrid Speed</label>
{{ torrent.rdSpeed || 0 }}
</div>
<div class="field">
<label class="label">Real-Debrid Seeders</label>
{{ torrent.rdSeeders || "0" }}
</div>
</div>
</div>
}
@if (activeTab === 1) {
<div>
<div class="field">
<table class="table is-fullwidth">
<thead>
<tr>
<th>ID</th>
<th>Path</th>
<th>Size</th>
<th>Selected</th>
</tr>
</thead>
<tbody>
@for (file of torrent.files; track file.id) {
<tr>
<td>
{{ file.id }}
</td>
<td>
{{ file.path }}
</td>
<td>
{{ file.bytes | filesize }}
</td>
<td>
@if (file.selected) {
<i class="fas fa-check" style="color: green"></i>
} @else {
<i class="fas fa-times" style="color: red"></i>
}
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
@if (activeTab === 2) {
<div>
<div class="field">
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th style="width: 35px"></th>
<th>Link</th>
<th>Size</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@for (download of torrent.downloads; track download.downloadId) {
@let expanded = downloadExpanded[download.downloadId];
<tr (click)="downloadExpanded[download.downloadId] = !expanded">
<td style="width: 35px">
@if (!expanded) {
<i class="fas fa-caret-right"></i>
} @else {
<i class="fas fa-caret-down"></i>
}
</td>
<td>
@if (download.link) {
{{ download.link | decodeURI }}
}
@if (!download.link) {
{{ download.path }}
</div>
<div class="field">
<label class="label">Download</label>
{{ download.bytesDone | filesize }} / {{ download.bytesTotal | filesize }} ({{
download.speed | filesize
}}/s)
</div>
<div class="field">
<label class="label">Retry Count</label>
{{ download.retryCount }} / {{ torrent.downloadRetryAttempts }}
</div>
</div>
<div style="flex: 1 1 0">
<div class="field">
<label class="label">Added</label>
<ng-container *ngIf="download.added">
{{ download.added | date: "fullDate" }} {{ download.added | date: "mediumTime" }}
</ng-container>
<ng-container *ngIf="!download.added">(not added yet) </ng-container>
</div>
<div class="field">
<label class="label">Download Queued</label>
<ng-container *ngIf="download.downloadQueued">
{{ download.downloadQueued | date: "fullDate" }}
{{ download.downloadQueued | date: "mediumTime" }}
</ng-container>
<ng-container *ngIf="!download.downloadQueued">(not queued for downloading yet) </ng-container>
</div>
<div class="field">
<label class="label">Download Started</label>
<ng-container *ngIf="download.downloadStarted">
{{ download.downloadStarted | date: "fullDate" }}
{{ download.downloadStarted | date: "mediumTime" }}
</ng-container>
<ng-container *ngIf="!download.downloadStarted">(not started downloading yet) </ng-container>
</div>
<div class="field">
<label class="label">Download Finished</label>
<ng-container *ngIf="download.downloadFinished">
{{ download.downloadFinished | date: "fullDate" }}
{{ download.downloadFinished | date: "mediumTime" }}
</ng-container>
<ng-container *ngIf="!download.downloadFinished">(not finished yet) </ng-container>
</div>
<div class="field">
<label class="label">Unpacking Queued</label>
<ng-container *ngIf="download.unpackingQueued">
{{ download.unpackingQueued | date: "fullDate" }}
{{ download.unpackingQueued | date: "mediumTime" }}
</ng-container>
<ng-container *ngIf="!download.unpackingQueued">(not queued for unpacking yet) </ng-container>
</div>
<div class="field">
<label class="label">Unpacking Started</label>
<ng-container *ngIf="download.unpackingStarted">
{{ download.unpackingStarted | date: "fullDate" }}
{{ download.unpackingStarted | date: "mediumTime" }}
</ng-container>
<ng-container *ngIf="!download.unpackingStarted">(not started unpacking yet) </ng-container>
</div>
<div class="field">
<label class="label">Unpacking Finished</label>
<ng-container *ngIf="download.unpackingFinished">
{{ download.unpackingFinished | date: "fullDate" }}
{{ download.unpackingFinished | date: "mediumTime" }}
</ng-container>
<ng-container *ngIf="!download.unpackingFinished">(not finished unpacking yet) </ng-container>
</div>
<div class="field">
<label class="label">Completed</label>
<ng-container *ngIf="download.completed">
{{ download.completed | date: "fullDate" }}
{{ download.completed | date: "mediumTime" }}
</ng-container>
<ng-container *ngIf="!download.completed">(not completed yet) </ng-container>
</div>
</div>
</div>
</td>
</tr>
</ng-container>
</tbody>
</table>
</div>
}
</td>
<td>
{{ download.bytesTotal | filesize }}
</td>
<td>
{{ download | downloadStatus }}
</td>
</tr>
@if (expanded) {
<tr class="separator">
<td style="width: 35px"></td>
<td colspan="5">
<div class="flex-container">
<div style="flex: 1 1 0">
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" (click)="showDownloadRetryModal(download.downloadId)">
Retry Download
</button>
</div>
</div>
@if (download.error) {
<div class="field">
<label class="label">Error</label>
{{ download.error }}
</div>
}
<div class="field">
<label class="label">Real-Debrid Unrestricted Link</label>
@if (download.link) {
<a href="{{ download.link }}" target="_blank"> {{ download.link | decodeURI }}</a>
}
</div>
<div class="field">
<label class="label">Real-Debrid Link</label>
{{ download.path }}
</div>
<div class="field">
<label class="label">Download</label>
{{ download.bytesDone | filesize }} / {{ download.bytesTotal | filesize }} ({{
download.speed | filesize
}}/s)
</div>
<div class="field">
<label class="label">Retry Count</label>
{{ download.retryCount }} / {{ torrent.downloadRetryAttempts }}
</div>
</div>
<div style="flex: 1 1 0">
<div class="field">
<label class="label">Added</label>
@if (download.added) {
{{ download.added | date: "fullDate" }} {{ download.added | date: "mediumTime" }}
} @else {
(not added yet)
}
</div>
<div class="field">
<label class="label">Download Queued</label>
@if (download.downloadQueued) {
{{ download.downloadQueued | date: "fullDate" }}
{{ download.downloadQueued | date: "mediumTime" }}
} @else {
(not queued for downloading yet)
}
</div>
<div class="field">
<label class="label">Download Started</label>
@if (download.downloadStarted) {
{{ download.downloadStarted | date: "fullDate" }}
{{ download.downloadStarted | date: "mediumTime" }}
} @else {
(not started downloading yet)
}
</div>
<div class="field">
<label class="label">Download Finished</label>
@if (download.downloadFinished) {
{{ download.downloadFinished | date: "fullDate" }}
{{ download.downloadFinished | date: "mediumTime" }}
} @else {
(not finished yet)
}
</div>
<div class="field">
<label class="label">Unpacking Queued</label>
@if (download.unpackingQueued) {
{{ download.unpackingQueued | date: "fullDate" }}
{{ download.unpackingQueued | date: "mediumTime" }}
} @else {
(not queued for unpacking yet)
}
</div>
<div class="field">
<label class="label">Unpacking Started</label>
@if (download.unpackingStarted) {
{{ download.unpackingStarted | date: "fullDate" }}
{{ download.unpackingStarted | date: "mediumTime" }}
} @else {
(not started unpacking yet)
}
</div>
<div class="field">
<label class="label">Unpacking Finished</label>
@if (download.unpackingFinished) {
{{ download.unpackingFinished | date: "fullDate" }}
{{ download.unpackingFinished | date: "mediumTime" }}
} @else {
(not finished unpacking yet)
}
</div>
<div class="field">
<label class="label">Completed</label>
@if (download.completed) {
{{ download.completed | date: "fullDate" }}
{{ download.completed | date: "mediumTime" }}
} @else {
(not completed yet)
}
</div>
</div>
</div>
</td>
</tr>
}
}
</tbody>
</table>
</div>
</div>
}
</div>
</div>
}
<div class="modal" [class.is-active]="isDeleteModalActive">
<div class="modal-background"></div>
@ -391,9 +464,9 @@
<div class="notification is-primary">
Deleting a torrent from Real-Debrid will automatically delete it here too.
</div>
<div class="notification is-danger is-light" *ngIf="deleteError?.length > 0">
Error deleting torrent: {{ deleteError }}
</div>
@if (deleteError?.length > 0) {
<div class="notification is-danger is-light">Error deleting torrent: {{ deleteError }}</div>
}
</section>
<footer class="modal-card-foot">
<button
@ -424,9 +497,9 @@
This action will delete all the torrent data + all local downloads. Then it will re-add the original magnet link
or torrent file to Real-Debrid.
</p>
<div class="notification is-danger is-light" *ngIf="retryError?.length > 0">
Error retrying torrent: {{ retryError }}
</div>
@if (retryError?.length > 0) {
<div class="notification is-danger is-light">Error retrying torrent: {{ retryError }}</div>
}
</section>
<footer class="modal-card-foot">
<button
@ -454,9 +527,9 @@
<section class="modal-card-body">
<p>Are you sure you want to retry this download?</p>
<p>This action will remove the local download and re-download the file from Real-Debrid.</p>
<div class="notification is-danger is-light" *ngIf="downloadRetryError?.length > 0">
Error retrying download: {{ downloadRetryError }}
</div>
@if (downloadRetryError?.length > 0) {
<div class="notification is-danger is-light">Error retrying download: {{ downloadRetryError }}</div>
}
</section>
<footer class="modal-card-foot">
<button