PageRenderTime 78ms CodeModel.GetById 3ms RepoModel.GetById 0ms app.codeStats 1ms

/web/cobbler_web/views.py

https://github.com/dbvan/cobbler
Python | 1411 lines | 1393 code | 10 blank | 8 comment | 7 complexity | da4f6d491c83be9fe8700b10ed9d3405 MD5 | raw file
Possible License(s): GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. from django.template.loader import get_template
  2. from django.template import RequestContext
  3. from django.http import HttpResponse
  4. from django.http import HttpResponseRedirect
  5. from django.shortcuts import render_to_response
  6. from django.views.decorators.http import require_POST
  7. from django.views.decorators.csrf import csrf_protect
  8. import xmlrpclib
  9. import time
  10. import simplejson
  11. import string
  12. import cobbler.item_distro as item_distro
  13. import cobbler.item_profile as item_profile
  14. import cobbler.item_system as item_system
  15. import cobbler.item_repo as item_repo
  16. import cobbler.item_image as item_image
  17. import cobbler.item_mgmtclass as item_mgmtclass
  18. import cobbler.item_package as item_package
  19. import cobbler.item_file as item_file
  20. import cobbler.settings as item_settings
  21. import cobbler.field_info as field_info
  22. import cobbler.utils as utils
  23. url_cobbler_api = None
  24. remote = None
  25. username = None
  26. # ==================================================================================
  27. def index(request):
  28. """
  29. This is the main greeting page for cobbler web.
  30. """
  31. if not test_user_authenticated(request):
  32. return login(request, next="/cobbler_web", expired=True)
  33. t = get_template('index.tmpl')
  34. html = t.render(RequestContext(request, {
  35. 'version': remote.extended_version(request.session['token'])['version'],
  36. 'username': username,
  37. }))
  38. return HttpResponse(html)
  39. # ========================================================================
  40. def task_created(request):
  41. """
  42. Let's the user know what to expect for event updates.
  43. """
  44. if not test_user_authenticated(request):
  45. return login(request, next="/cobbler_web/task_created", expired=True)
  46. t = get_template("task_created.tmpl")
  47. html = t.render(RequestContext(request, {
  48. 'version': remote.extended_version(request.session['token'])['version'],
  49. 'username': username
  50. }))
  51. return HttpResponse(html)
  52. # ========================================================================
  53. def error_page(request, message):
  54. """
  55. This page is used to explain error messages to the user.
  56. """
  57. if not test_user_authenticated(request):
  58. return login(request, expired=True)
  59. # FIXME: test and make sure we use this rather than throwing lots of tracebacks for
  60. # field errors
  61. t = get_template('error_page.tmpl')
  62. message = message.replace("<Fault 1: \"<class 'cobbler.cexceptions.CX'>:'", "Remote exception: ")
  63. message = message.replace("'\">", "")
  64. html = t.render(RequestContext(request, {
  65. 'version': remote.extended_version(request.session['token'])['version'],
  66. 'message': message,
  67. 'username': username
  68. }))
  69. return HttpResponse(html)
  70. # ==================================================================================
  71. def get_fields(what, is_subobject, seed_item=None):
  72. """
  73. Helper function. Retrieves the field table from the cobbler objects
  74. and formats it in a way to make it useful for Django templating.
  75. The field structure indicates what fields to display and what the default
  76. values are, etc.
  77. """
  78. if what == "distro":
  79. field_data = item_distro.FIELDS
  80. if what == "profile":
  81. field_data = item_profile.FIELDS
  82. if what == "system":
  83. field_data = item_system.FIELDS
  84. if what == "repo":
  85. field_data = item_repo.FIELDS
  86. if what == "image":
  87. field_data = item_image.FIELDS
  88. if what == "mgmtclass":
  89. field_data = item_mgmtclass.FIELDS
  90. if what == "package":
  91. field_data = item_package.FIELDS
  92. if what == "file":
  93. field_data = item_file.FIELDS
  94. if what == "setting":
  95. field_data = item_settings.FIELDS
  96. settings = remote.get_settings()
  97. fields = []
  98. for row in field_data:
  99. elem = {
  100. "name": row[0],
  101. "dname": row[0].replace("*", ""),
  102. "value": "?",
  103. "caption": row[3],
  104. "editable": row[4],
  105. "tooltip": row[5],
  106. "choices": row[6],
  107. "css_class": "generic",
  108. "html_element": "generic",
  109. }
  110. if not elem["editable"]:
  111. continue
  112. if seed_item is not None:
  113. if what == "setting":
  114. elem["value"] = seed_item[row[0]]
  115. elif row[0].startswith("*"):
  116. # system interfaces are loaded by javascript, not this
  117. elem["value"] = ""
  118. elem["name"] = row[0].replace("*", "")
  119. elif row[0].find("widget") == -1:
  120. elem["value"] = seed_item[row[0]]
  121. elif is_subobject:
  122. elem["value"] = row[2]
  123. else:
  124. elem["value"] = row[1]
  125. if elem["value"] is None:
  126. elem["value"] = ""
  127. # we'll process this for display but still need to present the original to some
  128. # template logic
  129. elem["value_raw"] = elem["value"]
  130. if isinstance(elem["value"], basestring) and elem["value"].startswith("SETTINGS:"):
  131. key = elem["value"].replace("SETTINGS:", "", 1)
  132. elem["value"] = settings[key]
  133. # flatten hashes of all types, they can only be edited as text
  134. # as we have no HTML hash widget (yet)
  135. if isinstance(elem["value"], dict):
  136. if elem["name"] == "mgmt_parameters":
  137. # Render dictionary as YAML for Management Parameters field
  138. tokens = []
  139. for (x, y) in elem["value"].items():
  140. if y is not None:
  141. tokens.append("%s: %s" % (x, y))
  142. else:
  143. tokens.append("%s: " % x)
  144. elem["value"] = "{ %s }" % ", ".join(tokens)
  145. else:
  146. tokens = []
  147. for (x, y) in elem["value"].items():
  148. if isinstance(y, basestring) and y.strip() != "~":
  149. y = y.replace(" ", "\\ ")
  150. tokens.append("%s=%s" % (x, y))
  151. elif isinstance(y, list):
  152. for l in y:
  153. l = l.replace(" ", "\\ ")
  154. tokens.append("%s=%s" % (x, l))
  155. elif y is not None:
  156. tokens.append("%s" % x)
  157. elem["value"] = " ".join(tokens)
  158. name = row[0]
  159. if name.find("_widget") != -1:
  160. elem["html_element"] = "widget"
  161. elif name in field_info.USES_SELECT:
  162. elem["html_element"] = "select"
  163. elif name in field_info.USES_MULTI_SELECT:
  164. elem["html_element"] = "multiselect"
  165. elif name in field_info.USES_RADIO:
  166. elem["html_element"] = "radio"
  167. elif name in field_info.USES_CHECKBOX:
  168. elem["html_element"] = "checkbox"
  169. elif name in field_info.USES_TEXTAREA:
  170. elem["html_element"] = "textarea"
  171. else:
  172. elem["html_element"] = "text"
  173. elem["block_section"] = field_info.BLOCK_MAPPINGS.get(name, "General")
  174. # flatten lists for those that aren't using select boxes
  175. if isinstance(elem["value"], list):
  176. if elem["html_element"] != "select":
  177. elem["value"] = string.join(elem["value"], sep=" ")
  178. # FIXME: need to handle interfaces special, they are prefixed with "*"
  179. fields.append(elem)
  180. return fields
  181. # ==================================================================================
  182. def __tweak_field(fields, field_name, attribute, value):
  183. """
  184. Helper function to insert extra data into the field list.
  185. """
  186. # FIXME: eliminate this function.
  187. for x in fields:
  188. if x["name"] == field_name:
  189. x[attribute] = value
  190. # ==================================================================================
  191. def __format_columns(column_names, sort_field):
  192. """
  193. Format items retrieved from XMLRPC for rendering by the generic_edit template
  194. """
  195. dataset = []
  196. # Default is sorting on name
  197. if sort_field is not None:
  198. sort_name = sort_field
  199. else:
  200. sort_name = "name"
  201. if sort_name.startswith("!"):
  202. sort_name = sort_name[1:]
  203. sort_order = "desc"
  204. else:
  205. sort_order = "asc"
  206. for fieldname in column_names:
  207. fieldorder = "none"
  208. if fieldname == sort_name:
  209. fieldorder = sort_order
  210. dataset.append([fieldname, fieldorder])
  211. return dataset
  212. # ==================================================================================
  213. def __format_items(items, column_names):
  214. """
  215. Format items retrieved from XMLRPC for rendering by the generic_edit template
  216. """
  217. dataset = []
  218. for itemhash in items:
  219. row = []
  220. for fieldname in column_names:
  221. if fieldname == "name":
  222. html_element = "name"
  223. elif fieldname in ["system", "repo", "distro", "profile", "image", "mgmtclass", "package", "file"]:
  224. html_element = "editlink"
  225. elif fieldname in field_info.USES_CHECKBOX:
  226. html_element = "checkbox"
  227. else:
  228. html_element = "text"
  229. row.append([fieldname, itemhash[fieldname], html_element])
  230. dataset.append(row)
  231. return dataset
  232. # ==================================================================================
  233. def genlist(request, what, page=None):
  234. """
  235. Lists all object types, complete with links to actions
  236. on those objects.
  237. """
  238. if not test_user_authenticated(request):
  239. return login(request, next="/cobbler_web/%s/list" % what, expired=True)
  240. # get details from the session
  241. if page is None:
  242. page = int(request.session.get("%s_page" % what, 1))
  243. limit = int(request.session.get("%s_limit" % what, 50))
  244. sort_field = request.session.get("%s_sort_field" % what, "name")
  245. filters = simplejson.loads(request.session.get("%s_filters" % what, "{}"))
  246. pageditems = remote.find_items_paged(what, utils.strip_none(filters), sort_field, page, limit)
  247. # what columns to show for each page?
  248. # we also setup the batch actions here since they're dependent
  249. # on what we're looking at
  250. # everythng gets batch delete
  251. batchactions = [
  252. ["Delete", "delete", "delete"],
  253. ]
  254. if what == "distro":
  255. columns = ["name"]
  256. batchactions += [
  257. ["Build ISO", "buildiso", "enable"],
  258. ]
  259. if what == "profile":
  260. columns = ["name", "distro"]
  261. batchactions += [
  262. ["Build ISO", "buildiso", "enable"],
  263. ]
  264. if what == "system":
  265. # FIXME: also list network, once working
  266. columns = ["name", "profile", "status", "netboot_enabled"]
  267. batchactions += [
  268. ["Power on", "power", "on"],
  269. ["Power off", "power", "off"],
  270. ["Reboot", "power", "reboot"],
  271. ["Change profile", "profile", ""],
  272. ["Netboot enable", "netboot", "enable"],
  273. ["Netboot disable", "netboot", "disable"],
  274. ["Build ISO", "buildiso", "enable"],
  275. ]
  276. if what == "repo":
  277. columns = ["name", "mirror"]
  278. batchactions += [
  279. ["Reposync", "reposync", "go"],
  280. ]
  281. if what == "image":
  282. columns = ["name", "file"]
  283. if what == "network":
  284. columns = ["name"]
  285. if what == "mgmtclass":
  286. columns = ["name"]
  287. if what == "package":
  288. columns = ["name", "installer"]
  289. if what == "file":
  290. columns = ["name"]
  291. # render the list
  292. t = get_template('generic_list.tmpl')
  293. html = t.render(RequestContext(request, {
  294. 'what': what,
  295. 'columns': __format_columns(columns, sort_field),
  296. 'items': __format_items(pageditems["items"], columns),
  297. 'pageinfo': pageditems["pageinfo"],
  298. 'filters': filters,
  299. 'version': remote.extended_version(request.session['token'])['version'],
  300. 'username': username,
  301. 'limit': limit,
  302. 'batchactions': batchactions,
  303. }))
  304. return HttpResponse(html)
  305. @require_POST
  306. @csrf_protect
  307. def modify_list(request, what, pref, value=None):
  308. """
  309. This function is used in the generic list view
  310. to modify the page/column sort/number of items
  311. shown per page, and also modify the filters.
  312. This function modifies the session object to
  313. store these preferences persistently.
  314. """
  315. if not test_user_authenticated(request):
  316. return login(request, next="/cobbler_web/%s/modifylist/%s/%s" % (what, pref, str(value)), expired=True)
  317. # what preference are we tweaking?
  318. if pref == "sort":
  319. # FIXME: this isn't exposed in the UI.
  320. # sorting list on columns
  321. old_sort = request.session.get("%s_sort_field" % what, "name")
  322. if old_sort.startswith("!"):
  323. old_sort = old_sort[1:]
  324. old_revsort = True
  325. else:
  326. old_revsort = False
  327. # User clicked on the column already sorted on,
  328. # so reverse the sorting list
  329. if old_sort == value and not old_revsort:
  330. value = "!" + value
  331. request.session["%s_sort_field" % what] = value
  332. request.session["%s_page" % what] = 1
  333. elif pref == "limit":
  334. # number of items to show per page
  335. request.session["%s_limit" % what] = int(value)
  336. request.session["%s_page" % what] = 1
  337. elif pref == "page":
  338. # what page are we currently on
  339. request.session["%s_page" % what] = int(value)
  340. elif pref in ("addfilter", "removefilter"):
  341. # filters limit what we show in the lists
  342. # they are stored in json format for marshalling
  343. filters = simplejson.loads(request.session.get("%s_filters" % what, "{}"))
  344. if pref == "addfilter":
  345. (field_name, field_value) = value.split(":", 1)
  346. # add this filter
  347. filters[field_name] = field_value
  348. else:
  349. # remove this filter, if it exists
  350. if value in filters:
  351. del filters[value]
  352. # save session variable
  353. request.session["%s_filters" % what] = simplejson.dumps(filters)
  354. # since we changed what is viewed, reset the page
  355. request.session["%s_page" % what] = 1
  356. else:
  357. return error_page(request, "Invalid preference change request")
  358. # redirect to the list page
  359. return HttpResponseRedirect("/cobbler_web/%s/list" % what)
  360. # ======================================================================
  361. @require_POST
  362. @csrf_protect
  363. def generic_rename(request, what, obj_name=None, obj_newname=None):
  364. """
  365. Renames an object.
  366. """
  367. if not test_user_authenticated(request):
  368. return login(request, next="/cobbler_web/%s/rename/%s/%s" % (what, obj_name, obj_newname), expired=True)
  369. if obj_name is None:
  370. return error_page(request, "You must specify a %s to rename" % what)
  371. if not remote.has_item(what, obj_name):
  372. return error_page(request, "Unknown %s specified" % what)
  373. elif not remote.check_access_no_fail(request.session['token'], "modify_%s" % what, obj_name):
  374. return error_page(request, "You do not have permission to rename this %s" % what)
  375. else:
  376. obj_id = remote.get_item_handle(what, obj_name, request.session['token'])
  377. remote.rename_item(what, obj_id, obj_newname, request.session['token'])
  378. return HttpResponseRedirect("/cobbler_web/%s/list" % what)
  379. # ======================================================================
  380. @require_POST
  381. @csrf_protect
  382. def generic_copy(request, what, obj_name=None, obj_newname=None):
  383. """
  384. Copies an object.
  385. """
  386. if not test_user_authenticated(request):
  387. return login(request, next="/cobbler_web/%s/copy/%s/%s" % (what, obj_name, obj_newname), expired=True)
  388. # FIXME: shares all but one line with rename, merge it.
  389. if obj_name is None:
  390. return error_page(request, "You must specify a %s to rename" % what)
  391. if not remote.has_item(what, obj_name):
  392. return error_page(request, "Unknown %s specified" % what)
  393. elif not remote.check_access_no_fail(request.session['token'], "modify_%s" % what, obj_name):
  394. return error_page(request, "You do not have permission to copy this %s" % what)
  395. else:
  396. obj_id = remote.get_item_handle(what, obj_name, request.session['token'])
  397. remote.copy_item(what, obj_id, obj_newname, request.session['token'])
  398. return HttpResponseRedirect("/cobbler_web/%s/list" % what)
  399. # ======================================================================
  400. @require_POST
  401. @csrf_protect
  402. def generic_delete(request, what, obj_name=None):
  403. """
  404. Deletes an object.
  405. """
  406. if not test_user_authenticated(request):
  407. return login(request, next="/cobbler_web/%s/delete/%s" % (what, obj_name), expired=True)
  408. # FIXME: consolidate code with above functions.
  409. if obj_name is None:
  410. return error_page(request, "You must specify a %s to delete" % what)
  411. if not remote.has_item(what, obj_name):
  412. return error_page(request, "Unknown %s specified" % what)
  413. elif not remote.check_access_no_fail(request.session['token'], "remove_%s" % what, obj_name):
  414. return error_page(request, "You do not have permission to delete this %s" % what)
  415. else:
  416. # check whether object is to be deleted recursively
  417. recursive = simplejson.loads(request.POST.get("recursive", "false"))
  418. try:
  419. remote.xapi_object_edit(what, obj_name, "remove", {'name': obj_name, 'recursive': recursive}, request.session['token'])
  420. except Exception, e:
  421. return error_page(request, str(e))
  422. return HttpResponseRedirect("/cobbler_web/%s/list" % what)
  423. # ======================================================================
  424. @require_POST
  425. @csrf_protect
  426. def generic_domulti(request, what, multi_mode=None, multi_arg=None):
  427. """
  428. Process operations like profile reassignment, netboot toggling, and deletion
  429. which occur on all items that are checked on the list page.
  430. """
  431. if not test_user_authenticated(request):
  432. return login(request, next="/cobbler_web/%s/multi/%s/%s" % (what, multi_mode, multi_arg), expired=True)
  433. names = request.POST.get('names', '').strip().split()
  434. if names == "":
  435. return error_page(request, "Need to select some '%s' objects first" % what)
  436. if multi_mode == "delete":
  437. # check whether the objects are to be deleted recursively
  438. recursive = simplejson.loads(request.POST.get("recursive_batch", "false"))
  439. for obj_name in names:
  440. try:
  441. remote.xapi_object_edit(what, obj_name, "remove", {'name': obj_name, 'recursive': recursive}, request.session['token'])
  442. except Exception, e:
  443. return error_page(request, str(e))
  444. elif what == "system" and multi_mode == "netboot":
  445. netboot_enabled = multi_arg # values: enable or disable
  446. if netboot_enabled is None:
  447. return error_page(request, "Cannot modify systems without specifying netboot_enabled")
  448. if netboot_enabled == "enable":
  449. netboot_enabled = True
  450. elif netboot_enabled == "disable":
  451. netboot_enabled = False
  452. else:
  453. return error_page(request, "Invalid netboot option, expect enable or disable")
  454. for obj_name in names:
  455. obj_id = remote.get_system_handle(obj_name, request.session['token'])
  456. remote.modify_system(obj_id, "netboot_enabled", netboot_enabled, request.session['token'])
  457. remote.save_system(obj_id, request.session['token'], "edit")
  458. elif what == "system" and multi_mode == "profile":
  459. profile = multi_arg
  460. if profile is None:
  461. return error_page(request, "Cannot modify systems without specifying profile")
  462. for obj_name in names:
  463. obj_id = remote.get_system_handle(obj_name, request.session['token'])
  464. remote.modify_system(obj_id, "profile", profile, request.session['token'])
  465. remote.save_system(obj_id, request.session['token'], "edit")
  466. elif what == "system" and multi_mode == "power":
  467. # FIXME: power should not loop, but send the list of all systems in one set.
  468. power = multi_arg
  469. if power is None:
  470. return error_page(request, "Cannot modify systems without specifying power option")
  471. options = {"systems": names, "power": power}
  472. remote.background_power_system(options, request.session['token'])
  473. elif what == "system" and multi_mode == "buildiso":
  474. options = {"systems": names, "profiles": []}
  475. remote.background_buildiso(options, request.session['token'])
  476. elif what == "profile" and multi_mode == "buildiso":
  477. options = {"profiles": names, "systems": []}
  478. remote.background_buildiso(options, request.session['token'])
  479. elif what == "distro" and multi_mode == "buildiso":
  480. if len(names) > 1:
  481. return error_page(request, "You can only select one distro at a time to build an ISO for")
  482. options = {"standalone": True, "distro": str(names[0])}
  483. remote.background_buildiso(options, request.session['token'])
  484. elif what == "repo" and multi_mode == "reposync":
  485. options = {"repos": names, "tries": 3}
  486. remote.background_reposync(options, request.session['token'])
  487. else:
  488. return error_page(request, "Unknown batch operation on %ss: %s" % (what, str(multi_mode)))
  489. # FIXME: "operation complete" would make a lot more sense here than a redirect
  490. return HttpResponseRedirect("/cobbler_web/%s/list" % what)
  491. # ======================================================================
  492. def import_prompt(request):
  493. if not test_user_authenticated(request):
  494. return login(request, next="/cobbler_web/import/prompt", expired=True)
  495. t = get_template('import.tmpl')
  496. html = t.render(RequestContext(request, {
  497. 'version': remote.extended_version(request.session['token'])['version'],
  498. 'username': username,
  499. }))
  500. return HttpResponse(html)
  501. # ======================================================================
  502. def check(request):
  503. """
  504. Shows a page with the results of 'cobbler check'
  505. """
  506. if not test_user_authenticated(request):
  507. return login(request, next="/cobbler_web/check", expired=True)
  508. results = remote.check(request.session['token'])
  509. t = get_template('check.tmpl')
  510. html = t.render(RequestContext(request, {
  511. 'version': remote.extended_version(request.session['token'])['version'],
  512. 'username': username,
  513. 'results': results
  514. }))
  515. return HttpResponse(html)
  516. # ======================================================================
  517. @require_POST
  518. @csrf_protect
  519. def buildiso(request):
  520. if not test_user_authenticated(request):
  521. return login(request, next="/cobbler_web/buildiso", expired=True)
  522. remote.background_buildiso({}, request.session['token'])
  523. return HttpResponseRedirect('/cobbler_web/task_created')
  524. # ======================================================================
  525. @require_POST
  526. @csrf_protect
  527. def import_run(request):
  528. if not test_user_authenticated(request):
  529. return login(request, next="/cobbler_web/import/prompt", expired=True)
  530. options = {
  531. "name": request.POST.get("name", ""),
  532. "path": request.POST.get("path", ""),
  533. "breed": request.POST.get("breed", ""),
  534. "arch": request.POST.get("arch", "")
  535. }
  536. remote.background_import(options, request.session['token'])
  537. return HttpResponseRedirect('/cobbler_web/task_created')
  538. # ======================================================================
  539. def ksfile_list(request, page=None):
  540. """
  541. List all kickstart templates and link to their edit pages.
  542. """
  543. if not test_user_authenticated(request):
  544. return login(request, next="/cobbler_web/ksfile/list", expired=True)
  545. ksfiles = remote.get_kickstart_templates(request.session['token'])
  546. ksfile_list = []
  547. for ksfile in ksfiles:
  548. ksfile_list.append((ksfile, ksfile.replace('/var/lib/cobbler/kickstarts/', ''), 'editable'))
  549. t = get_template('ksfile_list.tmpl')
  550. html = t.render(RequestContext(request, {
  551. 'what': 'ksfile',
  552. 'ksfiles': ksfile_list,
  553. 'version': remote.extended_version(request.session['token'])['version'],
  554. 'username': username,
  555. 'item_count': len(ksfile_list[0]),
  556. }))
  557. return HttpResponse(html)
  558. # ======================================================================
  559. @csrf_protect
  560. def ksfile_edit(request, ksfile_name=None, editmode='edit'):
  561. """
  562. This is the page where a kickstart file is edited.
  563. """
  564. if not test_user_authenticated(request):
  565. return login(request, next="/cobbler_web/ksfile/edit/file:%s" % ksfile_name, expired=True)
  566. if editmode == 'edit':
  567. editable = False
  568. else:
  569. editable = True
  570. deleteable = False
  571. ksdata = ""
  572. if ksfile_name is not None:
  573. editable = remote.check_access_no_fail(request.session['token'], "modify_kickstart", ksfile_name)
  574. deleteable = not remote.is_kickstart_in_use(ksfile_name, request.session['token'])
  575. ksdata = remote.read_kickstart_template(ksfile_name, request.session['token'])
  576. t = get_template('ksfile_edit.tmpl')
  577. html = t.render(RequestContext(request, {
  578. 'ksfile_name': ksfile_name,
  579. 'deleteable': deleteable,
  580. 'ksdata': ksdata,
  581. 'editable': editable,
  582. 'editmode': editmode,
  583. 'version': remote.extended_version(request.session['token'])['version'],
  584. 'username': username
  585. }))
  586. return HttpResponse(html)
  587. # ======================================================================
  588. @require_POST
  589. @csrf_protect
  590. def ksfile_save(request):
  591. """
  592. This page processes and saves edits to a kickstart file.
  593. """
  594. if not test_user_authenticated(request):
  595. return login(request, next="/cobbler_web/ksfile/list", expired=True)
  596. # FIXME: error checking
  597. editmode = request.POST.get('editmode', 'edit')
  598. ksfile_name = request.POST.get('ksfile_name', None)
  599. ksdata = request.POST.get('ksdata', "").replace('\r\n', '\n')
  600. if ksfile_name is None:
  601. return HttpResponse("NO KSFILE NAME SPECIFIED")
  602. if editmode != 'edit':
  603. ksfile_name = "/var/lib/cobbler/kickstarts/" + ksfile_name
  604. delete1 = request.POST.get('delete1', None)
  605. delete2 = request.POST.get('delete2', None)
  606. if delete1 and delete2:
  607. remote.remove_kickstart_template(ksfile_name, request.session['token'])
  608. return HttpResponseRedirect('/cobbler_web/ksfile/list')
  609. else:
  610. remote.write_kickstart_template(ksfile_name, ksdata, request.session['token'])
  611. return HttpResponseRedirect('/cobbler_web/ksfile/list')
  612. # ======================================================================
  613. def snippet_list(request, page=None):
  614. """
  615. This page lists all available snippets and has links to edit them.
  616. """
  617. if not test_user_authenticated(request):
  618. return login(request, next="/cobbler_web/snippet/list", expired=True)
  619. snippets = remote.get_snippets(request.session['token'])
  620. snippet_list = []
  621. base_dir = "/var/lib/cobbler/snippets/"
  622. for snippet in snippets:
  623. if snippet.startswith(base_dir):
  624. snippet_list.append((snippet, snippet.replace(base_dir, ""), 'editable'))
  625. else:
  626. return error_page(request, "Invalid snippet at %s, outside %s" % (snippet, base_dir))
  627. t = get_template('snippet_list.tmpl')
  628. html = t.render(RequestContext(request, {
  629. 'what': 'snippet',
  630. 'snippets': snippet_list,
  631. 'version': remote.extended_version(request.session['token'])['version'],
  632. 'username': username
  633. }))
  634. return HttpResponse(html)
  635. # ======================================================================
  636. @csrf_protect
  637. def snippet_edit(request, snippet_name=None, editmode='edit'):
  638. """
  639. This page edits a specific snippet.
  640. """
  641. if not test_user_authenticated(request):
  642. return login(request, next="/cobbler_web/edit/file:%s" % snippet_name, expired=True)
  643. if editmode == 'edit':
  644. editable = False
  645. else:
  646. editable = True
  647. deleteable = False
  648. snippetdata = ""
  649. if snippet_name is not None:
  650. editable = remote.check_access_no_fail(request.session['token'], "modify_snippet", snippet_name)
  651. deleteable = True
  652. snippetdata = remote.read_kickstart_snippet(snippet_name, request.session['token'])
  653. t = get_template('snippet_edit.tmpl')
  654. html = t.render(RequestContext(request, {
  655. 'snippet_name': snippet_name,
  656. 'deleteable': deleteable,
  657. 'snippetdata': snippetdata,
  658. 'editable': editable,
  659. 'editmode': editmode,
  660. 'version': remote.extended_version(request.session['token'])['version'],
  661. 'username': username
  662. }))
  663. return HttpResponse(html)
  664. # ======================================================================
  665. @require_POST
  666. @csrf_protect
  667. def snippet_save(request):
  668. """
  669. This snippet saves a snippet once edited.
  670. """
  671. if not test_user_authenticated(request):
  672. return login(request, next="/cobbler_web/snippet/list", expired=True)
  673. # FIXME: error checking
  674. editmode = request.POST.get('editmode', 'edit')
  675. snippet_name = request.POST.get('snippet_name', None)
  676. snippetdata = request.POST.get('snippetdata', "").replace('\r\n', '\n')
  677. if snippet_name is None:
  678. return HttpResponse("NO SNIPPET NAME SPECIFIED")
  679. if editmode != 'edit':
  680. if snippet_name.find("/var/lib/cobbler/snippets/") != 0:
  681. snippet_name = "/var/lib/cobbler/snippets/" + snippet_name
  682. delete1 = request.POST.get('delete1', None)
  683. delete2 = request.POST.get('delete2', None)
  684. if delete1 and delete2:
  685. remote.remove_kickstart_snippet(snippet_name, request.session['token'])
  686. return HttpResponseRedirect('/cobbler_web/snippet/list')
  687. else:
  688. remote.write_kickstart_snippet(snippet_name, snippetdata, request.session['token'])
  689. return HttpResponseRedirect('/cobbler_web/snippet/list')
  690. # ======================================================================
  691. def setting_list(request):
  692. """
  693. This page presents a list of all the settings to the user. They are not editable.
  694. """
  695. if not test_user_authenticated(request):
  696. return login(request, next="/cobbler_web/setting/list", expired=True)
  697. settings = remote.get_settings()
  698. skeys = settings.keys()
  699. skeys.sort()
  700. results = []
  701. for k in skeys:
  702. results.append([k, settings[k]])
  703. t = get_template('settings.tmpl')
  704. html = t.render(RequestContext(request, {
  705. 'settings': results,
  706. 'version': remote.extended_version(request.session['token'])['version'],
  707. 'username': username,
  708. }))
  709. return HttpResponse(html)
  710. @csrf_protect
  711. def setting_edit(request, setting_name=None):
  712. if not setting_name:
  713. return HttpResponseRedirect('/cobbler_web/setting/list')
  714. if not test_user_authenticated(request):
  715. return login(request, next="/cobbler_web/setting/edit/%s" % setting_name, expired=True)
  716. settings = remote.get_settings()
  717. if setting_name not in settings:
  718. return error_page(request, "Unknown setting: %s" % setting_name)
  719. cur_setting = {
  720. 'name': setting_name,
  721. 'value': settings[setting_name],
  722. }
  723. fields = get_fields('setting', False, seed_item=cur_setting)
  724. sections = {}
  725. for field in fields:
  726. bmo = field_info.BLOCK_MAPPINGS_ORDER[field['block_section']]
  727. fkey = "%d_%s" % (bmo, field['block_section'])
  728. if fkey not in sections:
  729. sections[fkey] = {}
  730. sections[fkey]['name'] = field['block_section']
  731. sections[fkey]['fields'] = []
  732. sections[fkey]['fields'].append(field)
  733. t = get_template('generic_edit.tmpl')
  734. html = t.render(RequestContext(request, {
  735. 'what': 'setting',
  736. # 'fields': fields,
  737. 'sections': sections,
  738. 'subobject': False,
  739. 'editmode': 'edit',
  740. 'editable': True,
  741. 'version': remote.extended_version(request.session['token'])['version'],
  742. 'username': username,
  743. 'name': setting_name,
  744. }))
  745. return HttpResponse(html)
  746. @csrf_protect
  747. def setting_save(request):
  748. if not test_user_authenticated(request):
  749. return login(request, next="/cobbler_web/setting/list", expired=True)
  750. # load request fields and see if they are valid
  751. setting_name = request.POST.get('name', "")
  752. setting_value = request.POST.get('value', None)
  753. if setting_name == "":
  754. return error_page(request, "The setting name was not specified")
  755. settings = remote.get_settings()
  756. if setting_name not in settings:
  757. return error_page(request, "Unknown setting: %s" % setting_name)
  758. if remote.modify_setting(setting_name, setting_value, request.session['token']):
  759. return error_page(request, "There was an error saving the setting")
  760. return HttpResponseRedirect("/cobbler_web/setting/list")
  761. # ======================================================================
  762. def events(request):
  763. """
  764. This page presents a list of all the events and links to the event log viewer.
  765. """
  766. if not test_user_authenticated(request):
  767. return login(request, next="/cobbler_web/events", expired=True)
  768. events = remote.get_events()
  769. events2 = []
  770. for id in events.keys():
  771. (ttime, name, state, read_by) = events[id]
  772. events2.append([id, time.asctime(time.localtime(ttime)), name, state])
  773. def sorter(a, b):
  774. return cmp(a[0], b[0])
  775. events2.sort(sorter)
  776. t = get_template('events.tmpl')
  777. html = t.render(RequestContext(request, {
  778. 'results': events2,
  779. 'version': remote.extended_version(request.session['token'])['version'],
  780. 'username': username
  781. }))
  782. return HttpResponse(html)
  783. # ======================================================================
  784. def eventlog(request, event=0):
  785. """
  786. Shows the log for a given event.
  787. """
  788. if not test_user_authenticated(request):
  789. return login(request, next="/cobbler_web/eventlog/%s" % str(event), expired=True)
  790. event_info = remote.get_events()
  791. if event not in event_info:
  792. return HttpResponse("event not found")
  793. data = event_info[event]
  794. eventname = data[0]
  795. eventtime = data[1]
  796. eventstate = data[2]
  797. eventlog = remote.get_event_log(event)
  798. t = get_template('eventlog.tmpl')
  799. vars = {
  800. 'eventlog': eventlog,
  801. 'eventname': eventname,
  802. 'eventstate': eventstate,
  803. 'eventid': event,
  804. 'eventtime': eventtime,
  805. 'version': remote.extended_version(request.session['token'])['version'],
  806. 'username': username
  807. }
  808. html = t.render(RequestContext(request, vars))
  809. return HttpResponse(html)
  810. # ======================================================================
  811. def random_mac(request, virttype="xenpv"):
  812. """
  813. Used in an ajax call to fill in a field with a mac address.
  814. """
  815. # FIXME: not exposed in UI currently
  816. if not test_user_authenticated(request):
  817. return login(request, expired=True)
  818. random_mac = remote.get_random_mac(virttype, request.session['token'])
  819. return HttpResponse(random_mac)
  820. # ======================================================================
  821. @require_POST
  822. @csrf_protect
  823. def sync(request):
  824. """
  825. Runs 'cobbler sync' from the API when the user presses the sync button.
  826. """
  827. if not test_user_authenticated(request):
  828. return login(request, next="/cobbler_web/sync", expired=True)
  829. remote.background_sync({"verbose": "True"}, request.session['token'])
  830. return HttpResponseRedirect("/cobbler_web/task_created")
  831. # ======================================================================
  832. @require_POST
  833. @csrf_protect
  834. def reposync(request):
  835. """
  836. Syncs all repos that are configured to be synced.
  837. """
  838. if not test_user_authenticated(request):
  839. return login(request, next="/cobbler_web/reposync", expired=True)
  840. remote.background_reposync({"names": "", "tries": 3}, request.session['token'])
  841. return HttpResponseRedirect("/cobbler_web/task_created")
  842. # ======================================================================
  843. @require_POST
  844. @csrf_protect
  845. def hardlink(request):
  846. """
  847. Hardlinks files between repos and install trees to save space.
  848. """
  849. if not test_user_authenticated(request):
  850. return login(request, next="/cobbler_web/hardlink", expired=True)
  851. remote.background_hardlink({}, request.session['token'])
  852. return HttpResponseRedirect("/cobbler_web/task_created")
  853. # ======================================================================
  854. @require_POST
  855. @csrf_protect
  856. def replicate(request):
  857. """
  858. Replicate configuration from the central cobbler server, configured
  859. in /etc/cobbler/settings (note: this is uni-directional!)
  860. FIXME: this is disabled because we really need a web page to provide options for
  861. this command.
  862. """
  863. # settings = remote.get_settings()
  864. # options = settings # just load settings from file until we decide to ask user (later?)
  865. # remote.background_replicate(options, request.session['token'])
  866. if not test_user_authenticated(request):
  867. return login(request, next="/cobbler_web/replicate", expired=True)
  868. return HttpResponseRedirect("/cobbler_web/task_created")
  869. # ======================================================================
  870. def __names_from_dicts(loh, optional=True):
  871. """
  872. Tiny helper function.
  873. Get the names out of an array of hashes that the remote interface returns.
  874. """
  875. results = []
  876. if optional:
  877. results.append("<<None>>")
  878. for x in loh:
  879. results.append(x["name"])
  880. results.sort()
  881. return results
  882. # ======================================================================
  883. @csrf_protect
  884. def generic_edit(request, what=None, obj_name=None, editmode="new"):
  885. """
  886. Presents an editor page for any type of object.
  887. While this is generally standardized, systems are a little bit special.
  888. """
  889. target = ""
  890. if obj_name is not None:
  891. target = "/%s" % obj_name
  892. if not test_user_authenticated(request):
  893. return login(request, next="/cobbler_web/%s/edit%s" % (what, target), expired=True)
  894. obj = None
  895. child = False
  896. if what == "subprofile":
  897. what = "profile"
  898. child = True
  899. if obj_name is not None:
  900. editable = remote.check_access_no_fail(request.session['token'], "modify_%s" % what, obj_name)
  901. obj = remote.get_item(what, obj_name, False)
  902. else:
  903. editable = remote.check_access_no_fail(request.session['token'], "new_%s" % what, None)
  904. obj = None
  905. interfaces = {}
  906. if what == "system":
  907. if obj:
  908. interfaces = obj.get("interfaces", {})
  909. else:
  910. interfaces = {}
  911. fields = get_fields(what, child, obj)
  912. # create the kickstart pulldown list
  913. # allow for an empty value in the webui
  914. kickstart_list = remote.get_kickstart_templates()
  915. kickstart_list.append("")
  916. kickstart_list.sort()
  917. # populate some select boxes
  918. if what == "profile":
  919. if (obj and obj["parent"] not in (None, "")) or child:
  920. __tweak_field(fields, "parent", "choices", __names_from_dicts(remote.get_profiles()))
  921. else:
  922. __tweak_field(fields, "distro", "choices", __names_from_dicts(remote.get_distros()))
  923. __tweak_field(fields, "kickstart", "choices", kickstart_list)
  924. __tweak_field(fields, "repos", "choices", __names_from_dicts(remote.get_repos()))
  925. __tweak_field(fields, "mgmt_classes", "choices", __names_from_dicts(remote.get_mgmtclasses(), optional=False))
  926. elif what == "system":
  927. __tweak_field(fields, "profile", "choices", __names_from_dicts(remote.get_profiles()))
  928. __tweak_field(fields, "image", "choices", __names_from_dicts(remote.get_images(), optional=True))
  929. __tweak_field(fields, "kickstart", "choices", kickstart_list)
  930. __tweak_field(fields, "mgmt_classes", "choices", __names_from_dicts(remote.get_mgmtclasses(), optional=False))
  931. elif what == "mgmtclass":
  932. __tweak_field(fields, "packages", "choices", __names_from_dicts(remote.get_packages()))
  933. __tweak_field(fields, "files", "choices", __names_from_dicts(remote.get_files()))
  934. elif what == "distro":
  935. __tweak_field(fields, "arch", "choices", remote.get_valid_archs())
  936. __tweak_field(fields, "os_version", "choices", remote.get_valid_os_versions())
  937. __tweak_field(fields, "breed", "choices", remote.get_valid_breeds())
  938. __tweak_field(fields, "mgmt_classes", "choices", __names_from_dicts(remote.get_mgmtclasses(), optional=False))
  939. elif what == "image":
  940. __tweak_field(fields, "arch", "choices", remote.get_valid_archs())
  941. __tweak_field(fields, "breed", "choices", remote.get_valid_breeds())
  942. __tweak_field(fields, "os_version", "choices", remote.get_valid_os_versions())
  943. __tweak_field(fields, "kickstart", "choices", kickstart_list)
  944. # if editing save the fields in the session for comparison later
  945. if editmode == "edit":
  946. request.session['%s_%s' % (what, obj_name)] = fields
  947. sections = {}
  948. for field in fields:
  949. bmo = field_info.BLOCK_MAPPINGS_ORDER[field['block_section']]
  950. fkey = "%d_%s" % (bmo, field['block_section'])
  951. if fkey not in sections:
  952. sections[fkey] = {}
  953. sections[fkey]['name'] = field['block_section']
  954. sections[fkey]['fields'] = []
  955. sections[fkey]['fields'].append(field)
  956. t = get_template('generic_edit.tmpl')
  957. inames = interfaces.keys()
  958. inames.sort()
  959. html = t.render(RequestContext(request, {
  960. 'what': what,
  961. # 'fields': fields,
  962. 'sections': sections,
  963. 'subobject': child,
  964. 'editmode': editmode,
  965. 'editable': editable,
  966. 'interfaces': interfaces,
  967. 'interface_names': inames,
  968. 'interface_length': len(inames),
  969. 'version': remote.extended_version(request.session['token'])['version'],
  970. 'username': username,
  971. 'name': obj_name
  972. }))
  973. return HttpResponse(html)
  974. # ======================================================================
  975. @require_POST
  976. @csrf_protect
  977. def generic_save(request, what):
  978. """
  979. Saves an object back using the cobbler API after clearing any 'generic_edit' page.
  980. """
  981. if not test_user_authenticated(request):
  982. return login(request, next="/cobbler_web/%s/list" % what, expired=True)
  983. # load request fields and see if they are valid
  984. editmode = request.POST.get('editmode', 'edit')
  985. obj_name = request.POST.get('name', "")
  986. subobject = request.POST.get('subobject', "False")
  987. if subobject == "False":
  988. subobject = False
  989. else:
  990. subobject = True
  991. if obj_name == "":
  992. return error_page(request, "Required field name is missing")
  993. prev_fields = []
  994. if "%s_%s" % (what, obj_name) in request.session and editmode == "edit":
  995. prev_fields = request.session["%s_%s" % (what, obj_name)]
  996. # grab the remote object handle
  997. # for edits, fail in the object cannot be found to be edited
  998. # for new objects, fail if the object already exists
  999. if editmode == "edit":
  1000. if not remote.has_item(what, obj_name):
  1001. return error_page(request, "Failure trying to access item %s, it may have been deleted." % (obj_name))
  1002. obj_id = remote.get_item_handle(what, obj_name, request.session['token'])
  1003. else:
  1004. if remote.has_item(what, obj_name):
  1005. return error_page(request, "Could not create a new item %s, it already exists." % (obj_name))
  1006. obj_id = remote.new_item(what, request.session['token'])
  1007. # system needs either profile or image to be set
  1008. # fail if both are not set
  1009. if what == "system":
  1010. profile = request.POST.getlist('profile')
  1011. image = request.POST.getlist('image')
  1012. if "<<None>>" in profile and "<<None>>" in image:
  1013. return error_page(request, "Please provide either a valid profile or image for the system")
  1014. # walk through our fields list saving things we know how to save
  1015. fields = get_fields(what, subobject)
  1016. for field in fields:
  1017. if field['name'] == 'name' and editmode == 'edit':
  1018. # do not attempt renames here
  1019. continue
  1020. elif field['name'].startswith("*"):
  1021. # interface fields will be handled below
  1022. continue
  1023. else:
  1024. # check and see if the value exists in the fields stored in the session
  1025. prev_value = None
  1026. for prev_field in prev_fields:
  1027. if prev_field['name'] == field['name']:
  1028. prev_value = prev_field['value']
  1029. break
  1030. value = request.POST.get(field['name'], None)
  1031. # Checkboxes return the value of the field if checked, otherwise None
  1032. # convert to True/False
  1033. if field["html_element"] == "checkbox":
  1034. if value == field['name']:
  1035. value = True
  1036. else:
  1037. value = False
  1038. # Multiselect fields are handled differently
  1039. if field["html_element"] == "multiselect":
  1040. values = request.POST.getlist(field['name'])
  1041. value = []
  1042. if '<<inherit>>' in values:
  1043. value = '<<inherit>>'
  1044. else:
  1045. for single_value in values:
  1046. if single_value != "<<None>>":
  1047. value.insert(0, single_value)
  1048. if value is not None:
  1049. if value == "<<None>>":
  1050. value = ""
  1051. if value is not None and (not subobject or field['name'] != 'distro') and value != prev_value:
  1052. try:
  1053. remote.modify_item(what, obj_id, field['name'], value, request.session['token'])
  1054. except Exception, e:
  1055. return error_page(request, str(e))
  1056. # special handling for system interface fields
  1057. # which are the only objects in cobbler that will ever work this way
  1058. if what == "system":
  1059. interface_field_list = []
  1060. for field in fields:
  1061. if field['name'].startswith("*"):
  1062. field = field['name'].replace("*", "")
  1063. interface_field_list.append(field)
  1064. interfaces = request.POST.get('interface_list', "").split(",")
  1065. for interface in interfaces:
  1066. if interface == "":
  1067. continue
  1068. ifdata = {}
  1069. for item in interface_field_list:
  1070. ifdata["%s-%s" % (item, interface)] = request.POST.get("%s-%s" % (item, interface), "")
  1071. ifdata = utils.strip_none(ifdata)
  1072. # FIXME: I think this button is missing.
  1073. present = request.POST.get("present-%s" % interface, "")
  1074. original = request.POST.get("original-%s" % interface, "")
  1075. try:
  1076. if present == "0" and original == "1":
  1077. remote.modify_system(obj_id, 'delete_interface', interface, request.session['token'])
  1078. elif present == "1":
  1079. remote.modify_system(obj_id, 'modify_interface', ifdata, request.session['token'])
  1080. except Exception, e:
  1081. return error_page(request, str(e))
  1082. try:
  1083. remote.save_item(what, obj_id, request.session['token'], editmode)
  1084. except Exception, e:
  1085. return error_page(request, str(e))
  1086. return HttpResponseRedirect('/cobbler_web/%s/list' % what)
  1087. # ======================================================================
  1088. # Login/Logout views
  1089. def test_user_authenticated(request):
  1090. global remote
  1091. global username
  1092. global url_cobbler_api
  1093. if url_cobbler_api is None:
  1094. url_cobbler_api = utils.local_get_cobbler_api_url()
  1095. remote = xmlrpclib.Server(url_cobbler_api, allow_none=True)
  1096. # if we have a token, get the associated username from
  1097. # the remote server via XMLRPC. We then compare that to
  1098. # the value stored in the session. If everything matches up,
  1099. # the user is considered successfully authenticated
  1100. if 'token' in request.session and request.session['token'] != '':
  1101. try:
  1102. if remote.token_check(request.session['token']):
  1103. token_user = remote.get_user_from_token(request.session['token'])
  1104. if 'username' in request.session and request.session['username'] == token_user:
  1105. username = request.session['username']
  1106. return True
  1107. except:
  1108. # just let it fall through to the 'return False' below
  1109. pass
  1110. return False
  1111. use_passthru = -1
  1112. @csrf_protect
  1113. def login(request, next=None, message=None, expired=False):
  1114. global use_passthru
  1115. if use_passthru < 0:
  1116. token = remote.login("", utils.get_shared_secret())
  1117. auth_module = remote.get_authn_module_name(token)
  1118. use_passthru = auth_module == 'authn_passthru'
  1119. if use_passthru:
  1120. return accept_remote_user(request, next)
  1121. if expired and not message:
  1122. message = "Sorry, either you need to login or your session expired."
  1123. return render_to_response('login.tmpl', RequestContext(request, {'next': next, 'message': message}))
  1124. def accept_remote_user(request, nextsite):
  1125. global username
  1126. username = request.META['REMOTE_USER']
  1127. token = remote.login(username, utils.get_shared_secret())
  1128. request.session['username'] = username
  1129. request.session['token'] = token
  1130. if nextsite:
  1131. return HttpResponseRedirect(nextsite)
  1132. else:
  1133. return HttpResponseRedirect("/cobb…

Large files files are truncated, but you can click here to view the full file