diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e064608675c5073b6bdf5365f4581b5fbda76bd7..d4e9d67018670241199fb786936b9066f1ad389d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,7 +2,7 @@ image: docker:19.03.12
 
 variables:
   DOCKER_TLS_CERTDIR: "/certs"
-  IMAGE_VER: 1.7.0
+  IMAGE_VER: 1.8.0
 
 services:
   - docker:19.03.12-dind
diff --git a/lib/PiTube.pm b/lib/PiTube.pm
index 112392da36d4d7001d04aa7bd9d691b42fc3c204..2642188e7ee216a6b6108e17104f5775068805be 100644
--- a/lib/PiTube.pm
+++ b/lib/PiTube.pm
@@ -79,6 +79,9 @@ sub startup {
     $r->get('/streams/:id')->to('Stream#info');
     $r->put('/streams/:id/recording')->to('Stream#recording');
 
+    $r->get('/calendar')->to(cb => sub { shift->render('calendar'); });
+    $r->get('/api/calendar')->to('Calendar#list');
+
     $r->get('/archive')->to(cb => sub { shift->render('archive'); });
     $r->get('/api/records')->to('Record#list');
     $r->delete('/api/records/:id')->to('Record#delete');
diff --git a/lib/PiTube/Controller/Calendar.pm b/lib/PiTube/Controller/Calendar.pm
new file mode 100644
index 0000000000000000000000000000000000000000..4c1658973dd1e670ed8f3172bb0eb71a2ab69062
--- /dev/null
+++ b/lib/PiTube/Controller/Calendar.pm
@@ -0,0 +1,79 @@
+package PiTube::Controller::Calendar;
+
+use feature 'signatures';
+no warnings qw{ experimental::signatures };
+use Mojo::Base 'Mojolicious::Controller';
+use Mojo::UserAgent;
+
+sub list {
+    my $c = shift;
+
+    my $ua = Mojo::UserAgent->new;
+
+    my $res;
+
+    eval { $res = $ua->get( $c->config->{calendar}{iapi} )->result; };
+
+    # TODO: OpenAPI error
+    if ( $@ || ! $res->is_success ) {
+
+        $c->app->log->error( $@ ? $@ : $res->message);
+
+        $c->render( json => {
+            total => 0,
+            rows  => [],
+        });
+        return;
+    }
+
+    my @calendar;
+    my @days = sort { $a->{date} cmp $b->{date} } @{ $res->json->{days} };
+
+    DAY:
+    foreach my $day ( @days ) {
+        my @events = sort { $a->{start} cmp $b->{start} } @{ $day->{events} };
+
+        EVENT:
+        foreach my $event ( @events ) {
+
+            # cistka nejhakeho bordelu z iCal. asi radsi v iAPI
+            $event->{summary} =~ s/\\\W/$1/g;
+
+            my $item = {
+                date        => $day->{date_cz},
+                time        => $event->{start} . '-' . $event->{end},
+                description => $event->{summary},
+            };
+
+            if (
+                $event->{summary} =~ s/^\s*stream\s+(\w+)\W+//i
+                ||
+                $event->{summary} =~ s/^\s*(\w+)\W+//i
+            ) {
+                my $key = lc($1);
+
+                my $stream = $c->schema->resultset('Stream')->search({
+                    key => $key
+                })->first;
+
+                if ( $stream ) {
+                    $item->{description} = ucfirst($event->{summary});
+                    $item->{stream_name} = $stream->name;
+                    if ( $stream->is_live ) {
+                        $item->{stream_key}  = $stream->key;
+                    }
+                }
+            }
+
+            push @calendar, $item;
+        }
+    }
+
+    $c->render( json => {
+        total => scalar @calendar,
+        rows  => \@calendar,
+    });
+
+};
+
+1;
diff --git a/pi_tube.conf b/pi_tube.conf
index 967bc747b3baca1769445d339d03ac92f569d54a..99eb08d5d26eff85501e31e40642ff777d0d31e9 100644
--- a/pi_tube.conf
+++ b/pi_tube.conf
@@ -36,6 +36,10 @@
   nginx => {
     base_url => $ENV{NGINX_BASE_URL} // 'http://nginx-rtmp',
   },
+  calendar => {
+    url  => $ENV{CALENDAR_URL},
+    iapi => $ENV{CALENDAR_IAPI},
+  },
   redis => {
     server => $ENV{REDIS_SERVER},
   },
diff --git a/templates/calendar.html.ep b/templates/calendar.html.ep
new file mode 100644
index 0000000000000000000000000000000000000000..8f423f7dc99330076665e2872d7095cd19814260
--- /dev/null
+++ b/templates/calendar.html.ep
@@ -0,0 +1,31 @@
+% layout 'default';
+% title 'Kalendar vysílání';
+
+<div class="container pt-2 lg:pb-2 ">
+<table id="Calendar"
+    class="table table--bordered"
+    data-url="/api/calendar">
+  <thead>
+    <tr>
+      <th data-field="date" data-width="14" data-width-unit="em">Datum</th>
+      <th data-field="time" data-width="4" data-width-unit="em">Čas</th>
+      <th data-field="stream_name" data-width="12" data-width-unit="em" data-formatter="formatterStream">Stream</th>
+      <th data-field="description">Popis</th>
+    </tr>
+  </thead>
+</table>
+</div>
+
+<script>
+$('#Calendar').bootstrapTable({});
+
+function formatterStream(value, row) {
+    if ( row.stream_key ) {
+        return '<a href="/play/'+row.stream_key+'">'+value+'</a>';
+    }
+    else {
+        return value;
+    }
+}
+</script>
+
diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep
index 5b7395a68c4f575b2081813e6509286b470d9bfb..675b2398fba3a56d2dbd2643d1175672235d5bf7 100644
--- a/templates/layouts/default.html.ep
+++ b/templates/layouts/default.html.ep
@@ -55,6 +55,7 @@
 % if ( $c->is_user_authenticated ) {
                   <li class="navbar-menu__item"><a href="/streams" data-href="/streams" class="navbar-menu__link">Streamy</a></li>
 % }
+                  <li class="navbar-menu__item"><a href="/calendar" data-href="/calendar" class="navbar-menu__link">Kalendář</a></li>
 %#                <li class="navbar-menu__item"><a href="/help" data-href="/help" class="navbar-menu__link">Nápověda</a></li>
                   <li class="navbar-menu__item">
 % if ( $c->is_user_authenticated ) {