Dateien schnell und elegant über das lokale Netzwerk teilen

Dateien via Docker mit Geräten im lokalen Netz teilen
Dateien via Docker mit Geräten im lokalen Netz teilen

Sicher kennt ihr das: Man sitzt mit dem Laptop bei Freund:in und/oder Kunden:in und möchte schnell mal ein paar Bilder oder einen Installer teilen. Bei kleinen Dateien mag eine E-Mail gerechtfertigt sein, aber schon bei wenigen MB dauert das gut und gern mal ewig, wenn der Mailserver nicht schon aus Prinzip bei größeren Dateien zickt. Diejenigen, die verstehen wo die Mail überall lang muss, können sicher nachvollziehen, dass das eine äußerst unelegante Lösung ist.

Alternativ bietet sich ein USB-Stick an. Aber wer hat schon immer einen Stick dabei? Und dann ist der auch noch so formatiert, dass er auf dem Zielsystem eventuell gar nicht gelesen werden kann…
Diese Lösungen sind eher so… neunziger… ;-)

Eleganter ist es einen Webserver am eigenen Rechner zu starten, der die Dateien über das lokale Netzwerk ausliefert!

Für so einen Pippifax einen ganzen Webserver installieren und konfigurieren? Wie viel schneller soll das Betriebssystem denn noch altern??

Ja, genau! Aber nicht nativ auf dem Laptop, sondern virtualisiert im Container. Heutzutage hat wahrscheinlich jeder gute IT-ler sowieso Docker oder ähnliches auf dem Laptop, und ein Image von einem Webserver ist in der Regel auch schon heruntergeladen (ansonsten lädt Docker das auch relativ fix bei der ersten Benutzung nach).
Damit lässt sich dann jedenfalls sehr schnell und einfach jede Datei teilen. Ich benutze beispielsweise gern den Nginx Webserver (das geht aber natürlich auch mit dem httpd von Apache, lighttpd oder anderen). Wenn ich die Datei /path/to/file.zip teilen will, mach’ ich das einfach so:

docker run --rm -v /path/to/file.zip:/usr/share/nginx/html/file.zip -p 80:80 nginx

Thada..! Jetzt kann file.zip vom Laptop als http://IP_DES_LAPTOPS/file.zip heruntergeladen werden. Falls der Laptop im Netzwerk einen Namen bekommen hat geht es natürlich noch komfortabler.

Das funktioniert wirklich erstaunlich schnell und zuverlässig in fast jedem Netzwerk. Wesentlich schneller jedenfalls , als wenn man es zu seinem Mailserver schickt, der das dann zum nächsten Mailserver weiterleitet, etc…
Einzige Voraussetzung ist, dass sich die Rechner im Netzwerk sehen können (einige wenige Netze haben das aus Sicherheitsgründen ausgeschaltet). Es werden auch ausschließlich Dateien geteilt, die in den Container gemountet werden. Alle anderen Dateien auf dem Laptop sieht der Webserver gar nicht!

Am Ende dann nicht vergessen den Container wieder zu stoppen, damit man nicht versehentlich im nächsten Netzwerk weiter “teilt” und/oder teilende Webserver akkumuliert ;-)
Dank des --rm ist danach alles wieder sauber aufgeräumt. Das System bleibt jung!

Mehr Alternativen

  • Wenn beide Personen das gleiche Chat-System benutzen, kann man Dateien auch schnell per Matrix oder Threema oder so übertragen.
  • Wer eine Cloud hat (Nextcloud, Dropbox, Onedrive, Google Drive, Mega, etc), kann Dateien natürlich auch darüber komfortabel teilen. Es gibt auch eine Reihe von Anbietern im Netz, bei denen man Dateien ohne Anmeldung hochladen und teilen kann.

Diese Ansätze bedeuten jedoch, dass die Dateien die lokale Infrastruktur verlassen müssen. Das ist hinsichtlich Sicherheit und Privatsphäre eher nicht so schön und dauert tendenziell länger.
Die Lösung mit dem lokalen Webserver im Docker-Container wäre sicher zu bevorzugen.

Eigene Bilder in OpenStreetMap anzeigen

Die Kunsthalle Rostock hat nach einer Möglichkeit gesucht, verschiedene Open-Air Werke digital auf einer Webseite zu präsentieren. Gefragt getan… Mit OpenStreetMap geht das total super!

Hier ist ein Beispiel, bei dem ich das Logo von CODE AHOI in den Rostocker Stadthafen projiziert habe:

Dies ist ein Platzhalter

Um die Karte anzeigen zu können muss dein Browser Inhalte von OpenStreetMap nachladen. Die Datenschutzerklärung von OpenStreetMap findest du hier: wiki.osmfoundation.org/wiki/Privacy_Policy

Ist das nicht cool :)

Selber Machen!

Mit dem OpenLayers-Projekt ist das vergleichsweise einfach. Zunächst ist ein bisschen HTML-Grundgerüst notwendig: Wir …

Ein Template könnte etwa wie folgt aussehen:

<body>

  <div id="map" style="width:100%; height: 50vh">
    <!-- hier wird die Karte angezeigt -->
  </div>
  
  <div id="popup" class="ol-popup">
    <a href="#" id="popup-closer" class="ol-popup-closer">
      <!-- kleines x um das Pop-Up zu schließen -->
    </a>
    <div id="popup-content">
      <!-- Inhalte im Pop-Up -->
    </div>
  </div>

  <!-- Bibliotheken des OpenLayers' Projekt herunterladen -->
  <link rel="stylesheet" href="/path/to/openlayers/ol.css">
  <script src="/path/to/openlayers/ol.js"></script>
  
</body>

Ein Icon-Feature pro Icon

Jetzt können wir mit der Magie beginnen. Für jedes Icon, dass in die Map soll, muss ein Icon-Feature erstellt werden. Für das Beispiel oben sieht das ungefähr so aus:

// erstelle ein Feature, dass später als Icon angezeigt wird
const iconFeature = new ol.Feature({
  geometry: new ol.geom.Point(ol.proj.fromLonLat([12.1110,54.0966])),
  info: {
    headline: "OpenStreetMap AHOI",
    body: "Das CODE AHOI Papierschiffchen schwimmt im Rostocker Stadthafen. [...]"
  },
});

// style das Feature: hier wird ein Icon-Bild gesetzt
iconFeature.setStyle(new ol.style.Style({
  image: new ol.style.Icon({
    src: "https://codeahoi.de/s/articles/2020/openlayers-showcase.png"
  })
}));

// alles zu einem Layer zusammenbauen
var vectorLayer = new ol.layer.Vector({
  source: new ol.source.Vector ({
    features: [iconFeature]
  })
});

Im ersten Block werden ein paar Eigenschaften für das Icon gesetzt: Wo soll das Icon angezeigt werden? Welche Überschrift und welcher Text soll im Pop-up angezeigt werden? Der zweite Block definiert dann, wie das Icon aussehen soll. Hier laden wir ein Bild vom CODE AHOI Webserver, dass dann in der Karte angezeigt werden soll.. Im dritten Block erzeugen wir eine neue Vektor-Ebene, die später über die Karte gelegt werden soll – die Spezialität von OpenLayers :)
Das sieht in diesem Fall vielleicht ein bisschen kompliziert aus für ein einzelnes Logo, lässt sich bei vielen Icons aber gut wegautomatisieren.

Karte laden

Im nächsten Schritt können wir die Karte laden. Als unterste Ebene wollen wir die Kacheln (Tiles) von OpenStreetMap nehmen, auf die wir dann unsere neue Ebene mit dem Icon legen. Die Map soll dann auf Level 15 gezoomt und im Rostocker Stadthafen zentriert werden. Koordinaten und Zoomlevel kann man aus der Karte auf openstreetmap.org entnehmen. Wenn, wie in diesem Fall, die Koordinaten des Icons mit dem Zentrum der Karte übereinstimmen, wird das Icon zentriert in der Karte angezeigt ;-)

var map = new ol.Map({
  // die karte soll im container mit der ID 'map' angezeigt werden
  target: 'map',
  
  // die ebenen, die in der Karte angezeigt werden sollen
  layers: [
    new ol.layer.Tile({
      // erstes Layer sind die Tiles von OpenStreetMap
      source: new ol.source.OSM()
    }),
    // zweites Layer wird unser Icon Layer
    vectorLayer
  ],
  
  view: new ol.View({
    // zentriere initial auf die Koordinaten [12.1110,54.0966] (Stadthafen Rostock)
    center: ol.proj.fromLonLat([12.1110,54.0966]),
    // und zoome auf level 15
    zoom: 15
  })
});

Die Karte wird jetzt ordentlich geladen und das Icon wird an der richtigen Stelle angezeigt.

Pop-Up implementieren

Damit sind wir also fast fertig, aber wir wollen ja noch ein kleines Pop-Up anzeigen, wenn ein Nutzer auf das Icon klickt! Dafür müssen wir natürlich auch noch ein bisschen Code schreiben:

var container = document.getElementById('popup');
var content = document.getElementById('popup-content');
var closer = document.getElementById('popup-closer');


// Das Pop-Up wird eine neue Ebene in OpenLayers
var popup = new ol.Overlay({
  element: container,
  autoPan: true,
  autoPanAnimation: {
    duration: 250
  },
  positioning: 'bottom-center',
  stopEvent: false,
  offset: [0, -25]
});
map.addOverlay(popup);


// Event-Listener für den Klick auf das x
closer.onclick = function() {
  popup.setPosition(undefined);
  return false;
};


// Event-Listener für einen Klick in die Karte: wird eines unserer Icons angeklickt -> Pop-Up anzeigen
map.on('click', function(evt) {
  // wurde eins unserer Features angeklickt?
  var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) { return feature; });
  // wenn ja, fülle das Pop-Up mit den Informationen aus dem Feature
  if (feature) {
    // wo soll das Pop-Up angezeigt werden?
    var coordinates = feature.getGeometry().getCoordinates();
    // was soll in dem Pop-Up stehen?
    content.innerHTML = '<h2>' + feature["values_"]["info"]["headline"] + '</h2><p>' + feature["values_"]["info"]["body"] + "</p>";
    
    // Pop-Up an der richtigen stelle anzeigen
    popup.setPosition(coordinates);
  } else {
    // es wurde etwas anderes angeklickt -> Pop-Up schließen
    popup.setPosition(undefined);
  }
});

Das Pop-Up selbst können wir mit OpenLayers direkt als weitere Ebene (siehe zweiten Block) in der Karte realisieren – damit fühlt sich das Pop-Up nicht so aufdringlich an. Die nötigen Informationen für das Pop-Up haben wir oben direkt an unser Icon-Feature gespeichert. Diese können wir jetzt auslesen und anzeigen, wenn ein Nutzer das Icon anklickt (siehe letzter Block).

Styling

Die Logik ist fertig. Aber für das Makeup brauchen wir jetzt noch ein ordentliches Finishing ;-)
Das Styling ist natürlich sehr vom Layout drumherum und vielen subjektiven Faktoren abhängig. Generell könnte man aber ein Schließen-Symbol in dem popup-closer Container anzeigen und die Pop-Up-Blase ein bisschen anpassen. Hier ist ein Beispiel:

/* x zum Schließen */
.ol-popup-closer {
  text-decoration: none;
  position: absolute;
  top: 2px;
  right: 8px;
}
.ol-popup-closer:after {
  content: "✖";
}

/* Pop-Up-Blase mit kleinem Dreieck, das auf das Icon zeigt */
.ol-popup {
  position: absolute;
  background-color: white;
  box-shadow: 0 1px 4px rgba(0,0,0,0.2);
  padding: 15px;
  border-radius: 10px;
  border: 1px solid #cccccc;
  bottom: 12px;
  left: -50px;
  min-width: 280px;
  display: none;
}
.ol-popup:after, .ol-popup:before {
  top: 100%;
  border: solid transparent;
  content: " ";
  height: 0;
  width: 0;
  position: absolute;
  pointer-events: none;
}
.ol-popup:after {
  border-top-color: white;
  border-width: 10px;
  left: 48px;
  margin-left: -10px;
}
.ol-popup:before {
  border-top-color: #cccccc;
  border-width: 11px;
  left: 48px;
  margin-left: -11px;
}

Komplette Lösung

Meinen Code für das Beispiel oben gibt es unverschleiert zum Download:

Das HTML-Gerüst ist mit dem Artikel drumherum in dem Fall ein bisschen komplexer, aber das könnt ihr euch ja selbstständig im Quellcode ansehen :)