#! /usr/bin/env python2 ############################################################################### # TeacherTool .03: This is a utility designed to work alongside # and install of LTSP in a classroom enviroment. It currently # Supports: # Listing all students@machines connections # Allowing you to run commands on student@machine # # Todo: # Configuration Setup/Saving # Process Viewer # Allowing you to log out students (not per machine) (connect to process viewer) # Allowing auto-save of the "last run" command list # Include distributed PyUnit tests # # Requires: # Python with Tkinter libs # # Coding Conventions: # Class/functionName in Java style, underscores are stupid (and hard to type) # No tabs, all code is indented by 4 spaces (vim expandtab is your friend) # I edit with an xterm open with 125 columns, so 124 wide columns are Ok! # DocString or die. #################################################################################### # Copyright (C) 2001 Robert R Melton # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # #################################################################################### import os, string, Tkinter ############################################################################################################################ class GeneralUtilities: """ General Utilities that will probably be useful in multiple applications """ def uniqueItems(self, s): " Takes a list and returns a unique version of it " u = [] for x in s: if x not in u: u.append(x) return u ############################################################################################################################ class GenericListBox: """ This is a listbox with a scrollbar attached to it not very configurable as of yet. """ def __init__(self, window, **kw): " Create a frame (will be put using grid) and pack widgets inside it " self.frame = Tkinter.Frame(window) self.scrollBar = Tkinter.Scrollbar(self.frame, orient="vertical") self.data = Tkinter.Listbox(self.frame, exportselection=0, selectmode="extended", yscrollcommand=self.scrollBar.set) self.data.pack(side="left", fill="y") self.scrollBar.pack(side="left", fill="y") self.scrollBar.config(command=self.data.yview) def selectAll(self): " Select everything in the list " self.data.select_set(0, Tkinter.END) def deleteAll(self): " delete everything in the list " self.data.delete(0, Tkinter.END) def add(self, data): " add an item to the list " self.data.insert(Tkinter.END, data) def gridAt(self, **kw): " grid forwarding call " self.frame.grid(kw) ############################################################################################################################ class UserButton(Tkinter.Button): """ Generic button, use gridAt to make it fill horiz. """ def __init__(self, window, **kw): " Create a simple grey/black button " apply(Tkinter.Button.__init__, (self, window), kw) self.config(fg="black", bg="lightgrey") def gridAt(self, **kw): " Use this in place of grid " kw["sticky"] = "nesw" self.grid(kw) ############################################################################################################################ class SettingsWindow(Tkinter.Toplevel): """ This is for setting up teacher tool, it allows configuration of anything that is easily configurable and saves it to disk """ def __init__(self, window, **kw): " Generate the settings window " apply(Tkinter.Toplevel.__init__, (self, window), kw) ############################################################################################################################ class ProcessWindow(Tkinter.Toplevel): """ This is for watching the processes of a single student, it allows for killing """ def __init__(self, window, user, **kw): " Generate the process watcher window " apply(Tkinter.Toplevel.__init__, (self, window), kw) self.user = user self.title("Process List of "+self.user) self.listBox = GenericListBox(self) self.listBox.gridAt(row=0, column=0, rowspan=3) self.populateListButton = UserButton(self, text="Refresh Task List", command=self.getProcesses) self.populateListButton.gridAt(row=0, column=1) self.killTasksButton = UserButton(self, text="Kill Task", command=self.killProcesses) self.killTasksButton.gridAt(row=1, column=1) self.getProcesses() def getProcesses(self): " Update the list of prcesses for the user " self.listBox.deleteAll() temp = [] fd = os.popen("ps --User "+self.user) consoleOutput = fd.readlines() fd.close() # Make sure the little process list is alpha ordered (total hack, I know) for x in consoleOutput: lineParts = string.split(x) temp.append(lineParts[3]+", "+lineParts[0]) temp.sort() for x in temp[1:]: self.listBox.add(x) def killProcesses(self): " Kill all selected processes " for x in self.getSelectedPids(): os.system("kill -15 "+x+" || kill -9 "+x) # nice || hard self.getProcesses() def getSelectedPids(self): " Return the select pids (not names) from the list " pidList = [] for x in self.listBox.data.curselection(): (name, pid) = string.split(self.listBox.data.get(x), ", ") pidList.append(pid) return pidList def getSelectedNames(self): " Return the select names (not pids) from the list " nameList = [] for x in self.listBox.data.curselection(): (name, pid) = string.split(self.listBox.data.get(x), ", ") nameList.append(name) return nameList ############################################################################################################################ class RunWindow(Tkinter.Toplevel): """ This is the form for running a command against a student (or set of students) """ def __init__(self, window, userList, **kw): " Initialize the run form " apply(Tkinter.Toplevel.__init__, (self, window), kw) self.users = userList self.title("Run Command") self.resizable(0,0) names = "" for x in userList: names = names+"\n"+x self.runLabel = Tkinter.Label(self, text="Run Command(s) for "+names) self.runLabel.grid(row=0, column=0, columnspan=2, sticky='nesw') self.runEntry = Tkinter.Entry(self) self.runEntry.grid(row=1, column=0, sticky='ew') self.runButton = UserButton(self, text="Run Command", command=self.runCommand) self.runButton.grid(row=1, column=1, sticky='ew') self.runVncButton = UserButton(self, text="Run Vnc", command=self.runVnc) self.runVncButton.gridAt(row=2, column=0, columnspan=2, sticky='ew') self.runSpyButton = UserButton(self, text="Run Spy", command=self.runSpy) self.runSpyButton.gridAt(row=3, column=0, columnspan=2, sticky='ew') self.runSpy2Button = UserButton(self, text="View-ONLY", command=self.runSpy2) self.runSpy2Button.gridAt(row=4, column=0, columnspan=2, sticky='ew') def runSpy(self): " Run spy against a student workstation " for x in self.users: user, computer = string.split(x, '@') #print("export VNC_VIA_CMD='/usr/bin/ssh -f -L %L:%H:%R %G \"x11vncnew -scale .4 -display :0\";sleep 4';vncviewer -via root@"+computer+" localhost:0") # TESTING # os.system("export VNC_VIA_CMD='/usr/bin/ssh -f -L %L:%H:%R %G \"x11vncnew -scale .4 -display :0\";sleep 4';vncviewer -via root@"+computer+" localhost:0") # TESTING os.system("export VNC_VIA_CMD='/usr/bin/ssh -l root -f -L %L:%H:%R %G \"x11vncnew -scale .6 -display :0 -rfbport 5900 -localhost\";sleep 4';vncviewer -xrm Vncviewer.title:"+user+"@%s -via root@"+computer+" localhost:0 &" ) # REAL def runSpy2(self): " Run spy against a student workstation " for x in self.users: user, computer = string.split(x, '@') #print("export VNC_VIA_CMD='/usr/bin/ssh -f -L %L:%H:%R %G \"x11vncnew -scale .4 -display :0\";sleep 4';vncviewer -via root@"+computer+" localhost:0") # TESTING # os.system("export VNC_VIA_CMD='/usr/bin/ssh -f -L %L:%H:%R %G \"x11vncnew -scale .4 -display :0\";sleep 4';vncviewer -via root@"+computer+" localhost:0") # TESTING os.system("export VNC_VIA_CMD='/usr/bin/ssh -l root -f -L %L:%H:%R %G \"x11vncnew -scale .6 -display :0 -viewonly -rfbport 5900 -localhost\";sleep 4';vncviewer -xrm Vncviewer.title:"+user+"@%s -via root@"+computer+" localhost:0 &" ) # REAL def runVnc(self): " Run vnc as a student " self.run("vncviewer -passwd /usr/local/share/passwd server:99") def runCommand(self): " Run the command in the entry box " self.run(self.runEntry.get()) def run(self, command): " Runs the given command as every select user (as them, on their screen) " for x in self.users: user, computer = string.split(x, '@') #print("export DISPLAY="+computer+":0.0 && su "+user+" -c '"+command+" & '") # TESTING os.system("export DISPLAY="+computer+":0.0 && su "+user+" -c '"+command+" &'") # REAL ############################################################################################################################ class TeacherTool: """ This is the teacher tool in all its glory :) """ def __init__(self, window, **kw): " Configure the title, drawUI, drawMenu, don't resize, start main loop " self.window = window self.gu = GeneralUtilities() self.window.title("Teacher Tool Revised") self.window.resizable(0,0) self.drawUi() self.drawMenu() self.window.mainloop() def drawUi(self): " Generate the Ui " self.listBox = GenericListBox(self.window) self.listBox.gridAt(row=0, column=0, rowspan=4) self.populateListButton = UserButton(self.window, text="(re)Populate List", command=self.populateList) self.populateListButton.gridAt(row=0, column=1) self.runCommandButton = UserButton(self.window, text="Run...", command=self.createRunWindow) self.runCommandButton.gridAt(row=1, column=1) self.userProcessesButton = UserButton(self.window, text="View User(s) Processes", command=self.createProcessWindow) self.userProcessesButton.gridAt(row=2, column=1) self.logOffButton = UserButton(self.window, text="Log Off User(s)", command=self.logOff) self.logOffButton.gridAt(row=3, column=1) self.populateList() def drawMenu(self): " Generate the menu " self.menu = Tkinter.Menu(self.window) self.window.config(menu=self.menu) self.fileMenu = Tkinter.Menu(self.menu) self.menu.add_cascade(label="File", menu=self.fileMenu) self.fileMenu.add_command(label="Configure", command=self.createConfigureWindow) self.fileMenu.add_separator() self.fileMenu.add_command(label="Exit", command=self.gracefulExit) self.editMenu = Tkinter.Menu(self.menu) self.menu.add_cascade(label="Edit", menu=self.editMenu) self.editMenu.add_command(label="Select All", command=self.listBox.selectAll) def populateList(self): " Populates the list of user with unique user@machines combinations " self.listBox.deleteAll() userList = [] #fd = os.popen("cat FakeData") # TESTING fd = os.popen("netstat -t -e | grep x11 | sed -e 's/ \+/ /g' -e 's/:/ /g'") # REAL consoleOutput = fd.readlines() fd.close() self.listBox.deleteAll() for line in consoleOutput: lineParts = string.split(line) if len(lineParts) >= 9: machine = lineParts[5] user = lineParts[8] else: # RH9 bug? machine = lineParts[3] user = lineParts[6] if (user != "root" and not user[0] in string.digits): userList.append(user+"@"+machine) userList = self.gu.uniqueItems(userList) userList.sort() for item in userList: self.listBox.add(item) def logOff(self): " Completely log off a user or users " for x in self.getSelectedUsers(): os.system("skill -15 -u "+x+" || skill -9 -u "+x) # nice || hard self.populateList() def createRunWindow(self): " Generate an instance of the run window " self.getSelected() RunWindow(self.window, self.getSelected()) def createProcessWindow(self): " Generate instances of the process window for each selected user " for x in self.getSelectedUsers(): ProcessWindow(self.window, x) def getSelectedUsers(self): " Return the select users (not computers) from the list " userList = [] for x in self.listBox.data.curselection(): (user, computer) = string.split(self.listBox.data.get(x), "@") userList.append(user) return self.gu.uniqueItems(userList) def getSelectedComputers(self): " Return the select computers (not users) from the list " computerList = [] for x in self.listBox.data.curselection(): (user, computer) = string.split(self.listBox.data.get(x), "@") computerList.append(computer) return self.gu.uniqueItems(computerList) def getSelected(self): list = [] for x in self.listBox.data.curselection(): list.append(self.listBox.data.get(x)) return list def createConfigureWindow(self): " Generate a (single) configuration window " pass def gracefulExit(self): " Exit saving changes " self.window.destroy() ############################################################################################################################ if __name__ == "__main__": TeacherTool(Tkinter.Tk())