Remove old GUI
This commit is contained in:
parent
1aa2a15d70
commit
28df5a8dc4
31
bin/ceo-old
31
bin/ceo-old
|
@ -1,31 +0,0 @@
|
|||
#!/usr/bin/python2.4 --
|
||||
"""CEO SUID Python Wrapper Script"""
|
||||
import os, sys
|
||||
|
||||
safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME', 'TERM', 'LANG'
|
||||
'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY',
|
||||
'LC_NUMERIC', 'LC_TIME', 'UID', 'GID', 'SSH_CONNECTION', 'SSH_AUTH_SOCK',
|
||||
'SSH_CLIENT']
|
||||
|
||||
for key in os.environ.keys():
|
||||
if key not in safe_environment:
|
||||
del os.environ[key]
|
||||
|
||||
os.environ['LESSSECURE'] = '1'
|
||||
os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
|
||||
|
||||
for pathent in sys.path[:]:
|
||||
if not pathent.find('/usr') == 0:
|
||||
sys.path.remove(pathent)
|
||||
|
||||
euid = os.geteuid()
|
||||
egid = os.getegid()
|
||||
try:
|
||||
os.setreuid(euid, euid)
|
||||
os.setregid(egid, egid)
|
||||
except OSError, e:
|
||||
print str(e)
|
||||
sys.exit(1)
|
||||
|
||||
import csc.apps.legacy.main
|
||||
csc.apps.legacy.main.run()
|
|
@ -1,8 +0,0 @@
|
|||
"""
|
||||
Legacy User Interface
|
||||
|
||||
This module contains the legacy CEO user interface and related modules.
|
||||
|
||||
main - all of the main UI logic
|
||||
helpers - user interface library routines
|
||||
"""
|
|
@ -1,411 +0,0 @@
|
|||
"""
|
||||
Helpers for legacy User Interface
|
||||
|
||||
This module contains numerous functions that are designed to immitate
|
||||
the look and behavior of the previous CEO. Included is code for various
|
||||
curses-based UI widgets that were provided by Perl 5's Curses and
|
||||
Curses::Widgets libraries.
|
||||
|
||||
Though attempts have been made to keep the UI [bug-]compatible with
|
||||
the previous system, some compromises have been made. For example,
|
||||
the input and textboxes draw 'OK' and 'Cancel' buttons where the old
|
||||
CEO had them, but they are fake. That is, the buttons in the old
|
||||
CEO were selectable but non-operational, but in the new CEO they are
|
||||
not even selectable.
|
||||
"""
|
||||
import curses.ascii
|
||||
|
||||
# key constants not defined in CURSES
|
||||
KEY_RETURN = ord('\n')
|
||||
KEY_ESCAPE = 27
|
||||
KEY_EOT = 4
|
||||
|
||||
|
||||
def center(parent_dim, child_dim):
|
||||
"""Helper for centering a length in a larget length."""
|
||||
return (parent_dim-child_dim)/2
|
||||
|
||||
|
||||
def read_input(wnd, offy, offx, width, maxlen, echo=True):
|
||||
"""
|
||||
Read user input within a confined region of a window.
|
||||
|
||||
Basic line-editing is supported:
|
||||
LEFT, RIGHT, HOME, and END move around.
|
||||
BACKSPACE and DEL remove characters.
|
||||
INSERT switches between insert and overwrite mode.
|
||||
ESC and C-d abort input.
|
||||
RETURN completes input.
|
||||
|
||||
Parameters:
|
||||
wnd - parent window for region
|
||||
offy - the vertical offset for the beginning of the input region
|
||||
offx - the horizontal offset for the beginning of the input region
|
||||
width - the width of the region
|
||||
maxlen - greatest number of characters to read (0 for no limit)
|
||||
echo - boolean: whether to display typed characters
|
||||
|
||||
Returns: the string, or None when the user aborts.
|
||||
"""
|
||||
|
||||
# turn on cursor
|
||||
try:
|
||||
curses.curs_set(1)
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
# set keypad mode to allow UP, DOWN, etc
|
||||
wnd.keypad(1)
|
||||
|
||||
# the input string
|
||||
inputbuf = ""
|
||||
|
||||
# offset of cursor in input
|
||||
# i.e. the next operation is applied at input[inputoff]
|
||||
inputoff = 0
|
||||
|
||||
# display offset (for scrolling)
|
||||
# i.e. the first character in the region is input[displayoff]
|
||||
displayoff = 0
|
||||
|
||||
# insert mode (True) or overwrite mode (False)
|
||||
insert = True
|
||||
|
||||
while True:
|
||||
|
||||
# echo mode, display the string
|
||||
if echo:
|
||||
# discard characters before displayoff,
|
||||
# as the window may be scrolled to the right
|
||||
substring = inputbuf[displayoff:]
|
||||
|
||||
# pad the string with zeroes to overwrite stale characters
|
||||
substring = substring + " " * (width - len(substring))
|
||||
|
||||
# display the substring
|
||||
wnd.addnstr(offy, offx, substring, width)
|
||||
|
||||
# await input
|
||||
key = wnd.getch(offy, offx + inputoff - displayoff)
|
||||
|
||||
# not echo mode, don't display the string
|
||||
else:
|
||||
# await input at arbitrary location
|
||||
key = wnd.getch(offy, offx)
|
||||
|
||||
# enter returns input
|
||||
if key == KEY_RETURN:
|
||||
return inputbuf
|
||||
|
||||
# escape aborts input
|
||||
elif key == KEY_ESCAPE:
|
||||
return None
|
||||
|
||||
# EOT (C-d) aborts if there is no input
|
||||
elif key == KEY_EOT:
|
||||
if len(inputbuf) == 0:
|
||||
return None
|
||||
|
||||
# backspace removes the previous character
|
||||
elif key == curses.KEY_BACKSPACE:
|
||||
if inputoff > 0:
|
||||
|
||||
# remove the character immediately before the input offset
|
||||
inputbuf = inputbuf[0:inputoff-1] + inputbuf[inputoff:]
|
||||
inputoff -= 1
|
||||
|
||||
# move either the cursor or entire line of text left
|
||||
if displayoff > 0:
|
||||
displayoff -= 1
|
||||
|
||||
# delete removes the current character
|
||||
elif key == curses.KEY_DC:
|
||||
if inputoff < len(input):
|
||||
|
||||
# remove the character at the input offset
|
||||
inputbuf = inputbuf[0:inputoff] + inputbuf[inputoff+1:]
|
||||
|
||||
# left moves the cursor one character left
|
||||
elif key == curses.KEY_LEFT:
|
||||
if inputoff > 0:
|
||||
|
||||
# move the cursor to the left
|
||||
inputoff -= 1
|
||||
|
||||
# scroll left if necessary
|
||||
if inputoff < displayoff:
|
||||
displayoff -= 1
|
||||
|
||||
# right moves the cursor one character right
|
||||
elif key == curses.KEY_RIGHT:
|
||||
if inputoff < len(inputbuf):
|
||||
|
||||
# move the cursor to the right
|
||||
inputoff += 1
|
||||
|
||||
# scroll right if necessary
|
||||
if displayoff - inputoff == width:
|
||||
displayoff += 1
|
||||
|
||||
# home moves the cursor to the first character
|
||||
elif key == curses.KEY_HOME:
|
||||
inputoff = 0
|
||||
displayoff = 0
|
||||
|
||||
# end moves the cursor past the last character
|
||||
elif key == curses.KEY_END:
|
||||
inputoff = len(inputbuf)
|
||||
displayoff = len(inputbuf) - width + 1
|
||||
|
||||
# insert toggles insert/overwrite mode
|
||||
elif key == curses.KEY_IC:
|
||||
insert = not insert
|
||||
|
||||
# other (printable) characters are added to the input string
|
||||
elif curses.ascii.isprint(key):
|
||||
if len(inputbuf) < maxlen or maxlen == 0:
|
||||
|
||||
# insert mode: insert before current offset
|
||||
if insert:
|
||||
inputbuf = inputbuf[0:inputoff] + chr(key) + inputbuf[inputoff:]
|
||||
|
||||
# overwrite mode: replace current offset
|
||||
else:
|
||||
inputbuf = inputbuf[0:inputoff] + chr(key) + inputbuf[inputoff+1:]
|
||||
|
||||
# increment the input offset
|
||||
inputoff += 1
|
||||
|
||||
# scroll right if necessary
|
||||
if inputoff - displayoff == width:
|
||||
displayoff += 1
|
||||
|
||||
|
||||
def inputbox(wnd, prompt, field_width, echo=True):
|
||||
"""Display a window for user input."""
|
||||
|
||||
wnd_height, wnd_width = wnd.getmaxyx()
|
||||
height, width = 12, field_width + 7
|
||||
|
||||
# draw a window for the dialog
|
||||
childy, childx = center(wnd_height-1, height)+1, center(wnd_width, width)
|
||||
child_wnd = wnd.subwin(height, width, childy, childx)
|
||||
child_wnd.clear()
|
||||
child_wnd.border()
|
||||
|
||||
# draw another window for the text box
|
||||
texty, textx = center(height-1, 3)+1, center(width-1, width-5)+1
|
||||
textheight, textwidth = 3, width-5
|
||||
text_wnd = child_wnd.derwin(textheight, textwidth, texty, textx)
|
||||
text_wnd.clear()
|
||||
text_wnd.border()
|
||||
|
||||
# draw the prompt
|
||||
prompty, promptx = 2, 3
|
||||
child_wnd.addnstr(prompty, promptx, prompt, width-2)
|
||||
|
||||
# draw the fake buttons
|
||||
fakey, fakex = 9, width - 19
|
||||
child_wnd.addstr(fakey, fakex, "< OK > < Cancel >")
|
||||
child_wnd.addch(fakey, fakex+2, "O", curses.A_UNDERLINE)
|
||||
child_wnd.addch(fakey, fakex+9, "C", curses.A_UNDERLINE)
|
||||
|
||||
# update the screen
|
||||
child_wnd.noutrefresh()
|
||||
text_wnd.noutrefresh()
|
||||
curses.doupdate()
|
||||
|
||||
# read an input string within the field region of text_wnd
|
||||
inputy, inputx, inputwidth = 1, 1, textwidth - 2
|
||||
inputbuf = read_input(text_wnd, inputy, inputx, inputwidth, 0, echo)
|
||||
|
||||
# erase the window
|
||||
child_wnd.erase()
|
||||
child_wnd.refresh()
|
||||
|
||||
return inputbuf
|
||||
|
||||
|
||||
def line_wrap(line, width):
|
||||
"""Wrap a string to a certain width (returns a list of strings)."""
|
||||
|
||||
wrapped_lines = []
|
||||
tokens = line.split(" ")
|
||||
tokens.reverse()
|
||||
tmp = tokens.pop()
|
||||
if len(tmp) > width:
|
||||
wrapped_lines.append(tmp[0:width])
|
||||
tmp = tmp[width:]
|
||||
while len(tokens) > 0:
|
||||
token = tokens.pop()
|
||||
if len(tmp) + len(token) + 1 <= width:
|
||||
tmp += " " + token
|
||||
elif len(token) > width:
|
||||
tmp += " " + token[0:width-len(tmp)-1]
|
||||
tokens.push(token[width-len(tmp)-1:])
|
||||
else:
|
||||
wrapped_lines.append(tmp)
|
||||
tmp = token
|
||||
wrapped_lines.append(tmp)
|
||||
return wrapped_lines
|
||||
|
||||
|
||||
def msgbox(wnd, msg, title="Message"):
|
||||
"""Display a message in a window."""
|
||||
|
||||
# split message into a list of lines
|
||||
lines = msg.split("\n")
|
||||
|
||||
# determine the dimensions of the method
|
||||
message_height = len(lines)
|
||||
message_width = 0
|
||||
for line in lines:
|
||||
if len(line) > message_width:
|
||||
message_width = len(line)
|
||||
|
||||
# ensure the window fits the title
|
||||
if len(title) > message_width:
|
||||
message_width = len(title)
|
||||
|
||||
# maximum message width
|
||||
parent_height, parent_width = wnd.getmaxyx()
|
||||
max_message_width = parent_width - 8
|
||||
|
||||
# line-wrap if necessary
|
||||
if message_width > max_message_width:
|
||||
newlines = []
|
||||
for line in lines:
|
||||
for newline in line_wrap(line, max_message_width):
|
||||
newlines.append(newline)
|
||||
lines = newlines
|
||||
message_width = max_message_width
|
||||
message_height = len(lines)
|
||||
|
||||
# random padding that perl's curses adds
|
||||
pad_width = 2
|
||||
|
||||
# create the outer window
|
||||
outer_height, outer_width = message_height + 8, message_width + pad_width + 6
|
||||
outer_y, outer_x = center(parent_height+1, outer_height)-1, center(parent_width, outer_width)
|
||||
outer_wnd = wnd.derwin(outer_height, outer_width, outer_y, outer_x)
|
||||
outer_wnd.erase()
|
||||
outer_wnd.border()
|
||||
|
||||
# create the inner window
|
||||
inner_height, inner_width = message_height + 2, message_width + pad_width + 2
|
||||
inner_y, inner_x = 2, center(outer_width, inner_width)
|
||||
inner_wnd = outer_wnd.derwin(inner_height, inner_width, inner_y, inner_x)
|
||||
inner_wnd.border()
|
||||
|
||||
# display the title
|
||||
outer_wnd.addstr(0, 1, " " + title + " ", curses.A_REVERSE | curses.A_BOLD)
|
||||
|
||||
# display the message
|
||||
for i in xrange(len(lines)):
|
||||
inner_wnd.addnstr(i+1, 1, lines[i], message_width)
|
||||
|
||||
# draw a solid block at the end of the first few lines
|
||||
if i < len(lines) - 1:
|
||||
inner_wnd.addch(i+1, inner_width-1, ' ', curses.A_REVERSE)
|
||||
|
||||
# display the fake OK button
|
||||
fakey, fakex = outer_height - 3, outer_width - 8
|
||||
outer_wnd.addstr(fakey, fakex, "< OK >", curses.A_REVERSE)
|
||||
outer_wnd.addch(fakey, fakex+2, "O", curses.A_UNDERLINE | curses.A_REVERSE)
|
||||
|
||||
# update display
|
||||
outer_wnd.noutrefresh()
|
||||
inner_wnd.noutrefresh()
|
||||
curses.doupdate()
|
||||
|
||||
# read a RETURN or ESC before returning
|
||||
curses.curs_set(0)
|
||||
outer_wnd.keypad(1)
|
||||
while True:
|
||||
key = outer_wnd.getch(0, 0)
|
||||
if key == KEY_RETURN or key == KEY_ESCAPE:
|
||||
break
|
||||
|
||||
# clear the window
|
||||
outer_wnd.erase()
|
||||
outer_wnd.refresh()
|
||||
|
||||
|
||||
def menu(wnd, offy, offx, width, options, _acquire_wnd=None):
|
||||
"""
|
||||
Draw a menu and wait for a selection.
|
||||
|
||||
Parameters:
|
||||
wnd - parent window
|
||||
offy - vertical offset for menu region
|
||||
offx - horizontal offset for menu region
|
||||
width - width of menu region
|
||||
options - a list of selections
|
||||
_acquire_wnd - hack to support resize: must be a function callback
|
||||
that returns new values for wnd, offy, offx, height,
|
||||
width. Unused if None.
|
||||
|
||||
Returns: index into options that was selected
|
||||
"""
|
||||
|
||||
# the currently selected option
|
||||
selected = 0
|
||||
|
||||
while True:
|
||||
# disable cursor
|
||||
curses.curs_set(0)
|
||||
|
||||
# hack to support resize: recreate the
|
||||
# parent window every iteration
|
||||
if _acquire_wnd:
|
||||
wnd, offy, offx, height, width = _acquire_wnd()
|
||||
|
||||
# keypad mode so getch() works with up, down
|
||||
wnd.keypad(1)
|
||||
|
||||
# display the menu
|
||||
for i in xrange(len(options)):
|
||||
text, callback = options[i]
|
||||
text = text + " " * (width - len(text))
|
||||
|
||||
# the selected option is displayed in reverse video
|
||||
if i == selected:
|
||||
wnd.addstr(i+offy, offx, text, curses.A_REVERSE)
|
||||
else:
|
||||
wnd.addstr(i+offy, offx, text)
|
||||
# display the member
|
||||
|
||||
wnd.refresh()
|
||||
|
||||
# read one keypress
|
||||
keypress = wnd.getch()
|
||||
|
||||
# UP moves to the previous option
|
||||
if (keypress == curses.KEY_UP or keypress == ord('k')) and selected > 0:
|
||||
selected = (selected - 1)
|
||||
|
||||
# DOWN moves to the next option
|
||||
elif (keypress == curses.KEY_DOWN or keypress == ord('j')) and selected < len(options) - 1:
|
||||
selected = (selected + 1)
|
||||
|
||||
# RETURN runs the callback for the selected option
|
||||
elif keypress == KEY_RETURN:
|
||||
text, callback = options[selected]
|
||||
|
||||
# highlight the selected option
|
||||
text = text + " " * (width - len(text))
|
||||
wnd.addstr(selected+offy, offx, text, curses.A_REVERSE | curses.A_BOLD)
|
||||
wnd.refresh()
|
||||
|
||||
# execute the selected option
|
||||
if callback(wnd): # success
|
||||
break
|
||||
|
||||
|
||||
def reset():
|
||||
"""Reset the terminal and clear the screen."""
|
||||
|
||||
reset = curses.tigetstr('rs1')
|
||||
if not reset: reset = '\x1bc'
|
||||
curses.putp(reset)
|
||||
|
|
@ -1,531 +0,0 @@
|
|||
"""
|
||||
CEO-like Frontend
|
||||
|
||||
This frontend aims to be compatible in both look and function with the
|
||||
curses UI of CEO.
|
||||
|
||||
Some small improvements have been made, such as not echoing passwords and
|
||||
aborting when nothing is typed into the first input box during an operation.
|
||||
|
||||
This frontend is poorly documented, deprecated, and undoubtedly full of bugs.
|
||||
"""
|
||||
import curses.ascii, re, os
|
||||
from helpers import menu, inputbox, msgbox, reset
|
||||
from csc.adm import accounts, members, terms
|
||||
from csc.common.excep import InvalidArgument
|
||||
|
||||
# color of the ceo border
|
||||
BORDER_COLOR = curses.COLOR_RED
|
||||
|
||||
|
||||
def read_uid(wnd):
|
||||
"""Read a username."""
|
||||
|
||||
prompt = 'Username:'
|
||||
return inputbox(wnd, prompt, 36)
|
||||
|
||||
def read_member(wnd):
|
||||
"""Looks up a member."""
|
||||
|
||||
# connect the members module to its backend if necessary
|
||||
if not members.connected(): members.connect()
|
||||
|
||||
uid = read_uid(wnd)
|
||||
if not uid or uid.lower() == 'exit':
|
||||
return
|
||||
|
||||
member = members.get(uid)
|
||||
if not member:
|
||||
msgbox(wnd, "Invalid username: %s" % uid)
|
||||
return
|
||||
|
||||
# display user
|
||||
display_member_details(wnd, member)
|
||||
|
||||
return member
|
||||
|
||||
|
||||
def action_library(wnd):
|
||||
"""Display a link to the library."""
|
||||
msgbox(wnd, "Please visit library.csclub.uwaterloo.ca")
|
||||
|
||||
def action_new_member(wnd):
|
||||
"""Interactively add a new member."""
|
||||
|
||||
userid, program = '', ''
|
||||
|
||||
msgbox(wnd, "Membership is $2.00 CDN. Please ensure\n"
|
||||
"the money is desposited in the safe\n"
|
||||
"before continuing.")
|
||||
|
||||
# read the name
|
||||
prompt = "New member's full name: "
|
||||
realname = inputbox(wnd, prompt, 30)
|
||||
|
||||
# abort if no name is entered
|
||||
if not realname or realname.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# read the program of study
|
||||
prompt = "New member's program of study:"
|
||||
program = inputbox(wnd, prompt, 30)
|
||||
|
||||
# abort if exit is entered
|
||||
if program is None or program.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# read user id
|
||||
prompt = "New member's UWdir username:"
|
||||
while userid == '':
|
||||
userid = inputbox(wnd, prompt, 30)
|
||||
|
||||
# user abort
|
||||
if userid is None or userid.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# connect the members module to its backend if necessary
|
||||
if not members.connected(): members.connect()
|
||||
|
||||
# attempt to create the member
|
||||
try:
|
||||
members.new(userid, realname, program)
|
||||
|
||||
msgbox(wnd, "Success! Your username is %s. You are now registered\n"
|
||||
% userid + "for the " + terms.current() + " term.")
|
||||
|
||||
except members.InvalidRealName:
|
||||
msgbox(wnd, 'Invalid real name: "%s"' % realname)
|
||||
return False
|
||||
except InvalidArgument, e:
|
||||
if e.argname == 'uid' and e.explanation == 'duplicate uid':
|
||||
msgbox(wnd, 'A member with this user ID exists.')
|
||||
return False
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def action_term_register(wnd):
|
||||
"""Interactively register a member for a term."""
|
||||
|
||||
term = ''
|
||||
|
||||
member = read_member(wnd)
|
||||
if not member:
|
||||
return False
|
||||
uid = member['uid'][0]
|
||||
|
||||
# verify member
|
||||
prompt = "Is this the correct member?"
|
||||
answer = None
|
||||
while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
|
||||
answer = inputbox(wnd, prompt, 28)
|
||||
|
||||
# user abort
|
||||
if answer == "exit":
|
||||
return False
|
||||
|
||||
# read the term
|
||||
prompt = "Which term to register for ([wsf]20nn):"
|
||||
while not re.match('^[wsf][0-9]{4}$', term) and not term == 'exit':
|
||||
term = inputbox(wnd, prompt, 41)
|
||||
|
||||
# abort when exit is entered
|
||||
if term.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# already registered?
|
||||
if members.registered(uid, term):
|
||||
msgbox(wnd, "You are already registered for term " + term)
|
||||
return False
|
||||
|
||||
try:
|
||||
|
||||
# attempt to register
|
||||
members.register(uid, term)
|
||||
|
||||
# display success message
|
||||
msgbox(wnd, "You are now registered for term " + term)
|
||||
|
||||
except members.InvalidTerm:
|
||||
msgbox(wnd, "Term is not valid: %s" % term)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def action_term_register_multiple(wnd):
|
||||
"""Interactively register a member for multiple terms."""
|
||||
|
||||
base, num = '', None
|
||||
|
||||
member = read_member(wnd)
|
||||
if not member:
|
||||
return False
|
||||
uid = member['uid'][0]
|
||||
|
||||
# verify member
|
||||
prompt = "Is this the correct member?"
|
||||
answer = None
|
||||
while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
|
||||
answer = inputbox(wnd, prompt, 28)
|
||||
|
||||
# user abort
|
||||
if answer == "exit":
|
||||
return False
|
||||
|
||||
# read the base
|
||||
prompt = "Which term to start registering ([fws]20nn):"
|
||||
while not re.match('^[wsf][0-9]{4}$', base) and not base == 'exit':
|
||||
base = inputbox(wnd, prompt, 41)
|
||||
|
||||
# abort when exit is entered
|
||||
if base.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# read number of terms
|
||||
prompt = 'How many terms?'
|
||||
while not num or not re.match('^[0-9]*$', num):
|
||||
num = inputbox(wnd, prompt, 36)
|
||||
num = int(num)
|
||||
|
||||
# any terms in the range?
|
||||
if num < 1:
|
||||
msgbox(wnd, "No terms to register.")
|
||||
return False
|
||||
|
||||
# compile a list to register
|
||||
term_list = terms.interval(base, num)
|
||||
|
||||
# already registered?
|
||||
for term in term_list:
|
||||
if members.registered(uid, term):
|
||||
msgbox(wnd, "You are already registered for term " + term)
|
||||
return False
|
||||
|
||||
try:
|
||||
|
||||
# attempt to register all terms
|
||||
members.register(uid, term_list)
|
||||
|
||||
# display success message [sic]
|
||||
msgbox(wnd, "Your are now registered for terms: " + ", ".join(term_list))
|
||||
|
||||
except members.InvalidTerm:
|
||||
msgbox(wnd, "Invalid term entered.")
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def input_password(wnd):
|
||||
|
||||
# password input loop
|
||||
password = "password"
|
||||
check = "check"
|
||||
while password != check:
|
||||
|
||||
# read password
|
||||
prompt = "User password:"
|
||||
password = None
|
||||
while not password:
|
||||
password = inputbox(wnd, prompt, 18, False)
|
||||
|
||||
# read another password
|
||||
prompt = "Enter the password again:"
|
||||
check = None
|
||||
while not check:
|
||||
check = inputbox(wnd, prompt, 27, False)
|
||||
|
||||
return password
|
||||
|
||||
|
||||
def action_create_account(wnd):
|
||||
"""Interactively create an account for a member."""
|
||||
|
||||
member = read_member(wnd)
|
||||
if not member:
|
||||
return False
|
||||
|
||||
# member already has an account?
|
||||
if not accounts.connected(): accounts.connect()
|
||||
if 'posixAccount' in member['objectClass']:
|
||||
msgbox(wnd, "Account already exists.")
|
||||
return False
|
||||
|
||||
# verify member
|
||||
prompt = "Is this the correct member?"
|
||||
answer = None
|
||||
while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
|
||||
answer = inputbox(wnd, prompt, 28)
|
||||
|
||||
# user abort
|
||||
if answer == "exit":
|
||||
return False
|
||||
|
||||
# incorrect member; abort
|
||||
if answer == "no" or answer == "n":
|
||||
msgbox(wnd, "I suggest searching for the member by userid or name from the main menu.")
|
||||
return False
|
||||
|
||||
msgbox(wnd, "Ensure the member has signed the machine\n"
|
||||
"usage policy. Accounts of users who have\n"
|
||||
"not signed will be suspended if discovered.")
|
||||
|
||||
# read password
|
||||
password = input_password(wnd)
|
||||
|
||||
# create the UNIX account
|
||||
try:
|
||||
if not accounts.connected(): accounts.connect()
|
||||
accounts.create_member(member['uid'][0], password, member['cn'][0])
|
||||
except accounts.NameConflict, e:
|
||||
msgbox(wnd, str(e))
|
||||
return False
|
||||
except accounts.NoAvailableIDs, e:
|
||||
msgbox(wnd, str(e))
|
||||
return False
|
||||
except accounts.InvalidArgument, e:
|
||||
msgbox(wnd, str(e))
|
||||
return False
|
||||
except accounts.LDAPException, e:
|
||||
msgbox(wnd, "Error creating LDAP entry - Contact the Systems Administrator: %s" % e)
|
||||
return False
|
||||
except accounts.KrbException, e:
|
||||
msgbox(wnd, "Error creating Kerberos principal - Contact the Systems Administrator: %s" % e)
|
||||
return False
|
||||
|
||||
# success
|
||||
msgbox(wnd, "Please run 'addhomedir " + member['uid'][0] + "'.")
|
||||
msgbox(wnd, "Success! Your account has been added")
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def display_member_details(wnd, member):
|
||||
"""Display member attributes in a message box."""
|
||||
|
||||
# clone and sort term_list
|
||||
if 'term' in member:
|
||||
term_list = list(member['term'])
|
||||
else:
|
||||
term_list = []
|
||||
term_list.sort( terms.compare )
|
||||
|
||||
# labels for data
|
||||
id_label, name_label = "ID:", "Name:"
|
||||
program_label, terms_label = "Program:", "Terms:"
|
||||
|
||||
if 'program' in member:
|
||||
program = member['program'][0]
|
||||
else:
|
||||
program = None
|
||||
|
||||
# format it all into a massive string
|
||||
message = "%8s %-20s %10s %-10s\n" % (name_label, member['cn'][0], id_label, member['uid'][0]) + \
|
||||
"%8s %-20s\n" % (program_label, program )
|
||||
|
||||
message += "%s %s" % (terms_label, " ".join(term_list))
|
||||
|
||||
# display the string in a message box
|
||||
msgbox(wnd, message)
|
||||
|
||||
|
||||
def action_display_member(wnd):
|
||||
"""Interactively display a member."""
|
||||
|
||||
if not members.connected(): members.connect()
|
||||
member = read_member(wnd)
|
||||
return False
|
||||
|
||||
|
||||
def page(text):
|
||||
"""Send a text buffer to an external pager for display."""
|
||||
|
||||
try:
|
||||
pager = '/usr/bin/less'
|
||||
pipe = os.popen(pager, 'w')
|
||||
pipe.write(text)
|
||||
pipe.close()
|
||||
except IOError:
|
||||
# broken pipe (user didn't read the whole list)
|
||||
pass
|
||||
|
||||
|
||||
def format_members(member_list):
|
||||
"""Format a member list into a string."""
|
||||
|
||||
# clone and sort member_list
|
||||
member_list = list(member_list)
|
||||
member_list.sort( lambda x, y: cmp(x['uid'], y['uid']) )
|
||||
|
||||
buf = ''
|
||||
|
||||
for member in member_list:
|
||||
if 'uid' in member:
|
||||
uid = member['uid'][0]
|
||||
else:
|
||||
uid = None
|
||||
if 'program' in member:
|
||||
program = member['program'][0]
|
||||
else:
|
||||
program = None
|
||||
attrs = ( uid, member['cn'][0], program )
|
||||
buf += "%10s %30s\n%41s\n\n" % attrs
|
||||
|
||||
return buf
|
||||
|
||||
|
||||
def action_list_term(wnd):
|
||||
"""Interactively list members registered in a term."""
|
||||
|
||||
term = None
|
||||
|
||||
# read the term
|
||||
prompt = "Which term to list members for ([fws]20nn): "
|
||||
while term is None or (not term == '' and not re.match('^[wsf][0-9]{4}$', term) and not term == 'exit'):
|
||||
term = inputbox(wnd, prompt, 41)
|
||||
|
||||
# abort when exit is entered
|
||||
if not term or term.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# connect the members module to its backends if necessary
|
||||
if not members.connected(): members.connect()
|
||||
|
||||
# retrieve a list of members for term
|
||||
member_list = members.list_term(term)
|
||||
|
||||
# format the data into a mess of text
|
||||
buf = format_members(member_list.values())
|
||||
|
||||
# display the mass of text with a pager
|
||||
page( buf )
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def action_list_name(wnd):
|
||||
"""Interactively search for members by name."""
|
||||
|
||||
name = None
|
||||
|
||||
# read the name
|
||||
prompt = "Enter the member's name: "
|
||||
name = inputbox(wnd, prompt, 41)
|
||||
|
||||
# abort when exit is entered
|
||||
if not name or name.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# connect the members module to its backends if necessary
|
||||
if not members.connected(): members.connect()
|
||||
|
||||
# retrieve a list of members with similar names
|
||||
member_list = members.list_name(name)
|
||||
|
||||
# format the data into a mess of text
|
||||
buf = format_members(member_list.values())
|
||||
|
||||
# display the mass of text with a pager
|
||||
page( buf )
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def null_callback(wnd):
|
||||
"""Callback for unimplemented menu options."""
|
||||
return False
|
||||
|
||||
|
||||
def exit_callback(wnd):
|
||||
"""Callback for the exit option."""
|
||||
return True
|
||||
|
||||
|
||||
# the top level ceo menu
|
||||
top_menu = [
|
||||
( "New member", action_new_member ),
|
||||
( "Register for a term", action_term_register ),
|
||||
( "Register for multiple terms", action_term_register_multiple ),
|
||||
( "Display a member", action_display_member ),
|
||||
( "List members registered in a term", action_list_term ),
|
||||
( "Search for a member by name", action_list_name ),
|
||||
( "Create an account", action_create_account ),
|
||||
( "Library functions", action_library ),
|
||||
( "Exit", exit_callback ),
|
||||
]
|
||||
|
||||
|
||||
def acquire_ceo_wnd(screen=None):
|
||||
"""Create the top level ceo window."""
|
||||
|
||||
# hack to get a reference to the entire screen
|
||||
# even when the caller doesn't (shouldn't) have one
|
||||
if screen is None:
|
||||
screen = globals()['screen']
|
||||
else:
|
||||
globals()['screen'] = screen
|
||||
|
||||
# if the screen changes size, a mess may be left
|
||||
screen.erase()
|
||||
|
||||
# for some reason, the legacy ceo system
|
||||
# excluded the top line from its window
|
||||
height, width = screen.getmaxyx()
|
||||
ceo_wnd = screen.subwin(height-1, width, 1, 0)
|
||||
|
||||
# draw the border around the ceo window
|
||||
curses.init_pair(1, BORDER_COLOR, -1)
|
||||
color_attr = curses.color_pair(1) | curses.A_BOLD
|
||||
ceo_wnd.attron(color_attr)
|
||||
ceo_wnd.border()
|
||||
ceo_wnd.attroff(color_attr)
|
||||
|
||||
# return window and dimensions of inner area
|
||||
return ceo_wnd, 1, 1, height-2, width-2
|
||||
|
||||
|
||||
def ceo_main_curses(screen):
|
||||
"""Wrapped main for curses."""
|
||||
|
||||
curses.use_default_colors()
|
||||
|
||||
# workaround for SSH sessions on virtual consoles (reset terminal)
|
||||
reset()
|
||||
|
||||
# create ceo window
|
||||
ceo_wnd, menu_y, menu_x, menu_height, menu_width = acquire_ceo_wnd(screen)
|
||||
|
||||
try:
|
||||
# display the top level menu
|
||||
menu(ceo_wnd, menu_y, menu_x, menu_width, top_menu, acquire_ceo_wnd)
|
||||
finally:
|
||||
members.disconnect()
|
||||
accounts.disconnect()
|
||||
|
||||
|
||||
def run():
|
||||
"""Main function for legacy UI."""
|
||||
|
||||
# workaround for xterm-color (bad terminfo? - curs_set(0) fails)
|
||||
if "TERM" in os.environ and os.environ['TERM'] == "xterm-color":
|
||||
os.environ['TERM'] = "xterm"
|
||||
|
||||
# wrap the entire program using curses.wrapper
|
||||
# so that the terminal is restored to a sane state
|
||||
# when the program exits
|
||||
try:
|
||||
curses.wrapper(ceo_main_curses)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except curses.error:
|
||||
print "Is your screen too small?"
|
||||
raise
|
||||
except:
|
||||
reset()
|
||||
raise
|
||||
|
||||
# clean up screen before exit
|
||||
reset()
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
|
Loading…
Reference in New Issue