diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..ad45155
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+- package-ecosystem: "github-actions"
+  directory: "/"
+  schedule:
+    interval: monthly
diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
new file mode 100644
index 0000000..3cbb70d
--- /dev/null
+++ b/.github/workflows/php.yml
@@ -0,0 +1,43 @@
+name: PHP Tests
+
+on:
+  push:
+    branches:
+      - main
+      - release/*
+  pull_request:
+    branches:
+      - main
+      - release/*
+
+jobs:
+  lint:
+    name: Lint and Code Style
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        php: ['8.2', '8.3']
+        os: ['ubuntu-latest']
+
+    steps:
+      - name: Checkout code base
+        uses: actions/checkout@v4
+
+      - name: Setup PHP
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: ${{ matrix.php }}
+          tools: phpcs
+
+      - name: Setup dependencies
+        run: composer require -n --no-progress overtrue/phplint
+
+      - name: PHP Lint
+        if: success() || matrix.allow_failure
+        run: ./vendor/bin/phplint -n --exclude={^vendor/.*} -- .
+
+      - name: PHP CodeSniffer
+        if: success() || matrix.allow_failure
+        run: phpcs -wps --colors
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..655204b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+# Exclude editor files
+.*.sw[op]
+*~
+/.idea/
+\#*
+.\#*
+*TODO*
+!.git*
+
+# PHP artifacts
+_icingaweb2/
+_libraries/
+.phpunit.cache/
+.phplint.cache/
+reports/
+vendor/
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4bceed1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,6 @@
+.PHONY: setup lint phpcs
+
+lint:
+	phplint application/ library/ configuration.php
+phpcs:
+	phpcs application/ library/ configuration.php
diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php
index eea2f0c..1b63f70 100644
--- a/application/controllers/ConfigController.php
+++ b/application/controllers/ConfigController.php
@@ -2,7 +2,6 @@
 
 namespace Icinga\Module\Grafana\Controllers;
 
-
 use Icinga\Web\Controller;
 use Icinga\Module\Grafana\Forms\Config\GeneralConfigForm;
 
@@ -23,4 +22,3 @@ public function indexAction()
         $this->view->tabs = $this->Module()->getConfigTabs()->activate('config');
     }
 }
-
diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php
index 7881b39..565d367 100644
--- a/application/controllers/DashboardController.php
+++ b/application/controllers/DashboardController.php
@@ -46,5 +46,4 @@ public function indexAction()
         $graph = new Grapher();
         $this->view->graph = $graph->getPreviewHtml($object);
     }
-
 }
diff --git a/application/controllers/GraphController.php b/application/controllers/GraphController.php
index 9d559f3..5007254 100644
--- a/application/controllers/GraphController.php
+++ b/application/controllers/GraphController.php
@@ -2,7 +2,6 @@
 
 namespace Icinga\Module\Grafana\Controllers;
 
-
 use Icinga\Exception\NotFoundError;
 use Icinga\Forms\ConfirmRemovalForm;
 use Icinga\Module\Grafana\Forms\Graph\GraphForm;
@@ -13,8 +12,8 @@ class GraphController extends Controller
 {
     public function init()
     {
-	$this->assertPermission('grafana/graphconfig');
-	}
+        $this->assertPermission('grafana/graphconfig');
+    }
 
     /**
      * List Grafana graphs
@@ -109,4 +108,3 @@ public function updateAction()
         $this->view->form = $graphs;
     }
 }
-
diff --git a/application/controllers/IcingadbimgController.php b/application/controllers/IcingadbimgController.php
index 3ed7d7c..3d6b582 100644
--- a/application/controllers/IcingadbimgController.php
+++ b/application/controllers/IcingadbimgController.php
@@ -1,10 +1,4 @@
 <?php
-/**
- * Created by PhpStorm.
- * User: carst
- * Date: 11.03.2018
- * Time: 07:18
- */
 
 namespace Icinga\Module\Grafana\Controllers;
 
diff --git a/application/controllers/IcingadbshowController.php b/application/controllers/IcingadbshowController.php
index 48bf123..28f0cb9 100644
--- a/application/controllers/IcingadbshowController.php
+++ b/application/controllers/IcingadbshowController.php
@@ -1,173 +1,166 @@
-<?php
-/**
- * Created by PhpStorm.
- * User: carst
- * Date: 17.02.2018
- * Time: 20:09
- */
-
-namespace Icinga\Module\Grafana\Controllers;
-
-use Icinga\Exception\NotFoundError;
-use Icinga\Module\Grafana\ProvidedHook\Icingadb\HostDetailExtension;
-use Icinga\Module\Grafana\ProvidedHook\Icingadb\ServiceDetailExtension;
-use Icinga\Module\Grafana\Web\Controller\IcingadbGrafanaController;
-use Icinga\Module\Grafana\Web\Widget\PrintAction;
-use Icinga\Module\Icingadb\Model\CustomvarFlat;
-use Icinga\Module\Icingadb\Model\Host;
-use Icinga\Module\Icingadb\Model\Service;
-use Icinga\Module\Grafana\Helpers\Timeranges;
-use Icinga\Application\Config;
-use ipl\Html\HtmlDocument;
-use ipl\Html\HtmlElement;
-use ipl\Html\HtmlString;
-use ipl\Stdlib\Filter;
-use ipl\Web\Url;
-
-
-class IcingadbshowController extends IcingadbGrafanaController
-{
-    /** @var bool */
-    protected $showFullscreen;
-    protected $host;
-    protected $custvardisable = "grafana_graph_disable";
-    protected $config;
-    protected $object;
-
-    public function init()
-    {
-        $this->assertPermission('grafana/showall');
-        $this->view->showFullscreen
-            = $this->showFullscreen
-            = (bool)$this->_helper->layout()->showFullscreen;
-        $this->host = $this->getParam('host');
-        $this->config = Config::module('grafana')->getSection('grafana');
-        /**
-         * Name of the custom variable to disable graph
-         */
-        $this->custvardisable = ($this->config->get('custvardisable', $this->custvardisable));
-    }
-
-    public function indexAction()
-    {
-        $this->disableAutoRefresh();
-
-				/*
-        if (!$this->showFullscreen) {
-            $this->getTabs()->add(
-                'graphs',
-                [
-                    'label' => $this->translate('Grafana Graphs'),
-                    'url' => $this->getRequest()->getUrl()
-                ]
-            )->activate('graphs');
-
-            $this->getTabs()->extend(new PrintAction());
-        }
-				*/
-
-        $this->addControl(
-            HtmlElement::create(
-                'h1',
-                null,
-                sprintf($this->translate('Performance graphs for %s'), $this->host)
-            )
-        );
-
-        // Preserve timerange if selected
-        $parameters = ['host' => $this->host];
-        if ($this->hasParam('timerange')) {
-            $parameters['timerange'] = $this->getParam('timerange');
-        }
-
-        /* The timerange menu */
-        $menu = new Timeranges($parameters, 'grafana/icingadbshow');
-        $this->addControl(new HtmlString($menu->getTimerangeMenu()));
-
-        /* first host object for host graph */
-        $this->object = $this->getHostObject($this->host);
-        $varsFlat = CustomvarFlat::on($this->getDb());
-        $this->applyRestrictions($varsFlat);
-
-        $varsFlat
-            ->columns(['flatname', 'flatvalue'])
-            ->orderBy('flatname');
-        $varsFlat->filter(Filter::equal('host.id', $this->object->id));
-        $customVars = $this->getDb()->fetchPairs($varsFlat->assembleSelect());
-        if ($this->object->perfdata_enabled == "y"
-            || !(isset($customVars[$this->custvardisable])
-                && json_decode(strtolower($customVars[$this->custvardisable])) !== false)
-        ) {
-            $object = (new HtmlDocument())
-                ->addHtml(HtmlElement::create('h2', null, $this->object->checkcommand_name));
-            $this->addContent($object);
-            $this->addContent((new HostDetailExtension())->getPreviewHtml($this->object, true));
-        }
-        /* Get all services for this host */
-        $query = Service::on($this->getDb())->with([
-            'state',
-            'icon_image',
-            'host',
-            'host.state'
-        ]);
-        $query->filter(Filter::equal('host.name', $this->host));
-
-        $this->applyRestrictions($query);
-
-        foreach ($query as $service) {
-            $this->object = $this->getServiceObject($service->name, $this->host);
-            $varsFlat = CustomvarFlat::on($this->getDb());
-            $this->applyRestrictions($varsFlat);
-
-            $varsFlat
-                ->columns(['flatname', 'flatvalue'])
-                ->orderBy('flatname');
-            $varsFlat->filter(Filter::equal('service.id', $service->id));
-            $customVars = $this->getDb()->fetchPairs($varsFlat->assembleSelect());
-            if ($this->object->perfdata_enabled == "y"
-                && !(isset($customVars[$this->custvardisable])
-                    && json_decode(strtolower($customVars[$this->custvardisable])) !== false)
-            ) {
-                $object = (new HtmlDocument())
-                    ->addHtml(HtmlElement::create('h2', null, $service->name));
-                $this->addContent($object);
-                $this->addContent((new ServiceDetailExtension())->getPreviewHtml($service, true));
-            }
-        }
-
-        unset($this->object);
-        unset($customVars);
-    }
-
-
-    public function getHostObject($host)
-    {
-        $query = Host::on($this->getDb())->with(['state', 'icon_image']);
-        $query->filter(Filter::equal('name', $host));
-        $this->applyRestrictions($query);
-        $host = $query->first();
-
-        if ($host === null) {
-            throw new NotFoundError(t('Host not found'));
-        }
-
-        return $host;
-    }
-
-    public function getServiceObject($service, $host)
-    {
-        $query = Service::on($this->getDb());
-
-        $query->filter(Filter::equal('name', $service));
-        $query->filter(Filter::equal('host.name', $host));
-        $this->applyRestrictions($query);
-
-        $service = $query->first();
-
-        if ($service === null) {
-            throw new NotFoundError(t('Service not found'));
-        }
-
-        return $service;
-    }
-}
+<?php
+
+namespace Icinga\Module\Grafana\Controllers;
+
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Grafana\ProvidedHook\Icingadb\HostDetailExtension;
+use Icinga\Module\Grafana\ProvidedHook\Icingadb\ServiceDetailExtension;
+use Icinga\Module\Grafana\Web\Controller\IcingadbGrafanaController;
+use Icinga\Module\Grafana\Web\Widget\PrintAction;
+use Icinga\Module\Icingadb\Model\CustomvarFlat;
+use Icinga\Module\Icingadb\Model\Host;
+use Icinga\Module\Icingadb\Model\Service;
+use Icinga\Module\Grafana\Helpers\Timeranges;
+use Icinga\Application\Config;
+use ipl\Html\HtmlDocument;
+use ipl\Html\HtmlElement;
+use ipl\Html\HtmlString;
+use ipl\Stdlib\Filter;
+use ipl\Web\Url;
+
+class IcingadbshowController extends IcingadbGrafanaController
+{
+    /** @var bool */
+    protected $showFullscreen;
+    protected $host;
+    protected $custvardisable = "grafana_graph_disable";
+    protected $config;
+    protected $object;
+
+    public function init()
+    {
+        $this->assertPermission('grafana/showall');
+        $this->view->showFullscreen
+            = $this->showFullscreen
+            = (bool)$this->_helper->layout()->showFullscreen;
+        $this->host = $this->getParam('host');
+        $this->config = Config::module('grafana')->getSection('grafana');
+        /**
+         * Name of the custom variable to disable graph
+         */
+        $this->custvardisable = ($this->config->get('custvardisable', $this->custvardisable));
+    }
+
+    public function indexAction()
+    {
+        $this->disableAutoRefresh();
+
+                /*
+        if (!$this->showFullscreen) {
+            $this->getTabs()->add(
+                'graphs',
+                [
+                    'label' => $this->translate('Grafana Graphs'),
+                    'url' => $this->getRequest()->getUrl()
+                ]
+            )->activate('graphs');
+
+            $this->getTabs()->extend(new PrintAction());
+        }
+                */
+
+        $this->addControl(
+            HtmlElement::create(
+                'h1',
+                null,
+                sprintf($this->translate('Performance graphs for %s'), $this->host)
+            )
+        );
+
+        // Preserve timerange if selected
+        $parameters = ['host' => $this->host];
+        if ($this->hasParam('timerange')) {
+            $parameters['timerange'] = $this->getParam('timerange');
+        }
+
+        /* The timerange menu */
+        $menu = new Timeranges($parameters, 'grafana/icingadbshow');
+        $this->addControl(new HtmlString($menu->getTimerangeMenu()));
+
+        /* first host object for host graph */
+        $this->object = $this->getHostObject($this->host);
+        $varsFlat = CustomvarFlat::on($this->getDb());
+        $this->applyRestrictions($varsFlat);
+
+        $varsFlat
+            ->columns(['flatname', 'flatvalue'])
+            ->orderBy('flatname');
+        $varsFlat->filter(Filter::equal('host.id', $this->object->id));
+        $customVars = $this->getDb()->fetchPairs($varsFlat->assembleSelect());
+        if ($this->object->perfdata_enabled == "y"
+            || !(isset($customVars[$this->custvardisable])
+                && json_decode(strtolower($customVars[$this->custvardisable])) !== false)
+        ) {
+            $object = (new HtmlDocument())
+                ->addHtml(HtmlElement::create('h2', null, $this->object->checkcommand_name));
+            $this->addContent($object);
+            $this->addContent((new HostDetailExtension())->getPreviewHtml($this->object, true));
+        }
+        /* Get all services for this host */
+        $query = Service::on($this->getDb())->with([
+            'state',
+            'icon_image',
+            'host',
+            'host.state'
+        ]);
+        $query->filter(Filter::equal('host.name', $this->host));
+
+        $this->applyRestrictions($query);
+
+        foreach ($query as $service) {
+            $this->object = $this->getServiceObject($service->name, $this->host);
+            $varsFlat = CustomvarFlat::on($this->getDb());
+            $this->applyRestrictions($varsFlat);
+
+            $varsFlat
+                ->columns(['flatname', 'flatvalue'])
+                ->orderBy('flatname');
+            $varsFlat->filter(Filter::equal('service.id', $service->id));
+            $customVars = $this->getDb()->fetchPairs($varsFlat->assembleSelect());
+            if ($this->object->perfdata_enabled == "y"
+                && !(isset($customVars[$this->custvardisable])
+                    && json_decode(strtolower($customVars[$this->custvardisable])) !== false)
+            ) {
+                $object = (new HtmlDocument())
+                    ->addHtml(HtmlElement::create('h2', null, $service->name));
+                $this->addContent($object);
+                $this->addContent((new ServiceDetailExtension())->getPreviewHtml($service, true));
+            }
+        }
+
+        unset($this->object);
+        unset($customVars);
+    }
+
+
+    public function getHostObject($host)
+    {
+        $query = Host::on($this->getDb())->with(['state', 'icon_image']);
+        $query->filter(Filter::equal('name', $host));
+        $this->applyRestrictions($query);
+        $host = $query->first();
+
+        if ($host === null) {
+            throw new NotFoundError(t('Host not found'));
+        }
+
+        return $host;
+    }
+
+    public function getServiceObject($service, $host)
+    {
+        $query = Service::on($this->getDb());
+
+        $query->filter(Filter::equal('name', $service));
+        $query->filter(Filter::equal('host.name', $host));
+        $this->applyRestrictions($query);
+
+        $service = $query->first();
+
+        if ($service === null) {
+            throw new NotFoundError(t('Service not found'));
+        }
+
+        return $service;
+    }
+}
diff --git a/application/forms/Config/GeneralConfigForm.php b/application/forms/Config/GeneralConfigForm.php
index 55db9a6..a57dd61 100644
--- a/application/forms/Config/GeneralConfigForm.php
+++ b/application/forms/Config/GeneralConfigForm.php
@@ -45,7 +45,7 @@ public function createElements(array $formData)
              )
         );
 
-        if (isset($formData['grafana_protocol']) && $formData['grafana_protocol'] === 'https' ) {
+        if (isset($formData['grafana_protocol']) && $formData['grafana_protocol'] === 'https') {
             $this->addElement(
                 'checkbox',
                 'grafana_ssl_verifypeer',
@@ -199,7 +199,7 @@ public function createElements(array $formData)
                     'class' => 'autosubmit'
                 )
             );
-            if (isset($formData['grafana_authentication']) && $formData['grafana_authentication'] === 'basic' ) {
+            if (isset($formData['grafana_authentication']) && $formData['grafana_authentication'] === 'basic') {
                     $this->addElement(
                         'text',
                         'grafana_username',
@@ -219,7 +219,7 @@ public function createElements(array $formData)
                             'required' => true
                         )
                     );
-            } elseif (isset($formData['grafana_authentication']) && $formData['grafana_authentication'] === 'token' ) {
+            } elseif (isset($formData['grafana_authentication']) && $formData['grafana_authentication'] === 'token') {
                 $this->addElement(
                     'text',
                     'grafana_apitoken',
@@ -332,4 +332,4 @@ public function createElements(array $formData)
             )
         );
     }
-}
\ No newline at end of file
+}
diff --git a/application/forms/Graph/GraphForm.php b/application/forms/Graph/GraphForm.php
index 9ec5f77..b34567d 100644
--- a/application/forms/Graph/GraphForm.php
+++ b/application/forms/Graph/GraphForm.php
@@ -1,6 +1,7 @@
 <?php
 
 namespace Icinga\Module\Grafana\Forms\Graph;
+
 //namespace Icinga\Module\Grafana\Forms\Config;
 
 use Icinga\Module\Grafana\Forms\Config\GeneralConfigForm;
@@ -29,7 +30,7 @@ class GraphForm extends ConfigForm
      */
     public function init()
     {
-       $this->setName('form_config_grafana_graph');
+        $this->setName('form_config_grafana_graph');
     }
 
     /**
@@ -151,7 +152,7 @@ public function createElements(array $formData)
                 'required'      => false
             )
         );
-}
+    }
 
     /**
      * {@inheritdoc}
@@ -197,11 +198,10 @@ public function onSuccess()
             'dashboarduid' => $this->getElement('dashboarduid')->getValue()
         );
 
-	    if (empty($values['timerange']))
-	    {
+        if (empty($values['timerange'])) {
             $values['timerange'] = null;
         }
-	    if (empty($values['customVars'])) {
+        if (empty($values['customVars'])) {
             $values['customVars'] = null;
         }
         if (empty($values['height'])) {
diff --git a/configuration.php b/configuration.php
index 4bcc392..ce1f40a 100644
--- a/configuration.php
+++ b/configuration.php
@@ -17,18 +17,17 @@
     'url' => 'config'
 ));
 
-if ($auth->hasPermission('grafana/graphconfig'))
-{
-   $section = $this->menuSection('Grafana Graphs')->setUrl('grafana/graph')->setPriority(999)->setIcon('chart-area');
+if ($auth->hasPermission('grafana/graphconfig')) {
+    $section = $this->menuSection('Grafana Graphs')->setUrl('grafana/graph')->setPriority(999)->setIcon('chart-area');
 
-   $section->add(N_('Graphs Configuration'))->setUrl('grafana/graph')->setPriority(30);
-   $section->add(N_('Module Configuration'))->setUrl('grafana/config')->setPriority(40);
+    $section->add(N_('Graphs Configuration'))->setUrl('grafana/graph')->setPriority(30);
+    $section->add(N_('Module Configuration'))->setUrl('grafana/config')->setPriority(40);
 
-   $this->provideConfigTab('graph', array(
+    $this->provideConfigTab('graph', array(
        'title' => 'Graphs',
        'label' => 'Graphs',
        'url' => 'graph'
-   ));
+    ));
 }
 
 $this->provideJsFile('behavior/iframe.js');
diff --git a/library/Grafana/Helpers/Timeranges.php b/library/Grafana/Helpers/Timeranges.php
index 8040f1e..2178b3a 100644
--- a/library/Grafana/Helpers/Timeranges.php
+++ b/library/Grafana/Helpers/Timeranges.php
@@ -1,143 +1,141 @@
-<?php
-/**
- * Created by PhpStorm.
- * User: carst
- * Date: 19.02.2018
- * Time: 19:05
- */
-
-namespace Icinga\Module\Grafana\Helpers;
-
-use Icinga\Application\Icinga;
-use Icinga\Application\Modules\Module;
-use Icinga\Module\Grafana\ProvidedHook\Icingadb\IcingadbSupport;
-
-
-class Timeranges
-{
-    private $urlparams;
-    private $link;
-    private $view;
-
-    static $timeRanges = array(
-        'Minutes' => array(
-            '5m' => '5 minutes',
-            '15m' => '15 minutes',
-            '30m' => '30 minutes',
-            '45m' => '45 minutes'
-        ),
-        'Hours' => array(
-            '1h' => '1 hour',
-            '3h' => '3 hours',
-            '6h' => '6 hours',
-            '8h' => '8 hours',
-            '12h' => '12 hours',
-            '24h' => '24 hours'
-        ),
-        'Days' => array(
-            '2d' => '2 days',
-            '7d' => '7 days',
-            '14d' => '14 days',
-            '30d' => '30 days',
-        ),
-        'Months' => array(
-            '2M' => '2 month',
-            '6M' => '6 months',
-            '9M' => '9 months'
-        ),
-        'Years' => array(
-            '1y' => '1 year',
-            '2y' => '2 years',
-            '3y' => '3 years'
-        ),
-        'Special' => array(
-            '1d/d' => 'Yesterday',
-            '2d/d' => 'Day b4 yesterday',
-            '1w/w' => 'Previous week',
-            '1M/M' => 'Previous month',
-            '1Y/Y' => 'Previous Year',
-        )
-    );
-
-    public function __construct(array $array = array(), $link = "")
-    {
-        $this->urlparams = $array;
-        $this->link = $link;
-
-        $this->view = Icinga::app()->getViewRenderer()->view;
-    }
-
-    private function getTimerangeLink($rangeName, $timeRange)
-    {
-        $this->urlparams['timerange'] = $timeRange;
-
-        return $this->view->qlink(
-            $rangeName,
-            $this->link,
-            $this->urlparams,
-            array(
-                'class' => 'action-link',
-                'data-base-target' => '_self',
-                'title' => 'Set timerange for graph(s) to ' . $rangeName
-            )
-        );
-    }
-
-
-    protected function isValidTimeStamp($timestamp)
-    {
-        return ((string) (int) $timestamp === $timestamp)
-            && ($timestamp <= PHP_INT_MAX)
-            && ($timestamp >= ~PHP_INT_MAX);
-    }
-
-    private function buildTimerangeMenu($timerange = "", $timerangeto = "")
-    {
-        $url = 'grafana/icingadbdashboard?';
-
-        $clockIcon = $this->view->qlink($timerange, 'dashboard/new-dashlet',
-            ['url' => $url . http_build_query($this->urlparams, '', '&', PHP_QUERY_RFC3986)],
-            ['icon' => 'clock', 'title' => 'Add graph to dashboard']);
-
-        $menu = '<table class="grafana-table"><tr>';
-        $menu .= '<td>' . $clockIcon . '</td>';
-        foreach (self::$timeRanges as $key => $mainValue) {
-            $menu .= '<td><ul class="grafana-menu-navigation"><a class="main" href="#">' . $key . '</a>';
-            $counter = 1;
-            foreach ($mainValue as $subkey => $value) {
-                $menu .= '<li class="grafana-menu-n' . $counter . '">' . $this->getTimerangeLink($value,
-                        $subkey) . '</li>';
-                $counter++;
-            }
-            $menu .= '</ul></td>';
-        }
-
-        $timerange = urldecode($timerange);
-        $timerangeto = urldecode($timerangeto);
-
-        if($this->isValidTimeStamp($timerange)) {
-            $d = new \DateTime();
-            $d->setTimestamp($timerange/1000);
-            $timerange = $d->format("Y-m-d H:i:s");
-        }
-
-        if($this->isValidTimeStamp($timerangeto)) {
-            $d = new \DateTime();
-            $d->setTimestamp($timerangeto/1000);
-            $timerangeto = $d->format("Y-m-d H:i:s");
-        }
-
-        $menu .= '</tr></table>';
-        return $menu;
-    }
-
-    public function getTimerangeMenu($timerange = "", $timerangeto = "")
-    {
-        return $this->buildTimerangeMenu($timerange, $timerangeto);
-    }
-
-    public static function getTimeranges()
-    {
-        return call_user_func_array('array_merge', array_values(self::$timeRanges));
-    }
-}
+<?php
+
+namespace Icinga\Module\Grafana\Helpers;
+
+use Icinga\Application\Icinga;
+use Icinga\Application\Modules\Module;
+use Icinga\Module\Grafana\ProvidedHook\Icingadb\IcingadbSupport;
+
+class Timeranges
+{
+    private $urlparams;
+    private $link;
+    private $view;
+
+    private static $timeRanges = array(
+        'Minutes' => array(
+            '5m' => '5 minutes',
+            '15m' => '15 minutes',
+            '30m' => '30 minutes',
+            '45m' => '45 minutes'
+        ),
+        'Hours' => array(
+            '1h' => '1 hour',
+            '3h' => '3 hours',
+            '6h' => '6 hours',
+            '8h' => '8 hours',
+            '12h' => '12 hours',
+            '24h' => '24 hours'
+        ),
+        'Days' => array(
+            '2d' => '2 days',
+            '7d' => '7 days',
+            '14d' => '14 days',
+            '30d' => '30 days',
+        ),
+        'Months' => array(
+            '2M' => '2 month',
+            '6M' => '6 months',
+            '9M' => '9 months'
+        ),
+        'Years' => array(
+            '1y' => '1 year',
+            '2y' => '2 years',
+            '3y' => '3 years'
+        ),
+        'Special' => array(
+            '1d/d' => 'Yesterday',
+            '2d/d' => 'Day b4 yesterday',
+            '1w/w' => 'Previous week',
+            '1M/M' => 'Previous month',
+            '1Y/Y' => 'Previous Year',
+        )
+    );
+
+    public function __construct(array $array = array(), $link = "")
+    {
+        $this->urlparams = $array;
+        $this->link = $link;
+
+        $this->view = Icinga::app()->getViewRenderer()->view;
+    }
+
+    private function getTimerangeLink($rangeName, $timeRange)
+    {
+        $this->urlparams['timerange'] = $timeRange;
+
+        return $this->view->qlink(
+            $rangeName,
+            $this->link,
+            $this->urlparams,
+            array(
+                'class' => 'action-link',
+                'data-base-target' => '_self',
+                'title' => 'Set timerange for graph(s) to ' . $rangeName
+            )
+        );
+    }
+
+
+    protected function isValidTimeStamp($timestamp)
+    {
+        return ((string) (int) $timestamp === $timestamp)
+            && ($timestamp <= PHP_INT_MAX)
+            && ($timestamp >= ~PHP_INT_MAX);
+    }
+
+    private function buildTimerangeMenu($timerange = "", $timerangeto = "")
+    {
+        $url = 'grafana/icingadbdashboard?';
+
+        $clockIcon = $this->view->qlink(
+            $timerange,
+            'dashboard/new-dashlet',
+            ['url' => $url . http_build_query($this->urlparams, '', '&', PHP_QUERY_RFC3986)],
+            ['icon' => 'clock', 'title' => 'Add graph to dashboard']
+        );
+
+        $menu = '<table class="grafana-table"><tr>';
+        $menu .= '<td>' . $clockIcon . '</td>';
+        foreach (self::$timeRanges as $key => $mainValue) {
+            $menu .= '<td><ul class="grafana-menu-navigation"><a class="main" href="#">' . $key . '</a>';
+            $counter = 1;
+            foreach ($mainValue as $subkey => $value) {
+                $menu .= '<li class="grafana-menu-n' . $counter . '">' . $this->getTimerangeLink(
+                    $value,
+                    $subkey
+                ) . '</li>';
+                $counter++;
+            }
+            $menu .= '</ul></td>';
+        }
+
+        $timerange = urldecode($timerange);
+        $timerangeto = urldecode($timerangeto);
+
+        if ($this->isValidTimeStamp($timerange)) {
+            $d = new \DateTime();
+            $d->setTimestamp($timerange/1000);
+            $timerange = $d->format("Y-m-d H:i:s");
+        }
+
+        if ($this->isValidTimeStamp($timerangeto)) {
+            $d = new \DateTime();
+            $d->setTimestamp($timerangeto/1000);
+            $timerangeto = $d->format("Y-m-d H:i:s");
+        }
+
+        $menu .= '</tr></table>';
+        return $menu;
+    }
+
+    public function getTimerangeMenu($timerange = "", $timerangeto = "")
+    {
+        return $this->buildTimerangeMenu($timerange, $timerangeto);
+    }
+
+    public static function getTimeranges()
+    {
+        return call_user_func_array('array_merge', array_values(self::$timeRanges));
+    }
+}
diff --git a/library/Grafana/Helpers/Util.php b/library/Grafana/Helpers/Util.php
index c3bdb1e..a11073e 100644
--- a/library/Grafana/Helpers/Util.php
+++ b/library/Grafana/Helpers/Util.php
@@ -56,7 +56,7 @@ public static function httpStatusCodetoString($code = 0)
         
         $code = (string)$code;
         
-        if(array_key_exists($code, $statuscodes)) {
+        if (array_key_exists($code, $statuscodes)) {
             return $statuscodes[$code];
         } else {
             return $code;
diff --git a/library/Grafana/ProvidedHook/Icingadb/HostDetailExtension.php b/library/Grafana/ProvidedHook/Icingadb/HostDetailExtension.php
index 85a0126..9edc5cf 100644
--- a/library/Grafana/ProvidedHook/Icingadb/HostDetailExtension.php
+++ b/library/Grafana/ProvidedHook/Icingadb/HostDetailExtension.php
@@ -17,7 +17,7 @@ public function getHtmlForObject(Host $host): ValidHtml
         $graphs = $this->getPreviewHtml($host);
 
         if (! empty($graphs)) {
-          return HtmlString::create($graphs);
+            return HtmlString::create($graphs);
         }
 
         return HtmlString::create('');
diff --git a/library/Grafana/ProvidedHook/Icingadb/IcingaDbGrapher.php b/library/Grafana/ProvidedHook/Icingadb/IcingaDbGrapher.php
index 118e0f4..68f7492 100644
--- a/library/Grafana/ProvidedHook/Icingadb/IcingaDbGrapher.php
+++ b/library/Grafana/ProvidedHook/Icingadb/IcingaDbGrapher.php
@@ -353,7 +353,7 @@ public function getPreviewHtml(Model $object, $report = false)
     {
         $this->object = $object;
         //$this->cacheTime = round($object->state->next_check - $object->state->last_update);
-				$this->cacheTime = 0;
+                $this->cacheTime = 0;
 
         if ($object instanceof Host) {
             $serviceName = $object->checkcommand_name;
@@ -390,7 +390,7 @@ public function getPreviewHtml(Model $object, $report = false)
 
         $customvars = $this->getDb()->fetchPairs($varsFlat->assembleSelect());
 
-        if ($object->perfdata_enabled == "n" || (( isset($customvars[$this->custvardisable]) && json_decode(strtolower($customvars[$this->custvardisable])) !== false)) ) {
+        if ($object->perfdata_enabled == "n" || (( isset($customvars[$this->custvardisable]) && json_decode(strtolower($customvars[$this->custvardisable])) !== false))) {
             return '';
         }
 
diff --git a/library/Grafana/ProvidedHook/Icingadb/ServiceDetailExtension.php b/library/Grafana/ProvidedHook/Icingadb/ServiceDetailExtension.php
index 00fa6ed..fd3f60f 100644
--- a/library/Grafana/ProvidedHook/Icingadb/ServiceDetailExtension.php
+++ b/library/Grafana/ProvidedHook/Icingadb/ServiceDetailExtension.php
@@ -17,10 +17,10 @@ public function getHtmlForObject(Service $service): ValidHtml
         //$this->object = $service;
         $graphs = $this->getPreviewHtml($service);
 
-				if (! empty($graphs)) {
-					return HtmlString::create($graphs);
+        if (! empty($graphs)) {
+            return HtmlString::create($graphs);
         }
 
-				return HtmlString::create('');
+                return HtmlString::create('');
     }
 }
diff --git a/phpcs.xml b/phpcs.xml
new file mode 100644
index 0000000..864b922
--- /dev/null
+++ b/phpcs.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<ruleset name="PHP_CodeSniffer">
+    <description>Sniff our code a while</description>
+
+    <file>configuration.php</file>
+    <!--<file>run.php</file>-->
+    <file>application/</file>
+    <file>library/</file>
+
+    <exclude-pattern>vendor/*</exclude-pattern>
+
+    <arg value="wps"/>
+    <arg name="colors" />
+    <arg name="report-width" value="auto" />
+    <arg name="report-full" />
+    <arg name="report-gitblame" />
+    <arg name="report-summary" />
+    <arg name="encoding" value="UTF-8" />
+
+    <!--
+    <rule ref="PEAR"/>
+    -->
+
+    <rule ref="PSR2">
+        <exclude name="PEAR.Commenting.FileComment.Missing"/>
+        <exclude name="PEAR.Commenting.ClassComment.Missing"/>
+        <exclude name="PEAR.Commenting.FunctionComment.Missing"/>
+    </rule>
+
+    <rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">
+        <exclude-pattern>*/application/views/helpers/*</exclude-pattern>
+    </rule>
+    <rule ref="Squiz.Classes.ValidClassName.NotCamelCaps">
+        <exclude-pattern>*/application/views/helpers/*</exclude-pattern>
+    </rule>
+
+    <rule ref="Generic.Files.LineLength">
+        <properties>
+            <property name="lineLimit" value="255"/>
+        </properties>
+    </rule>
+</ruleset>