Ext JS Summer-Camp-Workshop

Status
Für weitere Antworten geschlossen.

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Ext JS ist schöner Wohnen mit der DS.

Für den Workshop braucht man ein wenig HTML und JavaScript-Kenntnisse und sollte einen Telnet/ssh-Zugang zu seiner DS haben.

Das Ext JS 2.2.1 SDK kann man hier downloaden. Leider habe ich bemerkt, dass das Ext JS auf der DS nicht gerade das aktuellste ist und der RC3 der Version 3.0 noch nicht in den Tutorials berücksichtigt worden ist, deswegen die Version 2.2.1.

Wer das Ext JS (noch) nicht kennt, der sollte sich hier informieren.

Wer sich warm lesen möchte, dem empfehle ich das Tutorial hier.

Als Anwendungsbeispiel (3rd-party-apps) soll ein Process Snapshot programmiert werden. Also ungefähr das Gleiche wie das 'ps'-Kommando auf der Kommandozeile.

Folgende Vorarbeiten sind zu erledigen:

1] anlegen eines Verzeichnisses /usr/syno/synoman/scripts/ext-2.2.1 und kopieren des obigen Downloads dorthin, so dass man auf folgende Datei direkt zugreifen kann: /usr/syno/synoman/scripts/ext-2.2.1/ext-all.js

2] anlegen eines Verzeichnisses bzw. einer Datei /usr/syno/synoman/webman/3rdparty/ps/application.cfg mit folgendem Inhalt:

Rich (BBCode):
text = Process Snapshot
description = Process Snapshot
path = /phpsrc/ps/ps.html
type = embedded

3] anlegen eines Verzeichnisses /usr/syno/synoman/phpsrc/ps

4] anlegen der Datei /usr/syno/synoman/phpsrc/ps/ps.html mit folgendem Inhalt:

Rich (BBCode):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>process snapshot</title>
    <script type="text/javascript" src="/scripts/ext-2.2.1/adapter/yui/yui-utilities.js"></script>
    <script type="text/javascript" src="/scripts/ext-2.2.1/adapter/yui/ext-yui-adapter.js"></script>
    <script type="text/javascript" src="/scripts/ext-2.2.1/ext-all.js"></script>
    <script type="text/javascript" src="ps.js"></script>
    <link rel="stylesheet" type="text/css" href="/scripts/ext-2.2.1/resources/css/ext-all.css">
    <link rel="stylesheet" type="text/css" href="/scripts/ext-2.2.1/resources/css/ytheme-vista.css">
</head>
<body>
  <h3>Process Snapshot</h3>
  <div id="ps-grid" style="overflow: hidden; margin:7px 7px;"></div>
  <div id="msg" style="visibility:hidden;"></div>
</body>
</html>

5] Aufruf der 3rdparty-Appication mit dem DS Manager testen

Erinnerung: Alle Geschichten gehen wie immer auf eigene Kappe :D und stehen unter der GPL3. Das ist besonders für die Nutzung der Ext JS-Bibliotheken notwendig, weil man diese sonst nicht kostenfrei nutzen darf. D.h. jeder Entwicklung mit Ext JS muss wieder unter GPL3 veröffentlich werden, wenn man die GPL3-Lizenz von Ext JS wählt. Genaueres kann man an anderen Stellen dazu lesen. Das ist nun auch einer der Gründe, warum ich nicht die eingebaute Ext JS-Version verwende, weil ich nicht mit der Synology-Lizenz, welche ja kommerziell ist, in Konflikt geraten möchte.

Itari
 
Zuletzt bearbeitet:

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Schritt 1

Die Idee ist es, eine tabellenartige Anzeige (ein Grid) für die Anzeige der Prozessliste mit Ext JS zu basteln. Damit das ganze auch modern und schick ist, wählen wir auch als Dateneingabestrom für unser Grid eine XML-Strecke (JSON wäre auch möglich).

Wie sieht die XML-Strecke ungefähr aus?

Rich (BBCode):
<?xml version="1.0" encoding="UTF-8"?>
<Items>
 <Item>
  <PID>1</PID>
  <PPID>0</PPID>
  <Prio>16</Prio>
  <Nice>0</Nice>
  <Uid>root</Uid>
  <VmSize>288</VmSize>
  <Status>S</Status>
  <Command>init</Command>
 </Item>
 <Item>
  <PID>2</PID>
  <PPID>1</PPID>
  <Prio>34</Prio>
  <Nice>19</Nice>
  <Uid>root</Uid>
  <VmSize>288</VmSize>
  <Status>SWN</Status>
  <Command>[ksoftirqd/0]</Command>
 </Item>
</Items>

wobei das Tag <Item>...</Item> natürlich für jede Prozesszeile vorkommen soll.

Zum Generieren dieser XML-Strecke verwenden wir der Einfachheit ein php-Skript. Damit das auch im sys-Apache läuft, muss das Init_3rdparty.spk installiert worden sein, denn damit wird das PHP freigeschaltet.

Das PHP-Skript /usr/syno/synoman/phpsrc/ps/ps.php lädt sich die Informationen via exec()-Funktionen, die auf die Shell zugreifen können. Also ein exec('ps',$out) hat nach der Ausführung einen Zeilen-Array $out gefüllt (und zwar im Append-Modus, deswegen muss man solche Arrays gegebenenfalls wieder löschen, falls man sie mehrfach verwendet) und man kann mit einer foreach-Schleife an die Zeile heran. Da die erste Zweile eine Überschrift ist, wird erst einmal array_shift() gemacht, welche den Array um den ersten Array-Eintrag kürzt. Das gleiche wird auch am Ende des Arrays 2x mit einem array_pop() gemacht, weil wir ja auch den exec-Aufruf (sh und ps) nicht wirklich sehen wollen.

Header() und print '<?xml ...' sind Vorbereitungen für die Definition der XML-Strecke.

In der Schleife wird die ps-Zeile zerlegt mit einem preg_split() und dann stückchenweise abgearbeitet. Da nicht alle Informationen, die wir über einen Prozess in Erfahrung bringen wollen, auch vom ps geliefert bekommen, müssen mir die Zusatzinformationen aus dem /proc-Dateisystem herausholen. Das /proc-Dateisystem ist eine virtuelle Schnittestelle zum Linux-Kernel und erlaubt uns wie in einem Dateisystem zu denken, also ll /proc/1 und schon sehen wir die Datenstruktur des Prozesse mit der PID 1.

Eine für uns nette Info steckt in der Datei status ... mit cat /proc/1/status kann man sich die Daten anschauen. Wir wollen die PPid (ParentProcessID) und holen uns deswegen per grep PPid /proc/1/status diese Zeile aus der Datenliste heraus. Das gilt natürlich auch für alle weiteren Prozesse.

In der Datei stat stehen die Hardcore-Informationen (*guck*). Als 18. Feld steht die Prozess-Priorität, an 19. Stelle der Nice-Value. Beides sind wichtige Informationen über die Aktivität eines Prozesses (auch wenn er meist ja schläft :D), weil mit ihnen das Prozess-Scheduling direkt beeinflusst wird. Man kann noch mehr herausholen ... aber das soll es erstmal sein.

Bei Status-Wert gibt es eine kleine Problematik, weil er Flags repräsentiert und mal eine Leerstelle in der Mitte haben kann ... da Leerstellen beim Splitten als Trennzeichen verwendet wurden, muss man jetzt analysieren, ob man nicht etwas zuviel getrennt hat ... außerdem kann da ein < drin vorkommen und das ist für XML ungenießbar, daher muss es ausgetauscht werden gegen & #60; Achtung ich hab da eine falsche Leerstelle drin, damit das hier auch angezeigt wird ... jaja so ist das halt.

Zuletzt wollen wir eine richtige Kommandozeile und nicht nur das auf die 80.Stelle Abgetrennte der ps-Ausgabe. In /proc/1/cmd kann nun dazu mehr stehen ... kann aber auch nichts stehen (und dann steht im ps mehr drin). Deswegen auch hier noch ein wenig Gerödel.
PHP:
<?php
exec('ps',$out);
array_shift($out);array_pop($out);array_pop($out);
header('Content-Type: application/xml');
print '<?xml version="1.0" encoding="UTF-8"?><Items>';
foreach($out as $item) {
  $part = preg_split('/ +/',' '.$item);  
  print '<Item>';
  print '<PID>'.$part[1].'</PID>';$pid=$part[1];array_shift($part);
  unset($ppid); exec('grep PPid /proc/'.$pid.'/status',$ppid);
  print '<PPID>'.preg_replace('/^PPid: */','',$ppid[0]).'</PPID>';
  unset($prio); exec('awk "{print \$18}" /proc/'.$pid.'/stat',$prio);  
  print '<Prio>'.$prio[0].'</Prio>'; 
  unset($nice); exec('awk "{print \$19}" /proc/'.$pid.'/stat',$nice);  
  print '<Nice>'.$nice[0].'</Nice>';    
  print '<Uid>'.$part[1].'</Uid>';array_shift($part);
  if (preg_match('/\d+/',$part[1])) { print '<VmSize>'.$part[1].'</VmSize>';array_shift($part);}
  print '<Status>'.preg_replace('/\</','& #60;',$part[1]);array_shift($part);
  if ($part[1]=='<') { print '& #60;';array_shift($part);}
  print '</Status><Command>';array_shift($part);
  if ($part[0][0]=='[') 
    foreach($part as $ppart) print $ppart.' ';
  else { 
    unset($cmd); exec('cat /proc/'.$pid.'/cmdline',$cmd); print preg_replace('/\x00/',' ',$cmd[0]); 
  }
  print '</Command></Item>';
}
print '</Items>';
?>

Achtung nochmal der Hinweis: & #60; muss ohne die Leerstelle geschrieben werden ... ich kanns es nur nicht hier posten, weil der Forumseditor mir sofort daraus einen < macht ... Sorry.

Wenn alles richtig ist, dann kann man sich die XML-Strecke im Browser anzeigen lassen: https://syno:5001/phpsrc/ps/ps.php

Erst wenn das läuft, macht der nächste Schritt Sinn.
 
Zuletzt bearbeitet:

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Schritt 2

Jetzt kommen wir zu dem eigentlichen Bauteil, einer Datei namens /usr/syno/synoman/phpsrc/ps/ps.js Hier wird jetzt alles Ext JS-spezifische umgesetzt. Das Ext JS - Manual ist hier. Wahrscheinlich wird man erst anhand von Beispielen wirklich dahinter kommen, was wo wie am besten einzusetzen ist. Deswegen machen wir ja hier auch ein Beispiel :D

Gesteuert wird alles bei Ext JS über die Ext.onReady-Methode. Als erstes wollen wir die XML-Strecke in einen internen Storage-Bereich von Ext holen. Das geht mit dem Anlegen des Objektes 'store' (Instanz) der Klasse Ext.data.Store. Diese braucht eine URL (natürlich zu unserem PHP-Skript), eine Methode zum Einlesen der Daten (hier der XmlReader, weil ja XML-Strecke) und die Definition der internen Datenstruktur, z.B. der Datentypen, wenn man später mal numerisch sortierte Spalten gerne hätte.

Dann wird das Grid aufgebaut (Ext.grid.GridPanel). Ein Verweis auf die Datenstorage ('store'), einem Titel, der Spaltendefinitionen (Überschrift, Breite, sortierbar, Datenbindung und zum Beispiel Ausrichtung), der ID des Html-Tags, wo das ganze in die HTML-Datei hin soll, ob die Zeilen 'gestreift' werden sollen und wie hoch das Ganze werden soll.

Zuletzt wird das Laden der Daten ausgelöst: store.data().

Damit die Tausender-Trennpunkte bei numerischen Angaben auch schön gesetzt werden, habe ich zudem noch die Klasse Ext.util.Format um die Methode mille() ergänzt. Ich fand keinen passenderen Namen :D.

PHP:
Ext.util.Format.mille = function (v) {
    v = String(v);
    var r = /(\d+)(\d{3})/;
    while (r.test(v)) {
        v = v.replace(r, '$1' + '.' + '$2');
    }
    return v;
};

Ext.onReady(function(){
    var store = new Ext.data.Store({
        url: '/phpsrc/ps/ps.php',
        reader: new Ext.data.XmlReader({
               record: 'Item',
               id: 'PID'
           }, [ 
               {name:'PPID',type:'float'},'Uid',{name:'VmSize',type:'float'},'Status','Command',
               {name:'PID',type:'float'},{name:'Nice',type:'float'},{name:'Prio',type:'float'}
           ])
    });

    var grid = new Ext.grid.GridPanel({
        store: store,
        title: 'ps',
        columns: [
            {header: "PID", width: 40, dataIndex: 'PID', sortable: true, align: 'right'},
            {header: "PPID", width: 40, dataIndex: 'PPID', sortable: true, align: 'right'},            
            {header: "Uid", width: 65, dataIndex: 'Uid', sortable: true},          
            {header: "VmSize", width: 65, dataIndex: 'VmSize', sortable: true, align: 'right',renderer: 'mille'},
            {header: "Prio", width: 40, dataIndex: 'Prio', sortable: true, align: 'right'},
            {header: "Nice", width: 40, dataIndex: 'Nice', sortable: true, align: 'right'},                        
            {header: "Status", width: 55, dataIndex: 'Status', sortable: true},
            {header: "Command", width: 1200, dataIndex: 'Command', sortable: true}                    
            ],
        renderTo:'ps-grid',
        stripeRows: true,
        height:520
    });
    store.load();
});

So und das war es auch schon.

Im nächsten Schritt werden wir noch ein Kontext-Menü hineinbasteln - z. B. zum Killen von Prozessen - aber bis dahin erstmal viel Spaß mit diesem Teil hier. Auch mal auf die Spaltenköpfe klicken zum Umsortieren, Spaltenbreite verstellen ... auch die Reihenfolge und das Ausblenden probieren. Sorry für die Breite der Command-Spalte ... aber wir wollen ja auch gerne alle Aufrufparameter sehen ...

Itari

Ach ja, das Bildchen ... wer möchte, kann das <h3>-Tag aus der ps.html nun entfernen ;)
.
 

Anhänge

  • ps.jpg
    ps.jpg
    40,4 KB · Aufrufe: 217
Zuletzt bearbeitet:

QTip

Super-Moderator
Teammitglied
Mitglied seit
04. Sep 2008
Beiträge
2.341
Punkte für Reaktionen
13
Punkte
84
cool itari, super Tutorial. Funktioniert bei mir aber allerdings erst nach ein paar Korrekturen ;)
Im 1. Post von dir, wo du erklärst, man sollte ein Verzeichnis /usr/syno/synoman/scripts/etx-2.2.1/ext-all.js erstellen; das muss doch ext-2.2.1 heissen. Nur so als Hinweis, falls jemand sich wundert, dass das Script nicht aufgerufen werden kann.

Im PHP-Script sind 2 kleine Typos drin:

wahrscheinlich hast die Leerstelle vom Text in den Code mit übernommen
PHP:
print '<Status>'.preg_replace('/\</','& 60;',$part[1]);array_shift($part);
                                       ^
                                      hier
dann meckert XML weiterhin, da ein 2. < vor dem Item Status existiert. Habe dann in der folgenden Codezeile das < einfach entfernt, ab da hat es bei mir erst funktioniert.
PHP:
if ($part[1]=='<') { print '<';array_shift($part);}
                            ^
                           da
Kene Ahnung ob das bei dir so funktioniert hat, bei mir erst nach den 2 Korrekturen.
 

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Hi Qtip,

danke für die Fehlerkorrekturen: einmal natürlich ein Tippfehler-Dreher und dann die blöde Geschichte mit dem < -Zeichen und der Ersatzdarstellung ... da wo 'da' steht, muss auch wieder & #60; (ohne Leerstelle natürlich) hin.

Natürlich hab ich es in meinem Skript so gehabt wie du nun auch. Nur beim Reinkopieren ist mir halt dann aufgefallen, was der Forumseditor daraus macht ...

Nochmals danke

Itari
 

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Schritt 3

Ein Kontext-Menu zum Anzeigen umfangreicherer Prozessinformationen und zum 'Killen' eines ausgewählten Prozesses steht nun an.

Gedanklich sind folgende Teile zu programmieren bzw. zu berücksichtigen:

1] 2 PHP-Skripte für die umfangreichere Prozessinformation und das 'Killen'
2] das Einrichten eines Kontextmenüs (u.a. gehören dazu zwei kleine Icons)
3] das Verbinden des Kontextmenüs mit Funktionen
4] das Abschicken und Entgegennehmen von Daten an die PHP-Skripte
5] bei der Informationsanzeige das Aufbauen eines Anzeige-Fensters (Panel)
6] ein Refresh nach dem 'killen' des Prozesses, um nun wieder eine aktuelle Liste zu erhalten

Das Skript ps.js wird erweitert innerhalb der Methode onReady (also sozusagen geht es nun nach der Zeile mit dem 'store.load()' nahtlos weiter:

PHP:
   var PSListingSelectedRow;
    PSListingContextMenu = new Ext.menu.Menu({
      id: 'PSListingGridPanelContextMenu',
      items: [
        { text: 'more Infos',        handler: infoPSContextMenu, icon: 'icons/application2.png'},
        { text: 'Kill this Process', handler: killPSContextMenu, icon: 'icons/cross.png'}        
      ]
      });    
    function onPSListingGridPanelContextMenu(grid, rowIndex, e) {
      e.stopEvent();
      var coords = e.getXY();
      PSListingContextMenu.rowRecord = grid.store.getAt(rowIndex);
      grid.selModel.selectRow(rowIndex);
      PSListingSelectedRow=rowIndex;
      PSListingContextMenu.showAt([coords[0], coords[1]]);
      }
    function infoPSContextMenu(){
      var selections = grid.selModel.getSelections(); 
      var info_pid = selections[0].data.PID;         
      var p = new Ext.Panel({
        title: 'Process Information PID: ' + info_pid,
        collapsible: false,
        shadow: true,
        renderTo: 'container',
        bodyStyle: 'padding:5px',
        width:400,
        autoLoad: {url:'/phpsrc/ps/psinfo.php',params:'pid=' + info_pid},
        tools:[
          { id: 'print', hidden: false, handler: function(event, toolEl, panel){ window.print(); }},     
          { id: 'close', hidden: false, handler: function(event, toolEl, panel){ panel.hide(); }}
          ] 
        });
      p.show();
      }
    function killPSContextMenu(){
      var selections = grid.selModel.getSelections();
      var kill_pid = selections[0].data.PID;
      //alert("kill "+kill_pid);
      var msg = Ext.get("msg");
		  msg.load({
			  url: '/phpsrc/ps/kill.php',
			  params: "pid=" + kill_pid,
			  text: "Updating..."
		    });
		  msg.show();
      window.location.reload();		    
    }
    grid.addListener('rowcontextmenu', onPSListingGridPanelContextMenu);
}); // des ist schon noch da ...

Für die Auswahl der aktuellen Klick-Zeile des Kontext-Menüs wird die Variable PSListingSelectedRow angelegt (ProcessSnapshotListingSelectedRow ... jaja die Namen).

Dann wird das Kontext-Menü PSListingContextMenu mit den beiden Items 'more Infos' und 'Kill this Process' definiert. Dazu werden die im Anhang liegenden Icons verwendet. Und jede Item-Zeile wird mit einer Funktion (handler) verknüpft.

Die Methode onPSListingGridPanelContextMenu nimmt den Auslöse-Klick (event e) entgegen und bestimmt die Grid-Zeile und legt die Position des Kontext-Menüs fest.

Aktiviert wird diese Methode durch die letzte Zeile mit dem grid.addListener().

Nun fehlen nur noch die beiden Methoden/Funktionen, die durch das Menü ausgelöst werden: infoPSContextMenu() und killPSContextMenu().

Die Methode infoPSContextMenu() nimmt sich erst einmal die Selektion (die Klick-Zeile) zur Brust und besorgt sich den Inhalt der ersten Spalte, die PID. Dann wird ein Panel aufgespannt. Dieses benötigt in der ps.html ein div-Tag mit der id='container', welches hübsch positioniert wird:

Rich (BBCode):
<div id="container" style="position:absolute;top:20px;left:150px;font-family:Consolas,Courier New,Courier;font-size:8pt;"></div>

Mit dem Attribut autoLoad wird dafür gesorgt, dass das PHP-Skript psinfo.php ins Spiel gebracht wird, denn dieses soll uns ja in Abhängigkeit von der PID mit umfangreichen Prozessinformationen versorgen:

PHP:
<?php
if (isset($_REQUEST['pid'])) {
  unset($info); exec('cat /proc/'.$pid.'/status',$info);
  foreach($info as $line) $out.=$line.'<br/>';
  print $out;
}
?>

Die Rückgabe landet wie gesagt im div-Tag mit der id=container und wird hübsch gemacht. Unter anderem gibt es 2 Fenster-Schaltknöpfe fürs 'wieder zu machen' und fürs 'drucken'. Das mit dem Drucken ist nur Show, weil da eigentlich nur das Druck-Menü aufgemacht wird.

Die Methode killPSContextMenu() macht erstmal dasselbe wie gerade besprochen, besorgt sich nämlich die PID. Dann wird das div-Tag mit der id=msg als Zwischenlager verwendet, um sich nun vom PHP-Skript kill.php versorgen zu lassen, was aber auch eigentlich nur Show ist:

PHP:
<?php
if (isset($_REQUEST['pid'])) {
  exec('kill -9 '.$_REQUEST['pid']);
  print 'killed process '.$_REQUEST['pid'];
}
?>

Im Grunde geht es ja nur darum, den exec auszuführen.

Da das nun gutes AJAX ist, müssen wir ein wenig warten, bis alles soweit ist. Das geschieht automatisch durch die Methode show(). Wenn also show() soweit ist, dann geben wir zwar noch den Text grad aus, aber machen in einem Atemzug den reload() des Fensters. Und damit wird ja alles 'neu' gemacht ...

Ich hoffe, dass das Beispiel einige interessante Konzepte von Ext JS aufgezeigt hat und auch, dass es zwar komplex ist, aber - wenn man irgendwo was abschreiben kann - nicht unbedingt aufwendig ist. Es liest sich doch recht gut.

Öhm ... das Ausprobieren der Funktion 'kill' sollte mit Bedacht gewählt werden. Vielleicht kann man ja ein Telnet oder ssh aufmachen und den dann killen. Ansonsten kann es schon sein, dass das System stehen bleibt :D Es gelten natürlich die gleichen Regeln wie beim 'kill' auf der Kommandoszeile, mehr kann auch diese Geschichte hier nicht. Nebenbemerkung, der exec mit dem kill geht nur, weil der Sys-Apache mit root-Rechten läuft ... im User-Apache ging da nichts von ...

Itari
 

Anhänge

  • application2.png
    application2.png
    337 Bytes · Aufrufe: 208
  • cross.png
    cross.png
    764 Bytes · Aufrufe: 207
  • ps2.jpg
    ps2.jpg
    20,1 KB · Aufrufe: 206
  • ps3.jpg
    ps3.jpg
    45,2 KB · Aufrufe: 206

QTip

Super-Moderator
Teammitglied
Mitglied seit
04. Sep 2008
Beiträge
2.341
Punkte für Reaktionen
13
Punkte
84
funktioniert ebenfalls :D

schön wären noch ein Filter und wenn per Doppelklick direkt das Infofenster angezeigt würde. Dazu noch das Infofenster freibeweglich ;)
 

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
funktioniert ebenfalls :D

schön wären noch ein Filter und wenn per Doppelklick direkt das Infofenster angezeigt würde. Dazu noch das Infofenster freibeweglich ;)

Ich schreib es mir mal auf meine TODO-Liste ... ist ja nicht abwegig, was du dir da wünschst ...

Itari
 

QTip

Super-Moderator
Teammitglied
Mitglied seit
04. Sep 2008
Beiträge
2.341
Punkte für Reaktionen
13
Punkte
84
danke :) ein "Refresh"-Button wäre auch nicht schlecht *zwinker*
 

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Schritt 4

... und wenn per Doppelklick direkt das Infofenster angezeigt würde. Dazu noch das Infofenster freibeweglich ...

Also 'Doppelklick':

Rich (BBCode):
grid.addListener('rowcontextmenu', onPSListingGridPanelContextMenu);
grid.addListener('rowdblclick', onPSListingGridPanelDblClick);

und dann die Methode onPSListingGridPanelDblClick irgendwo davor schön platzieren:

Rich (BBCode):
    function onPSListingGridPanelDblClick(grid, rowIndex, e) {
      e.stopEvent();
      var coords = e.getXY();
      grid.selModel.selectRow(rowIndex);
      PSListingSelectedRow=rowIndex;
      infoPSContextMenu();
      }

und 'freibewegliches Infofenster':

Rich (BBCode):
      var p = new Ext.Panel({
        title: 'Process Information PID: ' + info_pid,
        renderTo: Ext.getBody(),        
        x:100,y:20,floating:true,frame:true,width:400,shadow: true,collapsible:false,
        bodyStyle: 'padding:5px;font-family:Consolas,Courier New,Courier;font-size:8pt;',
        autoLoad: {url:'/phpsrc/ps/psinfo.php',params:'pid=' + info_pid},
        tools:[
          { id: 'print', hidden: false, handler: function(event, toolEl, panel){ window.print(); }},     
          { id: 'close', hidden: false, handler: function(event, toolEl, panel){ panel.hide(); }}
          ],
        draggable: {
          insertProxy: false,
          onDrag : function(e){
            var pel = this.proxy.getEl();
            this.x = pel.getLeft(true);
            this.y = pel.getTop(true);
            },
          endDrag : function(e){
            this.panel.setPosition(this.x, this.y);
            }
          }
         });

Ich hab das Teil ein wenig modifiziert, so dass man das <div>-Tag mit der id=container nicht mehr braucht. Sieht insgesamt auch ein wenig schöner aus.

Itari
 

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Schritt 5

Seitenweises Anzeigen mit Refresh-Button (Paging-Toolbar) ...

Diese Paging ist eines der Themen, um die sich die Entwickler von Ext JS ein wenig herummogeln ... und obwohl es in der Version 3.0 andere Verhältnisse gibt, scheint das Problem dort auch nicht gelöst zu sein. Paging geht, aber es ist vorgesehen, dass man sich die anzuzeigenden Häppchen jeweils von außen holt und das, obwohl man ja einen internen Zwischenspeicher hat. Es gibt zwar einige Ansätze aus der Community, das auch rein intern zu lösen, aber das wäre jetzt eine wenig zu viel auf einmal und auch recht anspruchsvoll.

Deshalb die Lösung mit der Versorgung aus dem externen PHP-Skript, welches wir nun umschreiben müssen. Damit Sortierungen und der danach erfolgende Abgriff einer Page (wir machen hier 20 Zeilen) auch für aller Spalten funzt, müssen wir einen PHP-Array als Zwischenspeicher bilden. Hierauf dann die Sortierung jagen (wobei ich nur nach einer Spalte sortieren lasse) und dann das Abzählen der Zeilen, die für die Anzeige in Frage kommen sollen. Zudem habe ich jetzt das JSON-Format statt XML eingesetzt, weil es einfacher ist zu erzeugen. Deswegen ändert sich auch ein wenig in der ps.js-Datei an dieser Stelle.

Also zunächst die ps.php:

PHP:
<?php
$start = ($_REQUEST["start"] == null)? 0  : $_REQUEST["start"];
$count = ($_REQUEST["limit"] == null)? 20 : $_REQUEST["limit"];
exec('ps',$out0);
array_shift($out0);array_pop($out0);array_pop($out0);
$nbrows = count($out0);
$maxlimit=$start+$limit<$nbrows?$start+$limit:$nbrows;
foreach($out0 as $item) {
  $part = preg_split('/ +/',' '.$item);
  $pid = $part[1];array_shift($part);
  unset($out); exec('grep PPid /proc/'.$pid.'/status',$out); $ppid = preg_replace('/^PPid:\s*/','',$out[0]);
  unset($out); exec('awk "{print \$18}" /proc/'.$pid.'/stat',$out); $prio = $out[0];
  unset($out); exec('awk "{print \$19}" /proc/'.$pid.'/stat',$out); $nice = $out[0];
  $uid = $part[1];array_shift($part);
  if (preg_match('/\d+/',$part[1])) { $vmsize = $part[1];array_shift($part); } else $vmsize = '';
  $status = preg_replace('/\</','<',$part[1]);array_shift($part);
  if ($part[1]=='<') { $status.='<';array_shift($part);}      
  if ($part[0][0]=='[') 
    foreach($part as $ppart) $command.=$ppart.' ';
  else { 
    unset($out); exec('cat /proc/'.$pid.'/cmdline',$out); $command=preg_replace('/\x00/',' ',$out[0]); 
  } 
  $data[] = array('PID'=>$pid,'PPID'=>$ppid,'Prio'=>$prio,'Nice'=>$nice,'Uid'=>$uid,'VmSize'=>$vmsize,'Status'=>$status,'Command'=>$command);
}
$sort = ($_REQUEST['sort'] == null)? 'PID'  : $_REQUEST['sort'];
foreach ($data as $key => $row) $sel[$key] = $row[$sort];
if ($_REQUEST['dir'] == 'DESC') array_multisort($sel, SORT_DESC, $data);
else array_multisort($sel, SORT_ASC, $data);
for($i=$start; $i<$maxlimit ;$i++) $data_result[] = $data[$i];
$jsonresult = json_encode($data_result);
if ($nbrows > 0)
  print '({"total":"'.$nbrows.'","results":'.$jsonresult.'})';
else
  print '({"total":"0", "results":""})';
?>

Über die $_REQUEST-Felder wird das Skript versorgt (start, limit, sort, dir[ection]). Der Multisort ist ein wenig heftig ... am besten die Beispiele dazu im PHP-Handbuch anschauen.

Und nun der Teil in der ps.js, der sich mit der Sortierung beschäftigt:

Rich (BBCode):
   var store = new Ext.data.Store({
        url: '/phpsrc/ps/ps.php',
        remoteSort : true, 
        reader: new Ext.data.JsonReader({   
               root: 'results',
               totalProperty: 'total',
               id: 'PID'
               },[ 
               {name:'PPID',type:'float'},'Uid',{name:'VmSize',type:'float'},'Status','Command',
               {name:'PID',type:'float'},{name:'Nice',type:'float'},{name:'Prio',type:'float'}
           ])
    });

und die Seitensteurung:

Rich (BBCode):
    var grid = new Ext.grid.GridPanel({
        store: store,
        title: 'ps',
        columns: [
            {header: "PID", width: 40, dataIndex: 'PID', sortable: true, align: 'right'},
            {header: "PPID", width: 40, dataIndex: 'PPID', sortable: true, align: 'right'},            
            {header: "Uid", width: 65, dataIndex: 'Uid', sortable: true},          
            {header: "VmSize", width: 65, dataIndex: 'VmSize', sortable: true, align: 'right',renderer: 'mille'},
            {header: "Prio", width: 40, dataIndex: 'Prio', sortable: true, align: 'right'},
            {header: "Nice", width: 40, dataIndex: 'Nice', sortable: true, align: 'right'},                        
            {header: "Status", width: 55, dataIndex: 'Status', sortable: true},
            {header: "Command", width: 1200, dataIndex: 'Command', sortable: true}                    
            ],
        renderTo:'ps-grid',
        stripeRows: true,
        height:510,
        bbar: new Ext.PagingToolbar({
	          store: store,
	          pageSize: 20
	          })
    });
    store.load({params: {start: 0, limit: 20}});

Das war es auch schon. Blättern geht - Sortieren und Blättern geht - Refresh geht ...

Itari
.
 

Anhänge

  • ps4.jpg
    ps4.jpg
    41,5 KB · Aufrufe: 147

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Schritt 6

Jetzt mal ein Beispiel mit Eingabe usw. ... die Cronjob-3rdparty-Apps ;)

Wie bei dem vorherigen Beispiel halt alles für eine 3rdparty-apps vorbeireiten ... application.cfg und die Verzeichnisse ...

Die cronjobs.html-Datei sieht dann so aus:

Rich (BBCode):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Cronjobs</title>
    <script type="text/javascript" src="/scripts/ext-2.2.1/adapter/yui/yui-utilities.js"></script>
    <script type="text/javascript" src="/scripts/ext-2.2.1/adapter/yui/ext-yui-adapter.js"></script>
    <script type="text/javascript" src="/scripts/ext-2.2.1/ext-all.js"></script>   
    <script type="text/javascript" src="cronjobs.js"></script>
    <link rel="stylesheet" type="text/css" href="/scripts/ext-2.2.1/resources/css/ext-all.css">
    <link rel="stylesheet" type="text/css" href="/scripts/ext-2.2.1/resources/css/ytheme-vista.css">
<style>
.reload{background:url(icons/page_refresh.png) no-repeat 0 2px !important;}
.add{background:url(icons/page_white_paintbrush.png) no-repeat 0 2px !important;}
.save{background:url(icons/page_save.png) no-repeat 0 2px !important;}
</style>
</head>
<body> 
</body>
</html>

Man hätte das Style-Tag auch als Datei auslagern können ... die Icons hab ich hier heruntergeladen.

Die cronjobs.php sieht so aus:

PHP:
<?php
if ($_REQUEST['task'] == 'save') {
  $out='#minute hour    mday    month   wday    who     command';
  $out.=$_REQUEST['data'];
  file_put_contents('/etc/crontab', stripslashes($out));
  exec('killall crond;/usr/sbin/crond -l 8 -L /var/log/cron.log');  
  print '1';
  }
else {
  exec('cat /etc/crontab',$out0);
  array_shift($out0);
  $nbrows = count($out0);
  foreach($out0 as $item) {
    $part = preg_split('/\s+/',' '.$item);
    if ($part[1][0]=='#') { $comment = '#'; $minute = substr($part[1],1); } 
    else $minute = $part[1]; array_shift($part);
    $hour  = $part[1];array_shift($part);
    $mday  = $part[1];array_shift($part);
    $month = $part[1];array_shift($part);
    $wday  = $part[1];array_shift($part); 
    $who   = $part[1];array_shift($part);array_shift($part);unset($command);    
    foreach($part as $ppart) $command.=$ppart.' ';
    $data[] = array('comment'=>$comment,'minute'=>$minute,'hour'=>$hour,'mday'=>$mday,'month'=>$month,'wday'=>$wday,'who'=>$who,'command'=>$command);
  }
  $jsonresult = json_encode($data);
  if ($nbrows > 0)
    print '({"total":"'.$nbrows.'","results":'.$jsonresult.'})';
  else
    print '({"total":"0", "results":""})';
}
?>

Ich nutze dieses Skript sowohl fürs Abholen als auch zu Neu-Schreiben der /etc/crontab. Bitte macht vorher eine Kopie der /etc/crontab, falls doch mal was schief gehen sollte ;)

Fortsetzung im nächsten Post ...

.
 

Anhänge

  • cronjobs.jpg
    cronjobs.jpg
    27,5 KB · Aufrufe: 165

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Schritt 7

Fortsetzung vom vorherigen Post ...


So und die cronjobs.js kommt nun:

Rich (BBCode):
Ext.onReady(function(){ 
    var cronjob = Ext.data.Record.create([
           {name: 'comment',   type: 'string'},
           {name: 'minute',    type: 'string'},
           {name: 'hour',      type: 'string'},
           {name: 'mday',      type: 'string'},
           {name: 'month',     type: 'string'},
           {name: 'wday',      type: 'string'},
           {name: 'who',       type: 'string'},
           {name: 'command',   type: 'string'}
      ]);
   var store = new Ext.data.Store({
        url: '/phpsrc/cronjobs/cronjobs.php',
        reader: new Ext.data.JsonReader({   
               root: 'results',
               totalProperty: 'total',
               }, cronjob )
        });  
    var fm = Ext.form;     
    var grid = new Ext.grid.EditorGridPanel({
        store: store,
        title: '/etc/crontab',
        columns: [
            {header: "#",      width: 20,  dataIndex: 'comment', sortable: false, menuDisabled: true,
                                                                 editor: new fm.TextField({ allowBlank: true })},
            {header: "minute", width: 100, dataIndex: 'minute',  sortable: true, 
             tooltip: '*|0..59: 8 1,3,5 2-5 * */2',              editor: new fm.TextField({ allowBlank: false })},            
            {header: "hour",   width: 60,  dataIndex: 'hour',    sortable: true, 
             tooltip: '*|0..23: 8 1,3,5 2-5 * */2',              editor: new fm.TextField({ allowBlank: false })},          
            {header: "mday",   width: 40,  dataIndex: 'mday',    sortable: true, 
             tooltip: '*|1..31: 8 1,3,5 2-5 * */2',              editor: new fm.TextField({ allowBlank: false })},
            {header: "month",  width: 40,  dataIndex: 'month',   sortable: true, 
            tooltip: '*|1..12: 8 1,3,5 2-5 * */2',               editor: new fm.TextField({ allowBlank: false })},
            {header: "wday",   width: 40,  dataIndex: 'wday',    sortable: true, 
            tooltip: '*|0..6: so=0 sa=6',                        editor: new fm.TextField({ allowBlank: false })},                        
            {header: "who",    width: 60,  dataIndex: 'who',     sortable: true, editor: new fm.TextField({ allowBlank: false })},
            {header: "command",width: 400, dataIndex: 'command', sortable: true, editor: new fm.TextField({ allowBlank: false })}                
            ],
        renderTo: Ext.getBody(),
        stripeRows: true,
        height:510,
        clicksToEdit:2,
        enableDragDrop   : true,
        ddGroup          : 'GridDDGroup',
        tbar: [
          {text: 'Reload',
           iconCls: 'reload',          
           handler: function() { window.location.reload(); }},'-',   
          {text: 'Add Line',
           iconCls: 'add',
           handler : function(){
             var lastline = store.getCount(); 
             var line = new cronjob ({
                comment: '',
                mday:    '*',
                month:   '*',
                wday:    '*',
                who:     'root',
                id: lastline
                });
              grid.stopEditing();
              grid.store.add(line);                           
              grid.startEditing(lastline, 1);
              }},'-',
          {text: 'Save & Restart crond',
           iconCls: 'save',
           handler: saveCronjobs}    
           ]
      });
    var ListingSelectedRow;
    var ClipRow = new cronjob();
    ListingContextMenu = new Ext.menu.Menu({
      id: 'ListingGridPanelContextMenu',
      items: [
        { text: 'copy',   handler: copyContextMenu,   icon: 'icons/page_copy.png'},
        { text: 'paste before',  handler: paste1ContextMenu,  icon: 'icons/page_white_get.png'},
        { text: 'paste after',   handler: paste2ContextMenu,  icon: 'icons/page_white_put.png'},                
        { text: 'delete', handler: deleteContextMenu, icon: 'icons/cross.png'}        
      ]
      });    
    function onListingGridPanelContextMenu(grid, rowIndex, e) {
      e.stopEvent();
      grid.stopEditing();      
      var coords = e.getXY();
      ListingContextMenu.rowRecord = grid.store.getAt(rowIndex);
      ListingSelectedRow=rowIndex;
      ListingContextMenu.showAt([coords[0], coords[1]]);
      } 
   function copyContextMenu(){
      ClipRow = grid.store.getAt(ListingSelectedRow);
      } 
   function paste1ContextMenu(){
      if (ClipRow.data != undefined) {
        var lastline = grid.store.getCount(); 
        grid.stopEditing();
        var line = new cronjob ({
                comment: ClipRow.data.comment,
                minute:  ClipRow.data.minute,                
                hour:    ClipRow.data.hour,
                mday:    ClipRow.data.mday,
                month:   ClipRow.data.month,
                wday:    ClipRow.data.wday,
                who:     ClipRow.data.who,
                command: ClipRow.data.command,
                id:      lastline
                }); 
        grid.store.insert(ListingSelectedRow,line);
         }      
      }
   function paste2ContextMenu(){
      if (ClipRow.data != undefined) {
        var lastline = grid.store.getCount(); 
        grid.stopEditing();
        var line = new cronjob ({
                comment: ClipRow.data.comment,
                minute:  ClipRow.data.minute,                
                hour:    ClipRow.data.hour,
                mday:    ClipRow.data.mday,
                month:   ClipRow.data.month,
                wday:    ClipRow.data.wday,
                who:     ClipRow.data.who,
                command: ClipRow.data.command,
                id:      lastline
                }); 
        grid.store.insert(ListingSelectedRow+1,line);
         }      
      }      
   function deleteContextMenu(){
      grid.store.remove(grid.store.getAt(ListingSelectedRow));
      }                     
   function saveCronjobs(){
     var out='\n';
     function printRow(record, index, allItems) {
       out+=record.data.comment+record.data.minute+'\t'+record.data.hour+'\t'+record.data.mday+'\t'+record.data.month+'\t';
       out+=record.data.wday+'\t'+record.data.who+'\t'+record.data.command+'\n'
     }
     store.each(printRow);
     alert(out);
     Ext.Ajax.request({   
        waitMsg: 'Please wait...',
        url: 'cronjobs.php',
        params: {
         task: "save",
         data: out
        }, 
        success: function(response){							
         var result=eval(response.responseText);
         switch(result){
         case 1:
            store.commitChanges();   
            store.reload();          
            break;					
         default:
            Ext.MessageBox.alert('Uh uh...','We couldn\'t save');
            break;
         }
        },
        failure: function(response){
         var result=response.responseText;
         Ext.MessageBox.alert('error','could not connect to the cronjobs.php. retry later');		
        }									    
      });   
   }
             
   store.load();
   grid.addListener('rowcontextmenu', onListingGridPanelContextMenu);   
});

Klar können wir das Teil irgendwann zu einem spk-Paket schnüren ... aber erst mal anschauen und testen ;) Hatte ich schon gesagt, dass man mit einem Doppelklick auf die Felder den Editor aktiviert?

Wie immer geht alles auf eigene Kappe :D

Itari

.
 

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Macht eigentlich jemand außer QTip hier mit? Falls der Workshop kein Interesse findet, muss ich ja nicht unbedingt weitere Bespiele proggen. Ansonsten wäre es schön, wenn der/die ein oder andere auch eigene Skripte mit Ext JS hier vorstellen würde ... ;)

Itari
 

QTip

Super-Moderator
Teammitglied
Mitglied seit
04. Sep 2008
Beiträge
2.341
Punkte für Reaktionen
13
Punkte
84
Hi itari,

hab ab dem cronjob Teil nicht mehr mitgemacht, da ich mit der Weiterentwicklung vom Rootkit Hunter WebUI beschäftigt bin. Werde aber so schnell wie möglich den Rest deiner Skripte testen und Feedback geben.
Finde deinen Workshop echt toll und plane den Rootkit Hunter in der nächsten (nicht kommenden) Version in ExtJS zu coden bzw. umzuschreiben.
 

Matthieu

Benutzer
Mitglied seit
03. Nov 2008
Beiträge
13.222
Punkte für Reaktionen
88
Punkte
344
Ich habe mir auch alle Posts durchgelesen, kann aber leider nicht Testen weil mir dazu die Zeit fehlt. Schreib doch deinen Workshop ins Wiki damit er nicht verloren geht und dann mal sehen ... ich denke viele sind auch momentan im Urlaub und können daher gar nicht teilnehmen.

MfG Matthieu

Und vielen vielen Dank für ein weiteres deiner tollen Projekte.
 

QTip

Super-Moderator
Teammitglied
Mitglied seit
04. Sep 2008
Beiträge
2.341
Punkte für Reaktionen
13
Punkte
84
Cronjobs funktioniert auch. Allerdings funktioniert die Info beim System Processes nicht mehr; weder per Contextmenu noch per Doppelklick.
Muss ich mal genauer untersuchen, ansonsten super Sache das ExtJS und der Workshop sowieso :)
 

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Schritt 8

Heute mal wieder was. Download der neustes ExtJS-Version 3 ist angesagt. Am besten in ein Verzeichnis /volume1/web/extJS/ext-3.0.0

Ich hab mir gedacht, mal eine kleine WebEditor-Geschichte vorzunehmen.

Also /volume1/web/extJS/editor.html:

Rich (BBCode):
<?xml version="1.0" encoding="iso8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>editor</title>  
    <link rel="stylesheet" type="text/css" href="ext-3.0.0/resources/css/ext-all.css">    
    <script type="text/javascript" src="ext-3.0.0/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="ext-3.0.0/ext-all.js"></script>
    <script type="text/javascript" src="editor.js"></script>
    <link rel="stylesheet" type="text/css" href="ext-3.0.0/shared/examples.css">
<style>
.open{background:url(icons/doc_page.png) no-repeat 0 0px !important;}
.save{background:url(icons/disk.png) no-repeat 0 0px !important;}
</style>
</head>
<body></body>
</html>

Die php-File für den AJAX-Request ist diesmal ganz klein /volume1/web/extJS/editor.php:

PHP:
<?php
if ($_REQUEST['action'] == 'save' && $_REQUEST['path'] != '')
   file_put_contents($_REQUEST['path'],stripslashes(preg_replace('/\x5Ct/',chr(9),$_REQUEST['content'])));
print(file_get_contents($_REQUEST['path']));
?>

Die stripslashes()-Funktion hab ich drinne, weil ich nun nichts in PHP umkonfiguriert habe - wer das nicht braucht, kanns ja draußen lassen.

Und nun die /volume1/web/extJS/editor.js:

Rich (BBCode):
Ext.onReady(function(){
var editor = new Ext.Panel({
    title: 'Editor', width: 700, height: 500, frame: true,
    tools: [{ id: 'close', hidden: false, handler: function(event, toolEl, panel){ panel.hide(); }}],
    tbar: new Ext.Toolbar({
           height: 25,
           items: [
          {text: 'Open', iconCls: 'open', handler: function(){
             Ext.Ajax.request({   
                waitMsg: 'Please wait...',
                url:     'editor.php',
                params: { action: 'open', path: editor.get(0).getValue() },
                success: function(response){ var result=response.responseText; editor.get(1).setValue(result); },
                failure: function(response){ Ext.MessageBox.alert('Could not read'); }                   
              })            
          }},'-',
          {text: 'Save', iconCls: 'save', handler: function(){   
             Ext.Ajax.request({   
                waitMsg: 'Please wait...',
                url:     'editor.php',
                params: { action: 'save', path: editor.get(0).getValue(), content: editor.get(1).getValue() },
                success: function(response){ var result=response.responseText; editor.get(1).setValue(result); },
                failure: function(response){ Ext.MessageBox.alert('Could not write'); }                   
              })  
            }               
          }                   
          ]}),  
    items: [{ xtype: 'textfield', height: 20, width: 688, style: {font: '11px Arial' }},
            { xtype: 'textarea', width:  688, height: 418, preventScrollbars: true, style: {font: '11px Consolas' }}
           ]
});
editor.render(document.body);  
})

Ich denke, das ist hier wirklich sehr überschaubar ... ist ja nur ein ganz einfacher Editor. Das mein PHP überall hingucken darf, kann ich auch die /etc/crontab lesen, allerdings wegen der Zugriffsrecht nicht schreiben. Sinnvollerweise würde man den Editor ja auch als 3rdparty-apps für den SYS-Apache integrieren. Dann dürfte er alles, könnte aber auch nur aus der Oberh-heit des DS_Managers gestartet werden.

Randbemerkung: Ich hab eine Tabulator-Eingabe-Alternative mit '\t' formuliert, die im PHP-Skript dann substituiert wird. Also TABs wären damit möglich und andere Sonderzeichen könnte man ähnlich behandeln.

Itari
.

PS. Ich hab nichts dagegen, wenn das jemand als spk verpackt oder ergänzt. Ansonsten gilt wie immer, es geht auf eigene Kappe. Die Skripte stehen wie immer unter GPL3.
 

Anhänge

  • editor.jpg
    editor.jpg
    29,9 KB · Aufrufe: 109

itari

Benutzer
Mitglied seit
15. Mai 2008
Beiträge
21.900
Punkte für Reaktionen
14
Punkte
0
Schritt 9

Ein HTML-Editor.

Voraussetzungen:
1] Installation von extJS 3.0.0 ins Verzeichnis /volume1/web/extJS/ext-3.0.0
2] Installation der Icons von hier ins Verzeichnis /volume1/web/extJS/icons
3] entpacken der Anlage nach /volume1/web/writer

Die zusätzlichen Editor-Plugins bis auf replace sind dankenswerterweise von VinylFox (man könnte sie hier herunterladen). Das Plug-in replace benutzt die in JavaScript für die Bildung von Regulären Ausdrücken vorgesehenen Möglichkeiten. Options-Schalter ist 'g'.

Der Editor schlägt Wurzel im Verzeichnis /volume1/web/writer/root, damit andere Dateien nicht aus Versehen beeinflusst werden. Das lässt sich natürlich abändern. Der Gedanke ist allerdings mehr der, dass man hier sein Archiv für diesen Editor aufschlägt und seine Gedanken, Foren- und Blogbeiträge usw. hierarchisch ablegt. Der Editor basiert auf MIDAS; er produziert zwar kein well-formed HTML, trotzdem macht es Sinn, als Dateiendung .html zu verwenden.

Man kann beim Aufruf einen Dateinamen mitgeben: ...writer.html?bedienung.html. Bilder und iframes werden im Editor angezeigt. Undo ist mit Strg-Z möglich ...

Dies ist ein Übungs-Beispiel, welches man zum Erlernen der extJS-Logik benutzen sollte. Man sollte an die Gebrauchsfähigkeit also nicht allzu hohe Erwartungen stellen - und wenn, dann die Motivation zur Verfeinerung des Codes verwenden. ;)

Wie immer auf eigene Kappe und unter GPL3.

Itari

PS. ich hab da Teil nur mit dem FF3.5 getestet ...

.
 

Anhänge

  • writer.zip
    13,2 KB · Aufrufe: 5
  • writer.jpg
    writer.jpg
    49,9 KB · Aufrufe: 99
Zuletzt bearbeitet:

sowosamma

Benutzer
Mitglied seit
16. Jun 2009
Beiträge
565
Punkte für Reaktionen
0
Punkte
42
Macht eigentlich jemand außer QTip hier mit? Falls der Workshop kein Interesse findet, muss ich ja nicht unbedingt weitere Bespiele proggen. Ansonsten wäre es schön, wenn der/die ein oder andere auch eigene Skripte mit Ext JS hier vorstellen würde ... ;)

Itari

Habe den Workshop heute zum ersten Mal gelesen und finde es super spannend was Du mit der DS alles auf die Beine stellst. Woebi ich zugeben muss, dass ich gerade mal 10% davon verstehe was da genau vor sich geht. Aber was noch nicht ist kann ja noch werden....

/Andi
 
Status
Für weitere Antworten geschlossen.
 

Kaffeautomat

Wenn du das Forum hilfreich findest oder uns unterstützen möchtest, dann gib uns doch einfach einen Kaffee aus.

Als Dankeschön schalten wir deinen Account werbefrei.

:coffee:

Hier gehts zum Kaffeeautomat