diff --git a/app/Item.php b/app/Item.php
index af1022be..29a77fdb 100644
--- a/app/Item.php
+++ b/app/Item.php
@@ -31,6 +31,7 @@ class Item extends Model
'Duplicati' => \App\SupportedApps\Duplicati::class,
'Emby' => \App\SupportedApps\Emby::class,
'Gitea' => \App\SupportedApps\Gitea::class,
+ 'Glances' => \App\SupportedApps\Glances::class,
'Graylog' => \App\SupportedApps\Graylog::class,
'Home Assistant' => \App\SupportedApps\HomeAssistant::class,
'Jackett' => \App\SupportedApps\Jackett::class,
@@ -56,6 +57,7 @@ class Item extends Model
'Sabnzbd' => \App\SupportedApps\Sabnzbd::class,
'Sickrage' => \App\SupportedApps\Sickrage::class,
'Sonarr' => \App\SupportedApps\Sonarr::class,
+ 'Transmission' => \App\SupportedApps\Transmission::class,
'Traefik' => \App\SupportedApps\Traefik::class,
'Ttrss' => \App\SupportedApps\Ttrss::class,
'UniFi' => \App\SupportedApps\Unifi::class,
diff --git a/app/SupportedApps/Glances.php b/app/SupportedApps/Glances.php
new file mode 100644
index 00000000..ca593814
--- /dev/null
+++ b/app/SupportedApps/Glances.php
@@ -0,0 +1,14 @@
+ ["percentDone","status","rateDownload","rateUpload"]);
+ $this->_client = new Client(
+ ['http_errors' => false,
+ 'timeout' => 10,
+ 'body' => json_encode($body)]
+ );
+ }
+
+ public function defaultColour()
+ {
+ return '#950003';
+ }
+ public function icon()
+ {
+ return 'supportedapps/Transmission.png';
+ }
+ public function configDetails()
+ {
+ return 'transmission';
+ }
+ public function testConfig()
+ {
+ $res = $this->sendRequest();
+ if ($res == null) {
+ echo 'Transmission connection failed';
+ return;
+ }
+ switch($res->getStatusCode()) {
+ case 200:
+ $data = json_decode($res->getBody());
+ echo "Successfully connected with status: ".$data->result."\n";
+ break;
+ case 401:
+ echo 'Failed: Invalid credentials';
+ break;
+ case 404:
+ echo 'Failed: Please make sure your URL is correct and includes the port';
+ break;
+ case 409:
+ echo 'Failed: Incorrect session id';
+ break;
+ default:
+ echo 'Something went wrong... Code: '.$res->getStatusCode();
+ break;
+ }
+ }
+
+ public function executeConfig()
+ {
+ $html = '';
+ $active = 'active';
+ $res = $this->sendRequest();
+ if ($res == null) {
+ Log::debug('Transmission connection failed');
+ return '';
+ }
+ $data = json_decode($res->getBody());
+ if (! isset($data->arguments)) {
+ Log::debug('Failed to fetch data from Transmission');
+ return '';
+ }
+ $torrents = $data->arguments->torrents;
+ $torrentCount = count($torrents);
+ $rateDownload = $rateUpload = $completedTorrents = 0;
+ foreach ($torrents as $thisTorrent) {
+ $rateDownload += $thisTorrent->rateDownload;
+ $rateUpload += $thisTorrent->rateUpload;
+ if ($thisTorrent->percentDone == 1) {
+ $completedTorrents += 1;
+ }
+ }
+ if ($torrentCount - $completedTorrents == 0) {
+ // Don't poll as frequently if we don't have any active torrents
+ $active = 'inactive';
+ }
+
+ $html = '
+
+ - Done'.$completedTorrents.' / '.$torrentCount.'
+ - Down'.format_bytes($rateDownload).'
+ - Up'.format_bytes($rateUpload).'
+
+ ';
+ return json_encode(['status' => $active, 'html' => $html]);;
+ }
+
+ private function sendRequest()
+ {
+ $optionsSet = $this->setClientOptions();
+ if (! $optionsSet) {
+ // Pass the failed response back up the chain
+ return null;
+ }
+ $res = $this->torrentGet();
+ if ($res->getStatusCode() == 409) {
+ $this->setClientOptions();
+ $res = $this->torrentGet();
+ }
+ return $res;
+ }
+
+ private function torrentGet()
+ {
+ $res = null;
+ try{
+ $res = $this->_client->request(
+ 'POST',
+ $this->getApiUrl(),
+ $this->_clientOptions
+ );
+ }catch(\GuzzleHttp\Exception\BadResponseException $e){
+ Log::error("Connection to {$e->getRequest()->getUrl()} failed");
+ Log::debug($e->getMessage());
+ $res = $e->getRequest();
+ }catch(\GuzzleHttp\Exception\ConnectException $e) {
+ Log::error("Transmission connection refused");
+ Log::debug($e->getMessage());
+ }
+ return $res;
+ }
+
+ private function setClientOptions()
+ {
+ if ($this->config->username != '' || $this->config->password != '') {
+ $this->_clientOptions = ['auth'=> [$this->config->username, $this->config->password, 'Basic']];
+ }
+ try{
+ $res = $this->_client->request('HEAD', $this->getApiUrl(), $this->_clientOptions);
+ $xtId = $res->getHeaderLine('X-Transmission-Session-Id');
+ if ($xtId != null) {
+ $this->_clientOptions['headers'] = ['X-Transmission-Session-Id' => $xtId];
+ } else {
+ Log::error("Unable to get Transmission session information");
+ Log::debug("Status Code: ".$res->getStatusCode());
+ }
+ }catch(\GuzzleHttp\Exception\ConnectException $e){
+ Log::error("Failed connection to Transmission");
+ return false;
+ }
+ return true;
+ }
+
+ private function getApiUrl()
+ {
+ $url = $this->config->url;
+ $url = rtrim($url, '/');
+ $apiUrl = $url.'/transmission/rpc';
+ return $apiUrl;
+ }
+}
diff --git a/config/app.php b/config/app.php
index c5832d05..9b8a7a87 100644
--- a/config/app.php
+++ b/config/app.php
@@ -14,7 +14,7 @@ return [
*/
'name' => env('APP_NAME', 'Heimdall'),
- 'version' => '1.4.6',
+ 'version' => '1.4.7',
/*
|--------------------------------------------------------------------------
diff --git a/readme.md b/readme.md
index 5b489b14..8b3a55d7 100644
--- a/readme.md
+++ b/readme.md
@@ -31,6 +31,7 @@ You can use the app to link to any site or application, but Foundation apps will
- NZBGet
- Pihole
- Sabnzbd
+- Transmission
**Foundation**
- Deluge
diff --git a/resources/views/items/form.blade.php b/resources/views/items/form.blade.php
index 8c5388f8..278247c7 100644
--- a/resources/views/items/form.blade.php
+++ b/resources/views/items/form.blade.php
@@ -34,7 +34,7 @@
{!! Form::text('colour', null, array('placeholder' => __('app.apps.hex'),'class' => 'form-control color-picker')) !!}
- {!! Form::select('tags', $tags, $current_tags, ['class' => 'tags', 'multiple']) !!}
+ {!! Form::select('tags[]', $tags, $current_tags, ['class' => 'tags', 'multiple']) !!}