diff --git a/xCAT-UI/css/ajaxterm.css b/xCAT-UI/css/ajaxterm.css
new file mode 100644
index 000000000..b9a5f8771
--- /dev/null
+++ b/xCAT-UI/css/ajaxterm.css
@@ -0,0 +1,64 @@
+pre.stat {
+ margin: 0px;
+ padding: 4px;
+ display: block;
+ font-family: monospace;
+ white-space: pre;
+ background-color: black;
+ border-top: 1px solid black;
+ color: white;
+}
+pre.stat span {
+ padding: 0px;
+}
+pre.stat .on {
+ background-color: #080;
+ font-weight: bold;
+ color: white;
+ cursor: pointer;
+}
+pre.stat .off {
+ background-color: #888;
+ font-weight: bold;
+ color: white;
+ cursor: pointer;
+}
+pre.term {
+ margin: 0px;
+ padding: 4px;
+ display: block;
+ font-family: monospace;
+ white-space: pre;
+ background-color: black;
+ border-top: 1px solid white;
+ color: #eee;
+}
+pre.term span.f0 { color: #000; }
+pre.term span.f1 { color: #b00; }
+pre.term span.f2 { color: #0b0; }
+pre.term span.f3 { color: #bb0; }
+pre.term span.f4 { color: #00b; }
+pre.term span.f5 { color: #b0b; }
+pre.term span.f6 { color: #0bb; }
+pre.term span.f7 { color: #bbb; }
+pre.term span.f8 { color: #666; }
+pre.term span.f9 { color: #f00; }
+pre.term span.f10 { color: #0f0; }
+pre.term span.f11 { color: #ff0; }
+pre.term span.f12 { color: #00f; }
+pre.term span.f13 { color: #f0f; }
+pre.term span.f14 { color: #0ff; }
+pre.term span.f15 { color: #fff; }
+pre.term span.b0 { background-color: #000; }
+pre.term span.b1 { background-color: #b00; }
+pre.term span.b2 { background-color: #0b0; }
+pre.term span.b3 { background-color: #bb0; }
+pre.term span.b4 { background-color: #00b; }
+pre.term span.b5 { background-color: #b0b; }
+pre.term span.b6 { background-color: #0bb; }
+pre.term span.b7 { background-color: #bbb; }
+
+body { background-color: #888; }
+#term {
+ float: left;
+}
diff --git a/xCAT-UI/js/nodes/nodes.js b/xCAT-UI/js/nodes/nodes.js
index 8b4b85d8f..716b1fb54 100644
--- a/xCAT-UI/js/nodes/nodes.js
+++ b/xCAT-UI/js/nodes/nodes.js
@@ -374,6 +374,17 @@ function loadNodes(data) {
}
});
+ /*
+ * Open the Rcons page
+ */
+ var rcons = $('Open Rcons');
+ rcons.bind('click', function(event){
+ var tgtNodes = getNodesChecked('nodesDataTable');
+ if (tgtNodes) {
+ loadRoncsPage(tgtNodes);
+ }
+ });
+
/*
* Advanced
*/
@@ -384,7 +395,14 @@ function loadNodes(data) {
var powerActionMenu = createMenu(powerActions);
// Advanced actions
- var advancedActions = [ boot2NetworkLnk, scriptLnk, setBootStateLnk, updateLnk ];
+ var advancedActions;
+ if ('compute' == group){
+ advancedActions = [ boot2NetworkLnk, scriptLnk, setBootStateLnk, updateLnk, rcons ];
+ }
+ else{
+ advancedActions = [ boot2NetworkLnk, scriptLnk, setBootStateLnk, updateLnk ];
+ }
+
var advancedActionMenu = createMenu(advancedActions);
/**
@@ -1337,4 +1355,24 @@ function selectAllCheckbox(event, obj) {
var status = obj.attr('checked');
$('#' + datatableId + ' :checkbox').attr('checked', status);
event.stopPropagation();
+}
+
+function loadRoncsPage(tgtNodes){
+ var hostName = window.location.host;
+ var urlPath = window.location.pathname;
+ var redirectUrl = 'https://';
+ var pos = 0;
+ //we only support one node
+ if (-1 != tgtNodes.indexOf(',')){
+ alert("Sorry, the Rcons Page only support one node.");
+ return;
+ }
+
+ redirectUrl += hostName;
+ pos = urlPath.lastIndexOf('/');
+ redirectUrl += urlPath.substring(0, pos + 1);
+ redirectUrl += 'rconsShow.php';
+
+ //open the rcons page
+ window.open(redirectUrl + "?rconsnd=" + tgtNodes, '', "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=no,width=670,height=436");
}
\ No newline at end of file
diff --git a/xCAT-UI/js/rcons/rcons.js b/xCAT-UI/js/rcons/rcons.js
new file mode 100644
index 000000000..f9501d91f
--- /dev/null
+++ b/xCAT-UI/js/rcons/rcons.js
@@ -0,0 +1,226 @@
+rconsTerm = function(nodeName, height, width){
+ var sid=nodeName;
+ var keyBuf = [];
+ var receivingFlag = false;
+ var sendTimeout;
+ var errorTimeout;
+ var queryStable = 's=' + sid + '&w=' + height + '&h=' + width + '&c=1&k=';
+ var maxDelay = 200;
+ var firstFlag = true;
+
+ var workingStatus;
+ var termArea;
+ var errorArea;
+
+ var ie=0;
+ if(window.ActiveXObject){
+ ie=1;
+ }
+
+ rconsInit();
+ //init
+ function rconsInit(){
+ //create status, configure the css
+ workingStatus = $('.');
+ workingStatus.attr('class', 'off');
+
+ //create the disconnect button
+ var disconnectButton = $('Disconnect');
+ disconnectButton.bind('click', function(){
+ window.close();
+ });
+
+ //create the control panel, add to the rcons div
+ var controlPanel = $('
');
+ $('#term').append(controlPanel);
+
+ //create the error erea
+ errorArea = $('');
+
+ //add all item to controlPanel
+ controlPanel.append(workingStatus);
+ controlPanel.append(disconnectButton);
+ controlPanel.append(errorArea);
+
+ //create the termArea
+ termArea = $('');
+ $('#term').append(termArea);
+
+ //bind keypress event
+ document.onkeypress=rconsKeypress;
+ document.onkeydown=rconsKeydown;
+ window.onbeforeunload = function(){
+ rconsDisconnect();
+ alert("This rcons page is closed.");
+ };
+
+ rconsSend();
+ }
+
+ //close the connection
+ function rconsDisconnect(){
+ window.clearTimeout(sendTimeout);
+ window.clearTimeout(errorTimeout);
+
+ $.ajax({
+ type : "POST",
+ url : "lib/rcons.php",
+ data : queryStable + '&q=1',
+ dataType : 'json'
+ });
+ }
+
+ //translate the key press
+ function rconsKeypress(event){
+ if (!event) var event=window.event;
+ var kc;
+ var k="";
+ if (event.keyCode)
+ kc=event.keyCode;
+ if (event.which)
+ kc=event.which;
+ if (event.altKey) {
+ if (kc>=65 && kc<=90)
+ kc+=32;
+ if (kc>=97 && kc<=122) {
+ k=String.fromCharCode(27)+String.fromCharCode(kc);
+ }
+ } else if (event.ctrlKey) {
+ if (kc>=65 && kc<=90) k=String.fromCharCode(kc-64); // Ctrl-A..Z
+ else if (kc>=97 && kc<=122) k=String.fromCharCode(kc-96); // Ctrl-A..Z
+ else if (kc==54) k=String.fromCharCode(30); // Ctrl-^
+ else if (kc==109) k=String.fromCharCode(31); // Ctrl-_
+ else if (kc==219) k=String.fromCharCode(27); // Ctrl-[
+ else if (kc==220) k=String.fromCharCode(28); // Ctrl-\
+ else if (kc==221) k=String.fromCharCode(29); // Ctrl-]
+ else if (kc==219) k=String.fromCharCode(29); // Ctrl-]
+ else if (kc==219) k=String.fromCharCode(0); // Ctrl-@
+ } else if (event.which==0) {
+ if (kc==9) k=String.fromCharCode(9); // Tab
+ else if (kc==8) k=String.fromCharCode(127); // Backspace
+ else if (kc==27) k=String.fromCharCode(27); // Escape
+ else {
+ if (kc==33) k="[5~"; // PgUp
+ else if (kc==34) k="[6~"; // PgDn
+ else if (kc==35) k="[4~"; // End
+ else if (kc==36) k="[1~"; // Home
+ else if (kc==37) k="[D"; // Left
+ else if (kc==38) k="[A"; // Up
+ else if (kc==39) k="[C"; // Right
+ else if (kc==40) k="[B"; // Down
+ else if (kc==45) k="[2~"; // Ins
+ else if (kc==46) k="[3~"; // Del
+ else if (kc==112) k="[[A"; // F1
+ else if (kc==113) k="[[B"; // F2
+ else if (kc==114) k="[[C"; // F3
+ else if (kc==115) k="[[D"; // F4
+ else if (kc==116) k="[[E"; // F5
+ else if (kc==117) k="[17~"; // F6
+ else if (kc==118) k="[18~"; // F7
+ else if (kc==119) k="[19~"; // F8
+ else if (kc==120) k="[20~"; // F9
+ else if (kc==121) k="[21~"; // F10
+ else if (kc==122) k="[23~"; // F11
+ else if (kc==123) k="[24~"; // F12
+ if (k.length) {
+ k=String.fromCharCode(27)+k;
+ }
+ }
+ } else {
+ if (kc==8)
+ k=String.fromCharCode(127); // Backspace
+ else
+ k=String.fromCharCode(kc);
+ }
+ if(k.length) {
+ if(k=="+") {
+ rconsQueue("%2B");
+ } else {
+ rconsQueue(escape(k));
+ }
+ }
+ event.cancelBubble=true;
+ if (event.stopPropagation) event.stopPropagation();
+ if (event.preventDefault) event.preventDefault();
+ return false;
+ }
+
+ //translate the key press, same with rconsKeypress
+ function rconsKeydown(event){
+ if (!event) var event=window.event;
+ if (ie) {
+ o={9:1,8:1,27:1,33:1,34:1,35:1,36:1,37:1,38:1,39:1,40:1,45:1,46:1,112:1, 113:1,114:1,115:1,116:1,117:1,118:1,119:1,120:1,121:1,122:1,123:1};
+ if (o[event.keyCode] || event.ctrlKey || event.altKey) {
+ event.which=0;
+ return keypress(event);
+ }
+ }
+ }
+
+ //send the command and request to server
+ function rconsSend(){
+ var keyPressList = '';
+ var requireString = '';
+ if(receivingFlag){
+ return;
+ }
+
+ receivingFlag = true;
+ workingStatus.attr('class', 'on');
+
+ while(keyBuf.length > 0){
+ keyPressList += keyBuf.pop();
+ }
+
+ if (firstFlag){
+ requireString = queryStable + keyPressList + '&f=1';
+ firstFlag = false;
+ }
+ else{
+ requireString = queryStable + keyPressList;
+ }
+
+ $.ajax({
+ type : "POST",
+ url : "lib/rcons.php",
+ data : requireString,
+ dataType : 'json',
+ success : function(data){
+ rconsUpdate(data);
+ }
+ });
+
+ errorTimeout = window.setTimeout(rconsSendError, 15000);
+
+ }
+
+ //when receive the response, update the term area
+ function rconsUpdate(data){
+ window.clearTimeout(errorTimeout);
+ errorArea.empty();
+ if (data.term){
+ termArea.empty().append(data.term);
+ maxDelay = 200;
+ }
+ else{
+ maxDelay = 2000;
+ }
+
+ receivingFlag = false;
+ workingStatus.attr('class', 'off');
+ sendTimeout = window.setTimeout(rconsSend, maxDelay);
+ }
+
+ function rconsSendError(){
+ workingStatus.attr('class', 'off');
+ errorArea.empty().append('Send require error.');
+ }
+
+ function rconsQueue(kc){
+ keyBuf.unshift(kc);
+ if (false == receivingFlag){
+ window.clearTimeout(sendTimeout);
+ sendTimeout = window.setTimeout(rconsSend, 1);
+ }
+ }
+};
\ No newline at end of file
diff --git a/xCAT-UI/lib/AJAXTERM/ajaxterm.html b/xCAT-UI/lib/AJAXTERM/ajaxterm.html
new file mode 100644
index 000000000..9edf75967
--- /dev/null
+++ b/xCAT-UI/lib/AJAXTERM/ajaxterm.html
@@ -0,0 +1,19 @@
+
+
+
+ Ajaxterm
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xCAT-UI/lib/AJAXTERM/ajaxterm.py b/xCAT-UI/lib/AJAXTERM/ajaxterm.py
new file mode 100644
index 000000000..2ec2561d0
--- /dev/null
+++ b/xCAT-UI/lib/AJAXTERM/ajaxterm.py
@@ -0,0 +1,572 @@
+#!/usr/bin/env python
+
+""" Ajaxterm """
+
+import array,cgi,fcntl,glob,mimetypes,optparse,os,pty,random,re,signal,select,sys,threading,time,termios,struct,pwd
+
+os.chdir(os.path.normpath(os.path.dirname(__file__)))
+# Optional: Add QWeb in sys path
+sys.path[0:0]=glob.glob('../../python')
+
+import qweb
+
+class Terminal:
+ def __init__(self,width=80,height=24):
+ self.width=width
+ self.height=height
+ self.init()
+ self.reset()
+ def init(self):
+ self.esc_seq={
+ "\x00": None,
+ "\x05": self.esc_da,
+ "\x07": None,
+ "\x08": self.esc_0x08,
+ "\x09": self.esc_0x09,
+ "\x0a": self.esc_0x0a,
+ "\x0b": self.esc_0x0a,
+ "\x0c": self.esc_0x0a,
+ "\x0d": self.esc_0x0d,
+ "\x0e": None,
+ "\x0f": None,
+ "\x1b#8": None,
+ "\x1b=": None,
+ "\x1b>": None,
+ "\x1b(0": None,
+ "\x1b(A": None,
+ "\x1b(B": None,
+ "\x1b[c": self.esc_da,
+ "\x1b[0c": self.esc_da,
+ "\x1b]R": None,
+ "\x1b7": self.esc_save,
+ "\x1b8": self.esc_restore,
+ "\x1bD": None,
+ "\x1bE": None,
+ "\x1bH": None,
+ "\x1bM": self.esc_ri,
+ "\x1bN": None,
+ "\x1bO": None,
+ "\x1bZ": self.esc_da,
+ "\x1ba": None,
+ "\x1bc": self.reset,
+ "\x1bn": None,
+ "\x1bo": None,
+ }
+ for k,v in self.esc_seq.items():
+ if v==None:
+ self.esc_seq[k]=self.esc_ignore
+ # regex
+ d={
+ r'\[\??([0-9;]*)([@ABCDEFGHJKLMPXacdefghlmnqrstu`])' : self.csi_dispatch,
+ r'\]([^\x07]+)\x07' : self.esc_ignore,
+ }
+ self.esc_re=[]
+ for k,v in d.items():
+ self.esc_re.append((re.compile('\x1b'+k),v))
+ # define csi sequences
+ self.csi_seq={
+ '@': (self.csi_at,[1]),
+ '`': (self.csi_G,[1]),
+ 'J': (self.csi_J,[0]),
+ 'K': (self.csi_K,[0]),
+ }
+ for i in [i[4] for i in dir(self) if i.startswith('csi_') and len(i)==5]:
+ if not self.csi_seq.has_key(i):
+ self.csi_seq[i]=(getattr(self,'csi_'+i),[1])
+ # Init 0-256 to latin1 and html translation table
+ self.trl1=""
+ for i in range(256):
+ if i<32:
+ self.trl1+=" "
+ elif i<127 or i>160:
+ self.trl1+=chr(i)
+ else:
+ self.trl1+="?"
+ self.trhtml=""
+ for i in range(256):
+ if i==0x0a or (i>32 and i<127) or i>160:
+ self.trhtml+=chr(i)
+ elif i<=32:
+ self.trhtml+="\xa0"
+ else:
+ self.trhtml+="?"
+ def reset(self,s=""):
+ self.scr=array.array('i',[0x000700]*(self.width*self.height))
+ self.st=0
+ self.sb=self.height-1
+ self.cx_bak=self.cx=0
+ self.cy_bak=self.cy=0
+ self.cl=0
+ self.sgr=0x000700
+ self.buf=""
+ self.outbuf=""
+ self.last_html=""
+ def peek(self,y1,x1,y2,x2):
+ return self.scr[self.width*y1+x1:self.width*y2+x2]
+ def poke(self,y,x,s):
+ pos=self.width*y+x
+ self.scr[pos:pos+len(s)]=s
+ def zero(self,y1,x1,y2,x2):
+ w=self.width*(y2-y1)+x2-x1+1
+ z=array.array('i',[0x000700]*w)
+ self.scr[self.width*y1+x1:self.width*y2+x2+1]=z
+ def scroll_up(self,y1,y2):
+ self.poke(y1,0,self.peek(y1+1,0,y2,self.width))
+ self.zero(y2,0,y2,self.width-1)
+ def scroll_down(self,y1,y2):
+ self.poke(y1+1,0,self.peek(y1,0,y2-1,self.width))
+ self.zero(y1,0,y1,self.width-1)
+ def scroll_right(self,y,x):
+ self.poke(y,x+1,self.peek(y,x,y,self.width))
+ self.zero(y,x,y,x)
+ def cursor_down(self):
+ if self.cy>=self.st and self.cy<=self.sb:
+ self.cl=0
+ q,r=divmod(self.cy+1,self.sb+1)
+ if q:
+ self.scroll_up(self.st,self.sb)
+ self.cy=self.sb
+ else:
+ self.cy=r
+ def cursor_right(self):
+ q,r=divmod(self.cx+1,self.width)
+ if q:
+ self.cl=1
+ else:
+ self.cx=r
+ def echo(self,c):
+ if self.cl:
+ self.cursor_down()
+ self.cx=0
+ self.scr[(self.cy*self.width)+self.cx]=self.sgr|ord(c)
+ self.cursor_right()
+ def esc_0x08(self,s):
+ self.cx=max(0,self.cx-1)
+ def esc_0x09(self,s):
+ x=self.cx+8
+ q,r=divmod(x,8)
+ self.cx=(q*8)%self.width
+ def esc_0x0a(self,s):
+ self.cursor_down()
+ def esc_0x0d(self,s):
+ self.cl=0
+ self.cx=0
+ def esc_save(self,s):
+ self.cx_bak=self.cx
+ self.cy_bak=self.cy
+ def esc_restore(self,s):
+ self.cx=self.cx_bak
+ self.cy=self.cy_bak
+ self.cl=0
+ def esc_da(self,s):
+ self.outbuf="\x1b[?6c"
+ def esc_ri(self,s):
+ self.cy=max(self.st,self.cy-1)
+ if self.cy==self.st:
+ self.scroll_down(self.st,self.sb)
+ def esc_ignore(self,*s):
+ pass
+# print "term:ignore: %s"%repr(s)
+ def csi_dispatch(self,seq,mo):
+ # CSI sequences
+ s=mo.group(1)
+ c=mo.group(2)
+ f=self.csi_seq.get(c,None)
+ if f:
+ try:
+ l=[min(int(i),1024) for i in s.split(';') if len(i)<4]
+ except ValueError:
+ l=[]
+ if len(l)==0:
+ l=f[1]
+ f[0](l)
+# else:
+# print 'csi ignore',c,l
+ def csi_at(self,l):
+ for i in range(l[0]):
+ self.scroll_right(self.cy,self.cx)
+ def csi_A(self,l):
+ self.cy=max(self.st,self.cy-l[0])
+ def csi_B(self,l):
+ self.cy=min(self.sb,self.cy+l[0])
+ def csi_C(self,l):
+ self.cx=min(self.width-1,self.cx+l[0])
+ self.cl=0
+ def csi_D(self,l):
+ self.cx=max(0,self.cx-l[0])
+ self.cl=0
+ def csi_E(self,l):
+ self.csi_B(l)
+ self.cx=0
+ self.cl=0
+ def csi_F(self,l):
+ self.csi_A(l)
+ self.cx=0
+ self.cl=0
+ def csi_G(self,l):
+ self.cx=min(self.width,l[0])-1
+ def csi_H(self,l):
+ if len(l)<2: l=[1,1]
+ self.cx=min(self.width,l[1])-1
+ self.cy=min(self.height,l[0])-1
+ self.cl=0
+ def csi_J(self,l):
+ if l[0]==0:
+ self.zero(self.cy,self.cx,self.height-1,self.width-1)
+ elif l[0]==1:
+ self.zero(0,0,self.cy,self.cx)
+ elif l[0]==2:
+ self.zero(0,0,self.height-1,self.width-1)
+ def csi_K(self,l):
+ if l[0]==0:
+ self.zero(self.cy,self.cx,self.cy,self.width-1)
+ elif l[0]==1:
+ self.zero(self.cy,0,self.cy,self.cx)
+ elif l[0]==2:
+ self.zero(self.cy,0,self.cy,self.width-1)
+ def csi_L(self,l):
+ for i in range(l[0]):
+ if self.cy=self.st and self.cy<=self.sb:
+ for i in range(l[0]):
+ self.scroll_up(self.cy,self.sb)
+ def csi_P(self,l):
+ w,cx,cy=self.width,self.cx,self.cy
+ end=self.peek(cy,cx,cy,w)
+ self.csi_K([0])
+ self.poke(cy,cx,end[l[0]:])
+ def csi_X(self,l):
+ self.zero(self.cy,self.cx,self.cy,self.cx+l[0])
+ def csi_a(self,l):
+ self.csi_C(l)
+ def csi_c(self,l):
+ #'\x1b[?0c' 0-8 cursor size
+ pass
+ def csi_d(self,l):
+ self.cy=min(self.height,l[0])-1
+ def csi_e(self,l):
+ self.csi_B(l)
+ def csi_f(self,l):
+ self.csi_H(l)
+ def csi_h(self,l):
+ if l[0]==4:
+ pass
+# print "insert on"
+ def csi_l(self,l):
+ if l[0]==4:
+ pass
+# print "insert off"
+ def csi_m(self,l):
+ for i in l:
+ if i==0 or i==39 or i==49 or i==27:
+ self.sgr=0x000700
+ elif i==1:
+ self.sgr=(self.sgr|0x000800)
+ elif i==7:
+ self.sgr=0x070000
+ elif i>=30 and i<=37:
+ c=i-30
+ self.sgr=(self.sgr&0xff08ff)|(c<<8)
+ elif i>=40 and i<=47:
+ c=i-40
+ self.sgr=(self.sgr&0x00ffff)|(c<<16)
+# else:
+# print "CSI sgr ignore",l,i
+# print 'sgr: %r %x'%(l,self.sgr)
+ def csi_r(self,l):
+ if len(l)<2: l=[0,self.height]
+ self.st=min(self.height-1,l[0]-1)
+ self.sb=min(self.height-1,l[1]-1)
+ self.sb=max(self.st,self.sb)
+ def csi_s(self,l):
+ self.esc_save(0)
+ def csi_u(self,l):
+ self.esc_restore(0)
+ def escape(self):
+ e=self.buf
+ if len(e)>32:
+# print "error %r"%e
+ self.buf=""
+ elif e in self.esc_seq:
+ self.esc_seq[e](e)
+ self.buf=""
+ else:
+ for r,f in self.esc_re:
+ mo=r.match(e)
+ if mo:
+ f(e,mo)
+ self.buf=""
+ break
+# if self.buf=='': print "ESC %r\n"%e
+ def write(self,s):
+ for i in s:
+ if len(self.buf) or (i in self.esc_seq):
+ self.buf+=i
+ self.escape()
+ elif i == '\x1b':
+ self.buf+=i
+ else:
+ self.echo(i)
+ def read(self):
+ b=self.outbuf
+ self.outbuf=""
+ return b
+ def dump(self):
+ r=''
+ for i in self.scr:
+ r+=chr(i&255)
+ return r
+ def dumplatin1(self):
+ return self.dump().translate(self.trl1)
+ def dumphtml(self,color=1):
+ h=self.height
+ w=self.width
+ r=""
+ span=""
+ span_bg,span_fg=-1,-1
+ for i in range(h*w):
+ q,c=divmod(self.scr[i],256)
+ if color:
+ bg,fg=divmod(q,256)
+ else:
+ bg,fg=0,7
+ if i==self.cy*w+self.cx:
+ bg,fg=1,7
+ if (bg!=span_bg or fg!=span_fg or i==h*w-1):
+ if len(span):
+ r+='%s'%(span_fg,span_bg,cgi.escape(span.translate(self.trhtml)))
+ span=""
+ span_bg,span_fg=bg,fg
+ span+=chr(c)
+ if i%w==w-1:
+ span+='\n'
+ r='%s
'%r
+ if self.last_html==r:
+ return ''
+ else:
+ self.last_html=r
+# print self
+ return r
+ def __repr__(self):
+ d=self.dumplatin1()
+ r=""
+ for i in range(self.height):
+ r+="|%s|\n"%d[self.width*i:self.width*(i+1)]
+ return r
+
+class SynchronizedMethod:
+ def __init__(self,lock,orig):
+ self.lock=lock
+ self.orig=orig
+ def __call__(self,*l):
+ self.lock.acquire()
+ r=self.orig(*l)
+ self.lock.release()
+ return r
+
+class Multiplex:
+ def __init__(self,cmd=None):
+ signal.signal(signal.SIGCHLD, signal.SIG_IGN)
+ self.cmd=cmd
+ self.proc={}
+ self.lock=threading.RLock()
+ self.thread=threading.Thread(target=self.loop)
+ self.alive=1
+ # synchronize methods
+ for name in ['create','fds','proc_read','proc_write','dump','die','run']:
+ orig=getattr(self,name)
+ setattr(self,name,SynchronizedMethod(self.lock,orig))
+ self.thread.start()
+ def create(self,w=80,h=25):
+ pid,fd=pty.fork()
+ if pid==0:
+ try:
+ fdl=[int(i) for i in os.listdir('/proc/self/fd')]
+ except OSError:
+ fdl=range(256)
+ for i in [i for i in fdl if i>2]:
+ try:
+ os.close(i)
+ except OSError:
+ pass
+ if self.cmd:
+ cmd=['/bin/sh','-c',self.cmd]
+ elif os.getuid()==0:
+ cmd=['/bin/login']
+ else:
+ sys.stdout.write("Login: ")
+ login=sys.stdin.readline().strip()
+ if re.match('^[0-9A-Za-z-_. ]+$',login):
+ cmd=['ssh']
+ cmd+=['-oPreferredAuthentications=keyboard-interactive,password']
+ cmd+=['-oNoHostAuthenticationForLocalhost=yes']
+ cmd+=['-oLogLevel=FATAL']
+ cmd+=['-F/dev/null','-l',login,'localhost']
+ else:
+ os._exit(0)
+ env={}
+ env["COLUMNS"]=str(w)
+ env["LINES"]=str(h)
+ env["TERM"]="linux"
+ env["PATH"]=os.environ['PATH']
+ os.execvpe(cmd[0],cmd,env)
+ else:
+ fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
+ # python bug http://python.org/sf/1112949 on amd64
+ fcntl.ioctl(fd, struct.unpack('i',struct.pack('I',termios.TIOCSWINSZ))[0], struct.pack("HHHH",h,w,0,0))
+ self.proc[fd]={'pid':pid,'term':Terminal(w,h),'buf':'','time':time.time()}
+ return fd
+ def die(self):
+ self.alive=0
+ def run(self):
+ return self.alive
+ def fds(self):
+ return self.proc.keys()
+ def proc_kill(self,fd):
+ if fd in self.proc:
+ self.proc[fd]['time']=0
+ t=time.time()
+ for i in self.proc.keys():
+ t0=self.proc[i]['time']
+ if (t-t0)>120:
+ try:
+ os.close(i)
+ os.kill(self.proc[i]['pid'],signal.SIGTERM)
+ except (IOError,OSError):
+ pass
+ del self.proc[i]
+ def proc_read(self,fd):
+ try:
+ t=self.proc[fd]['term']
+ t.write(os.read(fd,65536))
+ reply=t.read()
+ if reply:
+ os.write(fd,reply)
+ self.proc[fd]['time']=time.time()
+ except (KeyError,IOError,OSError):
+ self.proc_kill(fd)
+ def proc_write(self,fd,s):
+ try:
+ os.write(fd,s)
+ except (IOError,OSError):
+ self.proc_kill(fd)
+ def dump(self,fd,color=1):
+ try:
+ return self.proc[fd]['term'].dumphtml(color)
+ except KeyError:
+ return False
+ def loop(self):
+ while self.run():
+ fds=self.fds()
+ i,o,e=select.select(fds, [], [], 1.0)
+ for fd in i:
+ self.proc_read(fd)
+ if len(i):
+ time.sleep(0.002)
+ for i in self.proc.keys():
+ try:
+ os.close(i)
+ os.kill(self.proc[i]['pid'],signal.SIGTERM)
+ except (IOError,OSError):
+ pass
+
+class AjaxTerm:
+ def __init__(self,cmd=None,index_file='ajaxterm.html'):
+ self.files={}
+ for i in ['css','html','js']:
+ for j in glob.glob('*.%s'%i):
+ self.files[j]=file(j).read()
+ self.files['index']=file(index_file).read()
+ self.mime = mimetypes.types_map.copy()
+ self.mime['.html']= 'text/html; charset=UTF-8'
+ self.multi = Multiplex(cmd)
+ self.session = {}
+ def __call__(self, environ, start_response):
+ req = qweb.QWebRequest(environ, start_response,session=None)
+ if req.PATH_INFO.endswith('/u'):
+ s=req.REQUEST["s"]
+ k=req.REQUEST["k"]
+ c=req.REQUEST["c"]
+ w=req.REQUEST.int("w")
+ h=req.REQUEST.int("h")
+ q=req.REQUEST.int("q")
+ if s in self.session:
+ term=self.session[s]
+ else:
+ if not (w>2 and w<256 and h>2 and h<100):
+ w,h=80,25
+ term=self.session[s]=self.multi.create(w,h)
+ if k:
+ self.multi.proc_write(term,k)
+ time.sleep(0.002)
+ dump=self.multi.dump(term,c)
+ req.response_headers['Content-Type']='text/xml'
+ if isinstance(dump,str):
+ req.write(dump)
+ req.response_gzencode=1
+ else:
+ del self.session[s]
+ req.write('')
+# print "sessions %r"%self.session
+ if (1 == q):
+ self.multi.proc_kill(self.session[s])
+ time.sleep(0.002)
+ del self.session[s]
+ else:
+ n=os.path.basename(req.PATH_INFO)
+ if n in self.files:
+ req.response_headers['Content-Type'] = self.mime.get(os.path.splitext(n)[1].lower(), 'application/octet-stream')
+ req.write(self.files[n])
+ else:
+ req.response_headers['Content-Type'] = 'text/html; charset=UTF-8'
+ req.write(self.files['index'])
+ return req
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option("-p", "--port", dest="port", default="8022", help="Set the TCP port (default: 8022)")
+ parser.add_option("-c", "--command", dest="cmd", default=None,help="set the command (default: /bin/login or ssh localhost)")
+ parser.add_option("-l", "--log", action="store_true", dest="log",default=0,help="log requests to stderr (default: quiet mode)")
+ parser.add_option("-d", "--daemon", action="store_true", dest="daemon", default=0, help="run as daemon in the background")
+ parser.add_option("-P", "--pidfile",dest="pidfile",default="/var/run/ajaxterm.pid",help="set the pidfile (default: /var/run/ajaxterm.pid)")
+ parser.add_option("-i", "--index", dest="index_file", default="ajaxterm.html",help="default index file (default: ajaxterm.html)")
+ parser.add_option("-u", "--uid", dest="uid", help="Set the daemon's user id")
+ (o, a) = parser.parse_args()
+ if o.daemon:
+ pid=os.fork()
+ if pid == 0:
+ #os.setsid() ?
+ os.setpgrp()
+ nullin = file('/dev/null', 'r')
+ nullout = file('/dev/null', 'w')
+ os.dup2(nullin.fileno(), sys.stdin.fileno())
+ os.dup2(nullout.fileno(), sys.stdout.fileno())
+ os.dup2(nullout.fileno(), sys.stderr.fileno())
+ if os.getuid()==0 and o.uid:
+ try:
+ os.setuid(int(o.uid))
+ except:
+ os.setuid(pwd.getpwnam(o.uid).pw_uid)
+ else:
+ try:
+ file(o.pidfile,'w+').write(str(pid)+'\n')
+ except:
+ pass
+ print 'AjaxTerm at http://localhost:%s/ pid: %d' % (o.port,pid)
+ sys.exit(0)
+ else:
+ print 'AjaxTerm at http://localhost:%s/' % o.port
+ at=AjaxTerm(o.cmd,o.index_file)
+# f=lambda:os.system('firefox http://localhost:%s/&'%o.port)
+# qweb.qweb_wsgi_autorun(at,ip='localhost',port=int(o.port),threaded=0,log=o.log,callback_ready=None)
+ try:
+ qweb.QWebWSGIServer(at,ip='localhost',port=int(o.port),threaded=0,log=o.log).serve_forever()
+ except KeyboardInterrupt,e:
+ sys.excepthook(*sys.exc_info())
+ at.multi.die()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/xCAT-UI/lib/AJAXTERM/qweb.py b/xCAT-UI/lib/AJAXTERM/qweb.py
new file mode 100644
index 000000000..20c509230
--- /dev/null
+++ b/xCAT-UI/lib/AJAXTERM/qweb.py
@@ -0,0 +1,1356 @@
+#!/usr/bin/python2.3
+#
+# vim:set et ts=4 fdc=0 fdn=2 fdl=0:
+#
+# There are no blank lines between blocks beacause i use folding from:
+# http://www.vim.org/scripts/script.php?script_id=515
+#
+
+"""= QWeb Framework =
+
+== What is QWeb ? ==
+
+QWeb is a python based [http://www.python.org/doc/peps/pep-0333/ WSGI]
+compatible web framework, it provides an infratructure to quickly build web
+applications consisting of:
+
+ * A lightweight request handler (QWebRequest)
+ * An xml templating engine (QWebXml and QWebHtml)
+ * A simple name based controler (qweb_control)
+ * A standalone WSGI Server (QWebWSGIServer)
+ * A cgi and fastcgi WSGI wrapper (taken from flup)
+ * A startup function that starts cgi, factgi or standalone according to the
+ evironement (qweb_autorun).
+
+QWeb applications are runnable in standalone mode (from commandline), via
+FastCGI, Regular CGI or by any python WSGI compliant server.
+
+QWeb doesn't provide any database access but it integrates nicely with ORMs
+such as SQLObject, SQLAlchemy or plain DB-API.
+
+Written by Antony Lesuisse (email al AT udev.org)
+
+Homepage: http://antony.lesuisse.org/qweb/trac/
+
+Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum]
+
+== Quick Start (for Linux, MacOS X and cygwin) ==
+
+Make sure you have at least python 2.3 installed and run the following commands:
+
+{{{
+$ wget http://antony.lesuisse.org/qweb/files/QWeb-0.7.tar.gz
+$ tar zxvf QWeb-0.7.tar.gz
+$ cd QWeb-0.7/examples/blog
+$ ./blog.py
+}}}
+
+And point your browser to http://localhost:8080/
+
+You may also try AjaxTerm which uses qweb request handler.
+
+== Download ==
+
+ * Version 0.7:
+ * Source [/qweb/files/QWeb-0.7.tar.gz QWeb-0.7.tar.gz]
+ * Python 2.3 Egg [/qweb/files/QWeb-0.7-py2.3.egg QWeb-0.7-py2.3.egg]
+ * Python 2.4 Egg [/qweb/files/QWeb-0.7-py2.4.egg QWeb-0.7-py2.4.egg]
+
+ * [/qweb/trac/browser Browse the source repository]
+
+== Documentation ==
+
+ * [/qweb/trac/browser/trunk/README.txt?format=raw Read the included documentation]
+ * QwebTemplating
+
+== Mailin-list ==
+
+ * Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum]
+ * No mailing-list exists yet, discussion should happen on: [http://mail.python.org/mailman/listinfo/web-sig web-sig] [http://mail.python.org/pipermail/web-sig/ archives]
+
+QWeb Components:
+----------------
+
+QWeb also feature a simple components api, that enables developers to easily
+produces reusable components.
+
+Default qweb components:
+
+ - qweb_static:
+ A qweb component to serve static content from the filesystem or from
+ zipfiles.
+
+ - qweb_dbadmin:
+ scaffolding for sqlobject
+
+License
+-------
+qweb/fcgi.py wich is BSD-like from saddi.com.
+Everything else is put in the public domain.
+
+
+TODO
+----
+ Announce QWeb to python-announce-list@python.org web-sig@python.org
+ qweb_core
+ rename request methods into
+ request_save_files
+ response_404
+ response_redirect
+ response_download
+ request callback_generator, callback_function ?
+ wsgi callback_server_local
+ xml tags explicitly call render_attributes(t_att)?
+ priority form-checkbox over t-value (for t-option)
+
+"""
+
+import BaseHTTPServer,SocketServer,Cookie
+import cgi,datetime,email,email.Message,errno,gzip,os,random,re,socket,sys,tempfile,time,types,urllib,urlparse,xml.dom
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+#----------------------------------------------------------
+# Qweb Xml t-raw t-esc t-if t-foreach t-set t-call t-trim
+#----------------------------------------------------------
+class QWebEval:
+ def __init__(self,data):
+ self.data=data
+ def __getitem__(self,expr):
+ if self.data.has_key(expr):
+ return self.data[expr]
+ r=None
+ try:
+ r=eval(expr,self.data)
+ except NameError,e:
+ pass
+ except AttributeError,e:
+ pass
+ except Exception,e:
+ print "qweb: expression error '%s' "%expr,e
+ if self.data.has_key("__builtins__"):
+ del self.data["__builtins__"]
+ return r
+ def eval_object(self,expr):
+ return self[expr]
+ def eval_str(self,expr):
+ if expr=="0":
+ return self.data[0]
+ if isinstance(self[expr],unicode):
+ return self[expr].encode("utf8")
+ return str(self[expr])
+ def eval_format(self,expr):
+ try:
+ return str(expr%self)
+ except:
+ return "qweb: format error '%s' "%expr
+# if isinstance(r,unicode):
+# return r.encode("utf8")
+ def eval_bool(self,expr):
+ if self.eval_object(expr):
+ return 1
+ else:
+ return 0
+class QWebXml:
+ """QWeb Xml templating engine
+
+ The templating engine use a very simple syntax, "magic" xml attributes, to
+ produce any kind of texutal output (even non-xml).
+
+ QWebXml:
+ the template engine core implements the basic magic attributes:
+
+ t-att t-raw t-esc t-if t-foreach t-set t-call t-trim
+
+ """
+ def __init__(self,x=None,zipname=None):
+ self.node=xml.dom.Node
+ self._t={}
+ self._render_tag={}
+ prefix='render_tag_'
+ for i in [j for j in dir(self) if j.startswith(prefix)]:
+ name=i[len(prefix):].replace('_','-')
+ self._render_tag[name]=getattr(self.__class__,i)
+
+ self._render_att={}
+ prefix='render_att_'
+ for i in [j for j in dir(self) if j.startswith(prefix)]:
+ name=i[len(prefix):].replace('_','-')
+ self._render_att[name]=getattr(self.__class__,i)
+
+ if x!=None:
+ if zipname!=None:
+ import zipfile
+ zf=zipfile.ZipFile(zipname, 'r')
+ self.add_template(zf.read(x))
+ else:
+ self.add_template(x)
+ def register_tag(self,tag,func):
+ self._render_tag[tag]=func
+ def add_template(self,x):
+ if hasattr(x,'documentElement'):
+ dom=x
+ elif x.startswith("%s%s%s>"%(name,g_att,pre,inner,name)
+ else:
+ return "<%s%s/>"%(name,g_att)
+
+ # Attributes
+ def render_att_att(self,e,an,av,v):
+ if an.startswith("t-attf-"):
+ att,val=an[7:],self.eval_format(av,v)
+ elif an.startswith("t-att-"):
+ att,val=(an[6:],self.eval_str(av,v))
+ else:
+ att,val=self.eval_object(av,v)
+ return ' %s="%s"'%(att,cgi.escape(val,1))
+
+ # Tags
+ def render_tag_raw(self,e,t_att,g_att,v):
+ return self.eval_str(t_att["raw"],v)
+ def render_tag_rawf(self,e,t_att,g_att,v):
+ return self.eval_format(t_att["rawf"],v)
+ def render_tag_esc(self,e,t_att,g_att,v):
+ return cgi.escape(self.eval_str(t_att["esc"],v))
+ def render_tag_escf(self,e,t_att,g_att,v):
+ return cgi.escape(self.eval_format(t_att["escf"],v))
+ def render_tag_foreach(self,e,t_att,g_att,v):
+ expr=t_att["foreach"]
+ enum=self.eval_object(expr,v)
+ if enum!=None:
+ var=t_att.get('as',expr).replace('.','_')
+ d=v.copy()
+ size=-1
+ if isinstance(enum,types.ListType):
+ size=len(enum)
+ elif isinstance(enum,types.TupleType):
+ size=len(enum)
+ elif hasattr(enum,'count'):
+ size=enum.count()
+ d["%s_size"%var]=size
+ d["%s_all"%var]=enum
+ index=0
+ ru=[]
+ for i in enum:
+ d["%s_value"%var]=i
+ d["%s_index"%var]=index
+ d["%s_first"%var]=index==0
+ d["%s_even"%var]=index%2
+ d["%s_odd"%var]=(index+1)%2
+ d["%s_last"%var]=index+1==size
+ if index%2:
+ d["%s_parity"%var]='odd'
+ else:
+ d["%s_parity"%var]='even'
+ if isinstance(i,types.DictType):
+ d.update(i)
+ else:
+ d[var]=i
+ ru.append(self.render_element(e,g_att,d))
+ index+=1
+ return "".join(ru)
+ else:
+ return "qweb: t-foreach %s not found."%expr
+ def render_tag_if(self,e,t_att,g_att,v):
+ if self.eval_bool(t_att["if"],v):
+ return self.render_element(e,g_att,v)
+ else:
+ return ""
+ def render_tag_call(self,e,t_att,g_att,v):
+ # TODO t-prefix
+ if t_att.has_key("import"):
+ d=v
+ else:
+ d=v.copy()
+ d[0]=self.render_element(e,g_att,d)
+ return self.render(t_att["call"],d)
+ def render_tag_set(self,e,t_att,g_att,v):
+ if t_att.has_key("eval"):
+ v[t_att["set"]]=self.eval_object(t_att["eval"],v)
+ else:
+ v[t_att["set"]]=self.render_element(e,g_att,v)
+ return ""
+
+#----------------------------------------------------------
+# QWeb HTML (+deprecated QWebFORM and QWebOLD)
+#----------------------------------------------------------
+class QWebURL:
+ """ URL helper
+ assert req.PATH_INFO== "/site/admin/page_edit"
+ u = QWebURL(root_path="/site/",req_path=req.PATH_INFO)
+ s=u.url2_href("user/login",{'a':'1'})
+ assert s=="../user/login?a=1"
+
+ """
+ def __init__(self, root_path="/", req_path="/",defpath="",defparam={}):
+ self.defpath=defpath
+ self.defparam=defparam
+ self.root_path=root_path
+ self.req_path=req_path
+ self.req_list=req_path.split("/")[:-1]
+ self.req_len=len(self.req_list)
+ def decode(self,s):
+ h={}
+ for k,v in cgi.parse_qsl(s,1):
+ h[k]=v
+ return h
+ def encode(self,h):
+ return urllib.urlencode(h.items())
+ def request(self,req):
+ return req.REQUEST
+ def copy(self,path=None,param=None):
+ npath=self.defpath
+ if path:
+ npath=path
+ nparam=self.defparam.copy()
+ if param:
+ nparam.update(param)
+ return QWebURL(self.root_path,self.req_path,npath,nparam)
+ def path(self,path=''):
+ if not path:
+ path=self.defpath
+ pl=(self.root_path+path).split('/')
+ i=0
+ for i in range(min(len(pl), self.req_len)):
+ if pl[i]!=self.req_list[i]:
+ break
+ else:
+ i+=1
+ dd=self.req_len-i
+ if dd<0:
+ dd=0
+ return '/'.join(['..']*dd+pl[i:])
+ def href(self,path='',arg={}):
+ p=self.path(path)
+ tmp=self.defparam.copy()
+ tmp.update(arg)
+ s=self.encode(tmp)
+ if len(s):
+ return p+"?"+s
+ else:
+ return p
+ def form(self,path='',arg={}):
+ p=self.path(path)
+ tmp=self.defparam.copy()
+ tmp.update(arg)
+ r=''.join([''%(k,cgi.escape(str(v),1)) for k,v in tmp.items()])
+ return (p,r)
+class QWebField:
+ def __init__(self,name=None,default="",check=None):
+ self.name=name
+ self.default=default
+ self.check=check
+ # optional attributes
+ self.type=None
+ self.trim=1
+ self.required=1
+ self.cssvalid="form_valid"
+ self.cssinvalid="form_invalid"
+ # set by addfield
+ self.form=None
+ # set by processing
+ self.input=None
+ self.css=None
+ self.value=None
+ self.valid=None
+ self.invalid=None
+ self.validate(1)
+ def validate(self,val=1,update=1):
+ if val:
+ self.valid=1
+ self.invalid=0
+ self.css=self.cssvalid
+ else:
+ self.valid=0
+ self.invalid=1
+ self.css=self.cssinvalid
+ if update and self.form:
+ self.form.update()
+ def invalidate(self,update=1):
+ self.validate(0,update)
+class QWebForm:
+ class QWebFormF:
+ pass
+ def __init__(self,e=None,arg=None,default=None):
+ self.fields={}
+ # all fields have been submitted
+ self.submitted=False
+ self.missing=[]
+ # at least one field is invalid or missing
+ self.invalid=False
+ self.error=[]
+ # all fields have been submitted and are valid
+ self.valid=False
+ # fields under self.f for convenience
+ self.f=self.QWebFormF()
+ if e:
+ self.add_template(e)
+ # assume that the fields are done with the template
+ if default:
+ self.set_default(default,e==None)
+ if arg!=None:
+ self.process_input(arg)
+ def __getitem__(self,k):
+ return self.fields[k]
+ def set_default(self,default,add_missing=1):
+ for k,v in default.items():
+ if self.fields.has_key(k):
+ self.fields[k].default=str(v)
+ elif add_missing:
+ self.add_field(QWebField(k,v))
+ def add_field(self,f):
+ self.fields[f.name]=f
+ f.form=self
+ setattr(self.f,f.name,f)
+ def add_template(self,e):
+ att={}
+ for (an,av) in e.attributes.items():
+ an=str(an)
+ if an.startswith("t-"):
+ att[an[2:]]=av.encode("utf8")
+ for i in ["form-text", "form-password", "form-radio", "form-checkbox", "form-select","form-textarea"]:
+ if att.has_key(i):
+ name=att[i].split(".")[-1]
+ default=att.get("default","")
+ check=att.get("check",None)
+ f=QWebField(name,default,check)
+ if i=="form-textarea":
+ f.type="textarea"
+ f.trim=0
+ if i=="form-checkbox":
+ f.type="checkbox"
+ f.required=0
+ self.add_field(f)
+ for n in e.childNodes:
+ if n.nodeType==n.ELEMENT_NODE:
+ self.add_template(n)
+ def process_input(self,arg):
+ for f in self.fields.values():
+ if arg.has_key(f.name):
+ f.input=arg[f.name]
+ f.value=f.input
+ if f.trim:
+ f.input=f.input.strip()
+ f.validate(1,False)
+ if f.check==None:
+ continue
+ elif callable(f.check):
+ pass
+ elif isinstance(f.check,str):
+ v=f.check
+ if f.check=="email":
+ v=r"/^[^@#!& ]+@[A-Za-z0-9-][.A-Za-z0-9-]{0,64}\.[A-Za-z]{2,5}$/"
+ if f.check=="date":
+ v=r"/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/"
+ if not re.match(v[1:-1],f.input):
+ f.validate(0,False)
+ else:
+ f.value=f.default
+ self.update()
+ def validate_all(self,val=1):
+ for f in self.fields.values():
+ f.validate(val,0)
+ self.update()
+ def invalidate_all(self):
+ self.validate_all(0)
+ def update(self):
+ self.submitted=True
+ self.valid=True
+ self.errors=[]
+ for f in self.fields.values():
+ if f.required and f.input==None:
+ self.submitted=False
+ self.valid=False
+ self.missing.append(f.name)
+ if f.invalid:
+ self.valid=False
+ self.error.append(f.name)
+ # invalid have been submitted and
+ self.invalid=self.submitted and self.valid==False
+ def collect(self):
+ d={}
+ for f in self.fields.values():
+ d[f.name]=f.value
+ return d
+class QWebURLEval(QWebEval):
+ def __init__(self,data):
+ QWebEval.__init__(self,data)
+ def __getitem__(self,expr):
+ r=QWebEval.__getitem__(self,expr)
+ if isinstance(r,str):
+ return urllib.quote_plus(r)
+ else:
+ return r
+class QWebHtml(QWebXml):
+ """QWebHtml
+ QWebURL:
+ QWebField:
+ QWebForm:
+ QWebHtml:
+ an extended template engine, with a few utility class to easily produce
+ HTML, handle URLs and process forms, it adds the following magic attributes:
+
+ t-href t-action t-form-text t-form-password t-form-textarea t-form-radio
+ t-form-checkbox t-form-select t-option t-selected t-checked t-pager
+
+ # explication URL:
+ # v['tableurl']=QWebUrl({p=afdmin,saar=,orderby=,des=,mlink;meta_active=})
+ # t-href="tableurl?desc=1"
+ #
+ # explication FORM: t-if="form.valid()"
+ # Foreach i
+ # email:
+ #
+ #
+ # Simple forms:
+ #
+ #
+ #
+ #
+ #
+ #
+ #