Zur Höhe von Listen bei gefloatetem Inhalt

Wegen einer Nachfrage hier noch ein Kommentar zur Höhenberechnung von dynamisch (!) generierten Listen, in denen die Höhe der Listenelemente und ihrer Inhalte (also irgendwelcher Tags innerhalb der <li>-Tags) nicht vorab bekannt sind und variieren können. Dies betrifft indirekt auch den letzten Beitrag in dieser Blog-Kategorie zur Generierung dynamischer Listen aus Datenbank-Sätzen. Dort hatten wir beschrieben, wie man mit Listen und dem Floaten des Inhalts formatierten Output für Datenbank-Abfragen erzeugen kann.

Eine Kollegin hat nun auf Basis des Beitrags ausprobiert, eine solche Liste unbekannter Höhe zu erstellen, bei der innerhalb der <li> mehrere Elemente (u.a. <div>-Elemente) mit “float:left;” positioniert worden waren. Als sie sich Rahmen um die <li> und das <ul>-Tag zeichnen ließ, war sie sehr erschrocken darüber, dass im Firefox der Rahmen des <ul>-Elements auf einen Strich zusammengezogen erschien. Die Höhe des <ul>-Block-Elements erschien also völlig falsch berechnet.

Komischerweise war das Verhalten im MS IE 7 ganz anders: Hier erschien die Border des <ul>-Tags richtig gezeichnet – also die Höhe des <ul>-Elementes aus den Inhalten der <LI>-Tags richtig berechnet. Warum diese große Diskrepanz zwischen den Browsern? Hat Firefox hier eine Macke? Eine Analyse des Codes zeigt dann dagegen, dass sich der MS IE eigentlich falsch verhielt und Firefox im Wesentlichen richtig!

Ein kurzer Blick auf das <li>-Elemente und die zugehörigen CSS-Anweisungen zeigte (verkürzt) Folgendes:

<ul style="width:30.0em;………border: …… ">
    <li style="width:30.0em; clear:left; float:left; border: ….. ; line-height:1.8em; ….">
        <div style="float:left; ….&quot>graphisches Element</div>
        <div style="float:left; ….&quot><p>mehrzeiliger Text unbekannter Höhe</p></div>
    </li>
    <li style="width:30.0em; clear:left; float:left; border: ….. ; line-height:1.8em; ….">
    ……
    ……
</ul>

Die Listenzeilen wurden schön untereinander dargestellt; leider z.T. mit etwas falschen Abständen, sobald mehrzeilige Elemente auftauchten.

Der erste grundsätzliche Fehler liegt hier zunächst einmal in dem überflüssigen und falschen “float:left” für das <li>-Tag. Dass die Listenelemente überhaupt untereinander dargestellt werden, liegt hier nur an der Breitenbegrenzung des <ul>-Elementes!

Eine Elimination der “float”-Anweisung aus dem <li>-Tag ergab denn schon mal als ersten Fortschritt einen vertikal ausgedehnten Bereich für das <ul>-Element.

Leider hatte das <ul>-Element aber immer noch eine falsche Höhe und auch die Abstände zwischen den <li>-Elementen waren immer noch nicht gleichmäßig. Es hatte also den Anschein, dass die Höhe der <li>-Elemente und damit auch die resultierende Summe für das <ul>-Element nach wie vor falsch berechnet wurden – nämlich so, als ob nur die “line-height” relevant wäre.

Hier kommt nun der interessante Punkt:

Eine “float”-Anweisung hebt den gefloateten Bereich aus dem normalen Inhaltsverlauf (Kontext) des umgebenden Elementes heraus ! Man kann das näherungsweise mit dem Verhalten von absolut positionierten Elementen (z.B. DIVs) in ihrem Parent-Tag (z.B. einem mit “position:relative;” positionierten Container-DIV) vergleichen. Legt man für das umgebende Container DIV eine Höhe und Breite fest, so wird diese im Firefox exakt beachtet; die inneren DIVs ragen aber je nach Position und Größe völlig aus dem umgebenden Bereich heraus. Gleiches gilt für den Inhalt von <p>-Tags: Ist dieser zu groß
und ein Text-Umbruch nicht möglich, so ragt der Text im Firefox aus dem umgebenden Block-Element (DIV) heraus. Dies ist vollkommen CSS-konform. (Der alte MS IE 6 verhält sich hier bei P-Tags noch ganz anders – nämlich so, dass er das umgebende DIV-Tag mit dem<P>-Tag ausdehnt.)

Wo liegt also im obigen Beispiel der Fehler? Warum wird die Höhe der <li>-Elemente nicht richtig berechnet? Antwort: Weil die float-Anweisung nicht innerhalb des <li>-Tags wieder aufgehoben wird!

Das Hinzufügen eines Dummy-Elements zum “Clearen” der float-Anweisung innerhalb des <li>-Tags genügt hierfür! Im obigen Beispiel bringt ein zusätzliches <p style="clear:left; ….">-Tag im <li>-Element alles ins Lot:

<ul style="width:30.0em;………border: …… ">
    <li style="width:30.0em; border: ….. ; line-height:1.8em; ….">
        <div style="float:left; ….&quot>graphisches Element</div>
        <div style="float:left; ….&quot><p>mehrzeiliger Text unbekannter Höhe</p></div>
        <p style="clear:left; font-size:1px; line-height:1px; margin:0; "> </p>
    </li>
    ……
    ……
  </ul>

Natürlich wird dann auch die “clear”-Anweisung für das <li>-Element selbst überflüssig!

Merke also: Eine korrekte Höhenberechnungen von Elementen, die gefloateten Inhalt umschließen, setzt ein Aufheben der Float-Anweisung durch ein abschließendes (Dummy-) Element voraus.

Das gilt natürlich auch und vor allem für die im letzten Beitrag dieser Kategorie diskutierten dynamisch generierten Listen über PHP IT- oder ITX-Templates !

“NIC-Names” unter SuSE

Bei der Vorbereitung auf ein LPI-Zertifikat lernt man den Umgang mit den Befehlen “ifconfig”, “ip” und “iwconfig” in umfassender Weise. Dabei beziehen sich die Befehle allerdings auf vorhandene Interface-Namen, die dem System bereits bekannt sind. Ifconfig kann u.a. die Zuordnung einer IP-Adresse zu einem definierten und benannten Netzwerk-Interface vornehmen. Diese Zuordnung überdauert aber einen Neustart des Systems jedoch nicht.

Trotz Yast kann ein SuSE-Anwender öfter als ihm lieb ist, in eine Situation geraten, in der die Beherrschung von ifconfig nicht mehr genügt und er gerne auch die Antwort auf folgende Fragen wissen möchte:

Wo wird eigentlich unter SuSE der Name eines Netzwerk-Interfaces dauerhaft hinterlegt? Wie wird eine erkannte NIC eigentlich dem jeweiligen Interface-Namen zugeordnet? Wo steht das? Wo und wie hinterlegt SuSE die Netzwerkkonfigurationsdaten dauerhaft zu einem Interface-Namen? Woher erhält das System z.B. beim Startup die Information, dass es zwei Interfaces eth0 und eth1 gibt, de konfiguriert werden müssen? Genau diese Fragen wurden mir heute gestellt – und LPI-Bücher helfen hier nicht wirklich weiter.

Ich versuche nachfolgend zu zeigen, wie man die zugehörigen Konfigurationsdateien und Informationen in einem SuSE-System durch ein wenig Nachdenken und systematisches Stöbern im System finden kann. Bei aktuellen SuSE-Systemen gelangt man dabei unweigerlich zum Verzeichnis “/sys”. Wir gehen daher kurz auch auf seine grundlegende Bedeutung für Kernel 2.6-Systeme ein.

Die Suche nach der Nadel im Heuhaufen

Angesichts der Vielzahl von Konfigurationsdateien, Startup-Skripts und SuSE- bzw. Yast-spezifischen Konfigurationsdateien kann man auch als erfahrener Linux-Anwender bei der Suche nach spezifischen Informationen zu Netzwerkkarten und deren Interface-Namen fast verzweifeln, wenn einem das Internet mal nicht als zusätzliche Informationsquelle zur Verfügung steht.

Um einen Startpunkt zu bekommen, könnte man als erstes geneigt sein, einmal unter “/proc/sys/ipv4/” und genauer unter “/proc/sys/ipv4/conf” nachzusehen. Der Kernel muss ja zumindest wissen, welche Interfaces er im Griff hat. Das ist insofern ein guter Ansatz, als man dort schnell einen Überblick über die aktiven Karten erhält. Übrigens auch über die (virtuellen) Host-Interfaces eines gestarteten VMware-Prozesses, wobei das Linux-Systems als VMware-Host dient:

Netzinterfaces_2

Also irgendwie sind auch dem Kernel die für die Netzwerkkarten vergebenen Namen bekannt. Als SUSE-Anwender weiß man aber auch, dass man die Bezeichnung “eth0” eines Interfaces bereits bei der Einrichtung der Netzwerkkarte über Yast vergeben hat – oftmals schon während der Installation. Zudem bleiben die diesem Namen zugeordnete Konfigurationsvorgaben (z.B. zu DHCP oder einer statischen IP-Adresse) dauerhaft im System erhalten.

Wie gesagt: Der Befehl “ifconfig” kann den Netzwerkinterfaces Konfigurationswerte nur momentan, nicht aber über einen Reboot hinaus zuordnen. Mit ifconfig vergebene Werte überleben einen Neustart des Systems oder des Netzwerkes nicht. (Das kann man leicht ausprobieren!).

Also hegt man die Vermutung, dass es wohl Dateien geben wird, in denen Yast die benötigten Informationen persistent hinterlegt. Diese Informationen müssen dann zwangsläufig bei jedem Systemstart ausgewertet werden und auch dem Kernel verfügbar sein. Um herauszubekommen, woher das System beim Startup Informationen zur Netzwerkkonfiguration und zu Interfaces bezieht, werfen wir deshalb einen Blick in das entsprechende Startup-Skript von SuSE für das Netzwerk.

Dieses Skript finden wir – wie erwartet – unter “/etc/init.d/” als Datei “/etc/init.d/network”. Nach etwas Suchen im Skript stößt man recht bald auf eine Zeile in der das
Verzeichnis

/etc/sysconfig/network

aufgesucht wird.

Das Verzeichnis /etc/sysconfig/net

Bei SuSE werden grundlegende Einstellungen zum System im Verzeichnis “/etc/sysconfig” verankert. Tatsächlich gibt es dort denn auch ein Unterverzeichnis “/etc/sysconfig/network” sowie eine Konfigurations-Datei “/etc/sysconfig/network/config”. Unter SuSE’s Yast gibt es zudem ein Werkzeug – den “/etc/sysconfig”-Editor – mit dem man sich die Einträge unter /etc/sysconfig” ansehen und ggf. auch editieren kann. Die Parameter der Datei “/etc/sysconfig/network/config” erhält man im “/etc/sysconfig”-Editor z.B. unter dem Punkt “Network->General”.

Klickt man sich im “Sysconfig-Editor” weiter durch die Hauptpunkte durch, so gelangt man unter “Hardware->Network” auch zu Unterpunkten für die verschiedenen, namentlich definierten Netzwerk-Interfaces (z.B. eth0, eth1,…). U.a. sind hier die zuzuordnenden IP-Adressen hinterlegt. Weitere im “Sysconfig-Editor” definierte bzw. definierbare Parameter sind z.B.:

BOOTPROTO, BROADCAST, ETHTOOL_OPTIONS, IPADDR, MTU, NAME, NETWORK, REMOTE_IPADDR, STARTMODE, USERCONTROL.

Woher nimmt der “Sysconfig-Editor” diese Informationen? Ein kontrollierender Blick in das Verzeichnis “/etc/sysconfig/network” zeigt dann, dass die Vorgaben für das Interface eth0 (bei der Einrichtung der Netzwerkkarte mit Yast2->Netzwerkgeräte->Netzwerkkarte) in die Datei

/etc/sysconfig/network/ifcfg_eth0

geschrieben wurden. Entsprechende Konfigurationsdateien gibt es unter “/etc/sysconfig/network/” natürlich auch für andere NIC-Interfaces.

Aha, über diese Dateien oder über den “Sysconfig”-Editor kann man also unter SuSE IP-Adressen und Broadcast-Adressen für ein Interface (dauerhaft) ändern. Wenn man also im laufenden Betrieb nicht zu “ifconfig” greifen will oder aber Änderungen für ein Interface dauerhaft hinterlegen muss und die erneute Kartenkonfiguration mit “Yast->Netzwerkgeräte->Netzwerkkarten” scheut, so führt auch der Umweg über das Editieren der besagten Dateien zum Ziel. Im laufenden Betrieb muss man aber nach den Datei-Änderungen (über den “/etc/sysconfig”-Editor) den Befehl “/etc/init.d/rcnetwork restart” aufrufen, um die Änderungen konkret auf die vorhandenen NICs anzuwenden.

Die Startup-Skripts für das Aufsetzen des Netzwerkes greifen also auf die Interface-spezifischen Dateien vom Typ “/etc/sysconfig/network/ifcfg_ethx” zurück. Natürlich macht auch dieses Startup-Skript sich bei der Konfiguration des jeweilige Interfaces intern die Möglichkeiten von ifconfig(oder ip) zu Nutze. (Dies erkennt man, wenn man die Logik des Startup-Slripts weiter verfolgt.)

Nun führen aber Konfigurationsdateien wie “/etc/sysconfig/network/ifcfg_eth0” die Bezeichnung des Netzwerkinterfaces bereits im Namen! Die Datei wurde bei der Konfiguration der NIC erzeugt. Zu diesem Zeitpunkt war natürlich klar, um welche Hardware es sich handelt. Wie weiß das System aber im Nachhinein, welche Interface-Bezeichnung welcher NIC (welchem physikalischen Gerät) zuzuordnen ist? Wo wird also festgeschrieben, welche Netzwerk-Geräte und -Interfaces es im System gibt und welche Namen diesen Interfaces zugeordnet wurden ?

Wir werfen erneut einen Blick in die Startup-Datei “/etc/init.d/rcnetwork restart”. Nach weiterem Scrollen werden wir fündig (Opensuse 10.X):

Zum Durcharbeiten aller Netzwerkinterfaces wird (falls der “Networkmanager” nicht benutzt wird), die Variable “AVAILABLE_IFACES” herangezogen. Ein wenig weitere Forschungsarbeit zeigt schließlich, dass die Variable “AVAILABLE_IFACES” nach Durchlaufen eines Analyse-Loops über den Inhalt des Verzeichnisses

/sys/class/net/

festgelegt wird.

Von /sys/class/net zum sysFS

Tatsächlich zeigt ein Blick in das Verzeichnis “/sys/class/net/” auf einem meiner Systeme auszugsweise Folgendes:

Netz_interfaces_3

Also: Unsere ursprüngliche Frage, wo im System Informationen zu NIC-Interface-Namen hinterlegt sind, haben wir damit beantwortet: Unter “sys/class/net”.

Wir lernen bei der Gelegenheit jedoch noch ein wenig mehr:

Bei genauerem Hinsehen stellen wir fest, dass es sich bei den Dateien unter “/sys/class/net” um Links handelt, die wiederum auf Device-Dateien unter “/sys” (hier genauer: /sys/pci0000:00) verweisen. Wir erkennen nach einigem Studium ferner, dass sich auch Links aus dem Verzeichnis “/sys/bus/pci” auf die Device-Dateien beziehen. Aha, so langsam wird uns klar, dass das “/sys”-Verzeichnis offenbar essentielle und strukturierte Informationen zu Devices – und nicht nur zu Netzwerkdevices – enthält. Es scheint so, als würden Interface-Klassen separat von den Geräten aufgeführt, aber doch einander zugeordnet.

Ein Blick in “/etc/fstab” zeigt übrigens, dass “sysfs” auf “/sys” gemountet wird. Es handelt sich also um ein regelrechtes Filesystem. Wem nützt sowas? Natürlich u.a. dem Kernel (ab Version 2.6). Es handelt sich um das sog. “sysFS”-System, mit dem Geräte und deren Interfaces organisiert werden. Die sysFS-Organisation wird in den Startup-Skripts von SUSE (ab Vers. 9.1) explizit verwendet.

Generell sind die Unterverzeichnisse im sysFS die Verzeichnisse “/sys/devices/”, “/sys/bus”, “/sys/class” und “/sys/block” wesentlich. Die Verzeichnisse “/sys/devices” und “/sys/bus” entsprechen zwei verschiedenen Sichten auf die im System vorhandene Hardwaregeräte.

“/sys/class” und “/sys/block” enthalten alle “Schnittstellen” (Interfaces) zu konkreten Geräten. Nur diese definierten Interfaces können die Anwendungen des jeweiligen Systems ansprechen. es mag befremdlich wirken, aber “/dev/sdb” ist in diesem Sinne z.B. ein Interface zu einem Speichergerät. Von einem Eintrag in den Schnittstellenunterverzeichnissen “/sys/class” oder “/sys/block” führt ein eindeutiger Verweis auf ein entsprechendes Geräteunterverzeichnis in “/sys/devices” und dort zu einem speziellen Gerät.

Unter “/sys/block” findet man (wie der Name andeutet) Interfaces zu Blockgeräten, “/sys/class” enthält dagegen Interfaces zu zeichenorientierten Geräten. Die Major- und Minor-Nummern der Devices sind jeweils in einer zugehörigen Pseudodatei namens »dev« enthalten.

Aha, durch diese Verweise kann das System also wissen, welche Schnittstelle sich auf welches Gerät bezieht. Wie sieht es nun mit den Bezeichnungen der Netzwerk-Interfaces aus?

Durch die persistente Namensvergabe zu Geräte-Schnittstellen unter “/sys/class/net/*” und die entsprechende Bezeichnung der zugehörigen Datei “/etc/sysconfig/network/ifcfg_*” mit den Konfigurationsvorgaben ermöglicht es SUSE, die Netzwerk-Interface-Konfiguration über die sysFS-Struktur mit einem ganz bestimmten Gerät zu verbinden. Im Kern gilt folgende Zuordnungskette:

Gerät unter /sys/devices/Unterverzeichnis/…/Gerätedatei <<>> Bezeichnetes Interface unter /sys/class/net/*

Bezeichnetes Interface unter /sys/class/net/* <<>> (Netz-) Interfacekonfiguration unter /etc/sysconfig/network/ifcfg_*

Hiermit rundet sich unser Bild zur Netzwerkkonfiguration unter SUSE ab. Neben SUSE-spezifischen Konfigurationsdateien und Startup-Skripts spielt der Einsatz der “sysFS”-Struktur eine essentielle Rolle. Die persistente Namensgebung für die Netzwerkinterfaces spiegelt sich im Verzeichnis “/sys/class/net” wieder. Auf dieses nehmen auch die Startup-Skripts Bezug. Die unter “/sys/class/net” definierten Interfaces
sind dagegen mit konkreten Geräten unter “/sys/devices/….” verbunden.

Links

Wer nun immer noch neugierig ist, der möge sich mit tiefergehenden Geheimnissen des “sysFS”-Filesystems und weiterführend ggf. auch mit “udev” befassen.

Einen ersten Einstieg bieten folgende Adressen:

http://de.opensuse.org/SDB:SUSE_Linux_Geräte-_und_Schnittstellenkonfiguration

http://www.linux-magazin.de/heft_abo/ausgaben/2005/04/knoten_knuepfen/(offset)/2

Resultset-Listen in PHP-IT-Templates

Im Rahmen der Programmierung von Datenbank-Abfragen mit PHP ist man immer wieder mit der Frage konfrontiert, wie man die Ergebnisse in listenartiger Form darstellen soll. Hierbei können u.a. folgende Schwierigkeiten auftreten:

a) Die Länge oder Größe des Inhalts einzelner Felder aus der Datenbank ist zunächst unbekannt. Die einzelnen Listenelemente müssen daher in der Höhe variabel sein.

b) Innerhalb eines Listenelementes hat man oftmals komplexe Unterformatierungen vorzunehmen. So ist das Listen-Aufzählungszeichen darzustellen. Hinzu kommen evtl. Feldbezeichnungen, die vom eigentlichen Feldinhalt abgesetzt werden sollen.

c) Der einzelne Satz der Datenbankabfrage beinhaltet i.d.R. mehrere Felder und entsprechende Inhalte, die pro Satz untereinander aufgelistet werden sollen. Um ein komplettes Resultset einer Datenbank-Abfrage darzustellen, braucht man also mindestens eine zweifach geschachtelte Liste. Damit erschwert sich die Formatierung.

d) Die Darstellung der (geschachtelten) Listen erweist sich bzgl. der Formatierung oft als browserabhängig. Dies gilt im besonderen für die Behandlung von Abständen der Aufzählungszeichen zum linken Rand der Liste wie zum Inhalt des Listenpunktes. Hinzu kommen weitere Standardabstände, die ggf. mühsam korrigiert werden müssen.

Will man die Darstellung einer Datenbankabfrage also etwa nach dem Schema

>

Inhalt des Satzes Nr. 11

*

Feldbezeichner 1
Feldinhalt, Feldinhalt, Feldinhalt
Feldinhalt, Feldinhalt, Feldinhalt
Feldinhalt, Feldinhalt, Feldinhalt
Feldinhalt, Feldinhalt, Feldinhalt
Feldinhalt, Feldinhalt, Feldinhalt
Feldinhalt, Feldinhalt, Feldinhalt
………., ……. , …..
………., ……. , …..

 

*

Feldbezeichner 2
Feldinhalt, Feldinhalt, Feldinhalt
Feldinhalt, Feldinhalt, Feldinhalt
Feldinhalt, Feldinhalt, Feldinhalt
………., ……. , …..
………., ……. , …..

 

>

Inhalt des Satzes Nr. 12

*

Feldbezeichner 1
Feldinhalt, Feldinhalt, Feldinhalt
Feldinhalt, Feldinhalt, Feldinhalt
………., ……. , …..
………., ……. , …..

 

formatieren, so erweist sich der simple Einsatz von “<UL><LI></LI></UL>” oft als unzureichend und – nicht zuletzt wegen der Browserabhängigkeit der Listenformatierung – auch als schwierig.

In vielen Fällen sind neben dem oben dargestellten Elementen pro Satz des Resultsets noch weitere schmuckvolle Bereiche darzustellen: etwa eine Leiste mit Buttons zum Auslösen von Pflegeoperationen für den Datenbank-Satzes oder zur Anzeige seines Status.

Gestern bin ich denn auch bei einem Kunden gefragt worden, ob wir zur Darstellung von solchen Resultsetlisten eigentlich Tabellen verwenden würden. Bzw.: Wie wir diese Probleme generell im Zusammenspiel mit serverseitigem PHP lösen würden.

Hierzu gibt es mehrere Antworten:

  1. Wir verwenden zur Formatierung des HTML-Outputs grundsätzlich Templates – zumeist PEAR IT- oder ITX-Templates. (Smarty-Templates sind natürlich eine genauso gute Alternative). Hierdurch trennen wir die Datenbankinteraktion und evtl. Datensatzmanipulationen im PHP-Skript weitgehend von der Darstellung des Resultsets im Browser ab. (Das erleichtert im nachhinein auch den Schwenk von klassischer PHP-Interaktion mit HTML-Generierung zu moderneren Vorgehensweisen auf der Basis von Ajax-Technologien.)

    Alle nachfolgenden Hinweise zur Formatierung sind somit reine HTML- bzw. CSS-Hinweise für das Template.

  2. Bei der Formatierung des listenartigen Outputs im IT-Template setzen wir in der Regel keine Tabellen mehr ein, da wir deren Handhabung inzwischen eher als unflexibel empfinden. Vielmehr verwenden wir sehr wohl Listentags (<UL>, <LI>).
  3. Für die Listen schalten wir i.d.R. allerdings die Formatierung für linksseitige MARGINs und PADDINGs ab. Per CSS setzen wir solche “MARGINs” und “PADDINGs” auf 0, sowohl im <UL>-Tag wie auch im <LI>-Tag. Auch den “list-style-type” setzen wir i.d.R. auf “none”. Hiermit erzielen wir später eine Kompatibilität der Listendarstellung für praktisch alle gängigen Browser (s.u.).
  4. Grundsätzliche Breiteneinstellungen der Liste nehmen wir im <UL>-Tag und wg. des MS IE auch im <LI>-Tag vor. (Das UL-Element verhält sich grundsätzlich wie ein Block. Nur sieht das das der MS IE nicht so.) Die Höhenvorgabe für die gesamte Liste und auch die einzelnen Listenelemente lassen wir dagegen offen.
  5. Die Abstände zwischen den Listen-Elementen regeln “margin-bottom”-Anweisungen für die <LI>-Tags. Hintergrundsfarben für den gesamten Listenbereich regelt wir manchmal über ein DIV, das die Liste umfasst. Von der “normalen” Listenformatierung bleibt also bislang nur eine Breitenvorgabe und ein vorgegebener Abstand zwischen den Listenelementen übrig.
  6. Die Formatierung innerhalb eines Listenelements gestalten wir grundsätzlich mit DIVs. In das <LI>-Tag der Hauptliste binden wir deshalb zunächst ein Container-DIV (“Satz-Container”) vorgegebener Breite ein. Die Höhe dieses Containers bleibt offen.
  7. Innerhalb des “Satz-Container”-DIVs (in einem <LI>-Tag der Liste für die Sätze) positionieren wir nun ein weiteres DIV (“Header-Container”), das zusätzliche DIVs für den den satzspezifischen
    Header und das Aufzählungszeichen für den Satz aufnimmt. Die Positionierung dieser inneren DIVs im “Header-Container” erfolgt über float-Anweisungen; relative Abstände werden über “margin”-Anweisungen festgelegt. Wichtig ist hier noch, dass ein nachfolgendes Dummy-Element (z.B. ein <p>-Tag) das Floaten im Container wieder aufhebt. Breite und Höhe des Header-Containers können in der Regel fest vorgegeben werden. Zusätzlich dient ggf. eine “margin-bottom”-Anweisung zur Festlegung des minimalen Abstands zu weiteren nachfolgenden DIV-Elementen eines Satzes.

  8. Unterhalb des Header-Containers des Satzes positionieren wir nun eine weiteres DIV (“Container Feldliste”). Der Container “Feldliste” wird relativ (!) zum Header-Container positioniert (ggf. ist ein zusätzl. margin-top festzulegen). Seine Breite ist beliebig vorgebbar, seine Höhe bleibt offen.

    Damit die Positionierung auch richtig erfolgen kann, sollte man diesem DIV (Container Feldliste) zur Sicherheit noch eine Style-Anweisung “clear:both;” mitgegeben. Damit werden die vorhergehenden float-Anweisungen für die Header-Elemente aufgehoben, wenn man dies dort vergessen hat.

  9. Verschachtelte zweite Liste: In den “Container Feldliste” positionieren wir nun erneut eine <UL>-Liste, die dann der Darstellung der Feldelemente eines Satzes aus dem Resultset dient. Auch für diese Liste schalten wir evtl. intrinsische linksseitige margin- und padding-Vorgaben der Browser explizit ab und setzen den “list-style-type:none”. Zudem regeln wir den vertikalen Abstand der Listenelemente durch “margin-bottom”-Vorgaben.
  10. Pro Listenelement der feldbezogenen Liste erstellen wir nun einen weiteren DIV-Container (“Container Einzelfeld”). Auch seine Breite und Margins geben wir vor. Die Höhe bleibt dagegen offen.
  11. In den Container “Einzelfeld” werden nun weitere separate DIVs für ein evtl. Aufzählungszeichen, den Feldnamen und den Feldinhalt gesetzt. Die relative Positionierung der inneren DIVs zueinander erreichen wir erneut per CSS-float-Anweisungen. Die relative Positionierung der inneren DIVs zueinander regeln zusätzliche “margin”-Anweisungen. Auch hier heben wir die float-Anweisung wieder über ein abschliessendes Dummy-Element auf. Keines (!) der inneren DIVs wird also im Container über “position:absolute” positioniert. Es werden nur float- und margin-Anweisungen verwendet.
  12. Die Breite der einzelnen inneren DIVs für den Feldbereich geben wir per CSS vor. Die Höhe des DIVs zur Aufnahme des Feldinhalts lassen wir dagegen im Falle von Textfeldern offen, da die Länge und Größe des Feldinhalts dann ja vorab nicht bekannt ist.
  13. Um das Container-DIV “Einzelfeld” (wegen einer evtl. Rahmung oder Hintergrundsfarbe) an die vorab unbekannte Höhe der inneren DIVs – im besonderen des DIVs für den Feldinhalt anzupassen, ist folgender Schritt wichtig:

    Zusätzlich zu den inneren “gefloateten” DIVS bringen wir ein weiteres relativ positioniertes Dummy-Tag (z.B. ein P-Tag) an, das die “style”-Anweisung “clear:both;” enthält. Ohne diese Anweisung ist die Höhe des Container-DIVs “Einzelfeld” nur auf eine Minimum beschränkt und die gefloateten inneren DIVs ragen über den Minimalbereich des Containers weit hinaus. Das Dummy-Tag ermöglicht als reguläres Inhaltselement des Containers die Höhenbestimmung für das Elternelement, indem es die Float-Anweisungen aufhebt.

Durch diese zweifach verschachtelte Liste und die erwähnten Maßnahmen erreicht man eine sehr schöne und im nachhinein per CSS sehr einfach anpassbare Listendarstellung der Ergebnisse von Datenbankabfragen. Da die eigentliche Formatierung jedes Listenelements inkl. Aufzählungszeichen innerhalb
eines DIV-Containers erfolgt, wird man bzgl. der Listendarstellung der Sätze und der inneren Liste der Felder praktisch browser-unabhängig.

Natürlich lagert man die CSS-Anweisungen für die Listen und die inkludierten DIVs zur besseren Pflegbarkeit in eine separate Datei aus.

Orange – eine Farbe mit Flecken

Vor kurzem waren wir in Frankreich. Dort gibt es auch einen DSL-Provider, der sich einer bestimmten Farbe verschrieben hat. Die Freunde, bei denen wir zu Besuch waren, haben sich den Luxus geleistet, in ihrer Ferienwohnung einen DSL-Anschluss dieses Providers zu installieren.

Wir fanden das ganz toll – den Laptop raus, Linux anwerfen, Emailen und Surfen ……. – dachten wir.

Aber nein – mit Verwunderung mussten wir feststellen, dass die von uns vorgegebenen DNS-Server nicht akzeptiert wurden. Port 53 funktionierte nur für die DNS-Server dieses Providers. Noch viel interessanter wurde es dann bei Mails – der Port 25 ist für alle Server außer den eigenen SMTP-Servern des Providers geblockt. Hinzu kam die Erfahrung unserer Freunde, dass es nicht möglich war, eine verschlüsselte VPN-Verbindung zu ihrer Firma aufzubauen. Bei einem anderen Provider funktionierte dies dagegen ohne weiteres und auf Anhieb.

Im Internet fand ich dann den Hinweis, dass dieser Provider zumindest das Blockieren des SMTP-Ports für andere Server ohne Benachrichtigung an die eigenen Kunden durchgeführt hat. In diversen Foren gab es hierzu bereits hitzige Debatten.

Warum diese Eingriffe und die Begrenzung der Freiheit des Anwenders? Angeblich, um die Spamflut, durch die der französische Provider anscheinend besonders betroffen war, besser in den Griff zu bekommen. (Als ob es dazu nicht andere Methoden gäbe … ) Warum dann aber keine offizielle Ankündigung in diesem Sinne? Warum sich den Hass vieler internationaler Geschäftsleute zuziehen, die über ihre eigenen SMTP-Server operieren wollen oder müssen?

Interessant ist, dass der besagte Provider fest in der Hand der französischen Telekom und damit des Staates ist. Ein Schelm, wer dabei Böses denkt …. zumal nach den kürzlichen Erfahrungen mit anderen großen nationalen Telefongesellschaften …..

Remote Administration – VNC oder ssh -X ?

Es gibt eine Reihe von Situation bei der Remote-Wartung eines Rechners, in denen es nicht damit getan ist, nur auf eine Shell zuzugreifen. Manchmal benötigt man den graphischen Zugriff – zumindest auf einzelne Applikationen. Dies gilt vor allem dann, wenn der User des Remote-Rechners Fehler im graphischen Bereich meldet. Genau so einen Fall hatten wir gestern, als wir auf einen Linux-PC (SUSE 10.2) in Frankreich zugreifen mussten, um das ordnungsgemäße Wirken von Firefox zu überprüfen.

Als rettender Engel steht man nun vor der Frage, wie man den Zugriff am besten erledigt. Mit einer verschlüsselten Verbindung natürlich. Klar. Aber ist im Einzelfall eine SSH-verschlüsselte X-Sitzung sinnvoller als eine VNC-Sitzung? Ich meine, dass man das nicht pauschal sagen kann. In unserem Fall erwies sich einen verschlüsselte VNC-Sitzung als die bessere und schnellere Lösung. Wir diskutieren nachfolgend kurz beide Möglichkeiten, da wir schon mehrfach gefragt wurden, wie man für Remote-Sitzungen vorgeht.

Möglichkeit 1: SSH -X

Eine einfache Möglichkeit ist zunächst einmal die, den Output der kritischen Applikation auf den eigenen lokalen X-Server umzulenken. Dies erreicht man bequem dadurch, dass man sich auf dem Remote-Rechner mit

ssh -X remote-rechner

oder

ssh -X user@remote-rechner

anmeldet. Die auf jeden Fall erforderliche SSH-Verschlüsselung für die Verbindung erhält man dann inklusive. In vielen Fällen genügt dies, um ein Problem zu lösen. Man startet einfach in der geöffneten Shell die zu untersuchenden Applikationen, die ihren graphischen Output dann (i.d.R.) auch brav auf dem lokal laufenden X-Server des Administrator-PCs darstellen. Zur Klarstellung: Der X-Server läuft hier auf dem eigenen lokalen System des Admins und nicht auf dem zu untersuchenden Remote-System! Der Remote-Rechner sendet den graphischen Output der Applikation über das Netz zum X-Server des lokalen Rechners, von dem aus der Administrator auf das Remote-System zugreift. (Beim nachfolgend diskutierten VNC-Verfahren ist dies anders!).

Nun gibt es u.U. leider Bedingungen, unter denen das Vorgehen über “ssh -X” nicht funktioniert oder der Datentransfer einfach zu unbequem wird.

Hindernis 1 – Performance: Bei einer X-Session werden (applikationsabhängig) relativ viele Daten übertragen. Das kann u.U. zu einem erheblichen Geduldspiel werden. Tödlich sind meiner Erfahrung nach bei rel. langsamen Remote-Rechnern und begrenzter Verbindungsbandbreite solche Applikationen wie Yast (z.B zur Paketverwaltung). Auch Anwendungen, deren Inhalt selbst komplex ist (Browser mit Flash) führen ggf. zu Zuständen wie in der Warteschlange bei großen Telekommunikationsanbietern.

Hindernis 2 – Sicherheitseinstellungen: Die Applikation lässt ggf. oder unter bestimmten Umständen keinen Output auf dem Remote-Xserver zu.

Hindernis 3 – fehlerhafte Anwendungen: Die Anwendung weist Fehler auf oder ist mit dem Remote X-Server nicht kompatibel.

In unserem Fall war es wie gesagt so, dass wir das Verhalten von Firefox auf dem Remote-Rechner testen mussten. Leider ließ sich der von uns remote gestartete Firefox-Prozess unter keinen Umständen dazu bewegen, sich auf unserem lokalen X-Server zu präsentieren. (Nach Aussagen des Remote-Anwenders ging Firefox auf dem Remote KDE-Desktop aber sehr wohl auf! Siehe zum seltsamen Verhalten von Firefox weiter unten!). Zudem wurde das Update von Software über Yast2 zu einer erheblichen Geduldsprobe.

In solchen Fällen liegt es nahe, zumindest testhalber eine VNC-Verbindung über KDEs “krdc” aufzubauen und diese VNC-Verbindung ggf. für langsame Leitungen zu konfigurieren.

Möglichkeit 2: VNC
Auf dem Remote-System muss ein VNC-Server laufen. Die meisten Distributionen haben ein entsprechendes Paket, das man mit dem Paketmanager seiner Wahl installiert. Dann richtet man den VNC-Server und IPtables (oder die aktive Firewall) auf dem Remote-Rechner so ein, dass die Kommunikation überhaupt möglich wird. Typischerweise verwendet der VNC-Server auf dem Remote-Rechner als Default-Port den Port 5901.

Dieser Port muss für eingehende Verbindungen vom System des Administrators aus freigeschaltet werden – am besten nur für Verbindungen von diesem System aus! (Eine Diskussion der zugehörigen IPtables-Konfiguration führt an dieser Stelle zu weit – ich empfehle hierfür das sehr schöne Programm FWbuilder!). Unter Opensuse hilft einem Yast (s. den Punkt Netzwerkdienste). Nachteilig empfinde ich dabei allerdings die pauschale Freigabe des Ports auf allen Interfaces und für alle potentiellen IP-Adressen.

Nach dem Einrichten des VNC-Servers auf dem Remote-System ist ggf. ein Neustart von xdm, kdm oder gdm für den Anmeldebildschirm erforderlich (für SUSE und KDE hilft z.B.: “rckdm restart”). Das Einrichten des VNC-Servers macht man am besten über eine Standard SSH-Sitzung, wenn der Remote-Anwender sich mit der Konfiguration nicht auskennt.

Die Verbindung zwischen Administrator-System und dem Remote-Rechner (Port 5901) muss vor Beginn der VNC-Sitzung natürlich verschlüsselt werden. Hierzu baut man einen SSH-Tunnel vom eigenen Rechner zum Zielsystem auf. Wir setzen voraus, dass SSH auf beiden Systemen eingerichtet ist und dass der ssh-agent für eine automatische Passwortübergabe vorbereitet wurde. Der Tunnel wird dann vom PC des Administrators aus durch nachfolgenden Befehl aufgebaut:

z.B.: ssh -f -N -L8787:remote-rechner:5901 user@remote-rechner

Hierbei gilt Folgendes: “user” ist der Username unter dem die ssh-Sitzung auf dem Remote-Rechner aufgebaut wird. “remote-Rechner” ist durch den Namen oder die IP-Adresse des Remote-Systems zu ersetzen. Die Option “-f” bedingt die Option “-N”. “-f” sorgt für einen Fork-Prozess von ssh in den Hintergrund, “-N” sorgt dafür, dass kein Shell-Fenster geöffnet wird. Der ssh-agent muss hierbei – wie gesagt – in der Lage sein, Passwörter automatisch im Hintergrund auszutauschen. Als Zielport ist hier der Defaultport 5901 gewählt worden. Ob dieser gültig ist, muss man natürlich vorher wissen (Einrichtung des VNC-Servers). “8787” ist im Beispiel der Dummy-Port auf dem lokalen System, über den der lokale VNC-Client dann den Remote-Rechner auf Port 5901 – und damit den VNC-Server – anspricht.

Der SSH-Tunnel hat in diesem Beispiel also als Anfangspunkt den Port 8787 auf dem lokalen System des Administrators und als Endpunkt den Port 5901 auf dem Remote-System “remote-rechner”. Dort loggt sich der User ja gerade für die SSH-Sitzung ein. (Sonderfälle wie den Umweg über einen SSH-Relay-Rechner lassen wir hier mal der Einfachheit halber weg. )

Wichtiger Hinweis: Zu beachten ist, dass man nach getaner Arbeit nicht vergisst, den SSH-Tunnel (der ja im Hintergund läuft) wieder abzubauen und auch den Port 5901 wieder zu schließen !!!

Weitere Hinweise zum Tunneln: Schlägt der Fork-Prozess in den Hintergrund fehl, oder will man die Passwort-Verankerung in den Systemen nicht dem ssh-agent überlassen, so hilft

z.B.: ssh -L8787:remote-rechner:5901 user@remote-rechner

Allerdings öffnet man damit eine bleibende Remote-Shell im Vordergrund des lokalen eigenen Systems. Verwendet man

ssh -X -L8787:remote-rechner:5901 user@remote-rechner

so hat das den Vorteil, dass man bei Bedarf schnell noch eine Remote-Applikation für den eigen X-Server öffnen kann – zusätzlich zu den Vorgängen, die im VNC-Client dargestellt werden (also Vorgänge, die in einer X-Session auf dem Remote-System laufen).

Konfiguration des lokalen VNC-Clients: Im VNC-Client (z.B.
“krdc”) muss man im gegebenen Beispiel als Zielrechner natürlich “localhost:8787” eingeben, um die VNC-Sitzung über den Tunnel aufzubauen. Der Client spricht über den lokalen Port 8787 den Remote-Rechner an und erhält vom dortigen VNC-Server die Bilddaten der dortigen X-Sitzung zugeschickt. (Die X-Sitzung läuft auf dem Remote-Rechner ab! Der lokale VNC-Client erhält hiervon nur die Bilddaten, die er auf dem Schirm des Admin-PCs als spezielle lokale X-Applikation darstellt.).

Hat man bisher alles richtig gemacht, so öffnet sich im lokalen VNC-Client der graphische Anmeldeschirm des Remote-Systems und man gelangt nach dem Login in eine graphische X-Sitzung des Remote-Systems unter dem dortigen Window-Manager (z.B. KDE).

In unserem Fall stellten wir tatsächlich fest, dass das Arbeiten über VNC relativ zügig vonstatten ging und zwar schneller als mit einer “ssh -X” – Sitzung. Ob das allgemeingültig ist, trauen wir uns aber keinesfalls zu sagen.

Hinweis zu Remote-Tests von Firefox:
Firefox für Linux stellt fest, auf welchem X-Server und Screen ein bestimmter User eine erste Instanz des Firefox-Programms geöffnet hat und lässt die Öffnung weiterer Instanzen für andere X-Server nicht zu (vermutlich aus Sicherheitsgründen). Das führt für Remote-Zugriffe aber zu folgender unguter Situation:

Hat der Remote-User (nennen wir ihn “James”) noch irgendein Firefox-Fenster offen, so lässt sich auf dem lokalen X-Server des Administrators keine weitere Firefox-Instanz öffnen, wenn der Administrator als “James” auf dem Remote-System eingeloggt ist. Umgekehrt gilt das Gleiche: Hat der Administrator als User “James” auf dem Remote-System eine Firefox-Instanz für den lokalen X-Server geöffnet, so kann der User James auf dem Remote-System und in seiner eigenen X-Sitzung keine neue Firefox-Instanz öffnen, sondern erhält eine Warnung !
Man lernt nie aus!
Merke: Will man Firefox als Remote Administrator auf seinem eigenen lokalen X-Server testen, so muss der User auf dem entfernten Remote-Rechner alle Firefox-Fenster (Firefox-Programme) vollständig geschlossen haben – oder der Administrator muss unter einer anderen User ID eingeloggt sein.