summaryrefslogtreecommitdiffstats
path: root/modules-available
diff options
context:
space:
mode:
authorSimon Rettberg2016-05-03 19:03:09 +0200
committerSimon Rettberg2016-05-03 19:03:09 +0200
commit50404f3b23b7fd6aeae4c9d2f6df0ea25e984e66 (patch)
tree05e99fdffa696434960d7c77966c0bc36d6339e8 /modules-available
parentSecond half of merge.... (diff)
downloadslx-admin-50404f3b23b7fd6aeae4c9d2f6df0ea25e984e66.tar.gz
slx-admin-50404f3b23b7fd6aeae4c9d2f6df0ea25e984e66.tar.xz
slx-admin-50404f3b23b7fd6aeae4c9d2f6df0ea25e984e66.zip
WIP
Diffstat (limited to 'modules-available')
-rw-r--r--modules-available/adduser/config.json3
-rw-r--r--modules-available/adduser/lang/de/templates/page-adduser.json8
-rw-r--r--modules-available/adduser/lang/en/module.json8
-rw-r--r--modules-available/adduser/lang/en/templates/page-adduser.json8
-rw-r--r--modules-available/adduser/lang/pt/module.json8
-rw-r--r--modules-available/adduser/page.inc.php60
-rw-r--r--modules-available/adduser/templates/page-adduser.html28
-rw-r--r--modules-available/backup/config.json4
-rw-r--r--modules-available/backup/lang/de/module.json4
-rw-r--r--modules-available/backup/lang/de/templates/_page.json14
-rw-r--r--modules-available/backup/lang/de/templates/restore.json8
-rw-r--r--modules-available/backup/lang/en/module.json14
-rw-r--r--modules-available/backup/lang/en/templates/_page.json14
-rw-r--r--modules-available/backup/lang/en/templates/restore.json8
-rw-r--r--modules-available/backup/lang/pt/module.json14
-rw-r--r--modules-available/backup/page.inc.php164
-rw-r--r--modules-available/backup/templates/_page.html41
-rw-r--r--modules-available/backup/templates/restore.html62
-rw-r--r--modules-available/baseconfig/config.json4
-rw-r--r--modules-available/baseconfig/lang/de/cat_setting.json9
-rw-r--r--modules-available/baseconfig/lang/de/setting.json23
-rw-r--r--modules-available/baseconfig/lang/de/templates/_page.json15
-rw-r--r--modules-available/baseconfig/lang/en/cat_setting.json9
-rw-r--r--modules-available/baseconfig/lang/en/module.json27
-rw-r--r--modules-available/baseconfig/lang/en/setting.json23
-rw-r--r--modules-available/baseconfig/lang/en/templates/_page.json7
-rw-r--r--modules-available/baseconfig/lang/pt/module.json27
-rw-r--r--modules-available/baseconfig/page.inc.php129
-rw-r--r--modules-available/baseconfig/templates/_page.html188
-rw-r--r--modules-available/citymanagement/config.json4
-rw-r--r--modules-available/citymanagement/lang/en/module.json14
-rw-r--r--modules-available/citymanagement/lang/en/templates/citymanagement.json13
-rw-r--r--modules-available/citymanagement/lang/pt/module.json14
-rw-r--r--modules-available/citymanagement/page.inc.php81
-rw-r--r--modules-available/citymanagement/templates/page-citymanagement.html77
-rw-r--r--modules-available/dozmod/config.json4
-rw-r--r--modules-available/dozmod/lang/de/templates/images-delete.json12
-rw-r--r--modules-available/dozmod/lang/de/templates/mailconfig.json21
-rw-r--r--modules-available/dozmod/lang/de/templates/orglist.json6
-rw-r--r--modules-available/dozmod/lang/de/templates/userlist.json12
-rw-r--r--modules-available/dozmod/lang/en/module.json3
-rw-r--r--modules-available/dozmod/lang/en/templates/images-delete.json12
-rw-r--r--modules-available/dozmod/lang/en/templates/mailconfig.json21
-rw-r--r--modules-available/dozmod/lang/en/templates/orglist.json6
-rw-r--r--modules-available/dozmod/lang/en/templates/userlist.json12
-rw-r--r--modules-available/dozmod/page.inc.php252
-rw-r--r--modules-available/dozmod/templates/images-delete.html57
-rw-r--r--modules-available/dozmod/templates/mailconfig.html91
-rw-r--r--modules-available/dozmod/templates/orglist.html51
-rw-r--r--modules-available/dozmod/templates/userlist.html62
-rw-r--r--modules-available/eventlog/config.json4
-rw-r--r--modules-available/eventlog/lang/de/templates/_page.json6
-rw-r--r--modules-available/eventlog/lang/en/module.json7
-rw-r--r--modules-available/eventlog/lang/en/templates/_page.json6
-rw-r--r--modules-available/eventlog/lang/pt/module.json7
-rw-r--r--modules-available/eventlog/page.inc.php70
-rw-r--r--modules-available/eventlog/templates/_page.html41
-rw-r--r--modules-available/imgmanagement/config.json4
-rw-r--r--modules-available/imgmanagement/lang/pt/module.json3
-rw-r--r--modules-available/imgmanagement/page.inc.php63
-rw-r--r--modules-available/imgmanagement/templates/page-imgmanagement.html62
-rw-r--r--modules-available/internetaccess/config.json4
-rw-r--r--modules-available/internetaccess/lang/de/templates/_page.json14
-rw-r--r--modules-available/internetaccess/lang/de/templates/restart.json5
-rw-r--r--modules-available/internetaccess/lang/en/module.json3
-rw-r--r--modules-available/internetaccess/lang/en/templates/_page.json14
-rw-r--r--modules-available/internetaccess/lang/en/templates/restart.json5
-rw-r--r--modules-available/internetaccess/page.inc.php51
-rw-r--r--modules-available/internetaccess/templates/_page.html40
-rw-r--r--modules-available/internetaccess/templates/restart.html22
-rw-r--r--modules-available/locations/config.json4
-rw-r--r--modules-available/locations/lang/de/templates/location-subnets.json18
-rw-r--r--modules-available/locations/lang/de/templates/locations.json10
-rw-r--r--modules-available/locations/lang/de/templates/subnets.json7
-rw-r--r--modules-available/locations/lang/en/module.json3
-rw-r--r--modules-available/locations/lang/en/templates/location-subnets.json18
-rw-r--r--modules-available/locations/lang/en/templates/locations.json10
-rw-r--r--modules-available/locations/lang/en/templates/subnets.json7
-rw-r--r--modules-available/locations/page.inc.php348
-rw-r--r--modules-available/locations/templates/location-subnets.html73
-rw-r--r--modules-available/locations/templates/locations.html96
-rw-r--r--modules-available/locations/templates/subnets.html35
-rw-r--r--modules-available/main/category-icons.json7
-rw-r--r--modules-available/main/config.json3
-rw-r--r--modules-available/main/lang/de/module.json4
-rw-r--r--modules-available/main/lang/de/templates/dialog-generic.json3
-rw-r--r--modules-available/main/lang/de/templates/main-menu.json22
-rw-r--r--modules-available/main/lang/de/templates/messagebox-error.json3
-rw-r--r--modules-available/main/lang/de/templates/messagebox-info.json1
-rw-r--r--modules-available/main/lang/de/templates/messagebox-success.json1
-rw-r--r--modules-available/main/lang/de/templates/messagebox-warning.json1
-rw-r--r--modules-available/main/lang/de/templates/page-main-guest.json7
-rw-r--r--modules-available/main/lang/de/templates/page-main.json11
-rw-r--r--modules-available/main/lang/en/categories.json6
-rw-r--r--modules-available/main/lang/en/module.json13
-rw-r--r--modules-available/main/lang/en/templates/dialog-generic.json3
-rw-r--r--modules-available/main/lang/en/templates/main-menu.json28
-rw-r--r--modules-available/main/lang/en/templates/messagebox-warning.json2
-rw-r--r--modules-available/main/lang/en/templates/page-main-guest.json7
-rw-r--r--modules-available/main/lang/en/templates/page-main.json11
-rw-r--r--modules-available/main/lang/pt/module.json14
-rw-r--r--modules-available/main/page.inc.php64
-rw-r--r--modules-available/main/templates/dialog-generic.html13
-rw-r--r--modules-available/main/templates/footer.html2
-rw-r--r--modules-available/main/templates/main-menu.html72
-rw-r--r--modules-available/main/templates/messagebox-error.html1
-rw-r--r--modules-available/main/templates/messagebox-info.html1
-rw-r--r--modules-available/main/templates/messagebox-success.html1
-rw-r--r--modules-available/main/templates/messagebox-warning.html1
-rw-r--r--modules-available/main/templates/page-login.html11
-rw-r--r--modules-available/main/templates/page-main-guest.html15
-rw-r--r--modules-available/main/templates/page-main.html36
-rw-r--r--modules-available/main/templates/page-minilinux.html14
-rw-r--r--modules-available/main/templates/page-news.html57
-rw-r--r--modules-available/main/templates/page-syslog.html58
-rw-r--r--modules-available/main/templates/page-vmstore.html111
-rw-r--r--modules-available/main/templates/pagenav.html16
-rw-r--r--modules-available/main/templates/tm-callback-trigger.html15
-rw-r--r--modules-available/minilinux/config.json4
-rw-r--r--modules-available/minilinux/lang/de/templates/download.json1
-rw-r--r--modules-available/minilinux/lang/de/templates/filelist.json12
-rw-r--r--modules-available/minilinux/lang/de/templates/page-minilinux.json4
-rw-r--r--modules-available/minilinux/lang/en/module.json12
-rw-r--r--modules-available/minilinux/lang/en/templates/download.json2
-rw-r--r--modules-available/minilinux/lang/en/templates/filelist.json12
-rw-r--r--modules-available/minilinux/lang/en/templates/page-minilinux.json4
-rw-r--r--modules-available/minilinux/lang/pt/module.json12
-rw-r--r--modules-available/minilinux/page.inc.php128
-rw-r--r--modules-available/minilinux/templates/download.html1
-rw-r--r--modules-available/minilinux/templates/filelist.html77
-rw-r--r--modules-available/minilinux/templates/page-minilinux.html28
-rw-r--r--modules-available/news/config.json4
-rw-r--r--modules-available/news/lang/de/templates/page-news.json13
-rw-r--r--modules-available/news/lang/en/module.json12
-rw-r--r--modules-available/news/lang/en/templates/page-news.json12
-rw-r--r--modules-available/news/lang/pt/module.json13
-rw-r--r--modules-available/news/page.inc.php167
-rw-r--r--modules-available/news/templates/page-news.html57
-rw-r--r--modules-available/serversetup/config.json4
-rw-r--r--modules-available/serversetup/lang/de/templates/ipaddress.json7
-rw-r--r--modules-available/serversetup/lang/de/templates/ipxe.json19
-rw-r--r--modules-available/serversetup/lang/de/templates/ipxe_update.json4
-rw-r--r--modules-available/serversetup/lang/en/module.json34
-rw-r--r--modules-available/serversetup/lang/en/templates/ipaddress.json7
-rw-r--r--modules-available/serversetup/lang/en/templates/ipxe.json31
-rw-r--r--modules-available/serversetup/lang/en/templates/ipxe_update.json4
-rw-r--r--modules-available/serversetup/lang/pt/module.json40
-rw-r--r--modules-available/serversetup/lang/pt/templates/ipxe-adv.json28
-rw-r--r--modules-available/serversetup/page.inc.php194
-rw-r--r--modules-available/serversetup/templates/ipaddress.html35
-rw-r--r--modules-available/serversetup/templates/ipxe-adv.html149
-rw-r--r--modules-available/serversetup/templates/ipxe-smp.html62
-rw-r--r--modules-available/serversetup/templates/ipxe_update.html20
-rw-r--r--modules-available/session/config.json3
-rw-r--r--modules-available/session/lang/de/templates/page-login.json8
-rw-r--r--modules-available/session/lang/en/module.json8
-rw-r--r--modules-available/session/lang/en/templates/page-login.json8
-rw-r--r--modules-available/session/lang/pt/module.json8
-rw-r--r--modules-available/session/page.inc.php36
-rw-r--r--modules-available/session/templates/page-login.html11
-rw-r--r--modules-available/statistics/config.json5
-rw-r--r--modules-available/statistics/lang/de/module.json4
-rw-r--r--modules-available/statistics/lang/de/templates/clientlist.json14
-rw-r--r--modules-available/statistics/lang/de/templates/cpumodels.json6
-rw-r--r--modules-available/statistics/lang/de/templates/id44.json5
-rw-r--r--modules-available/statistics/lang/de/templates/kvmstate.json5
-rw-r--r--modules-available/statistics/lang/de/templates/machine-hdds.json13
-rw-r--r--modules-available/statistics/lang/de/templates/machine-main.json26
-rw-r--r--modules-available/statistics/lang/de/templates/machine-notes.json4
-rw-r--r--modules-available/statistics/lang/de/templates/machine-usage.json4
-rw-r--r--modules-available/statistics/lang/de/templates/memory.json5
-rw-r--r--modules-available/statistics/lang/de/templates/newclients.json4
-rw-r--r--modules-available/statistics/lang/de/templates/summary.json6
-rw-r--r--modules-available/statistics/lang/de/templates/syslog.json7
-rw-r--r--modules-available/statistics/lang/en/module.json3
-rw-r--r--modules-available/statistics/lang/en/templates/clientlist.json14
-rw-r--r--modules-available/statistics/lang/en/templates/cpumodels.json6
-rw-r--r--modules-available/statistics/lang/en/templates/id44.json5
-rw-r--r--modules-available/statistics/lang/en/templates/kvmstate.json5
-rw-r--r--modules-available/statistics/lang/en/templates/machine-hdds.json13
-rw-r--r--modules-available/statistics/lang/en/templates/machine-main.json26
-rw-r--r--modules-available/statistics/lang/en/templates/machine-notes.json4
-rw-r--r--modules-available/statistics/lang/en/templates/machine-usage.json4
-rw-r--r--modules-available/statistics/lang/en/templates/memory.json5
-rw-r--r--modules-available/statistics/lang/en/templates/newclients.json4
-rw-r--r--modules-available/statistics/lang/en/templates/summary.json6
-rw-r--r--modules-available/statistics/lang/en/templates/syslog.json7
-rw-r--r--modules-available/statistics/page.inc.php792
-rw-r--r--modules-available/statistics/templates/clientlist.html45
-rw-r--r--modules-available/statistics/templates/cpumodels.html51
-rw-r--r--modules-available/statistics/templates/id44.html48
-rw-r--r--modules-available/statistics/templates/kvmstate.html47
-rw-r--r--modules-available/statistics/templates/machine-hdds.html67
-rw-r--r--modules-available/statistics/templates/machine-main.html124
-rw-r--r--modules-available/statistics/templates/machine-notes.html17
-rw-r--r--modules-available/statistics/templates/machine-usage.html51
-rw-r--r--modules-available/statistics/templates/memory.html47
-rw-r--r--modules-available/statistics/templates/newclients.html44
-rw-r--r--modules-available/statistics/templates/summary.html33
-rw-r--r--modules-available/statistics/templates/syslog.html43
-rw-r--r--modules-available/support/config.json4
-rw-r--r--modules-available/support/faq.json19
-rw-r--r--modules-available/support/lang/en/module.json3
-rw-r--r--modules-available/support/lang/pt/module.json18
-rw-r--r--modules-available/support/page.inc.php83
-rw-r--r--modules-available/support/templates/page-faq.html29
-rw-r--r--modules-available/support/templates/page-support.html59
-rw-r--r--modules-available/sysconfig/addconfig.inc.php221
-rw-r--r--modules-available/sysconfig/addmodule.inc.php180
-rw-r--r--modules-available/sysconfig/addmodule_adauth.inc.php413
-rw-r--r--modules-available/sysconfig/addmodule_branding.inc.php238
-rw-r--r--modules-available/sysconfig/addmodule_custommodule.inc.php163
-rw-r--r--modules-available/sysconfig/addmodule_ldapauth.inc.php234
-rw-r--r--modules-available/sysconfig/addmodule_sshconfig.inc.php72
-rw-r--r--modules-available/sysconfig/config.json5
-rw-r--r--modules-available/sysconfig/lang/de/templates/_page.json28
-rw-r--r--modules-available/sysconfig/lang/de/templates/ad-finish.json7
-rw-r--r--modules-available/sysconfig/lang/de/templates/ad-selfsearch.json10
-rw-r--r--modules-available/sysconfig/lang/de/templates/ad-start.json23
-rw-r--r--modules-available/sysconfig/lang/de/templates/ad_ldap-checkconnection.json8
-rw-r--r--modules-available/sysconfig/lang/de/templates/ad_ldap-checkcredentials.json6
-rw-r--r--modules-available/sysconfig/lang/de/templates/branding-check.json5
-rw-r--r--modules-available/sysconfig/lang/de/templates/branding-start.json9
-rw-r--r--modules-available/sysconfig/lang/de/templates/cfg-finish.json4
-rw-r--r--modules-available/sysconfig/lang/de/templates/cfg-start.json7
-rw-r--r--modules-available/sysconfig/lang/de/templates/config-module-list.json4
-rw-r--r--modules-available/sysconfig/lang/de/templates/custom-filelist.json3
-rw-r--r--modules-available/sysconfig/lang/de/templates/custom-fileselect.json5
-rw-r--r--modules-available/sysconfig/lang/de/templates/custom-upload.json8
-rw-r--r--modules-available/sysconfig/lang/de/templates/ldap-checkconnection.json5
-rw-r--r--modules-available/sysconfig/lang/de/templates/ldap-checkcredentials.json6
-rw-r--r--modules-available/sysconfig/lang/de/templates/ldap-finish.json6
-rw-r--r--modules-available/sysconfig/lang/de/templates/ldap-start.json16
-rw-r--r--modules-available/sysconfig/lang/de/templates/sshconfig-start.json10
-rw-r--r--modules-available/sysconfig/lang/de/templates/start.json4
-rw-r--r--modules-available/sysconfig/lang/en/module.json4
-rw-r--r--modules-available/sysconfig/lang/en/templates/_page.json28
-rw-r--r--modules-available/sysconfig/lang/en/templates/ad-finish.json7
-rw-r--r--modules-available/sysconfig/lang/en/templates/ad-selfsearch.json10
-rw-r--r--modules-available/sysconfig/lang/en/templates/ad-start.json23
-rw-r--r--modules-available/sysconfig/lang/en/templates/ad_ldap-checkconnection.json8
-rw-r--r--modules-available/sysconfig/lang/en/templates/ad_ldap-checkcredentials.json6
-rw-r--r--modules-available/sysconfig/lang/en/templates/ad_ldap-homedir.json18
-rw-r--r--modules-available/sysconfig/lang/en/templates/branding-check.json5
-rw-r--r--modules-available/sysconfig/lang/en/templates/branding-start.json9
-rw-r--r--modules-available/sysconfig/lang/en/templates/cfg-finish.json4
-rw-r--r--modules-available/sysconfig/lang/en/templates/cfg-start.json6
-rw-r--r--modules-available/sysconfig/lang/en/templates/config-module-list.json4
-rw-r--r--modules-available/sysconfig/lang/en/templates/custom-filelist.json3
-rw-r--r--modules-available/sysconfig/lang/en/templates/custom-fileselect.json5
-rw-r--r--modules-available/sysconfig/lang/en/templates/custom-upload.json8
-rw-r--r--modules-available/sysconfig/lang/en/templates/ldap-checkconnection.json5
-rw-r--r--modules-available/sysconfig/lang/en/templates/ldap-checkcredentials.json6
-rw-r--r--modules-available/sysconfig/lang/en/templates/ldap-finish.json6
-rw-r--r--modules-available/sysconfig/lang/en/templates/ldap-start.json16
-rw-r--r--modules-available/sysconfig/lang/en/templates/sshconfig-start.json10
-rw-r--r--modules-available/sysconfig/lang/en/templates/start.json4
-rw-r--r--modules-available/sysconfig/lang/pt/module.json38
-rw-r--r--modules-available/sysconfig/page.inc.php396
-rw-r--r--modules-available/sysconfig/templates/_page.html227
-rw-r--r--modules-available/sysconfig/templates/ad-finish.html29
-rw-r--r--modules-available/sysconfig/templates/ad-selfsearch.html112
-rw-r--r--modules-available/sysconfig/templates/ad-start.html121
-rw-r--r--modules-available/sysconfig/templates/ad_ldap-checkconnection.html91
-rw-r--r--modules-available/sysconfig/templates/ad_ldap-checkcredentials.html67
-rw-r--r--modules-available/sysconfig/templates/branding-check.html26
-rw-r--r--modules-available/sysconfig/templates/branding-start.html25
-rw-r--r--modules-available/sysconfig/templates/cfg-finish.html12
-rw-r--r--modules-available/sysconfig/templates/cfg-start.html39
-rw-r--r--modules-available/sysconfig/templates/config-module-list.html17
-rw-r--r--modules-available/sysconfig/templates/custom-filelist.html16
-rw-r--r--modules-available/sysconfig/templates/custom-fileselect.html31
-rw-r--r--modules-available/sysconfig/templates/custom-upload.html18
-rw-r--r--modules-available/sysconfig/templates/ldap-finish.html29
-rw-r--r--modules-available/sysconfig/templates/ldap-start.html101
-rw-r--r--modules-available/sysconfig/templates/sshconfig-start.html27
-rw-r--r--modules-available/sysconfig/templates/start.html12
-rw-r--r--modules-available/sysconfignew/config.json3
-rw-r--r--modules-available/sysconfignew/lang/en/module.json23
-rw-r--r--modules-available/sysconfignew/lang/pt/module.json23
-rw-r--r--modules-available/sysconfignew/page.inc.php113
-rw-r--r--modules-available/sysconfignew/templates/_pagenew.html190
-rw-r--r--modules-available/sysconfignew/templates/module-editor.html269
-rw-r--r--modules-available/syslog/config.json4
-rw-r--r--modules-available/syslog/lang/de/module.json4
-rw-r--r--modules-available/syslog/lang/de/templates/page-syslog.json10
-rw-r--r--modules-available/syslog/lang/en/module.json11
-rw-r--r--modules-available/syslog/lang/en/templates/page-syslog.json10
-rw-r--r--modules-available/syslog/lang/pt/module.json11
-rw-r--r--modules-available/syslog/page.inc.php94
-rw-r--r--modules-available/syslog/templates/page-syslog.html58
-rw-r--r--modules-available/systemstatus/config.json4
-rw-r--r--modules-available/systemstatus/lang/de/templates/_page.json11
-rw-r--r--modules-available/systemstatus/lang/de/templates/addresses.json1
-rw-r--r--modules-available/systemstatus/lang/de/templates/diskstat.json12
-rw-r--r--modules-available/systemstatus/lang/de/templates/services.json1
-rw-r--r--modules-available/systemstatus/lang/de/templates/systeminfo.json15
-rw-r--r--modules-available/systemstatus/lang/en/module.json3
-rw-r--r--modules-available/systemstatus/lang/en/templates/_page.json11
-rw-r--r--modules-available/systemstatus/lang/en/templates/addresses.json2
-rw-r--r--modules-available/systemstatus/lang/en/templates/diskstat.json12
-rw-r--r--modules-available/systemstatus/lang/en/templates/services.json2
-rw-r--r--modules-available/systemstatus/lang/en/templates/systeminfo.json15
-rw-r--r--modules-available/systemstatus/lang/pt/module.json26
-rw-r--r--modules-available/systemstatus/page.inc.php361
-rw-r--r--modules-available/systemstatus/templates/_page.html129
-rw-r--r--modules-available/systemstatus/templates/addresses.html8
-rw-r--r--modules-available/systemstatus/templates/diskstat.html63
-rw-r--r--modules-available/systemstatus/templates/services.html6
-rw-r--r--modules-available/systemstatus/templates/systeminfo.html115
-rw-r--r--modules-available/translation/config.json4
-rw-r--r--modules-available/translation/lang/de/templates/_page.json9
-rw-r--r--modules-available/translation/lang/de/templates/edit.json12
-rw-r--r--modules-available/translation/lang/de/templates/template-list.json5
-rw-r--r--modules-available/translation/lang/en/module.json16
-rw-r--r--modules-available/translation/lang/en/templates/_page.json9
-rw-r--r--modules-available/translation/lang/en/templates/edit.json12
-rw-r--r--modules-available/translation/lang/en/templates/template-list.json5
-rw-r--r--modules-available/translation/lang/pt/module.json16
-rw-r--r--modules-available/translation/page.inc.php597
-rw-r--r--modules-available/translation/templates/_page.html17
-rw-r--r--modules-available/translation/templates/edit.html71
-rw-r--r--modules-available/translation/templates/module-list.html32
-rw-r--r--modules-available/translation/templates/template-list.html32
-rw-r--r--modules-available/usermanagement/config.json5
-rw-r--r--modules-available/usermanagement/lang/en/module.json19
-rw-r--r--modules-available/usermanagement/lang/en/templates/user-management.json18
-rw-r--r--modules-available/usermanagement/lang/pt/module.json19
-rw-r--r--modules-available/usermanagement/page.inc.php109
-rw-r--r--modules-available/usermanagement/templates/user-management.html127
-rw-r--r--modules-available/vmstore/config.json4
-rw-r--r--modules-available/vmstore/lang/de/templates/mount.json5
-rw-r--r--modules-available/vmstore/lang/de/templates/page-vmstore.json17
-rw-r--r--modules-available/vmstore/lang/en/module.json21
-rw-r--r--modules-available/vmstore/lang/en/templates/mount.json5
-rw-r--r--modules-available/vmstore/lang/en/templates/page-vmstore.json17
-rw-r--r--modules-available/vmstore/lang/pt/module.json21
-rw-r--r--modules-available/vmstore/page.inc.php63
-rw-r--r--modules-available/vmstore/templates/mount.html25
-rw-r--r--modules-available/vmstore/templates/page-vmstore.html111
-rw-r--r--modules-available/webinterface/config.json4
-rw-r--r--modules-available/webinterface/lang/de/templates/httpd-restart.json4
-rw-r--r--modules-available/webinterface/lang/de/templates/https.json12
-rw-r--r--modules-available/webinterface/lang/de/templates/passwords.json7
-rw-r--r--modules-available/webinterface/lang/en/module.json3
-rw-r--r--modules-available/webinterface/lang/en/templates/httpd-restart.json4
-rw-r--r--modules-available/webinterface/lang/en/templates/https.json12
-rw-r--r--modules-available/webinterface/lang/en/templates/passwords.json7
-rw-r--r--modules-available/webinterface/page.inc.php85
-rw-r--r--modules-available/webinterface/templates/httpd-restart.html6
-rw-r--r--modules-available/webinterface/templates/https.html60
-rw-r--r--modules-available/webinterface/templates/passwords.html25
352 files changed, 13181 insertions, 0 deletions
diff --git a/modules-available/adduser/config.json b/modules-available/adduser/config.json
new file mode 100644
index 00000000..4da67ef8
--- /dev/null
+++ b/modules-available/adduser/config.json
@@ -0,0 +1,3 @@
+{
+ "enabled":"true"
+}
diff --git a/modules-available/adduser/lang/de/templates/page-adduser.json b/modules-available/adduser/lang/de/templates/page-adduser.json
new file mode 100644
index 00000000..0f446025
--- /dev/null
+++ b/modules-available/adduser/lang/de/templates/page-adduser.json
@@ -0,0 +1,8 @@
+{
+ "lang_confirmation": "Wiederholen",
+ "lang_createUser": "Benutzer anlegen",
+ "lang_fullName": "Vollst\u00e4ndiger Name",
+ "lang_password": "Passwort",
+ "lang_telephone": "Telefon",
+ "lang_username": "Benutzerkennung"
+} \ No newline at end of file
diff --git a/modules-available/adduser/lang/en/module.json b/modules-available/adduser/lang/en/module.json
new file mode 100644
index 00000000..170c12f6
--- /dev/null
+++ b/modules-available/adduser/lang/en/module.json
@@ -0,0 +1,8 @@
+{
+ "lang_confirmation": "Confirm Password",
+ "lang_createUser": "Create User",
+ "lang_fullName": "Fullname",
+ "lang_password": "Password",
+ "lang_telephone": "Telephone",
+ "lang_username": "Username"
+} \ No newline at end of file
diff --git a/modules-available/adduser/lang/en/templates/page-adduser.json b/modules-available/adduser/lang/en/templates/page-adduser.json
new file mode 100644
index 00000000..42bae6dc
--- /dev/null
+++ b/modules-available/adduser/lang/en/templates/page-adduser.json
@@ -0,0 +1,8 @@
+{
+ "lang_confirmation": "Confirm Password",
+ "lang_createUser": "Create User",
+ "lang_fullName": "Full Name",
+ "lang_password": "Password",
+ "lang_telephone": "Telephone",
+ "lang_username": "Username"
+} \ No newline at end of file
diff --git a/modules-available/adduser/lang/pt/module.json b/modules-available/adduser/lang/pt/module.json
new file mode 100644
index 00000000..524f3dd5
--- /dev/null
+++ b/modules-available/adduser/lang/pt/module.json
@@ -0,0 +1,8 @@
+{
+ "lang_confirmation": "Confirmar Senha",
+ "lang_createUser": "Criar Usu\u00e1rio",
+ "lang_fullName": "Nome Completo",
+ "lang_password": "Senha",
+ "lang_telephone": "Telefone",
+ "lang_username": "Nome de Usu\u00e1rio"
+} \ No newline at end of file
diff --git a/modules-available/adduser/page.inc.php b/modules-available/adduser/page.inc.php
new file mode 100644
index 00000000..c236cb6f
--- /dev/null
+++ b/modules-available/adduser/page.inc.php
@@ -0,0 +1,60 @@
+<?php
+
+class Page_AddUser extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (isset($_POST['action']) && $_POST['action'] === 'adduser') {
+ // Check required fields
+ if (empty($_POST['user']) || empty($_POST['pass1']) || empty($_POST['pass2']) || empty($_POST['fullname'])) {
+ Message::addError('empty-field');
+ Util::redirect('?do=AddUser');
+ } elseif ($_POST['pass1'] !== $_POST['pass2']) {
+ Message::addError('password-mismatch');
+ Util::redirect('?do=AddUser');
+ } elseif (!User::hasPermission('superadmin') && Database::queryFirst('SELECT userid FROM user LIMIT 1') !== false) {
+ Message::addError('adduser-disabled');
+ Util::redirect('?do=Session&action=login');
+ } else {
+ $data = array(
+ 'user' => $_POST['user'],
+ 'pass' => Crypto::hash6($_POST['pass1']),
+ 'fullname' => $_POST['fullname'],
+ 'phone' => $_POST['phone'],
+ 'email' => $_POST['email'],
+ );
+ if (Database::exec('INSERT INTO user SET login = :user, passwd = :pass, fullname = :fullname, phone = :phone, email = :email', $data) != 1) {
+ Util::traceError('Could not create new user in DB');
+ }
+ // Make it superadmin if first user. This method sucks as it's a race condition but hey...
+ $ret = Database::queryFirst('SELECT Count(*) AS num FROM user');
+ if ($ret !== false && $ret['num'] == 1) {
+ Database::exec('UPDATE user SET permissions = 1');
+ EventLog::clear();
+ EventLog::info('Created first user ' . $_POST['user']);
+ } else {
+ EventLog::info(User::getName() . ' created user ' . $_POST['user']);
+ }
+ Message::addInfo('adduser-success');
+ Util::redirect('?do=Session&action=login');
+ }
+ }
+ }
+
+ protected function doRender()
+ {
+ // No user was added, check if current user is allowed to add a new user
+ // Currently you can only add users if there is no user yet. :)
+ if (!User::hasPermission('superadmin') && Database::queryFirst('SELECT userid FROM user LIMIT 1') !== false) {
+ Message::addError('adduser-disabled');
+ } else {
+
+ Render::setTitle(Dictionary::translate('lang_createUser'));
+ Render::addTemplate('page-adduser', $_POST);
+ }
+ }
+
+}
diff --git a/modules-available/adduser/templates/page-adduser.html b/modules-available/adduser/templates/page-adduser.html
new file mode 100644
index 00000000..0b097890
--- /dev/null
+++ b/modules-available/adduser/templates/page-adduser.html
@@ -0,0 +1,28 @@
+<form class="form-adduser" action="?do=AddUser" method="post">
+ <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;">
+ <input type="password" name="password_fake" id="password_fake" value="" style="display:none;">
+ <h2 class="form-signin-heading">{{lang_createUser}}</h2>
+ <div class="row">
+ <div class="col-md-4">{{lang_username}} *</div>
+ <div class="col-md-4"><input type="text" name="user" value="{{user}}" class="form-control" placeholder="{{lang_username}}" autofocus></div>
+ </div>
+ <div class="row">
+ <div class="col-md-4">{{lang_password}} *</div>
+ <div class="col-md-4"><input type="password" name="pass1" class="form-control" placeholder="{{lang_password}}"></div>
+ <div class="col-md-4"><input type="password" name="pass2" class="form-control" placeholder="{{lang_confirmation}}"></div>
+ </div>
+ <div class="row">
+ <div class="col-md-4">{{lang_fullName}} *</div>
+ <div class="col-md-4"><input type="text" name="fullname" value="{{fullname}}" class="form-control" placeholder="{{lang_fullName}}"></div>
+ </div>
+ <div class="row">
+ <div class="col-md-4">{{lang_telephone}}</div>
+ <div class="col-md-4"><input type="text" name="phone" value="{{phone}}" class="form-control" placeholder="{{lang_telephone}}"></div>
+ </div>
+ <div class="row">
+ <div class="col-md-4">E-Mail</div>
+ <div class="col-md-4"><input type="text" name="email" value="{{email}}" class="form-control" placeholder="E-Mail"></div>
+ </div>
+ <button class="btn btn-lg btn-primary btn-block" type="submit">{{lang_createUser}}</button>
+ <input type="hidden" name="action" value="adduser">
+</form> \ No newline at end of file
diff --git a/modules-available/backup/config.json b/modules-available/backup/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/backup/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/backup/lang/de/module.json b/modules-available/backup/lang/de/module.json
new file mode 100644
index 00000000..a0dbdf27
--- /dev/null
+++ b/modules-available/backup/lang/de/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "Sichern/Wiederherstellen",
+ "page_title": "Sichern und wiederherstellen"
+}
diff --git a/modules-available/backup/lang/de/templates/_page.json b/modules-available/backup/lang/de/templates/_page.json
new file mode 100644
index 00000000..5e3efea2
--- /dev/null
+++ b/modules-available/backup/lang/de/templates/_page.json
@@ -0,0 +1,14 @@
+{
+ "lang_backup": "Sichern",
+ "lang_backupDescription": "Hier k\u00f6nnen Sie die Konfiguration des Satellitenservers sichern. Dies beinhaltet die Datenbank \u00fcber alle Virtuellen Maschinen, Veranstaltungen, Authentifizerungsmodule, Passw\u00f6rter, Proxies, den konfigurierten VM-Store sowie weitere Konfiguration des MiniLinux.\r\nDie Festplattenabbilder der Virtuellen Maschinen auf dem VM-Store werden hierbei nicht gesichert. Eventuelle Backups des Stores m\u00fcssen separat durchgef\u00fchrt werden.",
+ "lang_backupRestore": "Sichern und Wiederherstellen",
+ "lang_browseForFile": "Durchsuchen",
+ "lang_download": "Herunterladen",
+ "lang_dozmodExplanation": "Die Datenbank des Dozentenmoduls wiederherstellen. Dazu geh\u00f6ren die Metadaten der Virtuellen Maschinen, die Veranstaltungen, etc. Bitte beachten Sie, dass hierzu auf dem konfigurierten VM-Store die passenden VM-Abbilder vorliegen m\u00fcssen, da diese extern gespeichert werden. Wenn sich der Servername oder die -adresse ge\u00e4ndert haben stellen Sie bitte sicher, dass die relativen Pfade innerhalb des Netzlaufwerks gleich geblieben sind. Ansonsten werden die wiederhergestellten VMs nicht verwendbar sein.",
+ "lang_restore": "Hochladen",
+ "lang_restoreDescription": "Hier k\u00f6nnen Sie ein Backup der Konfiguration wieder einspielen. Bitte beachten Sie, dass der Server dabei neu gestartet wird, daher sollten Sie dies m\u00f6glichst durchf\u00fchren, wenn das System nicht genutzt wird, und keine Dozenten Veranstaltungen oder Virtuelle Labore erstellen oder hoch-\/herunterladen. Bitte beachten Sie, dass dabei auch das urspr\u00fcngliche Passwort der Weboberfl\u00e4che wiederhergestellt wird.",
+ "lang_restoreDozmodConfig": "Dozentenmodul-Konfiguration wiederherstellen",
+ "lang_restoreSystemConfig": "Systemkonfiguration wiederherstellen",
+ "lang_selectFile": "Bitte w\u00e4hlen Sie ein Backup-Archiv",
+ "lang_systemExplanation": "Die Grundkonfiguration des Satelliten wiederherstellen: Authentifizierungmethode, Passw\u00f6rter, Proxies, VM-Storage, etc.\r\nACHTUNG: Wenn Sie ein Backup von vor WS15\/16 einspielen (Backup-Format vor Version 10), wird die Systemkonfiguration in jedem Fall wiederhergestellt, auch wenn Sie diesen Haken nicht setzen."
+} \ No newline at end of file
diff --git a/modules-available/backup/lang/de/templates/restore.json b/modules-available/backup/lang/de/templates/restore.json
new file mode 100644
index 00000000..6b3a7cdd
--- /dev/null
+++ b/modules-available/backup/lang/de/templates/restore.json
@@ -0,0 +1,8 @@
+{
+ "lang_backup": "Sichern...",
+ "lang_reboot": "Systemneustart",
+ "lang_restoreConfig": "Konfiguration wiederherstellen",
+ "lang_restoreFailed": "Wiederherstellung der Konfiguration fehlgeschlagen.",
+ "lang_stopping": "Stoppe",
+ "lang_waitReboot": "Warte auf Reboot."
+} \ No newline at end of file
diff --git a/modules-available/backup/lang/en/module.json b/modules-available/backup/lang/en/module.json
new file mode 100644
index 00000000..c9379ffd
--- /dev/null
+++ b/modules-available/backup/lang/en/module.json
@@ -0,0 +1,14 @@
+{
+ "lang_backup": "Backup",
+ "lang_backupDescription": "Here you can backup the complete configuration of this satellite server.",
+ "lang_backupRestore": "Backup and restore",
+ "lang_download": "Download",
+ "lang_file": "File",
+ "lang_reboot": "System reboot",
+ "lang_restore": "Upload",
+ "lang_restoreConfig": "Restore config",
+ "lang_restoreDescription": "Here you can restore a configuration backup. Please note that this will reboot the server, so it is advised to do this while nobody is using the system. Please note that this will also restore the password for the web interface that was active when the configuration backup was created.",
+ "lang_restoreFailed": "Restoring configuration failed.",
+ "lang_stopping": "Stopping",
+ "module_name": "Backup"
+} \ No newline at end of file
diff --git a/modules-available/backup/lang/en/templates/_page.json b/modules-available/backup/lang/en/templates/_page.json
new file mode 100644
index 00000000..799c6168
--- /dev/null
+++ b/modules-available/backup/lang/en/templates/_page.json
@@ -0,0 +1,14 @@
+{
+ "lang_backup": "Backup",
+ "lang_backupDescription": "Here you can backup the complete configuration of this satellite server. This includes lecture and virtual machine meta data. The HDD images of the virtual machines on the vm store are not included in this backup, because of their size. If desired, the store needs to be backed up manually.",
+ "lang_backupRestore": "Backup and restore",
+ "lang_browseForFile": "Browse",
+ "lang_download": "Download",
+ "lang_dozmodExplanation": "This restores all the virtual machine and lecture meta data created using the \"Dozentenmodul\". Please make sure the VM-storage configured still contains all the VM-Images associated with the virtual machines. If the location of the storage changed, make sure the relative pathes on the share are still the same, otherwise the virtual machines won't be usable.",
+ "lang_restore": "Upload",
+ "lang_restoreDescription": "Here you can restore a configuration backup. Please note that this will reboot the server, so it is advised to do this while nobody is using the system. Please note that this will also restore the password for the web interface that was active when the configuration backup was created.",
+ "lang_restoreDozmodConfig": "Restore Dozentenmodul config",
+ "lang_restoreSystemConfig": "Restore system config",
+ "lang_selectFile": "Please select a backup archive",
+ "lang_systemExplanation": "Restore basic configuration like authentication method, passwords, vm storage location, proxy config, etc. WARNING: If you restore a configuration backup that was made before WS15\/16 (backup format version <10), the system configuration will be restored regardless of this check mark."
+} \ No newline at end of file
diff --git a/modules-available/backup/lang/en/templates/restore.json b/modules-available/backup/lang/en/templates/restore.json
new file mode 100644
index 00000000..5a5f6f64
--- /dev/null
+++ b/modules-available/backup/lang/en/templates/restore.json
@@ -0,0 +1,8 @@
+{
+ "lang_backup": "Backup...",
+ "lang_reboot": "System reboot",
+ "lang_restoreConfig": "Restore config",
+ "lang_restoreFailed": "Restoring configuration failed.",
+ "lang_stopping": "Stopping",
+ "lang_waitReboot": "Waiting for reboot."
+} \ No newline at end of file
diff --git a/modules-available/backup/lang/pt/module.json b/modules-available/backup/lang/pt/module.json
new file mode 100644
index 00000000..5077cbfa
--- /dev/null
+++ b/modules-available/backup/lang/pt/module.json
@@ -0,0 +1,14 @@
+{
+ "lang_backup": "Backup",
+ "lang_backupDescription": "Aqui voc\u00ea pode fazer um backup completo da configura\u00e7\u00e3o deste server.",
+ "lang_backupRestore": "Backup e Recupera\u00e7\u00e3o",
+ "lang_download": "Baixar",
+ "lang_file": "Arquivo",
+ "lang_reboot": "Reinicializar Sistema",
+ "lang_restore": "Carregar",
+ "lang_restoreConfig": "Recuperar Configura\u00e7\u00e3o",
+ "lang_restoreDescription": "Aqui voc\u00ea pode recuperar um backup de configura\u00e7\u00e3o. Por favor note que isso ir\u00e1 reinicializar o servidor, portanto \u00e9 recomendado faz\u00ea-lo quando ningu\u00e9m estiver utilizando o sistema. Por favor note que isso tamb\u00e9m ir\u00e1 recuperar a senha da interface web que estava ativa quando a configura\u00e7\u00e3o de backup foi criada.",
+ "lang_restoreFailed": "Recupera\u00e7\u00e3o da configura\u00e7\u00e3o falhou",
+ "lang_stopping": "Parando",
+ "module_name": "Backup"
+} \ No newline at end of file
diff --git a/modules-available/backup/page.inc.php b/modules-available/backup/page.inc.php
new file mode 100644
index 00000000..4095f875
--- /dev/null
+++ b/modules-available/backup/page.inc.php
@@ -0,0 +1,164 @@
+<?php
+
+class Page_Backup extends Page
+{
+
+ private $action = false;
+ private $templateData = array();
+
+ protected function doPreprocess()
+ {
+ User::load();
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ $this->action = Request::post('action');
+ if ($this->action === 'backup') {
+ $this->backup();
+ } elseif ($this->action === 'restore') {
+ $this->restore();
+ }
+ }
+
+ protected function doRender()
+ {
+ Render::setTitle(Dictionary::translate('lang_titleBackup'));
+ if ($this->action === 'restore') {
+ Render::addTemplate('restore', $this->templateData);
+ } else {
+ Render::addScriptBottom('fileselect');
+ Render::addTemplate('_page');
+ }
+ }
+
+ private function backup()
+ {
+ $task = Taskmanager::submit('BackupRestore', array('mode' => 'backup'));
+ if (!isset($task['id'])) {
+ Message::addError('backup-failed');
+ Util::redirect('?do=Backup');
+ }
+ $task = Taskmanager::waitComplete($task, 30000);
+ if (!Taskmanager::isFinished($task) || !isset($task['data']['backupFile'])) {
+ Taskmanager::addErrorMessage($task);
+ Util::redirect('?do=Backup');
+ }
+ while ((@ob_get_level()) > 0)
+ @ob_end_clean();
+ $fh = @fopen($task['data']['backupFile'], 'rb');
+ if ($fh === false) {
+ Message::addError('error-read', $task['data']['backupFile']);
+ Util::redirect('?do=Backup');
+ }
+ Header('Content-Type: application/octet-stream', true);
+ Header('Content-Disposition: attachment; filename=' . 'satellite-backup_v' . Database::getExpectedSchemaVersion() . '_' . date('Y.m.d-H.i.s') . '.tgz');
+ Header('Content-Length: ' . @filesize($task['data']['backupFile']));
+ while (!feof($fh)) {
+ $data = fread($fh, 16000);
+ if ($data === false) {
+ EventLog::failure('Could not stream system backup to browser - backup corrupted!');
+ die("\r\n\nDOWNLOAD INTERRUPTED!\n");
+ }
+ echo $data;
+ @ob_flush();
+ @flush();
+ }
+ @fclose($fh);
+ @unlink($task['data']['backupFile']);
+ die();
+ }
+
+ private function restore()
+ {
+ if (!isset($_FILES['backupfile'])) {
+ Message::addError('missing-file');
+ Util::redirect('?do=Backup');
+ }
+ if ($_FILES['backupfile']['error'] != UPLOAD_ERR_OK) {
+ Message::addError('upload-failed', Util::uploadErrorString($_FILES['backupfile']['error']));
+ Util::redirect('?do=Backup');
+ }
+ $tempfile = '/tmp/bwlp-' . mt_rand(1, 100000) . '-' . crc32($_SERVER['REMOTE_ADDR']) . '.tgz';
+ if (!move_uploaded_file($_FILES['backupfile']['tmp_name'], $tempfile)) {
+ Message::addError('error-write', $tempfile);
+ Util::redirect('?do=Backup');
+ }
+ // Got uploaded file, now shut down all the daemons etc.
+ $parent = Trigger::stopDaemons(null, $this->templateData);
+ // Unmount store
+ $task = Taskmanager::submit('MountVmStore', array(
+ 'address' => 'null',
+ 'type' => 'images',
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['mountid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ EventLog::info('Creating backup, v' . Database::getExpectedSchemaVersion() . ' on ' . Property::getServerIp());
+ // Finally run restore
+ $task = Taskmanager::submit('BackupRestore', array(
+ 'mode' => 'restore',
+ 'backupFile' => $tempfile,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false,
+ 'restoreOpenslx' => Request::post('restore_openslx', 'off') === 'on',
+ 'restoreDozmod' => Request::post('restore_dozmod', 'off') === 'on',
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['restoreid'] = $task['id'];
+ $parent = $task['id'];
+ TaskmanagerCallback::addCallback($task, 'dbRestored');
+ }
+ // Wait a bit
+ $task = Taskmanager::submit('SleepTask', array(
+ 'seconds' => 3,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id']))
+ $parent = $task['id'];
+ // Reboot
+ $task = Taskmanager::submit('Reboot', array(
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ // Leave this comment so the i18n scanner finds it:
+ // Message::addSuccess('restore-done');
+ if (isset($task['id']))
+ $this->templateData['rebootid'] = $task['id'];
+ }
+ private function stopDaemons($parent)
+ {
+ $task = Taskmanager::submit('SyncdaemonLauncher', array(
+ 'operation' => 'stop',
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['syncid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ $task = Taskmanager::submit('DozmodLauncher', array(
+ 'operation' => 'stop',
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['dmsdid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ $task = Taskmanager::submit('LdadpLauncher', array(
+ 'ids' => array(),
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['ldadpid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ return $parent;
+ }
+}
diff --git a/modules-available/backup/templates/_page.html b/modules-available/backup/templates/_page.html
new file mode 100644
index 00000000..47b5a174
--- /dev/null
+++ b/modules-available/backup/templates/_page.html
@@ -0,0 +1,41 @@
+<h1>{{lang_backupRestore}}</h1>
+
+<form action="?do=Backup" method="post">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="backup">
+ <div class="panel panel-default">
+ <div class="panel-heading">{{lang_backup}}</div>
+ <div class="panel-body">
+ <p>{{lang_backupDescription}}</p>
+ <button class="btn btn-primary" type="submit">{{lang_download}}</button>
+ </div>
+ </div>
+</form>
+
+<form action="?do=Backup" method="post" enctype="multipart/form-data">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="restore">
+ <div class="panel panel-default">
+ <div class="panel-heading">{{lang_restore}}</div>
+ <div class="panel-body">
+ <p>{{lang_restoreDescription}}</p>
+ <div class="input-group upload-ex">
+ <input type="text" class="form-control" readonly placeholder="{{lang_selectFile}}">
+ <span class="input-group-btn">
+ <span class="btn btn-default btn-file">
+ {{lang_browseForFile}}&hellip; <input type="file" name="backupfile">
+ </span>
+ </span>
+ </div>
+ <div>
+ <label><input type="checkbox" name="restore_openslx" checked="checked"> {{lang_restoreSystemConfig}}</label>
+ <p><i>{{lang_systemExplanation}}</i></p>
+ </div>
+ <div>
+ <label><input type="checkbox" name="restore_dozmod" checked="checked"> {{lang_restoreDozmodConfig}}</label>
+ <p><i>{{lang_dozmodExplanation}}</i></p>
+ </div>
+ <button class="btn btn-primary" type="submit">{{lang_restore}}</button>
+ </div>
+ </div>
+</form> \ No newline at end of file
diff --git a/modules-available/backup/templates/restore.html b/modules-available/backup/templates/restore.html
new file mode 100644
index 00000000..4494a993
--- /dev/null
+++ b/modules-available/backup/templates/restore.html
@@ -0,0 +1,62 @@
+<div class="panel panel-default">
+ <div class="panel-heading">{{lang_backup}}</div>
+ <div class="panel-body">
+ <div id="zeug">
+ <div data-tm-id="{{syncid}}" data-tm-log="messages">{{lang_stopping}} syncdaemon</div>
+ <div data-tm-id="{{dmsdid}}" data-tm-log="messages">{{lang_stopping}} dmsd</div>
+ <div data-tm-id="{{ldadpid}}" data-tm-log="messages">{{lang_stopping}} ldadp</div>
+ <div data-tm-id="{{mountid}}" data-tm-log="messages">{{lang_stopping}} vmstore</div>
+ <div data-tm-id="{{restoreid}}" data-tm-log="messages" data-tm-callback="restoreCb">{{lang_restoreConfig}}</div>
+ <div data-tm-id="{{rebootid}}" data-tm-log="messages">{{lang_reboot}}</div>
+ </div>
+ <div id="restorefailed" class="alert alert-danger" style="display:none">
+ {{lang_restoreFailed}}
+ </div>
+ <div id="waiting" style="display:none">
+ <span id="dots"></span>
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript">
+ function restoreCb(task)
+ {
+ if (!task || !task.statusCode)
+ return;
+ if (task.statusCode === 'TASK_ERROR') {
+ $('#restorefailed').show('slow');
+ }
+ if (task.statusCode === 'TASK_ERROR' || task.statusCode === 'TASK_FINISHED') {
+ startRebootPoll();
+ }
+ }
+
+ function startRebootPoll()
+ {
+ $('#waiting').show();
+ $('#waiting').prepend('<span class="glyphicon glyphicon-refresh slx-rotation"></span>');
+ $('#dots').text('{{lang_waitReboot}}');
+ slxDotInterval = setInterval(function() { $('#dots').text($('#dots').text() + '..'); }, 3000);
+ setTimeout('rebootPoll()', 10000);
+ }
+
+ function rebootPoll()
+ {
+ if (slxDotInterval !== false) {
+ clearInterval(slxDotInterval);
+ slxDotInterval = false;
+ }
+ $('#dots').text($('#dots').text() + '..');
+ slxTimeoutId = setTimeout('rebootPoll()', 3500);
+ $.ajax({url: "index.php?do=Main", timeout: 3000}).success(function(data, textStatus, jqXHR) {
+ if (textStatus !== "success" && textStatus !== "notmodified")
+ return;
+ if (data.indexOf('Status: DB running') === -1)
+ return;
+ clearTimeout(slxTimeoutId);
+ setTimeout(function() {
+ window.location.replace("index.php?do=Main&message[]=success%7Crestore-done");
+ }, 3500);
+ });
+ }
+</script>
diff --git a/modules-available/baseconfig/config.json b/modules-available/baseconfig/config.json
new file mode 100644
index 00000000..b72e9c23
--- /dev/null
+++ b/modules-available/baseconfig/config.json
@@ -0,0 +1,4 @@
+{
+ "category": "main.settings",
+ "enabled": true
+}
diff --git a/modules-available/baseconfig/lang/de/cat_setting.json b/modules-available/baseconfig/lang/de/cat_setting.json
new file mode 100644
index 00000000..b8cb6935
--- /dev/null
+++ b/modules-available/baseconfig/lang/de/cat_setting.json
@@ -0,0 +1,9 @@
+{
+ "cat_1": "Inaktivit\u00e4t und Abschaltung",
+ "cat_2": "Internetzugriff",
+ "cat_3": "Zeitsynchronisation",
+ "cat_4": "Grundsystem",
+ "cat_5": "Gemeinsames Netzlaufwerk",
+ "cat_6": "Unkategorisiert",
+ "cat_7": "vmchooser"
+} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/de/setting.json b/modules-available/baseconfig/lang/de/setting.json
new file mode 100644
index 00000000..af1007be
--- /dev/null
+++ b/modules-available/baseconfig/lang/de/setting.json
@@ -0,0 +1,23 @@
+{
+ "SLX_ADDONS": "Zu ladende Addons. Zur Zeit steht nur *vmware* zur Verf\u00fcgung.",
+ "SLX_BENCHMARK_VM": "Tragen Sie hier den exakten Namen einer Veranstaltung, wie sie im *vmchooser* auftaucht ein, um diese VM nach dem Booten automatisch zu starten. Dies ist n\u00fctzlich f\u00fcr Bootzeitmessungen. Feld leer lassen, um Funktion zu deaktivieren.",
+ "SLX_BIOS_CLOCK": "Legt fest, ob und wie die interne Uhr des Rechners im Bezug auf die Systemzeit des \/MiniLinux\/ gesetzt werden soll.\r\n*off* = Die interne Uhr des Rechners wird nicht ver\u00e4ndert.\r\n*local* = Die interne Uhr wird auf die Lokalzeit gesetzt. Bevorzugt wenn z.B. noch eine native Windows-Installation auf dem PC vorhanden ist.\r\n*utc* = Die interne Uhr wird auf die \/Koordinierte Weltzeit\/ gesetzt. Dies ist die g\u00e4ngige Einstellung in einem reinen Linux-Umfeld.",
+ "SLX_COMMON_SHARE_AUTH": "Authentifizierungsmethode f\u00fcr das gemeinsame Netzlaufwerk. *guest* bedeutet, dass keine Authentifizierung notwendig ist, *user* bedeutet, dass die Credentials des angemeldeten Benutzers verwendet werden.",
+ "SLX_COMMON_SHARE_PATH": "Netzwerkpfad des gemeinsamen Netzlaufwerks. Es werden NFS (keine Authentifizierung) und CIFS\/SMB (mit und ohne Authentifizierung) unterst\u00fctzt.",
+ "SLX_DEMO_PASS": "Passwort f\u00fcr den eingebauten *demo*-Account. Leer lassen, um das Einloggen zu verbieten.\r\nDas Passwort wird wie das root-Passwort nur gehasht an den Client \u00fcbertragen.",
+ "SLX_LOGOUT_TIMEOUT": "Zeit \/in Sekunden\/, die eine Benutzersitzung ohne Aktion sein darf, bevor sie beendet wird.Feld leer lassen, um die Funktion zu deaktivieren.",
+ "SLX_NET_DOMAIN": "DNS-Dom\u00e4ne, in die sich die Clients eingliedern, sofern der DHCP Server keine solche vorgibt.",
+ "SLX_NTP_SERVER": "Adresse des NTP-Zeitservers. Es k\u00f6nnen mehrere Server mit Leerzeichen getrennt angegeben werden.Die Server werden der Reihe nach angefragt, bis ein antwortender Server gefunden wird.",
+ "SLX_PROXY_BLACKLIST": "Adressen bzw. Adressbereiche, f\u00fcr die der Proxyserver nicht verwendet werden soll (z.B. der Adressbereich der Einrichtung). G\u00fcltige Angaben sind einzelne IP-Adressen, sowie IP-Bereiche in CIDR-Notation (z.B. 1.2.0.0\/16). Mehrere Angaben k\u00f6nnen durch Leerzeichen getrennt werden.",
+ "SLX_PROXY_IP": "Die Adresse des zu verwendenden Proxy Servers.",
+ "SLX_PROXY_MODE": "Legt fest, ob zum Zugriff aufs Internet ein Proxy-Server ben\u00f6tigt wird.\r\n*off* = keinen Proxy benutzen.\r\n*on* = Proxy immer benutzen.\r\n*auto* = Proxy nur benutzen, wenn sich der Client-PC in einem privaten Adressbereich befindet.",
+ "SLX_PROXY_PORT": "Der Port des zu verwendenden Proxy Servers.",
+ "SLX_PROXY_TYPE": "Art des Proxys: *socks4*, *socks5*, *http-connect* (HTTP Proxy mit Unterst\u00fctzung der CONNECT-Methode), *http-relay* (Klassischer HTTP Proxy)",
+ "SLX_REMOTE_LOG_SESSIONS": "Legt fest, ob Logins und Logouts der Benutzer an den Satelliten gemeldet werden sollen.\r\n*yes* = Mit Benutzerkennung loggen\r\n*anonymous* = Anonym loggen\r\n*no* = Nicht loggen",
+ "SLX_ROOT_PASS": "Das root-Passwort des Grundsystems. Wird nur f\u00fcr Diagnosezwecke am Client ben\u00f6tigt.\r\nFeld leer lassen, um root-Logins zu verbieten.\r\n\/Hinweis\/: Das Passwort wird im Klartext in der lokalen Datenbank hinterlegt, jedoch immer gehasht an die Clients \u00fcbermittelt (SHA-512 mit Salt). Wenn Sie das Passwort auch im Satelliten nicht im Klartext speichern wollen, k\u00f6nnen Sie hier auch ein vorgehashtes Passwort eintragen (im *$6$....*-Format).",
+ "SLX_SHUTDOWN_SCHEDULE": "Feste Uhrzeit, zu der sich die Rechner ausschalten, auch wenn noch ein Benutzer aktiv ist.Mehrere Zeitpunkte k\u00f6nnen durch Leerzeichen getrennt angegeben werden.",
+ "SLX_SHUTDOWN_TIMEOUT": "Zeit in Sekunden, nach dem ein Rechner abgeschaltet wird, sofern kein Benutzer angemeldet ist.Feld leer lassen, um die Funktion zu deaktivieren.",
+ "SLX_VMCHOOSER_FORLOCATION": "Legt das Verhalten fest, wenn es Veranstaltungen gibt, die an einen bestimmten Ort\/Raum gebunden sind.\r\n*IGNORE*: Mit den restlichen, globalen Veranstaltungen alphabetisch sortiert auflisten.\r\n*BUMP*: Die spezifischen Veranstaltungen oben auflisten, die globalen darunter.\r\n*EXCLUSIVE*: Spezifische Veranstaltungen oben auflisten, globale Veranstaltungen zun\u00e4chst ausblenden. Die globalen Veranstaltungen befinden sich unter einem eingeklappten Listenknoten.",
+ "SLX_VMCHOOSER_TAB": "Bestimmt, welcher Karteireiter im vmchooser standardm\u00e4\u00dfig ausgew\u00e4hlt wird.\r\n*0*: Native Linux-Sessions\r\n*1*: Nutzerspezifische Kurse\r\n*2*: Alle Kurse\r\n*AUTO*: Hat der Rechner beschr\u00e4nkte Ressourcen, werden die Linux-Sitzungen angezeigt, sonst alle Kurse\r\n\r\nHat der Benutzer ein persistentes Home-Verzeichnis, wirkt sich diese Einstellung nur beim ersten Anmelden aus. Bei sp\u00e4teren Sitzungen markiert der vmchooser die zuletzt gestartete Sitzung und wechselt zum entsprechenden Karteireiter.",
+ "SLX_VMCHOOSER_TEMPLATES": "Legt fest, wie Veranstaltungen in der Sortierung behandelt werden, welche auf eine VM linken, die eine Vorlage ist.\r\n*IGNORE*: Wie regul\u00e4re Veranstaltungen behandeln\r\n*BUMP*: Weiter oben in der Liste einsortieren"
+} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/de/templates/_page.json b/modules-available/baseconfig/lang/de/templates/_page.json
new file mode 100644
index 00000000..0f4819b4
--- /dev/null
+++ b/modules-available/baseconfig/lang/de/templates/_page.json
@@ -0,0 +1,15 @@
+{
+ "lang_basicConfiguration": "Basiskonfiguration",
+ "lang_catUser": "Benutzerverwaltung",
+ "lang_clientRelatedConfig": "Die Optionen auf dieser Seite beziehen sich auf das Verhalten der bwLehrpool-Clients.",
+ "lang_close": "Schlie\u00dfen",
+ "lang_create": "Schaffen",
+ "lang_delete": "L\u00f6schen",
+ "lang_help": "Hilfe",
+ "lang_newUser": "Neuer Benutzer",
+ "lang_partitionMountPoint": "Mount Point",
+ "lang_reset": "Zur\u00fccksetzen",
+ "lang_save": "Speichern",
+ "lang_userName": "Benutzername",
+ "lang_userPasswd": "Passwort"
+} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/cat_setting.json b/modules-available/baseconfig/lang/en/cat_setting.json
new file mode 100644
index 00000000..7c0ab654
--- /dev/null
+++ b/modules-available/baseconfig/lang/en/cat_setting.json
@@ -0,0 +1,9 @@
+{
+ "cat_1": "Inactivity and Shutdown",
+ "cat_2": "Internet Access",
+ "cat_3": "Time Synchronization",
+ "cat_4": "Basic System",
+ "cat_5": "Common network share",
+ "cat_6": "Uncategorized",
+ "cat_7": "vmchooser"
+} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/module.json b/modules-available/baseconfig/lang/en/module.json
new file mode 100644
index 00000000..9345c27c
--- /dev/null
+++ b/modules-available/baseconfig/lang/en/module.json
@@ -0,0 +1,27 @@
+{
+ "lang_basicConfiguration": "Basic Configuration",
+ "lang_catPartition": "Partition Managment",
+ "lang_catUser": "User Managment",
+ "lang_close": "Close",
+ "lang_confirm": "Would you like to save the settings on [ \/srv\/openslx\/www\/boot\/config ] ?",
+ "lang_create": "Create",
+ "lang_delete": "Delete",
+ "lang_help": "Help",
+ "lang_helpId": "Partition Id",
+ "lang_helpMountPoint": "Must be a directory: \/example\/directory\/",
+ "lang_helpOptions": "Currently, only option 'bootable' is available",
+ "lang_helpSize": "Must be in Gigabytes e.g. 15G",
+ "lang_newPartition": "New Partition",
+ "lang_newUser": "New User",
+ "lang_partitionId": "Id",
+ "lang_partitionMountPoint": "Mount Point",
+ "lang_partitionOptions": "Options",
+ "lang_partitionSize": "Size",
+ "lang_reset": "Reset",
+ "lang_resetConfirm": "Do you really wish to reset the variable to their default values?",
+ "lang_resetDefault": "Reset Default",
+ "lang_save": "Save",
+ "lang_userName": "Username",
+ "lang_userPasswd": "Password",
+ "module_name": "Variables"
+} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/setting.json b/modules-available/baseconfig/lang/en/setting.json
new file mode 100644
index 00000000..36ac6e8c
--- /dev/null
+++ b/modules-available/baseconfig/lang/en/setting.json
@@ -0,0 +1,23 @@
+{
+ "SLX_ADDONS": "Addons to load. Currently, only *vmware* is available.",
+ "SLX_BENCHMARK_VM": "If non-empty, this should be the exact display name of an entry in *vmchooser*, so it will be automatically started after bootup. Useful for benchmarking.",
+ "SLX_BIOS_CLOCK": "Specifies whether and how the internal clock of the computer should be set in relation to the system time of the \/MiniLinux\/.\r\n*off* = The internal clock of the computer is not changed.\r\n*local* = The internal clock is set to local time. Preferably if, for example, there is still a native Windows installation available on the PC.\r\n*utc* = The internal clock is set to the \/Coordinated Universal Time\/. This is the most common setup in a pure Linux environment.",
+ "SLX_COMMON_SHARE_AUTH": "Athentication method for the common network share. *guest* means no authentication (public share), *user* means the user's credentials will be used.",
+ "SLX_COMMON_SHARE_PATH": "Path of network share. Supported are NFS (no authentication only) and CIFS\/SMB (with and without authentication).",
+ "SLX_DEMO_PASS": "Password for the *demo* account. Leave empty to disallow logging in as the demo user.\r\nLike the root password, the demo user's password will be sent to the client in its hashed form.",
+ "SLX_LOGOUT_TIMEOUT": "Time \/in seconds\/, in which a user session may remain without action before it is terminated.Leave field blank to disable the function.",
+ "SLX_NET_DOMAIN": "DNS domain in which the client integrate, provided the DHCP server does not specifies such.",
+ "SLX_NTP_SERVER": "Address of the NTP time server. Multiple servers can be specified separated by spaces.The servers are queried in sequence until a responding server is found.",
+ "SLX_PROXY_BLACKLIST": "Address or addresses ranges in which the proxy server is not used (for example the address range of the device). Valid entries are individual IP addresses and IP ranges in CIDR notation (for example 1.2.0.0\/16). Multiple selections can be separated by spaces.",
+ "SLX_PROXY_IP": "The address to use for the proxy server.",
+ "SLX_PROXY_MODE": "Determines whether a proxy server is required to access the Internet.\r\n*off* = do not use a Proxy.\r\n*on* = Always use proxy.\r\n*auto* = Only use proxy when the client PC is in a private address space.",
+ "SLX_PROXY_PORT": "The port to use for the proxy server.",
+ "SLX_PROXY_TYPE": "Type of the proxy.*socks4*, *socks5*, *http-connect* (HTTP proxy with support from the CONNECT method), *http-relay* (Classic HTTP proxy)",
+ "SLX_REMOTE_LOG_SESSIONS": "Determines whether logins and logouts of the users should be reported to the satellite.\r\n*yes* = log with user ID\r\n*anonymous* = anonymous logging\r\n*no* = no logging",
+ "SLX_ROOT_PASS": "The root password of the client system. Only required for diagnostic purposes on the client.Leave field blank to disallow root logins.\r\n\/Hint\/: The password SHA-512-with-salt hashed before it's being sent to the client. It's only stored in clear text on the Satellite Server. If you want to have it hashed on the server too, you can supply a pre-hashed passoword in \/$6$...$...\/-format.",
+ "SLX_SHUTDOWN_SCHEDULE": "Fixed time to turn off the computer, even if there is a user active.Several times can be specified, separated by spaces.",
+ "SLX_SHUTDOWN_TIMEOUT": "Time in seconds after which a computer is switched off, if no user is logged on.Leave blank to disable the function.",
+ "SLX_VMCHOOSER_FORLOCATION": "Defines how lectures special to the user's location are handled in the vmchooser.\r\n*IGNORE*: Sort them alphabetically among the global lectures.\r\n*BUMP*: Put them atop the global lectures.\r\n*EXCLUSIVE*: Put them atop the global lectures and aditionally collapse the node which contains the global lectures.",
+ "SLX_VMCHOOSER_TAB": "Defines which tab is show by default, if the user doesn't have stored a last used session on his persistent home directory.\r\n*0*: Native Linux sessions\r\n*1*: User specific lectures\r\n*2*: All lectures\r\n*AUTO*: If the computer has low system specs, show the Linux sessions, otherwise, show all lectures",
+ "SLX_VMCHOOSER_TEMPLATES": "Defines how lectures that link to template VMs are treated wrt sorting.\r\n*IGNORE*: Sort among regular lectures\r\n*BUMP*: Move to top of list"
+} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/templates/_page.json b/modules-available/baseconfig/lang/en/templates/_page.json
new file mode 100644
index 00000000..6429a835
--- /dev/null
+++ b/modules-available/baseconfig/lang/en/templates/_page.json
@@ -0,0 +1,7 @@
+{
+ "lang_basicConfiguration": "Basic Configuration",
+ "lang_clientRelatedConfig": "The options on this page are related to the bwLehrpool client machines.",
+ "lang_close": "Close",
+ "lang_reset": "Reset",
+ "lang_save": "Save"
+} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/pt/module.json b/modules-available/baseconfig/lang/pt/module.json
new file mode 100644
index 00000000..0ac0a075
--- /dev/null
+++ b/modules-available/baseconfig/lang/pt/module.json
@@ -0,0 +1,27 @@
+{
+ "lang_basicConfiguration": "Configura\u00e7\u00e3o B\u00e1sica",
+ "lang_catPartition": "Gerenciamento de Parti\u00e7\u00f5es",
+ "lang_catUser": "Gerenciamente de Usu\u00e1rios",
+ "lang_close": "Fechar",
+ "lang_confirm": "Voc\u00ea deseja salvar configura\u00e7\u00f5es em [ \/srv\/openslx\/www\/boot\/default\/config ] ?",
+ "lang_create": "Criar",
+ "lang_delete": "Deletar",
+ "lang_help": "Ajuda",
+ "lang_helpId": "Id da parti\u00e7\u00e3o",
+ "lang_helpMountPoint": "Precisa ser um diret\u00f3rio: \/exemplo\/diret\u00f3rio\/",
+ "lang_helpOptions": "Atualmente, apenas a op\u00e7\u00e3o 'bootable' est\u00e1 dispon\u00edvel",
+ "lang_helpSize": "Precisa estar em Gigabytes, por exemplo 15G",
+ "lang_newPartition": "Nova Parti\u00e7\u00e3o",
+ "lang_newUser": "Novo Usu\u00e1rio",
+ "lang_partitionId": "Id",
+ "lang_partitionMountPoint": "Mount Point",
+ "lang_partitionOptions": "Op\u00e7\u00f5es",
+ "lang_partitionSize": "Tamanho",
+ "lang_reset": "Limpar",
+ "lang_resetConfirm": "Voc\u00ea realmente deseja restaurar as vari\u00e1veis para seus valores padr\u00f5es?",
+ "lang_resetDefault": "Restaurar Padr\u00e3o",
+ "lang_save": "Salvar",
+ "lang_userName": "Nome",
+ "lang_userPasswd": "Senha",
+ "module_name": "Vari\u00e1veis"
+} \ No newline at end of file
diff --git a/modules-available/baseconfig/page.inc.php b/modules-available/baseconfig/page.inc.php
new file mode 100644
index 00000000..8f914376
--- /dev/null
+++ b/modules-available/baseconfig/page.inc.php
@@ -0,0 +1,129 @@
+<?php
+
+class Page_BaseConfig extends Page
+{
+ private $qry_extra = array();
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ // Determine if we're setting global, distro or location
+ if (isset($_REQUEST['distroid'])) {
+ // TODO: Everything
+ $this->qry_extra[] = array(
+ 'name' => 'distroid',
+ 'value' => (int)$_REQUEST['distroid'],
+ 'table' => 'setting_distro',
+ );
+ if (isset($_REQUEST['locationid'])) {
+ $this->qry_extra[] = array(
+ 'name' => 'locationid',
+ 'value' => (int)$_REQUEST['locationid'],
+ 'table' => 'setting_location',
+ );
+ }
+ }
+
+ if (isset($_POST['setting']) && is_array($_POST['setting'])) {
+ if (User::hasPermission('superadmin')) {
+ // Build variables for specific sub-settings
+ $qry_insert = '';
+ $qry_values = '';
+ foreach ($this->qry_extra as $item) {
+ $qry_insert = ', ' . $item['name'];
+ $qry_values = ', :' . $item['name'];
+ }
+ // Load all existing config options to validate input
+ $res = Database::simpleQuery('SELECT setting, validator FROM setting');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $key = $row['setting'];
+ $validator = $row['validator'];
+ $displayValue = (isset($_POST['setting'][$key]) ? $_POST['setting'][$key] : '');
+ // Validate data first!
+ $mangledValue = Validator::validate($validator, $displayValue);
+ if ($mangledValue === false) {
+ Message::addWarning('value-invalid', $key, $displayValue);
+ continue;
+ }
+ // Now put into DB
+ Database::exec("INSERT INTO setting_global (setting, value, displayvalue $qry_insert)
+ VALUES (:key, :value, :displayvalue $qry_values)
+ ON DUPLICATE KEY UPDATE value = :value, displayvalue = :displayvalue",
+ $this->qry_extra + array(
+ 'key' => $key,
+ 'value' => $mangledValue,
+ 'displayvalue' => $displayValue
+ )
+ );
+ }
+ Message::addSuccess('settings-updated');
+ Util::redirect('?do=BaseConfig');
+ }
+ }
+ }
+
+ protected function doRender()
+ {
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ // Build left joins for specific settings
+ $joins = '';
+ foreach ($this->qry_extra as $item) {
+ $joins .= " LEFT JOIN {$item['table']} ";
+ }
+ // List global config option
+ $settings = array();
+ $res = Database::simpleQuery('SELECT cat_setting.catid, setting.setting, setting.defaultvalue, setting.permissions, setting.validator, tbl.displayvalue
+ FROM setting
+ INNER JOIN cat_setting USING (catid)
+ LEFT JOIN setting_global AS tbl USING (setting)
+ ORDER BY cat_setting.sortval ASC, setting.setting ASC');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['description'] = Util::markup(Page::translate($row['setting'], 'setting'));
+ if (is_null($row['displayvalue'])) $row['displayvalue'] = $row['defaultvalue'];
+ $row['item'] = $this->makeInput($row['validator'], $row['setting'], $row['displayvalue']);
+ $settings[$row['catid']]['settings'][] = $row;
+ if (!isset($settings[$row['catid']]['category_id'])) {
+ $settings[$row['catid']]['category_name'] = Page::translate('cat_' . $row['catid'], 'cat_setting');
+ $settings[$row['catid']]['category_id'] = $row['catid'];
+ }
+ }
+ Render::addTemplate('_page', array(
+ 'categories' => array_values($settings)
+ ));
+ }
+
+ /**
+ * Create html snippet for setting, based on given validator
+ * @param type $validator
+ * @return boolean
+ */
+ private function makeInput($validator, $setting, $current)
+ {
+ $parts = explode(':', $validator, 2);
+ if ($parts[0] === 'list') {
+ $items = explode('|', $parts[1]);
+ $ret = '<select name="setting[' . $setting . ']" class="form-control">';
+ foreach ($items as $item) {
+ if ($item === $current) {
+ $ret .= '<option selected="selected">' . $item . '</option>';
+ } else {
+ $ret .= '<option>' . $item . '</option>';
+ }
+ }
+ return $ret . '</select>';
+ }
+ // Password field guessing
+ if (stripos($validator, 'password') !== false) {
+ $type = Property::getPasswordFieldType();
+ } else {
+ $type = 'text';
+ }
+ // Fallback: single line input
+ return '<input type="' . $type . '" name="setting[' . $setting . ']" class="form-control" size="30" value="' . $current . '">';
+ }
+
+}
diff --git a/modules-available/baseconfig/templates/_page.html b/modules-available/baseconfig/templates/_page.html
new file mode 100644
index 00000000..273ee50c
--- /dev/null
+++ b/modules-available/baseconfig/templates/_page.html
@@ -0,0 +1,188 @@
+<h1>{{lang_basicConfiguration}}</h1>
+<p>{{lang_clientRelatedConfig}}</p>
+<form action="?do=BaseConfig" method="post">
+ <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
+ <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;">
+ <input type="password" name="password_fake" id="password_fake" value="" style="display:none;">
+ {{#categories}}
+ <div class="panel panel-default">
+ <div class="panel-heading" role="tab" id="heading{{category_id}}">
+ <a data-toggle="collapse" data-parent="#accordion" href="#collapse{{category_id}}" aria-expanded="false" aria-controls="collapse{{category_id}}">
+ {{category_name}}
+ </a>
+ </div>
+ <div id="collapse{{category_id}}" class="accordion-body collapse" role="tabpanel" aria-labelledby="heading{{category_id}}">
+ <div class="panel-body">
+ <div class="list-group">
+ {{#settings}}
+ <div class="list-group-item {{class}}">
+ <div class="row">
+ <div class="col-md-1" style="width:118px;">
+ <input type="checkbox" name="switch[{{setting}}]" {{checked}}>
+ </div>
+ <div class="col-md-4">
+ {{setting}}
+ <div class="slx-default">{{defaultvalue}}</div>
+ </div>
+ <div class="col-md-4">
+ {{{item}}}
+ </div>
+ <div class="col-md-2">
+ <a class="btn btn-default" data-toggle="modal" data-target="#help-{{setting}}"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </div>
+ </div>
+ </div>
+ <div class="modal fade" id="help-{{setting}}" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{setting}}</div>
+ <div class="modal-body">{{{description}}}</div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+ </div>
+ {{/settings}}
+ </div>
+ <div id="cat-extra-{{category_id}}">
+
+ </div>
+ </div>
+ </div>
+ </div>
+ {{/categories}}
+
+
+
+ <!-- User Managment Section -->
+ <div class="panel panel-default">
+ <div class="panel-heading" role="tab" id="headingUsers">
+ <a data-toggle="collapse" data-parent="#accordion" href="#collapseUsers" aria-expanded="false" aria-controls="collapseUsers">
+ {{lang_catUser}}
+ </a>
+ <span style="display:inline-block; float: right; margin-top: -7px;">
+ <a class="btn btn-default " data-toggle="modal" data-target="#add-user">
+ <span class="glyphicon glyphicon-plus"></span>
+ </a>
+ </span>
+ </div>
+ <div id="collapseUsers" class="accordion-body collapse" role="tabpanel" aria-labelledby="headingUsers">
+ <div class="panel-body">
+ <div class="list-group">
+ {{#users}}
+ <div class="list-group-item">
+ <div class="row">
+ <div class="col-md-5">
+ <input name="user-{{id}}-name" type="text" class="form-control" size="30" value="{{name}}" />
+ </div>
+ <div class="col-md-5">
+ <input name="user-{{id}}-password" type="text" class="form-control" size="30" placeholder="{{lang_userPasswd}}" />
+ </div>
+ <div class="col-md-2">
+ <a class="btn btn-danger" href="?do=BaseConfig&deleteUser={{id}}&token={{token}}" ><span class="glyphicon glyphicon-trash"></span> {{lang_delete}}</a>
+ </div>
+ </div>
+ </div>
+ {{/users}}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <input type="hidden" name="token" value="{{token}}">
+ <button class="btn btn-lg btn-primary" type="submit">{{lang_save}}</button>
+ <button class="btn btn-lg btn-primary" type="reset">{{lang_reset}}</button>
+ <a class="btn btn-lg btn-primary" href="api.php?do=getconfig">Download</a>
+</form>
+<p>
+ <form method="post" action="?do=BaseConfig">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="reset">
+ <button class="btn btn-default" type="submit" onclick="return confirm('{{lang_resetConfirm}}');">{{lang_resetDefault}}</button>
+ </form>
+</p>
+<!-- Create User Window -->
+<form action="?do=BaseConfig" method="post">
+ <div class="modal fade" id="add-user" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+
+ <div class="modal-content">
+ <div class="modal-header">{{lang_newUser}}</div>
+ <div class="modal-body">
+
+ <p>
+ <input name="new-user-name" placeholder="{{lang_userName}}" class="form-control" type="text">
+ </p>
+ <p>
+ <input name="new-user-passwd" placeholder="{{lang_userPasswd}}" class="form-control" type="password">
+ </p>
+ <input type="submit" class="btn btn-primary" value="{{lang_create}}">
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+ </div>
+ <input type="hidden" name="action" value="new_user">
+ <input type="hidden" name="token" value="{{token}}">
+</form>
+<!-- Create Partition Window -->
+<form action="?do=BaseConfig" method="post">
+ <div class="modal fade" id="add-partition" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+
+ <div class="modal-content">
+ <div class="modal-header">{{lang_newPartition}}</div>
+ <div class="modal-body">
+
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_partitionId}}</span>
+ <input name="new-partition-id" class="form-control" type="text">
+ </div>
+ <p class="help-block">{{lang_helpId}}</p>
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_partitionSize}}</span>
+ <input name="new-partition-size" class="form-control" type="text">
+ </div>
+ <p class="help-block">{{lang_helpSize}}</p>
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_partitionMountPoint}}</span>
+ <input name="new-partition-mount-point" class="form-control" type="text">
+ </div>
+ <p class="help-block">{{lang_helpMountPoint}}</p>
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_partitionOptions}}</span>
+ <input name="new-partition-options" class="form-control" type="text">
+ </div>
+ <p class="help-block">{{lang_helpOptions}}</p>
+ <input type="submit" class="btn btn-primary" value="{{lang_create}}">
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+ </div>
+ <input type="hidden" name="action" value="new_partition">
+ <input type="hidden" name="token" value="{{token}}">
+</form>
+<script type="text/javascript">
+
+function saveConfig(){
+ if(confirm('{{lang_confirm}}'))
+ window.location = 'api.php?do=getconfig&user={{user}}&save=true';
+ else
+ window.location = 'api.php?do=getconfig&user={{user}}';
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+ document.getElementById("cat-extra-6").innerHTML = "<div class='list-group'> <div class='list-group-item' style='background-color:#f5f5f5;color:#428bca;'> " +
+ "{{lang_catPartition}} <span style='display:inline-block; float: right; margin-top: -7px;'> <a class='btn btn-default ' data-toggle='modal' " +
+ "data-target='#add-partition'> <span class='glyphicon glyphicon-plus'></span> </a> </span> </div> {{#partitions}} <div class='list-group-item'> " +
+ "<div class='row'> <div class='col-md-1'> <input name='partition-{{id}}-partition_id' type='text' class='form-control' size='30' value='{{partition_id}}'" +
+ " placeholder='{{lang_partitionId}}' /> </div> <div class='col-md-1'> <input name='partition-{{id}}-size' type='text' class='form-control' size='30' " +
+ "value='{{size}}' placeholder='{{lang_partitionSize}}'/> </div> <div class='col-md-4'> <input name='partition-{{id}}-mount_point' type='text' " +
+ "class='form-control' size='30' value='{{mount_point}}' placeholder='{{lang_partitionMountPoint}}'/> </div> <div class='col-md-4'> <input " +
+ "name='partition-{{id}}-options' type='text' class='form-control' size='30' value='{{options}}' placeholder='{{lang_partitionOptions}}'/> </div> " +
+ "<div class='col-md-2'> <a class='btn btn-danger' href='?do=BaseConfig&deletePartition={{id}}&token={{token}}' ><span class='glyphicon glyphicon-trash'>" +
+ "</span> {{lang_delete}}</a> </div> </div> </div> {{/partitions}} </div> </div>";
+ $("[name^='switch']").bootstrapSwitch();
+}
+
+</script>
diff --git a/modules-available/citymanagement/config.json b/modules-available/citymanagement/config.json
new file mode 100644
index 00000000..aff54b3a
--- /dev/null
+++ b/modules-available/citymanagement/config.json
@@ -0,0 +1,4 @@
+{
+ "enabled":"false",
+ "permission":"0"
+}
diff --git a/modules-available/citymanagement/lang/en/module.json b/modules-available/citymanagement/lang/en/module.json
new file mode 100644
index 00000000..ba6c5c44
--- /dev/null
+++ b/modules-available/citymanagement/lang/en/module.json
@@ -0,0 +1,14 @@
+{
+ "lang_cancelConfirm": "Do you really want to remove this city?",
+ "lang_cityInfo": "Here you can create new cities for the website, besides editing or removing the existing ones",
+ "lang_cityPage": "Manage cities",
+ "lang_cityname": "City name",
+ "lang_close": "Close",
+ "lang_create": "Create",
+ "lang_edit": "Edit",
+ "lang_editcity": "Edit City",
+ "lang_operations": "Operations",
+ "lang_remove": "Remove",
+ "lang_save": "Save",
+ "module_name": "Management"
+} \ No newline at end of file
diff --git a/modules-available/citymanagement/lang/en/templates/citymanagement.json b/modules-available/citymanagement/lang/en/templates/citymanagement.json
new file mode 100644
index 00000000..27bb60c0
--- /dev/null
+++ b/modules-available/citymanagement/lang/en/templates/citymanagement.json
@@ -0,0 +1,13 @@
+{
+ "lang_cancelConfirm": "Do you really want to remove this city?",
+ "lang_cityInfo":"Here you can create new cities for the website, besides editing or removing the existing ones",
+ "lang_cityPage":"Manage cities",
+ "lang_cityname":"City name",
+ "lang_close": "Close",
+ "lang_create": "Create",
+ "lang_edit":"Edit",
+ "lang_editcity":"Edit City",
+ "lang_operations": "Operations",
+ "lang_remove": "Remove",
+ "lang_save": "Save"
+}
diff --git a/modules-available/citymanagement/lang/pt/module.json b/modules-available/citymanagement/lang/pt/module.json
new file mode 100644
index 00000000..c514f437
--- /dev/null
+++ b/modules-available/citymanagement/lang/pt/module.json
@@ -0,0 +1,14 @@
+{
+ "lang_cancelConfirm": "Deseja realmente remover a cidade?",
+ "lang_cityInfo": "Nesta se\u00e7\u00e3o voc\u00ea poder\u00e1 criar cidades para o site, al\u00e9m de editar ou remover cidades existentes",
+ "lang_cityPage": "Gerenciar cidades",
+ "lang_cityname": "Nome da cidade",
+ "lang_close": "Fechar",
+ "lang_create": "Criar",
+ "lang_edit": "Editar",
+ "lang_editcity": "Editar Cidade",
+ "lang_operations": "Opera\u00e7\u00f5es",
+ "lang_remove": "Remover",
+ "lang_save": "Salvar",
+ "module_name": "Gerenciamento"
+} \ No newline at end of file
diff --git a/modules-available/citymanagement/page.inc.php b/modules-available/citymanagement/page.inc.php
new file mode 100644
index 00000000..acc30bf9
--- /dev/null
+++ b/modules-available/citymanagement/page.inc.php
@@ -0,0 +1,81 @@
+<?php
+
+class Page_Citymanagement extends Page
+{
+
+ private $page;
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ $p = Request::get('page');
+ if($p != false)
+ $this->page = $p;
+ else
+ $this->page = 1;
+ switch(Request::post('action')){
+ case "edit":
+ $this->edit(Request::post('cityid'),Request::post('name'));
+ break;
+ case "create":
+ $this->create(Request::post('name'));
+ break;
+ case "delete":
+ $this->delete(Request::post('cityid'));
+ break;
+ }
+
+
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ }
+
+ protected function doRender()
+ {
+ // load every city
+ $cities = array();
+ $res = Database::simpleQuery("SELECT cityid, name FROM cities ORDER BY cityid DESC");
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $cities[] = array(
+ 'id' => $row['cityid'],
+ 'name' => $row['name'],
+ );
+ }
+
+ $pag = new Pagination($cities,$this->page);
+
+ Render::addTemplate('page-citymanagement', array(
+ 'cities' => $pag->getItems(),
+ 'pages' => $pag->getPagination()
+ ));
+ }
+
+ private function edit($cityid, $newname){
+ $data = array (
+ 'cityid' => $cityid,
+ 'name' => $newname,
+ );
+ Database::exec ( 'UPDATE cities SET name = :name WHERE cityid = :cityid', $data );
+ Message::addSuccess('update-city');
+ }
+
+ private function create($name){
+ $data = array (
+ 'name' => $name,
+ );
+ Database::exec('INSERT INTO cities(name) VALUES( :name )',$data);
+ Message::addSuccess('add-city');
+ }
+
+ private function delete($cityid){
+ $data = array (
+ 'cityid' => $cityid
+ );
+ Database::exec ( 'DELETE FROM cities WHERE cityid = :cityid', $data );
+ Message::addSuccess('delete-city');
+ }
+}
diff --git a/modules-available/citymanagement/templates/page-citymanagement.html b/modules-available/citymanagement/templates/page-citymanagement.html
new file mode 100644
index 00000000..6d7750cf
--- /dev/null
+++ b/modules-available/citymanagement/templates/page-citymanagement.html
@@ -0,0 +1,77 @@
+<div class="panel panel-default" style="border-color:#333;">
+ <div class="panel-heading" style="border-color:#333;background-color:#333;background-image: none;color:#FFF;">
+ <div class="panel-title">{{lang_cityPage}}</div>
+ </div>
+ <div class="panel-body">
+ <p>
+ {{lang_cityInfo}}
+ </p>
+
+ </div>
+ <table class="table">
+ <tr>
+ <form method="post" action="?do=Citymanagement">
+ <input type="hidden" name="action" value="create">
+ <input type="hidden" name="token" value="{{token}}">
+ <td><input class="form-control" name="name" type="text" placeholder="{{lang_cityname}}"></td>
+ <td><input class="btn btn-primary" type="submit" value="{{lang_create}}"></td>
+ </form>
+ </tr>
+ <tr>
+ <th style="text-align: center;">ID</th>
+ <th style="text-align: center;">{{lang_cityname}}</th>
+ <th colspan="4">{{lang_operations}}</th>
+ </tr>
+ {{#cities}}
+ <tr>
+ <td><input class="form-control" type="text" disabled="disabled" value="{{id}}" size="3"></td>
+ <td><input class="form-control" type="text" disabled="disabled" value="{{name}}"></td>
+ <td colspan="4">
+ <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#city{{id}}"><span class="glyphicon glyphicon-edit" aria-hidden="true"></span> {{lang_edit}}</button>
+ <form method="post" action="?do=Citymanagement" style="display:inline-block;">
+ <input type="hidden" name="action" value="delete">
+ <input type="hidden" name="cityid" value="{{id}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <button class="btn btn-danger" type="submit" onclick="return confirm('{{lang_cancelConfirm}}');"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> {{lang_remove}}</a>
+ </form>
+ </td>
+ </tr>
+ {{/cities}}
+ <tr>
+ <td colspan="6">
+ <ul class="pagination" style="margin:10px 0;">
+ {{#pages}}
+ <li class="{{class}}"><a href="?do=Citymanagement&page={{page}}">{{page}}</a></li>
+ {{/pages}}
+ </ul>
+ </td>
+ </tr>
+ </table>
+</div>
+{{#cities}}
+<div class="modal fade" id="city{{id}}" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header"><h4 class="modal-title">{{lang_editcity}}</h4></div>
+ <div class="modal-body">
+ <form method="post" action="">
+ <input type="hidden" name="action" value="edit">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="form-group">
+ <label for="cityid">ID</label>
+ <input type="text" class="form-control" name="cityid" readonly="readonly" value="{{id}}">
+ </div>
+ <div class="form-group">
+ <label for="cityid">{{lang_cityname}}</label>
+ <input type="text" class="form-control" name="name" placeholder="{{lang_cityname}}" value="{{name}}">
+ </div>
+ <div class="modal-footer">
+ <input class="btn btn-primary" type="submit" value="{{lang_save}}">
+ <a class="btn btn-default" data-dismiss="modal">{{lang_close}}</a>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
+{{/cities}}
diff --git a/modules-available/dozmod/config.json b/modules-available/dozmod/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/dozmod/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/dozmod/lang/de/templates/images-delete.json b/modules-available/dozmod/lang/de/templates/images-delete.json
new file mode 100644
index 00000000..8c54e4d1
--- /dev/null
+++ b/modules-available/dozmod/lang/de/templates/images-delete.json
@@ -0,0 +1,12 @@
+{
+ "lang_delButton": "Gew\u00e4hlte Images endg\u00fcltig l\u00f6schen",
+ "lang_delete": "L\u00f6schen",
+ "lang_description": "Diese Liste zeigt Images, die entweder abgelaufen sind, oder deren Datei besch\u00e4digt, verschoben oder gel\u00f6scht wurde. Diese Images sind zur Zeit im Lehrpool nicht verf\u00fcgbar, ihre endg\u00fcltige L\u00f6schung muss aber manuell best\u00e4tigt werden, um gr\u00f6\u00dfere Katastrophen durch Softwarefehler, verstellte Systemuhren etc. zu vermeiden.",
+ "lang_hasNewer": "Neuere Version existiert",
+ "lang_heading": "Zu l\u00f6schende Image-Versionen",
+ "lang_image": "VM",
+ "lang_owner": "Besitzer",
+ "lang_size": "Gr\u00f6\u00dfe",
+ "lang_subHeading": "Images, die abgelaufen oder besch\u00e4digt sind",
+ "lang_version": "Version vom"
+} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/de/templates/mailconfig.json b/modules-available/dozmod/lang/de/templates/mailconfig.json
new file mode 100644
index 00000000..4509c3c7
--- /dev/null
+++ b/modules-available/dozmod/lang/de/templates/mailconfig.json
@@ -0,0 +1,21 @@
+{
+ "lang_asteriskRequired": "Felder mit (*) sind erforderlich",
+ "lang_host": "Host",
+ "lang_mailConfig": "SMTP-Konfiguration zum Versenden von Mails",
+ "lang_mailConfigHeadline": "EMail-Konfiguration",
+ "lang_mailDescription": "F\u00fcllen Sie die folgenden Felder aus, wenn sie m\u00f6chten, dass Dozenten Benachrichtigungen per Mail bekommen, falls eine von ihnen genutzte oder erstellte VM oder Veranstaltung abl\u00e4uft. Um diese Funktion zu deaktivieren, lassen Sie eines der mit (*) gekennzeichneten Felder leer. Wenn das hier angegebene E-Mail-Konto nur zum Versenden von Mails genutzt wird, sollten Sie einen Auto-Responder einrichten f\u00fcr den Fall, dass ein Dozent auf eine der automatisch generierten Mails antwortet (bzw. eine explizit angegebene Reply-To Adresse ignoriert).",
+ "lang_password": "Passwort",
+ "lang_port": "Port",
+ "lang_replyTo": "Reply-To Adresse (z.B. Helpdesk)",
+ "lang_save": "Konfiguration speichern",
+ "lang_senderAddress": "Absenderadresse",
+ "lang_senderName": "Absender Anzeigename",
+ "lang_ssl": "SSL-Modus",
+ "lang_sslExplicit": "Explizites SSL (\"STARTTLS\")",
+ "lang_sslImplicit": "Implizites SSL",
+ "lang_sslNone": "Kein SSL",
+ "lang_test": "Test-Mail senden",
+ "lang_testConfiguration": "Um die Konfiguration zu testen, geben Sie hier eine Empf\u00e4ngeradresse ein",
+ "lang_testRecipient": "Empf\u00e4nger",
+ "lang_username": "Benutzername (SMTP-Auth)"
+} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/de/templates/orglist.json b/modules-available/dozmod/lang/de/templates/orglist.json
new file mode 100644
index 00000000..938429f0
--- /dev/null
+++ b/modules-available/dozmod/lang/de/templates/orglist.json
@@ -0,0 +1,6 @@
+{
+ "lang_canLogin": "Nutzer dieser Einrichtung k\u00f6nnen sich am Satelliten anmelden",
+ "lang_organization": "Einrichtung",
+ "lang_organizationList": "Liste der Einrichtungen",
+ "lang_organizationListHeader": "Nutzungsrechte f\u00fcr den Satelliten festlegen"
+} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/de/templates/userlist.json b/modules-available/dozmod/lang/de/templates/userlist.json
new file mode 100644
index 00000000..e9e33cc2
--- /dev/null
+++ b/modules-available/dozmod/lang/de/templates/userlist.json
@@ -0,0 +1,12 @@
+{
+ "lang_canLogin": "Dieser Benutzer kann sich am Satelilten anmelden",
+ "lang_email": "EMail",
+ "lang_emailNotifications": "EMail-Benachrichtigungen aktiviert",
+ "lang_lastLogin": "Letzte Anmeldung",
+ "lang_organization": "Organisation",
+ "lang_superUser": "Ist SuperUser (darf alle Veranstaltungen und VMs bearbeiten\/l\u00f6schen)",
+ "lang_user": "Benutzername",
+ "lang_userList": "Benutzerliste",
+ "lang_userListDescription": "Hier k\u00f6nnen Sie individuelle Nutzer zu \"Super-Usern\" machen. Diese haben im Dozentenmodul auf alle Veranstaltungen und VMs Vollzugriff, unabh\u00e4ngig von den gesetzten Berechtigungen. Au\u00dferdem k\u00f6nnen Sie hier Benutzer vom Zugriff mittels des Dozentenmoduls ausschlie\u00dfen.",
+ "lang_userListHeader": "Dem Satelliten bekannte Benutzer"
+} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/module.json b/modules-available/dozmod/lang/en/module.json
new file mode 100644
index 00000000..e42d21ea
--- /dev/null
+++ b/modules-available/dozmod/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Tutor Module"
+} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/templates/images-delete.json b/modules-available/dozmod/lang/en/templates/images-delete.json
new file mode 100644
index 00000000..fcc8c7e7
--- /dev/null
+++ b/modules-available/dozmod/lang/en/templates/images-delete.json
@@ -0,0 +1,12 @@
+{
+ "lang_delButton": "Permanently delete selected images",
+ "lang_delete": "Delete",
+ "lang_description": "This list shows images that reached their expire date, or where the image file in the file system is damaged or missing. You need to manually confirm the deletion of these files for safety reasons (software bugs, wrong system time, etc.).",
+ "lang_hasNewer": "Newer version exists",
+ "lang_heading": "Images marked for deletion",
+ "lang_image": "VM",
+ "lang_owner": "Owner",
+ "lang_size": "Size",
+ "lang_subHeading": "Expired or damaged images",
+ "lang_version": "Version timestamp"
+} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/templates/mailconfig.json b/modules-available/dozmod/lang/en/templates/mailconfig.json
new file mode 100644
index 00000000..0c0dcd7f
--- /dev/null
+++ b/modules-available/dozmod/lang/en/templates/mailconfig.json
@@ -0,0 +1,21 @@
+{
+ "lang_asteriskRequired": "Fields marked with (*) are required",
+ "lang_host": "Host",
+ "lang_mailConfig": "SMTP configuration for sending mails",
+ "lang_mailConfigHeadline": "email configuration",
+ "lang_mailDescription": "Fill in the following fields if you want to notify tutors\/professors\/lecturers about expiring VMs and lectures. If you leave one of the required fields blank, the feature will be disabled.",
+ "lang_password": "Password",
+ "lang_port": "Port",
+ "lang_replyTo": "Reply-To address",
+ "lang_save": "Save configuration",
+ "lang_senderAddress": "Sender address",
+ "lang_senderName": "Sender's display name",
+ "lang_ssl": "SSL mode",
+ "lang_sslExplicit": "Explicit SSL (\"STARTTLS\")",
+ "lang_sslImplicit": "Implicit SSL",
+ "lang_sslNone": "No SSL",
+ "lang_test": "Send test mail",
+ "lang_testConfiguration": "To test the configuration, enter a recipient address here",
+ "lang_testRecipient": "Recipient",
+ "lang_username": "User name (SMTP auth)"
+} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/templates/orglist.json b/modules-available/dozmod/lang/en/templates/orglist.json
new file mode 100644
index 00000000..37b89e02
--- /dev/null
+++ b/modules-available/dozmod/lang/en/templates/orglist.json
@@ -0,0 +1,6 @@
+{
+ "lang_canLogin": "Members of this organization can login",
+ "lang_organization": "Organization",
+ "lang_organizationList": "List of organizations",
+ "lang_organizationListHeader": "Set access permissions for organizations"
+} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/templates/userlist.json b/modules-available/dozmod/lang/en/templates/userlist.json
new file mode 100644
index 00000000..615f1b14
--- /dev/null
+++ b/modules-available/dozmod/lang/en/templates/userlist.json
@@ -0,0 +1,12 @@
+{
+ "lang_canLogin": "This user can login to this satellite",
+ "lang_email": "E-Mail",
+ "lang_emailNotifications": "E-Mail notifications enabled",
+ "lang_lastLogin": "Last login",
+ "lang_organization": "Organization",
+ "lang_superUser": "Is super user (can edit\/delete all lectures and VMs)",
+ "lang_user": "User name",
+ "lang_userList": "User list",
+ "lang_userListDescription": "Here you can promote \"super users\", which will have all permissions in the \"Dozenzenmodul\". You can also ban users from accessing this server via the \"Dozentenmodul\".",
+ "lang_userListHeader": "Users known to this satellite"
+} \ No newline at end of file
diff --git a/modules-available/dozmod/page.inc.php b/modules-available/dozmod/page.inc.php
new file mode 100644
index 00000000..f98d8952
--- /dev/null
+++ b/modules-available/dozmod/page.inc.php
@@ -0,0 +1,252 @@
+<?php
+
+class Page_DozMod extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ $action = Request::post('action');
+
+ if ($action === 'mail') {
+ $this->mailHandler();
+ }
+ if ($action === 'delimages') {
+ $result = $this->handleDeleteImages();
+ if (!empty($result)) {
+ Message::addInfo('delete-images', $result);
+ }
+ Util::redirect('?do=DozMod');
+ }
+ }
+
+ protected function doRender()
+ {
+ $this->listDeletePendingImages();
+ // Mail config
+ $conf = Database::queryFirst('SELECT value FROM sat.configuration WHERE parameter = :param', array('param' => 'mailconfig'));
+ if ($conf != null) {
+ $conf = @json_decode($conf['value'], true);
+ if (is_array($conf)) {
+ $conf['set_' . $conf['ssl']] = 'selected="selected"';
+ }
+ }
+ Render::addTemplate('mailconfig', $conf);
+ // User list for making people admin
+ $this->listUsers();
+ $this->listOrganizations();
+ }
+
+ private function listDeletePendingImages()
+ {
+ $res = Database::simpleQuery("SELECT b.displayname,"
+ . " own.firstname, own.lastname, own.email,"
+ . " v.imageversionid, v.createtime, v.filesize, v.deletestate,"
+ . " lat.expiretime AS latexptime, lat.deletestate AS latdelstate"
+ . " FROM sat.imageversion v"
+ . " INNER JOIN sat.imagebase b ON (b.imagebaseid = v.imagebaseid)"
+ . " INNER JOIN sat.user own ON (b.ownerid = own.userid)"
+ . " LEFT JOIN sat.imageversion lat ON (b.latestversionid = lat.imageversionid)"
+ . " WHERE v.deletestate <> 'KEEP'"
+ . " ORDER BY b.displayname ASC, v.createtime ASC");
+ $NOW = time();
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if ($row['latexptime'] > $NOW && $row['latdelstate'] === 'KEEP') {
+ $row['hasNewerClass'] = 'glyphicon-ok green';
+ } else {
+ $row['hasNewerClass'] = 'glyphicon-remove red';
+ }
+ if ($row['deletestate'] === 'WANT_DELETE') {
+ $row['name_extra_class'] = 'slx-strike';
+ }
+ $row['version'] = date('d.m.Y H:i:s', $row['createtime']);
+ $row['filesize'] = Util::readableFileSize($row['filesize']);
+ $rows[] = $row;
+ }
+ if (empty($rows))
+ return;
+ Render::addTemplate('images-delete', array('images' => $rows));
+ }
+
+ private function cleanMailArray()
+ {
+ $keys = array('host', 'port', 'ssl', 'senderAddress', 'replyTo', 'username', 'password', 'serverName');
+ $data = array();
+ foreach ($keys as $key) {
+ $data[$key] = Request::post($key, '');
+ settype($data[$key], 'string');
+ if (is_numeric($data[$key])) {
+ settype($data[$key], 'int');
+ }
+ }
+ return $data;
+ }
+
+ protected function doAjax()
+ {
+ if (!User::hasPermission('superadmin'))
+ return;
+
+ $action = Request::post('action');
+ if ($action === 'mail') {
+ $this->handleTestMail();
+ } elseif ($action === 'setmail' || $action === 'setsu' || $action == 'setlogin') {
+ $this->setUserOption($action);
+ } elseif ($action === 'setorglogin') {
+ $this->setOrgOption($action);
+ } elseif ($action === 'delimages') {
+ die($this->handleDeleteImages());
+ }
+ }
+
+ private function handleDeleteImages()
+ {
+ $images = Request::post('images', false);
+ if (is_array($images)) {
+ foreach ($images as $image => $val) {
+ if (strtolower($val) !== 'on')
+ continue;
+ Database::exec("UPDATE sat.imageversion SET deletestate = 'WANT_DELETE'"
+ . " WHERE deletestate = 'SHOULD_DELETE' AND imageversionid = :imageversionid", array(
+ 'imageversionid' => $image
+ ));
+ }
+ if (!empty($images)) {
+ $ret = Download::asStringPost('http://127.0.0.1:9080/do/delete-images', false, 2, $code);
+ if ($code == 999) {
+ $ret .= "\nConnection to DMSD failed.";
+ }
+ return $ret;
+ }
+ }
+ return false;
+ }
+
+ private function handleTestMail()
+ {
+ $do = Request::post('button');
+ if ($do === 'test') {
+ // Prepare array
+ $data = $this->cleanMailArray();
+ Header('Content-Type: text/plain; charset=utf-8');
+ $data['recipient'] = Request::post('recipient', '');
+ if (!preg_match('/.+@.+\..+/', $data['recipient'])) {
+ $result = 'No recipient given!';
+ } else {
+ $result = Download::asStringPost('http://127.0.0.1:9080/do/mailtest', $data, 2, $code);
+ }
+ die($result);
+ }
+ }
+
+ private function mailHandler()
+ {
+ // Check action
+ $do = Request::post('button');
+ if ($do === 'save') {
+ // Prepare array
+ $data = $this->cleanMailArray();
+ $data = json_encode($data);
+ Database::exec('INSERT INTO sat.configuration (parameter, value)'
+ . ' VALUES (:param, :value)'
+ . ' ON DUPLICATE KEY UPDATE value = VALUES(value)', array(
+ 'param' => 'mailconfig',
+ 'value' => $data
+ ));
+ Message::addSuccess('mail-config-saved');
+ }
+ Util::redirect('?do=DozMod');
+ }
+
+ private function listUsers()
+ {
+ $res = Database::simpleQuery('SELECT userid, firstname, lastname, email, lastlogin, user.canlogin, issuperuser, emailnotifications,'
+ . ' organization.displayname AS orgname FROM sat.user'
+ . ' LEFT JOIN sat.organization USING (organizationid)'
+ . ' ORDER BY lastname ASC, firstname ASC');
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['canlogin'] = $this->checked($row['canlogin']);
+ $row['issuperuser'] = $this->checked($row['issuperuser']);
+ $row['emailnotifications'] = $this->checked($row['emailnotifications']);
+ $row['lastlogin'] = date('d.m.Y', $row['lastlogin']);
+ $rows[] = $row;
+ }
+ Render::addTemplate('userlist', array('users' => $rows));
+ }
+
+ private function listOrganizations()
+ {
+ $res = Database::simpleQuery('SELECT organizationid, displayname, canlogin FROM sat.organization'
+ . ' ORDER BY displayname ASC');
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['canlogin'] = $this->checked($row['canlogin']);
+ $rows[] = $row;
+ }
+ Render::addTemplate('orglist', array('organizations' => $rows));
+ }
+
+ private function checked($val)
+ {
+ if ($val)
+ return 'checked="checked"';
+ return '';
+ }
+
+ private function setUserOption($option)
+ {
+ $val = (string) Request::post('value', '-');
+ if ($val !== '1' && $val !== '0')
+ die('Nein');
+ if ($option === 'setmail') {
+ $field = 'emailnotifications';
+ } elseif ($option === 'setsu') {
+ $field = 'issuperuser';
+ } elseif ($option === 'setlogin') {
+ $field = 'canlogin';
+ } else {
+ die('Unknown');
+ }
+ $user = (string) Request::post('userid', '?');
+ $ret = Database::exec("UPDATE sat.user SET $field = :onoff WHERE userid = :userid", array(
+ 'userid' => $user,
+ 'onoff' => $val
+ ));
+ error_log("Setting $field to $val for $user - affected: $ret");
+ if ($ret === false)
+ die('Error');
+ if ($ret == 0)
+ die(1 - $val);
+ die($val);
+ }
+
+ private function setOrgOption($option)
+ {
+ $val = (string) Request::post('value', '-');
+ if ($val !== '1' && $val !== '0')
+ die('Nein');
+ if ($option === 'setorglogin') {
+ $field = 'canlogin';
+ } else {
+ die('Unknown');
+ }
+ $ret = Database::exec("UPDATE sat.organization SET $field = :onoff WHERE organizationid = :organizationid", array(
+ 'organizationid' => (string) Request::post('organizationid', ''),
+ 'onoff' => $val
+ ));
+ if ($ret === false)
+ die('Error');
+ if ($ret === 0)
+ die(1 - $val);
+ die($val);
+ }
+
+}
diff --git a/modules-available/dozmod/templates/images-delete.html b/modules-available/dozmod/templates/images-delete.html
new file mode 100644
index 00000000..c4cbfd34
--- /dev/null
+++ b/modules-available/dozmod/templates/images-delete.html
@@ -0,0 +1,57 @@
+<h2>{{lang_heading}}</h2>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_subHeading}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_description}}</p>
+ <div class="table-responsive">
+ <form id="delform" method="post" action="?do=DozMod" onsubmit="return slxPostdel()">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="delimages">
+ <table class="table table-stripped table-condensed">
+ <thead>
+ <tr>
+ <th>{{lang_image}}</th>
+ <th>{{lang_version}}</th>
+ <th>{{lang_owner}}</th>
+ <th><span class="glyphicon glyphicon-upload" title="{{lang_hasNewer}}"></th>
+ <th>{{lang_size}}</th>
+ <th><span class="glyphicon glyphicon-trash" title="{{lang_delete}}"></span></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#images}}
+ <tr>
+ <td class="text-left slx-nowrap {{name_extra_class}}">{{displayname}}<br><span class="small">{{imageversionid}}</span></td>
+ <td class="text-left slx-nowrap">{{version}}</td>
+ <td class="text-left slx-nowrap"><a href="mailto:{{email}}">{{lastname}}, {{firstname}}</a></td>
+ <td class="text-left slx-nowrap"><span class="glyphicon {{hasNewerClass}}"></span></td>
+ <td class="text-right slx-nowrap">{{filesize}}</td>
+ <td><input name="images[{{imageversionid}}]" type="checkbox" checked="checked"></td>
+ </tr>
+ {{/images}}
+ </tbody>
+ </table>
+ <button id="delbtn" class="btn btn-danger" type="submit" name="button" value="save">{{lang_delButton}}</button>
+ </form>
+ <pre style="display:none" id="deloutput"></pre>
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript"><!--
+
+function slxPostdel() {
+ var f = $('#delform');
+ $('#delbtn').prop('disabled', true);
+ $.post('?do=DozMod', f.serialize()).done(function (data) {
+ $('#deloutput').text(data).css('display', '');
+ }).fail(function () {
+ $('#deloutput').text('ERROR').css('display', '');
+ });
+ return false;
+}
+
+--> </script> \ No newline at end of file
diff --git a/modules-available/dozmod/templates/mailconfig.html b/modules-available/dozmod/templates/mailconfig.html
new file mode 100644
index 00000000..b19776c0
--- /dev/null
+++ b/modules-available/dozmod/templates/mailconfig.html
@@ -0,0 +1,91 @@
+<h2>{{lang_mailConfigHeadline}}</h2>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_mailConfig}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_mailDescription}}</p>
+ <p>[BETA] Diese Funktionalität ist neu. Wir bitten um Nachsicht, falls es Situationen gibt, in denen zu viele
+ oder zu wenige Nachrichten verschickt werden.</p>
+ <form action="?do=DozMod" method="post" id="mailconf">
+ <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;">
+ <input type="password" name="password_fake" id="password_fake" value="" style="display:none;">
+ <div class="input-group">
+ <label class="input-group-addon slx-ga2" for="host-id">{{lang_host}} *</label>
+ <input type="text" name="host" id ="host-id" class="form-control" placeholder="smtp.example.com" value="{{host}}">
+ </div>
+ <div class="input-group">
+ <label class="input-group-addon slx-ga2" for="port-id">{{lang_port}} *</label>
+ <input type="text" name="port" id ="port-id" class="form-control" placeholder="465" value="{{port}}">
+ </div>
+ <div class="input-group">
+ <label class="input-group-addon slx-ga2" for="ssl-id">{{lang_ssl}} *</label>
+ <select class="form-control" name="ssl" id="ssl-id">
+ <option value="NONE" {{set_NONE}}>{{lang_sslNone}}</option>
+ <option value="IMPLICIT" {{set_IMPLICIT}}>{{lang_sslImplicit}}</option>
+ <option value="EXPLICIT" {{set_EXPLICIT}}>{{lang_sslExplicit}}</option>
+ </select>
+ </div>
+ <div class="input-group">
+ <label class="input-group-addon slx-ga2" for="senderAddress-id">{{lang_senderAddress}} *</label>
+ <input type="text" name="senderAddress" id ="senderAddress-id" class="form-control" placeholder="smtp-username@hs-example.com" value="{{senderAddress}}">
+ </div>
+ <div class="input-group">
+ <label class="input-group-addon slx-ga2" for="serverName-id">{{lang_senderName}}</label>
+ <input type="text" name="serverName" id ="serverName-id" class="form-control" placeholder="bwLehrpool HS Example" value="{{serverName}}">
+ </div>
+ <div class="input-group">
+ <label class="input-group-addon slx-ga2" for="replyTo-id">{{lang_replyTo}}</label>
+ <input type="text" name="replyTo" id ="replyTo-id" class="form-control" placeholder="helpdesk@hs-example.com" value="{{replyTo}}">
+ </div>
+ <div class="input-group">
+ <label class="input-group-addon slx-ga2" for="username-id">{{lang_username}}</label>
+ <input type="text" name="username" id ="username-id" class="form-control" placeholder="smtp-username" value="{{username}}">
+ </div>
+ <div class="input-group">
+ <label class="input-group-addon slx-ga2" for="password-id">{{lang_password}}</label>
+ <input type="{{password_type}}" name="password" id ="password-id" class="form-control" placeholder="geheim" value="{{password}}">
+ </div>
+ <p>{{lang_asteriskRequired}}</p>
+ <br>
+ <p>{{lang_testConfiguration}}</p>
+ <div class="input-group">
+ <label class="input-group-addon slx-ga2" for="test-id">{{lang_testRecipient}}</label>
+ <input type="text" name="recipient" id ="test-id" class="form-control" placeholder="test@example.com" value="">
+ </div>
+ <br>
+ <button class="btn btn-primary btn-sm" type="button" id="test-button" name="button" value="test" onclick="slxTestConfig()">{{lang_test}}</button>
+ <span id="test-spin" style="display:none"><span class="glyphicon glyphicon-refresh slx-rotation"></span></span>
+ <pre id="test-output" style="display:none"></pre>
+ <button class="btn btn-primary btn-sm" type="submit" name="button" value="save">{{lang_save}}</button>
+ <br>
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="mail">
+ </form>
+ </div>
+</div>
+
+<script type="text/javascript"><!--
+function slxTestConfig() {
+ $('#test-button').prop('disabled', true);
+ $('#test-spin').css('display', '');
+ var str = $('#mailconf').serialize();
+ str += '&button=test';
+ console.log(str);
+ $.post('?do=DozMod', str).done(function(data) {
+ console.log('Success');
+ console.log(data);
+ checkRes(data);
+ }).fail(function() {
+ checkRes('DozMod refused the connection');
+ }).always(function() {
+ $('#test-button').prop('disabled', false);
+ $('#test-spin').css('display', 'none');
+ });
+ }
+
+ function checkRes(text) {
+ $('#test-output').css('display', '').text(text);
+ }
+// --> </script> \ No newline at end of file
diff --git a/modules-available/dozmod/templates/orglist.html b/modules-available/dozmod/templates/orglist.html
new file mode 100644
index 00000000..d325cc4d
--- /dev/null
+++ b/modules-available/dozmod/templates/orglist.html
@@ -0,0 +1,51 @@
+<h2>{{lang_organizationList}}</h2>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_organizationListHeader}}
+ </div>
+ <div class="panel-body">
+ <div class="table-responsive">
+ <table class="table table-stripped table-condensed">
+ <thead>
+ <tr>
+ <th>{{lang_organization}}</th>
+ <th><span class="glyphicon glyphicon-ok" title="{{lang_canLogin}}"></span></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#organizations}}
+ <tr>
+ <td class="text-left slx-nowrap">{{displayname}}</td>
+ <td><input onclick="seto('setorglogin', this, '{{organizationid}}')" type="checkbox" {{{canlogin}}}></td>
+ </tr>
+ {{/organizations}}
+ </tbody>
+ </table>
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript"><!--
+
+function seto(action, el, orgid) {
+ var box = $(el);
+ var v = el.checked ? '1' : '0';
+ var old = el.checked == true;
+ box.css('display', 'none');
+ $.post('?do=DozMod', { token: TOKEN, action: action, organizationid: orgid, value: v }).done(function (data) {
+ if (data != 1 && data != 0) {
+ el.checked = !old;
+ box.parent().css('background-color', 'red !important');
+ } else {
+ el.checked = (data == 1);
+ }
+ box.css('display', '');
+ }).fail(function() {
+ el.checked = !old;
+ box.parent().css('background-color', 'red !important');
+ box.css('display', '');
+ });
+}
+
+--> </script> \ No newline at end of file
diff --git a/modules-available/dozmod/templates/userlist.html b/modules-available/dozmod/templates/userlist.html
new file mode 100644
index 00000000..a76eae5e
--- /dev/null
+++ b/modules-available/dozmod/templates/userlist.html
@@ -0,0 +1,62 @@
+<h2>{{lang_userList}}</h2>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_userListHeader}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_userListDescription}}</p>
+ <div class="table-responsive">
+ <table class="table table-stripped table-condensed">
+ <thead>
+ <tr>
+ <th>{{lang_user}}</th>
+ <th>{{lang_organization}}</th>
+ <th>{{lang_lastLogin}}</th>
+ <th>{{lang_email}}</th>
+ <th><span class="glyphicon glyphicon-envelope" title="{{lang_emailNotifications}}"></span></th>
+ <th><span class="glyphicon glyphicon-king" title="{{lang_superUser}}"></span></th>
+ <th><span class="glyphicon glyphicon-ok" title="{{lang_canLogin}}"></span></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#users}}
+ <tr>
+ <td class="text-left slx-nowrap">{{lastname}}, {{firstname}}</td>
+ <td class="text-left slx-nowrap">{{orgname}}</td>
+ <td class="text-left slx-nowrap">{{lastlogin}}</td>
+ <td class="text-left slx-nowrap"><a href="mailto:{{email}}">{{email}}</a></td>
+ <td><input onclick="setu('setmail', this, '{{userid}}')" type="checkbox" {{{emailnotifications}}}></td>
+ <td><input onclick="setu('setsu', this, '{{userid}}')" type="checkbox" {{{issuperuser}}}></td>
+ <td><input onclick="setu('setlogin', this, '{{userid}}')" type="checkbox" {{{canlogin}}}></td>
+ </tr>
+ {{/users}}
+ </tbody>
+ </table>
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript"><!--
+
+function setu(action, el, uid) {
+ var box = $(el);
+ var v = el.checked ? '1' : '0';
+ var old = el.checked == true;
+ box.css('display', 'none');
+ $.post('?do=DozMod', { token: TOKEN, action: action, userid: uid, value: v }).done(function (data) {
+ if (data != 1 && data != 0) {
+ el.checked = !old;
+ box.parent().css('background-color', 'red !important');
+ } else {
+ el.checked = (data == 1);
+ }
+ box.css('display', '');
+ }).fail(function() {
+ el.checked = !old;
+ box.parent().css('background-color', 'red !important');
+ box.css('display', '');
+ });
+}
+
+--> </script> \ No newline at end of file
diff --git a/modules-available/eventlog/config.json b/modules-available/eventlog/config.json
new file mode 100644
index 00000000..650ab2fe
--- /dev/null
+++ b/modules-available/eventlog/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.status",
+ "enabled":"true"
+}
diff --git a/modules-available/eventlog/lang/de/templates/_page.json b/modules-available/eventlog/lang/de/templates/_page.json
new file mode 100644
index 00000000..b1a292e6
--- /dev/null
+++ b/modules-available/eventlog/lang/de/templates/_page.json
@@ -0,0 +1,6 @@
+{
+ "lang_details": "Details",
+ "lang_event": "Ereignis",
+ "lang_eventLog": "Ereignisprotokoll",
+ "lang_when": "Wann"
+} \ No newline at end of file
diff --git a/modules-available/eventlog/lang/en/module.json b/modules-available/eventlog/lang/en/module.json
new file mode 100644
index 00000000..a0f6afa6
--- /dev/null
+++ b/modules-available/eventlog/lang/en/module.json
@@ -0,0 +1,7 @@
+{
+ "lang_details": "Details",
+ "lang_event": "Event",
+ "lang_eventLog": "Server Log",
+ "lang_when": "When",
+ "module_name": "Client Log"
+} \ No newline at end of file
diff --git a/modules-available/eventlog/lang/en/templates/_page.json b/modules-available/eventlog/lang/en/templates/_page.json
new file mode 100644
index 00000000..21ec64ea
--- /dev/null
+++ b/modules-available/eventlog/lang/en/templates/_page.json
@@ -0,0 +1,6 @@
+{
+ "lang_details": "Details",
+ "lang_event": "Event",
+ "lang_eventLog": "Event log",
+ "lang_when": "When"
+} \ No newline at end of file
diff --git a/modules-available/eventlog/lang/pt/module.json b/modules-available/eventlog/lang/pt/module.json
new file mode 100644
index 00000000..4fcf321a
--- /dev/null
+++ b/modules-available/eventlog/lang/pt/module.json
@@ -0,0 +1,7 @@
+{
+ "lang_details": "Detalhes",
+ "lang_event": "Evento",
+ "lang_eventLog": "Log dos Eventos",
+ "lang_when": "Quando",
+ "module_name": "Log dos Clientes"
+} \ No newline at end of file
diff --git a/modules-available/eventlog/page.inc.php b/modules-available/eventlog/page.inc.php
new file mode 100644
index 00000000..7cfc8a55
--- /dev/null
+++ b/modules-available/eventlog/page.inc.php
@@ -0,0 +1,70 @@
+<?php
+
+class Page_EventLog extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ User::setLastSeenEvent(Property::getLastWarningId());
+ }
+
+ protected function doRender()
+ {
+ Render::setTitle(Dictionary::translate('lang_titleEventLog'));
+ $today = date('d.m.Y');
+ $yesterday = date('d.m.Y', time() - 86400);
+ $lines = array();
+ $paginate = new Paginate("SELECT logid, dateline, logtypeid, description, extra FROM eventlog ORDER BY logid DESC", 50);
+ $res = $paginate->exec();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $day = date('d.m.Y', $row['dateline']);
+ if ($day === $today) {
+ $day = Dictionary::translate('today');
+ } elseif ($day === $yesterday) {
+ $day = Dictionary::translate('yesterday');
+ }
+ $row['date'] = $day . date(' H:i', $row['dateline']);
+ $row['icon'] = $this->typeToIcon($row['logtypeid']);
+ $row['color'] = $this->typeToColor($row['logtypeid']);
+ $lines[] = $row;
+ }
+
+ $paginate->render('_page', array(
+ 'list' => $lines
+ ));
+ }
+
+ private function typeToIcon($type)
+ {
+ switch ($type) {
+ case 'info':
+ return 'ok';
+ case 'warning':
+ return 'exclamation-sign';
+ case 'failure':
+ return 'remove';
+ default:
+ return 'question-sign';
+ }
+ }
+
+ private function typeToColor($type)
+ {
+ switch ($type) {
+ case 'info':
+ return '';
+ case 'warning':
+ return 'orange';
+ case 'failure':
+ return 'red';
+ default:
+ return '';
+ }
+ }
+
+}
diff --git a/modules-available/eventlog/templates/_page.html b/modules-available/eventlog/templates/_page.html
new file mode 100644
index 00000000..2e657805
--- /dev/null
+++ b/modules-available/eventlog/templates/_page.html
@@ -0,0 +1,41 @@
+<h1>{{lang_eventLog}}</h1>
+{{{pagenav}}}
+<table class="table table-striped table-condensed">
+ <thead>
+ <th width="1"></th>
+ <th>{{lang_when}}</th>
+ <th>{{lang_event}}</th>
+ <th width="1">{{lang_details}}</th>
+ </thead>
+ <tbody>
+ {{#list}}
+ <tr>
+ <td><span class="glyphicon glyphicon-{{icon}}" title="{{logtypeid}}"></span></td>
+ <td class="text-right" nowrap="nowrap">{{date}}</td>
+ <td class="{{color}}">{{description}}</td>
+ <td>{{#extra}}
+ <a class="btn btn-default btn-xs pull-left" onclick="$('#details-body').html($('#extra-{{logid}}').html())" data-toggle="modal" data-target="#myModal">&raquo;</a>
+ <div class="hidden" id="extra-{{logid}}">{{extra}}</div>
+ {{/extra}}</td>
+ </tr>
+ {{/list}}
+ </tbody>
+</table>
+{{{pagenav}}}
+
+<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+ <div class="modal-dialog modal-lg">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+ <h4 class="modal-title" id="myModalLabel">{{lang_details}}</h4>
+ </div>
+ <div class="modal-body">
+ <pre id="details-body"></pre>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/modules-available/imgmanagement/config.json b/modules-available/imgmanagement/config.json
new file mode 100644
index 00000000..5c5c05ca
--- /dev/null
+++ b/modules-available/imgmanagement/config.json
@@ -0,0 +1,4 @@
+{
+ "enabled":"false",
+ "permission":"1"
+}
diff --git a/modules-available/imgmanagement/lang/pt/module.json b/modules-available/imgmanagement/lang/pt/module.json
new file mode 100644
index 00000000..83b9751a
--- /dev/null
+++ b/modules-available/imgmanagement/lang/pt/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Gerenciamento de Imagens"
+} \ No newline at end of file
diff --git a/modules-available/imgmanagement/page.inc.php b/modules-available/imgmanagement/page.inc.php
new file mode 100644
index 00000000..dc19cb2a
--- /dev/null
+++ b/modules-available/imgmanagement/page.inc.php
@@ -0,0 +1,63 @@
+<?php
+
+class Page_Imgmanagement extends Page
+{
+
+ private $page;
+ private $baselocation;
+ private $images;
+
+ protected function doPreprocess()
+ {
+
+ User::load();
+ if (!User::hasPermission('baseconfig_local')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ error_reporting(E_ALL);
+ ini_set('display_errors','on');
+
+ Session::get('token');
+
+ }
+
+ protected function doRender()
+ {
+ error_reporting(E_ALL);
+ ini_set('display_errors','on');
+
+ $actives = array();
+ $deactives = array();
+
+ $res = Database::simpleQuery("SELECT id, name, path, userid, is_template, is_active, description FROM images ORDER BY id DESC");
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if($row['is_active'])
+ $actives[] = array(
+ 'id' => $row['id'],
+ 'name' => $row['name'],
+ 'path' => $row['path'],
+ 'userid' => $row['userid'],
+ 'is_template' => $row['is_template'],
+ 'is_active' => $row['is_active'],
+ 'description' => $row['description']
+ );
+ else
+ $deactives[] = array(
+ 'id' => $row['id'],
+ 'name' => $row['name'],
+ 'path' => $row['path'],
+ 'userid' => $row['userid'],
+ 'is_template' => $row['is_template'],
+ 'is_active' => $row['is_active'],
+ 'description' => $row['description']
+ );
+
+ }
+
+ Render::addTemplate('page-imgmanagement', array(
+ 'deactives' => $deactives,
+ 'actives' => $actives));
+ }
+}
diff --git a/modules-available/imgmanagement/templates/page-imgmanagement.html b/modules-available/imgmanagement/templates/page-imgmanagement.html
new file mode 100644
index 00000000..cdae627b
--- /dev/null
+++ b/modules-available/imgmanagement/templates/page-imgmanagement.html
@@ -0,0 +1,62 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ Gerenciamento de Imagens
+ </div>
+
+ <div class="panel-body">
+ <div class="alert alert-info">
+ Imagens em azul indicam imagens criadas pelo usuário
+ </div>
+
+ <div class="panel-body col-xs-6">
+ <div class="left">
+ <ul class="source connected">
+ {{#deactives}}
+ <li>{{name}}</li>
+ {{/deactives}}
+ </ul>
+ </div>
+ </div>
+
+ <div class="panel-body col-xs-6">
+ <div class="right">
+ <ul class="target connected" id="ativa1">
+
+ {{#actives}}
+ <li>{{name}}</li>
+ {{/actives}}
+ </ul>
+ </div>
+ </div>
+
+ <div class="panel-body text-center">
+ <input type='button' value='<<' id='move_left' />
+ <input type='button' value='>>' id='move_right' />
+ <a class="btn btn-default" href="#" role="button">Upload de Nova Imagem<a>
+ <a class="btn btn-info" href="#" role="button">Gerar IPXE<a>
+ <a class="btn btn-info" href="#" role="button">Salvar<a>
+ </div>
+
+ </div>
+</div>
+
+<script src="script/jquery.sortable.min.js"></script>
+<script type="text/javascript">
+ $(document).ready(function() {
+ $(".source, .target").sortable({
+ connectWith: ".connected"
+ });
+
+ });
+ $('body').on('click', 'li', function() {
+ $(this).toggleClass('selected');
+ });
+
+ $('#move_left').click(function() {
+ $('.source').append($('.target .selected').removeClass('selected'));
+ });
+
+ $('#move_right').click(function() {
+ $('.target').append($('.source .selected').removeClass('selected'));
+ });
+</script>
diff --git a/modules-available/internetaccess/config.json b/modules-available/internetaccess/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/internetaccess/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/internetaccess/lang/de/templates/_page.json b/modules-available/internetaccess/lang/de/templates/_page.json
new file mode 100644
index 00000000..61e324a8
--- /dev/null
+++ b/modules-available/internetaccess/lang/de/templates/_page.json
@@ -0,0 +1,14 @@
+{
+ "lang_automatic": "Automatisch",
+ "lang_description": "Hier k\u00f6nnen Sie konfigurieren, wie der Satellitenserver auf das Internet zugreifen soll. Dies wird in erster Linie f\u00fcr das Aktualisieren des Systems sowie das Synchronisieren von Virtuellen Maschinen mit dem Zentralserver verwendet. Gegenw\u00e4rtig wird neben Direktzugriff noch SOCKS4\/5 unterst\u00fctzt.",
+ "lang_internetAccess": "Internetzugriff",
+ "lang_manual": "Manuelle Angabe",
+ "lang_manualProxyConfig": "Wenn Sie einen SOCKS-Proxy manuell konfigurieren m\u00f6chten, geben Sie bitte hier die Verbindungsdaten an.",
+ "lang_no": "Keiner",
+ "lang_proxyAddress": "Adresse",
+ "lang_proxyPassword": "Passwort",
+ "lang_proxyPort": "Port",
+ "lang_proxyType": "Proxy Typ",
+ "lang_proxyUsername": "Benutzername",
+ "lang_save": "Speichern"
+} \ No newline at end of file
diff --git a/modules-available/internetaccess/lang/de/templates/restart.json b/modules-available/internetaccess/lang/de/templates/restart.json
new file mode 100644
index 00000000..899a1d28
--- /dev/null
+++ b/modules-available/internetaccess/lang/de/templates/restart.json
@@ -0,0 +1,5 @@
+{
+ "lang_restartFailed": "Neustart eines oder mehrerer Dienste fehlgeschlagen!",
+ "lang_restarting": "Neustart",
+ "lang_serviceRestart": "Neustarten der Dienste"
+} \ No newline at end of file
diff --git a/modules-available/internetaccess/lang/en/module.json b/modules-available/internetaccess/lang/en/module.json
new file mode 100644
index 00000000..f0c2f72f
--- /dev/null
+++ b/modules-available/internetaccess/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Internet Access"
+} \ No newline at end of file
diff --git a/modules-available/internetaccess/lang/en/templates/_page.json b/modules-available/internetaccess/lang/en/templates/_page.json
new file mode 100644
index 00000000..c02ca4f5
--- /dev/null
+++ b/modules-available/internetaccess/lang/en/templates/_page.json
@@ -0,0 +1,14 @@
+{
+ "lang_automatic": "Auto",
+ "lang_description": "Here you can configure how the satellite server has to access the internet.",
+ "lang_internetAccess": "Internet access",
+ "lang_manual": "Manual",
+ "lang_manualProxyConfig": "If you want to configure a proxy server manually, please supply the credentials here.",
+ "lang_no": "None",
+ "lang_proxyAddress": "Address",
+ "lang_proxyPassword": "Password",
+ "lang_proxyPort": "Port",
+ "lang_proxyType": "Proxy type",
+ "lang_proxyUsername": "User",
+ "lang_save": "Save"
+} \ No newline at end of file
diff --git a/modules-available/internetaccess/lang/en/templates/restart.json b/modules-available/internetaccess/lang/en/templates/restart.json
new file mode 100644
index 00000000..badad460
--- /dev/null
+++ b/modules-available/internetaccess/lang/en/templates/restart.json
@@ -0,0 +1,5 @@
+{
+ "lang_restartFailed": "Restarting one or more services failed!",
+ "lang_restarting": "Restarting",
+ "lang_serviceRestart": "Restart of services"
+} \ No newline at end of file
diff --git a/modules-available/internetaccess/page.inc.php b/modules-available/internetaccess/page.inc.php
new file mode 100644
index 00000000..b949be26
--- /dev/null
+++ b/modules-available/internetaccess/page.inc.php
@@ -0,0 +1,51 @@
+<?php
+
+class Page_InternetAccess extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ if (isset($_POST['PROXY_CONF'])) {
+ $data = array();
+ foreach (array('PROXY_CONF', 'PROXY_ADDR', 'PROXY_PORT', 'PROXY_USERNAME', 'PROXY_PASSWORD') as $key) {
+ $data[$key] = Request::post($key, '');
+ }
+ if (!FileUtil::arrayToFile(CONFIG_PROXY_CONF, $data)) {
+ Message::addError('error-write', CONFIG_PROXY_CONF);
+ Util::redirect();
+ } else {
+ Message::addSuccess('settings-updated');
+ Taskmanager::release(Taskmanager::submit('ReloadProxy'));
+ $taskids = array();
+ Trigger::stopDaemons(NULL, $taskids);
+ $taskids = array();
+ Trigger::startDaemons(NULL, $taskids);
+ Session::set('ia-restart', $taskids);
+ Util::redirect('?do=InternetAccess&show=update');
+ }
+ }
+ }
+
+ protected function doRender()
+ {
+ if (Request::any('show') === 'update') {
+ $taskids = Session::get('ia-restart');
+ if (is_array($taskids)) {
+ Render::addTemplate('restart', $taskids);
+ } else {
+ Message::addError('invalid-action', 'Restart');
+ }
+ }
+ $data = FileUtil::fileToArray(CONFIG_PROXY_CONF);
+ if (!isset($data['PROXY_CONF']))
+ $data['PROXY_CONF'] = 'AUTO';
+ $data['selected_' . $data['PROXY_CONF']] = 'selected';
+ Render::addTemplate('_page', $data);
+ }
+
+}
diff --git a/modules-available/internetaccess/templates/_page.html b/modules-available/internetaccess/templates/_page.html
new file mode 100644
index 00000000..a2aaddac
--- /dev/null
+++ b/modules-available/internetaccess/templates/_page.html
@@ -0,0 +1,40 @@
+<h1>{{lang_internetAccess}}</h1>
+
+<form action="?do=InternetAccess" method="post">
+ <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;">
+ <input type="password" name="password_fake" id="password_fake" value="" style="display:none;">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="panel panel-default">
+ <div class="panel-heading">{{lang_internetAccess}}</div>
+ <div class="panel-body">
+ <p>{{lang_description}}</p>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_proxyType}}</span>
+ <select name="PROXY_CONF" class="form-control">
+ <option value="AUTO" {{selected_AUTO}}>{{lang_automatic}} (dns-wpad)</option>
+ <option value="NO" {{selected_NO}}>{{lang_no}}</option>
+ <option value="YES" {{selected_YES}}>{{lang_manual}}</option>
+ </select>
+ </div>
+ <br>
+ <p>{{lang_manualProxyConfig}}</p>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_proxyAddress}} *</span>
+ <input name="PROXY_ADDR" value="{{PROXY_ADDR}}" type="text" class="form-control">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_proxyPort}} *</span>
+ <input name="PROXY_PORT" value="{{PROXY_PORT}}" type="text" class="form-control">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_proxyUsername}}</span>
+ <input name="PROXY_USERNAME" value="{{PROXY_USERNAME}}" type="text" class="form-control">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_proxyPassword}}</span>
+ <input name="PROXY_PASSWORD" value="{{PROXY_PASSWORD}}" type="{{password_type}}" class="form-control">
+ </div>
+ <button class="btn btn-primary" type="submit">{{lang_save}}</button>
+ </div>
+ </div>
+</form>
diff --git a/modules-available/internetaccess/templates/restart.html b/modules-available/internetaccess/templates/restart.html
new file mode 100644
index 00000000..effe1feb
--- /dev/null
+++ b/modules-available/internetaccess/templates/restart.html
@@ -0,0 +1,22 @@
+<div class="panel panel-default">
+ <div class="panel-heading">{{lang_serviceRestart}}</div>
+ <div class="panel-body">
+ <div data-tm-id="{{syncid}}" data-tm-log="messages" data-tm-callback="restartCb">{{lang_restarting}} syncdaemon</div>
+ <div data-tm-id="{{dmsdid}}" data-tm-log="messages" data-tm-callback="restartCb">{{lang_restarting}} dmsd</div>
+ <div data-tm-id="{{ldadpid}}" data-tm-log="messages" data-tm-callback="restartCb">{{lang_restarting}} ldadp</div>
+ <div id="restartfailed" class="alert alert-danger" style="display:none">
+ {{lang_restartFailed}}
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript">
+ function restartCb(task)
+ {
+ if (!task || !task.statusCode)
+ return;
+ if (task.statusCode === 'TASK_ERROR') {
+ $('#restartfailed').show('slow');
+ }
+ }
+</script>
diff --git a/modules-available/locations/config.json b/modules-available/locations/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/locations/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/locations/lang/de/templates/location-subnets.json b/modules-available/locations/lang/de/templates/location-subnets.json
new file mode 100644
index 00000000..6caa1991
--- /dev/null
+++ b/modules-available/locations/lang/de/templates/location-subnets.json
@@ -0,0 +1,18 @@
+{
+ "lang_addNewSubnet": "Neues Subnetz hinzuf\u00fcgen",
+ "lang_assignSubnetExplanation": "Rechner, die in einen der hier aufgef\u00fchrten Adressbereiche fallen, werden diesem Ort zugeschrieben und erhalten damit z.B. f\u00fcr diesen Raum angepasste Veranstaltungslisten.",
+ "lang_assignedSubnets": "Zugeordnete Subnetze bzw. IP-Bereiche",
+ "lang_deleteChildLocations": "Untergeordnete Orte ebenfalls l\u00f6schen",
+ "lang_deleteLocation": "Ort l\u00f6schen",
+ "lang_deleteSubnet": "Bereich l\u00f6schen",
+ "lang_endAddress": "Endadresse",
+ "lang_locationInfo": "Details zu diesem Ort",
+ "lang_locationSettings": "Raum\/Ort bearbeiten",
+ "lang_matchingMachines": "Enthaltene Rechner",
+ "lang_name": "Name",
+ "lang_parentLocation": "\u00dcbergeordneter Ort",
+ "lang_referencingLectures": "Veranstaltungen",
+ "lang_save": "Speichern",
+ "lang_startAddress": "Startadresse",
+ "lang_subnet": "IP-Bereich"
+} \ No newline at end of file
diff --git a/modules-available/locations/lang/de/templates/locations.json b/modules-available/locations/lang/de/templates/locations.json
new file mode 100644
index 00000000..3e25ef45
--- /dev/null
+++ b/modules-available/locations/lang/de/templates/locations.json
@@ -0,0 +1,10 @@
+{
+ "lang_areYouSureNoUndo": "Sind Sie sicher? Diese Aktion kann nicht r\u00fcckg\u00e4ngig gemacht werden.",
+ "lang_edit": "Bearbeiten",
+ "lang_location": "Ort",
+ "lang_locationName": "Name",
+ "lang_locationsMainHeading": "Verwaltung von R\u00e4umen\/Orten",
+ "lang_noParent": "Kein \u00fcbergeordneter Ort",
+ "lang_save": "Speichern",
+ "lang_thisListBySubnet": "Nach Subnetzen auflisten"
+} \ No newline at end of file
diff --git a/modules-available/locations/lang/de/templates/subnets.json b/modules-available/locations/lang/de/templates/subnets.json
new file mode 100644
index 00000000..b57f87ce
--- /dev/null
+++ b/modules-available/locations/lang/de/templates/subnets.json
@@ -0,0 +1,7 @@
+{
+ "lang_endAddress": "Ende",
+ "lang_listOfSubnets": "Liste der Subnetze",
+ "lang_location": "Ort",
+ "lang_startAddress": "Start",
+ "lang_thisListByLocation": "Zur Ortsansicht"
+} \ No newline at end of file
diff --git a/modules-available/locations/lang/en/module.json b/modules-available/locations/lang/en/module.json
new file mode 100644
index 00000000..b2a837b6
--- /dev/null
+++ b/modules-available/locations/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Room\/Locations"
+} \ No newline at end of file
diff --git a/modules-available/locations/lang/en/templates/location-subnets.json b/modules-available/locations/lang/en/templates/location-subnets.json
new file mode 100644
index 00000000..2ba94384
--- /dev/null
+++ b/modules-available/locations/lang/en/templates/location-subnets.json
@@ -0,0 +1,18 @@
+{
+ "lang_addNewSubnet": "Add new subnet",
+ "lang_assignSubnetExplanation": "Client machines which fall into an IP range listed below will be assigned to this location and will see an according lecture list (e.g. they will see lectures that are exclusively assigned to this location).",
+ "lang_assignedSubnets": "Assigned subnets \/ IP ranges",
+ "lang_deleteChildLocations": "Delete child locations aswell",
+ "lang_deleteLocation": "Delete location",
+ "lang_deleteSubnet": "Delete range",
+ "lang_endAddress": "End address",
+ "lang_locationInfo": "Location details",
+ "lang_locationSettings": "Edit this room or location",
+ "lang_matchingMachines": "Matching clients",
+ "lang_name": "Name",
+ "lang_parentLocation": "Parent location",
+ "lang_referencingLectures": "Assigned Lectures",
+ "lang_save": "Save",
+ "lang_startAddress": "Start address",
+ "lang_subnet": "IP range"
+} \ No newline at end of file
diff --git a/modules-available/locations/lang/en/templates/locations.json b/modules-available/locations/lang/en/templates/locations.json
new file mode 100644
index 00000000..db4fd0a7
--- /dev/null
+++ b/modules-available/locations/lang/en/templates/locations.json
@@ -0,0 +1,10 @@
+{
+ "lang_areYouSureNoUndo": "Are you sure? This cannot be undone!",
+ "lang_edit": "Edit",
+ "lang_location": "Ort",
+ "lang_locationName": "Name",
+ "lang_locationsMainHeading": "Manage rooms and locations",
+ "lang_noParent": "No parent",
+ "lang_save": "Save",
+ "lang_thisListBySubnet": "List by subnet"
+} \ No newline at end of file
diff --git a/modules-available/locations/lang/en/templates/subnets.json b/modules-available/locations/lang/en/templates/subnets.json
new file mode 100644
index 00000000..65da254b
--- /dev/null
+++ b/modules-available/locations/lang/en/templates/subnets.json
@@ -0,0 +1,7 @@
+{
+ "lang_endAddress": "End",
+ "lang_listOfSubnets": "List of subnets",
+ "lang_location": "Location",
+ "lang_startAddress": "Start",
+ "lang_thisListByLocation": "List by location"
+} \ No newline at end of file
diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php
new file mode 100644
index 00000000..60af719b
--- /dev/null
+++ b/modules-available/locations/page.inc.php
@@ -0,0 +1,348 @@
+<?php
+
+class Page_Locations extends Page
+{
+
+ private $action;
+
+ /*
+ * Action handling
+ */
+
+ protected function doPreprocess()
+ {
+ User::load();
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ $this->action = Request::post('action');
+ if ($this->action === 'updatelocation') {
+ $this->updateLocation();
+ } elseif ($this->action === 'addlocations') {
+ $this->addLocations();
+ }
+ }
+
+ private function addLocations()
+ {
+ $names = Request::post('newlocation', false);
+ $parents = Request::post('newparent', false);
+ if (!is_array($names) || !is_array($parents)) {
+ Message::addError('empty-field');
+ Util::redirect('?do=Locations');
+ }
+ $locs = Location::getLocations();
+ $count = 0;
+ foreach ($names as $idx => $name) {
+ $name = trim($name);
+ if (empty($name)) continue;
+ $parent = isset($parents[$idx]) ? (int)$parents[$idx] : 0;
+ if ($parent !== 0) {
+ $ok = false;
+ foreach ($locs as $loc) {
+ if ($loc['locationid'] == $parent) {
+ $ok = true;
+ }
+ }
+ if (!$ok) {
+ Message::addWarning('value-invalid', 'parentlocationid', $parent);
+ continue;
+ }
+ }
+ Database::exec("INSERT INTO location (parentlocationid, locationname)"
+ . " VALUES (:parent, :name)", array(
+ 'parent' => $parent,
+ 'name' => $name
+ ));
+ $count++;
+ }
+ Message::addSuccess('added-x-entries', $count);
+ Util::redirect('?do=Locations');
+ }
+
+ private function updateLocation()
+ {
+ $locationId = Request::post('locationid', false, 'integer');
+ $del = Request::post('deletelocation', false, 'integer');
+ if ($locationId === false) {
+ Message::addError('parameter-missing', 'locationid');
+ Util::redirect('?do=Locations');
+ }
+ $location = Database::queryFirst('SELECT locationid, parentlocationid, locationname FROM location'
+ . ' WHERE locationid = :lid', array('lid' => $locationId));
+ if ($location === false) {
+ Message::addError('value-invalid', 'locationid', $locationId);
+ Util::redirect('?do=Locations');
+ }
+ // Delete location?
+ if ($locationId === $del) {
+ $this->deleteLocation($location);
+ }
+ // Update subnets
+ $this->updateLocationSubnets($location);
+ // Insert subnets
+ $this->addNewLocationSubnets($location); // TODO
+ // Update location!
+ $this->updateLocationData($location);
+ Util::redirect('?do=Locations');
+ }
+
+ private function deleteLocation($location)
+ {
+ $locationId = (int)$location['locationid'];
+ $ids = $locationId;
+ if (Request::post('recursive', false) === 'on') {
+ $rows = Location::queryLocations();
+ $rows = Location::buildTree($rows, $locationId);
+ $rows = Location::extractIds($rows);
+ if (!empty($rows)) {
+ $ids .= ',' . implode(',', $rows);
+ }
+ }
+ $subs = Database::exec("DELETE FROM subnet WHERE locationid IN ($ids)");
+ $locs = Database::exec("DELETE FROM location WHERE locationid IN ($ids)");
+ Database::exec('UPDATE location SET parentlocationid = :newparent WHERE parentlocationid = :oldparent', array(
+ 'newparent' => $location['parentlocationid'],
+ 'oldparent' => $location['locationid']
+ ));
+ Message::addSuccess('location-deleted', $locs, $subs);
+ Util::redirect('?do=Locations');
+ }
+
+ private function updateLocationData($location)
+ {
+ $locationId = (int)$location['locationid'];
+ $newParent = Request::post('parentlocationid', false, 'integer');
+ $newName = Request::post('locationname', false, 'string');
+ if ($newName === false || preg_match('/^\s*$/', $newName)) {
+ if ($newName !== false) {
+ Message::addWarning('value-invalid', 'location name', $newName);
+ }
+ $newName = $location['locationname'];
+ }
+ if ($newParent === false) {
+ $newParent = $location['parentlocationid'];
+ } else if ($newParent !== 0) {
+ $rows = Location::queryLocations();
+ $all = Location::extractIds(Location::buildTree($rows));
+ if (!in_array($newParent, $all) || $newParent === $locationId) {
+ Message::addWarning('value-invalid', 'parent', $newParent);
+ $newParent = $location['parentlocationid'];
+ } else {
+ $rows = Location::extractIds(Location::buildTree($rows, $locationId));
+ if (in_array($newParent, $rows)) {
+ Message::addWarning('value-invalid', 'parent', $newParent);
+ $newParent = $location['parentlocationid'];
+ }
+ }
+ }
+ $ret = Database::exec('UPDATE location SET parentlocationid = :parent, locationname = :name'
+ . ' WHERE locationid = :lid', array(
+ 'lid' => $locationId,
+ 'parent' => $newParent,
+ 'name' => $newName
+ ));
+ if ($ret > 0) {
+ Message::addSuccess('location-updated', $newName);
+ }
+ }
+
+ private function updateLocationSubnets($location)
+ {
+ $locationId = (int)$location['locationid'];
+ // Deletion first
+ $dels = Request::post('deletesubnet', false);
+ if (is_array($dels)) {
+ $count = 0;
+ $stmt = Database::prepare('DELETE FROM subnet WHERE subnetid = :id');
+ foreach ($dels as $key => $value) {
+ if (!is_numeric($key) || $value !== 'on') continue;
+ if ($stmt->execute(array('id' => $key))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ if ($count > 0) {
+ Message::addInfo('subnets-deleted', $count);
+ }
+ }
+ // Now actual updates
+ // TODO: Warn on mismatch/overlap (should lie entirely in parent's subnet, not overlap with others)
+ $starts = Request::post('startaddr', false);
+ $ends = Request::post('endaddr', false);
+ if (!is_array($starts) || !is_array($ends)) {
+ return;
+ }
+ $count = 0;
+ $stmt = Database::prepare('UPDATE subnet SET startaddr = :start, endaddr = :end'
+ . ' WHERE subnetid = :id');
+ foreach ($starts as $key => $start) {
+ if (!isset($ends[$key]) || !is_numeric($key)) continue;
+ $end = $ends[$key];
+ list($startLong, $endLong) = $this->rangeToLong($start, $end);
+ if ($startLong === false) {
+ Message::addWarning('value-invalid', 'start addr', $start);
+ }
+ if ($endLong === false) {
+ Message::addWarning('value-invalid', 'end addr', $start);
+ }
+ if ($startLong === false || $endLong === false) continue;
+ if ($startLong > $endLong) {
+ Message::addWarning('value-invalid', 'range', $start . ' - ' . $end);
+ continue;
+ }
+ if ($stmt->execute(array('id' => $key, 'start' => $startLong, 'end' => $endLong))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ if ($count > 0) {
+ Message::addInfo('subnets-updated', $count);
+ }
+ }
+
+ private function addNewLocationSubnets($location)
+ {
+ $locationId = (int)$location['locationid'];
+ $starts = Request::post('newstartaddr', false);
+ $ends = Request::post('newendaddr', false);
+ if (!is_array($starts) || !is_array($ends)) {
+ return;
+ }
+ $count = 0;
+ $stmt = Database::prepare('INSERT INTO subnet SET startaddr = :start, endaddr = :end, locationid = :location');
+ foreach ($starts as $key => $start) {
+ if (!isset($ends[$key]) || !is_numeric($key)) continue;
+ $end = $ends[$key];
+ list($startLong, $endLong) = $this->rangeToLong($start, $end);
+ if ($startLong === false) {
+ Message::addWarning('value-invalid', 'new start addr', $start);
+ }
+ if ($endLong === false) {
+ Message::addWarning('value-invalid', 'new end addr', $start);
+ }
+ if ($startLong === false || $endLong === false) continue;
+ if ($startLong > $endLong) {
+ Message::addWarning('value-invalid', 'range', $start . ' - ' . $end);
+ continue;
+ }
+ if ($stmt->execute(array('location' => $locationId, 'start' => $startLong, 'end' => $endLong))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ if ($count > 0) {
+ Message::addInfo('subnets-created', $count);
+ }
+ }
+
+ /*
+ * Rendering normal pages
+ */
+
+ protected function doRender()
+ {
+ //Render::setTitle(Dictionary::translate('lang_titleBackup'));
+ $getAction = Request::get('action');
+ if (empty($getAction)) {
+ // Until we have a main landing page?
+ Util::redirect('?do=Locations&action=showlocations');
+ }
+ if ($getAction === 'showsubnets') {
+ $res = Database::simpleQuery("SELECT subnetid, startaddr, endaddr, locationid FROM subnet");
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['startaddr'] = long2ip($row['startaddr']);
+ $row['endaddr'] = long2ip($row['endaddr']);
+ $row['locations'] = Location::getLocations($row['locationid']);
+ $rows[] = $row;
+ }
+ Render::addTemplate('subnets', array('list' => $rows));
+ } elseif ($getAction === 'showlocations') {
+ $locs = Location::getLocations();
+ Render::addTemplate('locations', array('list' => $locs));
+ }
+ }
+
+ /*
+ * Ajax
+ */
+
+ protected function doAjax()
+ {
+ User::load();
+ if (!User::isLoggedIn()) {
+ die('Unauthorized');
+ }
+ $action = Request::any('action');
+ if ($action === 'showlocation') {
+ $this->ajaxShowLocation();
+ }
+ }
+
+ private function ajaxShowLocation()
+ {
+ $locationId = Request::any('locationid', 0, 'integer');
+ $loc = Database::queryFirst('SELECT locationid, parentlocationid, locationname FROM location WHERE locationid = :lid',
+ array('lid' => $locationId));
+ if ($loc === false) {
+ die('Unknown locationid');
+ }
+ $res = Database::simpleQuery("SELECT subnetid, startaddr, endaddr FROM subnet WHERE locationid = :lid",
+ array('lid' => $locationId));
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['startaddr'] = long2ip($row['startaddr']);
+ $row['endaddr'] = long2ip($row['endaddr']);
+ $rows[] = $row;
+ }
+ $data = array(
+ 'locationid' => $loc['locationid'],
+ 'locationname' => $loc['locationname'],
+ 'list' => $rows,
+ 'parents' => Location::getLocations($loc['parentlocationid'], $locationId, true)
+ );
+ // if (moduleEnabled(DOZMOD) {
+ $lectures = Database::queryFirst('SELECT Count(*) AS cnt FROM sat.lecture l '
+ . ' INNER JOIN sat.lecture_x_location ll ON (l.lectureid = ll.lectureid AND ll.locationid = :lid)',
+ array('lid' => $locationId));
+ $data['lectures'] = $lectures['cnt'];
+ // }
+ // Get clients matching this location's subnet(s)
+ $mres = Database::simpleQuery("SELECT lastseen, logintime FROM machine"
+ . " INNER JOIN subnet ON (INET_ATON(machine.clientip) BETWEEN startaddr AND endaddr)"
+ . " WHERE subnet.locationid = :lid OR machine.locationid = :lid", array('lid' => $locationId));
+ $count = $online = $used = 0;
+ $DL = time() - 605;
+ while ($row = $mres->fetch(PDO::FETCH_ASSOC)) {
+ $count++;
+ if ($row['lastseen'] > $DL) {
+ $online++;
+ if ($row['logintime'] != 0) {
+ $used++;
+ }
+ }
+ }
+ $data['machines'] = $count;
+ $data['machines_online'] = $online;
+ $data['machines_used'] = $used;
+ $data['used_percent'] = round(100 * $used / $online);
+ echo Render::parse('location-subnets', $data);
+ }
+
+ /*
+ * Helpers
+ */
+
+ private function rangeToLong($start, $end)
+ {
+ $startLong = ip2long($start);
+ $endLong = ip2long($end);
+ if ($startLong !== false) {
+ $startLong = sprintf("%u", $startLong);
+ }
+ if ($endLong !== false) {
+ $endLong = sprintf("%u", $endLong);
+ }
+ return array($startLong, $endLong);
+ }
+
+}
diff --git a/modules-available/locations/templates/location-subnets.html b/modules-available/locations/templates/location-subnets.html
new file mode 100644
index 00000000..76b7442a
--- /dev/null
+++ b/modules-available/locations/templates/location-subnets.html
@@ -0,0 +1,73 @@
+<div class="slx-well">
+ <div class="slx-bold">{{lang_locationSettings}}</div>
+ <form method="post" action="?do=Locations">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="updatelocation">
+ <input type="hidden" name="locationid" value="{{locationid}}">
+ <div style="display:none">
+ <button type="submit" class="btn btn-primary">Save</button>
+ </div>
+ <div class="row">
+ <div class="col-sm-6">
+ <div class="input-group">
+ <span class="input-group-addon slx-ga2">{{lang_name}}</span>
+ <input class="form-control" type="text" name="locationname" value="{{locationname}}" pattern=".*\S.*">
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="input-group">
+ <span class="input-group-addon slx-ga2">{{lang_parentLocation}}</span>
+ <select class="form-control" name="parentlocationid">
+ {{#parents}}
+ <option value="{{locationid}}" {{#selected}}selected="selected"{{/selected}}>{{locationpad}} {{locationname}}</option>
+ {{/parents}}
+ </select>
+ </div>
+ </div>
+ </div>
+ <div>
+ <div class="pull-right">
+ <label><input type="checkbox" name="recursive" value="on"> {{lang_deleteChildLocations}}</label>
+ <button type="submit" class="btn btn-sm btn-danger" name="deletelocation" value="{{locationid}}" onclick="return slxConfirm()">{{lang_deleteLocation}}</button>
+ </div>
+ <div class="clearfix"></div>
+ </div>
+ <br>
+ <div class="slx-bold">{{lang_assignedSubnets}}</div>
+ <div><i>{{lang_assignSubnetExplanation}}</i></div>
+ <table class="table table-condensed table-striped">
+ <tr>
+ <th>#</th>
+ <th>{{lang_startAddress}}</th>
+ <th>{{lang_endAddress}}</th>
+ <th title="{{lang_deleteSubnet}}"><span class="glyphicon glyphicon-trash"></span></th>
+ </tr>
+ {{#list}}
+ <tr>
+ <td>{{subnetid}}</td>
+ <td><input class="form-control" type="text" name="startaddr[{{subnetid}}]" value="{{startaddr}}" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"></td>
+ <td><input class="form-control" type="text" name="endaddr[{{subnetid}}]" value="{{endaddr}}" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"></td>
+ <td class="danger" align="center"><input type="checkbox" name="deletesubnet[{{subnetid}}]" value="on"></td>
+ </tr>
+ {{/list}}
+ <tr id="loc-sub-{{locationid}}">
+ <td colspan="2">
+ <button class="btn btn-success btn-sm" type="button" onclick="slxAddSubnetRow(this, {{locationid}})" title="{{lang_addNewSubnet}}">
+ <span class="glyphicon glyphicon-plus-sign"></span> {{lang_subnet}}
+ </button>
+ </td>
+ <td colspan="2" align="right">
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ </div>
+ </tr>
+ </table>
+ </form>
+ <br>
+ <div class="slx-bold">{{lang_locationInfo}}</div>
+ <div>
+ <span class="slx-ga2">{{lang_referencingLectures}}:</span> {{lectures}}
+ </div>
+ <div>
+ <span class="slx-ga2">{{lang_matchingMachines}}:</span> <a href="?do=Statistics&amp;filter=location&amp;argument={{locationid}}">{{machines}} / {{machines_online}} / {{machines_used}} ({{used_percent}}%)</a>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/locations/templates/locations.html b/modules-available/locations/templates/locations.html
new file mode 100644
index 00000000..76c8f97c
--- /dev/null
+++ b/modules-available/locations/templates/locations.html
@@ -0,0 +1,96 @@
+<div>
+ <div class="pull-right">
+ <a href="?do=Locations&amp;action=showsubnets">{{lang_thisListBySubnet}}</a>
+ </div>
+ <h1>{{lang_locationsMainHeading}}</h1>
+ <table class="table table-condensed" style="margin-bottom:0px">
+ <tr>
+ <th>#</th>
+ <th width="100%">{{lang_locationName}}</th>
+ <th></th>
+ </tr>
+ {{#list}}
+ <tr>
+ <td>{{locationid}}</td>
+ <td><div style="display:inline-block;width:{{depth}}em"></div>{{locationname}}</td>
+ <td align="right">
+ <a class="btn btn-success btn-xs" onclick="slxOpenLocation(this, {{locationid}})"><span class="glyphicon glyphicon-edit"></span> {{lang_edit}}</a>
+ </td>
+ </tr>
+ {{/list}}
+ </table>
+ <form method="post" action="?do=Locations">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="addlocations">
+ <table class="table table-condensed table-hover">
+ <tr id="lasttr">
+ <td>
+ <button class="btn btn-success btn-sm" type="button" onclick="slxAddLocationRow()">
+ <span class="glyphicon glyphicon-plus-sign"></span> {{lang_location}}
+ </button>
+ </td>
+ <td width="80%">&emsp;</td>
+ <td width="20%" align="right">
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ </td>
+ </tr>
+ </table>
+ </form>
+</div>
+<script type="text/javascript"><!--
+var slxAddCounter = 0;
+var slxLastLocation = false;
+
+function slxAddLocationRow() {
+ var tr = $('#lasttr');
+ tr.before('<tr>\
+ <td>#</td>\
+ <td><input class="form-control" type="text" name="newlocation[' + slxAddCounter + ']" placeholder="{{lang_locationName}}" pattern=".*\\S.*"></td>\
+ <td><select class="form-control" name="newparent[' + slxAddCounter + ']">\
+ <option value="0">{{lang_noParent}}</option>\
+ {{#list}}<option value="{{locationid}}">{{locationpad}} {{locationname}}</option>{{/list}}\
+ </select></td>\
+ </tr>');
+ slxAddCounter++;
+}
+
+function slxOpenLocation(e, lid) {
+ if (slxLastLocation !== false) {
+ slxLastLocation.hide();
+ $(slxLastLocation).prev().removeClass('active slx-bold');
+ }
+ var existing = $('#location-details-' + lid);
+ if (existing.length > 0) {
+ if (existing.is(slxLastLocation)) {
+ slxLastLocation = false;
+ } else {
+ existing.show();
+ $(e).closest('tr').addClass('active slx-bold');
+ slxLastLocation = existing;
+ }
+ return;
+ }
+ var td = $('<td>').attr('colspan', '12').css('padding', '0px 0px 12px');
+ var tr = $('<tr>').attr('id', 'location-details-' + lid);
+ tr.append(td);
+ $(e).closest('tr').addClass('active slx-bold').after(tr);
+ td.load('?do=Locations&action=showlocation&locationid=' + lid);
+ slxLastLocation = tr;
+}
+
+function slxAddSubnetRow(e, lid) {
+ var tr = $('#loc-sub-' + lid);
+ tr.before('<tr>\
+ <td>#</td>\
+ <td><input class="form-control" type="text" name="newstartaddr[' + slxAddCounter + ']" pattern="\\d{1,3}\.\\d{1,3}\.\\d{1,3}\.\\d{1,3}"></td>\
+ <td><input class="form-control" type="text" name="newendaddr[' + slxAddCounter + ']" pattern="\\d{1,3}\.\\d{1,3}\.\\d{1,3}\.\\d{1,3}"></td>\
+ <td></td>\
+ </tr>');
+ slxAddCounter++;
+}
+
+function slxConfirm() {
+ return confirm('{{lang_areYouSureNoUndo}}');
+}
+ // -->
+</script>
diff --git a/modules-available/locations/templates/subnets.html b/modules-available/locations/templates/subnets.html
new file mode 100644
index 00000000..2294f42b
--- /dev/null
+++ b/modules-available/locations/templates/subnets.html
@@ -0,0 +1,35 @@
+<div>
+ <div class="pull-right">
+ <a href="?do=Locations&amp;action=showlocations">{{lang_thisListByLocation}}</a>
+ </div>
+ <h1>{{lang_listOfSubnets}}</h1>
+ <form method="post" action="?do=Locations">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="updatesubnets">
+ <table class="table table-condensed table-striped">
+ <tr>
+ <th>#</th>
+ <th>{{lang_startAddress}}</th>
+ <th>{{lang_endAddress}}</th>
+ <th>{{lang_location}}</th>
+ </tr>
+ {{#list}}
+ <tr>
+ <td>{{subnetid}}</td>
+ <td><input class="form-control" type="text" name="startaddr[{{subnetid}}]" value="{{startaddr}}"></td>
+ <td><input class="form-control" type="text" name="endaddr[{{subnetid}}]" value="{{endaddr}}"></td>
+ <td>
+ <select class="form-control" name="location[{{subnetid}}]">
+ {{#locations}}
+ <option value="{{locationid}}" {{#selected}}selected="selected"{{/selected}}>{{locationpad}} {{locationname}}</option>
+ {{/locations}}
+ </select>
+ </td>
+ </tr>
+ {{/list}}
+ </table>
+ <div>
+ <button type="submit" class="btn btn-primary">Späschohn (geht noch nicht!)</button>
+ </div>
+ </form>
+</div>
diff --git a/modules-available/main/category-icons.json b/modules-available/main/category-icons.json
new file mode 100644
index 00000000..97b76eac
--- /dev/null
+++ b/modules-available/main/category-icons.json
@@ -0,0 +1,7 @@
+{
+ "cities":"tower",
+ "content":"th",
+ "settings":"cog",
+ "status":"tasks",
+ "users":"user"
+} \ No newline at end of file
diff --git a/modules-available/main/config.json b/modules-available/main/config.json
new file mode 100644
index 00000000..4da67ef8
--- /dev/null
+++ b/modules-available/main/config.json
@@ -0,0 +1,3 @@
+{
+ "enabled":"true"
+}
diff --git a/modules-available/main/lang/de/module.json b/modules-available/main/lang/de/module.json
new file mode 100644
index 00000000..f03e52ad
--- /dev/null
+++ b/modules-available/main/lang/de/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "Dashboard",
+ "page_title": "Startseite"
+}
diff --git a/modules-available/main/lang/de/templates/dialog-generic.json b/modules-available/main/lang/de/templates/dialog-generic.json
new file mode 100644
index 00000000..ff429fdd
--- /dev/null
+++ b/modules-available/main/lang/de/templates/dialog-generic.json
@@ -0,0 +1,3 @@
+{
+ "lang_next": "Weiter"
+} \ No newline at end of file
diff --git a/modules-available/main/lang/de/templates/main-menu.json b/modules-available/main/lang/de/templates/main-menu.json
new file mode 100644
index 00000000..057bfcf2
--- /dev/null
+++ b/modules-available/main/lang/de/templates/main-menu.json
@@ -0,0 +1,22 @@
+{
+ "lang_configurationBasic": "PXE\/Boot",
+ "lang_configurationVariables": "KonfigurationsVariablen",
+ "lang_dozmod": "Dozentenmodul",
+ "lang_eventLog": "Server Log",
+ "lang_internetAccess": "Internetzugriff",
+ "lang_language": "Sprachen",
+ "lang_localization": "Lokalisierung + Integration",
+ "lang_locations": "R\u00e4ume\/Orte",
+ "lang_login": "Anmelden",
+ "lang_logout": "Abmelden",
+ "lang_needsSetup": "Einrichtung unvollst\u00e4ndig",
+ "lang_news": "vmChooser News",
+ "lang_server": "Server",
+ "lang_serverStatus": "Server Status",
+ "lang_settings": "Einstellungen",
+ "lang_status": "Status",
+ "lang_translations": "\u00dcbersetzungen",
+ "lang_vmLocation": "VM Speicherort",
+ "lang_warning": "Warnung",
+ "lang_webInterface": "Web-Schnittstelle"
+}
diff --git a/modules-available/main/lang/de/templates/messagebox-error.json b/modules-available/main/lang/de/templates/messagebox-error.json
new file mode 100644
index 00000000..c44dc44f
--- /dev/null
+++ b/modules-available/main/lang/de/templates/messagebox-error.json
@@ -0,0 +1,3 @@
+[
+
+] \ No newline at end of file
diff --git a/modules-available/main/lang/de/templates/messagebox-info.json b/modules-available/main/lang/de/templates/messagebox-info.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/modules-available/main/lang/de/templates/messagebox-info.json
@@ -0,0 +1 @@
+{}
diff --git a/modules-available/main/lang/de/templates/messagebox-success.json b/modules-available/main/lang/de/templates/messagebox-success.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/modules-available/main/lang/de/templates/messagebox-success.json
@@ -0,0 +1 @@
+{}
diff --git a/modules-available/main/lang/de/templates/messagebox-warning.json b/modules-available/main/lang/de/templates/messagebox-warning.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/modules-available/main/lang/de/templates/messagebox-warning.json
@@ -0,0 +1 @@
+{}
diff --git a/modules-available/main/lang/de/templates/page-main-guest.json b/modules-available/main/lang/de/templates/page-main-guest.json
new file mode 100644
index 00000000..876fccac
--- /dev/null
+++ b/modules-available/main/lang/de/templates/page-main-guest.json
@@ -0,0 +1,7 @@
+{
+ "lang_introGuest": "Dies ist das Administrations-Interface der lokalen bwLehrpool-Installation. Bitte authentifizieren Sie sich, um Einstellungen vorzunehmen.",
+ "lang_login": "Anmelden",
+ "lang_noExistingAccount": "Es existiert noch kein Administrator-Zugang f\u00fcr diesen Satelliten-Server.",
+ "lang_register": "Registrieren",
+ "lang_welcome": "Willkommen"
+} \ No newline at end of file
diff --git a/modules-available/main/lang/de/templates/page-main.json b/modules-available/main/lang/de/templates/page-main.json
new file mode 100644
index 00000000..a927e4ce
--- /dev/null
+++ b/modules-available/main/lang/de/templates/page-main.json
@@ -0,0 +1,11 @@
+{
+ "lang_bootMenuWarning": "Das Bootmen\u00fc ist veraltet oder wurde noch nicht generiert.",
+ "lang_configure": "Konfigurieren",
+ "lang_intro": "Dies ist die bwLehrpool Konfigurationsoberfl\u00e4che.",
+ "lang_minilinuxMissing": "Wichtige Dateien der MiniLinux-Installation fehlen.",
+ "lang_numerOfImagesMarkedForDeletion": "Zur L\u00f6schung markierte Abbilder",
+ "lang_systemConfiguration": "Systemkonfiguration",
+ "lang_systemConfigurationNotChosen": "Es wurde noch keine Systemkonfiguration ausgew\u00e4hlt.",
+ "lang_vmLocationNotSet": "Es ist noch kein Speicherort f\u00fcr die Virtuellen Maschinen festgelegt.",
+ "lang_welcome": "Willkommen"
+} \ No newline at end of file
diff --git a/modules-available/main/lang/en/categories.json b/modules-available/main/lang/en/categories.json
new file mode 100644
index 00000000..3d67bcfd
--- /dev/null
+++ b/modules-available/main/lang/en/categories.json
@@ -0,0 +1,6 @@
+{
+ "settings": "Settings",
+ "status": "Status",
+ "content": "Content",
+ "users": "Users"
+}
diff --git a/modules-available/main/lang/en/module.json b/modules-available/main/lang/en/module.json
new file mode 100644
index 00000000..613213d9
--- /dev/null
+++ b/modules-available/main/lang/en/module.json
@@ -0,0 +1,13 @@
+{
+ "lang_intro": "Esta \u00e9 a interface de configura\u00e7\u00e3o do OpenSLX.",
+ "lang_introGuest": "This is the administration interface of the local bwLehrpool intallation. Please authenticate yourself to adjust settings.",
+ "lang_language": "Language",
+ "lang_login": "Login",
+ "lang_logout": "Logout",
+ "lang_needsSetup": "Setup incomplete",
+ "lang_next": "Next",
+ "lang_noExistingAccount": "No account has been created yet. Sign up to become the administrator.",
+ "lang_register": "Register",
+ "lang_warning": "Warning",
+ "lang_welcome": "Welcome"
+} \ No newline at end of file
diff --git a/modules-available/main/lang/en/templates/dialog-generic.json b/modules-available/main/lang/en/templates/dialog-generic.json
new file mode 100644
index 00000000..c7551ed3
--- /dev/null
+++ b/modules-available/main/lang/en/templates/dialog-generic.json
@@ -0,0 +1,3 @@
+{
+ "lang_next": "Next"
+} \ No newline at end of file
diff --git a/modules-available/main/lang/en/templates/main-menu.json b/modules-available/main/lang/en/templates/main-menu.json
new file mode 100644
index 00000000..635c7aa8
--- /dev/null
+++ b/modules-available/main/lang/en/templates/main-menu.json
@@ -0,0 +1,28 @@
+{
+ "lang_backup": "Backup\/Restore",
+ "lang_client": "Client",
+ "lang_clientLog": "Client Log",
+ "lang_clientStats": "Client statistics",
+ "lang_configurationBasic": "PXE\/Boot",
+ "lang_configurationVariables": "Configuration Variables",
+ "lang_dozmod": "Tutor module",
+ "lang_eventLog": "Server Log",
+ "lang_internetAccess": "Internet access",
+ "lang_language": "Language",
+ "lang_localization": "Localization",
+ "lang_locations": "Rooms\/Locations",
+ "lang_login": "Login",
+ "lang_logout": "Logout",
+ "lang_needsSetup": "Setup incomplete",
+ "lang_news": "vmChooser news",
+ "lang_server": "Server",
+ "lang_serverStatus": "Server status",
+ "lang_settings": "Settings",
+ "lang_status": "Status",
+ "lang_translations": "Translations",
+ "lang_vmLocation": "VM Location",
+ "lang_warning": "Warning",
+ "lang_webInterface": "Web interface",
+ "lang_loggedInPrefix": "Logged in as",
+ "lang_loggedInSuffix": " "
+}
diff --git a/modules-available/main/lang/en/templates/messagebox-warning.json b/modules-available/main/lang/en/templates/messagebox-warning.json
new file mode 100644
index 00000000..2c63c085
--- /dev/null
+++ b/modules-available/main/lang/en/templates/messagebox-warning.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/modules-available/main/lang/en/templates/page-main-guest.json b/modules-available/main/lang/en/templates/page-main-guest.json
new file mode 100644
index 00000000..6526f5bd
--- /dev/null
+++ b/modules-available/main/lang/en/templates/page-main-guest.json
@@ -0,0 +1,7 @@
+{
+ "lang_introGuest": "This is the administration interface of the local bwLehrpool intallation. Please authenticate yourself to adjust settings.",
+ "lang_login": "Login",
+ "lang_noExistingAccount": "No account has been created yet. Sign up to become the administrator.",
+ "lang_register": "Register",
+ "lang_welcome": "Welcome"
+} \ No newline at end of file
diff --git a/modules-available/main/lang/en/templates/page-main.json b/modules-available/main/lang/en/templates/page-main.json
new file mode 100644
index 00000000..8031ac1c
--- /dev/null
+++ b/modules-available/main/lang/en/templates/page-main.json
@@ -0,0 +1,11 @@
+{
+ "lang_bootMenuWarning": "The boot menu is outdated or has not been generated.",
+ "lang_configure": "Configure",
+ "lang_intro": "This is the bwLehrpool configuration interface.",
+ "lang_minilinuxMissing": "Important files from the mini Linux installation are missing.",
+ "lang_numerOfImagesMarkedForDeletion": "Images marked for deletion",
+ "lang_systemConfiguration": "System Configuration",
+ "lang_systemConfigurationNotChosen": "A system configuration has not been chosen yet.",
+ "lang_vmLocationNotSet": "A location for the virtual machine is not set yet.",
+ "lang_welcome": "Welcome"
+} \ No newline at end of file
diff --git a/modules-available/main/lang/pt/module.json b/modules-available/main/lang/pt/module.json
new file mode 100644
index 00000000..e4e35acd
--- /dev/null
+++ b/modules-available/main/lang/pt/module.json
@@ -0,0 +1,14 @@
+{
+ "lang_intro": "Esta \u00e9 a interface de configura\u00e7\u00e3o do OpenSLX.",
+ "lang_introGuest": "Esta \u00e9 a interface de administra\u00e7\u00e3o da instala\u00e7\u00e3o local do bwLehrpool. Por favor, autentique-se para ajustar op\u00e7\u00f5es.",
+ "lang_language": "L\u00edngua",
+ "lang_login": "Entrar",
+ "lang_logout": "Sair",
+ "lang_needsSetup": "Instala\u00e7\u00e3o incompleta",
+ "lang_next": "Pr\u00f3ximo",
+ "lang_noExistingAccount": "Nenhuma conta foi criada ainda. Registre-se para se tornar administrador.",
+ "lang_register": "Registrar",
+ "lang_translations": "Tradu\u00e7\u00f5es",
+ "lang_warning": "Aten\u00e7\u00e3o",
+ "lang_welcome": "Bem-vindo"
+}
diff --git a/modules-available/main/page.inc.php b/modules-available/main/page.inc.php
new file mode 100644
index 00000000..369d4b54
--- /dev/null
+++ b/modules-available/main/page.inc.php
@@ -0,0 +1,64 @@
+<?php
+
+class Page_Main extends Page
+{
+
+ private $sysconfig;
+ private $minilinux;
+ private $vmstore;
+ private $ipxe;
+ private $delPending;
+
+ protected function doPreprocess()
+ {
+ User::load();
+ if (User::isLoggedIn()) {
+ $this->sysconfig = !file_exists(CONFIG_HTTP_DIR . '/default/config.tgz');
+ $this->minilinux = !file_exists(CONFIG_HTTP_DIR . '/default/kernel') || !file_exists(CONFIG_HTTP_DIR . '/default/initramfs-stage31') || !file_exists(CONFIG_HTTP_DIR . '/default/stage32.sqfs');
+ $this->vmstore = !is_array(Property::getVmStoreConfig());
+ $this->ipxe = !preg_match('/^\d+\.\d+\.\d+\.\d+$/', Property::getServerIp());
+ Property::setNeedsSetup(($this->sysconfig || $this->minilinux || $this->vmstore || $this->ipxe) ? 1 : 0);
+ $res = Database::queryFirst("SELECT Count(*) AS cnt FROM sat.imageversion WHERE deletestate = 'SHOULD_DELETE'", array(), true);
+ $this->delPending = isset($res['cnt']) ? $res['cnt'] : 0;
+ }
+ }
+
+ protected function doRender()
+ {
+ // Render::setTitle('abc');
+
+ if (!User::isLoggedIn()) {
+ Render::addTemplate('page-main-guest', array(
+ 'register' => (Database::queryFirst('SELECT userid FROM user LIMIT 1') === false)
+ ));
+ return;
+ }
+ // Logged in here
+
+ // Load news
+ $lines = array();
+ $paginate = new Paginate("SELECT newsid, dateline, title, content FROM news ORDER BY dateline DESC", 10);
+ $res = $paginate->exec();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if(count($lines) >= 3) break;
+ $lines[] = $row;
+ }
+
+ Render::addTemplate('page-main', array(
+ 'user' => User::getName(),
+ 'sysconfig' => $this->sysconfig,
+ 'minilinux' => $this->minilinux,
+ 'vmstore' => $this->vmstore,
+ 'ipxe' => $this->ipxe,
+ 'delpending' => $this->delPending,
+ 'news' => $lines
+ ));
+ }
+
+ protected function doAjax()
+ {
+ User::isLoggedIn();
+ die('Status: DB running');
+ }
+
+}
diff --git a/modules-available/main/templates/dialog-generic.html b/modules-available/main/templates/dialog-generic.html
new file mode 100644
index 00000000..5face8ce
--- /dev/null
+++ b/modules-available/main/templates/dialog-generic.html
@@ -0,0 +1,13 @@
+<div class="modal-dialog slx-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h4 class="modal-title">{{title}}</h4>
+ </div>
+ <div class="modal-body">
+ {{{body}}}
+ </div>
+ <div class="modal-footer">
+ {{#next}}<a class="btn btn-primary" href="{{next}}">{{lang_next}} &raquo;</a>{{/next}}
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/main/templates/footer.html b/modules-available/main/templates/footer.html
new file mode 100644
index 00000000..8cf71a5c
--- /dev/null
+++ b/modules-available/main/templates/footer.html
@@ -0,0 +1,2 @@
+<hr>
+<div class="pull-right slx-footer">{{text}}</div> \ No newline at end of file
diff --git a/modules-available/main/templates/main-menu.html b/modules-available/main/templates/main-menu.html
new file mode 100644
index 00000000..65085ee6
--- /dev/null
+++ b/modules-available/main/templates/main-menu.html
@@ -0,0 +1,72 @@
+<div class="slx-topbar">
+ <div>
+ {{#dbupdate}}
+ <a href="api.php?do=update"><span class="slx-warning-badge badge"><span class="glyphicon glyphicon-exclamation-sign"></span> DB-Update</span></a>
+ {{/dbupdate}}
+ {{#warning}}
+ <a href="?do=EventLog"><span class="slx-warning-badge badge"><span class="glyphicon glyphicon-exclamation-sign"></span> {{lang_warning}}</span></a>
+ {{/warning}}
+ {{#needsSetup}}
+ <a href="?do=Main"><span class="slx-warning-badge badge"><span class="glyphicon glyphicon-exclamation-sign"></span> {{lang_needsSetup}}</span></a>
+ {{/needsSetup}}
+ </div>
+</div>
+<div class="sidebar-bg"></div>
+<nav class="navbar navbar-inverse sidebar" role="navigation">
+ <div class="container-fluid">
+ <!-- Brand and toggle get grouped for better mobile display -->
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-sidebar-navbar-collapse-1">
+ <span class="sr-only">{{lang_toggleNavigation}}</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="#">OpenSLX</a>
+ </div>
+ <!-- Collect the nav links, forms, and other content for toggling -->
+ <div class="collapse navbar-collapse" id="bs-sidebar-navbar-collapse-1">
+ <ul class="nav navbar-nav">
+ {{#categories}}
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle slx-nopointer" data-toggle="dropdown"><span class="sidebar-visible-inline {{icon}}"></span> {{displayName}}<b class="caret sidebar-hide"></b></a>
+ <ul class="dropdown-menu sidebar-visible-block" role="menu">
+ {{#modules}}
+ <li class="{{className}}"><a href="?do={{identifier}}">{{displayName}}</a></li>
+ {{/modules}}
+ </ul>
+ </li>
+ {{/categories}}
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">
+ <img src="lang/{{current_lang}}/flag.png" alt="{{current_lang}}">
+ <span class="sidebar-visible-inline">{{lang_language}}</span>
+ <b class="caret"></b>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a href="?do=Translation">{{lang_translations}}</a></li>
+ <li class="dropdown-header">{{lang_language}}</li>
+ {{#langs}}
+ <li><a href="?lang={{cc}}&amp;url={{url}}"><img src="lang/{{cc}}/flag.png" alt="{{name}}"> {{name}}</a></li>
+ {{/langs}}
+ </ul>
+ </li>
+ </ul>
+ <ul class="nav navbar-nav navbar-right">
+ {{#user}}
+ <li><span>{{lang_loggedInPrefix}} {{user}} {{lang_loggedInSuffix}}</span></li>
+ <li>
+ <form method="post" action="?do=Session">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="logout">
+ <button class="btn btn-default btn-xs" type="submit">{{lang_logout}}</button>
+ </form>
+ </li>
+ {{/user}}
+ {{^user}}
+ <li><a href="?do=Session&amp;action=login">{{lang_login}}</a></li>
+ {{/user}}
+ </ul>
+ </div>
+ </div>
+</nav>
diff --git a/modules-available/main/templates/messagebox-error.html b/modules-available/main/templates/messagebox-error.html
new file mode 100644
index 00000000..873716c9
--- /dev/null
+++ b/modules-available/main/templates/messagebox-error.html
@@ -0,0 +1 @@
+<div class="alert alert-danger"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> {{{message}}}</div>
diff --git a/modules-available/main/templates/messagebox-info.html b/modules-available/main/templates/messagebox-info.html
new file mode 100644
index 00000000..eb9d518a
--- /dev/null
+++ b/modules-available/main/templates/messagebox-info.html
@@ -0,0 +1 @@
+<div class="alert alert-info"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> {{{message}}}</div>
diff --git a/modules-available/main/templates/messagebox-success.html b/modules-available/main/templates/messagebox-success.html
new file mode 100644
index 00000000..93674d69
--- /dev/null
+++ b/modules-available/main/templates/messagebox-success.html
@@ -0,0 +1 @@
+<div class="alert alert-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {{{message}}}</div>
diff --git a/modules-available/main/templates/messagebox-warning.html b/modules-available/main/templates/messagebox-warning.html
new file mode 100644
index 00000000..b02e2e8a
--- /dev/null
+++ b/modules-available/main/templates/messagebox-warning.html
@@ -0,0 +1 @@
+<div class="alert alert-warning"><span class="glyphicon glyphicon-warning" aria-hidden="true"></span> {{{message}}}</div>
diff --git a/modules-available/main/templates/page-login.html b/modules-available/main/templates/page-login.html
new file mode 100644
index 00000000..247e9a55
--- /dev/null
+++ b/modules-available/main/templates/page-login.html
@@ -0,0 +1,11 @@
+<form class="form-signin" action="?do=Session" method="post">
+ <h2 class="form-signin-heading">{{lang_enter}}</h2>
+ <input type="text" name="user" class="form-control" placeholder="{{lang_username}}" autofocus>
+ <input type="password" name="pass" class="form-control" placeholder="{{lang_password}}">
+ <!--label class="checkbox">
+ <input type="checkbox" name="remember" value="remember-me"> {{lang_rememberID}}
+ </label-->
+ <button class="btn btn-lg btn-primary btn-block" type="submit">{{lang_login}}</button>
+ <a class="btn btn-lg btn-primary btn-block" href="?do=AddUser">{{lang_register}}</a>
+ <input type="hidden" name="action" value="login">
+</form> \ No newline at end of file
diff --git a/modules-available/main/templates/page-main-guest.html b/modules-available/main/templates/page-main-guest.html
new file mode 100644
index 00000000..28b0d04c
--- /dev/null
+++ b/modules-available/main/templates/page-main-guest.html
@@ -0,0 +1,15 @@
+<div class="col-md-10" style="float:none; margin: 0 auto;">
+ <div class="jumbotron">
+ <h1>{{lang_welcome}}</h1>
+ <p>{{lang_introGuest}}</p>
+ {{#register}}
+ <ul class="list-group">
+ <li class="list-group-item list-group-item-info">
+ {{lang_noExistingAccount}}
+ <a href="?do=AddUser" class="btn btn-primary btn-lg">{{lang_register}} &raquo;</a>
+ </li>
+ </ul>
+ {{/register}}
+ <p><a href="?do=Session&amp;action=login" class="btn btn-primary btn-lg">{{lang_login}} &raquo;</a></p>
+ </div>
+</div>
diff --git a/modules-available/main/templates/page-main.html b/modules-available/main/templates/page-main.html
new file mode 100644
index 00000000..39e4e74e
--- /dev/null
+++ b/modules-available/main/templates/page-main.html
@@ -0,0 +1,36 @@
+<div class="jumbotron">
+ <h1>{{lang_welcome}}, {{user}}</h1>
+ <p>{{lang_intro}}</p>
+
+</div>
+<ul class="list-group">
+{{#vmstore}}
+ <li class="list-group-item list-group-item-text">
+ {{lang_vmLocationNotSet}}
+ <a class="btn btn-sm btn-primary" href="?do=VmStore">{{lang_configure}} &raquo;</a>
+ </li>
+{{/vmstore}}
+{{#ipxe}}
+ <li class="list-group-item list-group-item-text">
+ {{lang_bootMenuWarning}}
+ <a class="btn btn-sm btn-primary" href="?do=ServerSetup">{{lang_configure}} &raquo;</a>
+ </li>
+{{/ipxe}}
+{{#minilinux}}
+ <li class="list-group-item list-group-item-text">
+ {{lang_minilinuxMissing}}
+ <a class="btn btn-sm btn-primary" href="?do=MiniLinux">MiniLinux &raquo;</a>
+ </li>
+{{/minilinux}}
+{{#sysconfig}}
+ <li class="list-group-item list-group-item-text">
+ {{lang_systemConfigurationNotChosen}}
+ <a class="btn btn-sm btn-primary" href="?do=SysConfig">{{lang_systemConfiguration}} &raquo;</a>
+ </li>
+{{/sysconfig}}
+{{#delpending}}
+ <li class="list-group-item list-group-item-text">
+ <a href="?do=DozMod">{{lang_numerOfImagesMarkedForDeletion}}: {{delpending}}</a>
+ </li>
+{{/delpending}}
+</ul>
diff --git a/modules-available/main/templates/page-minilinux.html b/modules-available/main/templates/page-minilinux.html
new file mode 100644
index 00000000..dc13e6b0
--- /dev/null
+++ b/modules-available/main/templates/page-minilinux.html
@@ -0,0 +1,14 @@
+<div id="systemlist">
+ <div class="panel panel-default">{{lang_listObtained}}</div>
+</div>
+
+<script type="text/javascript"><!--
+ function loadSystemList(version) {
+ $('#systemlist').load('{{{listurl}}}', { token: TOKEN, version: version }, function( response, status, xhr ) {
+ if ( status === "error" ) {
+ var msg = "{{lang_errorGetting}}";
+ $( "#systemlist" ).html( msg + xhr.status + " " + xhr.statusText );
+ }
+ });
+ }
+// --></script> \ No newline at end of file
diff --git a/modules-available/main/templates/page-news.html b/modules-available/main/templates/page-news.html
new file mode 100644
index 00000000..8e400498
--- /dev/null
+++ b/modules-available/main/templates/page-news.html
@@ -0,0 +1,57 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_editNews}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_newsIntro}}</p>
+ <form action="?do=News&amp;action=save" method="post">
+ <div class="form-group">
+ <label for="news-title-id">{{lang_title}}</label>
+ <input type="text" name="news-title" id ="news-title-id" class="form-control" placeholder="{{welcome}}" value="{{latestTitle}}">
+ </div>
+ <div class="form-group">
+ <label for="news-content-id">{{lang_content}}</label>
+ <textarea name="news-content" id ="news-content-id" class="form-control" rows="5" cols="30" placeholder="">{{latestContent}}</textarea>
+ </div>
+ <p>{{lang_latestUpdate}}: {{latestDate}}</p>
+ <button class="btn btn-primary btn-sm" type="submit">{{lang_save}}</button>
+ <input type="hidden" name="token" value="{{token}}">
+ </form>
+ </div>
+</div>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_newsOld}}
+ </div>
+ <div class="panel-body">
+ <div class="table-responsive">
+ <form method="post" action="?do=News&amp;action=delete">
+ <input type="hidden" name="token" value="{{token}}">
+ <table class="table table-stripped table-condensed">
+ <thead>
+ <tr>
+ <th>{{lang_date}}</th>
+ <th>{{lang_title}}</th>
+ <th>{{lang_content}}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#list}}
+ <tr {{#active}}class="active"{{/active}}>
+ <td class="text-left nowrap">{{date}}</td>
+ <td class="slx-ellipsis">{{title}}</td>
+ <td class="slx-ellipsis">{{content}}</td>
+ <td>
+ <a class="btn btn-primary btn-xs" href="?do=news&amp;newsid={{newsid}}&amp;action=show"><span class="glyphicon glyphicon-share-alt"></span> {{lang_show}}</a>
+ <button class="btn btn-danger btn-xs" type="submit" name="newsid" value="{{newsid}}"><span class="glyphicon glyphicon-remove"></span> {{lang_delete}}</button>
+ </td>
+ </tr>
+ {{/list}}
+ </tbody>
+ </table>
+ </form>
+ </div>
+ </div>
+</div>
diff --git a/modules-available/main/templates/page-syslog.html b/modules-available/main/templates/page-syslog.html
new file mode 100644
index 00000000..98e94291
--- /dev/null
+++ b/modules-available/main/templates/page-syslog.html
@@ -0,0 +1,58 @@
+<h1>{{lang_clientLog}}</h1>
+<form method="post" action="?do=SysLog">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_filter}}</span>
+ <input id="filterstring" type="text" placeholder="id" value="{{filter}}" name="filter" data-role="tagsinput" />
+ <span class="input-group-addon">
+ <input type="checkbox" name="not" {{#not}}checked="checked"{{/not}}> {{lang_not}}
+ </span>
+ <span class="input-group-btn">
+ <button class="btn btn-default" type="submit">{{lang_go}}</button>
+ </span>
+ </div>
+</form>
+{{{pagenav}}}
+<table class="table table-striped table-condensed">
+ <thead>
+ <th width="1"></th>
+ <th>{{lang_when}}</th>
+ <th>{{lang_client}}</th>
+ <th>{{lang_event}}</th>
+ <th width="1">{{lang_details}}</th>
+ </thead>
+ <tbody>
+ {{#list}}
+ <tr>
+ <td><span class="glyphicon {{icon}}" title="{{logtypeid}}" onclick="$('#filterstring').tagsinput('add', '{{logtypeid}}')"></span></td>
+ <td class="text-right" nowrap="nowrap">{{date}}</td>
+ <td>{{clientip}}</td>
+ <td>{{description}}</td>
+ <td>{{#extra}}
+ <a class="btn btn-default btn-xs pull-left" onclick="$('#details-body').html($('#extra-{{logid}}').html())" data-toggle="modal" data-target="#myModal">&raquo;</a>
+ <div class="hidden" id="extra-{{logid}}">{{extra}}</div>
+ {{/extra}}</td>
+ </tr>
+ {{/list}}
+ </tbody>
+</table>
+{{{pagenav}}}
+
+<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+ <div class="modal-dialog modal-lg">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+ <h4 class="modal-title" id="myModalLabel">{{lang_details}}</h4>
+ </div>
+ <div class="modal-body">
+ <pre id="details-body"></pre>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+
diff --git a/modules-available/main/templates/page-vmstore.html b/modules-available/main/templates/page-vmstore.html
new file mode 100644
index 00000000..fe2c5225
--- /dev/null
+++ b/modules-available/main/templates/page-vmstore.html
@@ -0,0 +1,111 @@
+<form role="form" method="post" action="?do=VmStore">
+ <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;">
+ <input type="password" name="password_fake" id="password_fake" value="" style="display:none;">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="setstore">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_vmLocation}} <a class="btn btn-default" data-toggle="modal" data-target="#help-store"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </div>
+ <div class="panel-body slx-md-width">
+ <p>{{lang_vmLocationChoose}}</p>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <input type="radio" name="storetype" value="internal" {{pre-internal}}> {{lang_intern}}
+ </div>
+ <div class="panel-body">
+ {{lang_noAdditionalInformation}}
+ </div>
+ </div>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <input type="radio" name="storetype" value="nfs" {{pre-nfs}}> NFS
+ <a class="btn btn-default btn-sm" data-toggle="modal" data-target="#help-nfs"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </div>
+ <div class="panel-body">
+ <label for="nfsaddr">NFS-Export</label>
+ <input type="text" class="form-control" name="nfsaddr" value="{{nfsaddr}}" placeholder="1.2.3.4:/export/bwlp" id="nfsaddr">
+ </div>
+ </div>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <input type="radio" name="storetype" value="cifs" {{pre-cifs}}> CIFS
+ </div>
+ <div class="panel-body">
+ <label for="cifsaddr">UNC-Pfad</label>
+ <input type="text" class="form-control" name="cifsaddr" value="{{cifsaddr}}" placeholder="\\samba.server.example.com\bwlp" id="cifsaddr">
+ <br>
+ <label for="cifsuser">{{lang_readWrite}}</label>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">
+ {{lang_username}}
+ </span>
+ <input type="text" class="form-control" name="cifsuser" value="{{cifsuser}}" placeholder="{{lang_username}}" id="cifsuser">
+ <span class="input-group-addon">
+ {{lang_password}}
+ </span>
+ <input type="{{password_type}}" class="form-control" name="cifspasswd" value="{{cifspasswd}}" placeholder="{{lang_password}}">
+ </div>
+ <br>
+ <label for="cifsuserro">{{lang_readOnly}}</label>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">
+ {{lang_username}}
+ </span>
+ <input type="text" class="form-control" name="cifsuserro" value="{{cifsuserro}}" placeholder="{{lang_username}}" id="cifsuserro">
+ <span class="input-group-addon">
+ {{lang_password}}
+ </span>
+ <input type="{{password_type}}" class="form-control" name="cifspasswdro" value="{{cifspasswdro}}" placeholder="{{lang_password}}">
+ </div>
+ </div>
+ </div>
+ <button class="btn btn-primary" type="submit">{{lang_save}}</button>
+ </div>
+ </div>
+</form>
+
+<div class="modal fade" id="help-store" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{lang_vmLocation}}</div>
+ <div class="modal-body">
+ <p>
+ {{lang_vmLocationHelp1}}
+ </p>
+ <p>
+ {{lang_vmLocationHelp2}}
+ </p>
+ <p>
+ {{lang_vmLocationHelp3}}
+ </p>
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div>
+
+<div class="modal fade" id="help-nfs" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">NFS</div>
+ <div class="modal-body">
+ <p>
+ {{lang_nfsHelp1}}
+ </p>
+ <pre>
+/mnt/images 1.2.3.4(rw,no_root_squash,async)
+/mnt/images *(ro,async,nolock)
+ </pre>
+ <p>
+ {{lang_nfsHelp2}}
+ </p>
+ <pre>
+/mnt/images 1.2.3.4(rw,all_squash,anon_uid=1234,async)
+/mnt/images *(ro,async,nolock)
+ </pre>
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div>
diff --git a/modules-available/main/templates/pagenav.html b/modules-available/main/templates/pagenav.html
new file mode 100644
index 00000000..93194999
--- /dev/null
+++ b/modules-available/main/templates/pagenav.html
@@ -0,0 +1,16 @@
+<ul class="pagination pagination-sm pull-right">
+ {{#pages}}
+ {{#current}}
+ <li class="active"><a href="{{url}}page={{page}}">{{text}} <span class="sr-only">(current)</span></a></li>
+ {{/current}}
+ {{^current}}
+ {{#text}}
+ <li><a href="{{url}}page={{page}}">{{text}}</a></li>
+ {{/text}}
+ {{^text}}
+ <li class="disabled"><a href="#">&hellip;</a></li>
+ {{/text}}
+ {{/current}}
+ {{/pages}}
+</ul>
+<div class="clearfix"></div> \ No newline at end of file
diff --git a/modules-available/main/templates/tm-callback-trigger.html b/modules-available/main/templates/tm-callback-trigger.html
new file mode 100644
index 00000000..cd03a1fe
--- /dev/null
+++ b/modules-available/main/templates/tm-callback-trigger.html
@@ -0,0 +1,15 @@
+<script type="text/javascript">
+ var slxCbCooldown = 0;
+ function slxCheckCallbacks() {
+ $.post('api.php?do=cb', { token: TOKEN }, function(data) {
+ if ( data.indexOf('True') >= 0 ) {
+ slxCbCooldown = 0;
+ } else {
+ slxCbCooldown++;
+ }
+ if (slxCbCooldown < 4)
+ setTimeout(slxCheckCallbacks, (slxCbCooldown + 1) * 1500);
+ }, 'text');
+ }
+ document.addEventListener("DOMContentLoaded", slxCheckCallbacks, false);
+</script> \ No newline at end of file
diff --git a/modules-available/minilinux/config.json b/modules-available/minilinux/config.json
new file mode 100644
index 00000000..f2abe27c
--- /dev/null
+++ b/modules-available/minilinux/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.settings",
+ "enabled":"true"
+}
diff --git a/modules-available/minilinux/lang/de/templates/download.json b/modules-available/minilinux/lang/de/templates/download.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/modules-available/minilinux/lang/de/templates/download.json
@@ -0,0 +1 @@
+{}
diff --git a/modules-available/minilinux/lang/de/templates/filelist.json b/modules-available/minilinux/lang/de/templates/filelist.json
new file mode 100644
index 00000000..214d758a
--- /dev/null
+++ b/modules-available/minilinux/lang/de/templates/filelist.json
@@ -0,0 +1,12 @@
+{
+ "lang_actual": "Aktuell",
+ "lang_canUpdate1": "Mindestens eine Komponente von",
+ "lang_canUpdate2": "kann aktualisiert werden. F\u00fcr einen reibungslosen Betrieb wird empfohlen, alle Komponenten auf dem aktuellen Stand zu halten.",
+ "lang_configurationPackageNotFound": "Keine Konfigurationspakete gefunden!",
+ "lang_desiredVersion": "Gew\u00fcnschte Version",
+ "lang_filesInVersion": "Dateien zu Version",
+ "lang_outdated": "Veraltet",
+ "lang_redownload": "Erneut herunterladen",
+ "lang_systemUpdated": "Das System ist auf dem aktuellen Stand.",
+ "lang_update": "Aktualisieren"
+} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/de/templates/page-minilinux.json b/modules-available/minilinux/lang/de/templates/page-minilinux.json
new file mode 100644
index 00000000..f30f7b76
--- /dev/null
+++ b/modules-available/minilinux/lang/de/templates/page-minilinux.json
@@ -0,0 +1,4 @@
+{
+ "lang_errorGetting": "Fehler beim Herunterladen der Liste!",
+ "lang_listObtained": "Liste wird heruntergeladen..."
+} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/module.json b/modules-available/minilinux/lang/en/module.json
new file mode 100644
index 00000000..b9262b5a
--- /dev/null
+++ b/modules-available/minilinux/lang/en/module.json
@@ -0,0 +1,12 @@
+{
+ "lang_actual": "Actual",
+ "lang_canUpdate1": "At least one component of",
+ "lang_canUpdate2": "can be updated. For a smooth operation, it is recommended to keep all components up to date.",
+ "lang_configurationPackageNotFound": "Configuration package not found!",
+ "lang_errorGetting": "Error while downloading list!",
+ "lang_listObtained": "Downloading list...",
+ "lang_outdated": "Outdated",
+ "lang_systemUpdated": "The system is up to date.",
+ "lang_update": "Update",
+ "module_name": "Minilinux"
+} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/templates/download.json b/modules-available/minilinux/lang/en/templates/download.json
new file mode 100644
index 00000000..2c63c085
--- /dev/null
+++ b/modules-available/minilinux/lang/en/templates/download.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/modules-available/minilinux/lang/en/templates/filelist.json b/modules-available/minilinux/lang/en/templates/filelist.json
new file mode 100644
index 00000000..89fed42b
--- /dev/null
+++ b/modules-available/minilinux/lang/en/templates/filelist.json
@@ -0,0 +1,12 @@
+{
+ "lang_actual": "Actual",
+ "lang_canUpdate1": "At least one component of",
+ "lang_canUpdate2": "can be updated. For a smooth operation, it is recommended to keep all components up to date.",
+ "lang_configurationPackageNotFound": "Configuration package not found!",
+ "lang_desiredVersion": "Desired version",
+ "lang_filesInVersion": "Files for version",
+ "lang_outdated": "Outdated",
+ "lang_redownload": "Download again",
+ "lang_systemUpdated": "The system is up to date.",
+ "lang_update": "Update"
+} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/templates/page-minilinux.json b/modules-available/minilinux/lang/en/templates/page-minilinux.json
new file mode 100644
index 00000000..2f59fa57
--- /dev/null
+++ b/modules-available/minilinux/lang/en/templates/page-minilinux.json
@@ -0,0 +1,4 @@
+{
+ "lang_errorGetting": "Error while downloading list!",
+ "lang_listObtained": "Downloading list..."
+} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/pt/module.json b/modules-available/minilinux/lang/pt/module.json
new file mode 100644
index 00000000..0ce7629d
--- /dev/null
+++ b/modules-available/minilinux/lang/pt/module.json
@@ -0,0 +1,12 @@
+{
+ "lang_actual": "Atual",
+ "lang_canUpdate1": "Pelo menos um componente de",
+ "lang_canUpdate2": "pode ser atualizado. Para um bom funcionamento, recomenda-se manter todos os componentes atualizados.",
+ "lang_configurationPackageNotFound": "Pacote de configura\u00e7\u00e3o n\u00e3o encontrado!",
+ "lang_errorGetting": "Erro ao baixar a lista!",
+ "lang_listObtained": "Carregando lista...",
+ "lang_outdated": "Desatualizado",
+ "lang_systemUpdated": "O sistema est\u00e1 atualizado.",
+ "lang_update": "Atualizar",
+ "module_name": "Minilinux"
+} \ No newline at end of file
diff --git a/modules-available/minilinux/page.inc.php b/modules-available/minilinux/page.inc.php
new file mode 100644
index 00000000..91be456e
--- /dev/null
+++ b/modules-available/minilinux/page.inc.php
@@ -0,0 +1,128 @@
+<?php
+
+class Page_MiniLinux extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ }
+
+ protected function doRender()
+ {
+ Render::addTemplate('page-minilinux', array(
+ 'listurl' => '?do=MiniLinux&async=true&action=list'
+ ));
+ Render::addFooter('<script> $(window).load(function (e) {
+ loadSystemList(0);
+ }); // </script>');
+ }
+
+ protected function doAjax()
+ {
+ $data = Property::getVersionCheckInformation();
+ if (!is_array($data) || !isset($data['systems'])) {
+ echo Render::parse('messagebox-error', array(
+ 'message' => 'Failed to retrieve the list: ' . print_r($data, true)
+ ),'main');
+ return;
+ }
+ $action = Request::any('action');
+ $selectedVersion = (int)Request::any('version', 0);
+ switch ($action) {
+ case 'list':
+ $count = 0;
+ foreach ($data['systems'] as &$system) {
+ // Get latest version, build simple array of all version numbers
+ $versionNumbers = array();
+ $selected = false;
+ foreach ($system['versions'] as $version) {
+ if (!is_numeric($version['version']) || $version['version'] < 1)
+ continue;
+ if ($selectedVersion === 0 && ($selected === false || $selected['version'] < $version['version']))
+ $selected = $version;
+ elseif ($version['version'] == $selectedVersion)
+ $selected = $version;
+ $versionNumbers[(int)$version['version']] = array(
+ 'version' => $version['version']
+ );
+ }
+ if ($selected === false) continue; // No versions for this system!?
+ ksort($versionNumbers);
+ // Mark latest version as selected
+ $versionNumbers[(int)$selected['version']]['selected'] = true;
+ // Add status information to system and its files
+ foreach ($selected['files'] as &$file) {
+ $file['uid'] = 'dlid' . $count++;
+ $local = CONFIG_HTTP_DIR . '/' . $system['id'] . '/' . $file['name'];
+ if (!file_exists($local) || filesize($local) !== $file['size'] || filemtime($local) < $file['mtime']) {
+ $file['fileChanged'] = true;
+ $system['systemChanged'] = true;
+ }
+ $taskId = Property::getDownloadTask($file['md5']);
+ if ($taskId !== false) {
+ $task = Taskmanager::status($taskId);
+ if (isset($task['data']['progress'])) {
+ $file['download'] = Render::parse('download', array(
+ 'task' => $taskId,
+ 'name' => $file['name']
+ ));
+ }
+ }
+ }
+ unset($system['versions']);
+ $system['files'] = $selected['files'];
+ $system['version'] = $selected['version'];
+ }
+ $data['versions'] = array_values($versionNumbers);
+ echo Render::parse('filelist', $data);
+ return;
+ case 'download':
+ $id = Request::post('id');
+ $name = Request::post('name');
+ if (!$id || !$name || strpos("$id$name", '/') !== false) {
+ echo "Invalid download request";
+ return;
+ }
+ $file = false;
+ $gpg = 'missing';
+ foreach ($data['systems'] as &$system) {
+ if ($system['id'] !== $id) continue;
+ foreach ($system['versions'] as &$version) {
+ if ($version['version'] != $selectedVersion) continue;
+ foreach ($version['files'] as &$f) {
+ if ($f['name'] !== $name) continue;
+ $file = $f;
+ if (!empty($f['gpg'])) $gpg = $f['gpg'];
+ break;
+ }
+ }
+ }
+ if ($file === false) {
+ echo "Nonexistent system/file: $id / $name";
+ return;
+ }
+ $task = Taskmanager::submit('DownloadFile', array(
+ 'url' => CONFIG_REMOTE_ML . '/' . $id . '/' . $selectedVersion . '/' . $name,
+ 'destination' => CONFIG_HTTP_DIR . '/' . $id . '/' . $name,
+ 'gpg' => $gpg
+ ));
+ if (!isset($task['id'])) {
+ echo 'Error launching download task: ' . $task['statusCode'];
+ return;
+ }
+ Property::setDownloadTask($file['md5'], $task['id']);
+ echo Render::parse('download', array(
+ 'name' => $name,
+ 'task' => $task['id']
+ ));
+ return;
+ }
+ }
+
+}
diff --git a/modules-available/minilinux/templates/download.html b/modules-available/minilinux/templates/download.html
new file mode 100644
index 00000000..2e32df5a
--- /dev/null
+++ b/modules-available/minilinux/templates/download.html
@@ -0,0 +1 @@
+<div data-tm-id="{{task}}" data-tm-log="error" data-tm-progress="progress">{{name}}</div> \ No newline at end of file
diff --git a/modules-available/minilinux/templates/filelist.html b/modules-available/minilinux/templates/filelist.html
new file mode 100644
index 00000000..ca94f4d0
--- /dev/null
+++ b/modules-available/minilinux/templates/filelist.html
@@ -0,0 +1,77 @@
+ {{#systems}}
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h4>{{title}}</h4>
+ </div>
+ <div class="panel-body" id="download-{{id}}">
+ <div class="input-group pull-right" style="max-width: 400px">
+ <span class="input-group-addon slx-ga">{{lang_desiredVersion}}</span>
+ <select id="versionbox" class="form-control" onchange="loadSystemList($('#versionbox').val())">
+ {{#versions}}
+ {{#selected}}
+ <option value="{{version}}" selected>{{version}}</option>
+ {{/selected}}
+ {{^selected}}
+ <option value="{{version}}">{{version}}</option>
+ {{/selected}}
+ {{/versions}}
+ </select>
+ </div>
+ {{#systemChanged}}
+ <p>
+ {{lang_canUpdate1}} <b>{{title}}</b> {{lang_canUpdate2}}
+ </p>
+ <p><span class="btn btn-primary" onclick="slxUpdateAll(this, 'download-{{id}}')">{{lang_update}}</span></p>
+ {{/systemChanged}}
+ {{^systemChanged}}
+ <p>{{lang_systemUpdated}}</p>
+ {{/systemChanged}}
+ <hr>
+ <p><b>{{lang_filesInVersion}} {{version}}</b></p>
+ <ul class="list-group">
+ {{#files}}
+ <li class="list-group-item" id="{{uid}}">
+ <div class="row">
+ <div class="col-sm-2">{{name}}</div>
+ <div class="col-xs-2">
+ {{^fileChanged}}<span class="glyphicon glyphicon-ok"></span> <b>{{lang_actual}}</b>{{/fileChanged}}
+ {{#fileChanged}}<span class="glyphicon glyphicon-exclamation-sign"></span> <b>{{lang_outdated}}</b>{{/fileChanged}}
+ </div>
+ <div class="col-xs-2">
+ {{#fileChanged}}<span class="btn btn-primary btn-xs update-button" onclick="slxUpdate('{{uid}}', '{{id}}', '{{name}}')">{{lang_update}}</span>{{/fileChanged}}
+ {{^fileChanged}}<span class="btn btn-default btn-xs" onclick="slxUpdate('{{uid}}', '{{id}}', '{{name}}')">{{lang_redownload}}</span>{{/fileChanged}}
+ </div>
+ </div>
+ {{{download}}}
+ </li>
+ {{/files}}
+ </ul>
+ </div>
+ </div>
+ {{/systems}}
+ {{^systems}}
+ <div class="row well well-sm">{{lang_configurationPackageNotFound}}</div>
+ {{/systems}}
+
+<script type="text/javascript">
+function slxUpdate(uid, id, name)
+{
+ $('#' + uid).html('');
+ $('#' + uid).load('?do=MiniLinux',
+ { action : "download", token : TOKEN, id : id, name : name, version : $('#versionbox').val() },
+ function(response, status, xhr) {
+ if (status === "error") {
+ var msg = "Fehler beim Abruf: ";
+ $('#' + uid).html(msg + xhr.status + " " + xhr.statusText);
+ } else {
+ setTimeout(tmInit, 100);
+ }
+ });
+}
+function slxUpdateAll(t, uid)
+{
+ $(t).hide(0);
+ $('#' + uid).find('.update-button').click();
+}
+tmInit();
+</script>
diff --git a/modules-available/minilinux/templates/page-minilinux.html b/modules-available/minilinux/templates/page-minilinux.html
new file mode 100644
index 00000000..007e1e1b
--- /dev/null
+++ b/modules-available/minilinux/templates/page-minilinux.html
@@ -0,0 +1,28 @@
+<div id="systemlist">
+ <div class="panel panel-default">{{lang_listObtained}}</div>
+ <!-- OLD CODE ??
+ <script type="text/javascript">
+ var slx_check = setInterval(function() {
+ if (typeof $ === 'undefined') return;
+ clearInterval(slx_check);
+ $('#systemlist').load('{{{listurl}}}', function( response, status, xhr ) {
+ if ( status === "error" ) {
+ var msg = "{{lang_errorGetting}}";
+ $( "#systemlist" ).html( msg + xhr.status + " " + xhr.statusText );
+ }
+ })
+ }, 100);
+ </script>
+ OLD CODE ?? -->
+</div>
+
+<script type="text/javascript"><!--
+ function loadSystemList(version) {
+ $('#systemlist').load('{{{listurl}}}', { token: TOKEN, version: version }, function( response, status, xhr ) {
+ if ( status === "error" ) {
+ var msg = "{{lang_errorGetting}}";
+ $( "#systemlist" ).html( msg + xhr.status + " " + xhr.statusText );
+ }
+ });
+ }
+// --></script> \ No newline at end of file
diff --git a/modules-available/news/config.json b/modules-available/news/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/news/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/news/lang/de/templates/page-news.json b/modules-available/news/lang/de/templates/page-news.json
new file mode 100644
index 00000000..7801584d
--- /dev/null
+++ b/modules-available/news/lang/de/templates/page-news.json
@@ -0,0 +1,13 @@
+{
+ "lang_activeNews": "Aktive News",
+ "lang_content": "Inhalt",
+ "lang_date": "Datum",
+ "lang_delete": "L\u00f6schen",
+ "lang_editNews": "News bearbeiten",
+ "lang_latestUpdate": "Letzte Aktualisierung",
+ "lang_newsIntro": "Hier haben Sie die M\u00f6glichkeit, die von bwLehrpool-Clients angezeigten News zu editieren.",
+ "lang_newsOld": "Alte News",
+ "lang_save": "Speichern",
+ "lang_show": "Ansehen",
+ "lang_title": "Titel"
+} \ No newline at end of file
diff --git a/modules-available/news/lang/en/module.json b/modules-available/news/lang/en/module.json
new file mode 100644
index 00000000..5acbe58d
--- /dev/null
+++ b/modules-available/news/lang/en/module.json
@@ -0,0 +1,12 @@
+{
+ "lang_activeNews": "News",
+ "lang_content": "Content",
+ "lang_date": "Date",
+ "lang_delete": "Delete",
+ "lang_latestUpdate": "Last Update",
+ "lang_newsOld": "Old News",
+ "lang_save": "Save",
+ "lang_show": "Show",
+ "lang_title": "Title",
+ "module_name": "News"
+} \ No newline at end of file
diff --git a/modules-available/news/lang/en/templates/page-news.json b/modules-available/news/lang/en/templates/page-news.json
new file mode 100644
index 00000000..df63658c
--- /dev/null
+++ b/modules-available/news/lang/en/templates/page-news.json
@@ -0,0 +1,12 @@
+{
+ "lang_content": "Content",
+ "lang_date": "Date",
+ "lang_delete": "Delete",
+ "lang_editNews": "Edit news",
+ "lang_latestUpdate": "Latest update",
+ "lang_newsIntro": "Here you have the possibility to edit the news displayed to the bwLehrpool clients.",
+ "lang_newsOld": "Old News",
+ "lang_save": "Save",
+ "lang_show": "Show",
+ "lang_title": "Title"
+} \ No newline at end of file
diff --git a/modules-available/news/lang/pt/module.json b/modules-available/news/lang/pt/module.json
new file mode 100644
index 00000000..e1997fce
--- /dev/null
+++ b/modules-available/news/lang/pt/module.json
@@ -0,0 +1,13 @@
+{
+ "lang_activeNews": "Novidades",
+ "lang_content": "Conte\u00fado",
+ "lang_date": "Data",
+ "lang_delete": "Excluir",
+ "lang_latestUpdate": "\u00daltima Atualiza\u00e7\u00e3o",
+ "lang_newsIntro": "Aqui voc\u00ea tem a possibilidade de editar as novidades que s\u00e3o mostradas para os clientes do bwLehrpool.",
+ "lang_newsOld": "Antigas",
+ "lang_save": "Salvar",
+ "lang_show": "Mostrar",
+ "lang_title": "T\u00edtulo",
+ "module_name": "Novidades"
+} \ No newline at end of file
diff --git a/modules-available/news/page.inc.php b/modules-available/news/page.inc.php
new file mode 100644
index 00000000..9bbadc4f
--- /dev/null
+++ b/modules-available/news/page.inc.php
@@ -0,0 +1,167 @@
+<?php
+
+class Page_News extends Page
+{
+ /**
+ * Member variables needed to represent a news entry.
+ *
+ * @var newsId int ID of the news entry attributed by the database.
+ * @var string Title of the entry.
+ * $newsContent string Content as text. (TODO: html-Support?)
+ * $newsDate string Unix epoch date of the news' creation.
+ */
+ private $newsId = false;
+ private $newsTitle = false;
+ private $newsContent = false;
+ private $newsDate = false;
+
+ /**
+ * Implementation of the abstract doPreprocess function
+ *
+ * Checks if the user is logged in and processes any
+ * action if one was specified in the request.
+ *
+ */
+ protected function doPreprocess()
+ {
+ // load user, we will need it later
+ User::load();
+
+ // only admins should be able to edit news
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ // check which action we need to do
+ $action = Request::any('action', 'show');
+ if ($action === 'clear') {
+ // clear news input fields
+ // TODO: is this the right way?
+ $this->newsId = false;
+ $this->newsTitle = false;
+ $this->newsContent = false;
+ $this->newsDate = false;
+ } elseif ($action === 'show') {
+ // show news
+ if (!$this->loadNews(Request::any('newsid'))) {
+ Message::addError('news-empty');
+ }
+ } elseif ($action === 'save') {
+ // save to DB
+ if (!$this->saveNews()) {
+ // re-set the fields we got
+ Request::post('news-title') ? $this->newsTitle = Request::post('news-title') : $this->newsTitle = false;
+ Request::post('news-content') ? $this->newsContent = Request::post('news-content') : $this->newsContent = false;
+ } else {
+ Message::addSuccess('news-save-success');
+ Util::redirect('?do=News');
+ }
+ } elseif ($action === 'delete') {
+ // delete it
+ $this->delNews(Request::post('newsid'));
+ } else {
+ // unknown action, redirect user
+ Message::addError('invalid-action', $action);
+ Util::redirect('?do=News');
+ }
+ }
+
+ /**
+ * Implementation of the abstract doRender function
+ *
+ * Fetch the list of news from the database and paginate it.
+ *
+ */
+ protected function doRender()
+ {
+ // fetch the list of the older news
+ $lines = array();
+ $paginate = new Paginate("SELECT newsid, dateline, title, content FROM news ORDER BY dateline DESC", 10);
+ $res = $paginate->exec();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['date'] = date('d.m.Y H:i', $row['dateline']);
+
+ if ($row['newsid'] == $this->newsId) $row['active'] = "active";
+ $lines[] = $row;
+ }
+ $paginate->render('page-news', array(
+ 'token' => Session::get('token'),
+ 'latestDate' => ($this->newsDate ? date('d.m.Y H:i', $this->newsDate) : '--'),
+ 'latestContent' => $this->newsContent,
+ 'latestTitle' => $this->newsTitle,
+ 'list' => $lines ));
+
+ }
+ /**
+ * Loads the news with the given ID into the form.
+ *
+ * @param int $newsId ID of the news to be shown.
+ * @return boolean true if loading that news worked
+ *
+ */
+ private function loadNews($newsId)
+ {
+ // check to see if we need to request a specific newsid
+ if ($newsId !== false) {
+ $row = Database::queryFirst("SELECT newsid, title, content, dateline FROM news WHERE newsid = :newsid LIMIT 1", array(
+ 'newsid' => $newsId
+ ));
+ } else {
+ $row = Database::queryFirst("SELECT newsid, title, content, dateline FROM news ORDER BY dateline DESC LIMIT 1");
+ }
+
+ // fetch the news to be shown
+ if ($row !== false) {
+ $this->newsId = $row['newsid'];
+ $this->newsTitle = $row['title'];
+ $this->newsContent = $row['content'];
+ $this->newsDate = $row['dateline'];
+ }
+ return $row !== false;
+ }
+
+ /**
+ * Save the given $newsTitle and $newsContent as POST'ed into the database.
+ *
+ */
+ private function saveNews()
+ {
+ // check if news content were set by the user
+ $newsTitle = Request::post('news-title');
+ $newsContent = Request::post('news-content');
+ if ($newsContent !== '' && $newsTitle !== '') {
+ // we got title and content, save it to DB
+ Database::exec("INSERT INTO news (dateline, title, content) VALUES (:dateline, :title, :content)", array(
+ 'dateline' => time(),
+ 'title' => $newsTitle,
+ 'content' => $newsContent
+ ));
+ return true;
+ } else {
+ Message::addError('empty-field');
+ return false;
+ }
+ }
+
+ /**
+ * Delete the news entry with ID $newsId
+ *
+ * @param int $newsId ID of the entry to be deleted.
+ */
+ private function delNews($newsId)
+ {
+ // sanity check: is newsId even numeric?
+ if (!is_numeric($newsId)) {
+ Message::addError('value-invalid', 'newsid', $newsId);
+ } else {
+ // check passed - do delete
+ Database::exec("DELETE FROM news WHERE newsid = :newsid LIMIT 1", array(
+ 'newsid' => $newsId
+ ));
+ Message::addSuccess('news-del-success');
+ }
+ Util::redirect('?do=News');
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/news/templates/page-news.html b/modules-available/news/templates/page-news.html
new file mode 100644
index 00000000..8e400498
--- /dev/null
+++ b/modules-available/news/templates/page-news.html
@@ -0,0 +1,57 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_editNews}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_newsIntro}}</p>
+ <form action="?do=News&amp;action=save" method="post">
+ <div class="form-group">
+ <label for="news-title-id">{{lang_title}}</label>
+ <input type="text" name="news-title" id ="news-title-id" class="form-control" placeholder="{{welcome}}" value="{{latestTitle}}">
+ </div>
+ <div class="form-group">
+ <label for="news-content-id">{{lang_content}}</label>
+ <textarea name="news-content" id ="news-content-id" class="form-control" rows="5" cols="30" placeholder="">{{latestContent}}</textarea>
+ </div>
+ <p>{{lang_latestUpdate}}: {{latestDate}}</p>
+ <button class="btn btn-primary btn-sm" type="submit">{{lang_save}}</button>
+ <input type="hidden" name="token" value="{{token}}">
+ </form>
+ </div>
+</div>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_newsOld}}
+ </div>
+ <div class="panel-body">
+ <div class="table-responsive">
+ <form method="post" action="?do=News&amp;action=delete">
+ <input type="hidden" name="token" value="{{token}}">
+ <table class="table table-stripped table-condensed">
+ <thead>
+ <tr>
+ <th>{{lang_date}}</th>
+ <th>{{lang_title}}</th>
+ <th>{{lang_content}}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#list}}
+ <tr {{#active}}class="active"{{/active}}>
+ <td class="text-left nowrap">{{date}}</td>
+ <td class="slx-ellipsis">{{title}}</td>
+ <td class="slx-ellipsis">{{content}}</td>
+ <td>
+ <a class="btn btn-primary btn-xs" href="?do=news&amp;newsid={{newsid}}&amp;action=show"><span class="glyphicon glyphicon-share-alt"></span> {{lang_show}}</a>
+ <button class="btn btn-danger btn-xs" type="submit" name="newsid" value="{{newsid}}"><span class="glyphicon glyphicon-remove"></span> {{lang_delete}}</button>
+ </td>
+ </tr>
+ {{/list}}
+ </tbody>
+ </table>
+ </form>
+ </div>
+ </div>
+</div>
diff --git a/modules-available/serversetup/config.json b/modules-available/serversetup/config.json
new file mode 100644
index 00000000..f2abe27c
--- /dev/null
+++ b/modules-available/serversetup/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.settings",
+ "enabled":"true"
+}
diff --git a/modules-available/serversetup/lang/de/templates/ipaddress.json b/modules-available/serversetup/lang/de/templates/ipaddress.json
new file mode 100644
index 00000000..eb3d34d3
--- /dev/null
+++ b/modules-available/serversetup/lang/de/templates/ipaddress.json
@@ -0,0 +1,7 @@
+{
+ "lang_active": "Aktiv",
+ "lang_bootAddress": "Boot-Adresse des Servers",
+ "lang_bootHint": "Das Bootmen\u00fc muss nach einer \u00c4nderung der IP-Adresse neu generiert werden. In der Regel geschieht dies automatisch, der Vorgang kann in der Sektion Bootmen\u00fc allerdings auch manuell ausgel\u00f6st werden.",
+ "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.",
+ "lang_set": "Setzen"
+} \ No newline at end of file
diff --git a/modules-available/serversetup/lang/de/templates/ipxe.json b/modules-available/serversetup/lang/de/templates/ipxe.json
new file mode 100644
index 00000000..edce0b5e
--- /dev/null
+++ b/modules-available/serversetup/lang/de/templates/ipxe.json
@@ -0,0 +1,19 @@
+{
+ "lang_bootBehavior": "Standard-Bootverhalten",
+ "lang_bootInfo": "Hier k\u00f6nnen Anpassungen am Erscheinungsbild des Bootmen\u00fcs vorgenommen werden.",
+ "lang_bootMenu": "Bootmen\u00fc",
+ "lang_bootMenuCreate": "Bootmen\u00fc erzeugen",
+ "lang_close": "Schlie\u00dfen",
+ "lang_compile": "Kompilieren",
+ "lang_customEntry": "Eigener Eintrag",
+ "lang_example": "Beispiel",
+ "lang_localHDD": "Lokale HDD",
+ "lang_masterPassword": "Master-Passwort",
+ "lang_masterPasswordHelp": "Das Master-Passwort wird ben\u00f6tigt, um einen Booteintrag direkt am Client tempor\u00e4r durch Dr\u00fccken der Tab-Taste zu editieren. Da dies f\u00fcr Manipulation am Client genutzt werden kann, sollte diese Funktion unbedingt mit einem Passwort gesch\u00fctzt werden.",
+ "lang_menuCustom": "Benutzerdefinierter Men\u00fczusatz",
+ "lang_menuCustomHint1": "Hier haben Sie die M\u00f6glichkeit, eigenen Men\u00fc-Code zum angezeigten PXE-Men\u00fc hinzuzuf\u00fcgen, um z.B. auf weitere PXE-Server zu verweisen. Das Format entspricht dem syslinux Men\u00fcformat.",
+ "lang_menuCustomHint2": "Sie k\u00f6nnen ein oder mehrere Eintr\u00e4ge erzeugen. Wenn Sie einen Eintrag erzeugen m\u00f6chten, der automatisch gestartet wird, wenn der Benutzer keine Auswahl t\u00e4tigt, vergeben Sie als",
+ "lang_menuCustomHint3": "und w\u00e4hlen Sie als Standard-Bootverhalten ebenfalls custom.",
+ "lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs",
+ "lang_seconds": "Sekunden"
+} \ No newline at end of file
diff --git a/modules-available/serversetup/lang/de/templates/ipxe_update.json b/modules-available/serversetup/lang/de/templates/ipxe_update.json
new file mode 100644
index 00000000..b984de19
--- /dev/null
+++ b/modules-available/serversetup/lang/de/templates/ipxe_update.json
@@ -0,0 +1,4 @@
+{
+ "lang_generationFailed": "Erzeugen des Bootmen\u00fcs fehlgeschlagen. Der Netzwerkboot von bwLehrpool wird wahrscheinlich nicht funktionieren. Wenn Sie den Fehler nicht selbst beheben k\u00f6nnen, melden Sie bitte obenstehende Fehlermeldung an das bwLehrpool-Projekt.",
+ "lang_menuGeneration": "Erzeugen des Bootmen\u00fcs"
+} \ No newline at end of file
diff --git a/modules-available/serversetup/lang/en/module.json b/modules-available/serversetup/lang/en/module.json
new file mode 100644
index 00000000..c7c06098
--- /dev/null
+++ b/modules-available/serversetup/lang/en/module.json
@@ -0,0 +1,34 @@
+{
+ "lang_active": "Active",
+ "lang_bootAddress": "Boot Address of the Server",
+ "lang_bootBehavior": "Default Boot Behavior",
+ "lang_bootHint": "The Boot menu must be recreated after changing the IP address. Usually this is done automatically, but the process can also be triggered manually in the section of the boot menu.",
+ "lang_bootInfo": "Here adjustments can be made to the appearance of the boot menu.",
+ "lang_bootMenu": "Boot Menu",
+ "lang_bootMenuCreate": "Create Boot Menu",
+ "lang_cancel": "Cancel",
+ "lang_chooseIP": "Please select the IP address that the client server will use to boot.",
+ "lang_close": "Close",
+ "lang_compile": "Compile",
+ "lang_compilingIpxe": "Compiling iPXE",
+ "lang_customScript": "Custom Script",
+ "lang_download": "Download",
+ "lang_example": "Example",
+ "lang_extension": "Extension",
+ "lang_ipxeInfo": "Here it is possible to compile and download iPXE for USB using a custom script.",
+ "lang_ipxeWarning": "If this is your first time compiling, it may take 1 to 4 minutes to finish.",
+ "lang_loading": "Loading",
+ "lang_localHDD": "Local HDD",
+ "lang_menuCustom": "Custom Extra Menu",
+ "lang_menuCustomHint1": "Here you have the opportunity to add your own menu code to the displayed PXE menu, eg to refer to other PXE server. The format corresponds to the syslinux menu format.",
+ "lang_menuCustomHint2": "You can create one or more entries. If you want to create an entry that starts automatically when the user makes a selection, assign as",
+ "lang_menuCustomHint3": "and select as the default boot behavior my-entry as well.",
+ "lang_menuDisplayTime": "Menu Display Time",
+ "lang_mountIpxe": "Mount iPXE",
+ "lang_restoreDefault": "Restore Default",
+ "lang_saveScript": "Save Script",
+ "lang_seconds": "Seconds",
+ "lang_set": "Set",
+ "lang_success": "Successfully create file:",
+ "module_name": "iPXE \/ Boot Menu"
+} \ No newline at end of file
diff --git a/modules-available/serversetup/lang/en/templates/ipaddress.json b/modules-available/serversetup/lang/en/templates/ipaddress.json
new file mode 100644
index 00000000..699aeacc
--- /dev/null
+++ b/modules-available/serversetup/lang/en/templates/ipaddress.json
@@ -0,0 +1,7 @@
+{
+ "lang_active": "Active",
+ "lang_bootAddress": "Boot Address of the Server",
+ "lang_bootHint": "The Boot menu must be recreated after changing the IP address. Usually this is done automatically, but the process can also be triggered manually in the section of the boot menu.",
+ "lang_chooseIP": "Please select the IP address that the client server will use to boot.",
+ "lang_set": "Set"
+} \ No newline at end of file
diff --git a/modules-available/serversetup/lang/en/templates/ipxe.json b/modules-available/serversetup/lang/en/templates/ipxe.json
new file mode 100644
index 00000000..70c17f43
--- /dev/null
+++ b/modules-available/serversetup/lang/en/templates/ipxe.json
@@ -0,0 +1,31 @@
+{
+ "lang_bootBehavior": "Default Boot Behavior",
+ "lang_bootInfo": "Here adjustments can be made to the appearance of the boot menu.",
+ "lang_bootMenu": "Boot Menu",
+ "lang_bootMenuCreate": "Create Boot Menu",
+ "lang_cancel": "Cancel",
+ "lang_close": "Close",
+ "lang_compile": "Compile",
+ "lang_compilingIpxe": "Compiling iPXE",
+ "lang_customEntry": "Custom entry",
+ "lang_customScript": "Custom script",
+ "lang_download": "Download",
+ "lang_example": "Example",
+ "lang_extension": "Extension",
+ "lang_ipxeInfo": "Here it is possible to compile iPXE using a custom script.",
+ "lang_ipxeWarning": "If this is your first time compiling, it may take 1 to 4 minutes to finish.",
+ "lang_loading": "Loading",
+ "lang_localHDD": "Local HDD",
+ "lang_masterPassword": "Master password",
+ "lang_masterPasswordHelp": "The master password is required to edit a boot menu entry. This should be set for security reasons.",
+ "lang_menuCustom": "Custom Extra Menu",
+ "lang_menuCustomHint1": "Here you have the opportunity to add your own menu code to the displayed PXE menu, eg to refer to other PXE server. The format corresponds to the syslinux menu format.",
+ "lang_menuCustomHint2": "You can create one or more entries. If you want to create an entry that starts automatically when the user makes a selection, assign as",
+ "lang_menuCustomHint3": "and select as the default boot behavior custom as well.",
+ "lang_menuDisplayTime": "Menu Display Time",
+ "lang_mountIpxe": "Mount iPXE",
+ "lang_restoreDefault": "Restore Default",
+ "lang_saveScript": "Save Script",
+ "lang_seconds": "Seconds",
+ "lang_success": "Successfully create file:"
+}
diff --git a/modules-available/serversetup/lang/en/templates/ipxe_update.json b/modules-available/serversetup/lang/en/templates/ipxe_update.json
new file mode 100644
index 00000000..b33b12dd
--- /dev/null
+++ b/modules-available/serversetup/lang/en/templates/ipxe_update.json
@@ -0,0 +1,4 @@
+{
+ "lang_generationFailed": "Could not generate boot menu. The bwLehrpool-System might not work properly. If you can't fix the problem, please report the error message above to the bwLehrpool project.",
+ "lang_menuGeneration": "Generating boot menu..."
+} \ No newline at end of file
diff --git a/modules-available/serversetup/lang/pt/module.json b/modules-available/serversetup/lang/pt/module.json
new file mode 100644
index 00000000..e0e5a2b6
--- /dev/null
+++ b/modules-available/serversetup/lang/pt/module.json
@@ -0,0 +1,40 @@
+{
+ "lang_active": "Ativo",
+ "lang_bootAddress": "Endere\u00e7o Boot do Servidor",
+ "lang_bootBehavior": "Comportamento Padr\u00e3o de Boot",
+ "lang_bootHint": "O menu de boot deve ser recriado ap\u00f3s alterar o endere\u00e7o IP. Geralmente isso \u00e9 feito automaticamente, mas o processo tamb\u00e9m pode ser acionado manualmente na se\u00e7\u00e3o do menu de boot.",
+ "lang_bootInfo": "Aqui ajustes podem ser feitos na apar\u00eancia do menu de boot.",
+ "lang_bootMenu": "Menu de Boot",
+ "lang_bootMenuCreate": "Criar Menu de Boot",
+ "lang_cancel": "Cancelar",
+ "lang_chooseIP": "Por favor, selecione o endere\u00e7o IP que o servidor do cliente utilizar\u00e1 realizar o boot.",
+ "lang_close": "Fechar",
+ "lang_compile": "Compilar",
+ "lang_compileIso": "Compilar .iso",
+ "lang_compileKkpxe": "Compilar .kkpxe",
+ "lang_compileUsb": "Compilar .usb",
+ "lang_compilingIpxe": "Compilando iPXE",
+ "lang_customScript": "Script Customizado",
+ "lang_download": "Baixar",
+ "lang_example": "Exemplo",
+ "lang_extension": "Extens\u00e3o",
+ "lang_ipxeAdv": "Gerar iPXE no Modo Avan\u00e7ado",
+ "lang_ipxeInfo": "Aqui \u00e9 poss\u00edvel compilar e baixar o iPXE utilizando um script customiz\u00e1vel.",
+ "lang_ipxeSmp": "Gerar iPXE no Modo Simples",
+ "lang_ipxeSmpInfo": "Aqui voc\u00ea pode escolher gerar o iPXE escolhendo apenas uma das extens\u00f5es abaixo",
+ "lang_ipxeWarning": "Se esta for a primeira vez compilando, poder\u00e1 levar entre 1 e 4 minutos para que termine.",
+ "lang_loading": "Carregando",
+ "lang_localHDD": "HDD Local",
+ "lang_menuCustom": "Menu Adicional Customizado",
+ "lang_menuCustomHint1": "Aqui voc\u00ea tem a oportunidade de adicionar seu pr\u00f3prio c\u00f3digo de menu para o menu PXE exibido, por exemplo, para se referir a outro servidor PXE. O formato corresponde ao formato de menu syslinux.",
+ "lang_menuCustomHint2": "Voc\u00ea pode criar uma ou mais entradas. Se voc\u00ea quiser criar uma entrada que \u00e9 iniciada automaticamente quando o usu\u00e1rio faz uma sele\u00e7\u00e3o, atribua como",
+ "lang_menuCustomHint3": "e selecione como o comportamento de boot padr\u00e3o tamb\u00e9m my-entry.",
+ "lang_menuDisplayTime": "Tempo de Exibi\u00e7\u00e3o do Menu",
+ "lang_mountIpxe": "Montar iPXE",
+ "lang_restoreDefault": "Restaurar Padr\u00e3o",
+ "lang_saveScript": "Salvar Script",
+ "lang_seconds": "Segundos",
+ "lang_set": "Definir",
+ "lang_success": "Arquivo criado com sucesso:",
+ "module_name": "iPXE \/ Boot Menu"
+} \ No newline at end of file
diff --git a/modules-available/serversetup/lang/pt/templates/ipxe-adv.json b/modules-available/serversetup/lang/pt/templates/ipxe-adv.json
new file mode 100644
index 00000000..19d120ad
--- /dev/null
+++ b/modules-available/serversetup/lang/pt/templates/ipxe-adv.json
@@ -0,0 +1,28 @@
+{
+ "lang_bootBehavior": "Comportamento Padr\u00e3o de Boot",
+ "lang_bootInfo": "Aqui ajustes podem ser feitos na apar\u00eancia do menu de boot.",
+ "lang_bootMenu": "Menu de Boot",
+ "lang_bootMenuCreate": "Criar Menu de Boot",
+ "lang_cancel": "Cancelar",
+ "lang_close": "Fechar",
+ "lang_compile": "Compilar",
+ "lang_compilingIpxe": "Compilando iPXE",
+ "lang_customScript": "Script Customizado",
+ "lang_download": "Baixar",
+ "lang_example": "Exemplo",
+ "lang_extension": "Extens\u00e3o",
+ "lang_ipxeInfo": "Aqui \u00e9 poss\u00edvel compilar e baixar o iPXE utilizando um script customiz\u00e1vel.",
+ "lang_ipxeWarning": "Se esta for a primeira vez compilando, poder\u00e1 levar entre 1 e 4 minutos para que termine.",
+ "lang_loading": "Carregando",
+ "lang_localHDD": "HDD Local",
+ "lang_menuCustom": "Menu Adicional Customizado",
+ "lang_menuCustomHint1": "Aqui voc\u00ea tem a oportunidade de adicionar seu pr\u00f3prio c\u00f3digo de menu para o menu PXE exibido, por exemplo, para se referir a outro servidor PXE. O formato corresponde ao formato de menu syslinux.",
+ "lang_menuCustomHint2": "Voc\u00ea pode criar uma ou mais entradas. Se voc\u00ea quiser criar uma entrada que \u00e9 iniciada automaticamente quando o usu\u00e1rio faz uma sele\u00e7\u00e3o, atribua como",
+ "lang_menuCustomHint3": "e selecione como o comportamento de boot padr\u00e3o tamb\u00e9m my-entry.",
+ "lang_menuDisplayTime": "Tempo de Exibi\u00e7\u00e3o do Menu",
+ "lang_mountIpxe": "Montar iPXE",
+ "lang_restoreDefault": "Restaurar Padr\u00e3o",
+ "lang_saveScript": "Salvar Script",
+ "lang_seconds": "Segundos",
+ "lang_success": "Arquivo criado com sucesso:"
+}
diff --git a/modules-available/serversetup/page.inc.php b/modules-available/serversetup/page.inc.php
new file mode 100644
index 00000000..289bf3d5
--- /dev/null
+++ b/modules-available/serversetup/page.inc.php
@@ -0,0 +1,194 @@
+<?php
+
+class Page_ServerSetup extends Page
+{
+
+ private $mountIpxeTask;
+ private $taskStatus;
+ private $currentAddress;
+ private $currentMenu;
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ $this->currentMenu = Property::getBootMenu();
+
+ if(Request::get('download') !== false){
+ $this->downloadIpxe(Request::get('download'));
+ }
+
+ if(Request::get('defaultIpxe') !== false){
+ $this->defaultIpxe(Request::get('defaultIpxe'));
+ }
+
+ $action = Request::post('action');
+
+ if ($action === false) {
+ $this->currentAddress = Property::getServerIp();
+ $this->getLocalAddresses();
+ }
+
+ if ($action === 'ip') {
+ // New address is to be set
+ $this->getLocalAddresses();
+ $this->updateLocalAddress();
+ }
+
+ if ($action === 'ipxe') {
+ // iPXE stuff changes
+ $this->updatePxeMenu();
+ }
+
+ if($action === 'save-script') {
+ // Save new iPXE script
+ $this->updateIpxeScript();
+ }
+
+ if($action === 'default-script') {
+ // Restore iPXE script to default
+ $this->defaultIpxe();
+ }
+ }
+
+ protected function doRender()
+ {
+ Render::setTitle(Dictionary::translate('lang_serverConfiguration'));
+
+ $taskid = Request::any('taskid');
+ if ($taskid !== false && Taskmanager::isTask($taskid)) {
+ Render::addTemplate('ipxe_update', array('taskid' => $taskid));
+ }
+
+ if (Request::get('advanced', 'false', 'string') === 'false') {
+ Render::addTemplate('ipxe-smp');
+ } else {
+ Render::addTemplate('ipaddress', array(
+ 'ips' => $this->taskStatus['data']['addresses']
+ ));
+ $data = $this->currentMenu;
+ if (!isset($data['defaultentry']))
+ $data['defaultentry'] = 'net';
+ if ($data['defaultentry'] === 'net')
+ $data['active-net'] = 'checked';
+ if ($data['defaultentry'] === 'hdd')
+ $data['active-hdd'] = 'checked';
+ if ($data['defaultentry'] === 'custom')
+ $data['active-custom'] = 'checked';
+ //There is no $this->username and no pxe.embed, why do we need this?
+ //Page won't load with lines below uncommented
+ //$data['username'] = $this->username;
+ //$data['script'] = file_get_contents("/opt/taskmanager/data/pxe.embed");
+ Render::addTemplate('ipxe-adv', $data);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+
+ private function getLocalAddresses()
+ {
+ $this->taskStatus = Taskmanager::submit('LocalAddressesList', array());
+
+ if ($this->taskStatus === false) {
+ $this->taskStatus['data']['addresses'] = false;
+ return false;
+ }
+
+ if ($this->taskStatus['statusCode'] === TASK_WAITING) { // TODO: Async if just displaying
+ $this->taskStatus = Taskmanager::waitComplete($this->taskStatus['id']);
+ }
+
+ $sortIp = array();
+ foreach (array_keys($this->taskStatus['data']['addresses']) as $key) {
+ $item = & $this->taskStatus['data']['addresses'][$key];
+ if (!isset($item['ip']) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $item['ip']) || substr($item['ip'], 0, 4) === '127.') {
+ unset($this->taskStatus['data']['addresses'][$key]);
+ continue;
+ }
+ if ($this->currentAddress === $item['ip']) {
+ $item['default'] = true;
+ }
+ $sortIp[] = $item['ip'];
+ }
+ unset($item);
+ array_multisort($sortIp, SORT_STRING, $this->taskStatus['data']['addresses']);
+ return true;
+ }
+
+ private function updateLocalAddress()
+ {
+ $newAddress = Request::post('ip', 'none');
+ $valid = false;
+ foreach ($this->taskStatus['data']['addresses'] as $item) {
+ if ($item['ip'] !== $newAddress)
+ continue;
+ $valid = true;
+ break;
+ }
+ if ($valid) {
+ Property::setServerIp($newAddress);
+ global $tidIpxe;
+ if (isset($tidIpxe) && $tidIpxe !== false)
+ Util::redirect('?do=ServerSetup&taskid=' . $tidIpxe);
+ } else {
+ Message::addError('invalid-ip', $newAddress);
+ }
+ Util::redirect();
+ }
+
+ private function updatePxeMenu()
+ {
+ $timeout = Request::post('timeout', 10);
+ if ($timeout === '')
+ $timeout = 0;
+ if (!is_numeric($timeout) || $timeout < 0) {
+ Message::addError('value-invalid', 'timeout', $timeout);
+ }
+ $this->currentMenu['defaultentry'] = Request::post('defaultentry', 'net');
+ $this->currentMenu['timeout'] = $timeout;
+ $this->currentMenu['custom'] = Request::post('custom', '');
+ $this->currentMenu['masterpasswordclear'] = Request::post('masterpassword', '');
+ if (empty($this->currentMenu['masterpasswordclear']))
+ $this->currentMenu['masterpassword'] = 'invalid';
+ else
+ $this->currentMenu['masterpassword'] = Crypto::hash6($this->currentMenu['masterpasswordclear']);
+ Property::setBootMenu($this->currentMenu);
+ $id = Trigger::ipxe();
+ Util::redirect('?do=ServerSetup&taskid=' . $id);
+ }
+
+ private function downloadIpxe($ipxe){
+ $file = '/opt/taskmanager/data/ipxe/src/bin/ipxe.' . $ipxe;
+ if (file_exists($file)) {
+ header('Content-Description: File Transfer');
+ header('Content-Type: application/octet-stream');
+ header('Content-Disposition: attachment; filename='.basename($file));
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate');
+ header('Pragma: public');
+ header('Content-Length: ' . filesize($file));
+ ob_clean();
+ flush();
+ readfile($file);
+ exit();
+ }
+ }
+
+ private function updateIpxeScript(){
+ $newScript = Request::post('custom-script');
+ file_put_contents("/opt/taskmanager/data/pxe.embed",$newScript);
+ Util::redirect('?do=ServerSetup');
+ }
+
+ private function defaultIpxe(){
+ $default = file_get_contents("/opt/taskmanager/data/pxe_default.embed");
+ $default = str_replace("{{ip}}", "http://" . Property::getServerIp(), $default);
+ file_put_contents("/opt/taskmanager/data/pxe.embed",$default);
+ Util::redirect('?do=ServerSetup');
+ }
+}
diff --git a/modules-available/serversetup/templates/ipaddress.html b/modules-available/serversetup/templates/ipaddress.html
new file mode 100644
index 00000000..e4c1fba9
--- /dev/null
+++ b/modules-available/serversetup/templates/ipaddress.html
@@ -0,0 +1,35 @@
+<a class="btn btn-default" href="?do=Serversetup&amp;advanced=false" role="button"><strong>{{lang_ipxeSmp}}</strong></a></br></br>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_bootAddress}}
+ </div>
+ <div class="panel-body">
+ <p>
+ {{lang_chooseIP}}
+ </p>
+ <form method="post" action="?do=ServerSetup">
+ <input type="hidden" name="action" value="ip">
+ <input type="hidden" name="token" value="{{token}}">
+ <table class="slx-table">
+ {{#ips}}
+ <tr>
+ <td>{{ip}}</td>
+ {{#default}}
+ <td>
+ <span class="btn btn-success btn-xs"><span class="glyphicon glyphicon-ok"></span> {{lang_active}}</span>
+ </td>
+ {{/default}}
+ {{^default}}
+ <td>
+ <button class="btn btn-primary btn-xs" name="ip" value="{{ip}}"><span class="glyphicon glyphicon-flag"></span> {{lang_set}}</button>
+ </td>
+ {{/default}}
+ </tr>
+ {{/ips}}
+ </table>
+ <p>
+ {{lang_bootHint}}
+ </p>
+ </form>
+ </div>
+</div>
diff --git a/modules-available/serversetup/templates/ipxe-adv.html b/modules-available/serversetup/templates/ipxe-adv.html
new file mode 100644
index 00000000..00e9fd3a
--- /dev/null
+++ b/modules-available/serversetup/templates/ipxe-adv.html
@@ -0,0 +1,149 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_mountIpxe}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_ipxeInfo}}</p>
+ <label for="ext">{{lang_extension}}:</label>
+ <select name="ext" class="form-control">
+ <option value="kkpxe">.kkpxe</option>
+ <option value="usb">.usb</option>
+ <option value="iso">.iso</option>
+ </select>
+ <br>
+ <form method="post" action="?do=ServerSetup" style="display:inline;">
+ <input type="hidden" name="action" value="save-script">
+ <input type="hidden" name="token" value="{{token}}">
+
+ <label for="custom-script">{{lang_customScript}}</label>
+ <textarea class="form-control" name="custom-script" rows="9" style="resize:none">{{script}}</textarea>
+ <br>
+ <input class="btn btn-default btn-sm" type="submit" value="{{lang_saveScript}}" />
+ </form>
+ <form method="post" action="?do=ServerSetup" style="display:inline;">
+ <input type="hidden" name="action" value="default-script">
+ <input type="hidden" name="token" value="{{token}}">
+ <input class="btn btn-default btn-sm" type="submit" value="{{lang_restoreDefault}}" />
+ </form>
+ </div>
+ <div class="panel-footer">
+ <button id="mount-button" onclick="mountIpxe();" class="btn btn-primary" type="button" data-toggle="modal" data-target="#ipxe-modal" data-backdrop="static"> {{lang_compile}}
+ </button>
+ </div>
+</div>
+
+<form method="post" action="?do=ServerSetup">
+ <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;">
+ <input type="password" name="password_fake" id="password_fake" value="" style="display:none;">
+ <input type="hidden" name="action" value="ipxe">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_bootMenu}}
+ </div>
+ <div class="panel-body">
+ <p>
+ {{lang_bootInfo}}
+ </p>
+ <br>
+
+ <div class="form-group">
+ <strong>{{lang_bootBehavior}}</strong>
+ <div><label class="radio-inline"><input type="radio" name="defaultentry" value="net" {{active-net}}> bwLehrpool</label></div>
+ <div><label class="radio-inline"><input type="radio" name="defaultentry" value="hdd" {{active-hdd}}> {{lang_localHDD}}</label></div>
+ <div><label class="radio-inline"><input type="radio" name="defaultentry" value="custom" {{active-custom}}> {{lang_customEntry}} (&quot;custom&quot;)</label></div>
+ </div>
+
+ <div class="form-group">
+ <strong>{{lang_menuDisplayTime}}</strong>
+ <div class="input-group form-narrow">
+ <input type="text" class="form-control" name="timeout" value="{{timeout}}" pattern="\d+">
+ <span class="input-group-addon">{{lang_seconds}}</span>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <strong>{{lang_masterPassword}}</strong>
+ <div class="form-narrow">
+ <input type="{{password_type}}" class="form-control" name="masterpassword" value="{{masterpasswordclear}}">
+ </div>
+ <i>{{lang_masterPasswordHelp}}</i>
+ </div>
+
+ <div class="form-group">
+ <strong>{{lang_menuCustom}}</strong> <a class="btn btn-default btn-xs" data-toggle="modal" data-target="#help-custom"><span class="glyphicon glyphicon-question-sign"></span></a>
+ <textarea class="form-control" name="custom" rows="8">{{custom}}</textarea>
+ </div>
+ </div>
+
+ <div class="panel-footer">
+ <button class="btn btn-primary" name="action" value="ipxe">{{lang_bootMenuCreate}}</button>
+ </div>
+ </div>
+</form>
+
+<div class="modal fade" id="help-custom" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{lang_menuCustom}}</div>
+ <div class="modal-body">
+ {{lang_menuCustomHint1}}
+ <br>{{lang_example}}:
+ <pre>LABEL custom
+ MENU LABEL ^My Boot Entry
+ KERNEL http://1.2.3.4/kernel
+ INITRD http://1.2.3.4/initramfs-stage31
+ APPEND custom=option
+ IPAPPEND 3</pre>
+ {{lang_menuCustomHint2}} LABEL <strong>custom</strong>
+ {{lang_menuCustomHint3}}
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div>
+
+<div class="modal fade" id="ipxe-modal" tabindex="-1" role="dialog" aria-labelledby="ipxe-modal-label" aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="ipxe-modal-label">{{lang_compilingIpxe}}</h4>
+ </div>
+ <div class="modal-body" id="ipxe-modal-body">
+
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button id="download-btn" type="button" class="btn btn-primary" disabled="disabled" onclick="downloadIpxe()">{{lang_download}}</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+ function mountIpxe() {
+ document.getElementById('ipxe-modal-body').innerHTML = "<div class='alert alert-info' role='alert'>{{lang_ipxeWarning}}</div>"
+ + "{{lang_loading}} <img src='fonts/loader.gif'/>";
+ $("#download-btn").prop("disabled",true);
+ var xmlhttp = new XMLHttpRequest();
+ var extension = $("select[name=ext]").val();
+ xmlhttp.open("GET","?do=ServerSetup&async=true&submitTask=true&extension=" + extension,true);
+ xmlhttp.onreadystatechange= function() {
+ if (xmlhttp.readyState==4 && xmlhttp.status==200) {
+ var initResponse = xmlhttp.responseText;
+ if(initResponse != "success")
+ document.getElementById('ipxe-modal-body').innerHTML = initResponse;
+ else {
+ document.getElementById('ipxe-modal-body').innerHTML = "{{lang_success}}: ipxe." + extension;
+ $("#download-btn").prop("disabled",false);
+ }
+ }
+ }
+ xmlhttp.send();
+ }
+
+ function downloadIpxe() {
+ window.location = "?do=ServerSetup&download=" + $("select[name=ext]").val();
+ }
+</script>
diff --git a/modules-available/serversetup/templates/ipxe-smp.html b/modules-available/serversetup/templates/ipxe-smp.html
new file mode 100644
index 00000000..d126710c
--- /dev/null
+++ b/modules-available/serversetup/templates/ipxe-smp.html
@@ -0,0 +1,62 @@
+<a class="btn btn-default" href="?do=Serversetup&amp;advanced=true" role="button"><strong>{{lang_ipxeAdv}}</strong></a></br></br>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_mountIpxe}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_ipxeSmpInfo}}</p>
+ <button id="mount-button" onclick="mountIpxe('iso');" class="btn btn-primary" type="button" data-toggle="modal" data-target="#ipxe-modal" data-backdrop="static"> {{lang_compileIso}}
+ </button>
+ <button id="mount-button" onclick="mountIpxe('usb');" class="btn btn-primary" type="button" data-toggle="modal" data-target="#ipxe-modal" data-backdrop="static"> {{lang_compileUsb}}
+ </button>
+ <br>
+ </div>
+</div>
+
+
+<div class="modal fade" id="ipxe-modal" tabindex="-1" role="dialog" aria-labelledby="ipxe-modal-label" aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="ipxe-modal-label">{{lang_compilingIpxe}}</h4>
+ </div>
+ <div class="modal-body" id="ipxe-modal-body">
+
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button id="download-btn" type="button" class="btn btn-primary" disabled="disabled" onclick="downloadIpxe()">{{lang_download}}</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+ function mountIpxe(extension) {
+ document.getElementById('ipxe-modal-body').innerHTML = "<div class='alert alert-info' role='alert'>{{lang_ipxeWarning}}</div>"
+ + "{{lang_loading}} <img src='fonts/loader.gif'/>";
+ $("#download-btn").prop("disabled",true);
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("GET","?do=ServerSetup&async=true&submitTask=true&extension=" + extension,true);
+ xmlhttp.onreadystatechange= function() {
+ if (xmlhttp.readyState==4 && xmlhttp.status==200) {
+ var initResponse = xmlhttp.responseText;
+ if(initResponse != "success")
+ document.getElementById('ipxe-modal-body').innerHTML = initResponse;
+ else {
+ document.getElementById('ipxe-modal-body').innerHTML = "{{lang_success}}: ipxe." + extension;
+ document.getElementById('download-btn').setAttribute('onclick','downloadIpxe(\''+extension+'\')');
+ $("#download-btn").prop("disabled",false);
+ }
+ }
+ }
+ xmlhttp.send();
+ }
+
+ function downloadIpxe(extension) {
+ console.log("TESTE");
+ console.log(extension);
+ window.location = "?do=ServerSetup&download=" + extension;
+ }
+</script>
diff --git a/modules-available/serversetup/templates/ipxe_update.html b/modules-available/serversetup/templates/ipxe_update.html
new file mode 100644
index 00000000..9c598667
--- /dev/null
+++ b/modules-available/serversetup/templates/ipxe_update.html
@@ -0,0 +1,20 @@
+<div class="panel panel-default">
+ <div class="panel-heading">{{lang_menuGeneration}}</div>
+ <div class="panel-body">
+ <div data-tm-id="{{taskid}}" data-tm-log="error" data-tm-callback="restartCb">{{lang_menuGeneration}}</div>
+ <div id="genfailed" class="alert alert-danger" style="display:none">
+ {{lang_generationFailed}}
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript">
+ function restartCb(task)
+ {
+ if (!task || !task.statusCode)
+ return;
+ if (task.statusCode === 'TASK_ERROR') {
+ $('#genfailed').show('slow');
+ }
+ }
+</script>
diff --git a/modules-available/session/config.json b/modules-available/session/config.json
new file mode 100644
index 00000000..4da67ef8
--- /dev/null
+++ b/modules-available/session/config.json
@@ -0,0 +1,3 @@
+{
+ "enabled":"true"
+}
diff --git a/modules-available/session/lang/de/templates/page-login.json b/modules-available/session/lang/de/templates/page-login.json
new file mode 100644
index 00000000..03576a07
--- /dev/null
+++ b/modules-available/session/lang/de/templates/page-login.json
@@ -0,0 +1,8 @@
+{
+ "lang_enter": "Anmeldung",
+ "lang_login": "Anmelden",
+ "lang_password": "Passwort",
+ "lang_register": "Registrieren",
+ "lang_rememberID": "Angemeldet bleiben",
+ "lang_username": "Benutzerkennung"
+} \ No newline at end of file
diff --git a/modules-available/session/lang/en/module.json b/modules-available/session/lang/en/module.json
new file mode 100644
index 00000000..6df26f22
--- /dev/null
+++ b/modules-available/session/lang/en/module.json
@@ -0,0 +1,8 @@
+{
+ "lang_enter": "Enter",
+ "lang_login": "Login",
+ "lang_password": "Password",
+ "lang_register": "Register",
+ "lang_rememberID": "Remember User",
+ "lang_username": "Username"
+} \ No newline at end of file
diff --git a/modules-available/session/lang/en/templates/page-login.json b/modules-available/session/lang/en/templates/page-login.json
new file mode 100644
index 00000000..4b192a7a
--- /dev/null
+++ b/modules-available/session/lang/en/templates/page-login.json
@@ -0,0 +1,8 @@
+{
+ "lang_enter": "Enter",
+ "lang_login": "Login",
+ "lang_password": "Password",
+ "lang_register": "Register",
+ "lang_rememberID": "Remember ID",
+ "lang_username": "Username"
+} \ No newline at end of file
diff --git a/modules-available/session/lang/pt/module.json b/modules-available/session/lang/pt/module.json
new file mode 100644
index 00000000..3d1e19eb
--- /dev/null
+++ b/modules-available/session/lang/pt/module.json
@@ -0,0 +1,8 @@
+{
+ "lang_enter": "Entrar",
+ "lang_login": "Entrar",
+ "lang_password": "Senha",
+ "lang_register": "Registrar",
+ "lang_rememberID": "Lembrar Usu\u00e1rio",
+ "lang_username": "Nome de Usu\u00e1rio"
+} \ No newline at end of file
diff --git a/modules-available/session/page.inc.php b/modules-available/session/page.inc.php
new file mode 100644
index 00000000..ef135f9d
--- /dev/null
+++ b/modules-available/session/page.inc.php
@@ -0,0 +1,36 @@
+<?php
+
+class Page_Session extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+ if (Request::post('action') === 'login') {
+ // Login - see if already logged in
+ if (User::isLoggedIn()) // and then just redirect
+ Util::redirect('?do=Main');
+ // Else, try to log in
+ if (User::login(Request::post('user'), Request::post('pass')))
+ Util::redirect('?do=Main');
+ // Login credentials wrong - delay and show error message
+ sleep(1);
+ Message::addError('loginfail');
+ }
+ if (Request::post('action') === 'logout') {
+ // Log user out (or do nothing if not logged in)
+ User::logout();
+ Util::redirect('?do=Main');
+ }
+
+ if (User::isLoggedIn())
+ Util::redirect('?do=Main');
+ }
+
+ protected function doRender()
+ {
+ Render::setTitle(Dictionary::translate('lang_login'));
+ Render::addTemplate('page-login');
+ }
+
+}
diff --git a/modules-available/session/templates/page-login.html b/modules-available/session/templates/page-login.html
new file mode 100644
index 00000000..247e9a55
--- /dev/null
+++ b/modules-available/session/templates/page-login.html
@@ -0,0 +1,11 @@
+<form class="form-signin" action="?do=Session" method="post">
+ <h2 class="form-signin-heading">{{lang_enter}}</h2>
+ <input type="text" name="user" class="form-control" placeholder="{{lang_username}}" autofocus>
+ <input type="password" name="pass" class="form-control" placeholder="{{lang_password}}">
+ <!--label class="checkbox">
+ <input type="checkbox" name="remember" value="remember-me"> {{lang_rememberID}}
+ </label-->
+ <button class="btn btn-lg btn-primary btn-block" type="submit">{{lang_login}}</button>
+ <a class="btn btn-lg btn-primary btn-block" href="?do=AddUser">{{lang_register}}</a>
+ <input type="hidden" name="action" value="login">
+</form> \ No newline at end of file
diff --git a/modules-available/statistics/config.json b/modules-available/statistics/config.json
new file mode 100644
index 00000000..b0123727
--- /dev/null
+++ b/modules-available/statistics/config.json
@@ -0,0 +1,5 @@
+{
+ "category":"main.status",
+ "enabled":"true",
+ "permission":"0"
+}
diff --git a/modules-available/statistics/lang/de/module.json b/modules-available/statistics/lang/de/module.json
new file mode 100644
index 00000000..35a62cce
--- /dev/null
+++ b/modules-available/statistics/lang/de/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "Client-Statistiken",
+ "page_title": "Client-Statistiken"
+}
diff --git a/modules-available/statistics/lang/de/templates/clientlist.json b/modules-available/statistics/lang/de/templates/clientlist.json
new file mode 100644
index 00000000..a1022e69
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/clientlist.json
@@ -0,0 +1,14 @@
+{
+ "lang_address": "Adresse",
+ "lang_clientList": "Liste ausgew\u00e4hlter Rechner",
+ "lang_cpuModel": "CPU",
+ "lang_gbRam": "RAM",
+ "lang_kvmSupport": "64\u2009Bit G\u00e4ste",
+ "lang_lastSeen": "Zuletzt gesehen",
+ "lang_machine": "Rechner",
+ "lang_machineIdle": "Der Rechner ist eingeschaltet und wird zur Zeit nicht benutzt",
+ "lang_machineOccupied": "Der Rechner ist eingeschaltet und wird benutzt",
+ "lang_machineOff": "Der Rechner ist ausgeschaltet, oder hat kein bwLehrpool gebootet",
+ "lang_realCores": "Kerne",
+ "lang_tmpGb": "HDD-Temp"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/cpumodels.json b/modules-available/statistics/lang/de/templates/cpumodels.json
new file mode 100644
index 00000000..85cf517f
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/cpumodels.json
@@ -0,0 +1,6 @@
+{
+ "lang_cpuCores": "CPU-Kerne",
+ "lang_modelCount": "Anzahl",
+ "lang_modelName": "Modellname",
+ "lang_modelStats": "PC-Modelle"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/id44.json b/modules-available/statistics/lang/de/templates/id44.json
new file mode 100644
index 00000000..e2660f20
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/id44.json
@@ -0,0 +1,5 @@
+{
+ "lang_machineCount": "Anzahl",
+ "lang_partitionSize": "Gr\u00f6\u00dfe",
+ "lang_tempPartStats": "Tempor\u00e4re Partition"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/kvmstate.json b/modules-available/statistics/lang/de/templates/kvmstate.json
new file mode 100644
index 00000000..3e8f1a96
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/kvmstate.json
@@ -0,0 +1,5 @@
+{
+ "lang_kvmState": "Status",
+ "lang_kvmStats": "64\u2009Bit Gast-Support",
+ "lang_machineCount": "Anzahl"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/machine-hdds.json b/modules-available/statistics/lang/de/templates/machine-hdds.json
new file mode 100644
index 00000000..f2f26baf
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/machine-hdds.json
@@ -0,0 +1,13 @@
+{
+ "lang_hdds": "Festplatten",
+ "lang_hours": "Stunden",
+ "lang_modelNo": "Modell",
+ "lang_partName": "Name",
+ "lang_partSize": "Gr\u00f6\u00dfe",
+ "lang_partType": "Typ",
+ "lang_pendingSectors": "Potentiell defekte Sektoren",
+ "lang_powerOnTime": "Betriebszeit",
+ "lang_reallocatedSectors": "Defekte Sektoren",
+ "lang_serialNo": "Serien-Nr",
+ "lang_total": "Gesamt"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/machine-main.json b/modules-available/statistics/lang/de/templates/machine-main.json
new file mode 100644
index 00000000..47c3e266
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/machine-main.json
@@ -0,0 +1,26 @@
+{
+ "lang_64bitSupport": "64\u2009Bit Gast-Support",
+ "lang_cores": "Kerne",
+ "lang_cpuModel": "CPU-Modell",
+ "lang_firstSeen": "Erste Aktivit\u00e4t",
+ "lang_hardwareSummary": "Hardware",
+ "lang_hostname": "Hostname",
+ "lang_ip": "IP-Adresse",
+ "lang_lastBoot": "Letzter Boot",
+ "lang_lastSeen": "Letzte Aktivit\u00e4t",
+ "lang_macAddr": "MAC-Adresse",
+ "lang_machineIdle": "Eingeschaltet, ungenutzt",
+ "lang_machineOccupied": "Eingeschaltet, in Verwendung",
+ "lang_machineOccupiedBy": "In Verwendung durch",
+ "lang_machineOff": "Kein bwLehrpool gestartet",
+ "lang_machineSummary": "Zusammenfassung",
+ "lang_maximumAbbrev": "Max.",
+ "lang_model": "Modell",
+ "lang_ram": "Arbeitsspeicher",
+ "lang_ramSlots": "Speicher-Slots",
+ "lang_sockets": "Sockel",
+ "lang_tempPart": "Temp. Partition",
+ "lang_usageState": "Zustand",
+ "lang_uuid": "UUID",
+ "lang_virtualCores": "Virtuelle Kerne"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/machine-notes.json b/modules-available/statistics/lang/de/templates/machine-notes.json
new file mode 100644
index 00000000..f9df1b92
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/machine-notes.json
@@ -0,0 +1,4 @@
+{
+ "lang_notes": "Anmerkungen",
+ "lang_save": "Speichern"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/machine-usage.json b/modules-available/statistics/lang/de/templates/machine-usage.json
new file mode 100644
index 00000000..e2c9a979
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/machine-usage.json
@@ -0,0 +1,4 @@
+{
+ "lang_timebarDesc": "Visuelle Darstellung der letzten Tage. Rote Abschnitte zeigen, wann der Rechner belegt war, gr\u00fcne, wann er nicht verwendet wurde, aber eingeschaltet war. Die leicht abgedunkelten Abschnitte markieren N\u00e4chte (22 bis 8 Uhr).",
+ "lang_usageDetails": "Nutzungsdetails"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/memory.json b/modules-available/statistics/lang/de/templates/memory.json
new file mode 100644
index 00000000..b5fdac89
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/memory.json
@@ -0,0 +1,5 @@
+{
+ "lang_machineCount": "Anzahl",
+ "lang_memoryStats": "Arbeitsspeicher",
+ "lang_ramSize": "Gr\u00f6\u00dfe"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/newclients.json b/modules-available/statistics/lang/de/templates/newclients.json
new file mode 100644
index 00000000..f1353389
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/newclients.json
@@ -0,0 +1,4 @@
+{
+ "lang_machine": "Client",
+ "lang_newMachines": "Neue Ger\u00e4te"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/summary.json b/modules-available/statistics/lang/de/templates/summary.json
new file mode 100644
index 00000000..30189871
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/summary.json
@@ -0,0 +1,6 @@
+{
+ "lang_inUseMachines": "In Verwendung",
+ "lang_knownMachines": "Bekannte Clients",
+ "lang_onlineMachines": "Gestartete Clients",
+ "lang_withBadSectors": "Clients mit potentiell defekten Festplatten (mehr als 10 defekte Sektoren)"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/templates/syslog.json b/modules-available/statistics/lang/de/templates/syslog.json
new file mode 100644
index 00000000..960de730
--- /dev/null
+++ b/modules-available/statistics/lang/de/templates/syslog.json
@@ -0,0 +1,7 @@
+{
+ "lang_details": "Details",
+ "lang_event": "Ereignis",
+ "lang_logHeadline": "Logging",
+ "lang_more": "Mehr",
+ "lang_when": "Wann"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/module.json b/modules-available/statistics/lang/en/module.json
new file mode 100644
index 00000000..b4c6db44
--- /dev/null
+++ b/modules-available/statistics/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Client Statistics"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/clientlist.json b/modules-available/statistics/lang/en/templates/clientlist.json
new file mode 100644
index 00000000..ae692154
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/clientlist.json
@@ -0,0 +1,14 @@
+{
+ "lang_address": "Address",
+ "lang_clientList": "List of selected machines",
+ "lang_cpuModel": "CPU",
+ "lang_gbRam": "RAM",
+ "lang_kvmSupport": "64\u2009Bit guests",
+ "lang_lastSeen": "Last seen",
+ "lang_machine": "Machine",
+ "lang_machineIdle": "Machine is powered on and is not used",
+ "lang_machineOccupied": "Machine is powered on and in use",
+ "lang_machineOff": "Machine is powered down, or is not running bwLehrpool",
+ "lang_realCores": "Cores",
+ "lang_tmpGb": "HDD temp"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/cpumodels.json b/modules-available/statistics/lang/en/templates/cpumodels.json
new file mode 100644
index 00000000..864933dd
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/cpumodels.json
@@ -0,0 +1,6 @@
+{
+ "lang_cpuCores": "CPU cores",
+ "lang_modelCount": "Count",
+ "lang_modelName": "Model name",
+ "lang_modelStats": "PC models"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/id44.json b/modules-available/statistics/lang/en/templates/id44.json
new file mode 100644
index 00000000..0d0081a8
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/id44.json
@@ -0,0 +1,5 @@
+{
+ "lang_machineCount": "Count",
+ "lang_partitionSize": "Size",
+ "lang_tempPartStats": "Temporary partition"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/kvmstate.json b/modules-available/statistics/lang/en/templates/kvmstate.json
new file mode 100644
index 00000000..b4846473
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/kvmstate.json
@@ -0,0 +1,5 @@
+{
+ "lang_kvmState": "State",
+ "lang_kvmStats": "64\u2009Bit guest support",
+ "lang_machineCount": "Count"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/machine-hdds.json b/modules-available/statistics/lang/en/templates/machine-hdds.json
new file mode 100644
index 00000000..8ce6801d
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/machine-hdds.json
@@ -0,0 +1,13 @@
+{
+ "lang_hdds": "Hard disk drives",
+ "lang_hours": "hours",
+ "lang_modelNo": "Model",
+ "lang_partName": "Name",
+ "lang_partSize": "Size",
+ "lang_partType": "Type",
+ "lang_pendingSectors": "Sectors pending reallocation",
+ "lang_powerOnTime": "Power on time",
+ "lang_reallocatedSectors": "Bad sectors",
+ "lang_serialNo": "Serial no",
+ "lang_total": "Total"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/machine-main.json b/modules-available/statistics/lang/en/templates/machine-main.json
new file mode 100644
index 00000000..1addd437
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/machine-main.json
@@ -0,0 +1,26 @@
+{
+ "lang_64bitSupport": "64\u2009Bit guest support",
+ "lang_cores": "Cores",
+ "lang_cpuModel": "CPU model",
+ "lang_firstSeen": "First seen",
+ "lang_hardwareSummary": "Hardware",
+ "lang_hostname": "Hostname",
+ "lang_ip": "IP address",
+ "lang_lastBoot": "Last boot",
+ "lang_lastSeen": "Last activity",
+ "lang_macAddr": "MAC address",
+ "lang_machineIdle": "Powered on, unused",
+ "lang_machineOccupied": "Powered on, in use",
+ "lang_machineOccupiedBy": "In use by",
+ "lang_machineOff": "bwLehrpool not running",
+ "lang_machineSummary": "Summary",
+ "lang_maximumAbbrev": "max.",
+ "lang_model": "Model",
+ "lang_ram": "Memory",
+ "lang_ramSlots": "Memory slots",
+ "lang_sockets": "Sockets",
+ "lang_tempPart": "Temp. partition",
+ "lang_usageState": "State",
+ "lang_uuid": "UUID",
+ "lang_virtualCores": "Virtual cores"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/machine-notes.json b/modules-available/statistics/lang/en/templates/machine-notes.json
new file mode 100644
index 00000000..7a13f28a
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/machine-notes.json
@@ -0,0 +1,4 @@
+{
+ "lang_notes": "Notes",
+ "lang_save": "Save"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/machine-usage.json b/modules-available/statistics/lang/en/templates/machine-usage.json
new file mode 100644
index 00000000..398996f6
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/machine-usage.json
@@ -0,0 +1,4 @@
+{
+ "lang_timebarDesc": "Visual representation of the last few days. Red parts mark periods where the client was occupied, green parts where the client was idle. Dimmed parts mark nights (10pm to 8am).",
+ "lang_usageDetails": "Detailed usage"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/memory.json b/modules-available/statistics/lang/en/templates/memory.json
new file mode 100644
index 00000000..decdd021
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/memory.json
@@ -0,0 +1,5 @@
+{
+ "lang_machineCount": "Count",
+ "lang_memoryStats": "Memory",
+ "lang_ramSize": "Size"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/newclients.json b/modules-available/statistics/lang/en/templates/newclients.json
new file mode 100644
index 00000000..f7e55f3f
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/newclients.json
@@ -0,0 +1,4 @@
+{
+ "lang_machine": "Client",
+ "lang_newMachines": "New machines"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/summary.json b/modules-available/statistics/lang/en/templates/summary.json
new file mode 100644
index 00000000..b57c290f
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/summary.json
@@ -0,0 +1,6 @@
+{
+ "lang_inUseMachines": "In use",
+ "lang_knownMachines": "Known clients",
+ "lang_onlineMachines": "Online clients",
+ "lang_withBadSectors": "Clients with potentially bad HDDs (more than 10 reallocated sectors)"
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/templates/syslog.json b/modules-available/statistics/lang/en/templates/syslog.json
new file mode 100644
index 00000000..6737ca68
--- /dev/null
+++ b/modules-available/statistics/lang/en/templates/syslog.json
@@ -0,0 +1,7 @@
+{
+ "lang_details": "Details",
+ "lang_event": "Event",
+ "lang_logHeadline": "Logging",
+ "lang_more": "More",
+ "lang_when": "When"
+} \ No newline at end of file
diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php
new file mode 100644
index 00000000..faf88521
--- /dev/null
+++ b/modules-available/statistics/page.inc.php
@@ -0,0 +1,792 @@
+<?php
+
+global $STATS_COLORS, $SIZE_ID44, $SIZE_RAM;
+$STATS_COLORS = array();
+for ($i = 0; $i < 10; ++$i) {
+ $STATS_COLORS[] = '#55' . sprintf('%02s%02s', dechex((($i+1)*($i+1)) / .3922), dechex(abs((5-$i) * 51)));
+}
+//$STATS_COLORS = array('#57e', '#ee8', '#5ae', '#fb7', '#6d7', '#e77', '#3af', '#666', '#e0e', '#999');
+$SIZE_ID44 = array(0, 8, 16, 24, 30, 40, 50, 60, 80, 100, 120, 150, 180, 250, 300, 350, 400, 450, 500);
+$SIZE_RAM = array(1, 2, 3, 4, 6, 8, 10, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 320, 480, 512, 768, 1024);
+
+class Page_Statistics extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ $action = Request::post('action');
+ if ($action === 'setnotes') {
+ $uuid = Request::post('uuid', '', 'string');
+ $text = Request::post('content', '', 'string');
+ if (empty($text)) {
+ $text = null;
+ }
+ Database::exec('UPDATE machine SET notes = :text WHERE machineuuid = :uuid', array(
+ 'uuid' => $uuid,
+ 'text' => $text
+ ));
+ Message::addSuccess('notes-saved');
+ Util::redirect('?do=Statistics&uuid=' . $uuid);
+ }
+ }
+
+ protected function doRender()
+ {
+ Render::setTitle(Dictionary::translate('lang_titleClientStatistics'));
+ $uuid = Request::get('uuid', false, 'string');
+ if ($uuid !== false) {
+ $this->showMachine($uuid);
+ return;
+ }
+ $filter = Request::get('filter', false, 'string');
+ if ($filter !== false) {
+ $argument = Request::get('argument', false, 'string');
+ $this->showMachineList($filter, $argument);
+ return;
+ }
+ Render::addScriptBottom('chart.min');
+ Render::openTag('div', array('class' => 'row'));
+ $this->showSummary();
+ $this->showMemory();
+ $this->showId44();
+ $this->showKvmState();
+ $this->showLatestMachines();
+ $this->showSystemModels();
+ Render::closeTag('div');
+ }
+
+ private function capChart(&$json, $cutoff, $minSlice = 0.015)
+ {
+ $total = 0;
+ foreach ($json as $entry) {
+ $total += $entry['value'];
+ }
+ if ($total === 0)
+ return;
+ $cap = ceil($total * $cutoff);
+ $accounted = 0;
+ $id = 0;
+ foreach ($json as $entry) {
+ if (($accounted >= $cap || $entry['value'] / $total < $minSlice) && $id >= 3) break;
+ $id++;
+ $accounted += $entry['value'];
+ }
+ $json = array_slice($json, 0, $id);
+ if ($accounted / $total < 0.99) {
+ $json[] = array(
+ 'color' => '#eee',
+ 'label' => 'invalid',
+ 'value' => ($total - $accounted)
+ );
+ }
+ }
+
+ private function showSummary()
+ {
+ $cutoff = time() - 86400 * 30;
+ $online = time() - 610;
+ $known = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $cutoff");
+ $on = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $online");
+ $used = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $online AND logintime <> 0");
+ $hdd = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE badsectors > 10 AND lastseen > $cutoff");
+ if ($on['val'] != 0) {
+ $usedpercent = round($used['val'] / $on['val'] * 100);
+ } else {
+ $usedpercent = 0;
+ }
+ $data = array(
+ 'known' => $known['val'],
+ 'online' => $on['val'],
+ 'used' => $used['val'],
+ 'usedpercent' => $usedpercent,
+ 'badhdd' => $hdd['val']
+ );
+ // Graph
+ $cutoff = time() - 2*86400;
+ $res = Database::simpleQuery("SELECT dateline, data FROM statistic WHERE typeid = '~stats' AND dateline > $cutoff ORDER BY dateline ASC");
+ $labels = array();
+ $points1 = array('data' => array(), 'label' => 'Online', 'fillColor' => '#efe', 'strokeColor' => '#aea', 'pointColor' => '#7e7', 'pointStrokeColor' => '#fff', 'pointHighlightFill' => '#fff', 'pointHighlightStroke' => '#7e7');
+ $points2 = array('data' => array(), 'label' => 'In use', 'fillColor' => '#fee', 'strokeColor' => '#eaa', 'pointColor' => '#e77', 'pointStrokeColor' => '#fff', 'pointHighlightFill' => '#fff', 'pointHighlightStroke' => '#e77');
+ $sum = 0;
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $x = explode('#', $row['data']);
+ if ($sum === 0) {
+ $labels[] = date('H:i', $row['dateline']);
+ } else {
+ $x[1] = max($x[1], array_pop($points1['data']));
+ $x[2] = max($x[2], array_pop($points2['data']));
+ }
+ $points1['data'][] = $x[1];
+ $points2['data'][] = $x[2];
+ $sum++;
+ if ($sum === 12) {
+ $sum = 0;
+ }
+ }
+ $data['json'] = json_encode(array('labels' => $labels, 'datasets' => array($points1, $points2)));
+ // Draw
+ Render::addTemplate('summary', $data);
+ }
+
+ private function showSystemModels()
+ {
+ global $STATS_COLORS;
+ $res = Database::simpleQuery("SELECT systemmodel, Round(AVG(realcores)) AS cores, Count(*) AS `count` FROM machine"
+ . " GROUP BY systemmodel ORDER BY `count` DESC, systemmodel ASC");
+ $lines = array();
+ $json = array();
+ $id = 0;
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (empty($row['systemmodel'])) continue;
+ settype($row['count'], 'integer');
+ $row['id'] = 'systemid' . $id;
+ $row['urlsystemmodel'] = urlencode($row['systemmodel']);
+ $lines[] = $row;
+ $json[] = array(
+ 'color' => $STATS_COLORS[$id % count($STATS_COLORS)],
+ 'label' => 'systemid' . $id,
+ 'value' => $row['count']
+ );
+ ++$id;
+ }
+ $this->capChart($json, 0.92);
+ Render::addTemplate('cpumodels', array('rows' => $lines, 'json' => json_encode($json)));
+ }
+
+ private function showMemory()
+ {
+ global $STATS_COLORS, $SIZE_RAM;
+ $res = Database::simpleQuery("SELECT mbram, Count(*) AS `count` FROM machine GROUP BY mbram");
+ $lines = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $gb = ceil($row['mbram'] / 1024);
+ for ($i = 1; $i < count($SIZE_RAM); ++$i) {
+ if ($SIZE_RAM[$i] < $gb) continue;
+ if ($SIZE_RAM[$i] - $gb >= $gb - $SIZE_RAM[$i-1]) --$i;
+ $gb = $SIZE_RAM[$i];
+ break;
+ }
+ if (isset($lines[$gb])) {
+ $lines[$gb] += $row['count'];
+ } else {
+ $lines[$gb] = $row['count'];
+ }
+ }
+ asort($lines);
+ $data = array('rows' => array());
+ $json = array();
+ $id = 0;
+ foreach (array_reverse($lines, true) as $k => $v) {
+ $data['rows'][] = array('gb' => $k, 'count' => $v, 'class' => $this->ramColorClass($k * 1024));
+ $json[] = array(
+ 'color' => $STATS_COLORS[$id % count($STATS_COLORS)],
+ 'label' => (string)$k,
+ 'value' => $v
+ );
+ ++$id;
+ }
+ $this->capChart($json, 0.92);
+ $data['json'] = json_encode($json);
+ Render::addTemplate('memory', $data);
+ }
+
+ private function showKvmState()
+ {
+ $colors = array('UNKNOWN' => '#666', 'UNSUPPORTED' => '#ea5', 'DISABLED' => '#e55', 'ENABLED' => '#6d6');
+ $res = Database::simpleQuery("SELECT kvmstate, Count(*) AS `count` FROM machine GROUP BY kvmstate ORDER BY `count` DESC");
+ $lines = array();
+ $json = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $lines[] = $row;
+ $json[] = array(
+ 'color' => isset($colors[$row['kvmstate']]) ? $colors[$row['kvmstate']] : '#000',
+ 'label' => $row['kvmstate'],
+ 'value' => $row['count']
+ );
+ }
+ Render::addTemplate('kvmstate', array('rows' => $lines, 'json' => json_encode($json)));
+ }
+
+ private function showId44()
+ {
+ global $STATS_COLORS, $SIZE_ID44;
+ $res = Database::simpleQuery("SELECT id44mb, Count(*) AS `count` FROM machine GROUP BY id44mb");
+ $lines = array();
+ $total = 0;
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $total += $row['count'];
+ $gb = ceil($row['id44mb'] / 1024);
+ for ($i = 1; $i < count($SIZE_ID44); ++$i) {
+ if ($SIZE_ID44[$i] < $gb) continue;
+ if ($SIZE_ID44[$i] - $gb >= $gb - $SIZE_ID44[$i-1]) --$i;
+ $gb = $SIZE_ID44[$i];
+ break;
+ }
+ if (isset($lines[$gb])) {
+ $lines[$gb] += $row['count'];
+ } else {
+ $lines[$gb] = $row['count'];
+ }
+ }
+ asort($lines);
+ $data = array('rows' => array());
+ $json = array();
+ $id = 0;
+ foreach (array_reverse($lines, true) as $k => $v) {
+ $data['rows'][] = array('gb' => $k, 'count' => $v, 'class' => $this->hddColorClass($k));
+ if ($k === 0) {
+ $color = '#e55';
+ } else {
+ $color = $STATS_COLORS[$id++ % count($STATS_COLORS)];
+ }
+ $json[] = array(
+ 'color' => $color,
+ 'label' => (string)$k,
+ 'value' => $v
+ );
+ }
+ $this->capChart($json, 0.95);
+ $data['json'] = json_encode($json);
+ Render::addTemplate('id44', $data);
+ }
+
+ private function showLatestMachines()
+ {
+ $data = array('cutoff' => ceil(time() / 3600) * 3600 - 86400 * 7);
+ $res = Database::simpleQuery("SELECT machineuuid, clientip, hostname, firstseen, mbram, kvmstate, id44mb FROM machine"
+ . " WHERE firstseen > :cutoff ORDER BY firstseen DESC LIMIT 32", $data);
+ $rows = array();
+ $count = 0;
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (empty($row['hostname'])) {
+ $row['hostname'] = $row['clientip'];
+ }
+ $row['firstseen'] = date('d.m. H:i', $row['firstseen']);
+ $row['gbram'] = round(round($row['mbram'] / 500) / 2, 1); // Trial and error until we got "expected" rounding..
+ $row['gbtmp'] = round($row['id44mb'] / 1024);
+ $row['ramclass'] = $this->ramColorClass($row['mbram']);
+ $row['kvmclass'] = $this->kvmColorClass($row['kvmstate']);
+ $row['hddclass'] = $this->hddColorClass($row['gbtmp']);
+ $row['kvmicon'] = $row['kvmstate'] === 'ENABLED' ? '✓' : '✗';
+ if (++$count > 5) {
+ $row['style'] = 'display:none';
+ }
+ $rows[] = $row;
+ }
+ Render::addTemplate('newclients', array('rows' => $rows, 'openbutton' => $count > 5));
+ }
+
+ private function showMachineList($filter, $argument)
+ {
+ global $SIZE_RAM, $SIZE_ID44;
+ $join = '';
+ $filters = array('cpumodel', 'realcores', 'kvmstate', 'clientip', 'macaddr', 'machineuuid', 'systemmodel');
+ if (in_array($filter, $filters)) {
+ // Simple filters mapping into db
+ $where = " $filter = :argument";
+ $args = array('argument' => $argument);
+ } elseif ($filter === 'gbram') {
+ // Memory by rounded GB
+ $lower = floor($this->findBestValue($SIZE_RAM, $argument, false) * 1024 - 100);
+ $upper = ceil($this->findBestValue($SIZE_RAM, $argument, true) * 1024 + 100);
+ $where = " mbram BETWEEN $lower AND $upper";
+ $args = array();
+ } elseif ($filter === 'hddgb') {
+ // HDD by rounded GB
+ $lower = floor($this->findBestValue($SIZE_ID44, $argument, false) * 1024 - 100);
+ $upper = ceil($this->findBestValue($SIZE_ID44, $argument, true) * 1024 + 100);
+ $where = " id44mb BETWEEN $lower AND $upper";
+ $args = array();
+ } elseif ($filter === 'subnet') {
+ $argument = preg_replace('/[^0-9\.:]/', '', $argument);
+ $where = " clientip LIKE '$argument%'";
+ $args = array();
+ } elseif ($filter === 'badsectors') {
+ $where = " badsectors >= :argument ";
+ $args = array('argument' => $argument);
+ } elseif ($filter === 'state') {
+ if ( $argument === 'on') {
+ $where = " lastseen + 600 > UNIX_TIMESTAMP() ";
+ } elseif ($argument === 'off') {
+ $where = " lastseen + 600 < UNIX_TIMESTAMP() ";
+ } elseif ($argument === 'idle') {
+ $where = " lastseen + 600 > UNIX_TIMESTAMP() AND logintime = 0 ";
+ } elseif ($argument === 'occupied') {
+ $where = " lastseen + 600 > UNIX_TIMESTAMP() AND logintime <> 0 ";
+ } else {
+ Message::addError('invalid-filter');
+ return;
+ }
+ } elseif ($filter === 'location') {
+ $where = "subnet.locationid = :lid OR machine.locationid = :lid";
+ $join = " INNER JOIN subnet ON (INET_ATON(clientip) BETWEEN startaddr AND endaddr) ";
+ $args = array('lid' => (int)$argument);
+ } else {
+ Message::addError('invalid-filter');
+ return;
+ }
+ $res = Database::simpleQuery("SELECT machineuuid, macaddr, clientip, firstseen, lastseen,"
+ . " logintime, lastboot, realcores, mbram, kvmstate, cpumodel, id44mb, hostname, notes IS NOT NULL AS hasnotes, badsectors FROM machine"
+ . " $join WHERE $where ORDER BY lastseen DESC, clientip ASC", $args);
+ $rows = array();
+ $NOW = time();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if ($NOW - $row['lastseen'] > 610) {
+ $row['state_off'] = true;
+ } elseif ($row['logintime'] == 0) {
+ $row['state_idle'] = true;
+ } else {
+ $row['state_occupied'] = true;
+ }
+ //$row['firstseen'] = date('d.m.Y H:i', $row['firstseen']);
+ $row['lastseen'] = date('d.m. H:i', $row['lastseen']);
+ //$row['lastboot'] = date('d.m. H:i', $row['lastboot']);
+ $row['gbram'] = round(round($row['mbram'] / 500) / 2, 1); // Trial and error until we got "expected" rounding..
+ $row['gbtmp'] = round($row['id44mb'] / 1024);
+ $octets = explode('.', $row['clientip']);
+ if (count($octets) === 4) {
+ $row['subnet'] = "$octets[0].$octets[1].$octets[2].";
+ $row['lastoctet'] = $octets[3];
+ }
+ $row['ramclass'] = $this->ramColorClass($row['mbram']);
+ $row['kvmclass'] = $this->kvmColorClass($row['kvmstate']);
+ $row['hddclass'] = $this->hddColorClass($row['gbtmp']);
+ if (empty($row['hostname'])) $row['hostname'] = $row['clientip'];
+ $rows[] = $row;
+ }
+ Render::addTemplate('clientlist', array('rows' => $rows, 'filter' => $filter, 'argument' => $argument));
+ }
+
+ private function ramColorClass($mb)
+ {
+ if ($mb < 1500)
+ return 'danger';
+ if ($mb < 2500)
+ return 'warning';
+ return '';
+ }
+
+ private function kvmColorClass($state)
+ {
+ if ($state === 'DISABLED')
+ return 'danger';
+ if ($state === 'UNKNOWN' || $state === 'UNSUPPORTED')
+ return 'warning';
+ return '';
+ }
+
+ private function hddColorClass($gb)
+ {
+ if ($gb < 7)
+ return 'danger';
+ if ($gb < 25)
+ return 'warning';
+ return '';
+ }
+
+ private function findBestValue($array, $value, $up)
+ {
+ $best = 0;
+ for ($i = 0; $i < count($array); ++$i) {
+ if (abs($array[$i] - $value) < abs($array[$best] - $value)) {
+ $best = $i;
+ }
+ }
+ if (!$up && $best === 0) {
+ return $array[0];
+ }
+ if ($up && $best + 1 === count($array)) {
+ return $array[$best];
+ }
+ if ($up) {
+ return ($array[$best] + $array[$best + 1]) / 2;
+ }
+ return ($array[$best] + $array[$best - 1]) / 2;
+ }
+
+ private function fillSessionInfo(&$row)
+ {
+ $res = Database::simpleQuery("SELECT dateline, username, data FROM statistic"
+ . " WHERE clientip = :ip AND typeid = '.vmchooser-session-name'"
+ . " AND dateline BETWEEN :start AND :end", array(
+ 'ip' => $row['clientip'],
+ 'start' => $row['logintime'] - 60,
+ 'end' => $row['logintime'] + 300
+ ));
+ $session = false;
+ while ($r = $res->fetch(PDO::FETCH_ASSOC)) {
+ if ($session === false || abs($session['dateline'] - $row['logintime']) > abs($r['dateline'] - $row['logintime'])) {
+ $session = $r;
+ }
+ }
+ if ($session !== false) {
+ $row['session'] = $session['data'];
+ $row['username'] = $session['username'];
+ }
+ }
+
+ private function showMachine($uuid)
+ {
+ $client = Database::queryFirst("SELECT machineuuid, macaddr, clientip, firstseen, lastseen, logintime, lastboot,"
+ . " mbram, kvmstate, cpumodel, id44mb, data, hostname, notes FROM machine WHERE machineuuid = :uuid",
+ array('uuid' => $uuid));
+ // Mangle fields
+ $NOW = time();
+ if ($NOW - $client['lastseen'] > 610) {
+ $client['state_off'] = true;
+ } elseif ($client['logintime'] == 0) {
+ $client['state_idle'] = true;
+ } else {
+ $client['state_occupied'] = true;
+ $this->fillSessionInfo($client);
+ }
+ $client['firstseen_s'] = date('d.m.Y H:i', $client['firstseen']);
+ $client['lastseen_s'] = date('d.m.Y H:i', $client['lastseen']);
+ $uptime = $NOW - $client['lastboot'];
+ $client['lastboot_s'] = date('d.m.Y H:i', $client['lastboot']) . ' (Up ' . floor($uptime / 86400) . 'd ' . gmdate('H:i', $uptime) . ')';
+ $client['logintime_s'] = date('d.m.Y H:i', $client['logintime']);
+ $client['gbram'] = round(round($client['mbram'] / 500) / 2, 1);
+ $client['gbtmp'] = round($client['id44mb'] / 1024);
+ $client['ramclass'] = $this->ramColorClass($client['mbram']);
+ $client['kvmclass'] = $this->kvmColorClass($client['kvmstate']);
+ $client['hddclass'] = $this->hddColorClass($client['gbtmp']);
+ // Parse the giant blob of data
+ $hdds = array();
+ if (preg_match_all('/##### ([^#]+) #+$(.*?)^#####/ims', $client['data'] . '########', $out, PREG_SET_ORDER)) {
+ foreach ($out as $section) {
+ if ($section[1] === 'CPU') {
+ $this->parseCpu($client, $section[2]);
+ }
+ if ($section[1] === 'dmidecode') {
+ $this->parseDmiDecode($client, $section[2]);
+ }
+ if ($section[1] === 'Partition tables') {
+ $this->parseHdd($hdds, $section[2]);
+ }
+ if (isset($hdds['hdds']) && $section[1] === 'smartctl') {
+ // This currently required that the partition table section comes first...
+ $this->parseSmartctl($hdds['hdds'], $section[2]);
+ }
+ }
+ }
+ unset($client['data']);
+ // Throw output at user
+ Render::addTemplate('machine-main', $client);
+ // Sessions
+ $NOW = time();
+ $cutoff = $NOW - 86400 * 7;
+ //if ($cutoff < $client['firstseen']) $cutoff = $client['firstseen'];
+ $scale = 100 / ($NOW - $cutoff);
+ $res = Database::simpleQuery("SELECT dateline, typeid, data FROM statistic"
+ . " WHERE dateline > :cutoff AND typeid IN ('~session-length', '~offline-length') AND machineuuid = :uuid ORDER BY dateline ASC", array(
+ 'cutoff' => $cutoff - 86400 * 14,
+ 'uuid' => $uuid
+ ));
+ $spans['rows'] = array();
+ $spans['graph'] = '';
+ $last = false;
+ $first = true;
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if ($first && $row['dateline'] > $cutoff && $client['lastboot'] > $cutoff) {
+ // Special case: offline before
+ $spans['graph'] .= '<div style="background:#444;left:0%;width:' . round((min($row['dateline'], $client['lastboot']) - $cutoff) * $scale, 2) . '%">&nbsp;</div>';
+ }
+ $first = false;
+ if ($row['dateline'] + $row['data'] < $cutoff || $row['data'] > 864000) continue;
+ if ($last !== false && abs($last['dateline'] - $row['dateline']) < 30
+ && abs($last['data'] - $row['data']) < 30) continue;
+ if ($last !== false && $last['dateline'] + $last['data'] > $row['dateline']) {
+ $point = $last['dateline'] + $last['data'];
+ $row['data'] -= ($point - $row['dateline']);
+ $row['dateline'] = $point;
+ }
+ if ($row['dateline'] < $cutoff) {
+ $row['data'] -= ($cutoff - $row['dateline']);
+ $row['dateline'] = $cutoff;
+ }
+ $row['from'] = date('d.m. H:i', $row['dateline']);
+ $row['duration'] = floor($row['data'] / 86400) . 'd ' . gmdate('H:i', $row['data']);
+ if ($row['typeid'] === '~offline-length') {
+ $row['glyph'] = 'off';
+ $color = '#444';
+ } else {
+ $row['glyph'] = 'user';
+ $color = '#e77';
+ }
+ $spans['graph'] .= '<div style="background:' . $color . ';left:' . round(($row['dateline'] - $cutoff) * $scale, 2) . '%;width:' . round(($row['data']) * $scale, 2) . '%">&nbsp;</div>';
+ $spans['rows'][] = $row;
+ $last = $row;
+ }
+ if ($first && $client['lastboot'] > $cutoff) {
+ // Special case: offline before
+ $spans['graph'] .= '<div style="background:#444;left:0%;width:' . round(($client['lastboot'] - $cutoff) * $scale, 2) . '%">&nbsp;</div>';
+ }
+ if (isset($client['state_occupied'])) {
+ $spans['graph'] .= '<div style="background:#e99;left:' . round(($client['logintime'] - $cutoff) * $scale, 2) . '%;width:' . round(($NOW - $client['logintime'] + 900) * $scale, 2) . '%">&nbsp;</div>';
+ } elseif (isset($client['state_off'])) {
+ $spans['graph'] .= '<div style="background:#444;left:' . round(($client['lastseen'] - $cutoff) * $scale, 2) . '%;width:' . round(($NOW - $client['lastseen'] + 900) * $scale, 2) . '%">&nbsp;</div>';
+ }
+ $t = explode('-', date('Y-n-j-G', $cutoff));
+ if ($t[3] >= 8 && $t[3] <= 22) {
+ $start = mktime(22, 0, 0, $t[1], $t[2], $t[0]);
+ } else {
+ $start = mktime(22, 0, 0, $t[1], $t[2] - 1, $t[0]);
+ }
+ for ($i = $start; $i < $NOW; $i += 86400) {
+ $spans['graph'] .= '<div style="background:rgba(0,0,90,.2);left:' . round(($i - $cutoff) * $scale, 2) . '%;width:' . round((10 * 3600) * $scale, 2) . '%">&nbsp;</div>';
+ }
+ if (count($spans['rows']) > 10) {
+ $spans['hasrows2'] = true;
+ $spans['rows2'] = array_slice($spans['rows'], ceil(count($spans['rows']) / 2));
+ $spans['rows'] = array_slice($spans['rows'], 0, ceil(count($spans['rows']) / 2));
+ }
+ Render::addTemplate('machine-usage', $spans);
+ // Any hdds?
+ if (!empty($hdds['hdds'])) {
+ Render::addScriptBottom('chart.min');
+ Render::addTemplate('machine-hdds', $hdds);
+ }
+ // Client log
+ $lres = Database::simpleQuery("SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog"
+ . " WHERE clientip = :clientip ORDER BY logid DESC LIMIT 25", array('clientip' => $client['clientip']));
+ $today = date('d.m.Y');
+ $yesterday = date('d.m.Y', time() - 86400);
+ $count = 0;
+ $log = array();
+ while ($row = $lres->fetch(PDO::FETCH_ASSOC)) {
+ if (substr($row['description'], -5) === 'on :0' && strpos($row['description'], 'root logged') === false) continue;
+ $day = date('d.m.Y', $row['dateline']);
+ if ($day === $today) {
+ $day = Dictionary::translate('today');
+ } elseif ($day === $yesterday) {
+ $day = Dictionary::translate('yesterday');
+ }
+ $row['date'] = $day . date(' H:i', $row['dateline']);
+ $row['icon'] = $this->eventToIconName($row['logtypeid']);
+ $log[] = $row;
+ if (++$count === 10) break;
+ }
+ Render::addTemplate('syslog', array(
+ 'clientip' => $client['clientip'],
+ 'list' => $log
+ ));
+ // Notes
+ Render::addTemplate('machine-notes', $client);
+ }
+
+ private function eventToIconName($event)
+ {
+ switch ($event) {
+ case 'session-open':
+ return 'glyphicon-log-in';
+ case 'session-close':
+ return 'glyphicon-log-out';
+ case 'partition-swap':
+ return 'glyphicon-info-sign';
+ case 'partition-temp':
+ case 'smartctl-realloc':
+ return 'glyphicon-exclamation-sign';
+ default:
+ return 'glyphicon-minus';
+ }
+ }
+
+ private function parseCpu(&$row, $data)
+ {
+ if (0 >= preg_match_all('/^(.+):\s+(\d+)$/im', $data, $out, PREG_SET_ORDER)) return;
+ foreach ($out as $entry) {
+ $row[str_replace(' ', '', $entry[1])] = $entry[2];
+ }
+ }
+
+ private function parseDmiDecode(&$row, $data)
+ {
+ $lines = preg_split("/[\r\n]+/", $data);
+ $section = false;
+ $ramOk = false;
+ $ramForm = $ramType = $ramSpeed = $ramClockSpeed = false;
+ foreach ($lines as $line) {
+ if (empty($line)) continue;
+ if ($line{0} !== "\t" && $line{0} !== ' ') {
+ $section = $line;
+ $ramOk = false;
+ if (($ramForm || $ramType) && ($ramSpeed || $ramClockSpeed)) {
+ if (isset($row['ramtype']) && !$ramClockSpeed) continue;
+ $row['ramtype'] = $ramType . ' ' . $ramForm;
+ if ($ramClockSpeed) $row['ramtype'] .= ', ' . $ramClockSpeed;
+ elseif ($ramSpeed) $row['ramtype'] .= ', ' . $ramSpeed;
+ $ramForm = false;
+ $ramType = false;
+ $ramClockSpeed = false;
+ }
+ continue;
+ }
+ if ($section === 'System Information' || $section === 'Base Board Information') {
+ if (empty($row['pcmodel']) && preg_match('/^\s*Product Name: +(\S.+?) *$/i', $line, $out)) {
+ $row['pcmodel'] = $out[1];
+ }
+ if (empty($row['manufacturer']) && preg_match('/^\s*Manufacturer: +(\S.+?) *$/i', $line, $out)) {
+ $row['manufacturer'] = $out[1];
+ }
+ }
+ else if ($section === 'Physical Memory Array') {
+ if (!$ramOk && preg_match('/Use: System Memory/i', $line)) {
+ $ramOk = true;
+ }
+ if ($ramOk && preg_match('/^\s*Number Of Devices: +(\S.+?) *$/i', $line, $out)) {
+ $row['ramslotcount'] = $out[1];
+ }
+ if ($ramOk && preg_match('/^\s*Maximum Capacity: +(\S.+?)\s*$/i', $line, $out)) {
+ $row['maxram'] = preg_replace('/([MGT])B/', '$1iB', $out[1]);
+ }
+ }
+ else if ($section === 'Memory Device') {
+ if (preg_match('/^\s*Size:\s*(.*?)\s*$/i', $line, $out)) {
+ $row['extram'] = true;
+ if (preg_match('/(\d+)\s*(\w)i?B/i', $out[1], $out)) {
+ $out[2] = strtoupper($out[2]);
+ if ($out[2] === 'K' || ($out[2] === 'M' && $out[1] < 500)) {
+ $ramForm = $ramType = $ramSpeed = $ramClockSpeed = false;
+ continue;
+ }
+ if ($out[2] === 'M' && $out[1] >= 1024) {
+ $out[2] = 'G';
+ $out[1] = floor(($out[1] + 100) / 1024);
+ }
+ $row['ramslot'][]['size'] = $out[1] . ' ' . strtoupper($out[2]) . 'iB';
+ } else if (!isset($row['ramslot']) || (count($row['ramslot']) < 8 && (!isset($row['ramslotcount']) || $row['ramslotcount'] <= 8))) {
+ $row['ramslot'][]['size'] = '_____';
+ }
+ }
+ if (preg_match('/^\s*Form Factor:\s*(.*?)\s*$/i', $line, $out) && $out[1] !== 'Unknown') {
+ $ramForm = $out[1];
+ }
+ if (preg_match('/^\s*Type:\s*(.*?)\s*$/i', $line, $out) && $out[1] !== 'Unknown') {
+ $ramType = $out[1];
+ }
+ if (preg_match('/^\s*Speed:\s*(\d.*?)\s*$/i', $line, $out)) {
+ $ramSpeed = $out[1];
+ }
+ if (preg_match('/^\s*Configured Clock Speed:\s*(\d.*?)\s*$/i', $line, $out)) {
+ $ramClockSpeed = $out[1];
+ }
+ }
+ }
+ if (empty($row['ramslotcount'])) $row['ramslotcount'] = count($row['ramslot']);
+ }
+
+ private function parseHdd(&$row, $data)
+ {
+ $hdds = array();
+ // Could have more than one disk - linear scan
+ $lines = preg_split("/[\r\n]+/", $data);
+ $dev = false;
+ $i = 0;
+ foreach ($lines as $line) {
+ if (preg_match('/^Disk (\S+):.* (\d+) bytes/i', $line, $out)) {
+ // disk total size and name
+ unset($hdd);
+ $unit = 0;
+ $hdd = array(
+ 'devid' => 'devid-' . ++$i,
+ 'dev' => $out[1],
+ 'size' => round($out[2] / (1024 * 1024 * 1024)),
+ 'used' => 0,
+ 'partitions' => array(),
+ 'json' => array(),
+ );
+ $hdds[] = &$hdd;
+ } elseif (preg_match('/^Units =.*= (\d+) bytes/i', $line, $out)) {
+ // Unit for start and end
+ $unit = $out[1] / (1024 * 1024); // Convert so that multiplying by unit yields MiB
+ } else if (isset($hdd) && $unit !== 0 && preg_match(',^/dev/(\S+)\s+.*\s(\d+)[\+\-]?\s+(\d+)[\+\-]?\s+\d+[\+\-]?\s+([0-9a-f]+)\s+(.*)$,i', $line, $out)) {
+ // Some partition
+ $type = strtolower($out[4]);
+ if ($type === '5' || $type === 'f' || $type === '85') continue;
+ $partsize = round(($out[3] - $out[2]) * $unit);
+ $hdd['partitions'][] = array(
+ 'id' => $out[1],
+ 'name' => $out[1],
+ 'size' => round($partsize / 1024, $partsize < 1024 ? 1 : 0),
+ 'type' => ($type === '44' ? 'OpenSLX' : $out[5]),
+ );
+ $hdd['json'][] = array(
+ 'label' => $out[1],
+ 'value' => $partsize,
+ 'color' => ($type === '44' ? '#4d4' : ($type === '82' ? '#48f' : '#e55')),
+ );
+ $hdd['used'] += $partsize;
+ }
+ }
+ unset($hdd);
+ $i = 0;
+ foreach ($hdds as &$hdd) {
+ $hdd['used'] = round($hdd['used'] / 1024);
+ $free = $hdd['size'] - $hdd['used'];
+ if ($free > 5) {
+ $hdd['partitions'][] = array(
+ 'id' => 'free-id-' . $i,
+ 'name' => Dictionary::translate('unused'),
+ 'size' => $free,
+ 'type' => '-',
+ );
+ $hdd['json'][] = array(
+ 'label' => 'free-id-' . $i,
+ 'value' => $free * 1024,
+ 'color' => '#aaa',
+ );
+ ++$i;
+ }
+ $hdd['json'] = json_encode($hdd['json']);
+ }
+ unset($hdd);
+ $row['hdds'] = &$hdds;
+ }
+
+ private function parseSmartctl(&$hdds, $data)
+ {
+ $lines = preg_split("/[\r\n]+/", $data);
+ $i = 0;
+ foreach ($lines as $line) {
+ if (preg_match('/^NEXTHDD=(.+)$/', $line, $out)) {
+ unset($dev);
+ foreach ($hdds as &$hdd) {
+ if ($hdd['dev'] === $out[1]) $dev =& $hdd;
+ }
+ continue;
+ }
+ if (!isset($dev)) continue;
+ if (preg_match('/^([A-Z][^:]+):\s*(.*)$/', $line, $out)) {
+ $dev['s_' . preg_replace('/\s|-|_/', '', $out[1])] = $out[2];
+ } elseif (preg_match('/^\s*\d+\s+(\S+)\s+\S+\s+\d+\s+\d+\s+\d+\s+\S+\s+(\d+)(\s|$)/', $line, $out)) {
+ $dev['s_' . preg_replace('/\s|-|_/', '', $out[1])] = $out[2];
+ }
+ }
+ // Format strings
+ foreach ($hdds as &$hdd) {
+ if (isset($hdd['s_PowerOnHours'])) {
+ $hdd['PowerOnTime'] = '';
+ $val = (int)$hdd['s_PowerOnHours'];
+ if ($val > 8760) {
+ $hdd['PowerOnTime'] .= floor($val / 8760) . 'Y, ';
+ $val %= 8760;
+ }
+ if ($val > 720) {
+ $hdd['PowerOnTime'] .= floor($val / 720) . 'M, ';
+ $val %= 720;
+ }
+ if ($val > 24) {
+ $hdd['PowerOnTime'] .= floor($val / 24) . 'd, ';
+ $val %= 24;
+ }
+ $hdd['PowerOnTime'] .= $val . 'h';
+ }
+ }
+ }
+
+}
diff --git a/modules-available/statistics/templates/clientlist.html b/modules-available/statistics/templates/clientlist.html
new file mode 100644
index 00000000..8e8565fe
--- /dev/null
+++ b/modules-available/statistics/templates/clientlist.html
@@ -0,0 +1,45 @@
+<h1>{{lang_clientList}}</h1>
+<div class="pull-right">{{filter}} ~= {{argument}}</div>
+<div class="clearfix"></div>
+
+<table class="table table-condensed table-striped">
+ <tr>
+ <th>{{lang_machine}}</th>
+ <th>{{lang_address}}</th>
+ <th class="text-right">{{lang_lastSeen}}</th>
+ <th>{{lang_kvmSupport}}</th>
+ <th class="text-right">{{lang_gbRam}}</th>
+ <th class="text-right">{{lang_tmpGb}}</th>
+ <th>{{lang_cpuModel}}</th>
+ </tr>
+ {{#rows}}
+ <tr>
+ <td class="slx-nowrap">
+ {{#hasnotes}}<span class="glyphicon glyphicon-exclamation-sign pull-right"></span>{{/hasnotes}}
+ {{#state_off}}
+ <span class="glyphicon glyphicon-off" title="{{lang_machineOff}}"></span>
+ {{/state_off}}
+ {{#state_idle}}
+ <span class="glyphicon glyphicon-ok green" title="{{lang_machineIdle}}"></span>
+ {{/state_idle}}
+ {{#state_occupied}}
+ <span class="glyphicon glyphicon-user red" title="{{lang_machineOccupied}}"></span>
+ {{/state_occupied}}
+ <a href="?do=Statistics&amp;uuid={{machineuuid}}"><b>{{hostname}}</b></a>
+ <div class="small">{{machineuuid}}</div>
+ </td>
+ <td><b><a href="?do=Statistics&amp;filter=subnet&amp;argument={{subnet}}">{{subnet}}</a>{{lastoctet}}</b><br>{{macaddr}}</td>
+ <td class="text-right">{{lastseen}}</td>
+ <td class="{{kvmclass}}">{{kvmstate}}</td>
+ <td class="text-right {{ramclass}}">{{gbram}}&thinsp;GiB</td>
+ <td class="text-right {{hddclass}}">
+ {{gbtmp}}&thinsp;GiB
+ {{#badsectors}}<div>
+ <span class="glyphicon glyphicon-exclamation-sign"></span>
+ {{badsectors}}
+ </div>{{/badsectors}}
+ </td>
+ <td>{{lang_realCores}}: {{realcores}}<div class="small">{{cpumodel}}</div></td>
+ </tr>
+ {{/rows}}
+</table>
diff --git a/modules-available/statistics/templates/cpumodels.html b/modules-available/statistics/templates/cpumodels.html
new file mode 100644
index 00000000..2f24cd92
--- /dev/null
+++ b/modules-available/statistics/templates/cpumodels.html
@@ -0,0 +1,51 @@
+<div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_modelStats}}
+ </div>
+ <div class="panel-body">
+ <div class="row">
+ <div class="col-md-8">
+ <table class="table table-condensed table-striped">
+ <tr>
+ <th>{{lang_modelName}}</th>
+ <th class="text-right">{{lang_cpuCores}}</th>
+ <th class="text-right">{{lang_modelCount}}</th>
+ </tr>
+ {{#rows}}
+ <tr id="{{id}}">
+ <td class="text-left slx-nowrap">
+ <a href="?do=Statistics&amp;filter=systemmodel&amp;argument={{urlsystemmodel}}">{{systemmodel}}</a>
+ </td>
+ <td class="text-right"><a href="?do=Statistics&amp;filter=realcores&amp;argument={{cores}}">{{cores}}</a></td>
+ <td class="text-right">{{count}}</td>
+ </tr>
+ {{/rows}}
+ </table>
+ </div>
+ <div class="col-md-4">
+ <canvas id="cpumodelchart" style="width:100%;height:380px"></canvas>
+ <script type="text/javascript">
+ document.addEventListener("DOMContentLoaded", function() {
+ var data = {{{json}}};
+ var sel = false;
+ new Chart(document.getElementById('cpumodelchart').getContext('2d')).Pie(data, {
+ animation: false,
+ tooltipTemplate: "<%if (label){%><%=label%><%}%>",
+ customTooltips: function(tooltip) {
+ if (sel !== false) sel.removeClass('info');
+ if (!tooltip) {
+ sel = false;
+ return;
+ }
+ sel = $('#' + tooltip.text);
+ sel.addClass('info');
+ }
+ });
+ }, false);
+ </script>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/modules-available/statistics/templates/id44.html b/modules-available/statistics/templates/id44.html
new file mode 100644
index 00000000..730839b1
--- /dev/null
+++ b/modules-available/statistics/templates/id44.html
@@ -0,0 +1,48 @@
+<div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_tempPartStats}}
+ </div>
+ <div class="panel-body">
+ <div class="row">
+ <div class="col-sm-6">
+ <table class="table table-condensed table-striped">
+ <tr>
+ <th>{{lang_partitionSize}}</th>
+ <th class="text-right">{{lang_machineCount}}</th>
+ </tr>
+ {{#rows}}
+ <tr id="tmpid{{gb}}" class="{{class}}">
+ <td class="text-left slx-nowrap"><a href="?do=Statistics&amp;filter=hddgb&amp;argument={{gb}}">{{gb}}&thinsp;GiB</td>
+ <td class="text-right">{{count}}</td>
+ </tr>
+ {{/rows}}
+ </table>
+ </div>
+ <div class="col-sm-6">
+ <canvas id="temppartchart" style="width:100%;height:250px"></canvas>
+ <script type="text/javascript">
+ document.addEventListener("DOMContentLoaded", function() {
+ var data = {{{json}}};
+ var sel = false;
+ new Chart(document.getElementById('temppartchart').getContext('2d')).Pie(data, {
+ animation: false,
+ tooltipTemplate: "<%if (label){%><%=label%><%}%>",
+ customTooltips: function(tooltip) {
+ if (sel !== false) sel.removeClass('info');
+ if (!tooltip) {
+ sel = false;
+ return;
+ }
+ sel = $('#tmpid' + String(tooltip.text));
+ console.log('#tmpid' + String(tooltip.text));
+ sel.addClass('info');
+ }
+ });
+ }, false);
+ </script>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/statistics/templates/kvmstate.html b/modules-available/statistics/templates/kvmstate.html
new file mode 100644
index 00000000..107a34f7
--- /dev/null
+++ b/modules-available/statistics/templates/kvmstate.html
@@ -0,0 +1,47 @@
+<div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_kvmStats}}
+ </div>
+ <div class="panel-body">
+ <div class="row">
+ <div class="col-sm-6">
+ <table class="table table-condensed table-striped">
+ <tr>
+ <th>{{lang_kvmState}}</th>
+ <th class="text-right">{{lang_machineCount}}</th>
+ </tr>
+ {{#rows}}
+ <tr id="kvm{{kvmstate}}">
+ <td class="text-left slx-nowrap"><a href="?do=Statistics&amp;filter=kvmstate&amp;argument={{kvmstate}}">{{kvmstate}}</a></td>
+ <td class="text-right">{{count}}</td>
+ </tr>
+ {{/rows}}
+ </table>
+ </div>
+ <div class="col-sm-6">
+ <canvas id="kvmchart" style="width:100%;height:250px"></canvas>
+ <script type="text/javascript">
+ document.addEventListener("DOMContentLoaded", function() {
+ var data = {{{json}}};
+ var sel = false;
+ new Chart(document.getElementById('kvmchart').getContext('2d')).Pie(data, {
+ animation: false,
+ tooltipTemplate: "<%if (label){%><%=label%><%}%>",
+ customTooltips: function(tooltip) {
+ if (sel !== false) sel.removeClass('info');
+ if (!tooltip) {
+ sel = false;
+ return;
+ }
+ sel = $('#kvm' + tooltip.text);
+ sel.addClass('info');
+ }
+ });
+ }, false);
+ </script>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/statistics/templates/machine-hdds.html b/modules-available/statistics/templates/machine-hdds.html
new file mode 100644
index 00000000..fd6cf1be
--- /dev/null
+++ b/modules-available/statistics/templates/machine-hdds.html
@@ -0,0 +1,67 @@
+<h3>{{lang_hdds}}</h3>
+<div class="row">
+ {{#hdds}}
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <b>{{s_ModelFamily}}</b> {{dev}}
+ </div>
+ <div class="panel-body">
+ {{#s_DeviceModel}}
+ <div>{{lang_modelNo}}: {{s_DeviceModel}}, {{lang_serialNo}}: {{s_SerialNumber}}</div>
+ {{/s_DeviceModel}}
+ {{#s_ReallocatedSectorCt}}
+ <div class="red">{{lang_reallocatedSectors}}: {{s_ReallocatedSectorCt}}</div>
+ {{/s_ReallocatedSectorCt}}
+ {{#s_CurrentPendingSector}}
+ <div class="red">{{lang_pendingSectors}}: {{s_CurrentPendingSector}}</div>
+ {{/s_CurrentPendingSector}}
+ {{#s_PowerOnHours}}
+ <div>{{lang_powerOnTime}}: {{s_PowerOnHours}}&thinsp;{{lang_hours}} ({{PowerOnTime}})</div>
+ {{/s_PowerOnHours}}
+ <div class="row">
+ <div class="col-sm-6">
+ <table class="table table-condensed table-striped table-responsive">
+ <tr>
+ <th>{{lang_partName}}</th>
+ <th class="text-right">{{lang_partSize}}</th>
+ <th>{{lang_partType}}</th>
+ </tr>
+ {{#partitions}}
+ <tr id="{{id}}">
+ <td>{{name}}</td>
+ <td class="text-right">{{size}}&thinsp;GiB</td>
+ <td>{{type}}</td>
+ </tr>
+ {{/partitions}}
+ </table>
+ <div class="slx-bold">{{lang_total}}: {{size}}&thinsp;GiB</div>
+ </div>
+ <div class="col-sm-6">
+ <canvas id="{{devid}}-chart" style="width:100%;height:250px"></canvas>
+ <script type="text/javascript">
+ document.addEventListener("DOMContentLoaded", function() {
+ var data = {{{json}}};
+ var sel = false;
+ new Chart(document.getElementById('{{devid}}-chart').getContext('2d')).Pie(data, {
+ animation: false,
+ tooltipTemplate: "<%if (label){%><%=label%><%}%>",
+ customTooltips: function(tooltip) {
+ if (sel !== false) sel.removeClass('info');
+ if (!tooltip) {
+ sel = false;
+ return;
+ }
+ sel = $('#' + tooltip.text);
+ sel.addClass('info');
+ }
+ });
+ }, false);
+ </script>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ {{/hdds}}
+</div> \ No newline at end of file
diff --git a/modules-available/statistics/templates/machine-main.html b/modules-available/statistics/templates/machine-main.html
new file mode 100644
index 00000000..8071416a
--- /dev/null
+++ b/modules-available/statistics/templates/machine-main.html
@@ -0,0 +1,124 @@
+<h1>
+ {{hostname}} {{#hostname}}–{{/hostname}} {{clientip}}
+ {{#notes}}<a href="#usernotes"><span class="glyphicon glyphicon-exclamation-sign"></span></a>{{/notes}}
+</h1>
+
+<div class="row">
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_machineSummary}}
+ </div>
+ <div class="panel-body">
+ <table class="table table-condensed">
+ <tr>
+ <td>{{lang_uuid}}</td>
+ <td>{{machineuuid}}</td>
+ </tr>
+ <tr>
+ <td>{{lang_macAddr}}</td>
+ <td>{{macaddr}}</td>
+ </tr>
+ <tr>
+ <td>{{lang_ip}}</td>
+ <td>{{clientip}}</td>
+ </tr>
+ {{#hostname}}
+ <tr>
+ <td>{{lang_hostname}}</td>
+ <td>{{hostname}}</td>
+ </tr>
+ {{/hostname}}
+ <tr>
+ <td>{{lang_firstSeen}}</td>
+ <td>{{firstseen_s}}</td>
+ </tr>
+ <tr>
+ <td>{{lang_lastBoot}}</td>
+ <td>{{lastboot_s}}</td>
+ </tr>
+ <tr>
+ <td>{{lang_lastSeen}}</td>
+ <td>{{lastseen_s}}</td>
+ </tr>
+ <tr>
+ <td>{{lang_usageState}}</td>
+ <td>
+ {{#state_off}}
+ <span class="glyphicon glyphicon-off"></span> {{lang_machineOff}}
+ {{/state_off}}
+ {{#state_idle}}
+ <span class="glyphicon glyphicon-ok green"></span> {{lang_machineIdle}}
+ {{/state_idle}}
+ {{#state_occupied}}
+ {{#username}}
+ <span class="glyphicon glyphicon-user red"></span> {{lang_machineOccupiedBy}} <b>{{username}}</b>
+ {{/username}}
+ {{^username}}
+ <span class="glyphicon glyphicon-user red"></span> {{lang_machineOccupied}}
+ {{/username}}
+ <div>{{logintime_s}}</div>
+ {{/state_occupied}}
+ {{#session}}
+ <div>{{session}}</div>
+ {{/session}}
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_hardwareSummary}}
+ </div>
+ <div class="panel-body">
+ <table class="table table-condensed">
+ <tr>
+ <td>{{lang_cpuModel}}</td>
+ <td>
+ {{cpumodel}}
+ {{#Sockets}}
+ <div class="small">
+ {{lang_sockets}}: {{Sockets}}, {{lang_cores}}: {{Realcores}}, {{lang_virtualCores}}: {{Virtualcores}}
+ </div>
+ {{/Sockets}}
+ </td>
+ </tr>
+ <tr>
+ <td>{{lang_model}}</td>
+ <td>{{pcmodel}} ({{manufacturer}})</td>
+ </tr>
+ <tr class="{{ramclass}}">
+ <td>{{lang_ram}}</td>
+ <td>
+ {{gbram}}&thinsp;GiB
+ {{#maxram}}({{lang_maximumAbbrev}} {{maxram}}){{/maxram}}
+ {{ramtype}}
+ </td>
+ </tr>
+ {{#extram}}
+ <tr>
+ <td>{{lang_ramSlots}}</td>
+ <td>
+ {{ramslotcount}}:
+ {{#ramslot}}
+ [ {{size}} ]
+ {{/ramslot}}
+ </td>
+ </tr>
+ {{/extram}}
+ <tr class="{{hddclass}}">
+ <td>{{lang_tempPart}}</td>
+ <td>{{gbtmp}}&thinsp;GiB</td>
+ </tr>
+ <tr class="{{kvmclass}}">
+ <td>{{lang_64bitSupport}}</td>
+ <td>{{kvmstate}}</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/modules-available/statistics/templates/machine-notes.html b/modules-available/statistics/templates/machine-notes.html
new file mode 100644
index 00000000..c4f97543
--- /dev/null
+++ b/modules-available/statistics/templates/machine-notes.html
@@ -0,0 +1,17 @@
+<a name="usernotes"></a>
+<h3>{{lang_notes}}</h3>
+<div class="row">
+ <div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-body">
+ <form action="?do=Statistics" method="post">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="setnotes">
+ <input type="hidden" name="uuid" value="{{machineuuid}}">
+ <textarea name="content" class="form-control" cols="101" rows="10">{{notes}}</textarea>
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ </form>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/statistics/templates/machine-usage.html b/modules-available/statistics/templates/machine-usage.html
new file mode 100644
index 00000000..ffaa747b
--- /dev/null
+++ b/modules-available/statistics/templates/machine-usage.html
@@ -0,0 +1,51 @@
+<div class="row">
+ <div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_usageDetails}}
+ </div>
+ <div class="panel-body">
+ <div class="row">
+ <div class="col-sm-6">
+ <table class="table table-condensed">
+ <tr>
+ <th>Type</th>
+ <th>When</th>
+ <th>Length</th>
+ </tr>
+ {{#rows}}
+ <tr>
+ <td><span class="glyphicon glyphicon-{{glyph}}"></span></td>
+ <td>{{from}}</td>
+ <td>{{duration}}</td>
+ </tr>
+ {{/rows}}
+ </table>
+ </div>
+ <div class="col-sm-6">
+ <table class="table table-condensed">
+ {{#hasrows2}}
+ <tr>
+ <th>Type</th>
+ <th>When</th>
+ <th>Length</th>
+ </tr>
+ {{/hasrows2}}
+ {{#rows2}}
+ <tr>
+ <td><span class="glyphicon glyphicon-{{glyph}}"></span></td>
+ <td>{{from}}</td>
+ <td>{{duration}}</td>
+ </tr>
+ {{/rows2}}
+ </table>
+ </div>
+ </div>
+ <div class="timebar">&nbsp;{{{graph}}}</div>
+ <div>
+ {{lang_timebarDesc}}
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/modules-available/statistics/templates/memory.html b/modules-available/statistics/templates/memory.html
new file mode 100644
index 00000000..f4d2ad24
--- /dev/null
+++ b/modules-available/statistics/templates/memory.html
@@ -0,0 +1,47 @@
+<div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_memoryStats}}
+ </div>
+ <div class="panel-body">
+ <div class="row">
+ <div class="col-sm-6">
+ <table class="table table-condensed table-striped">
+ <tr>
+ <th>{{lang_ramSize}}</th>
+ <th class="text-right">{{lang_machineCount}}</th>
+ </tr>
+ {{#rows}}
+ <tr id="ramid{{gb}}" class="{{class}}">
+ <td class="text-left slx-nowrap"><a href="?do=Statistics&amp;filter=gbram&amp;argument={{gb}}">{{gb}}&thinsp;GiB</a></td>
+ <td class="text-right">{{count}}</td>
+ </tr>
+ {{/rows}}
+ </table>
+ </div>
+ <div class="col-sm-6">
+ <canvas id="ramsizechart" style="width:100%;height:250px"></canvas>
+ <script type="text/javascript">
+ document.addEventListener("DOMContentLoaded", function() {
+ var data = {{{json}}};
+ var sel = false;
+ new Chart(document.getElementById('ramsizechart').getContext('2d')).Pie(data, {
+ animation: false,
+ tooltipTemplate: "<%if (label){%><%=label%><%}%>",
+ customTooltips: function(tooltip) {
+ if (sel !== false) sel.removeClass('info');
+ if (!tooltip) {
+ sel = false;
+ return;
+ }
+ sel = $('#ramid' + tooltip.text);
+ sel.addClass('info');
+ }
+ });
+ }, false);
+ </script>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/statistics/templates/newclients.html b/modules-available/statistics/templates/newclients.html
new file mode 100644
index 00000000..0d9c74df
--- /dev/null
+++ b/modules-available/statistics/templates/newclients.html
@@ -0,0 +1,44 @@
+<div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_newMachines}}
+ </div>
+ <div class="panel-body">
+ <table class="table table-condensed table-striped" id="newclienttable">
+ <tr>
+ <th>{{lang_machine}}</th>
+ <th class="text-right"></th>
+ <th>64Bit</th>
+ <th class="text-right">RAM</th>
+ <th class="text-right">HDD</th>
+ </tr>
+ {{#rows}}
+ <tr style="{{style}}">
+ <td class="slx-nowrap"><a href="?do=Statistics&amp;uuid={{machineuuid}}">{{hostname}}</a></td>
+ <td class="text-right">{{firstseen}}</td>
+ <td class="{{kvmclass}}">{{kvmicon}}</td>
+ <td class="text-right {{ramclass}}">{{gbram}}&thinsp;GiB</td>
+ <td class="text-right {{hddclass}}">{{gbtmp}}&thinsp;GiB</td>
+ </tr>
+ {{/rows}}
+ {{#openbutton}}
+ <tr>
+ <td colspan="5" onclick="slxExpandNew(this)">
+ <span class="btn-group btn-group-justified">
+ <span class="btn btn-default">
+ <span class="glyphicon glyphicon-menu-down"></span>
+ </span>
+ </span>
+ <script type="text/javascript">
+ function slxExpandNew(b) {
+ $('#newclienttable').find('tr').show();
+ $(b).hide();
+ }
+ </script>
+ </td>
+ </tr>
+ {{/openbutton}}
+ </table>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/statistics/templates/summary.html b/modules-available/statistics/templates/summary.html
new file mode 100644
index 00000000..5f16fd89
--- /dev/null
+++ b/modules-available/statistics/templates/summary.html
@@ -0,0 +1,33 @@
+<div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-body">
+ <div>
+ {{lang_knownMachines}}: <b>{{known}}</b>&emsp;
+ <a href="?do=Statistics&amp;filter=state&amp;argument=on">{{lang_onlineMachines}}</a>: <b>{{online}}</b>&emsp;
+ <a href="?do=Statistics&amp;filter=state&amp;argument=occupied">{{lang_inUseMachines}}</a>: <b>{{used}}</b> (<b>{{usedpercent}}%</b>)
+ </div>
+ {{#badhdd}}
+ <div>
+ <span class="glyphicon glyphicon-exclamation-sign red"></span>
+ <a href="?do=Statistics&amp;filter=badsectors&amp;argument=10">
+ {{lang_withBadSectors}}: <b>{{badhdd}}</b>
+ </a>
+ </div>
+ {{/badhdd}}
+ </div>
+ </div>
+ <div>
+ <canvas id="usagehist" style="width:100%;height:150px"></canvas>
+ <script type="text/javascript">
+ document.addEventListener("DOMContentLoaded", function() {
+ var data = {{{json}}};
+ var sel = false;
+ new Chart(document.getElementById('usagehist').getContext('2d')).Line(data, {
+ animation: false,
+ pointHitDetectionRadius: 5
+ });
+ }, false);
+ </script>
+ </div>
+</div>
+
diff --git a/modules-available/statistics/templates/syslog.html b/modules-available/statistics/templates/syslog.html
new file mode 100644
index 00000000..c82cb8ac
--- /dev/null
+++ b/modules-available/statistics/templates/syslog.html
@@ -0,0 +1,43 @@
+<h3>{{lang_logHeadline}}</h3>
+<table class="table table-striped table-condensed">
+ <thead>
+ <th width="1"></th>
+ <th>{{lang_when}}</th>
+ <th>{{lang_event}}</th>
+ <th width="1">{{lang_details}}</th>
+ </thead>
+ <tbody>
+ {{#list}}
+ <tr>
+ <td><span class="glyphicon {{icon}}" title="{{logtypeid}}" onclick="$('#filterstring').tagsinput('add', '{{logtypeid}}')"></span></td>
+ <td class="text-right" nowrap="nowrap">{{date}}</td>
+ <td>{{description}}</td>
+ <td>{{#extra}}
+ <a class="btn btn-default btn-xs pull-left" onclick="$('#details-body').html($('#extra-{{logid}}').html())" data-toggle="modal" data-target="#myModal">&raquo;</a>
+ <div class="hidden" id="extra-{{logid}}">{{extra}}</div>
+ {{/extra}}</td>
+ </tr>
+ {{/list}}
+ </tbody>
+</table>
+<div class="pull-right"><a class="btn btn-default btn-sm" href="?do=SysLog&amp;ip={{clientip}}">{{lang_more}} &raquo;</a></div>
+<div class="clearfix"></div>
+
+<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+ <div class="modal-dialog modal-lg">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+ <h4 class="modal-title" id="myModalLabel">{{lang_details}}</h4>
+ </div>
+ <div class="modal-body">
+ <pre id="details-body"></pre>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+
diff --git a/modules-available/support/config.json b/modules-available/support/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/support/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/support/faq.json b/modules-available/support/faq.json
new file mode 100644
index 00000000..1c1d372f
--- /dev/null
+++ b/modules-available/support/faq.json
@@ -0,0 +1,19 @@
+{
+ "faq": [
+ {"question": "Este é o FAQ",
+ "answer": "Ao clicar em cada uma pergunta, elas se expandem exibindo as respostas!",
+ "order": "One",
+ "highlight": true
+ },
+ {"question": "Por enquanto não há nada por aqui (ツ)_/¯ ",
+ "answer": "As perguntas serão adicionadas conforme as dúvidas forem surgindo",
+ "order": "Two",
+ "highlight": true
+ },
+ {"question": "Nem todas as perguntas estarão aqui",
+ "answer": "Se tiver uma dúvida que não está no FAQ, envie um e-mail clicando no botão abaixo ;)",
+ "order": "Three",
+ "highlight": false
+ }
+ ]
+}
diff --git a/modules-available/support/lang/en/module.json b/modules-available/support/lang/en/module.json
new file mode 100644
index 00000000..c44dc44f
--- /dev/null
+++ b/modules-available/support/lang/en/module.json
@@ -0,0 +1,3 @@
+[
+
+] \ No newline at end of file
diff --git a/modules-available/support/lang/pt/module.json b/modules-available/support/lang/pt/module.json
new file mode 100644
index 00000000..f82f6a04
--- /dev/null
+++ b/modules-available/support/lang/pt/module.json
@@ -0,0 +1,18 @@
+{
+ "lang_content": "Conte\u00fado",
+ "lang_content#1": "Ao clicar em cada uma pergunta, elas se expandem exibindo as respostas!",
+ "lang_content#2": "As perguntas ser\u00e3o adicionadas conforme as d\u00favidas forem surgindo",
+ "lang_content#3": "Se tiver uma d\u00favida que n\u00e3o est\u00e1 no FAQ, envie um e-mail clicando no bot\u00e3o abaixo ;)",
+ "lang_faq": "FAQ",
+ "lang_faq#1": "Este \u00e9 o FAQ",
+ "lang_faq#2": "Por enquanto n\u00e3o h\u00e1 nada por aqui (\u30c4)_\/\u00af",
+ "lang_faq#3": "Nem todas as perguntas estar\u00e3o aqui",
+ "lang_fileInput": "Arquivo:",
+ "lang_form": "Formul\u00e1rio",
+ "lang_header": "Assunto",
+ "lang_helpInput": "Voc\u00ea pode mandar uma foto\/arquivo que nos auxilie a entender o problema.",
+ "lang_showall": "Clique aqui para mostrar todo o FAQ",
+ "lang_submit": "Enviar",
+ "lang_supIntro": "N\u00e3o encontrou o que queria no FAQ? Mande-nos um e-mail informando o problema",
+ "module_name": "Suporte"
+} \ No newline at end of file
diff --git a/modules-available/support/page.inc.php b/modules-available/support/page.inc.php
new file mode 100644
index 00000000..10695969
--- /dev/null
+++ b/modules-available/support/page.inc.php
@@ -0,0 +1,83 @@
+<?php
+
+class Page_Support extends Page
+{
+
+ protected function doPreprocess(){
+ User::load();
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ error_reporting(E_ALL);
+ ini_set('display_errors','on');
+
+ Session::get('token');
+
+
+ //THIS IS NOT WORKING
+ //Cant connect to ANY smtp server
+ /*
+ if (strpos($_SERVER['REQUEST_URI'], "action=send") !== false){
+ require '/var/www/slx-admin/phpmailer/PHPMailerAutoload.php';
+
+ $mail = new PHPMailer;
+ $mail->SMTPDebug = 3; // Enable verbose debug output
+ $mail->isSMTP(); // Set mailer to use SMTP
+ $mail->Host = 'mx.c3sl.ufpr.br'; // Specify main and backup SMTP servers
+ $mail->SMTPAuth = true; // Enable SMTP authentication
+ $mail->Username = 'xxx00@inf.ufpr.br'; // SMTP username
+ $mail->Password = ''; // SMTP password
+ // $mail->SMTPSecure = 'false'; // Enable TLS encryption, `ssl` also accepted
+ $mail->Port = 25; // TCP port to connect to
+
+ $mail->From = 'xxx00@inf.ufpr.br';
+ $mail->FromName = 'Someone';
+ $mail->addAddress('receiver@email.com', 'Another One'); // Add a recipient
+ // $mail->addAttachment('/var/tmp/file.tar.gz'); // Add attachments
+ // $mail->addAttachment('/tmp/image.jpg', 'new.jpg'); // Optional name
+ $mail->isHTML(true); // Set email format to HTML
+ $mail->Subject = 'Here is the subject';
+ $mail->Body = 'This is the HTML message body <b>in bold!</b>';
+ $mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
+
+ if(!$mail->send()) {
+ echo 'Message could not be sent.';
+ echo 'Mailer Error: ' . $mail->ErrorInfo;
+ } else {
+ echo 'Message has been sent';
+ }
+ */
+// $uploaddir = '/var/www/uploads/';
+// $uploadfile = $uploaddir . basename($_FILES['inp_file']['name']);
+// if(move_uploaded_file($_FILES['inp_file']['tmp_name'], $uploadfile))
+// Message::addSuccess('news-save-success');
+// else
+// Message::addError('news-empty');
+// mail($to,$_POST[assuntoEmail],$_POST[conteudoEmail],"-r".$from);
+// mail($to,$assunto,$content,$headers);
+
+
+ }
+
+
+
+ protected function doRender(){
+ error_reporting(E_ALL);
+ ini_set('display_errors','on');
+ if (strpos($_SERVER['REQUEST_URI'], "true") !== false){
+ Render::addTemplate('page-faq',
+ json_decode(file_get_contents("modules/support/faq.json"),true)
+ );
+ }
+ else{
+ Render::addTemplate('page-support',
+ json_decode(file_get_contents("modules/support/faq.json"),true)
+ );
+ }
+// Render::addTemplate('page-support', array(
+// 'token' => Session:get('token'));
+ }
+
+
+}
diff --git a/modules-available/support/templates/page-faq.html b/modules-available/support/templates/page-faq.html
new file mode 100644
index 00000000..ae706f4b
--- /dev/null
+++ b/modules-available/support/templates/page-faq.html
@@ -0,0 +1,29 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <strong>{{lang_faq}}</strong>
+ </div>
+ <!-- //FAQ -->
+ <div class="panel-body" id="accordion" role="tablist" aria-multiselectable="true">
+
+ {{#faq}}
+ <div class="panel panel-default">
+ <div class="panel-heading" role="tab" id="heading{{order}}">
+ <h4 class="panel-title">
+ <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse{{order}}" aria-expanded="false" aria-controls="collapse{{order}}">
+ {{question}}
+ </a>
+ </h4>
+ </div>
+ <div id="collapse{{order}}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading{{order}}">
+ <div class="panel-body">
+ {{answer}}
+ </div>
+ </div>
+ </div>
+ {{/faq}}
+
+ <a class="btn btn-default" href="?do=Support&amp;showfaq=false" role="button"><strong>{{lang_supIntro}}</strong></a>
+ </div></div>
+</div>
+
+
diff --git a/modules-available/support/templates/page-support.html b/modules-available/support/templates/page-support.html
new file mode 100644
index 00000000..196e0891
--- /dev/null
+++ b/modules-available/support/templates/page-support.html
@@ -0,0 +1,59 @@
+<p>{{lang_supIntro}}</p>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <strong>{{lang_faq}}</strong>
+ </div>
+ <!-- //FAQ -->
+ <div class="panel-body" id="accordion" role="tablist" aria-multiselectable="true">
+
+ {{#faq}}
+ {{#highlight}}
+ <div class="panel panel-default">
+ <div class="panel-heading" role="tab" id="heading{{order}}">
+ <h4 class="panel-title">
+ <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse{{order}}" aria-expanded="false" aria-controls="collapse{{order}}">
+ {{question}}
+ </a>
+ </h4>
+ </div>
+ <div id="collapse{{order}}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading{{order}}">
+ <div class="panel-body">
+ {{answer}}
+ </div>
+ </div>
+ </div>
+ {{/highlight}}
+ {{/faq}}
+
+ <a class="btn btn-default" href="?do=Support&amp;showfaq=true" role="button"><strong>{{lang_showall}}</strong></a>
+ </div>
+</div>
+
+ <!-- //Send questions (token and input not ok) -->
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <strong>{{lang_form}}</strong>
+ </div>
+ <div class="panel-body">
+ <form action="?do=Support&amp;showfaq=false&amp;action=send" method="post" enctype="multipart/form-data">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="form-group">
+ <label for="emailHeader">{{lang_header}}</label>
+ <input type="text" class="form-control" placeholder="{{lang_header}}" value="{{assuntoEmail}}">
+ </div>
+ <div class="form-group">
+ <label for="emailContent">{{lang_content}}</label>
+ <textarea class="form-control" rows="2" cols="20" placeholder="{{lang_content}}">{{conteudoEmail}}</textarea>
+ </div>
+ <div class="form-group">
+ <label for="inputFile">{{lang_fileInput}}</label>
+ <input type="file" name="inp_file" id="inp_file">
+ <input type="hidden" name="token" value="{{token}}">
+ <p class="help-block">{{lang_helpInput}}</p>
+ </div>
+ <button type="submit" class="btn btn-default">{{lang_submit}}</button>
+ </form>
+ </div>
+</div>
+
+
diff --git a/modules-available/sysconfig/addconfig.inc.php b/modules-available/sysconfig/addconfig.inc.php
new file mode 100644
index 00000000..8203e555
--- /dev/null
+++ b/modules-available/sysconfig/addconfig.inc.php
@@ -0,0 +1,221 @@
+<?php
+
+/**
+ * Addconfig subpage base - makes sure
+ * we have the two required methods preprocess and render
+ */
+abstract class AddConfig_Base
+{
+
+ /**
+ * Holds the instance for the currently executing step
+ * @var \AddConfig_Base
+ */
+ private static $instance = false;
+
+ /**
+ * Config being edited (if any)
+ * @var \ConfigTgz
+ */
+ protected $edit = false;
+
+ /**
+ *
+ * @param type $step
+ * @return \AddConfig_Base
+ */
+ public static function setStep($step)
+ {
+ if (empty($step) || !class_exists($step) || get_parent_class($step) !== 'AddConfig_Base') {
+ Message::addError('invalid-action', $step);
+ Util::redirect('?do=SysConfig');
+ }
+ self::$instance = new $step();
+ if (Request::any('edit')) {
+ self::$instance->edit = ConfigTgz::get(Request::any('edit'));
+ if (self::$instance->edit === false)
+ Util::traceError('Invalid config id for editing');
+ Util::addRedirectParam('edit', self::$instance->edit->id());
+ }
+ }
+
+ protected function tmError()
+ {
+ Message::addError('taskmanager-error');
+ Util::redirect('?do=SysConfig');
+ }
+
+ protected function taskError($status)
+ {
+ if (isset($status['data']['error'])) {
+ $error = $status['data']['error'];
+ } elseif (isset($status['statusCode'])) {
+ $error = $status['statusCode'];
+ } else {
+ $error = Dictionary::translate('lang_unknwonTaskManager'); // TODO: No text
+ }
+ Message::addError('task-error', $error);
+ Util::redirect('?do=SysConfig');
+ }
+
+ /**
+ * Called before any HTML rendering happens, so you can
+ * pepare stuff, validate input, and optionally redirect
+ * early if something is wrong, or you received post
+ * data etc.
+ */
+ protected function preprocessInternal()
+ {
+ // void
+ }
+
+ /**
+ * Do page rendering.
+ */
+ protected function renderInternal()
+ {
+ // void
+ }
+
+ /**
+ * Handle ajax stuff
+ */
+ protected function ajaxInternal()
+ {
+ // void
+ }
+
+ public static function preprocess()
+ {
+ if (self::$instance === false) {
+ Util::traceError('No step instance yet');
+ }
+ self::$instance->preprocessInternal();
+ }
+
+ public static function render()
+ {
+ if (self::$instance === false)
+ Util::traceError('No step instance yet');
+ if (self::$instance->edit !== false)
+ Message::addInfo('replacing-config', self::$instance->edit->title());
+ self::$instance->renderInternal();
+ }
+
+ public static function ajax()
+ {
+ if (self::$instance === false) {
+ Util::traceError('No step instance yet');
+ }
+ self::$instance->ajaxInternal();
+ }
+
+}
+
+/**
+ * Start dialog for adding config. Ask for title,
+ * show selection of modules.
+ */
+class AddConfig_Start extends AddConfig_Base
+{
+
+ protected function renderInternal()
+ {
+ $mods = ConfigModule::getList();
+ $res = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath FROM configtgz_module"
+ . " ORDER BY title ASC"); // Move to ConfigModule
+ if ($this->edit === false) {
+ $active = array();
+ } else {
+ $active = $this->edit->getModuleIds();
+ }
+ $id = 0;
+ $modGroups = array();
+ foreach ($mods as &$mod) {
+ $mod['groupid'] = 'g' . ++$id;
+ $modGroups[$mod['group']] =& $mod;
+ }
+ unset($mod);
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (!isset($mods[$row['moduletype']])) {
+ $mods[$row['moduletype']] = array(
+ 'unique' => false,
+ 'group' => 'Undefined moduletype in addconfig.inc.php',
+ 'groupid' => 'g' . ++$id,
+ );
+ $modGroups[$mods[$row['moduletype']]['group']] =& $mods[$row['moduletype']];
+ }
+ unset($group);
+ $group =& $modGroups[$mods[$row['moduletype']]['group']];
+ if (!isset($group['modules'])) {
+ $group['modules'] = array();
+ }
+ if (empty($row['filepath']) || !file_exists($row['filepath'])) $row['missing'] = true;
+ $row['active'] = in_array($row['moduleid'], $active);
+ $group['modules'][] = $row;
+ }
+ if ($this->edit !== false) {
+ $title = $this->edit->title();
+ } elseif (Request::any('title')) {
+ $title = Request::any('title');
+ } else {
+ $title = '';
+ }
+ foreach ($modGroups as &$mod) {
+ if (!empty($mod['modules']) && $mod['unique']) {
+ array_unshift($mod['modules'], array(
+ 'moduleid' => 0,
+ 'title' => Dictionary::translate('lang_noModuleFromThisGroup'),
+ ));
+ }
+ }
+ unset($mod);
+ Render::addDialog(Dictionary::translate("lang_configurationCompilation"), false, 'cfg-start', array(
+ 'step' => 'AddConfig_Finish',
+ 'groups' => array_values($modGroups),
+ 'title' => $title,
+ 'edit' => ($this->edit !== false ? $this->edit->id() : false)
+ ));
+ }
+
+}
+
+/**
+ * Success dialog if adding config worked.
+ */
+class AddConfig_Finish extends AddConfig_Base
+{
+ private $config = false;
+
+ protected function preprocessInternal()
+ {
+ $modules = Request::post('module');
+ $title = Request::post('title');
+ if (!is_array($modules)) {
+ Message::addError('missing-file');
+ Util::redirect('?do=SysConfig&action=addconfig');
+ }
+ if (empty($title)) {
+ Message::addError('missing-title');
+ Util::redirect('?do=SysConfig&action=addconfig');
+ }
+ if ($this->edit === false) {
+ $this->config = ConfigTgz::insert($title, $modules);
+ } else {
+ $this->edit->update($title, $modules);
+ $this->config = $this->edit;
+ }
+ if ($this->config === false || $this->config->generate(true, 150) === false) {
+ Message::addError('unsuccessful-action');
+ Util::redirect('?do=SysConfig&action=addconfig');
+ }
+ }
+
+ protected function renderInternal()
+ {
+ Render::addDialog(Dictionary::translate('lang_configurationCompilation'), false, 'cfg-finish', array(
+ 'configid' => $this->config->id()
+ ));
+ }
+
+}
diff --git a/modules-available/sysconfig/addmodule.inc.php b/modules-available/sysconfig/addmodule.inc.php
new file mode 100644
index 00000000..bcd8e796
--- /dev/null
+++ b/modules-available/sysconfig/addmodule.inc.php
@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * Addmodule subpage base - makes sure
+ * we have the two required methods preprocess and render
+ */
+abstract class AddModule_Base
+{
+
+ /**
+ * Holds the instance for the currently executing step
+ * @var \AddModule_Base
+ */
+ private static $instance = false;
+
+ /**
+ * Instance of ConfigModule we're editing. False if not editing but creating.
+ * @var \ConfigModule
+ */
+ protected $edit = false;
+
+ /**
+ *
+ * @param type $step
+ * @return \AddModule_Base
+ */
+ public static function setStep($step)
+ {
+ if (empty($step) || !class_exists($step) || get_parent_class($step) !== 'AddModule_Base') {
+ Message::addError('invalid-action', $step);
+ Util::redirect('?do=SysConfig');
+ }
+ self::$instance = new $step();
+ if (Request::any('edit')) {
+ self::$instance->edit = ConfigModule::get(Request::any('edit'));
+ if (self::$instance->edit === false)
+ Util::traceError('Invalid module id for editing');
+ if (!preg_match('/^' . self::$instance->edit->moduleType() . '_/', $step))
+ Util::traceError('Module to edit is of different type!');
+ Util::addRedirectParam('edit', self::$instance->edit->id());
+ }
+ }
+
+ protected function tmError()
+ {
+ Message::addError('taskmanager-error');
+ Util::redirect('?do=SysConfig');
+ }
+
+ protected function taskError($status)
+ {
+ if (isset($status['data']['error'])) {
+ $error = $status['data']['error'];
+ } elseif (isset($status['statusCode'])) {
+ $error = $status['statusCode'];
+ } else {
+ $error = Dictionary::translate('lang_unknwonTaskManager');
+ }
+ Message::addError('task-error', $error);
+ Util::redirect('?do=SysConfig');
+ }
+
+ /**
+ * Called before any HTML rendering happens, so you can
+ * pepare stuff, validate input, and optionally redirect
+ * early if something is wrong, or you received post
+ * data etc.
+ */
+ protected function preprocessInternal()
+ {
+ // void
+ }
+
+ /**
+ * Do page rendering.
+ */
+ protected function renderInternal()
+ {
+ // void
+ }
+
+ /**
+ * Handle ajax stuff
+ */
+ protected function ajaxInternal()
+ {
+ // void
+ }
+
+ public static function preprocess()
+ {
+ if (self::$instance === false) {
+ Util::traceError('No step instance yet');
+ }
+ self::$instance->preprocessInternal();
+ }
+
+ public static function render()
+ {
+ if (self::$instance === false) {
+ Util::traceError('No step instance yet');
+ }
+ if (self::$instance->edit !== false)
+ Message::addInfo('replacing-module', self::$instance->edit->title());
+ self::$instance->renderInternal();
+ }
+
+ public static function ajax()
+ {
+ if (self::$instance === false) {
+ Util::traceError('No step instance yet');
+ }
+ self::$instance->ajaxInternal();
+ }
+
+}
+
+/**
+ * Start dialog for adding module. Here the user
+ * selects which kind of module they want to add.
+ */
+class AddModule_Start extends AddModule_Base
+{
+
+ protected function renderInternal()
+ {
+ $title = $order = array();
+ $mods = ConfigModule::getList();
+ foreach ($mods as $module) {
+ $title[] = $module['title'];
+ $order[] = $module['sortOrder'];
+ }
+ array_multisort($order, SORT_ASC, $title, SORT_ASC, $mods);
+ Render::addDialog(Dictionary::translate('lang_moduleAdd'), false, 'start', array('modules' => array_values($mods)));
+ }
+
+}
+
+/*
+ * Helper functions to set/get a batch of vars from/to post variables or a module
+ */
+
+/**
+ *
+ * @param \ConfigModule $module
+ * @param array $array
+ * @param array $keys
+ */
+function moduleToArray($module, &$array, $keys)
+{
+ foreach ($keys as $key) {
+ $array[$key] = $module->getData($key);
+ }
+}
+
+/**
+ *
+ * @param \ConfigModule $module
+ * @param array $array
+ * @param array $keys
+ */
+function arrayToModule($module, $array, $keys)
+{
+ foreach ($keys as $key) {
+ $module->setData($key, $array[$key]);
+ }
+}
+/**
+ *
+ * @param array $array
+ * @param array $keys
+ */
+function postToArray(&$array, $keys, $ignoreMissing = false)
+{
+ foreach ($keys as $key) {
+ $val = Request::post($key, '--not-in-post');
+ if ($ignoreMissing && $val === '--not-in-post') continue;
+ $array[$key] = $val;
+ }
+}
diff --git a/modules-available/sysconfig/addmodule_adauth.inc.php b/modules-available/sysconfig/addmodule_adauth.inc.php
new file mode 100644
index 00000000..e295630c
--- /dev/null
+++ b/modules-available/sysconfig/addmodule_adauth.inc.php
@@ -0,0 +1,413 @@
+<?php
+
+/*
+ * Wizard for setting up active directory integration for authentication.
+ */
+
+class AdAuth_Start extends AddModule_Base
+{
+
+ protected function renderInternal()
+ {
+ $ADAUTH_COMMON_FIELDS = array('title', 'server', 'searchbase', 'binddn', 'bindpw', 'home', 'homeattr', 'ssl', 'certificate');
+ $data = array();
+ if ($this->edit !== false) {
+ moduleToArray($this->edit, $data, $ADAUTH_COMMON_FIELDS);
+ $data['title'] = $this->edit->title();
+ $data['edit'] = $this->edit->id();
+ }
+ postToArray($data, $ADAUTH_COMMON_FIELDS, true);
+ $obdn = Request::post('originalbinddn');
+ if (!empty($obdn)) {
+ $data['binddn'] = $obdn;
+ }
+ if (preg_match('/^(.*)\:(636|3269|389|3268)$/', $data['server'], $out)) {
+ $data['server'] = $out[1];
+ }
+ $data['step'] = 'AdAuth_CheckConnection';
+ Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad-start', $data);
+ }
+
+}
+
+class AdAuth_CheckConnection extends AddModule_Base
+{
+
+ private $scanTask;
+ private $server;
+
+ protected function preprocessInternal()
+ {
+ $this->server = Request::post('server');
+ $binddn = Request::post('binddn');
+ $ssl = Request::post('ssl', 'off') === 'on';
+ if (empty($this->server) || empty($binddn)) {
+ Message::addError('empty-field');
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ if (preg_match('/^([^\:]+)\:(\d+)$/', $this->server, $out)) {
+ $ports = array($out[2]);
+ $this->server = $out[1];
+ } elseif ($ssl) {
+ $ports = array(636, 3269);
+ } else {
+ $ports = array(389, 3268);
+ }
+ $this->scanTask = Taskmanager::submit('PortScan', array(
+ 'host' => $this->server,
+ 'ports' => $ports,
+ 'certificate' => Request::post('certificate', '')
+ ));
+ if (!isset($this->scanTask['id'])) {
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ }
+
+ protected function renderInternal()
+ {
+ $data = array(
+ 'edit' => Request::post('edit'),
+ 'title' => Request::post('title'),
+ 'server' => $this->server,
+ 'searchbase' => Util::normalizeDn(Request::post('searchbase')),
+ 'binddn' => Util::normalizeDn(Request::post('binddn')),
+ 'bindpw' => Request::post('bindpw'),
+ 'home' => Request::post('home'),
+ 'homeattr' => Request::post('homeattr'),
+ 'ssl' => Request::post('ssl'),
+ 'certificate' => Request::post('certificate', ''),
+ 'taskid' => $this->scanTask['id']
+ );
+ $data['prev'] = 'AdAuth_Start';
+ if (preg_match('#^\w+[/\\\\]\w+$#', Request::post('binddn')) || strlen(Request::post('searchbase')) < 2) {
+ $data['next'] = 'AdAuth_SelfSearch';
+ } elseif (empty($data['homeattr'])) {
+ $data['next'] = 'AdAuth_HomeAttrCheck';
+ } else {
+ $data['next'] = 'AdAuth_CheckCredentials';
+ }
+ Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad_ldap-checkconnection', $data);
+ }
+
+}
+
+class AdAuth_SelfSearch extends AddModule_Base
+{
+
+ private $taskIds;
+ private $originalBindDn;
+
+ protected function preprocessInternal()
+ {
+ $server = Request::post('server');
+ $port = Request::post('port');
+ $searchbase = Request::post('searchbase', '');
+ $binddn = Request::post('binddn');
+ $bindpw = Request::post('bindpw');
+ $ssl = Request::post('ssl', 'off') === 'on';
+ if ($ssl && !Request::post('fingerprint')) {
+ Message::addError('error-read', 'fingerprint');
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ if (empty($server) || empty($binddn) || empty($port)) {
+ Message::addError('empty-field');
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ $this->originalBindDn = '';
+ // Fix bindDN if short name given
+ //
+ if ($ssl) { // Use the specific AD ports so the domain\username bind works
+ $uri = "ldaps://$server:3269/";
+ } else {
+ $uri = "ldap://$server:3268/";
+ }
+ preg_match('#^\w+[/\\\\](\w+)$#', $binddn, $out);
+ $user = $out[1];
+ $this->originalBindDn = str_replace('/', '\\', $binddn);
+ $selfSearch = Taskmanager::submit('LdapSearch', array(
+ 'server' => $uri,
+ 'searchbase' => $searchbase,
+ 'binddn' => $this->originalBindDn,
+ 'bindpw' => $bindpw,
+ 'filter' => "sAMAccountName=$user"
+ ));
+ if (!isset($selfSearch['id'])) {
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ $this->taskIds['self-search'] = $selfSearch['id'];
+ }
+
+ protected function renderInternal()
+ {
+ $data = array(
+ 'edit' => Request::post('edit'),
+ 'title' => Request::post('title'),
+ 'server' => Request::post('server'),
+ 'port' => Request::post('port'),
+ 'searchbase' => Request::post('searchbase'),
+ 'binddn' => Request::post('binddn'),
+ 'bindpw' => Request::post('bindpw'),
+ 'home' => Request::post('home'),
+ 'homeattr' => Request::post('homeattr'),
+ 'ssl' => Request::post('ssl') === 'on',
+ 'fingerprint' => Request::post('fingerprint'),
+ 'certificate' => Request::post('certificate', ''),
+ 'originalbinddn' => $this->originalBindDn,
+ 'prev' => 'AdAuth_Start'
+ );
+ if (empty($data['homeattr'])) {
+ $data['next'] = 'AdAuth_HomeAttrCheck';
+ } else {
+ $data['next'] = 'AdAuth_CheckCredentials';
+ }
+ Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad-selfsearch',
+ array_merge($this->taskIds, $data));
+ }
+
+}
+
+class AdAuth_HomeAttrCheck extends AddModule_Base
+{
+
+ private $taskIds;
+
+ protected function preprocessInternal()
+ {
+ $server = Request::post('server');
+ $port = Request::post('port');
+ $searchbase = Request::post('searchbase', '');
+ $binddn = Request::post('binddn');
+ $bindpw = Request::post('bindpw');
+ $ssl = Request::post('ssl', 'off') === 'on';
+ if ($ssl && !Request::post('fingerprint')) {
+ Message::addError('error-read', 'fingerprint');
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ if (empty($server) || empty($binddn) || empty($port)) {
+ Message::addError('empty-field');
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ if ($ssl) {
+ $uri = "ldaps://$server:$port/";
+ } else {
+ $uri = "ldap://$server:$port/";
+ }
+ preg_match('#^(\w+=[^,]+),#', $binddn, $out);
+ $filter = $out[1];
+ $data = array(
+ 'server' => $uri,
+ 'searchbase' => $searchbase,
+ 'binddn' => $binddn,
+ 'bindpw' => $bindpw,
+ 'filter' => $filter
+ );
+ $selfSearch = Taskmanager::submit('LdapSearch', $data);
+ if (!isset($selfSearch['id'])) {
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ $this->taskIds['self-search'] = $selfSearch['id'];
+ }
+
+ protected function renderInternal()
+ {
+ Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad-selfsearch', array_merge($this->taskIds, array(
+ 'edit' => Request::post('edit'),
+ 'title' => Request::post('title'),
+ 'server' => Request::post('server'),
+ 'port' => Request::post('port'),
+ 'searchbase' => Request::post('searchbase'),
+ 'binddn' => Request::post('binddn'),
+ 'bindpw' => Request::post('bindpw'),
+ 'home' => Request::post('home'),
+ 'homeattr' => Request::post('homeattr'),
+ 'ssl' => Request::post('ssl') === 'on',
+ 'fingerprint' => Request::post('fingerprint'),
+ 'certificate' => Request::post('certificate', ''),
+ 'originalbinddn' => Request::post('originalbinddn'),
+ 'tryHomeAttr' => true,
+ 'prev' => 'AdAuth_Start',
+ 'next' => 'AdAuth_CheckCredentials'
+ ))
+ );
+ }
+
+}
+
+class AdAuth_CheckCredentials extends AddModule_Base
+{
+
+ private $taskIds;
+
+ protected function preprocessInternal()
+ {
+ $server = Request::post('server');
+ $port = Request::post('port');
+ $searchbase = Request::post('searchbase', '');
+ $binddn = Request::post('binddn');
+ $bindpw = Request::post('bindpw');
+ $ssl = Request::post('ssl', 'off') === 'on';
+ if ($ssl && !Request::post('fingerprint')) {
+ Message::addError('error-read', 'fingerprint');
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ if (empty($server) || empty($binddn) || empty($port)) {
+ Message::addError('empty-field');
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ // Test query 4 users
+ if ($ssl) {
+ $uri = "ldaps://$server:$port/";
+ } else {
+ $uri = "ldap://$server:$port/";
+ }
+ $ldapSearch = Taskmanager::submit('LdapSearch', array(
+ 'server' => $uri,
+ 'searchbase' => $searchbase,
+ 'binddn' => $binddn,
+ 'bindpw' => $bindpw
+ ));
+ if (!isset($ldapSearch['id'])) {
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ $this->taskIds = array(
+ 'tm-search' => $ldapSearch['id']
+ );
+ if (isset($selfSearch['id']))
+ $this->taskIds['self-search'] = $selfSearch['id'];
+ }
+
+ protected function renderInternal()
+ {
+ Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad_ldap-checkcredentials', array_merge($this->taskIds, array(
+ 'edit' => Request::post('edit'),
+ 'title' => Request::post('title'),
+ 'server' => Request::post('server') . ':' . Request::post('port'),
+ 'searchbase' => Request::post('searchbase'),
+ 'binddn' => Request::post('binddn'),
+ 'bindpw' => Request::post('bindpw'),
+ 'home' => Request::post('home'),
+ 'homeattr' => Request::post('homeattr'),
+ 'ssl' => Request::post('ssl') === 'on',
+ 'fingerprint' => Request::post('fingerprint'),
+ 'certificate' => Request::post('certificate', ''),
+ 'originalbinddn' => Request::post('originalbinddn'),
+ 'prev' => 'AdAuth_Start',
+ 'next' => 'AdAuth_Finish'
+ ))
+ );
+ }
+
+}
+
+class AdAuth_Finish extends AddModule_Base
+{
+
+ private $taskIds;
+
+ protected function preprocessInternal()
+ {
+ $binddn = Request::post('binddn');
+ $searchbase = Request::post('searchbase');
+ if (empty($searchbase)) {
+ // If no search base was given, determine it from the dn
+ $originalBindDn = str_replace('\\', '/', trim(Request::post('originalbinddn')));
+ if (!preg_match('#^([^/]+)/[^/]+$#', $originalBindDn, $out)) {
+ Message::addError('value-invalid', 'binddn', $originalBindDn);
+ Util::redirect('?do=SysConfig&action=addmodule&step=AdAuth_Start');
+ } // $out[1] is the domain
+ // Find the domain in the dn
+ $i = mb_stripos($binddn, '=' . $out[1] . ',');
+ if ($i === false) {
+ Message::addError('value-invalid', 'binddn', $out[1]);
+ Util::redirect('?do=SysConfig&action=addmodule&step=AdAuth_Start');
+ }
+ // Now find ',' before it so we get the key
+ $i = mb_strrpos(mb_substr($binddn, 0, $i), ',');
+ if ($i === false)
+ $i = -1;
+ $searchbase = mb_substr($binddn, $i + 1);
+ } else {
+ $somedn = Request::post('somedn', false);
+ if (!empty($somedn)) {
+ $i = stripos($somedn, $searchbase);
+ if ($i !== false) {
+ $searchbase = substr($somedn, $i, strlen($searchbase));
+ }
+ }
+ }
+ $title = Request::post('title');
+ if (empty($title))
+ $title = 'AD: ' . Request::post('server');
+ if ($this->edit === false)
+ $module = ConfigModule::getInstance('AdAuth');
+ else
+ $module = $this->edit;
+ $ssl = Request::post('ssl', 'off') === 'on';
+ $module->setData('server', Request::post('server'));
+ $module->setData('searchbase', $searchbase);
+ $module->setData('binddn', $binddn);
+ $module->setData('bindpw', Request::post('bindpw'));
+ $module->setData('home', Request::post('home'));
+ $module->setData('homeattr', Request::post('homeattr'));
+ $module->setData('certificate', Request::post('certificate'));
+ $module->setData('ssl', $ssl);
+ if ($ssl) {
+ $module->setData('fingerprint', Request::post('fingerprint', ''));
+ } else {
+ $module->setData('fingerprint', '');
+ }
+ if ($this->edit !== false)
+ $ret = $module->update($title);
+ else
+ $ret = $module->insert($title);
+ if (!$ret) {
+ Message::addError('value-invalid', 'any', 'any');
+ $tgz = false;
+ } else {
+ $parent = $this->stopOldInstance();
+ $tgz = $module->generate($this->edit === false, $parent);
+ }
+ if ($tgz === false) {
+ AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
+ return;
+ }
+ $this->taskIds = array(
+ 'tm-config' => $tgz,
+ );
+ }
+
+ private function stopOldInstance()
+ {
+ if ($this->edit === false)
+ return NULL;
+ $list = ConfigTgz::getAllForModule($this->edit->id());
+ if (!is_array($list))
+ return NULL;
+ $parent = NULL;
+ foreach ($list as $tgz) {
+ if (!$tgz->isActive())
+ continue;
+ $task = Trigger::ldadp($tgz->id(), $parent);
+ if (isset($task['id']))
+ $parent = $task['id'];
+ }
+ return $parent;
+ }
+
+ protected function renderInternal()
+ {
+ Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad-finish', $this->taskIds);
+ }
+
+}
diff --git a/modules-available/sysconfig/addmodule_branding.inc.php b/modules-available/sysconfig/addmodule_branding.inc.php
new file mode 100644
index 00000000..67e20892
--- /dev/null
+++ b/modules-available/sysconfig/addmodule_branding.inc.php
@@ -0,0 +1,238 @@
+<?php
+
+/*
+ * Wizard for including a branding logo.
+ */
+
+class Branding_Start extends AddModule_Base
+{
+
+ protected function renderInternal()
+ {
+ Render::addScriptBottom('fileselect');
+ Render::addDialog(Dictionary::translate('config-module', 'branding_title'), false, 'branding-start', array(
+ 'step' => 'Branding_ProcessFile',
+ 'edit' => $this->edit ? $this->edit->id() : false
+ ));
+ }
+
+}
+
+class Branding_ProcessFile extends AddModule_Base
+{
+
+ private $task;
+ private $svgFile;
+ private $tarFile;
+
+ protected function preprocessInternal()
+ {
+ $url = Request::post('url');
+ if ((!isset($_FILES['file']['error']) || $_FILES['file']['error'] === UPLOAD_ERR_NO_FILE) && empty($url)) {
+ Message::addError('empty-field');
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ }
+
+ $this->svgFile = tempnam(sys_get_temp_dir(), 'bwlp-');
+ if (isset($_FILES['file']['error']) && $_FILES['file']['error'] !== UPLOAD_ERR_NO_FILE) {
+ // Prefer uploaded image over URL (in case both are given)
+ if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) {
+ Message::addError('upload-failed', Util::uploadErrorString($_FILES['file']['error']));
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ }
+ if (!move_uploaded_file($_FILES["file"]["tmp_name"], $this->svgFile)) {
+ Message::addError('upload-failed', 'Moving temp file failed');
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ }
+ } else {
+ // URL - launch task that fetches the SVG file from it
+ if (strpos($url, '://') === false)
+ $url = "http://$url";
+ $title = false;
+ if (!$this->downloadSvg($this->svgFile, $url, $title))
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ Session::set('logo_name', $title);
+ }
+ chmod($this->svgFile, 0644);
+ $this->tarFile = '/tmp/bwlp-' . time() . '-' . mt_rand() . '.tgz';
+ $this->task = Taskmanager::submit('BrandingGenerator', array(
+ 'tarFile' => $this->tarFile,
+ 'svgFile' => $this->svgFile
+ ));
+ $this->task = Taskmanager::waitComplete($this->task, 5000);
+ if (Taskmanager::isFailed($this->task)) {
+ @unlink($this->svgFile);
+ Taskmanager::addErrorMessage($this->task);
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ }
+ Session::set('logo_tgz', $this->tarFile);
+ Session::save();
+ }
+
+ protected function renderInternal()
+ {
+ $svg = $png = false;
+ if (isset($this->task['data']['pngFile']))
+ $png = base64_encode(file_get_contents($this->task['data']['pngFile']));
+ if (filesize($this->svgFile) < 1000000)
+ $svg = base64_encode(file_get_contents($this->svgFile));
+ Render::addDialog(Dictionary::translate('config-module', 'branding_title'), false, 'branding-check', array(
+ 'png' => $png,
+ 'svg' => $svg,
+ 'error' => $this->task['data']['error'],
+ 'step' => 'Branding_Finish',
+ 'edit' => $this->edit ? $this->edit->id() : false,
+ 'title' => $this->edit ? $this->edit->title() : false
+ )
+ );
+ @unlink($this->svgFile);
+ }
+
+ /**
+ * Download an svg file from the given url. This function has "wikipedia support", it tries to detect
+ * URLs in wikipedia articles or thumbnails and then find the actual svg file.
+ *
+ * @param string $svgName file to download to
+ * @param string $url url to download from
+ * @return boolean true of download succeded, false on download error (also returns true if downloaded file doesn't
+ * seem to be svg!)
+ */
+ private function downloadSvg($svgName, $url, &$title)
+ {
+ $title = false;
+ // [wikipedia] Did someone paste a link to a thumbnail of the svg? Let's fix that...
+ if (preg_match('#^(.*)/thumb/(.*\.svg)/.*\.svg#', $url, $out)) {
+ $url = $out[1] . '/' . $out[2];
+ }
+ for ($i = 0; $i < 5; ++$i) {
+ $code = 400;
+ if (!Download::toFile($svgName, $url, 3, $code) || $code < 200 || $code > 299) {
+ Message::addError('remote-timeout', $url, $code);
+ return false;
+ }
+ $content = FileUtil::readFile($svgName, 25000);
+ // Is svg file?
+ if (strpos($content, '<svg') !== false)
+ return true; // Found an svg tag - don't try to find links to the actual image
+
+ // [wikipedia] Try to be nice and detect links that might give a hint where the svg can be found
+ if (preg_match_all('#href="([^"]*upload.wikimedia.org/[^"]*/[^"]*/[^"]*\.svg|[^"]+/[^"]+:[^"]+\.svg[^"]*)"#', $content, $out, PREG_PATTERN_ORDER)) {
+ if ($title === false && preg_match('#<title>([^<]*)</title>#i', $content, $tout))
+ $title = trim(preg_replace('/\W*Wikipedia.*/', '', $tout[1]));
+ foreach ($out[1] as $res) {
+ if (strpos($res, 'action=edit') !== false)
+ continue;
+ $new = $this->internetCombineUrl($url, html_entity_decode($res, ENT_COMPAT, 'UTF-8'));
+ if ($new !== $url)
+ break;
+ }
+ if ($new === $url)
+ break;
+ $url = $new;
+ continue;
+ }
+ break;
+ }
+ Message::addError('no-image');
+ return false;
+ }
+
+ /**
+ * Make relative url absolute.
+ *
+ * @param string $absolute absolute url to use as base
+ * @param string $relative relative url that will be converted to an absolute url
+ * @return string combined absolute url
+ */
+ private function internetCombineUrl($absolute, $relative)
+ {
+ $p = parse_url($relative);
+ if (!empty($p["scheme"]))
+ return $relative;
+
+ $parsed = parse_url($absolute);
+ $path = dirname($parsed['path']);
+
+ if ($relative{0} === '/') {
+ if ($relative{1} === '/')
+ return "{$parsed['scheme']}:$relative";
+ $cparts = array_filter(explode("/", $relative));
+ } else {
+ $aparts = array_filter(explode("/", $path));
+ $rparts = array_filter(explode("/", $relative));
+ $cparts = array_merge($aparts, $rparts);
+ foreach ($cparts as $i => $part) {
+ if ($part == '.') {
+ $cparts[$i] = null;
+ }
+ if ($part == '..') {
+ $cparts[$i - 1] = null;
+ $cparts[$i] = null;
+ }
+ }
+ $cparts = array_filter($cparts);
+ }
+ $path = implode("/", $cparts);
+ $url = "";
+ if (!empty($parsed['scheme']))
+ $url = $parsed['scheme'] . "://";
+ if (!empty($parsed['user'])) {
+ $url .= $parsed['user'];
+ if (!empty($parsed['pass']))
+ $url .= ":" . $parsed['pass'];
+ $url .= "@";
+ }
+ if ($parsed['host'])
+ $url .= $parsed['host'] . "/";
+ $url .= $path;
+ return $url;
+ }
+
+}
+
+class Branding_Finish extends AddModule_Base
+{
+
+ protected function preprocessInternal()
+ {
+ $title = Request::post('title');
+ if ($title === false || empty($title))
+ $title = Session::get('logo_name');
+ if ($title === false || empty($title)) {
+ Message::addError('missing-title'); // TODO: Ask for title again instead of starting over
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ }
+ $tgz = Session::get('logo_tgz');
+ if ($tgz === false || !file_exists($tgz)) {
+ Message::addError('error-read', $tgz);
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ }
+ if ($this->edit === false)
+ $module = ConfigModule::getInstance('Branding');
+ else
+ $module = $this->edit;
+ if ($module === false) {
+ Message::addError('error-read', 'branding.inc.php');
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ }
+ $module->setData('tmpFile', $tgz);
+ if ($this->edit !== false)
+ $ret = $module->update($title);
+ else
+ $ret = $module->insert($title);
+ if (!$ret)
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ elseif ($module->generate($this->edit === false, NULL, 200) === false)
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ Session::set('logo_tgz', false);
+ Session::set('logo_name', false);
+ Session::save();
+ // Yay
+ if ($this->edit !== false)
+ Message::addSuccess('module-edited');
+ else
+ Message::addSuccess('module-added');
+ Util::redirect('?do=SysConfig');
+ }
+
+}
diff --git a/modules-available/sysconfig/addmodule_custommodule.inc.php b/modules-available/sysconfig/addmodule_custommodule.inc.php
new file mode 100644
index 00000000..023463f7
--- /dev/null
+++ b/modules-available/sysconfig/addmodule_custommodule.inc.php
@@ -0,0 +1,163 @@
+<?php
+
+/*
+ * Wizard for adding a custom module. A custom module is a plain archive that gets
+ * included into a config.tgz the way itz is. No handling, sanity checks or anything
+ * fancy is happening.
+ */
+
+class CustomModule_Start extends AddModule_Base
+{
+
+ protected function renderInternal()
+ {
+ Session::set('mod_temp', false);
+ Render::addScriptBottom('fileselect');
+ Render::addDialog(Dictionary::translate('config-module', 'custom_title'), false, 'custom-upload', array(
+ 'step' => 'CustomModule_ProcessUpload',
+ 'edit' => $this->edit ? $this->edit->id() : false
+ ));
+ }
+
+}
+
+/**
+ * Some file has just been uploaded. Try to store it, then try to unpack/analyze it.
+ * If this succeeds, we proceed to the next step where we present the user our findings
+ * and ask what to do with this.
+ */
+class CustomModule_ProcessUpload extends AddModule_Base
+{
+
+ private $taskId = false;
+
+ protected function preprocessInternal()
+ {
+ if (!isset($_FILES['modulefile'])) {
+ Message::addError('missing-file');
+ Util::redirect('?do=SysConfig');
+ }
+ if ($_FILES['modulefile']['error'] != UPLOAD_ERR_OK) {
+ Message::addError('upload-failed', Util::uploadErrorString($_FILES['modulefile']['error']));
+ Util::redirect('?do=SysConfig');
+ }
+ $tempfile = '/tmp/bwlp-' . mt_rand(1, 100000) . '-' . crc32($_SERVER['REMOTE_ADDR']) . '.tmp';
+ if (!move_uploaded_file($_FILES['modulefile']['tmp_name'], $tempfile)) {
+ Message::addError('error-write', $tempfile);
+ Util::redirect('?do=SysConfig');
+ }
+ $this->taskId = 'tgzmod' . mt_rand() . '-' . microtime(true);
+ Taskmanager::submit('ListArchive', array(
+ 'id' => $this->taskId,
+ 'file' => $tempfile
+ ), true);
+ Session::set('mod_temp', $tempfile);
+ }
+
+ protected function renderInternal()
+ {
+ $status = Taskmanager::waitComplete($this->taskId);
+ Taskmanager::release($this->taskId);
+ $tempfile = Session::get('mod_temp');
+ if (!isset($status['statusCode'])) {
+ unlink($tempfile);
+ $this->tmError();
+ }
+ if ($status['statusCode'] != TASK_FINISHED) {
+ unlink($tempfile);
+ $this->taskError($status);
+ }
+ // Sort files for better display
+ $dirs = array();
+ foreach ($status['data']['entries'] as $file) {
+ if ($file['isdir']) continue;
+ $dirs[dirname($file['name'])][] = $file;
+ }
+ ksort($dirs);
+ $list = array();
+ foreach ($dirs as $dir => $files) {
+ $list[] = array(
+ 'name' => $dir,
+ 'isdir' => true
+ );
+ sort($files);
+ foreach ($files as $file) {
+ $file['size'] = Util::readableFileSize($file['size']);
+ $list[] = $file;
+ }
+ }
+ if ($this->edit !== false)
+ $title = $this->edit->title();
+ elseif (isset($_FILES['modulefile']['name']))
+ $title = basename($_FILES['modulefile']['name']);
+ else
+ $title = '';
+ Render::addDialog(Dictionary::translate('config-module', 'custom_title'), false, 'custom-fileselect', array(
+ 'step' => 'CustomModule_CompressModule',
+ 'files' => $list,
+ 'edit' => $this->edit ? $this->edit->id() : false,
+ 'title' => $title
+ ));
+ Session::save();
+ }
+
+}
+
+class CustomModule_CompressModule extends AddModule_Base
+{
+
+ private $taskId = false;
+
+ protected function preprocessInternal()
+ {
+ $title = Request::post('title');
+ $tempfile = Session::get('mod_temp');
+ if (empty($title) || empty($tempfile) || !file_exists($tempfile)) {
+ Message::addError('empty-field');
+ Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start');
+ }
+ // Recompress using task manager
+ $this->taskId = 'tgzmod' . mt_rand() . '-' . microtime(true);
+ $destFile = tempnam(sys_get_temp_dir(), 'bwlp-') . '.tgz';
+ Taskmanager::submit('RecompressArchive', array(
+ 'id' => $this->taskId,
+ 'inputFiles' => array($tempfile),
+ 'outputFile' => $destFile
+ ), true);
+ $status = Taskmanager::waitComplete($this->taskId);
+ unlink($tempfile);
+ if (!isset($status['statusCode'])) {
+ $this->tmError();
+ }
+ if ($status['statusCode'] != TASK_FINISHED) {
+ $this->taskError($status);
+ }
+ // Seems ok, create entry
+ if ($this->edit === false)
+ $module = ConfigModule::getInstance('CustomModule');
+ else
+ $module = $this->edit;
+ if ($module === false) {
+ Message::addError('error-read', 'custommodule.inc.php');
+ Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start');
+ }
+ $module->setData('tmpFile', $destFile);
+ if ($this->edit !== false)
+ $ret = $module->update($title);
+ else
+ $ret = $module->insert($title);
+ if (!$ret)
+ Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start');
+ elseif (!$module->generate($this->edit === false, NULL, 200))
+ Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start');
+ Session::set('mod_temp', false);
+ Session::save();
+ // Yay
+ if ($this->edit !== false)
+ Message::addSuccess('module-edited');
+ else
+ Message::addSuccess('module-added');
+ Util::redirect('?do=SysConfig');
+ }
+
+}
diff --git a/modules-available/sysconfig/addmodule_ldapauth.inc.php b/modules-available/sysconfig/addmodule_ldapauth.inc.php
new file mode 100644
index 00000000..e17469a0
--- /dev/null
+++ b/modules-available/sysconfig/addmodule_ldapauth.inc.php
@@ -0,0 +1,234 @@
+<?php
+
+/*
+ * Wizard for setting up ldap integration for authentication.
+ */
+
+class LdapAuth_Start extends AddModule_Base
+{
+
+ protected function renderInternal()
+ {
+ $LDAPAUTH_COMMON_FIELDS = array('title', 'server', 'searchbase', 'binddn', 'bindpw', 'home', 'ssl', 'certificate');
+ $data = array();
+ if ($this->edit !== false) {
+ moduleToArray($this->edit, $data, $LDAPAUTH_COMMON_FIELDS);
+ $data['title'] = $this->edit->title();
+ $data['edit'] = $this->edit->id();
+ }
+ postToArray($data, $LDAPAUTH_COMMON_FIELDS, true);
+ if (preg_match('/^(.*)\:(636|389)$/', $data['server'], $out)) {
+ $data['server'] = $out[1];
+ }
+ $data['step'] = 'LdapAuth_CheckConnection';
+ Render::addDialog(Dictionary::translate('config-module', 'ldapAuth_title'), false, 'ldap-start', $data);
+ }
+
+}
+
+class LdapAuth_CheckConnection extends AddModule_Base
+{
+
+ private $scanTask;
+ private $server;
+
+ protected function preprocessInternal()
+ {
+ $this->server = Request::post('server');
+ $searchbase = Request::post('searchbase');
+ $ssl = Request::post('ssl', 'off') === 'on';
+ if (empty($this->server) || empty($searchbase)) {
+ Message::addError('empty-field');
+ AddModule_Base::setStep('LdapAuth_Start'); // Continues with LdapAuth_Start for render()
+ return;
+ }
+ if (preg_match('/^([^\:]+)\:(\d+)$/', $this->server, $out)) {
+ $ports = array($out[2]);
+ $this->server = $out[1];
+ } elseif ($ssl) {
+ $ports = array(636, 3269);
+ } else {
+ $ports = array(389, 3268);
+ }
+ $this->scanTask = Taskmanager::submit('PortScan', array(
+ 'host' => $this->server,
+ 'ports' => $ports,
+ 'certificate' => Request::post('certificate', '')
+ ));
+ if (!isset($this->scanTask['id'])) {
+ AddModule_Base::setStep('LdapAuth_Start'); // Continues with LdapAuth_Start for render()
+ return;
+ }
+ }
+
+ protected function renderInternal()
+ {
+ $data = array(
+ 'edit' => Request::post('edit'),
+ 'title' => Request::post('title'),
+ 'server' => $this->server,
+ 'searchbase' => Util::normalizeDn(Request::post('searchbase')),
+ 'binddn' => Util::normalizeDn(Request::post('binddn')),
+ 'bindpw' => Request::post('bindpw'),
+ 'home' => Request::post('home'),
+ 'ssl' => Request::post('ssl'),
+ 'certificate' => Request::post('certificate', ''),
+ 'taskid' => $this->scanTask['id']
+ );
+ $data['prev'] = 'LdapAuth_Start';
+ $data['next'] = 'LdapAuth_CheckCredentials';
+ Render::addDialog(Dictionary::translate('config-module', 'ldapAuth_title'), false, 'ad_ldap-checkconnection', $data);
+ }
+
+}
+
+class LdapAuth_CheckCredentials extends AddModule_Base
+{
+
+ private $taskIds;
+
+ protected function preprocessInternal()
+ {
+ $server = Request::post('server');
+ $port = Request::post('port');
+ $searchbase = Request::post('searchbase', '');
+ $binddn = Request::post('binddn');
+ $bindpw = Request::post('bindpw');
+ $ssl = Request::post('ssl', 'off') === 'on';
+ if ($ssl && !Request::post('fingerprint')) {
+ Message::addError('error-read', 'fingerprint');
+ AddModule_Base::setStep('LdapAuth_Start'); // Continues with LdapAuth_Start for render()
+ return;
+ }
+ if (empty($server) || empty($port)) {
+ Message::addError('empty-field');
+ AddModule_Base::setStep('LdapAuth_Start'); // Continues with LdapAuth_Start for render()
+ return;
+ }
+ $parent = null;
+ $server .= ':' . $port;
+ if ($ssl) {
+ $uri = "ldaps://$server/";
+ } else {
+ $uri = "ldap://$server/";
+ }
+ $ldapSearch = Taskmanager::submit('LdapSearch', array(
+ 'parentTask' => $parent,
+ 'server' => $uri,
+ 'searchbase' => $searchbase,
+ 'binddn' => $binddn,
+ 'bindpw' => $bindpw,
+ 'plainldap' => true,
+ ));
+ if (!isset($ldapSearch['id'])) {
+ AddModule_Base::setStep('LdapAuth_Start'); // Continues with LdapAuth_Start for render()
+ return;
+ }
+ $this->taskIds = array(
+ 'tm-search' => $ldapSearch['id']
+ );
+ if (isset($selfSearch['id']))
+ $this->taskIds['self-search'] = $selfSearch['id'];
+ }
+
+ protected function renderInternal()
+ {
+ Render::addDialog(Dictionary::translate('config-module', 'ldapAuth_title'), false, 'ad_ldap-checkcredentials', array_merge($this->taskIds, array(
+ 'edit' => Request::post('edit'),
+ 'title' => Request::post('title'),
+ 'server' => Request::post('server') . ':' . Request::post('port'),
+ 'searchbase' => Request::post('searchbase'),
+ 'binddn' => Request::post('binddn'),
+ 'bindpw' => Request::post('bindpw'),
+ 'home' => Request::post('home'),
+ 'ssl' => Request::post('ssl') === 'on',
+ 'fingerprint' => Request::post('fingerprint'),
+ 'certificate' => Request::post('certificate', ''),
+ 'prev' => 'LdapAuth_Start',
+ 'next' => 'LdapAuth_Finish'
+ ))
+ );
+ }
+
+}
+
+class LdapAuth_Finish extends AddModule_Base
+{
+
+ private $taskIds;
+
+ protected function preprocessInternal()
+ {
+ $binddn = Request::post('binddn');
+ $searchbase = Request::post('searchbase');
+ $title = Request::post('title');
+ if (empty($title))
+ $title = 'LDAP: ' . Request::post('server');
+ if ($this->edit === false)
+ $module = ConfigModule::getInstance('LdapAuth');
+ else
+ $module = $this->edit;
+ $somedn = Request::post('somedn', false);
+ if (!empty($somedn)) {
+ $i = stripos($somedn, $searchbase);
+ if ($i !== false) {
+ $searchbase = substr($somedn, $i, strlen($searchbase));
+ }
+ }
+ $ssl = Request::post('ssl', 'off') === 'on';
+ $module->setData('server', Request::post('server'));
+ $module->setData('searchbase', $searchbase);
+ $module->setData('binddn', $binddn);
+ $module->setData('bindpw', Request::post('bindpw'));
+ $module->setData('home', Request::post('home'));
+ $module->setData('certificate', Request::post('certificate'));
+ $module->setData('ssl', $ssl);
+ if ($ssl) {
+ $module->setData('fingerprint', Request::post('fingerprint', ''));
+ } else {
+ $module->setData('fingerprint', '');
+ }
+ if ($this->edit !== false)
+ $ret = $module->update($title);
+ else
+ $ret = $module->insert($title);
+ if (!$ret) {
+ Message::addError('value-invalid', 'any', 'any');
+ $tgz = false;
+ } else {
+ $parent = $this->stopOldInstance();
+ $tgz = $module->generate($this->edit === false, $parent);
+ }
+ if ($tgz === false) {
+ AddModule_Base::setStep('LdapAuth_Start'); // Continues with LdapAuth_Start for render()
+ return;
+ }
+ $this->taskIds = array(
+ 'tm-config' => $tgz,
+ );
+ }
+
+ private function stopOldInstance()
+ {
+ if ($this->edit === false)
+ return NULL;
+ $list = ConfigTgz::getAllForModule($this->edit->id());
+ if (!is_array($list))
+ return NULL;
+ $parent = NULL;
+ foreach ($list as $tgz) {
+ if (!$tgz->isActive())
+ continue;
+ $task = Trigger::ldadp($tgz->id(), $parent);
+ if (isset($task['id']))
+ $parent = $task['id'];
+ }
+ return $parent;
+ }
+
+ protected function renderInternal()
+ {
+ Render::addDialog(Dictionary::translate('config-module', 'ldapAuth_title'), false, 'ldap-finish', $this->taskIds);
+ }
+
+}
diff --git a/modules-available/sysconfig/addmodule_sshconfig.inc.php b/modules-available/sysconfig/addmodule_sshconfig.inc.php
new file mode 100644
index 00000000..5f0f7222
--- /dev/null
+++ b/modules-available/sysconfig/addmodule_sshconfig.inc.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * Wizard for configuring the sshd (client side).
+ */
+
+class SshConfig_Start extends AddModule_Base
+{
+
+ protected function renderInternal()
+ {
+ if ($this->edit !== false) {
+ $data = $this->edit->getData(false) + array(
+ 'title' => $this->edit->title(),
+ 'edit' => $this->edit->id(),
+ 'apl' => $this->edit->getData('allowPasswordLogin') === 'yes'
+ );
+ } else {
+ $data = array();
+ }
+ Render::addDialog(Dictionary::translate('lang_clientSshConfig'), false, 'sshconfig-start', $data + array(
+ 'step' => 'SshConfig_Finish',
+ ));
+ }
+
+}
+
+class SshConfig_Finish extends AddModule_Base
+{
+
+ protected function preprocessInternal()
+ {
+ $title = Request::post('title');
+ if (empty($title)) {
+ Message::addError('missing-title');
+ return;
+ }
+ // Seems ok, create entry
+ if ($this->edit === false)
+ $module = ConfigModule::getInstance('SshConfig');
+ else
+ $module = $this->edit;
+ if ($module === false) {
+ Message::addError('error-read', 'sshconfig.inc.php');
+ Util::redirect('?do=SysConfig&action=addmodule&step=SshConfig_Start');
+ }
+ $module->setData('allowPasswordLogin', Request::post('allowPasswordLogin') === 'yes');
+ if (!$module->setData('listenPort', Request::post('listenPort'))) {
+ Message::addError('value-invalid', 'port', Request::post('listenPort'));
+ Util::redirect('?do=SysConfig&action=addmodule&step=SshConfig_Start');
+ }
+ if (!$module->setData('publicKey', Request::post('publicKey'))) {
+ Message::addError('value-invalid', 'pubkey', Request::post('publicKey'));
+ Util::redirect('?do=SysConfig&action=addmodule&step=SshConfig_Start');
+ }
+ if ($this->edit !== false)
+ $ret = $module->update($title);
+ else
+ $ret = $module->insert($title);
+ if (!$ret)
+ Util::redirect('?do=SysConfig&action=addmodule&step=SshConfig_Start');
+ elseif (!$module->generate($this->edit === false, NULL, 200))
+ Util::redirect('?do=SysConfig&action=addmodule&step=SshConfig_Start');
+ // Yay
+ if ($this->edit !== false)
+ Message::addSuccess('module-edited');
+ else
+ Message::addSuccess('module-added');
+ Util::redirect('?do=SysConfig');
+ }
+
+}
diff --git a/modules-available/sysconfig/config.json b/modules-available/sysconfig/config.json
new file mode 100644
index 00000000..ee5c83c8
--- /dev/null
+++ b/modules-available/sysconfig/config.json
@@ -0,0 +1,5 @@
+{
+ "category":"main.settings",
+ "enabled":"true",
+ "dependencies": [ "minilinux" ]
+}
diff --git a/modules-available/sysconfig/lang/de/templates/_page.json b/modules-available/sysconfig/lang/de/templates/_page.json
new file mode 100644
index 00000000..844a12ef
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/_page.json
@@ -0,0 +1,28 @@
+{
+ "lang_activate": "Aktivieren",
+ "lang_active": "Aktiv",
+ "lang_availableModules": "Verf\u00fcgbare Konfigurationsmodule",
+ "lang_availableSystem": "Verf\u00fcgbare Systemkonfigurationen",
+ "lang_close": "Schlie\u00dfen",
+ "lang_configurationModuleNotFound": "Keine Konfigurationsmodule gefunden!",
+ "lang_delete": "L\u00f6schen",
+ "lang_deleteLong": "Modul oder Konfiguration l\u00f6schen.",
+ "lang_download": "Herunterladen",
+ "lang_downloadLong": "Dieses Modul \"so wie es ist\" herunterladen.",
+ "lang_edit": "Bearbeiten",
+ "lang_editLong": "Modul oder Konfiguration bearbeiten.",
+ "lang_helpModuleConfiguration": "Konfigurationsmodule sind die Bausteine, aus denen eine Systemkonfiguration erstellt wird. Hier lassen sich sowohl generische Module durch einen Wizard anlegen, als auch komplett eigene Module erstellen (fortgeschritten, Linuxkenntnisse erforderlich).",
+ "lang_helpSystemConfiguration": "\u00dcber eine Systemkonfiguration wird die grundlegende Lokalisierung des bwLehrpool-Systems durchgef\u00fchrt. Dazu geh\u00f6ren Aspekte wie das Authentifizierungsverfahren f\u00fcr Benutzer (z.B. Active Directory, LDAP), Druckerkonfiguration, Home-Verzeichnisse, etc. Eine Systemkonfiguration setzt sich aus einem oder mehreren Konfigurationsmodulen zusammen, welche im angrenzenden Panel verwaltet werden k\u00f6nnen.",
+ "lang_legend": "Legende",
+ "lang_moduleConfiguration": "Konfigurationsmodule",
+ "lang_newConfiguration": "Neue Konfiguration",
+ "lang_newModule": "Neues Modul",
+ "lang_rebuild": "Neu generieren",
+ "lang_rebuildLong": "Modul oder Konfiguration neu generieren. Das entsprechende Modul bzw. Konfiguration ist aktuell und sollte nicht neu generiert werden m\u00fcssen.",
+ "lang_rebuildOutdatedLong": "Modul oder Konfiguration neu generieren. Das entsprechende Modul bzw. Konfiguration ist veraltet oder nicht vorhanden.",
+ "lang_show": "Ansehen",
+ "lang_showLong": "Inhalt des Moduls anzeigen.",
+ "lang_systemConfiguration": "Systemkonfiguration",
+ "lang_systemConfigurationAlert": "Bevor Sie eine Systemkonfiguration erstellen k\u00f6nnen, m\u00fcssen Sie zun\u00e4chst ein Konfigurationsmodul erzeugen.",
+ "lang_systemConfigurationNotFound": "Keine Systemkonfigurationen gefunden.Erstellen Sie eine neue Konfiguration aus den unten aufgef\u00fchrten Konfigurationsmodulen."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/ad-finish.json b/modules-available/sysconfig/lang/de/templates/ad-finish.json
new file mode 100644
index 00000000..e91e98dc
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/ad-finish.json
@@ -0,0 +1,7 @@
+{
+ "lang_adStarted": "Der AD-Proxy wird nun konfiguriert und gestartet...",
+ "lang_generateModule": "Modul erzeugen",
+ "lang_restartWizard": "Assistent neustarten",
+ "lang_systemConfiguration": "Systemkonfiguration",
+ "lang_to": "Zur"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/ad-selfsearch.json b/modules-available/sysconfig/lang/de/templates/ad-selfsearch.json
new file mode 100644
index 00000000..89a957ee
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/ad-selfsearch.json
@@ -0,0 +1,10 @@
+{
+ "lang_back": "Zur\u00fcck",
+ "lang_continueAnyway": "Trotzdem weiter",
+ "lang_dnLookup": "Ermitteln der Bind-DN",
+ "lang_homeAttributeExplanation": "Bitte w\u00e4hlen Sie das Attribut, welches das Home-Verzeichnis der User enth\u00e4lt.",
+ "lang_next": "Weiter",
+ "lang_onProblemSearchBase": "Bei Problemen versuchen Sie, die Bind-DN und Suchbasis manuell anzugeben",
+ "lang_selectHomeAttribute": "Home-Attribut",
+ "lang_skip": "\u00dcberspringen"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/ad-start.json b/modules-available/sysconfig/lang/de/templates/ad-start.json
new file mode 100644
index 00000000..a85e133f
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/ad-start.json
@@ -0,0 +1,23 @@
+{
+ "lang_adText1": "Zum Einrichten der Benutzerauthentifizierung \u00fcber ein Active Directory wird neben der Adresse des anzusprechenden Servers ein dedizierter Benutzer im AD ben\u00f6tigt, \u00fcber welchen das AD angesprochen wird. Der Benutzer sollte in der gleichen Dom\u00e4ne sein, wie die Benutzer, die sich sp\u00e4ter an den Arbeitsstationen anmelden werden. Ansonsten ist es notwendig, die Suchbasis anzugeben.",
+ "lang_adText2": "Dieser Benutzer ben\u00f6tigt keine besonderen Rechte, Sie k\u00f6nnen diesem Benutzer zur Sicherheit auch die Rechte zum Anmelden entziehen. Der Benutzer muss lediglich ausreichende Berechtigungen haben, um einen LDAP-Bind am AD durchzuf\u00fchren, und nach Benutzern zu suchen.",
+ "lang_adText3": "Normalerweise k\u00f6nnen Sie als Bind DN die Kurzform im Format dom\u00e4ne\\benutzer angeben. Wenn dies nicht funktioniert, m\u00fcssen Sie den DN des Benutzers ermitteln. Z.B. unter Eingabe des folgenden Befehls auf einem DC:",
+ "lang_adText4": "Nach Eingabe aller ben\u00f6tigten Daten wird im n\u00e4chsten Schritt \u00fcberpr\u00fcft, ob die Kommunikation mit dem AD m\u00f6glich ist.",
+ "lang_asteriskMandatory": "Mit (*) gekennzeichnete Felder sind Pflichtfelder",
+ "lang_bindDN": "Bind DN",
+ "lang_close": "Schlie\u00dfen",
+ "lang_customCertificate": "Zur Validierung zus\u00e4tzlich erforderliche (Intermediate-)Zertifikate",
+ "lang_helpHomeAttrHead": "Name des Home-Verzeichnis-Attributs",
+ "lang_helpHomeAttrText": "Hier k\u00f6nnen Sie alternativ zum fest vorgegebenem Template des Home-Verzeichnis Servers den Attributsnamen im Active Directory angeben, der diesen Pfad bereitstellt. Normalerweise ist dies \"homeDirectory\". Wird das Feld leer gelassen, versucht der Assistent, das Attribut selbstst\u00e4ndig zu ermitteln. Falls das Einbinden der Home-Verzeichnisse anschlie\u00dfend nicht funktioniert, \u00fcberpr\u00fcfen Sie bitte den Client-Log (Status->Client Log) und den LDAP-Proxy-Log (Status->Server Status).",
+ "lang_homeAttr": "Home-Attribut",
+ "lang_moduleTitle": "Titel",
+ "lang_next": "Weiter",
+ "lang_password": "Passwort",
+ "lang_searchBase": "Suchbasis",
+ "lang_ssl": "SSL",
+ "lang_sslDescription": "Die Verbindung zum AD-Server mit SSL sichern. (Die Verbindung zwischen Client und Proxy wird in jedem Fall mit SSL abgewickelt.)",
+ "lang_userDirectory": "Benutzerverzeichnis",
+ "lang_userDirectoryInfo1": "Optionale Angabe: Wenn die Clients f\u00fcr die Benutzer ein eigenes Verzeichnis (Homeverzeichnis, Benutzerverzeichnis) von einem Server einbinden sollen, geben Sie bitte hier das Format in UNC-Notation an, also z.B.",
+ "lang_userDirectoryInfo2": "%s ist dabei ein Platzhalter f\u00fcr den Login-Namen des Benutzers.",
+ "lang_userDirectoryInfo3": "Das Verzeichnis wird mit den gleichen Zugangsdaten eingebunden, die der Benutzer beim Login angibt. (D.h. kein Kerberos Support o.\u00e4.)"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/ad_ldap-checkconnection.json b/modules-available/sysconfig/lang/de/templates/ad_ldap-checkconnection.json
new file mode 100644
index 00000000..6bf31104
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/ad_ldap-checkconnection.json
@@ -0,0 +1,8 @@
+{
+ "lang_back": "Zur\u00fcck",
+ "lang_connectionWait": "Pr\u00fcfe Verbindung...",
+ "lang_next": "Weiter",
+ "lang_noOpenPort": "Auf dem angegebenen Server wurde kein offener Port gefunden.",
+ "lang_noValidCert": "Der Server besitzt kein oder ein nicht valides Zertifikat.",
+ "lang_selfSignedNote": "Das Zertifikat des Servers scheint selbst signiert zu sein. Wenn Sie fortfahren wird versucht, die Zertifikatskette vom Server abzufragen. Dies ist in den meisten F\u00e4llen erfolgreich, sollte aber nur getan werden wenn Sie wissen, dass das Zertifikat des Servers von einer unbekannten CA signiert wurde. Falls die Authentifizierung anschlie\u00dfend nicht funktioniert, \u00fcberpr\u00fcfen Sie die LDAP-Proxy Logs auf der Serverstatus-Seite."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/ad_ldap-checkcredentials.json b/modules-available/sysconfig/lang/de/templates/ad_ldap-checkcredentials.json
new file mode 100644
index 00000000..f0ab6e15
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/ad_ldap-checkcredentials.json
@@ -0,0 +1,6 @@
+{
+ "lang_back": "Zur\u00fcck",
+ "lang_connectionWait": "Die Verbindung zum angegebenen AD-Server wird nun \u00fcberpr\u00fcft. Bitte haben Sie einen Moment Geduld.",
+ "lang_onProblemSearchBase": "Falls dieser Schritt fehlschl\u00e4gt, und Sie keine Suchbasis angegeben haben, versuchen Sie es erneut unter expliziter Angabe einer Suchbasis.",
+ "lang_skip": "\u00dcberspringen"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/branding-check.json b/modules-available/sysconfig/lang/de/templates/branding-check.json
new file mode 100644
index 00000000..740cc34f
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/branding-check.json
@@ -0,0 +1,5 @@
+{
+ "lang_brandingInfo": "Unten sehen Sie zur Kontrolle noch einmal das ausgew\u00e4hlte Logo. Sollten Sie das Logo nicht sehen k\u00f6nnen, pr\u00fcfen Sie bitte, ob Sie ein valides SVG-Bild verwendet haben. Alternativ ist es m\u00f6glich, dass beim Verarbeiten des Bildes ein Fehler auftrat. Sie k\u00f6nnen daher das Modul trotzdem speichern und testen, ob das Logo im bwLehrpool-System angezeigt wird.",
+ "lang_save": "Speichern",
+ "lang_title": "Titel"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/branding-start.json b/modules-available/sysconfig/lang/de/templates/branding-start.json
new file mode 100644
index 00000000..e85a51d0
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/branding-start.json
@@ -0,0 +1,9 @@
+{
+ "lang_branding": "Hier k\u00f6nnen Sie ihr Einrichtungslogo im SVG-Format hochladen. Das SVG-Format ist ein Vektorgrafikformat, was zum Skalieren vorteilhaft ist. Eine gute Quelle f\u00fcr SVG-Logos von Unis und Hochschulen ist ihr jeweiliger Wikipedia-Artikel.",
+ "lang_browseForFile": "Durchsuchen",
+ "lang_computerLoad": "Bild von lokalem Rechner hochladen",
+ "lang_or": "oder",
+ "lang_selectFile": "Bitte w\u00e4hlen Sie eine Datei",
+ "lang_upload": "Hochladen",
+ "lang_urlLoad": "Bild von URL laden"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/cfg-finish.json b/modules-available/sysconfig/lang/de/templates/cfg-finish.json
new file mode 100644
index 00000000..86fb7a15
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/cfg-finish.json
@@ -0,0 +1,4 @@
+{
+ "lang_configurationActive": "Konfiguration aktivieren",
+ "lang_configurationSuccess": "Die Konfiguration wurde erfolgreich erstellt."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/cfg-start.json b/modules-available/sysconfig/lang/de/templates/cfg-start.json
new file mode 100644
index 00000000..c22a96c3
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/cfg-start.json
@@ -0,0 +1,7 @@
+{
+ "lang_configuration": "Konfiguration",
+ "lang_configurationChoose": "Bitte w\u00e4hlen Sie, welche Module f\u00fcr diese Konfiguration verwendet werden sollen.",
+ "lang_name": "Name",
+ "lang_next": "Weiter",
+ "lang_noModuleOfType": "Kein Modul dieser Art vorhanden."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/config-module-list.json b/modules-available/sysconfig/lang/de/templates/config-module-list.json
new file mode 100644
index 00000000..f346a107
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/config-module-list.json
@@ -0,0 +1,4 @@
+{
+ "lang_noContent": "Kein Inhalt!",
+ "lang_show": "Ansehen"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/custom-filelist.json b/modules-available/sysconfig/lang/de/templates/custom-filelist.json
new file mode 100644
index 00000000..1a73a858
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/custom-filelist.json
@@ -0,0 +1,3 @@
+{
+ "lang_back": "Zur\u00fcck"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/custom-fileselect.json b/modules-available/sysconfig/lang/de/templates/custom-fileselect.json
new file mode 100644
index 00000000..94085f65
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/custom-fileselect.json
@@ -0,0 +1,5 @@
+{
+ "lang_checkFileContent": "Hier haben Sie die M\u00f6glichkeit, den Inhalt des Archivs noch einmal zu \u00fcberpr\u00fcfen.",
+ "lang_moduleName": "Modulname",
+ "lang_next": "Weiter"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/custom-upload.json b/modules-available/sysconfig/lang/de/templates/custom-upload.json
new file mode 100644
index 00000000..f9c3a8ae
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/custom-upload.json
@@ -0,0 +1,8 @@
+{
+ "lang_browseForFile": "Durchsuchen",
+ "lang_customModuleInfo1": "\u00dcber ein benutzerdefiniertes Modul ist es m\u00f6glich, beliebige Dateien zum Linux-Grundsystem, das auf den Clients gebootet wird, hinzuzuf\u00fcgen. Dazu kann ein Archiv mit einer Dateisystemstruktur hochgeladen werden, die in dieser Form 1:1 in das gebootete Linux extrahiert wird.",
+ "lang_customModuleInfo2": "Beispiel: Enth\u00e4lt das hochgeladene Archiv eine Datei etc\/beispiel.conf, so wird auf einem gebooteten Client diese Datei als \/etc\/beispiel.conf zu finden sein.",
+ "lang_selectFile": "Bitte w\u00e4hlen Sie ein Archiv",
+ "lang_supportedFiles": "Unterst\u00fctzte Archivformate",
+ "lang_upload": "Hochladen"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/ldap-checkconnection.json b/modules-available/sysconfig/lang/de/templates/ldap-checkconnection.json
new file mode 100644
index 00000000..4f2e619e
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/ldap-checkconnection.json
@@ -0,0 +1,5 @@
+{
+ "lang_back": "Zur\u00fcck",
+ "lang_connectionWait": "\u00dcberpr\u00fcfe Verbindung, bitte warten",
+ "lang_next": "Weiter"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/ldap-checkcredentials.json b/modules-available/sysconfig/lang/de/templates/ldap-checkcredentials.json
new file mode 100644
index 00000000..c8dcc9cd
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/ldap-checkcredentials.json
@@ -0,0 +1,6 @@
+{
+ "lang_back": "Zur\u00fcck",
+ "lang_connectionWait": "\u00dcberpr\u00fcfe LDAP-Zugangsdaten",
+ "lang_onProblemSearchBase": "Werden keine Benutzer gefunden, dann \u00fcberpr\u00fcfen Sie bitte die Suchbasis",
+ "lang_skip": "Weiter"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/ldap-finish.json b/modules-available/sysconfig/lang/de/templates/ldap-finish.json
new file mode 100644
index 00000000..7387f3a0
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/ldap-finish.json
@@ -0,0 +1,6 @@
+{
+ "lang_generateModule": "Modul erzeugen",
+ "lang_ldapStarted": "Der LDAP-Proxy wurde gestartet",
+ "lang_restartWizard": "Wizard neu starten",
+ "lang_toSystemConfiguration": "Zur Systemkonfiguration"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/ldap-start.json b/modules-available/sysconfig/lang/de/templates/ldap-start.json
new file mode 100644
index 00000000..df5b1c09
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/ldap-start.json
@@ -0,0 +1,16 @@
+{
+ "lang_bindDN": "Bind DN",
+ "lang_close": "Schlie\u00dfen",
+ "lang_customCertificate": "Zur Validierung zus\u00e4tzlich erforderliche (Intermediate-)Zertifikate",
+ "lang_ldapText1": "Mit diesem Wizard k\u00f6nnen Sie Authentifizierung gegen einen LDAP-Server einrichten.",
+ "lang_ldapText2": "Zu diesem Zweck wird ein LDAP-Proxy auf dem Satelliten-Server gestartet. Dies bedeutet, dass der LDAP-Server von diesem Server aus erreichbar sein muss. Die Pool-PCs hingegen m\u00fcssen nicht direkt mit dem LDAP-Server kommunizieren k\u00f6nnen.",
+ "lang_moduleTitle": "Modulname",
+ "lang_next": "Weiter",
+ "lang_password": "Passwort",
+ "lang_searchBase": "Suchbasis (Search base)",
+ "lang_ssl": "SSL",
+ "lang_sslDescription": "Zum Verbinden mit dem LDAP-Server SSL verwenden. (Die Verbindung zwischen Client und Proxy wird in jedem Fall mit SSL abgewickelt.) Einige LDAP-Server verweigern die Authentifizierung, wenn SSL nicht genutzt wird.",
+ "lang_userDirectory": "Home-Verzeichnis",
+ "lang_userDirectoryInfo1": "UNC-Pfad zum Home-Verzeichnis der Nutzer, mit Platzhalter '%s' f\u00fcr den Benutzernamen.",
+ "lang_userDirectoryInfo2": "Zur Zeit wird nur SMB\/CIFS unterst\u00fctzt."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/templates/sshconfig-start.json b/modules-available/sysconfig/lang/de/templates/sshconfig-start.json
new file mode 100644
index 00000000..ef9286d7
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/sshconfig-start.json
@@ -0,0 +1,10 @@
+{
+ "lang_allowPass": "Login mit Passwort zulassen",
+ "lang_allowPassInfo": "Wenn aktiviert, l\u00e4sst der sshd Logins mit Benutzername\/Passwort-Kombination zu. Ansonsten werden nur Logins nach dem pubkey-Verfahren zugelassen.",
+ "lang_listenPort": "Listen port",
+ "lang_listenPortInfo": "Der Port, auf dem der sshd lauscht. Der offizielle Standard ist 22.",
+ "lang_moduleName": "Modulname",
+ "lang_rootKey": "root pubkey (\u00f6ffentlicher Schl\u00fcssel)",
+ "lang_rootKeyInfo": "Tragen Sie hier den \u00f6ffentlichen Schl\u00fcssel eines Schl\u00fcsselpaars ein, mit dem Sie sich als root-Benutzer an den Clients anmelden wollen. Lassen Sie das Feld leer, um diese Funktion nicht zu verwenden.",
+ "lang_save": "Speichern"
+}
diff --git a/modules-available/sysconfig/lang/de/templates/start.json b/modules-available/sysconfig/lang/de/templates/start.json
new file mode 100644
index 00000000..37992831
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/templates/start.json
@@ -0,0 +1,4 @@
+{
+ "lang_add": "Hinzuf\u00fcgen",
+ "lang_moduleChoose": "Bitte w\u00e4hlen Sie aus, welche Art Konfigurationsmodul Sie erstellen m\u00f6chten."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/module.json b/modules-available/sysconfig/lang/en/module.json
new file mode 100644
index 00000000..c872d62a
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/module.json
@@ -0,0 +1,4 @@
+{
+ "lang_delete": "Delete",
+ "module_name": "Modules"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/_page.json b/modules-available/sysconfig/lang/en/templates/_page.json
new file mode 100644
index 00000000..6f139359
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/_page.json
@@ -0,0 +1,28 @@
+{
+ "lang_activate": "Activate",
+ "lang_active": "Active",
+ "lang_availableModules": "Available Configuration Modules",
+ "lang_availableSystem": "Available System Configuration",
+ "lang_close": "Close",
+ "lang_configurationModuleNotFound": "Configuration module not found!",
+ "lang_delete": "Delete",
+ "lang_deleteLong": "Delete module or configuration.",
+ "lang_download": "Download",
+ "lang_downloadLong": "Download module \"as is\".",
+ "lang_edit": "Edit",
+ "lang_editLong": "Edit module or configuration.",
+ "lang_helpModuleConfiguration": "Configuration modules are the building blocks from which a system configuration is created. Here you can create both generic modules by a wizard, as well as create completely custom modules (advanced Linux knowledge required).",
+ "lang_helpSystemConfiguration": "The fundamental localization of the bwLehrpool system is done through a system configuration. These include aspects such as the authentication method for users (eg Active Directory, LDAP), printer configuration, home directories, etc. A system configuration is composed of one or more configuration modules, which can be managed in the panel next to this one.",
+ "lang_legend": "Legend",
+ "lang_moduleConfiguration": "Module Configuration",
+ "lang_newConfiguration": "New Configuration",
+ "lang_newModule": "New Module",
+ "lang_rebuild": "Rebuild",
+ "lang_rebuildLong": "Rebuild module or configuration.",
+ "lang_rebuildOutdatedLong": "Rebuild module or configuration. The module\/configuration is outdated or missing and should be regenerated.",
+ "lang_show": "Show",
+ "lang_showLong": "Show content of module.",
+ "lang_systemConfiguration": "System Configuration",
+ "lang_systemConfigurationAlert": "Before you can create a system configuration, you must first create a configuration module.",
+ "lang_systemConfigurationNotFound": "No system configurations found. Create a new configuration from the configuration modules listed below."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/ad-finish.json b/modules-available/sysconfig/lang/en/templates/ad-finish.json
new file mode 100644
index 00000000..8f89046e
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/ad-finish.json
@@ -0,0 +1,7 @@
+{
+ "lang_adStarted": "The AD-proxy is now configured and started ...",
+ "lang_generateModule": "Generate Module",
+ "lang_restartWizard": "Restart Wizard",
+ "lang_systemConfiguration": "System Configuration",
+ "lang_to": "To"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/ad-selfsearch.json b/modules-available/sysconfig/lang/en/templates/ad-selfsearch.json
new file mode 100644
index 00000000..70db0620
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/ad-selfsearch.json
@@ -0,0 +1,10 @@
+{
+ "lang_back": "Back",
+ "lang_continueAnyway": "Continue anyway",
+ "lang_dnLookup": "Looking up bind dn",
+ "lang_homeAttributeExplanation": "Please select the attribute which holds the user's home directory.",
+ "lang_next": "Next",
+ "lang_onProblemSearchBase": "On failure, try to pass the bind dn and search base manually",
+ "lang_selectHomeAttribute": "Home attribute",
+ "lang_skip": "Skip"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/ad-start.json b/modules-available/sysconfig/lang/en/templates/ad-start.json
new file mode 100644
index 00000000..ff35fc03
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/ad-start.json
@@ -0,0 +1,23 @@
+{
+ "lang_adText1": "To set up user authentication through Active Directory, a dedicated user is required in AD next to the address of the server, which is addressed by the AD.",
+ "lang_adText2": "This user does not need special rights, you can follow this user to evade security and the rights to log on. The user only needs to have sufficient permissions to perform an LDAP Bind on AD, and search for users.",
+ "lang_adText3": "Next the distinguished name of the user must be specified. You can determine this by dsquery command line program on a domain controller as the following call:",
+ "lang_adText4": "After entering all required data in the next step, it checks whether communication is possible with the AD.",
+ "lang_asteriskMandatory": "Fields marked with (*) are mandatory",
+ "lang_bindDN": "Bind DN",
+ "lang_close": "Close",
+ "lang_customCertificate": "Additional (intermediate) certificates required for certificate validation",
+ "lang_helpHomeAttrHead": "Name of the home directory attribute",
+ "lang_helpHomeAttrText": "Here you can specify the name of the attribute on the Active Directory that contains the path of the home directory server. Usually this is \"homeDirectory\". If you leave this blank, the wiszard will try to determine the attribute name automatically. If home directories don't work, check the client log (Status->Client log) and the LDAP proxy log (Status->Server status).",
+ "lang_homeAttr": "Home attribute",
+ "lang_moduleTitle": "Title",
+ "lang_next": "Next",
+ "lang_password": "Password",
+ "lang_searchBase": "Search Base",
+ "lang_ssl": "SSL",
+ "lang_sslDescription": "Use SSL encryption to talk to AD server.",
+ "lang_userDirectory": "User Directory",
+ "lang_userDirectoryInfo1": "Optional: If the clients should embed a separate directory (home directory, user directory) from a server for the user, please enter here the format in UNC notation, eg",
+ "lang_userDirectoryInfo2": "%s is a placeholder for the user's login name.",
+ "lang_userDirectoryInfo3": "The directory is loaded with the same credentials that the user specifies when login. (That is no Kerberos support, etc.)"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/ad_ldap-checkconnection.json b/modules-available/sysconfig/lang/en/templates/ad_ldap-checkconnection.json
new file mode 100644
index 00000000..6a924f8b
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/ad_ldap-checkconnection.json
@@ -0,0 +1,8 @@
+{
+ "lang_back": "Back",
+ "lang_connectionWait": "Checking connection...",
+ "lang_next": "Next",
+ "lang_noOpenPort": "There is no open LDAP port on this server.",
+ "lang_noValidCert": "The server did not supply a certificate, or the certificate is invalid.",
+ "lang_selfSignedNote": "The certificate of this server cannot be verified using the builtin trust store. If you know that the server's certificate was signed by an unknown CA, you can try to proceed. The chain will then be extracted from the server, which should be successful in most cases. If the authentication module does not work afterwards, check the LDAP-proxy logs on the server status page."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/ad_ldap-checkcredentials.json b/modules-available/sysconfig/lang/en/templates/ad_ldap-checkcredentials.json
new file mode 100644
index 00000000..2b83a231
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/ad_ldap-checkcredentials.json
@@ -0,0 +1,6 @@
+{
+ "lang_back": "Back",
+ "lang_connectionWait": "The connection to the specified AD server is now checked. Please wait a moment.",
+ "lang_onProblemSearchBase": "If this step fails and you didn't supply a search base, try again with a valid one.",
+ "lang_skip": "Skip"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/ad_ldap-homedir.json b/modules-available/sysconfig/lang/en/templates/ad_ldap-homedir.json
new file mode 100644
index 00000000..90d4a211
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/ad_ldap-homedir.json
@@ -0,0 +1,18 @@
+{
+ "lang_folderRedirection": "Folder Redirection",
+ "lang_handlingNotes": "Here you can configure how network shares (like the user's home directory) are mapped inside the VM. Old Versions of bwLehrpool used the VMware Shared Folder technique, which could cause problems with certain file servers. The new \"native mode\" works much better, but on Windows guests, it requires that you (1) use an smb\/cifs file server (Windows Server, Linux with Samba) and (2) have openslx.exe setup to autorun in the VM (this is already configured for bwLehrpool templates). Native mode with fallback is experimental and known to cause temporary freezes with some VMs. Use with care. IMPORTANT: Pick a drive letter for the home directory that will be free in the Virtual Machines. Otherwise, a random letter will be assigned.",
+ "lang_homedirHandling": "(Home) directory handling",
+ "lang_mapModeNative": "Natively map inside the VM [openslx.exe]",
+ "lang_mapModeNativeFallback": "Natively map inside VM; fallback to VMware Shared Folders",
+ "lang_mapModeNone": "Don't map shares at all",
+ "lang_mapModeVmware": "VMware Shared Folders [VMwareTools]",
+ "lang_redirectionWarning": "WARNING: This feature is experimental. It remaps the selected folders after the VM booted (via openslx.exe) to the logged in user's home drive. This might cause problems with applications that start before the pathes are patched, as they will see the old unpatched settings. This also doesn't work fully on Windows 10. If you want to reliably remap these directories, it's recommended to change their locations in the VM before uploading it.",
+ "lang_shareDesktop": "Desktop (Might hide shortcuts created by the tutor)",
+ "lang_shareDocuments": "My Documents",
+ "lang_shareDownloads": "Downloads",
+ "lang_shareHomeDrive": "Home drive letter (Windows)",
+ "lang_shareMapCreate": "Create folders on network share if they don't exist",
+ "lang_shareMedia": "My Music, Videos, Pictures",
+ "lang_shareOther": "Other (Saved Games, Contacts, Favorites, ...)",
+ "lang_shareRemapMode": "Mapping mode"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/branding-check.json b/modules-available/sysconfig/lang/en/templates/branding-check.json
new file mode 100644
index 00000000..6a481d8b
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/branding-check.json
@@ -0,0 +1,5 @@
+{
+ "lang_brandingInfo": "Below you can check the selected logo. If you can not see the logo, please check whether you have used a valid SVG image. Alternatively, it is possible that during processing of the image, an error occurred. Therefore, you can save the module anyway and test whether the logo is displayed in the bwLehrpool system.",
+ "lang_save": "Save",
+ "lang_title": "Title"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/branding-start.json b/modules-available/sysconfig/lang/en/templates/branding-start.json
new file mode 100644
index 00000000..4423784d
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/branding-start.json
@@ -0,0 +1,9 @@
+{
+ "lang_branding": "For best results, you should upload the logo in SVG format. SVG is a vector graphics format, which is advantageous for scaling. A good source for SVG logos of universities and colleges is their respective Wikipedia article.",
+ "lang_browseForFile": "Browse",
+ "lang_computerLoad": "Load image from local computer",
+ "lang_or": "or",
+ "lang_selectFile": "Please select a file",
+ "lang_upload": "Upload",
+ "lang_urlLoad": "Load image from URL"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/cfg-finish.json b/modules-available/sysconfig/lang/en/templates/cfg-finish.json
new file mode 100644
index 00000000..b8729636
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/cfg-finish.json
@@ -0,0 +1,4 @@
+{
+ "lang_configurationActive": "Enable configuration",
+ "lang_configurationSuccess": "The configuration has been successfully created."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/cfg-start.json b/modules-available/sysconfig/lang/en/templates/cfg-start.json
new file mode 100644
index 00000000..9afc6ce3
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/cfg-start.json
@@ -0,0 +1,6 @@
+{
+ "lang_configuration": "Configuration",
+ "lang_configurationChoose": "Please select which modules will be used for this configuration.",
+ "lang_name": "Name",
+ "lang_noModuleOfType": "No module of this type found."
+}
diff --git a/modules-available/sysconfig/lang/en/templates/config-module-list.json b/modules-available/sysconfig/lang/en/templates/config-module-list.json
new file mode 100644
index 00000000..ff30c0b4
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/config-module-list.json
@@ -0,0 +1,4 @@
+{
+ "lang_noContent": "No content!",
+ "lang_show": "Show"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/custom-filelist.json b/modules-available/sysconfig/lang/en/templates/custom-filelist.json
new file mode 100644
index 00000000..50bec5b5
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/custom-filelist.json
@@ -0,0 +1,3 @@
+{
+ "lang_back": "Back"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/custom-fileselect.json b/modules-available/sysconfig/lang/en/templates/custom-fileselect.json
new file mode 100644
index 00000000..2ae25e16
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/custom-fileselect.json
@@ -0,0 +1,5 @@
+{
+ "lang_checkFileContent": "Here you have the possibility to check the contents of the archive again.",
+ "lang_moduleName": "Module Name",
+ "lang_next": "Next"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/custom-upload.json b/modules-available/sysconfig/lang/en/templates/custom-upload.json
new file mode 100644
index 00000000..26b7c186
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/custom-upload.json
@@ -0,0 +1,8 @@
+{
+ "lang_browseForFile": "Browse",
+ "lang_customModuleInfo1": "About a custom module, it is possible to add arbitrary files to a Linux system that is booted clients. For this purpose, an archive can be uploaded using a file system structure that is extracted in this form 1:1 in the booted Linux.",
+ "lang_customModuleInfo2": "Example: If the uploaded archive is the file etc\/example.conf, this file will be located as \/etc\/example.conf to a booted client.",
+ "lang_selectFile": "Please select an archive",
+ "lang_supportedFiles": "Supported File Formats",
+ "lang_upload": "Upload"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/ldap-checkconnection.json b/modules-available/sysconfig/lang/en/templates/ldap-checkconnection.json
new file mode 100644
index 00000000..52fbae9e
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/ldap-checkconnection.json
@@ -0,0 +1,5 @@
+{
+ "lang_back": "Back",
+ "lang_connectionWait": "Checking connection, please wait",
+ "lang_next": "Next"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/ldap-checkcredentials.json b/modules-available/sysconfig/lang/en/templates/ldap-checkcredentials.json
new file mode 100644
index 00000000..2a8723c9
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/ldap-checkcredentials.json
@@ -0,0 +1,6 @@
+{
+ "lang_back": "Back",
+ "lang_connectionWait": "Checking LDAP credentials",
+ "lang_onProblemSearchBase": "If no users are found, please check the search base",
+ "lang_skip": "Next"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/ldap-finish.json b/modules-available/sysconfig/lang/en/templates/ldap-finish.json
new file mode 100644
index 00000000..bd15bafc
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/ldap-finish.json
@@ -0,0 +1,6 @@
+{
+ "lang_generateModule": "Generating module",
+ "lang_ldapStarted": "The LDAP proxy has been launched",
+ "lang_restartWizard": "Restart wizard",
+ "lang_toSystemConfiguration": "Go to system configuration"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/ldap-start.json b/modules-available/sysconfig/lang/en/templates/ldap-start.json
new file mode 100644
index 00000000..45ba19f1
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/ldap-start.json
@@ -0,0 +1,16 @@
+{
+ "lang_bindDN": "Bind domain",
+ "lang_close": "Close",
+ "lang_customCertificate": "Additional (intermediate) certificates required for certificate validation",
+ "lang_ldapText1": "Here you can create a configuration module to authenticate agains an LDAP server",
+ "lang_ldapText2": "An LDAP-Proxy will be launched on this server. This means the LDAP-Server must be reachable from it. The client PCs in the labs however don't have to be able to talk to the LDAP server \u2013 they will use the proxy running on this server.",
+ "lang_moduleTitle": "Module name",
+ "lang_next": "Next",
+ "lang_password": "Password",
+ "lang_searchBase": "Search base",
+ "lang_ssl": "SSL",
+ "lang_sslDescription": "Use SSL to connect to the LDAP-Server. (The connection between clients and proxy uses SSL in either case.) Some LDAP servers require SSL for authenticating connections.",
+ "lang_userDirectory": "Home-Directory",
+ "lang_userDirectoryInfo1": "UNC-path to home directory of user. '%s' can be used as a placeholder for the account name.",
+ "lang_userDirectoryInfo2": "Currently, only CIFS\/SMB is supported."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/sshconfig-start.json b/modules-available/sysconfig/lang/en/templates/sshconfig-start.json
new file mode 100644
index 00000000..1ce553cd
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/sshconfig-start.json
@@ -0,0 +1,10 @@
+{
+ "lang_allowPass": "Allow password login",
+ "lang_allowPassInfo": "When active, logins via username and password are allowed. Otherwise, only pubkey authentication is possible.",
+ "lang_listenPort": "Listen port",
+ "lang_listenPortInfo": "Listen port for the sshd. Default is 22.",
+ "lang_moduleName": "Module name",
+ "lang_rootKey": "root pubkey",
+ "lang_rootKeyInfo": "Here you can add the public key of a keypair that you want to use for authentication as root-user. Leave this field blank to disable the feature.",
+ "lang_save": "Save"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/templates/start.json b/modules-available/sysconfig/lang/en/templates/start.json
new file mode 100644
index 00000000..4567d649
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/templates/start.json
@@ -0,0 +1,4 @@
+{
+ "lang_add": "Add",
+ "lang_moduleChoose": "Please select which type of configuration module you want to create."
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/pt/module.json b/modules-available/sysconfig/lang/pt/module.json
new file mode 100644
index 00000000..b2a9e60c
--- /dev/null
+++ b/modules-available/sysconfig/lang/pt/module.json
@@ -0,0 +1,38 @@
+{
+ "lang_activate": "Ativar",
+ "lang_active": "Ativo",
+ "lang_adText1": "Para configurar a autentica\u00e7\u00e3o de usu\u00e1rio por meio do Active Directory, um usu\u00e1rio dedicado \u00e9 necess\u00e1rio no AD pr\u00f3ximo ao endere\u00e7o do servidor, que \u00e9 endere\u00e7ado pelo AD.",
+ "lang_adText2": "Este usu\u00e1rio n\u00e3o precisa de direitos especiais, voc\u00ea pode seguir este usu\u00e1rio para escapar da seguran\u00e7a e dos direitos para fazer logon. O usu\u00e1rio s\u00f3 precisa ter permiss\u00f5es suficientes para executar a liga\u00e7\u00e3o LDAP no AD, e procurar por usu\u00e1rios.",
+ "lang_adText3": "A seguir o nome distinto do usu\u00e1rio deve ser especificado. Voc\u00ea pode determinar isso pelo programa de linha de comando dsquery em um controlador de dom\u00ednio como a seguinte chamada:",
+ "lang_adText4": "Depois de inserir todos os dados necess\u00e1rios na pr\u00f3xima etapa, ela verifica se a comunica\u00e7\u00e3o \u00e9 poss\u00edvel com o AD.",
+ "lang_availableModules": "M\u00f3dulos de Configura\u00e7\u00e3o Dispon\u00edveis",
+ "lang_availableSystem": "Configura\u00e7\u00f5es de Sistema Dispon\u00edveis",
+ "lang_back": "Voltar",
+ "lang_bindDN": "Vincular DN",
+ "lang_close": "Fechar",
+ "lang_configurationModuleNotFound": "M\u00f3dulo de configura\u00e7\u00e3o n\u00e3o encontrado!",
+ "lang_delete": "Excluir",
+ "lang_file": "Arquivo",
+ "lang_helpModuleConfiguration": "M\u00f3dulos de configura\u00e7\u00e3o s\u00e3o as pe\u00e7as fundamentais para a cria\u00e7\u00e3o de uma configura\u00e7\u00e3o de sistema. Aqui voc\u00ea pode criar tanto m\u00f3dulos gen\u00e9ricos atrav\u00e9s de nossa interface, tanto quanto criar m\u00f3dulos completamente customizados (\u00e9 necess\u00e1rio conhecimento de Linux avan\u00e7ado).",
+ "lang_helpSystemConfiguration": "A localiza\u00e7\u00e3o fundamental do sistema bwLehrpool \u00e9 feita atrav\u00e9s de uma configura\u00e7\u00e3o de sistema. Isso inclui aspectos como o m\u00e9todo de autentica\u00e7\u00e3o de usu\u00e1rios (por exemplo, Diret\u00f3rio Ativo, LDAP), configura\u00e7\u00e3o de impressora, diret\u00f3rios home, etc. Uma configura\u00e7\u00e3o de sistema \u00e9 composta por um ou mais m\u00f3dulo de configura\u00e7\u00e3o, que podem ser gerenciados na parte inferior da p\u00e1gina.",
+ "lang_moduleConfiguration": "Configura\u00e7\u00e3o do M\u00f3dulo",
+ "lang_moduleTitle": "T\u00edtulo",
+ "lang_name": "Nome",
+ "lang_new": "Novo",
+ "lang_newConfiguration": "Nova Configura\u00e7\u00e3o",
+ "lang_newModule": "Novo M\u00f3dulo",
+ "lang_next": "Pr\u00f3ximo",
+ "lang_noContent": "Sem conte\u00fado!",
+ "lang_password": "Senha",
+ "lang_save": "Salvar",
+ "lang_searchBase": "Base de Pesquisa",
+ "lang_show": "Mostrar",
+ "lang_systemConfiguration": "Confgura\u00e7\u00e3o do Sistema",
+ "lang_systemConfigurationAlert": "Antes de criar uma configura\u00e7\u00e3o de sistema, voc\u00ea deve criar primeiro um m\u00f3dulo de configura\u00e7\u00e3o.",
+ "lang_systemConfigurationNotFound": "Nenhuma configura\u00e7\u00e3o de sistena encontrada. Crie uma nova configura\u00e7\u00e3o a partir dos m\u00f3dulos de configura\u00e7\u00e3o abaixo.",
+ "lang_userDirectory": "Diret\u00f3rio de Usu\u00e1rio",
+ "lang_userDirectoryInfo1": "Opcional: Se os clientes devem incorporar um diret\u00f3rio separado (diret\u00f3rio home, diret\u00f3rio de usu\u00e1rio) de um servidor para o usu\u00e1rio, digite aqui o formato em nota\u00e7\u00e3o UNC, por exemplo,",
+ "lang_userDirectoryInfo2": "%s \u00e9 um marcador para o nome de login do usu\u00e1rio.",
+ "lang_userDirectoryInfo3": "O diret\u00f3rio \u00e9 carregado com as mesmas credenciais que o usu\u00e1rio especifica quando entra. (Isto \u00e9, n\u00e3o h\u00e1 suporte Kerberos, etc)",
+ "module_name": "M\u00f3dulos"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/page.inc.php b/modules-available/sysconfig/page.inc.php
new file mode 100644
index 00000000..0c06e125
--- /dev/null
+++ b/modules-available/sysconfig/page.inc.php
@@ -0,0 +1,396 @@
+<?php
+
+class Page_SysConfig extends Page
+{
+
+ /**
+ * Holds all the known configuration modules, with title, description, start class for their wizard, etc.
+ * @var array
+ */
+ protected static $moduleTypes = array();
+
+ /**
+ * Add a known configuration module. Every addmoule_* file should call this
+ * for its module provided.
+ *
+ * @param string $id Internal identifier for the module
+ * @param string $startClass Class to start wizard for creating such a module
+ * @param string $title Title of this module type
+ * @param string $description Description for this module type
+ * @param string $group Title for group this module type belongs to
+ * @param bool $unique Can only one such module be added to a config?
+ * @param int $sortOrder Lower comes first, alphabetical ordering otherwiese
+ */
+ public static function addModule($id, $startClass, $title, $description, $group, $unique, $sortOrder = 0)
+ {
+ self::$moduleTypes[$id] = array(
+ 'startClass' => $startClass,
+ 'title' => $title,
+ 'description' => $description,
+ 'group' => $group,
+ 'unique' => $unique,
+ 'sortOrder' => $sortOrder
+ );
+ }
+
+ /**
+ *
+ * @return array All registered module types
+ */
+ public static function getModuleTypes()
+ {
+ return self::$moduleTypes;
+ }
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ $action = Request::any('action', 'list');
+
+ // Load all addmodule classes, as they populate the $moduleTypes array
+ require_once 'modules/sysconfig/addmodule.inc.php';
+ foreach (glob('modules/sysconfig/addmodule_*.inc.php') as $file) {
+ require_once $file;
+ }
+
+ // Action: "addmodule" (upload new module)
+ if ($action === 'addmodule') {
+ $this->initAddModule();
+ AddModule_Base::preprocess();
+ }
+
+ if ($action === 'module') {
+ // Action: "delmodule" (delete module)
+ if (Request::post('del', 'no') !== 'no') {
+ $this->delModule();
+ }
+ if (Request::post('download', 'no') !== 'no') {
+ $this->downloadModule();
+ }
+ if (Request::post('rebuild', 'no') !== 'no') {
+ $this->rebuildModule();
+ }
+ }
+
+ // Action: "addconfig" (compose config from one or more modules)
+ if ($action === 'addconfig') {
+ $this->initAddConfig();
+ AddConfig_Base::preprocess();
+ }
+
+ if ($action === 'config') {
+ // Action: "delconfig" (delete config)
+ if (Request::post('del', 'no') !== 'no') {
+ $this->delConfig();
+ }
+ // Action "activate" (set sysconfig as active)
+ if (Request::post('activate', 'no') !== 'no') {
+ $this->activateConfig();
+ }
+ // Action "rebuild" (rebuild config.tgz from its modules)
+ if (Request::post('rebuild', 'no') !== 'no') {
+ $this->rebuildConfig();
+ }
+ }
+ }
+
+ /**
+ * Render module; called by main script when this module page should render
+ * its content.
+ */
+ protected function doRender()
+ {
+ Render::setTitle(Dictionary::translate('lang_location'));
+
+ $action = Request::any('action', 'list');
+ switch ($action) {
+ case 'addmodule':
+ AddModule_Base::render();
+ return;
+ case 'addconfig':
+ AddConfig_Base::render();
+ return;
+ case 'list':
+ $this->listConfigs();
+ return;
+ case 'module':
+ $listid = Request::post('list');
+ if ($listid !== false) {
+ $this->listModuleContents($listid);
+ return;
+ }
+ break;
+ case 'config':
+ $listid = Request::post('list');
+ if ($listid !== false) {
+ $this->listConfigContents($listid);
+ return;
+ }
+ break;
+ }
+ Message::addError('invalid-action', $action, 'main');
+ }
+
+ /**
+ * List all configurations and configuration modules.
+ */
+ private function listConfigs()
+ {
+ // Configs
+ $res = Database::simpleQuery("SELECT configtgz.configid, configtgz.title, configtgz.filepath, configtgz.status, GROUP_CONCAT(configtgz_x_module.moduleid) AS modlist"
+ . " FROM configtgz"
+ . " INNER JOIN configtgz_x_module USING (configid)"
+ . " GROUP BY configid"
+ . " ORDER BY title ASC");
+ $configs = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $configs[] = array(
+ 'configid' => $row['configid'],
+ 'config' => $row['title'],
+ 'modlist' => $row['modlist'],
+ 'current' => readlink(CONFIG_HTTP_DIR . '/default/config.tgz') === $row['filepath'],
+ 'needrebuild' => ($row['status'] !== 'OK')
+ );
+ }
+ // Config modules
+ $res = Database::simpleQuery("SELECT moduleid, title, moduletype, status FROM configtgz_module ORDER BY moduletype ASC, title ASC");
+ $modules = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $modules[] = array(
+ 'moduleid' => $row['moduleid'],
+ 'moduletype' => $row['moduletype'],
+ 'module' => $row['title'],
+ 'iscustom' => ($row['moduletype'] === 'CustomModule' || $row['moduletype'] === 'Branding'),
+ 'needrebuild' => ($row['status'] !== 'OK')
+ );
+ }
+ Render::addTemplate('_page', array(
+ 'configs' => $configs,
+ 'modules' => $modules,
+ 'havemodules' => (count($modules) > 0)
+ ));
+ Render::addScriptTop('custom');
+ Render::addFooter('<script> $(window).load(function (e) {
+ forceTable($("#modtable"));
+ forceTable($("#conftable"));
+ }); // </script>');
+ }
+
+ private function listModuleContents($moduleid)
+ {
+ // fetch the data
+ $row = Database::queryFirst("SELECT title, filepath FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
+ if ($row === false) {
+ Message::addError('config-invalid', $moduleid);
+ Util::redirect('?do=SysConfig');
+ }
+
+ // find files in that archive
+ $status = Taskmanager::submit('ListArchive', array(
+ 'file' => $row['filepath']
+ ));
+ if (isset($status['id']))
+ $status = Taskmanager::waitComplete($status, 4000);
+ if (!Taskmanager::isFinished($status) || Taskmanager::isFailed($status)) {
+ Taskmanager::addErrorMessage($status);
+ Util::redirect('?do=SysConfig');
+ }
+
+ // Sort files for better display
+ $dirs = array();
+ foreach ($status['data']['entries'] as $file) {
+ if ($file['isdir'])
+ continue;
+ $dirs[dirname($file['name'])][] = $file;
+ }
+ ksort($dirs);
+ $list = array();
+ foreach ($dirs as $dir => $files) {
+ $list[] = array(
+ 'name' => $dir,
+ 'isdir' => true
+ );
+ sort($files);
+ foreach ($files as $file) {
+ $file['size'] = Util::readableFileSize($file['size']);
+ $list[] = $file;
+ }
+ }
+
+ // render the template
+ Render::addDialog(Dictionary::translate('lang_contentOf') . ' ' . $row['title'], false, 'custom-filelist', array(
+ 'files' => $list,
+ ));
+ }
+
+ private function listConfigContents($configid)
+ {
+ // get config name
+ $config = Database::queryFirst("SELECT title FROM configtgz WHERE configid = :configid LIMIT 1", array('configid' => $configid));
+ if ($config === false) {
+ Message::addError('config-invalid', $configid);
+ Util::redirect('?do=SysConfig');
+ }
+ // fetch the data
+ $res = Database::simpleQuery("SELECT module.moduleid, module.title AS moduletitle"
+ . " FROM configtgz_module module"
+ . " INNER JOIN configtgz_x_module USING (moduleid)"
+ . " WHERE configtgz_x_module.configid = :configid"
+ . " ORDER BY module.title ASC", array('configid' => $configid));
+
+ $modules = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $modules[] = array(
+ 'module' => $row['moduletitle'],
+ 'moduleid' => $row['moduleid']
+ );
+ }
+
+ // render the template
+ Render::addDialog(Dictionary::translate('lang_contentOf') . ' ' . $config['title'], false, 'config-module-list', array(
+ 'modules' => $modules
+ ));
+ }
+
+ private function activateConfig()
+ {
+ $configid = Request::post('activate', 'MISSING');
+ $row = Database::queryFirst("SELECT title, filepath FROM configtgz WHERE configid = :configid LIMIT 1", array('configid' => $configid));
+ if ($row === false) {
+ Message::addError('config-invalid', $configid);
+ Util::redirect('?do=SysConfig');
+ }
+ $task = Taskmanager::submit('LinkConfigTgz', array(
+ 'destination' => $row['filepath']
+ ));
+ if (isset($task['statusCode']) && $task['statusCode'] === TASK_WAITING) {
+ $task = Taskmanager::waitComplete($task['id']);
+ }
+ if (!isset($task['statusCode']) || $task['statusCode'] === TASK_ERROR) {
+ Message::addError('task-error', $task['data']['error']);
+ } elseif ($task['statusCode'] === TASK_FINISHED) {
+ Message::addSuccess('config-activated', $row['title']);
+ Event::activeConfigChanged();
+ }
+ Util::redirect('?do=SysConfig');
+ }
+
+ private function rebuildConfig()
+ {
+ $configid = Request::post('rebuild', 'MISSING');
+ $config = ConfigTgz::get($configid);
+ if ($config === false) {
+ Message::addError('config-invalid', $configid);
+ Util::redirect('?do=SysConfig');
+ }
+ $ret = $config->generate(false, 350); // TODO
+ if ($ret === true)
+ Message::addSuccess('module-rebuilt', $config->title());
+ elseif ($ret === false)
+ Message::addError('module-rebuild-failed', $config->title());
+ else
+ Message::addInfo('module-rebuilding', $config->title());
+ Util::redirect('?do=SysConfig');
+ }
+
+ private function delModule()
+ {
+ $moduleid = Request::post('del', 'MISSING');
+ $row = Database::queryFirst("SELECT title, filepath FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
+ if ($row === false) {
+ Message::addError('config-invalid', $moduleid);
+ Util::redirect('?do=SysConfig');
+ }
+ $existing = Database::queryFirst("SELECT title FROM configtgz_x_module"
+ . " INNER JOIN configtgz USING (configid)"
+ . " WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
+ if ($existing !== false) {
+ Message::addError('module-in-use', $row['title'], $existing['title']);
+ Util::redirect('?do=SysConfig');
+ }
+ $task = Taskmanager::submit('DeleteFile', array(
+ 'file' => $row['filepath']
+ ));
+ if (isset($task['statusCode']) && $task['statusCode'] === TASK_WAITING) {
+ $task = Taskmanager::waitComplete($task['id']);
+ }
+ if (!isset($task['statusCode']) || $task['statusCode'] === TASK_ERROR) {
+ Message::addWarning('task-error', $task['data']['error']);
+ } elseif ($task['statusCode'] === TASK_FINISHED) {
+ Message::addSuccess('module-deleted', $row['title']);
+ }
+ Database::exec("DELETE FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
+ Util::redirect('?do=SysConfig');
+ }
+
+ private function downloadModule()
+ {
+ $moduleid = Request::post('download', 'MISSING');
+ $row = Database::queryFirst("SELECT title, filepath FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
+ if ($row === false) {
+ Message::addError('config-invalid', $moduleid);
+ Util::redirect('?do=SysConfig');
+ }
+ if (!Util::sendFile($row['filepath'], $row['title'] . '.tgz'))
+ Util::redirect('?do=SysConfig');
+ exit(0);
+ }
+
+ private function rebuildModule()
+ {
+ $moduleid = Request::post('rebuild', 'MISSING');
+ $module = ConfigModule::get($moduleid);
+ if ($module === false) {
+ Message::addError('config-invalid', $moduleid);
+ Util::redirect('?do=SysConfig');
+ }
+ $ret = $module->generate(false, 250);
+ if ($ret === true)
+ Message::addSuccess('module-rebuilt', $module->title());
+ elseif ($ret === false)
+ Message::addError('module-rebuild-failed', $module->title());
+ else
+ Message::addInfo('module-rebuilding', $module->title());
+ Util::redirect('?do=SysConfig');
+ }
+
+ private function delConfig()
+ {
+ $configid = Request::post('del', 'MISSING');
+ $config = ConfigTgz::get($configid);
+ if ($config === false) {
+ Message::addError('config-invalid', $configid);
+ Util::redirect('?do=SysConfig');
+ }
+ $config->delete();
+ Util::redirect('?do=SysConfig');
+ }
+
+ private function initAddModule()
+ {
+ ConfigModule::loadDb();
+ require_once 'modules/sysconfig/addmodule.inc.php';
+ $step = Request::any('step', 'AddModule_Start');
+ if (!class_exists($step) && preg_match('/^([a-zA-Z0-9]+)_/', $step, $out)) {
+ require_once 'modules/sysconfig/addmodule_' . strtolower($out[1]) . '.inc.php';
+ }
+ AddModule_Base::setStep($step);
+ }
+
+ private function initAddConfig()
+ {
+ ConfigModule::loadDb();
+ require_once 'modules/sysconfig/addconfig.inc.php';
+ $step = Request::any('step', 0);
+ if ($step === 0)
+ $step = 'AddConfig_Start';
+ AddConfig_Base::setStep($step);
+ }
+
+}
diff --git a/modules-available/sysconfig/templates/_page.html b/modules-available/sysconfig/templates/_page.html
new file mode 100644
index 00000000..1a2f64d6
--- /dev/null
+++ b/modules-available/sysconfig/templates/_page.html
@@ -0,0 +1,227 @@
+<div class="row">
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_availableSystem}}
+ <a class="btn btn-default" data-toggle="modal" data-target="#help-config"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </div>
+ <div class="panel-body">
+ <form method="post" action="?do=SysConfig">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="config">
+ <table id="conftable" class="slx-table" style="max-width:100px !important">
+ {{#configs}}
+ <tr>
+ <td data-modlist="{{modlist}}" class="slx-pointer slx-width-ignore slx-nowrap"onclick="showmod(this, 'bold')" onmouseover="showmod(this, 'fade')" onmouseout="showmod(this, 'reset')">
+ <div class="slx-dyn-ellipsis">{{config}}</div>
+ </td>
+ <td>
+ {{^current}}
+ <button class="btn btn-primary btn-xs" name="activate" value="{{configid}}">
+ <span class="glyphicon glyphicon-flag"></span>
+ {{lang_activate}}
+ </button>
+ {{/current}}
+ {{#current}}
+ <span class="btn btn-success btn-xs slx-nopointer">
+ <span class="glyphicon glyphicon-ok"></span>
+ {{lang_active}}
+ </span>
+ {{/current}}
+ </td>
+ <td class="slx-nowrap">
+ <button
+ {{#needrebuild}}
+ class="refconf btn btn-primary btn-xs"
+ {{/needrebuild}}
+ {{^needrebuild}}
+ class="refconf btn btn-default btn-xs"
+ {{/needrebuild}}
+ name="rebuild" value="{{configid}}" title="{{lang_rebuild}}"><span class="glyphicon glyphicon-refresh"></span></button>
+ </td>
+ <td class="slx-nowrap">
+ <a class="btn btn-success btn-xs" href="?do=SysConfig&amp;action=addconfig&amp;edit={{configid}}" title="{{lang_edit}}"><span class="glyphicon glyphicon-edit"></span></a>
+ <button class="btn btn-danger btn-xs" name="del" value="{{configid}}" title="{{lang_delete}}"><span class="glyphicon glyphicon-trash"></span></button>
+ </td>
+ </tr>
+ {{/configs}}
+ </table>
+ {{^configs}}
+ <div class="alert alert-warning">
+ {{lang_systemConfigurationNotFound}}
+ </div>
+ {{^modules}}
+ <div class="alert alert-danger">
+ {{lang_systemConfigurationAlert}}
+ </div>
+ {{/modules}}
+ {{/configs}}
+ </form>
+ </div>
+ {{#havemodules}}
+ <div class="panel-footer">
+ <a class="btn btn-primary" href="?do=SysConfig&amp;action=addconfig">{{lang_newConfiguration}}</a>
+ </div>
+ {{/havemodules}}
+ </div>
+ </div>
+
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_availableModules}}
+ <a class="btn btn-default" data-toggle="modal" data-target="#help-module"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </div>
+ <div class="panel-body">
+ <form method="post" action="?do=SysConfig">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="module">
+ <table id="modtable" class="slx-table" style="max-width:100px !important">
+ {{#modules}}
+ <tr>
+ <td class="badge slx-nowrap">{{moduletype}}</td>
+ <td data-id="{{moduleid}}" class="modrow slx-width-ignore slx-nowrap"><div class="slx-dyn-ellipsis">{{module}}</div></td>
+ <td class="slx-nowrap">
+ {{#iscustom}}
+ <button class="btn btn-default btn-xs" name="list" value="{{moduleid}}" title="{{lang_show}}"><span class="glyphicon glyphicon-eye-open"></span></button>
+ <!-- a class="btn btn-default btn-xs"><span class="glyphicon glyphicon-edit"></span> Bearbeiten</a -->
+ <button class="btn btn-default btn-xs" name="download" value="{{moduleid}}" title="{{lang_download}}"><span class="glyphicon glyphicon-download-alt"></span></button>
+ {{/iscustom}}
+ </td>
+ <td class="slx-nowrap">
+ <button
+ {{#needrebuild}}
+ class="refmod btn btn-primary btn-xs"
+ {{/needrebuild}}
+ {{^needrebuild}}
+ class="refmod btn btn-default btn-xs"
+ {{/needrebuild}}
+ name="rebuild" value="{{moduleid}}" title="{{lang_rebuild}}"><span class="glyphicon glyphicon-refresh"></span></button>
+ <a class="btn btn-success btn-xs" href="?do=SysConfig&amp;action=addmodule&amp;step={{moduletype}}_Start&amp;edit={{moduleid}}" title="{{lang_edit}}"><span class="glyphicon glyphicon-edit"></span></a>
+ <button class="btn btn-danger btn-xs" name="del" value="{{moduleid}}" title="{{lang_delete}}"><span class="glyphicon glyphicon-trash"></span></button>
+ </td>
+ </tr>
+ {{/modules}}
+ </table>
+ {{^modules}}
+ <div class="alert alert-warning">{{lang_configurationModuleNotFound}}</div>
+ {{/modules}}
+ </form>
+ </div>
+ <div class="panel-footer">
+ <a class="btn btn-primary" href="?do=SysConfig&amp;action=addmodule">{{lang_newModule}}</a>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="panel panel-default">
+ <div class="panel-heading">{{lang_legend}}</div>
+ <div class="panel-body">
+ <p>
+ <span class="btn btn-default btn-xs" title="{{lang_show}}"><span class="glyphicon glyphicon-eye-open"></span></span>
+ {{lang_showLong}}
+ </p>
+ <p>
+ <span class="btn btn-default btn-xs" title="{{lang_download}}"><span class="glyphicon glyphicon-download-alt"></span></span>
+ {{lang_downloadLong}}
+ </p>
+ <p>
+ <span class="btn btn-default btn-xs" title="{{lang_rebuild}}"><span class="glyphicon glyphicon-refresh"></span></span>
+ {{lang_rebuildLong}}
+ </p>
+ <p>
+ <span class="btn btn-primary btn-xs" title="{{lang_rebuild}}"><span class="glyphicon glyphicon-refresh"></span></span>
+ {{lang_rebuildOutdatedLong}}
+ </p>
+ <p>
+ <span class="btn btn-success btn-xs" title="{{lang_edit}}"><span class="glyphicon glyphicon-edit"></span></span>
+ {{lang_editLong}}
+ </p>
+ <div>
+ <span class="btn btn-danger btn-xs" title="{{lang_delete}}"><span class="glyphicon glyphicon-trash"></span></span>
+ {{lang_deleteLong}}
+ </div>
+ </div>
+</div>
+
+<div class="modal fade" id="help-config" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{lang_systemConfiguration}}</div>
+ <div class="modal-body">
+ {{lang_helpSystemConfiguration}}
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div>
+
+<div class="modal fade" id="help-module" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{lang_moduleConfiguration}}</div>
+ <div class="modal-body">
+ {{lang_helpModuleConfiguration}}
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript"><!--
+ var boldItem = false;
+ function showmod(e, action) {
+ var list = $(e).attr('data-modlist');
+ list = list.split(',');
+ if (action === 'bold') {
+ $(boldItem).removeClass("slx-bold");
+ if (boldItem === e) {
+ action = 'fade';
+ boldItem = false;
+ }
+ } else if (boldItem !== false) {
+ return;
+ }
+ $('.modrow').each(function () {
+ var elem = $(this);
+ elem.removeClass("slx-fade slx-bold");
+ if (action === 'reset')
+ return;
+ if (action === 'bold' && list.indexOf(elem.attr('data-id')) !== -1)
+ elem.addClass("slx-bold");
+ if (list.indexOf(elem.attr('data-id')) === -1)
+ elem.addClass("slx-fade");
+ });
+ if (action === 'bold') {
+ boldItem = e;
+ $(e).addClass("slx-bold");
+ }
+ }
+
+ var statusChecks = 0;
+ function checkBuildStatus() {
+ var mods = [];
+ var confs = [];
+ $(".refmod.btn-primary").each(function (index) {
+ mods.push($(this).val());
+ });
+ $(".refconf.btn-primary").each(function (index) {
+ confs.push($(this).val());
+ });
+ if (mods.length === 0 && confs.length === 0) return;
+ if (++statusChecks < 10) setTimeout(checkBuildStatus, 200 + 50 * statusChecks);
+ console.log("POSTING");
+ $.post('?do=SysConfig', { mods: mods.join(), confs: confs.join(), token: TOKEN, action: 'status' }, function (data) {
+ if (typeof data === 'undefined') return;
+ if (typeof data.mods === 'object') updateButtonColor($(".refmod.btn-primary"), data.mods);
+ if (typeof data.confs === 'object') updateButtonColor($(".refconf.btn-primary"), data.confs);
+ }, 'json');
+ }
+ function updateButtonColor(list,ids) {
+ list.each(function() {
+ if (ids.indexOf($(this).val()) >= 0) $(this).removeClass('btn-primary').addClass('btn-default');
+ });
+ }
+ document.addEventListener("DOMContentLoaded", checkBuildStatus, false);
+// --></script> \ No newline at end of file
diff --git a/modules-available/sysconfig/templates/ad-finish.html b/modules-available/sysconfig/templates/ad-finish.html
new file mode 100644
index 00000000..f73cad9d
--- /dev/null
+++ b/modules-available/sysconfig/templates/ad-finish.html
@@ -0,0 +1,29 @@
+<p>
+ {{lang_adStarted}}
+</p>
+
+<div id="zeug">
+ <div data-tm-id="{{tm-config}}" data-tm-log="error" data-tm-callback="ldapCb">{{lang_generateModule}}</div>
+</div>
+<br>
+<div id="back" class="pull-left" style="display:none">
+ <a href="?do=SysConfig&amp;action=addmodule&amp;step=AdAuth_Start" class="btn btn-primary">{{lang_restartWizard}}</a>
+</div>
+<div id="finish" class="pull-right" style="display:none">
+ <a href="?do=SysConfig" class="btn btn-primary">{{lang_to}} {{lang_systemConfiguration}}</a>
+</div>
+<script type="text/javascript">
+function ldapCb(task)
+{
+ if (!task || !task.statusCode)
+ return;
+ if (task.statusCode === 'TASK_FINISHED') {
+ $('#finish').attr('style', '');
+ }
+ if (task.statusCode === 'TASK_ERROR' || task.statusCode === 'PARENT_FAILED') {
+ $('#back').attr('style', '');
+ }
+}
+if ('{{tm-config}}' == '')
+ $('#finish').attr('style', '');
+</script>
diff --git a/modules-available/sysconfig/templates/ad-selfsearch.html b/modules-available/sysconfig/templates/ad-selfsearch.html
new file mode 100644
index 00000000..76952dc3
--- /dev/null
+++ b/modules-available/sysconfig/templates/ad-selfsearch.html
@@ -0,0 +1,112 @@
+<p>
+ {{lang_dnLookup}}
+</p>
+
+<div id="zeug">
+ <div data-tm-id="{{self-search}}" data-tm-log="messages" data-tm-callback="selfCb">LDAP Self-Query</div>
+ <pre style="display:none" id="result"></pre>
+ <div style="display:none" id="haselect">
+ <div class="slx-bold">{{lang_selectHomeAttribute}}</div>
+ {{lang_homeAttributeExplanation}}
+ </div>
+</div>
+<i>{{lang_onProblemSearchBase}}</i>
+<br><br>
+<div class="pull-left">
+ <form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{prev}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <input name="title" value="{{title}}" type="hidden">
+ <input name="server" value="{{server}}" type="hidden">
+ <input name="searchbase" value="{{searchbase}}" type="hidden">
+ <input name="binddn" value="{{binddn}}" type="hidden">
+ <input name="bindpw" value="{{bindpw}}" type="hidden">
+ <input name="home" value="{{home}}" type="hidden">
+ <input name="homeattr" value="{{homeattr}}" type="hidden">
+ {{#ssl}}
+ <input name="ssl" value="on" type="hidden">
+ <input type="hidden" name="certificate" value="{{certificate}}">
+ {{/ssl}}
+ <button type="submit" class="btn btn-primary">&laquo; {{lang_back}}</button>
+ </form>
+</div>
+<div class="pull-right">
+ <form id="nextform" role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{next}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <input name="title" value="{{title}}" type="hidden">
+ <input name="server" value="{{server}}" type="hidden">
+ <input name="port" value="{{port}}" type="hidden">
+ <input id="searchbase" name="searchbase" value="{{searchbase}}" type="hidden">
+ <input id="fulldn" name="binddn" value="" type="hidden">
+ <input id="givendn" name="originalbinddn" value="{{binddn}}" type="hidden">
+ <input name="bindpw" value="{{bindpw}}" type="hidden">
+ <input id="home" name="home" value="{{home}}" type="hidden">
+ <input id="homeattr" name="homeattr" value="{{homeattr}}" type="hidden">
+ {{#ssl}}
+ <input name="ssl" value="on" type="hidden">
+ <input type="hidden" name="certificate" value="{{certificate}}">
+ {{/ssl}}
+ <input name="fingerprint" value="{{fingerprint}}" type="hidden">
+ <button id="nextbutton" type="submit" class="btn btn-primary" style="display:none">{{lang_skip}} &raquo;</button>
+ </form>
+</div>
+<script type="text/javascript">
+ function selfCb(task)
+ {
+ if (!task || !task.statusCode || task.statusCode === 'TASK_WAITING' || task.statusCode === 'TASK_PROCESSING')
+ return;
+ if (task.statusCode === 'TASK_FINISHED' && task.data && task.data.dn) {
+ var fulldn = task.data.dn;
+ var domain = "-";
+ var search = $('#searchbase').val();
+ if ($('#searchbase').val().length < 2) {
+ domain = $('#givendn').val().replace(/[\/\\]\S+$/i, '');
+ var idx = fulldn.search(new RegExp('\\w+=' + domain + ',', "i"));
+ console.log(idx);
+ if (idx !== -1) {
+ search = fulldn.substring(idx);
+ }
+ $('#searchbase').val(search);
+ }
+ $('#fulldn').val(fulldn);
+ $('#result').text("BindDN: " + fulldn + "\nWinDomain: " + domain + "\nSearchBase: " + search).show();
+ var attrlist = [];
+ var tryHomeAttr = false;
+ {{#tryHomeAttr}}
+ tryHomeAttr = true;
+ if (task.data.home && task.data.home.length) attrlist = task.data.home;
+ {{/tryHomeAttr}}
+ if (typeof search !== 'string' || search.length === 0 || search.length + 2 >= fulldn.length
+ || (tryHomeAttr && $('#home').val().length === 0 && $('#homeattr').val().length === 0 && attrlist.length === 0)) {
+ $('#nextbutton').html('{{lang_continueAnyway}}');
+ } else if (attrlist.length > 1 && $('#homeattr').val().length === 0) {
+ var sel = $('<select>').attr('onchange', 'slxSetHomeAttr(this)').addClass('form-control');
+ var best = 0;
+ for (var i = 1; i < attrlist.length; ++i) {
+ if (attrlist[i].score > attrlist[best].score) best = i;
+ }
+ for (var i = 0; i < attrlist.length; ++i) {
+ var opt = $('<option>').attr('value', attrlist[i].attr).text(attrlist[i].attr + ' (' + attrlist[i].value + ')');
+ if (i === best) opt.attr('selected', 'selected');
+ sel.append(opt);
+ }
+ $('#haselect').append(sel).append('<br>').show();
+ slxSetHomeAttr(sel[0]);
+ $('#nextbutton').html('{{lang_next}}');
+ } else {
+ if (attrlist.length === 1 && $('#homeattr').val().length === 0) {
+ $('#homeattr').val(attrlist[0].attr);
+ }
+ $('#nextform').submit();
+ }
+ } else {
+ $('#nextbutton').html('{{lang_continueAnyway}}');
+ }
+ $('#nextbutton').show();
+ }
+
+ function slxSetHomeAttr(sel) {
+ $('#homeattr').val(sel.options[sel.selectedIndex].value);
+ }
+</script>
diff --git a/modules-available/sysconfig/templates/ad-start.html b/modules-available/sysconfig/templates/ad-start.html
new file mode 100644
index 00000000..98546140
--- /dev/null
+++ b/modules-available/sysconfig/templates/ad-start.html
@@ -0,0 +1,121 @@
+<p>
+ {{lang_adText1}}
+ <br>
+ {{lang_adText2}}
+ <br>
+ {{lang_adText3}}
+</p>
+<pre>dsquery user -name &quot;Username&quot;</pre>
+<p>
+ {{lang_adText4}}
+</p>
+
+<i>{{lang_asteriskMandatory}}</i>
+
+<form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
+ <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;">
+ <input type="password" name="password_fake" id="password_fake" value="" style="display:none;">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_moduleTitle}}</span>
+ <input tabindex="1" name="title" value="{{title}}" type="text" class="form-control">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">Server *</span>
+ <input tabindex="2" name="server" value="{{server}}" type="text" class="form-control" placeholder="dc0.institution.example.com">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_bindDN}} *</span>
+ <input tabindex="3" name="binddn" value="{{binddn}}" type="text" class="form-control" placeholder="domain\bwlp *ODER* CN=bwlp,OU=Benutzer,DC=domain,DC=hs-beispiel,DC=de">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_password}} *</span>
+ <input tabindex="4" name="bindpw" value="{{bindpw}}" type="{{password_type}}" class="form-control" placeholder="{{lang_password}}">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_searchBase}}</span>
+ <input tabindex="5" name="searchbase" value="{{searchbase}}" type="text" class="form-control" placeholder="dc=windows,dc=hs-beispiel,dc=de">
+ </div>
+ <br>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">Home</span>
+ <input tabindex="6" name="home" value="{{home}}" type="text" class="form-control" placeholder="\\server.example.com\%s">
+ <span class="input-group-btn">
+ <a class="btn btn-default" data-toggle="modal" data-target="#help-home"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </span>
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_homeAttr}}</span>
+ <input tabindex="6" name="homeattr" value="{{homeattr}}" type="text" class="form-control" placeholder="homeDirectory">
+ <span class="input-group-btn">
+ <a class="btn btn-default" data-toggle="modal" data-target="#help-homeattr"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </span>
+ </div>
+ <br>
+ <div>
+ <label>
+ <input type="checkbox" name="ssl" onchange="$('#cert-box').css('display', this.checked ? '' : 'none')" {{#ssl}}checked{{/ssl}}> {{lang_ssl}}
+ </label>
+ </div>
+ <i>{{lang_sslDescription}}</i>
+ <br>
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
+ </div>
+ <div class="clearfix"></div>
+ <hr>
+ <div {{^ssl}}style="display:none"{{/ssl}} id="cert-box">
+ <div class="well well-sm" id="wcustom">
+ {{lang_customCertificate}}
+ <pre class="small">
+-----BEGIN CERTIFICATE-----
+MIIFfTCCA...
+.....
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+.....
+-----END CERTIFICATE-----</pre>
+ <textarea name="certificate" class="form-control small" cols="101" rows="10">{{certificate}}</textarea>
+ </div>
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
+ </div>
+ <div class="clearfix"></div>
+ </div>
+</form>
+
+<div class="modal fade" id="help-home" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{lang_userDirectory}}</div>
+ <div class="modal-body">
+ <p>
+ {{lang_userDirectoryInfo1}}
+ </p>
+ <pre>\\fileserv.uni.example.com\users\%s</pre>
+ <p>
+ {{lang_userDirectoryInfo2}}
+ </p>
+ <p>
+ {{lang_userDirectoryInfo3}}
+ </p>
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div>
+
+<div class="modal fade" id="help-homeattr" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{lang_helpHomeAttrHead}}</div>
+ <div class="modal-body">
+ <p>
+ {{lang_helpHomeAttrText}}
+ </p>
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div>
diff --git a/modules-available/sysconfig/templates/ad_ldap-checkconnection.html b/modules-available/sysconfig/templates/ad_ldap-checkconnection.html
new file mode 100644
index 00000000..5925829a
--- /dev/null
+++ b/modules-available/sysconfig/templates/ad_ldap-checkconnection.html
@@ -0,0 +1,91 @@
+<p>
+ {{lang_connectionWait}}
+</p>
+
+<div id="zeug">
+ <div data-tm-id="{{taskid}}" data-tm-log="messages" data-tm-callback="portScan">Port Check</div>
+</div>
+<div id="self-signed" style="display:none" class="alert alert-info">{{lang_selfSignedNote}}</div>
+<div id="no-valid-cert" style="display:none" class="alert alert-danger">{{lang_noValidCert}}</div>
+<div id="no-open-port" style="display:none" class="alert alert-danger">{{lang_noOpenPort}}</div>
+<br>
+<div class="pull-left">
+ <form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{prev}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <input name="title" value="{{title}}" type="hidden">
+ <input name="server" value="{{server}}" type="hidden">
+ <input name="searchbase" value="{{searchbase}}" type="hidden">
+ <input name="binddn" value="{{binddn}}" type="hidden">
+ <input name="bindpw" value="{{bindpw}}" type="hidden">
+ <input name="home" value="{{home}}" type="hidden">
+ <input name="homeattr" value="{{homeattr}}" type="hidden">
+ {{#ssl}}
+ <input name="ssl" value="on" type="hidden">
+ <input type="hidden" name="certificate" value="{{certificate}}">
+ {{/ssl}}
+ <button type="submit" class="btn btn-primary">&laquo; {{lang_back}}</button>
+ </form>
+</div>
+<div class="pull-right">
+ <form id="nextform" role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{next}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <input name="title" value="{{title}}" type="hidden">
+ <input name="server" value="{{server}}" type="hidden">
+ <input id="port" name="port" value="" type="hidden">
+ <input name="searchbase" value="{{searchbase}}" type="hidden">
+ <input name="binddn" value="{{binddn}}" type="hidden">
+ <input name="bindpw" value="{{bindpw}}" type="hidden">
+ <input name="home" value="{{home}}" type="hidden">
+ <input name="homeattr" value="{{homeattr}}" type="hidden">
+ {{#ssl}}
+ <input id="ssl" name="ssl" value="on" type="hidden">
+ <input id="fingerprint" name="fingerprint" value="" type="hidden">
+ <input id="certificate" type="hidden" name="certificate" value="{{certificate}}">
+ {{/ssl}}
+ <input name="originalbinddn" value="{{binddn}}" type="hidden">
+ <button id="nextbutton" type="submit" class="btn btn-primary" style="display:none">{{lang_next}} &raquo;</button>
+ </form>
+</div>
+<div id="bla"></div>
+<script type="text/javascript">
+ function isSelfSigned(code)
+ {
+ return code == 18 || code == 19 || code == 20 || code == 21;
+ }
+ function portScan(task)
+ {
+ if (!task || !task.statusCode)
+ return;
+ if (task.statusCode === 'TASK_FINISHED' && task.data && task.data.ports) {
+ var ssl = $('#ssl').length > 0;
+ var ports = task.data.ports;
+ var verRes = -1;
+ var cert = ssl && $('#certificate').val().length > 10;
+ for (var i = 0; i < ports.length; ++i) {
+ if (!ports[i].open || !ports[i].port) continue;
+ if ($.isNumeric($('#port').val()) && $('#port').val() < ports[i].port) continue; // Prefer the global LDAP ports over the specific AD ports
+ if (ssl) {
+ if (verRes === -1) verRes = ports[i].verifyResult;
+ if (ports[i].certFingerprint.length < 10 || ports[i].certificateChain.length < 10) continue;
+ if (ports[i].verifyResult != 0 && (cert || !isSelfSigned(ports[i].verifyResult))) continue;
+ verRes = ports[i].verifyResult;
+ $('#fingerprint').val(ports[i].certFingerprint);
+ if (!cert && verRes != 0) $('#certificate').val(ports[i].certificateChain);
+ else if (!cert && verRes == 0) $('#certificate').val('default');
+ }
+ $('#port').val(ports[i].port);
+ }
+ if (ssl && verRes != 0 && (cert || !isSelfSigned(verRes))) {
+ $('#no-valid-cert').css('display', '');
+ } else if ($('#port').val() > 0) {
+ $('#nextbutton').show();
+ if (ssl && isSelfSigned(verRes)) $('#self-signed').css('display', '');
+ else $('#nextform').submit();
+ } else {
+ $('#no-open-port').css('display', '');
+ }
+ }
+ }
+</script>
diff --git a/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html b/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html
new file mode 100644
index 00000000..0586209b
--- /dev/null
+++ b/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html
@@ -0,0 +1,67 @@
+<p>
+ {{lang_connectionWait}}
+</p>
+
+<div id="zeug">
+ <div data-tm-id="{{tm-search}}" data-tm-log="messages" data-tm-callback="ldapCb">LDAP Test-Query</div>
+</div>
+<i>{{lang_onProblemSearchBase}}</i>
+<br><br>
+<div class="pull-left">
+ <form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{prev}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <input name="title" value="{{title}}" type="hidden">
+ <input name="server" value="{{server}}" type="hidden">
+ <input name="searchbase" value="{{searchbase}}" type="hidden">
+ <input name="binddn" value="{{binddn}}" type="hidden">
+ <input name="bindpw" value="{{bindpw}}" type="hidden">
+ <input name="home" value="{{home}}" type="hidden">
+ <input name="homeattr" value="{{homeattr}}" type="hidden">
+ {{#ssl}}
+ <input name="ssl" value="on" type="hidden">
+ <input type="hidden" name="certificate" value="{{certificate}}">
+ {{/ssl}}
+ <button type="submit" class="btn btn-primary">&laquo; {{lang_back}}</button>
+ </form>
+</div>
+<div class="pull-right">
+ <form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{next}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <input name="title" value="{{title}}" type="hidden">
+ <input name="server" value="{{server}}" type="hidden">
+ <input name="searchbase" value="{{searchbase}}" type="hidden">
+ <input id="setbase" name="somedn" value="" type="hidden">
+ <input name="binddn" value="{{binddn}}" type="hidden">
+ <input name="bindpw" value="{{bindpw}}" type="hidden">
+ <input name="home" value="{{home}}" type="hidden">
+ <input name="homeattr" value="{{homeattr}}" type="hidden">
+ {{#ssl}}
+ <input name="ssl" value="on" type="hidden">
+ <input type="hidden" name="certificate" value="{{certificate}}">
+ {{/ssl}}
+ <input name="fingerprint" value="{{fingerprint}}" type="hidden">
+ <input name="originalbinddn" value="{{binddn}}" type="hidden">
+ <button id="nextbutton" type="submit" class="btn btn-primary" style="display:none">{{lang_skip}} &raquo;</button>
+ </form>
+</div>
+<script type="text/javascript">
+ function ldapCb(task)
+ {
+ if (!task || !task.statusCode)
+ return;
+ if (task.statusCode === 'TASK_FINISHED') {
+ if (task.data && task.data.dn) {
+ $('#setbase').val(task.data.dn);
+ }
+ $('#nextbutton').html('Weiter &raquo;').show();
+ }
+ if (task.statusCode === 'TASK_ERROR' || task.statusCode === 'PARENT_FAILED') {
+ $('#nextbutton').html('Trotzdem weiter &raquo;');
+ }
+ if (task.statusCode === 'TASK_ERROR') {
+ $('#nextbutton').show();
+ }
+ }
+</script>
diff --git a/modules-available/sysconfig/templates/branding-check.html b/modules-available/sysconfig/templates/branding-check.html
new file mode 100644
index 00000000..8f6ef055
--- /dev/null
+++ b/modules-available/sysconfig/templates/branding-check.html
@@ -0,0 +1,26 @@
+<p>
+ {{lang_brandingInfo}}
+</p>
+<div class="pull-left">
+ {{#svg}}
+ <img src="data:image/svg+xml;base64,{{svg}}" width="192" height="192">
+ {{/svg}}
+</div>
+<div class="pull-right">
+ {{#png}}
+ <img src="data:image/png;base64,{{png}}">
+ {{/png}}
+</div>
+<div class="clearfix"></div>
+<div>{{error}}</div>
+<div>
+ <form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <div class="form-group">
+ <label for="title-id">{{lang_title}}</label>
+ <input type="text" name="title" value="{{title}}" id ="title-id" class="form-control" placeholder="Name des Moduls">
+ </div>
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ </form>
+</div>
diff --git a/modules-available/sysconfig/templates/branding-start.html b/modules-available/sysconfig/templates/branding-start.html
new file mode 100644
index 00000000..09b9ca27
--- /dev/null
+++ b/modules-available/sysconfig/templates/branding-start.html
@@ -0,0 +1,25 @@
+<p>
+ {{lang_branding}}
+</p>
+<form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <div class="form-group">
+ <label for="input-url">{{lang_urlLoad}}</label>
+ <input class="form-control" type="text" name="url" id="input-url">
+ </div>
+ {{lang_or}}
+ <div class="form-group">
+ <label for="input-file">{{lang_computerLoad}}</label>
+ <div class="input-group upload-ex">
+ <input type="text" class="form-control" readonly placeholder="{{lang_selectFile}}">
+ <span class="input-group-btn">
+ <span class="btn btn-default btn-file">
+ {{lang_browseForFile}}&hellip; <input type="file" name="file" id="input-file">
+ </span>
+ </span>
+ </div>
+ </div>
+ <button type="submit" class="btn btn-primary">{{lang_upload}}</button>
+</form>
+
diff --git a/modules-available/sysconfig/templates/cfg-finish.html b/modules-available/sysconfig/templates/cfg-finish.html
new file mode 100644
index 00000000..a6e51996
--- /dev/null
+++ b/modules-available/sysconfig/templates/cfg-finish.html
@@ -0,0 +1,12 @@
+<p>
+ {{lang_configurationSuccess}}
+</p>
+
+<form role="form" method="post" action="?do=SysConfig">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="config">
+ <input type="hidden" name="activate" value="{{configid}}">
+ <div class="pull-left">
+ <button type="submit" class="btn btn-primary">{{lang_configurationActive}}</button>
+ </div>
+</form>
diff --git a/modules-available/sysconfig/templates/cfg-start.html b/modules-available/sysconfig/templates/cfg-start.html
new file mode 100644
index 00000000..50f366ea
--- /dev/null
+++ b/modules-available/sysconfig/templates/cfg-start.html
@@ -0,0 +1,39 @@
+<form role="form" method="post" action="?do=SysConfig&amp;action=addconfig&amp;step={{step}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_name}} *</span>
+ <input type="text" name="title" value="{{title}}" class="form-control" placeholder="{{lang_configuration}}" autofocus="autofocus">
+ </div>
+ <hr>
+ <p>{{lang_configurationChoose}}</p>
+ {{#groups}}
+ <div class="panel panel-default">
+ <div class="slx-litehead">{{group}}</div>
+ <div class="panel-body">
+ {{#modules}}
+ <div class="input-group">
+ <span class="input-group-addon">
+ {{#unique}}
+ <input type="radio" name="module[{{groupid}}]" value="{{moduleid}}" id="module{{moduleid}}" {{#active}}checked{{/active}}>
+ {{/unique}}
+ {{^unique}}
+ <input type="checkbox" name="module[{{moduleid}}]" value="{{moduleid}}" id="module{{moduleid}}" {{#active}}checked{{/active}}>
+ {{/unique}}
+ </span>
+ <label class="form-control" for="module{{moduleid}}">{{title}}</label>
+ {{#missing}}
+ <span class="input-group-addon" title="Modul beschädigt! Bitte neu generieren."><span class="red glyphicon glyphicon-exclamation-sign"></span></span>
+ {{/missing}}
+ </div>
+ {{/modules}}
+ {{^modules}}
+ <div class="alert alert-info">{{lang_noModuleOfType}}</div>
+ {{/modules}}
+ </div>
+ </div>
+ {{/groups}}
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
+ </div>
+</form>
diff --git a/modules-available/sysconfig/templates/config-module-list.html b/modules-available/sysconfig/templates/config-module-list.html
new file mode 100644
index 00000000..6cd77f9e
--- /dev/null
+++ b/modules-available/sysconfig/templates/config-module-list.html
@@ -0,0 +1,17 @@
+<form method="post" action="?do=SysConfig">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="module">
+ <table class="slx-table">
+ {{#modules}}
+ <tr>
+ <td>{{module}}</td>
+ <td>
+ <button class="btn btn-default btn-xs" name="list" value="{{moduleid}}"><span class="glyphicon glyphicon-eye-open"></span> {{lang_show}}</button>
+ </td>
+ </tr>
+ {{/modules}}
+ </table>
+ {{^modules}}
+ <div class="alert alert-warning">{{lang_noContent}}</div>
+ {{/modules}}
+</form> \ No newline at end of file
diff --git a/modules-available/sysconfig/templates/custom-filelist.html b/modules-available/sysconfig/templates/custom-filelist.html
new file mode 100644
index 00000000..3ad241dd
--- /dev/null
+++ b/modules-available/sysconfig/templates/custom-filelist.html
@@ -0,0 +1,16 @@
+<input type="hidden" name="modid" value="{{modid}}">
+<input type="hidden" name="token" value="{{token}}">
+<table class="table table-bordered table-condensed">
+{{#files}}
+ <tr>
+ {{#isdir}}
+ <td class="fileEntry isdir" colspan="2">{{name}}</td>
+ {{/isdir}}
+ {{^isdir}}
+ <td class="fileEntry">{{name}}</td>
+ <td>{{size}}</td>
+ {{/isdir}}
+ </tr>
+ {{/files}}
+</table>
+<a class="btn btn-primary btn-sm" href="?do=SysConfig">{{lang_back}} &laquo;</a>
diff --git a/modules-available/sysconfig/templates/custom-fileselect.html b/modules-available/sysconfig/templates/custom-fileselect.html
new file mode 100644
index 00000000..000c8d10
--- /dev/null
+++ b/modules-available/sysconfig/templates/custom-fileselect.html
@@ -0,0 +1,31 @@
+<form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
+ <input type="hidden" name="modid" value="{{modid}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_moduleName}}</span>
+ <input type="text" name="title" value="{{title}}" class="form-control" placeholder="Mein Konfigurationsmodul" autofocus="autofocus">
+ </div>
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
+ </div>
+ <div class="clearfix"></div>
+ <hr>
+ <p>{{lang_checkFileContent}}</p>
+ <table class="table table-bordered table-condensed">
+ {{#files}}
+ <tr>
+ {{#isdir}}
+ <td class="fileEntry isdir" colspan="2">{{name}}</td>
+ {{/isdir}}
+ {{^isdir}}
+ <td class="fileEntry">{{name}}</td>
+ <td>{{size}}</td>
+ {{/isdir}}
+ </tr>
+ {{/files}}
+ </table>
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
+ </div>
+</form>
diff --git a/modules-available/sysconfig/templates/custom-upload.html b/modules-available/sysconfig/templates/custom-upload.html
new file mode 100644
index 00000000..c453a97d
--- /dev/null
+++ b/modules-available/sysconfig/templates/custom-upload.html
@@ -0,0 +1,18 @@
+<p>{{lang_customModuleInfo1}}</p>
+
+<p>{{lang_customModuleInfo2}}</p>
+
+<form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <div class="input-group upload-ex">
+ <input type="text" class="form-control" readonly placeholder="{{lang_selectFile}}">
+ <span class="input-group-btn">
+ <span class="btn btn-default btn-file">
+ {{lang_browseForFile}}&hellip; <input type="file" name="modulefile">
+ </span>
+ </span>
+ </div>
+ <p class="help-block">{{lang_supportedFiles}}: .tar.gz, .tar.bz2, .zip</p>
+ <button type="submit" class="btn btn-primary">{{lang_upload}}</button>
+</form>
diff --git a/modules-available/sysconfig/templates/ldap-finish.html b/modules-available/sysconfig/templates/ldap-finish.html
new file mode 100644
index 00000000..a735e792
--- /dev/null
+++ b/modules-available/sysconfig/templates/ldap-finish.html
@@ -0,0 +1,29 @@
+<p>
+ {{lang_ldapStarted}}
+</p>
+
+<div id="zeug">
+ <div data-tm-id="{{tm-config}}" data-tm-log="error" data-tm-callback="ldapCb">{{lang_generateModule}}</div>
+</div>
+<br>
+<div id="back" class="pull-left" style="display:none">
+ <a href="?do=SysConfig&amp;action=addmodule&amp;step=LdapAuth_Start" class="btn btn-primary">{{lang_restartWizard}}</a>
+</div>
+<div id="finish" class="pull-right" style="display:none">
+ <a href="?do=SysConfig" class="btn btn-primary">{{lang_toSystemConfiguration}}</a>
+</div>
+<script type="text/javascript">
+function ldapCb(task)
+{
+ if (!task || !task.statusCode)
+ return;
+ if (task.statusCode === 'TASK_FINISHED') {
+ $('#finish').attr('style', '');
+ }
+ if (task.statusCode === 'TASK_ERROR' || task.statusCode === 'PARENT_FAILED') {
+ $('#back').attr('style', '');
+ }
+}
+if ('{{tm-config}}' == '')
+ $('#finish').attr('style', '');
+</script>
diff --git a/modules-available/sysconfig/templates/ldap-start.html b/modules-available/sysconfig/templates/ldap-start.html
new file mode 100644
index 00000000..7892b63f
--- /dev/null
+++ b/modules-available/sysconfig/templates/ldap-start.html
@@ -0,0 +1,101 @@
+<p>
+ {{lang_ldapText1}}
+ <br>
+ {{lang_ldapText2}}
+</p>
+
+<form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
+ <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;">
+ <input type="password" name="password_fake" id="password_fake" value="" style="display:none;">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_moduleTitle}}</span>
+ <input tabindex="1" name="title" value="{{title}}" type="text" class="form-control">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">Server *</span>
+ <input tabindex="2" name="server" value="{{server}}" type="text" class="form-control" placeholder="dc0.institution.example.com">
+ <!--span class="input-group-btn">
+ <a class="btn btn-default"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </span-->
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_bindDN}}</span>
+ <input tabindex="3" name="binddn" value="{{binddn}}" type="text" class="form-control" placeholder="CN=bwlp,OU=Benutzer,DC=domain,DC=hs-beispiel,DC=de">
+ <!--span class="input-group-btn">
+ <a class="btn btn-default"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </span-->
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_password}}</span>
+ <input tabindex="4" name="bindpw" value="{{bindpw}}" type="{{password_type}}" class="form-control" placeholder="{{lang_password}}">
+ <!--span class="input-group-btn">
+ <a class="btn btn-default"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </span-->
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_searchBase}} *</span>
+ <input tabindex="5" name="searchbase" value="{{searchbase}}" type="text" class="form-control" placeholder="ou=users,dc=hochschule,dc=de">
+ <!--span class="input-group-btn">
+ <a class="btn btn-default"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </span-->
+ </div>
+ <br>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">Home</span>
+ <input tabindex="6" name="home" value="{{home}}" type="text" class="form-control" placeholder="\\server.example.com\%s">
+ <span class="input-group-btn">
+ <a class="btn btn-default" data-toggle="modal" data-target="#help-home"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </span>
+ </div>
+ <br>
+ <div>
+ <label>
+ <input type="checkbox" name="ssl" onchange="$('#cert-box').css('display', this.checked ? '' : 'none')" {{#ssl}}checked{{/ssl}}> {{lang_ssl}}
+ </label>
+ </div>
+ <i>{{lang_sslDescription}}</i>
+ <br>
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
+ </div>
+ <div class="clearfix"></div>
+ <hr>
+ <div {{^ssl}}style="display:none"{{/ssl}} id="cert-box">
+ <div class="well well-sm" id="wcustom">
+ {{lang_customCertificate}}
+ <pre class="small">
+-----BEGIN CERTIFICATE-----
+MIIFfTCCA...
+.....
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+.....
+-----END CERTIFICATE-----</pre>
+ <textarea name="certificate" class="form-control small" cols="101" rows="10">{{certificate}}</textarea>
+ </div>
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
+ </div>
+ <div class="clearfix"></div>
+ </div>
+</form>
+
+<div class="modal fade" id="help-home" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{lang_userDirectory}}</div>
+ <div class="modal-body">
+ <p>
+ {{lang_userDirectoryInfo1}}
+ </p>
+ <pre>\\fileserv.uni.example.com\users\%s</pre>
+ <p>
+ {{lang_userDirectoryInfo2}}
+ </p>
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div>
diff --git a/modules-available/sysconfig/templates/sshconfig-start.html b/modules-available/sysconfig/templates/sshconfig-start.html
new file mode 100644
index 00000000..2aa409d3
--- /dev/null
+++ b/modules-available/sysconfig/templates/sshconfig-start.html
@@ -0,0 +1,27 @@
+<form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_moduleName}}</span>
+ <input type="text" name="title" value="{{title}}" class="form-control" autofocus="autofocus">
+ </div>
+ <div class="form-group">
+ <label>
+ <input type="checkbox" name="allowPasswordLogin" value="yes" {{#apl}}checked{{/apl}}>
+ {{lang_allowPass}}
+ </label>
+ <p><i>{{lang_allowPassInfo}}</i></p>
+ </div>
+ <div class="form-group">
+ <label for="root-key">{{lang_rootKey}}</label>
+ <input class="form-control" type="text" name="publicKey" value="{{publicKey}}" id="root-key" pattern="[a-z0-9\-]+ +[a-zA-Z0-9=/\+]+ +.*">
+ <i>{{lang_rootKeyInfo}}</i>
+ </div>
+ <div class="form-group">
+ <label for="port">{{lang_listenPort}}</label>
+ <input class="form-control" type="text" name="listenPort" value="{{listenPort}}" id="port" pattern="\d+">
+ <i>{{lang_listenPortInfo}}</i>
+ </div>
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+</form>
+
diff --git a/modules-available/sysconfig/templates/start.html b/modules-available/sysconfig/templates/start.html
new file mode 100644
index 00000000..f55a5501
--- /dev/null
+++ b/modules-available/sysconfig/templates/start.html
@@ -0,0 +1,12 @@
+<p>{{lang_moduleChoose}}</p>
+
+{{#modules}}
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{title}} <a href="?do=SysConfig&amp;action=addmodule&amp;step={{wizardClass}}" class="pull-right btn btn-primary btn-xs"><span class="glyphicon glyphicon-plus"></span> {{lang_add}}</a>
+ </div>
+ <div class="panel-body">
+ {{description}}
+ </div>
+</div>
+{{/modules}}
diff --git a/modules-available/sysconfignew/config.json b/modules-available/sysconfignew/config.json
new file mode 100644
index 00000000..4da67ef8
--- /dev/null
+++ b/modules-available/sysconfignew/config.json
@@ -0,0 +1,3 @@
+{
+ "enabled":"true"
+}
diff --git a/modules-available/sysconfignew/lang/en/module.json b/modules-available/sysconfignew/lang/en/module.json
new file mode 100644
index 00000000..3ec89616
--- /dev/null
+++ b/modules-available/sysconfignew/lang/en/module.json
@@ -0,0 +1,23 @@
+{
+ "lang_back": "Back",
+ "lang_cancel": "Cancel",
+ "lang_cannotOpen": "could no be opened",
+ "lang_changeLink": "Change link",
+ "lang_compilingIpxe": "Compiling iPXE",
+ "lang_configName": "Name",
+ "lang_configsDescription": "Here it is possible to create a system configuration by choosing the desired modules.",
+ "lang_configurations": "Configurations",
+ "lang_create": "Create",
+ "lang_edit": "Edit",
+ "lang_file": "File",
+ "lang_leavingMessage": "You have unsaved changes on your module.",
+ "lang_loading": "Loading",
+ "lang_modules": "Modules",
+ "lang_modulesDescription": "Here it is possible to create a system module through an editor.",
+ "lang_name": "Name",
+ "lang_new": "New",
+ "lang_newConfig": "New Config",
+ "lang_newModule": "New Module",
+ "lang_remove": "Remove",
+ "lang_save": "Save"
+} \ No newline at end of file
diff --git a/modules-available/sysconfignew/lang/pt/module.json b/modules-available/sysconfignew/lang/pt/module.json
new file mode 100644
index 00000000..af0d7ad7
--- /dev/null
+++ b/modules-available/sysconfignew/lang/pt/module.json
@@ -0,0 +1,23 @@
+{
+ "lang_back": "Voltar",
+ "lang_cancel": "Cancelar",
+ "lang_cannotOpen": "n\u00e3o p\u00f4de ser aberto",
+ "lang_changeLink": "Mudar link",
+ "lang_compilingIpxe": "Compilando iPXE",
+ "lang_configName": "Nome",
+ "lang_configsDescription": "Aqui \u00e9 poss\u00edvel criar uma configura\u00e7\u00e3o do sistema escolhendo os m\u00f3dulos desejados.",
+ "lang_configurations": "Configura\u00e7\u00f5es",
+ "lang_create": "Criar",
+ "lang_edit": "Editar",
+ "lang_file": "Arquivo",
+ "lang_leavingMessage": "Voc\u00ea possui mudan\u00e7as n\u00e3o salvas no seu m\u00f3dulo.",
+ "lang_loading": "Carregando",
+ "lang_modules": "M\u00f3dulos",
+ "lang_modulesDescription": "Aqui \u00e9 poss\u00edvel criar um m\u00f3dulo do sistema atrav\u00e9s de um editor.",
+ "lang_name": "Nome",
+ "lang_new": "Novo",
+ "lang_newConfig": "Nova Configura\u00e7\u00e3o",
+ "lang_newModule": "Novo M\u00f3dulo",
+ "lang_remove": "Remover",
+ "lang_save": "Salvar"
+} \ No newline at end of file
diff --git a/modules-available/sysconfignew/page.inc.php b/modules-available/sysconfignew/page.inc.php
new file mode 100644
index 00000000..105ad6c7
--- /dev/null
+++ b/modules-available/sysconfignew/page.inc.php
@@ -0,0 +1,113 @@
+<?php
+
+class Page_SysConfigNew extends Page
+{
+ // private $tmpath = '/srv/openslx/www/';
+ private $tmpath = '/home/raul/tm-scripts/server';
+ private $tmconfigs;
+ private $tmmodules;
+
+ protected function doPreprocess(){
+ User::load();
+ if (!User::hasPermission('baseconfig_local')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ $this->tmconfigs = $this->tmpath . '/configs';
+ $this->tmmodules = $this->tmpath . '/modules';
+ }
+
+
+ protected function doRender(){
+ $module = $_GET['module'];
+ if(isset($module)){
+ Render::addTemplate('module-editor',array(
+ "module" => $module
+ ));
+ }else{
+ if(is_dir($this->tmpath)){
+ $configs = array();
+ $modules = array();
+
+ foreach($this->listDirectory($this->tmconfigs) as $key => $value)
+ $configs[] = array(
+ "name" => $value
+ );
+
+ foreach($this->listDirectory($this->tmmodules) as $key => $value)
+ $modules[] = array(
+ "name" => $value
+ );
+
+ $data = array(
+ "configs" => $configs,
+ "modules" => $modules
+ );
+ Render::addTemplate('_pagenew',$data);
+ }else{
+ Message::addError('no-tm-scripts');
+ }
+ }
+ }
+
+ protected function doAjax(){
+ $request = $_GET['request'];
+ switch($request){
+ case "module-contents":
+ $path = $this->tmpath . '/modules/' . Request::get('module');
+ $data = $this->getDirContents($path);
+ $json = json_encode($data);
+ print_r($json);
+ break;
+ case "configs":
+ $this->tmconfigs = $this->tmpath . '/configs';
+ $this->tmmodules = $this->tmpath . '/modules';
+ $userModules = $this->listDirectory($this->tmconfigs . '/' . Request::get('config'));
+ $modules = array();
+ foreach($this->listDirectory($this->tmmodules) as $key => $value){
+ $chosen = (in_array($value, $userModules)) ? true : false;
+ $modules[] = array(
+ "name" => $value,
+ "chosen" => $chosen
+ );
+ }
+
+ foreach ($modules as $module) {
+ $class = ($module['chosen']) ? "select-item select-item-selected" : "select-item";
+ $ret .= "<button type='button' class='" . $class . "' onclick='select(this)' >";
+ $ret .= $module['name'];
+ $ret .= "</button>";
+ }
+
+ echo $ret;
+ break;
+ }
+
+ }
+
+ private function getDirContents($path){
+ $ret = array();
+ foreach ($this->listDirectory($path) as $key => $value) {
+ if(is_dir($path . "/" . $value)){
+ $ret["dir_" . $value] = $this->getDirContents($path . "/" . $value);
+ }else{
+ if(is_link($path . "/" . $value)){
+ $ret["link_" . $value] = readlink($path . "/" . $value);
+ }else{
+ if(mime_content_type($path . "/" . $value) == "text/plain"){
+ $ret["file_" . $value] = file_get_contents($path . "/" . $value);
+ }else{
+ $ret["lock_" . $value] = " oops";
+ }
+ }
+ }
+ }
+ return $ret;
+ }
+
+ private function listDirectory($path){
+ return array_diff(scandir($path), array('..', '.'));
+ }
+
+}
diff --git a/modules-available/sysconfignew/templates/_pagenew.html b/modules-available/sysconfignew/templates/_pagenew.html
new file mode 100644
index 00000000..98881ae3
--- /dev/null
+++ b/modules-available/sysconfignew/templates/_pagenew.html
@@ -0,0 +1,190 @@
+<div class="row">
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading" >
+ <div class="panel-title">{{lang_configurations}}</div>
+ </div>
+ <div class="panel-body">
+ {{lang_configsDescription}}
+ </div>
+ <table class="table table-striped">
+ <thead>
+ <th style="width: 70%">{{lang_configName}}</th>
+ <th></th>
+ <th></th>
+ </thead>
+ <tbody>
+ {{#configs}}
+ <tr>
+ <td>{{name}}</td>
+ <td>
+ <button type="button" class="btn btn-xs btn-primary" onclick="loadConfig('{{name}}')" data-toggle="modal" data-target="#edit-modal" >
+ <span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
+ {{lang_edit}}
+ </button>
+ </td>
+ <td>
+ <button type="button" class="btn btn-xs btn-danger">
+ <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
+ {{lang_remove}}
+ </button>
+ </td>
+ </tr>
+ {{/configs}}
+ </tbody>
+ </table>
+ <div class="panel-footer">
+ <button id="new-config-button" type="button" class="btn btn-primary" data-toggle="modal" data-target="#new-config-modal" >
+ <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
+ {{lang_newConfig}}
+ </button>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading" >
+ <div class="panel-title">{{lang_modules}}</div>
+ </div>
+ <div class="panel-body">
+ {{lang_modulesDescription}}
+ </div>
+ <table class="table table-striped">
+ <thead>
+ <th style="width: 70%">{{lang_configName}}</th>
+ <th></th>
+ <th></th>
+ </thead>
+ <tbody>
+ {{#modules}}
+ <tr>
+ <td>{{name}}</td>
+ <td>
+ <button type="button" class="btn btn-xs btn-primary" onclick="loadModule('{{name}}')" >
+ <span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
+ {{lang_edit}}
+ </button>
+ </td>
+ <td>
+ <button type="button" class="btn btn-xs btn-danger">
+ <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
+ {{lang_remove}}
+ </button>
+ </td>
+ </tr>
+ {{/modules}}
+ </tbody>
+ </table>
+ <div class="panel-footer">
+ <button id="new-module-button" type="button" class="btn btn-primary" data-toggle="modal" data-target="#new-module-modal" >
+ <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
+ {{lang_newModule}}
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- Generic modal, will have its content changed dynamically -->
+<div class="modal fade" id="edit-modal" tabindex="-1" role="dialog" aria-labelledby="edit-modal-label" aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <!-- Header -->
+ <div class="modal-header" id="edit-modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="edit-modal-label">{{lang_compilingIpxe}}</h4>
+ </div>
+ <!-- Body -->
+ <div class="modal-body" id="edit-modal-body" style="overflow: auto;">
+ {{lang_loading}} <img src='fonts/loader.gif'/>
+
+ </div>
+ <!-- Footer -->
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="button" class="btn btn-primary" disabled="disabled">{{lang_save}}</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- New config modal -->
+<form action="" method="POST">
+ <div class="modal fade" id="new-config-modal" tabindex="-1" role="dialog" aria-labelledby="new-config-modal-label" aria-hidden="true">
+ <form action="#" method="post">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <!-- Header -->
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="new-config-modal-label">{{lang_compilingIpxe}}</h4>
+ </div>
+ <!-- Body -->
+ <div class="modal-body" id="new-config-modal-body" style="overflow: auto;">
+ <div>
+ <label for="new-config-name">{{lang_name}}</label>
+ <input type="text" name="new-config-name" class="form-control">
+ </div>
+ <hr>
+ <div>
+ <label>{{lang_modules}}</label>
+ </div>
+ {{#modules}}
+ <button name="{{name}}" type='button' class='select-item' onclick='select(this)' >{{name}}</button>
+ {{/modules}}
+ </div>
+ <!-- Footer -->
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <input type="submit" class="btn btn-primary" value="{{lang_create}}">
+ </div>
+ </div>
+ </div>
+ </form>
+ </div>
+</form>
+<!-- New module modal -->
+<div class="modal fade" id="new-module-modal" tabindex="-1" role="dialog" aria-labelledby="new-module-modal-label" aria-hidden="true">
+ <form action="#" method="post">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <!-- Header -->
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="new-module-modal-label">{{lang_compilingIpxe}}</h4>
+ </div>
+ <!-- Body -->
+ <div class="modal-body" id="new-module-modal-body">
+ <textarea class="form-control" style="resize:none"></textarea>
+ </div>
+ <!-- Footer -->
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="button" class="btn btn-primary" disabled="disabled">{{lang_save}}</button>
+ </div>
+ </div>
+ </div>
+ </form>
+</div>
+
+
+<script>
+ function loadConfig(config){
+ $.ajax({
+ method: "GET",
+ url: "?do=SysConfig",
+ data: { request:"configs", async: "True", config: config }
+ }).done(function( msg ) {
+ $("#edit-modal-body").html( msg );
+ $("#edit-modal-header").html( '<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <h4 class="modal-title" id="edit-modal-label">' + config + '</h4>' );
+ });
+ }
+
+ function loadModule(module){
+ window.location = "?do=SysConfig&module=" + module;
+ }
+
+ function select(element){0
+ element.className = (element.className == 'select-item') ? 'select-item select-item-selected' : 'select-item';
+ }
+</script> \ No newline at end of file
diff --git a/modules-available/sysconfignew/templates/module-editor.html b/modules-available/sysconfignew/templates/module-editor.html
new file mode 100644
index 00000000..3ea28bf8
--- /dev/null
+++ b/modules-available/sysconfignew/templates/module-editor.html
@@ -0,0 +1,269 @@
+
+<div class="container">
+ <div class="row">
+ <a href="?do=Sysconfig" style="display:inline-block;margin-bottom: 10px;">{{lang_back}}</a>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{module}}
+ </div>
+ <div class="panel-body">
+ <div class="mod-nav" id="mod-nav">
+
+ </div>
+ <input class="form-control" disabled="disabled" id="editor-header">
+ <textarea class="editor-box" id="mod-editor" onkeyup="changeContent(this);"></textarea>
+ </div>
+ <div class="panel-footer">
+ <button id="new-item-button" type="button" class="btn btn-default" data-toggle="modal" data-target="#item-modal" onclick="cleanModal()" >
+ <span class="glyphicon glyphicon-file" aria-hidden="true"></span>
+ {{lang_new}}
+ </button>
+ <button type="button" class="btn btn-default" onclick="save();">
+ <span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- New item modal -->
+<div class="modal fade" id="item-modal" tabindex="-1" role="dialog" aria-labelledby="item-modal-label" aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <!-- Header -->
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="new-config-modal-label">{{lang_new}}</h4>
+ </div>
+ <!-- Body -->
+ <div class="modal-body" id="new-module-modal-body" style="overflow: auto;">
+ <div class="mod-nav" style="height: 250px">
+ <div class="item dir" onclick="clickDir(this)" data-target="#root" data-path="/"><div class="glyphicon glyphicon-folder-close dir-icon" aria-hidden="true" id="root-icon"></div> /</div>
+ <div class="folder" id="root">
+ <div id="dir-nav">
+ </div>
+ </div>
+ </div>
+ <div style="float:left">
+ <input class="form-control" disabled="disabled" id="dir-path" style="width: 250px;margin-left: 20px;" value="/">
+ <br>
+ <div class="btn-group-vertical" role="group" aria-label="..." style="width: 250px;margin-left: 20px;">
+ <button type="button" class="btn btn-default" onclick="switchFileInput('file')">Arquivo de Texto</button>
+ <button type="button" class="btn btn-default" onclick="switchFileInput('upload')">Upload de Arquivo</button>
+ <button type="button" class="btn btn-default" onclick="switchFileInput('folder')">Pasta</button>
+ <button type="button" class="btn btn-default" onclick="switchFileInput('link')">Link</button>
+ </div>
+ <br>
+ <br>
+ <div id="new-file-input" style="width: 250px;margin-left: 20px;">
+ <div class="form-group">
+ <label for="name">Nome do Arquivo</label>
+ <input type="name" class="form-control" id="new-file-data">
+ </div>
+ </div>
+ </div>
+ </div>
+ <!-- Footer -->
+ <div class="modal-footer" >
+ <button type="button" class="btn btn-primary" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript">
+
+// Global variables
+var original;
+var module;
+var current;
+var path;
+
+function cleanModal(){
+ $('#dir-path').val("/");
+}
+
+function clickDir(element){
+ var target = element.getAttribute("data-target");
+ target = target.replace(/\./g,"\\.");
+ $(target + "-icon").attr('class',(($(target).css('display') == 'none') ? 'glyphicon glyphicon-folder-open dir-icon' : 'glyphicon glyphicon-folder-close dir-icon' ));
+ $(target).css('display', ($(target).css('display') == 'none') ? 'block' : 'none' );
+ $('#dir-path').val(element.getAttribute("data-path"));
+}
+
+function getFile(obj,file){
+ var folder = file.split("/")[1];
+ for (var property in obj) {
+ if (obj.hasOwnProperty(property)) {
+ if(property.substring(0, 4) == "dir_"){
+ var pname = property.slice(4);
+ if(pname == folder){
+ var ret = getFile(obj[property],file.replace("/" + folder,""));
+ if(ret != "Invalid file")
+ return ret;
+ }
+ }
+ if(property.substring(0, 5) == "file_"){
+ if(property.slice(5) == file.replace("/",""))
+ return obj[property];
+ }
+ }
+ }
+ return 'Invalid file';
+}
+
+function changeFile(obj,file,data){
+ var folder = file.split("/")[1];
+ for (var property in obj) {
+ if (obj.hasOwnProperty(property)) {
+ if(property.substring(0, 4) == "dir_"){
+ var pname = property.slice(4);
+ if(pname == folder){
+ var ret = changeFile(obj[property],file.replace("/" + folder,""),data);
+ if(ret != "")
+ return ret;
+ }
+ }
+ if(property.substring(0, 5) == "file_"){
+ if(property.slice(5) == file.replace("/","")){
+ obj[property] = data;
+ }
+ }
+ }
+ }
+ return "";
+}
+
+function clickItem(element){
+ var id = element.getAttribute("id");
+ var target = id.split("/").pop();
+ document.getElementById('mod-editor').value = getFile(module,id);
+ document.getElementById('editor-header').value = id;
+ current = id;
+}
+
+function clickLock(element){
+ var id = element.getAttribute("id");
+ alert("{{lang_file}}: " + id + " {{lang_cannotOpen}}");
+}
+
+function clickLink(element){
+ var data = element.getAttribute("data-value");
+ var newLink = prompt("{{lang_changeLink}}:",data);
+ if(newLink){
+ element.setAttribute("data-value",newLink);
+ }
+}
+
+function changeContent(element){
+ changeFile(module,current,element.value);
+}
+
+function switchFileInput(type){
+ var html = "";
+ switch(type){
+ case "file":
+ html = "<div class='form-group'><label for='name'>Nome do Arquivo</label><input type='name' class='form-control' id='new-file-data'></div>";
+ break;
+ case 'upload':
+ html = "<div class='form-group'><label for='name'>Arquivo</label><input type='file' class='form-control' id='new-file-data'></div>";
+ break;
+ case 'folder':
+ html = "<div class='form-group'><label for='name'>Nome da Pasta</label><input type='name' class='form-control' id='new-file-data'></div>";
+ break;
+ case 'link':
+ html = "<div class='form-group'><label for='name'>Nome do Link</label><input type='name' class='form-control' id='new-file-data'></div>";
+ break;
+ }
+ document.getElementById('new-file-input').innerHTML = html;
+}
+
+function getContent(obj,path,dirOnly){
+ var folders = "";
+ var files = "";
+ for (var property in obj) {
+ if (obj.hasOwnProperty(property)) {
+ if(property.substring(0, 4) == "dir_"){
+ var pname = property.slice(4);
+ // Create file navigation tree
+ if(!dirOnly){
+ folders += '<div class="item dir" onclick="clickDir(this)" data-target="#' + pname + '" data-path="' + path + pname + '/' + '"><div class="glyphicon glyphicon-folder-close dir-icon" aria-hidden="true" id="' + pname + '-icon"></div> ' + pname + '</div>';
+ folders += '<div class="folder" id="' + pname + '">';
+ folders += getContent(obj[property],path + pname + "/",dirOnly);
+ folders += '</div>';
+ }
+ // Create directory chooser
+ else{
+ folders += '<div class="item dir" onclick="clickDir(this)" data-target="#' + pname + '-dc" data-path="' + path + pname + '/' + '"><div class="glyphicon glyphicon-folder-close dir-icon" aria-hidden="true" id="' + pname + '-dc' + '-icon"></div> ' + pname + '</div>';
+ folders += '<div class="folder" id="' + pname + '-dc' + '">';
+ folders += getContent(obj[property],path + pname + "/",dirOnly);
+ folders += '</div>';
+ }
+ }
+ if(!dirOnly){
+ if(property.substring(0, 5) == "file_"){
+ var pname = property.slice(5);
+ files += '<div class="item" id="' + path + pname + '" onclick="clickItem(this)">' + pname + '</div>';
+ }
+ if(property.substring(0, 5) == "link_"){
+ var pname = property.slice(5);
+ files += '<div class="item item-link" data-value="' + obj[property] + '" onclick="clickLink(this)">'
+ + '<span class="glyphicon glyphicon-link" aria-hidden="true"></span> ' + pname + '</div>';
+ }
+ if(property.substring(0, 5) == "lock_"){
+ var pname = property.slice(5);
+ files += '<div class="item item-lock" id="' + path + pname + '" onclick="clickLock(this)">'
+ + pname + '</div>';
+ }
+ }
+ }
+ }
+ return folders+ files;
+}
+
+function mountNav(dirOnly){
+ document.getElementById((dirOnly) ? 'dir-nav' : 'mod-nav').innerHTML = getContent(module,"/",dirOnly);
+}
+
+function init(){
+ $.ajax({
+ method: "GET",
+ url: "?do=SysConfig&async=true",
+ data: { request:"module-contents", module: "{{module}}" }
+ }).done(function( data ) {
+ if(data != ""){
+ module = JSON.parse(data);
+ original = JSON.parse(data);
+ mountNav(false);
+ mountNav(true);
+ }
+ });
+}
+
+function save(){
+ //send JSON to PHP
+ if(JSON.stringify(original) != JSON.stringify(module)){
+
+ }
+}
+
+window.onbeforeunload = function (e) {
+ if(JSON.stringify(original) != JSON.stringify(module)){
+ var message = "{{lang_leavingMessage}}",
+ e = e || window.event;
+
+ // For IE and Firefox
+ if (e) {
+ e.returnValue = message;
+ }
+
+ // For Safari
+ return message;
+ }
+ return;
+};
+
+init();
+</script> \ No newline at end of file
diff --git a/modules-available/syslog/config.json b/modules-available/syslog/config.json
new file mode 100644
index 00000000..650ab2fe
--- /dev/null
+++ b/modules-available/syslog/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.status",
+ "enabled":"true"
+}
diff --git a/modules-available/syslog/lang/de/module.json b/modules-available/syslog/lang/de/module.json
new file mode 100644
index 00000000..b9e8de49
--- /dev/null
+++ b/modules-available/syslog/lang/de/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "Client-Log",
+ "page_title": "Lognachrichten gebooteter Clients"
+}
diff --git a/modules-available/syslog/lang/de/templates/page-syslog.json b/modules-available/syslog/lang/de/templates/page-syslog.json
new file mode 100644
index 00000000..49e94602
--- /dev/null
+++ b/modules-available/syslog/lang/de/templates/page-syslog.json
@@ -0,0 +1,10 @@
+{
+ "lang_client": "Client",
+ "lang_clientLog": "Client Log",
+ "lang_details": "Details",
+ "lang_event": "Ereignis",
+ "lang_filter": "Filter",
+ "lang_go": "Go",
+ "lang_not": "not",
+ "lang_when": "Wann"
+} \ No newline at end of file
diff --git a/modules-available/syslog/lang/en/module.json b/modules-available/syslog/lang/en/module.json
new file mode 100644
index 00000000..70107d9c
--- /dev/null
+++ b/modules-available/syslog/lang/en/module.json
@@ -0,0 +1,11 @@
+{
+ "lang_client": "Client",
+ "lang_clientLog": "Client Log",
+ "lang_details": "Details",
+ "lang_event": "Event",
+ "lang_filter": "Filter",
+ "lang_go": "go",
+ "lang_not": "no",
+ "lang_when": "When",
+ "module_name": "Server Log"
+} \ No newline at end of file
diff --git a/modules-available/syslog/lang/en/templates/page-syslog.json b/modules-available/syslog/lang/en/templates/page-syslog.json
new file mode 100644
index 00000000..71f61693
--- /dev/null
+++ b/modules-available/syslog/lang/en/templates/page-syslog.json
@@ -0,0 +1,10 @@
+{
+ "lang_client": "Client",
+ "lang_clientLog": "Client Log",
+ "lang_details": "Details",
+ "lang_event": "Event",
+ "lang_filter": "Filter",
+ "lang_go": "Go",
+ "lang_not": "not",
+ "lang_when": "When"
+} \ No newline at end of file
diff --git a/modules-available/syslog/lang/pt/module.json b/modules-available/syslog/lang/pt/module.json
new file mode 100644
index 00000000..0f7c5356
--- /dev/null
+++ b/modules-available/syslog/lang/pt/module.json
@@ -0,0 +1,11 @@
+{
+ "lang_client": "Cliente",
+ "lang_clientLog": "Log dos Clientes",
+ "lang_details": "Detalhes",
+ "lang_event": "Evento",
+ "lang_filter": "Filtro",
+ "lang_go": "Ir",
+ "lang_not": "n\u00e3o",
+ "lang_when": "Quando",
+ "module_name": "Log do Servidor"
+} \ No newline at end of file
diff --git a/modules-available/syslog/page.inc.php b/modules-available/syslog/page.inc.php
new file mode 100644
index 00000000..43a9bd28
--- /dev/null
+++ b/modules-available/syslog/page.inc.php
@@ -0,0 +1,94 @@
+<?php
+
+class Page_SysLog extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::isLoggedIn()) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ }
+
+ protected function doRender()
+ {
+ Render::setTitle('Client Log');
+ Render::addScriptBottom('bootstrap-tagsinput.min');
+
+ if (isset($_GET['filter'])) {
+ $filter = $_GET['filter'];
+ $not = isset($_GET['not']) ? 'NOT' : '';
+ } elseif (isset($_POST['filter'])) {
+ $filter = $_POST['filter'];
+ $not = isset($_POST['not']) ? 'NOT' : '';
+ Session::set('log_filter', $filter);
+ Session::set('log_not', $not);
+ Session::save();
+ } else {
+ $filter = Session::get('log_filter');
+ $not = Session::get('log_not') ? 'NOT' : '';
+ }
+ if (!empty($filter)) {
+ $filterList = explode(',', $filter);
+ $whereClause = array();
+ foreach ($filterList as $filterItem) {
+ $filterItem = preg_replace('/[^a-z0-9_\-]/', '', trim($filterItem));
+ if (empty($filterItem) || in_array($filterItem, $whereClause)) continue;
+ $whereClause[] = "'$filterItem'";
+ }
+ if (!empty($whereClause)) $whereClause = ' WHERE logtypeid ' . $not . ' IN (' . implode(', ', $whereClause) . ')';
+ }
+ if (!isset($whereClause) || empty($whereClause)) $whereClause = '';
+ if (Request::get('ip')) {
+ if (empty($whereClause))
+ $whereClause .= ' WHERE ';
+ else
+ $whereClause .= ' AND ';
+ $whereClause .= "clientip LIKE '" . preg_replace('/[^0-9\.\:]/', '', Request::get('ip')) . "%'";
+ }
+
+ $today = date('d.m.Y');
+ $yesterday = date('d.m.Y', time() - 86400);
+ $lines = array();
+ $paginate = new Paginate("SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog $whereClause ORDER BY logid DESC", 50);
+ $res = $paginate->exec();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $day = date('d.m.Y', $row['dateline']);
+ if ($day === $today) {
+ $day = Dictionary::translate('today');
+ } elseif ($day === $yesterday) {
+ $day = Dictionary::translate('yesterday');
+ }
+ $row['date'] = $day . date(' H:i', $row['dateline']);
+ $row['icon'] = $this->eventToIconName($row['logtypeid']);
+ $lines[] = $row;
+ }
+
+ $paginate->render('page-syslog', array(
+ 'filter' => $filter,
+ 'not' => $not,
+ 'list' => $lines
+ ));
+ }
+
+ private function eventToIconName($event)
+ {
+ switch ($event) {
+ case 'session-open':
+ return 'glyphicon-log-in';
+ case 'session-close':
+ return 'glyphicon-log-out';
+ case 'partition-swap':
+ return 'glyphicon-info-sign';
+ case 'partition-temp':
+ case 'smartctl-realloc':
+ return 'glyphicon-exclamation-sign';
+ default:
+ return 'glyphicon-minus';
+ }
+ }
+
+}
diff --git a/modules-available/syslog/templates/page-syslog.html b/modules-available/syslog/templates/page-syslog.html
new file mode 100644
index 00000000..98e94291
--- /dev/null
+++ b/modules-available/syslog/templates/page-syslog.html
@@ -0,0 +1,58 @@
+<h1>{{lang_clientLog}}</h1>
+<form method="post" action="?do=SysLog">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_filter}}</span>
+ <input id="filterstring" type="text" placeholder="id" value="{{filter}}" name="filter" data-role="tagsinput" />
+ <span class="input-group-addon">
+ <input type="checkbox" name="not" {{#not}}checked="checked"{{/not}}> {{lang_not}}
+ </span>
+ <span class="input-group-btn">
+ <button class="btn btn-default" type="submit">{{lang_go}}</button>
+ </span>
+ </div>
+</form>
+{{{pagenav}}}
+<table class="table table-striped table-condensed">
+ <thead>
+ <th width="1"></th>
+ <th>{{lang_when}}</th>
+ <th>{{lang_client}}</th>
+ <th>{{lang_event}}</th>
+ <th width="1">{{lang_details}}</th>
+ </thead>
+ <tbody>
+ {{#list}}
+ <tr>
+ <td><span class="glyphicon {{icon}}" title="{{logtypeid}}" onclick="$('#filterstring').tagsinput('add', '{{logtypeid}}')"></span></td>
+ <td class="text-right" nowrap="nowrap">{{date}}</td>
+ <td>{{clientip}}</td>
+ <td>{{description}}</td>
+ <td>{{#extra}}
+ <a class="btn btn-default btn-xs pull-left" onclick="$('#details-body').html($('#extra-{{logid}}').html())" data-toggle="modal" data-target="#myModal">&raquo;</a>
+ <div class="hidden" id="extra-{{logid}}">{{extra}}</div>
+ {{/extra}}</td>
+ </tr>
+ {{/list}}
+ </tbody>
+</table>
+{{{pagenav}}}
+
+<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+ <div class="modal-dialog modal-lg">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+ <h4 class="modal-title" id="myModalLabel">{{lang_details}}</h4>
+ </div>
+ <div class="modal-body">
+ <pre id="details-body"></pre>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+
diff --git a/modules-available/systemstatus/config.json b/modules-available/systemstatus/config.json
new file mode 100644
index 00000000..650ab2fe
--- /dev/null
+++ b/modules-available/systemstatus/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.status",
+ "enabled":"true"
+}
diff --git a/modules-available/systemstatus/lang/de/templates/_page.json b/modules-available/systemstatus/lang/de/templates/_page.json
new file mode 100644
index 00000000..9c97961a
--- /dev/null
+++ b/modules-available/systemstatus/lang/de/templates/_page.json
@@ -0,0 +1,11 @@
+{
+ "lang_addressConfiguration": "Adresskonfiguration",
+ "lang_advanced": "Erweitert \/ Debug",
+ "lang_dmsdLog": "dmsd Log",
+ "lang_iAmSure": "Ja, ich bin sicher",
+ "lang_ldadpLog": "AD\/LDAP-Proxy Log",
+ "lang_maintenance": "Maintenance",
+ "lang_services": "Dienste",
+ "lang_space": "Speicherplatz",
+ "lang_system": "System"
+} \ No newline at end of file
diff --git a/modules-available/systemstatus/lang/de/templates/addresses.json b/modules-available/systemstatus/lang/de/templates/addresses.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/modules-available/systemstatus/lang/de/templates/addresses.json
@@ -0,0 +1 @@
+{}
diff --git a/modules-available/systemstatus/lang/de/templates/diskstat.json b/modules-available/systemstatus/lang/de/templates/diskstat.json
new file mode 100644
index 00000000..c44433ea
--- /dev/null
+++ b/modules-available/systemstatus/lang/de/templates/diskstat.json
@@ -0,0 +1,12 @@
+{
+ "lang_capacity": "Kapazit\u00e4t",
+ "lang_foundStore": "Vorgefunden:",
+ "lang_free": "Frei",
+ "lang_goToStoreConf": "Zur VM-Store-Konfiguration wechseln",
+ "lang_storeMissingExpected": "VM-Store nicht eingebunden. Erwartet:",
+ "lang_storeNotConfigured": "Kein VM-Store konfiguriert!",
+ "lang_systemPartition": "Systempartition",
+ "lang_systemStoreError": "Fehler beim Ermitteln des verf\u00fcgbaren Systemspeichers",
+ "lang_vmStore": "VM-Speicher",
+ "lang_vmStoreError": "Fehler beim Ermitteln des verf\u00fcgbaren Speicherplatzes am VM-Speicherort. Bitte \u00fcberpr\u00fcfen Sie die Konfiguration."
+} \ No newline at end of file
diff --git a/modules-available/systemstatus/lang/de/templates/services.json b/modules-available/systemstatus/lang/de/templates/services.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/modules-available/systemstatus/lang/de/templates/services.json
@@ -0,0 +1 @@
+{}
diff --git a/modules-available/systemstatus/lang/de/templates/systeminfo.json b/modules-available/systemstatus/lang/de/templates/systeminfo.json
new file mode 100644
index 00000000..cd4027f1
--- /dev/null
+++ b/modules-available/systemstatus/lang/de/templates/systeminfo.json
@@ -0,0 +1,15 @@
+{
+ "lang_attention": "Achtung!",
+ "lang_average": "Durchschnitt",
+ "lang_cpuLoad": "CPU-Last",
+ "lang_free": "Frei",
+ "lang_logicCPUs": "Logische CPUs",
+ "lang_notDetermined": "Konnte nicht ermittelt werden",
+ "lang_occupied": "Belegt",
+ "lang_onlyOS": "Nur OS",
+ "lang_ramUsage": "RAM-Nutzung",
+ "lang_swapUsage": "swap-Nutzung",
+ "lang_swapWarning": "Es wird swap-Speicher genutzt. Dies kann ein Hinweis darauf sein, dass dem Satelliten-Server zu wenig physikalischer Speicher zur Verf\u00fcgung steht. Im Falle von Performance-Problemen oder Instabilit\u00e4t des Servers sollten Sie erw\u00e4gen, den Server mit mehr RAM auszustatten.",
+ "lang_total": "Gesamt",
+ "lang_uptimeOS": "OS Uptime"
+} \ No newline at end of file
diff --git a/modules-available/systemstatus/lang/en/module.json b/modules-available/systemstatus/lang/en/module.json
new file mode 100644
index 00000000..126d6bb8
--- /dev/null
+++ b/modules-available/systemstatus/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Server"
+} \ No newline at end of file
diff --git a/modules-available/systemstatus/lang/en/templates/_page.json b/modules-available/systemstatus/lang/en/templates/_page.json
new file mode 100644
index 00000000..f698521d
--- /dev/null
+++ b/modules-available/systemstatus/lang/en/templates/_page.json
@@ -0,0 +1,11 @@
+{
+ "lang_addressConfiguration": "Address Configuration",
+ "lang_advanced": "Advanced \/ Debug",
+ "lang_dmsdLog": "dmsd log",
+ "lang_iAmSure": "Yes, I am sure",
+ "lang_ldadpLog": "AD\/LDAP proxy log",
+ "lang_maintenance": "Maintenance",
+ "lang_services": "Services",
+ "lang_space": "Space",
+ "lang_system": "System"
+} \ No newline at end of file
diff --git a/modules-available/systemstatus/lang/en/templates/addresses.json b/modules-available/systemstatus/lang/en/templates/addresses.json
new file mode 100644
index 00000000..2c63c085
--- /dev/null
+++ b/modules-available/systemstatus/lang/en/templates/addresses.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/modules-available/systemstatus/lang/en/templates/diskstat.json b/modules-available/systemstatus/lang/en/templates/diskstat.json
new file mode 100644
index 00000000..6c880ebe
--- /dev/null
+++ b/modules-available/systemstatus/lang/en/templates/diskstat.json
@@ -0,0 +1,12 @@
+{
+ "lang_capacity": "Capacity",
+ "lang_foundStore": "Found:",
+ "lang_free": "Free",
+ "lang_goToStoreConf": "Go to VM store configuration",
+ "lang_storeMissingExpected": "VM store not mounted. Expected:",
+ "lang_storeNotConfigured": "No VM store configured!",
+ "lang_systemPartition": "System Partition",
+ "lang_systemStoreError": "Error querying available system storage",
+ "lang_vmStore": "VM Store",
+ "lang_vmStoreError": "Error determining available space of the VM storage. Please check the configuration."
+} \ No newline at end of file
diff --git a/modules-available/systemstatus/lang/en/templates/services.json b/modules-available/systemstatus/lang/en/templates/services.json
new file mode 100644
index 00000000..2c63c085
--- /dev/null
+++ b/modules-available/systemstatus/lang/en/templates/services.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/modules-available/systemstatus/lang/en/templates/systeminfo.json b/modules-available/systemstatus/lang/en/templates/systeminfo.json
new file mode 100644
index 00000000..602693f5
--- /dev/null
+++ b/modules-available/systemstatus/lang/en/templates/systeminfo.json
@@ -0,0 +1,15 @@
+{
+ "lang_attention": "Attention!",
+ "lang_average": "Average",
+ "lang_cpuLoad": "CPU Load",
+ "lang_free": "Free",
+ "lang_logicCPUs": "Logic CPUs",
+ "lang_notDetermined": "Could not be determined",
+ "lang_occupied": "Occupied",
+ "lang_onlyOS": "OS Only",
+ "lang_ramUsage": "RAM Usage",
+ "lang_swapUsage": "swap Usage",
+ "lang_swapWarning": "Memory swap is being used. This may be an indication that the satellite server does not have enough physical memory available. In the case of performance problems or server instability you should consider equipping the server with more RAM.",
+ "lang_total": "Total",
+ "lang_uptimeOS": "OS Uptime"
+} \ No newline at end of file
diff --git a/modules-available/systemstatus/lang/pt/module.json b/modules-available/systemstatus/lang/pt/module.json
new file mode 100644
index 00000000..c191bcfd
--- /dev/null
+++ b/modules-available/systemstatus/lang/pt/module.json
@@ -0,0 +1,26 @@
+{
+ "lang_addressConfiguration": "Configura\u00e7\u00e3o do Endere\u00e7o",
+ "lang_attention": "Aten\u00e7\u00e3o!",
+ "lang_average": "M\u00e9dia",
+ "lang_capacity": "Capacidade",
+ "lang_cpuLoad": "Carga da CPU",
+ "lang_free": "Livre",
+ "lang_iAmSure": "Sim, eu tenho certeza",
+ "lang_logicCPUs": "CPUs L\u00f3gicas",
+ "lang_maintenance": "Manuten\u00e7\u00e3o",
+ "lang_notDetermined": "N\u00e3o foi poss\u00edvel determinar",
+ "lang_occupied": "Ocupado",
+ "lang_onlyOS": "Apenas SO",
+ "lang_ramUsage": "Uso da RAM",
+ "lang_services": "Servi\u00e7os",
+ "lang_space": "Espa\u00e7o",
+ "lang_swapUsage": "Uso do swap",
+ "lang_swapWarning": "Swap de mem\u00f3ria est\u00e1 sendo usado. Isso pode ser uma indica\u00e7\u00e3o de que o servidor de sat\u00e9lite n\u00e3o tem mem\u00f3ria f\u00edsica suficiente dispon\u00edvel. No caso de problemas de desempenho ou instabilidade do servidor voc\u00ea deve considerar equipar o servidor com mais mem\u00f3ria RAM.",
+ "lang_system": "Sistema",
+ "lang_systemPartition": "Parti\u00e7\u00e3o do Sistema",
+ "lang_total": "Total",
+ "lang_uptimeOS": "Tempo de Atividade do SO",
+ "lang_vmStore": "Armazenamento da VM",
+ "lang_vmStoreError": "Erro ao determinar o espa\u00e7o em disco dispon\u00edvel no local da VM. Por favor, verifique a configura\u00e7\u00e3o.",
+ "module_name": "Servidor"
+} \ No newline at end of file
diff --git a/modules-available/systemstatus/page.inc.php b/modules-available/systemstatus/page.inc.php
new file mode 100644
index 00000000..2a5382e1
--- /dev/null
+++ b/modules-available/systemstatus/page.inc.php
@@ -0,0 +1,361 @@
+<?php
+
+class Page_SystemStatus extends Page
+{
+
+ private $rebootTask = false;
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::isLoggedIn()) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ if (Request::post('action') === 'reboot') {
+ if (Request::post('confirm') !== 'yep') {
+ Message::addError('reboot-unconfirmed');
+ Util::redirect('?do=SystemStatus');
+ }
+ $this->rebootTask = Taskmanager::submit('Reboot');
+ }
+ }
+
+ protected function doRender()
+ {
+ $data = array();
+ if (is_array($this->rebootTask) && isset($this->rebootTask['id'])) {
+ $data['rebootTask'] = $this->rebootTask['id'];
+ }
+ Render::addScriptTop('custom');
+ Render::addScriptBottom('circles.min');
+ Render::addTemplate('_page', $data);
+ }
+
+ protected function doAjax()
+ {
+ User::load();
+
+ if (!User::isLoggedIn())
+ return;
+
+ $action = 'ajax' . Request::any('action');
+ if (method_exists($this, $action)) {
+ $this->$action();
+ Message::renderList();
+ } else {
+ echo "Action $action not known in " . get_class();
+ }
+ }
+
+ protected function ajaxDmsdUsers()
+ {
+ $ret = Download::asStringPost('http://127.0.0.1:9080/status/fileserver', false, 2, $code);
+ if ($code != 200) {
+ Header('HTTP/1.1 502 Internal Server Error');
+ die('Internal Server Wurst');
+ }
+ $data = @json_decode($ret, true);
+ if (is_array($data)) {
+ $ret = 'Uploads: ' . $data['activeUploads'] . ', Downloads: ' . $data['activeDownloads'];
+ } else {
+ $ret = '???';
+ }
+ die($ret);
+ }
+
+ protected function ajaxDiskStat()
+ {
+ $task = Taskmanager::submit('DiskStat');
+ if ($task === false)
+ return;
+ $task = Taskmanager::waitComplete($task, 3000);
+
+ if (!isset($task['data']['list']) || empty($task['data']['list'])) {
+ Taskmanager::addErrorMessage($task);
+ return;
+ }
+ $store = Property::getVmStoreUrl();
+ $storeUsage = false;
+ $systemUsage = false;
+ if ($store !== false) {
+ if ($store === '<local>')
+ $storePoint = '/';
+ else
+ $storePoint = CONFIG_VMSTORE_DIR;
+ // Determine free space
+ foreach ($task['data']['list'] as $entry) {
+ if ($entry['mountPoint'] === $storePoint) {
+ $storeUsage = array(
+ 'percent' => $entry['usedPercent'],
+ 'size' => Util::readableFileSize($entry['sizeKb'] * 1024),
+ 'free' => Util::readableFileSize($entry['freeKb'] * 1024),
+ 'color' => $this->usageColor($entry['usedPercent'])
+ );
+ }
+ if ($entry['mountPoint'] === '/') {
+ $systemUsage = array(
+ 'percent' => $entry['usedPercent'],
+ 'size' => Util::readableFileSize($entry['sizeKb'] * 1024),
+ 'free' => Util::readableFileSize($entry['freeKb'] * 1024),
+ 'color' => $this->usageColor($entry['usedPercent'])
+ );
+ }
+ }
+ $data = array(
+ 'store' => $storeUsage,
+ 'system' => $systemUsage
+ );
+ // Determine if proper vm store is being used
+ if ($store !== '<local>') {
+ $data['storeMissing'] = $store;
+ }
+ foreach ($task['data']['list'] as $entry) {
+ if ($entry['mountPoint'] !== CONFIG_VMSTORE_DIR)
+ continue;
+ if ($store !== $entry['fileSystem']) {
+ $data['wrongStore'] = $entry['fileSystem'];
+ break;
+ }
+ $data['storeMissing'] = false;
+ }
+ } else {
+ $data['notConfigured'] = true;
+ }
+ echo Render::parse('diskstat', $data);
+ }
+
+ protected function ajaxAddressList()
+ {
+ $task = Taskmanager::submit('LocalAddressesList');
+ if ($task === false)
+ return;
+ $task = Taskmanager::waitComplete($task, 3000);
+
+ if (!isset($task['data']['addresses']) || empty($task['data']['addresses'])) {
+ Taskmanager::addErrorMessage($task);
+ return;
+ }
+
+ $sort = array();
+ $primary = Property::getServerIp();
+ foreach ($task['data']['addresses'] as &$addr) {
+ $sort[] = $addr['type'] . $addr['ip'];
+ if ($addr['ip'] === $primary)
+ $addr['primary'] = true;
+ }
+ array_multisort($sort, SORT_STRING, $task['data']['addresses']);
+ echo Render::parse('addresses', array(
+ 'addresses' => $task['data']['addresses']
+ ));
+ }
+
+ private function sysInfo()
+ {
+ $data = array();
+ $memInfo = file_get_contents('/proc/meminfo');
+ $stat = file_get_contents('/proc/stat');
+ preg_match_all('/\b(\w+):\s+(\d+)\s/s', $memInfo, $out, PREG_SET_ORDER);
+ foreach ($out as $e) {
+ $data[$e[1]] = $e[2];
+ }
+ if (preg_match('/\bcpu\s+(?<user>\d+)\s+(?<nice>\d+)\s+(?<system>\d+)\s+(?<idle>\d+)\s+(?<iowait>\d+)\s+(?<irq>\d+)\s+(?<softirq>\d+)(\s|$)/', $stat, $out)) {
+ $data['CpuTotal'] = $out['user'] + $out['nice'] + $out['system'] + $out['idle'] + $out['iowait'] + $out['irq'] + $out['softirq'];
+ $data['CpuIdle'] = $out['idle'] + $out['iowait'];
+ $data['CpuSystem'] = $out['irq'] + $out['softirq'];
+ }
+ return $data;
+ }
+
+ protected function ajaxSystemInfo()
+ {
+ $cpuInfo = file_get_contents('/proc/cpuinfo');
+ $uptime = file_get_contents('/proc/uptime');
+ $cpuCount = preg_match_all('/\bprocessor\s/', $cpuInfo, $out);
+ //$cpuCount = count($out);
+ $data = array(
+ 'cpuCount' => $cpuCount,
+ 'memTotal' => '???',
+ 'memFree' => '???',
+ 'swapTotal' => '???',
+ 'swapUsed' => '???',
+ 'uptime' => '???'
+ );
+ if (preg_match('/^(\d+)\D/', $uptime, $out)) {
+ $data['uptime'] = floor($out[1] / 86400) . ' ' . Dictionary::translate('lang_days') . ', ' . floor(($out[1] % 86400) / 3600) . ' ' . Dictionary::translate('lang_hours'); // TODO: i18n
+ }
+ $info = $this->sysInfo();
+ if (isset($info['MemTotal']) && isset($info['MemFree']) && isset($info['SwapTotal'])) {
+ $data['memTotal'] = Util::readableFileSize($info['MemTotal'] * 1024);
+ $data['memFree'] = Util::readableFileSize(($info['MemFree'] + $info['Buffers'] + $info['Cached']) * 1024);
+ $data['memPercent'] = 100 - round((($info['MemFree'] + $info['Buffers'] + $info['Cached']) / $info['MemTotal']) * 100);
+ $data['swapTotal'] = Util::readableFileSize($info['SwapTotal'] * 1024);
+ $data['swapUsed'] = Util::readableFileSize(($info['SwapTotal'] - $info['SwapFree']) * 1024);
+ $data['swapPercent'] = 100 - round(($info['SwapFree'] / $info['SwapTotal']) * 100);
+ $data['swapWarning'] = ($data['swapPercent'] > 50 || ($info['SwapTotal'] - $info['SwapFree']) > 200000);
+ }
+ if (isset($info['CpuIdle']) && isset($info['CpuSystem']) && isset($info['CpuTotal'])) {
+ $data['cpuLoad'] = 100 - round(($info['CpuIdle'] / $info['CpuTotal']) * 100);
+ $data['cpuSystem'] = round(($info['CpuSystem'] / $info['CpuTotal']) * 100);
+ $data['cpuLoadOk'] = true;
+ $data['CpuTotal'] = $info['CpuTotal'];
+ $data['CpuIdle'] = $info['CpuIdle'];
+ }
+ echo Render::parse('systeminfo', $data);
+ }
+
+ protected function ajaxSysPoll()
+ {
+ $info = $this->sysInfo();
+ $data = array(
+ 'CpuTotal' => $info['CpuTotal'],
+ 'CpuIdle' => $info['CpuIdle'],
+ 'MemPercent' => 100 - round((($info['MemFree'] + $info['Buffers'] + $info['Cached']) / $info['MemTotal']) * 100),
+ 'SwapPercent' => 100 - round(($info['SwapFree'] / $info['SwapTotal']) * 100)
+ );
+ Header('Content-Type: application/json; charset=utf-8');
+ die(json_encode($data));
+ }
+
+ protected function ajaxServices()
+ {
+ $data = array();
+
+ $taskId = Trigger::ldadp();
+ if ($taskId === false)
+ return;
+ $status = Taskmanager::waitComplete($taskId, 10000);
+
+ if (Taskmanager::isFailed($status)) {
+ if (isset($status['data']['messages']))
+ $data['ldadpError'] = $status['data']['messages'];
+ else
+ $data['ldadpError'] = 'Taskmanager error';
+ }
+ // TODO: Dozentenmodul, tftp, ...
+
+ echo Render::parse('services', $data);
+ }
+
+ protected function ajaxDmsdLog()
+ {
+ $fh = @fopen('/var/log/dmsd.log', 'r');
+ if ($fh === false) {
+ echo 'Error opening log file';
+ return;
+ }
+ fseek($fh, -6000, SEEK_END);
+ $data = fread($fh, 6000);
+ @fclose($fh);
+ if ($data === false) {
+ echo 'Error reading from log file';
+ return;
+ }
+ // If we could read less, try the .1 file too
+ $amount = 6000 - strlen($data);
+ if ($amount > 100) {
+ $fh = @fopen('/var/log/dmsd.log.1', 'r');
+ if ($fh !== false) {
+ fseek($fh, -$amount, SEEK_END);
+ $data = fread($fh, $amount) . $data;
+ @fclose($fh);
+ }
+ }
+ if (strlen($data) < 5990) {
+ $start = 0;
+ } else {
+ $start = strpos($data, "\n") + 1;
+ }
+ echo '<pre>', htmlspecialchars(substr($data, $start)), '</pre>';
+ }
+
+ protected function ajaxLdadpLog()
+ {
+ $files = glob('/var/log/ldadp/*.log', GLOB_NOSORT);
+ if ($files === false || empty($files)) echo('No logs found');
+ $now = time();
+ foreach ($files as $file) {
+ $mod = filemtime($file);
+ if ($now - $mod > 86400) continue;
+ // New enough - handle
+ preg_match(',/(\d+)\.log,', $file, $out);
+ $module = ConfigModule::get($out[1]);
+ if ($module === false) {
+ echo '<h4>Module ', $out[1], '</h4>';
+ } else {
+ echo '<h4>Module ', htmlspecialchars($module->title()), '</h4>';
+ }
+ $fh = @fopen($file, 'r');
+ if ($fh === false) {
+ echo '<pre>Error opening log file</pre>';
+ continue;
+ }
+ fseek($fh, -5000, SEEK_END);
+ $data = fread($fh, 5000);
+ @fclose($fh);
+ if ($data === false) {
+ echo '<pre>Error reading from log file</pre>';
+ continue;
+ }
+ if (strlen($data) < 4990) {
+ $start = 0;
+ } else {
+ $start = strpos($data, "\n") + 1;
+ }
+ echo '<pre>', htmlspecialchars(substr($data, $start)), '</pre>';
+ }
+ }
+
+ protected function ajaxNetstat()
+ {
+ $taskId = Taskmanager::submit('Netstat');
+ if ($taskId === false)
+ return;
+ $status = Taskmanager::waitComplete($taskId, 3500);
+
+ if (isset($status['data']['messages']))
+ $data = $status['data']['messages'];
+ else
+ $data = 'Taskmanager error';
+
+ echo '<pre>', htmlspecialchars($data), '</pre>';
+ }
+
+ protected function ajaxPsList()
+ {
+ $taskId = Taskmanager::submit('PsList');
+ if ($taskId === false)
+ return;
+ $status = Taskmanager::waitComplete($taskId, 3500);
+
+ if (isset($status['data']['messages']))
+ $data = $status['data']['messages'];
+ else
+ $data = 'Taskmanager error';
+
+ echo '<pre>', htmlspecialchars($data), '</pre>';
+ }
+
+ private function usageColor($percent)
+ {
+ if ($percent <= 50) {
+ $r = $b = $percent / 3;
+ $g = (100 - $percent * (50 / 80));
+ } elseif ($percent <= 70) {
+ $r = 55 + ($percent - 50) * (30 / 20);
+ $g = 60;
+ $b = 0;
+ } else {
+ $r = ($percent - 70) / 3 + 90;
+ $g = (100 - $percent) * (60 / 30);
+ $b = 0;
+ }
+ $r = dechex(round($r * 2.55));
+ $g = dechex(round($g * 2.55));
+ $b = dechex(round($b * 2.55));
+ return sprintf("%02s%02s%02s", $r, $g, $b);
+ }
+
+}
diff --git a/modules-available/systemstatus/templates/_page.html b/modules-available/systemstatus/templates/_page.html
new file mode 100644
index 00000000..51aa5b55
--- /dev/null
+++ b/modules-available/systemstatus/templates/_page.html
@@ -0,0 +1,129 @@
+{{#rebootTask}}
+<div data-tm-id="{{rebootTask}}" data-tm-log="messages">Reboot...</div>
+{{/rebootTask}}
+
+<div class="row">
+
+ <div class="col-sm-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_space}}
+ </div>
+ <div class="panel-body" id="diskstat">
+ <span class="glyphicon glyphicon-refresh slx-rotation"></span>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-sm-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_services}}
+ </div>
+ <div class="panel-body" id="services">
+ <span class="glyphicon glyphicon-refresh slx-rotation"></span>
+ </div>
+ </div>
+ </div>
+
+</div>
+
+
+<div class="row">
+
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_addressConfiguration}}
+ </div>
+ <div class="panel-body" id="addresses">
+ <span class="glyphicon glyphicon-refresh slx-rotation"></span>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_system}}
+ </div>
+ <div class="panel-body" id="systeminfo">
+ <span class="glyphicon glyphicon-refresh slx-rotation"></span>
+ </div>
+ </div>
+ </div>
+
+</div>
+
+<div class="row">
+
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_maintenance}}
+ </div>
+ <div class="panel-body">
+ <form class="form-adduser" action="?do=SystemStatus" method="post">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="reboot">
+ <div>Server Reboot</div>
+ <label><input type="checkbox" name="confirm" value="yep"> {{lang_iAmSure}}</label>
+ <button class="btn btn-warning btn-xs" type="submit">Reboot</button>
+ </form>
+ <div id="dmsd-users"></div>
+ </div>
+ </div>
+ </div>
+
+</div>
+
+<h3>{{lang_advanced}}</h3>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_dmsdLog}}
+ </div>
+ <div class="panel-body" id="dmsd-log">
+ <span class="glyphicon glyphicon-refresh slx-rotation"></span>
+ </div>
+</div>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_ldadpLog}}
+ </div>
+ <div class="panel-body" id="ldadp-log">
+ <span class="glyphicon glyphicon-refresh slx-rotation"></span>
+ </div>
+</div>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ netstat -tulpn
+ </div>
+ <div class="panel-body" id="netstat">
+ <span class="glyphicon glyphicon-refresh slx-rotation"></span>
+ </div>
+</div>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ ps auxf
+ </div>
+ <div class="panel-body" id="pslist">
+ <span class="glyphicon glyphicon-refresh slx-rotation"></span>
+ </div>
+</div>
+
+<script type="text/javascript">
+ document.addEventListener("DOMContentLoaded", function() {
+ $('#diskstat').load('?do=SystemStatus&action=DiskStat');
+ $('#addresses').load('?do=SystemStatus&action=AddressList');
+ $('#systeminfo').load('?do=SystemStatus&action=SystemInfo');
+ $('#services').load('?do=SystemStatus&action=Services');
+ $('#dmsd-users').load('?do=SystemStatus&action=DmsdUsers');
+ setTimeout(function() {
+ $('#dmsd-log').load('?do=SystemStatus&action=DmsdLog');
+ $('#netstat').load('?do=SystemStatus&action=Netstat');
+ $('#pslist').load('?do=SystemStatus&action=PsList');
+ $('#ldadp-log').load('?do=SystemStatus&action=LdadpLog');
+ }, 300);
+ }, false);
+</script>
diff --git a/modules-available/systemstatus/templates/addresses.html b/modules-available/systemstatus/templates/addresses.html
new file mode 100644
index 00000000..ce92c4d5
--- /dev/null
+++ b/modules-available/systemstatus/templates/addresses.html
@@ -0,0 +1,8 @@
+<table class="slx-table">
+ {{#addresses}}
+ <tr {{#primary}} class="slx-bold" {{/primary}}>
+ <td>{{ip}}</td>
+ <td>({{iface}})</td>
+ </tr>
+ {{/addresses}}
+</table> \ No newline at end of file
diff --git a/modules-available/systemstatus/templates/diskstat.html b/modules-available/systemstatus/templates/diskstat.html
new file mode 100644
index 00000000..528d9792
--- /dev/null
+++ b/modules-available/systemstatus/templates/diskstat.html
@@ -0,0 +1,63 @@
+<div class="slx-storechart">
+ {{#system}}
+ <b>{{lang_systemPartition}}</b>
+ <div id="circles-system"></div>
+ <div>{{lang_capacity}}: {{size}}</div>
+ <div>{{lang_free}}: {{free}}</div>
+ {{/system}}
+ {{^system}}
+ <b>{{lang_systemStoreError}}</b>
+ {{/system}}
+</div>
+<div class="slx-storechart">
+ {{#store}}
+ <b>{{lang_vmStore}}</b>
+ <div id="circles-store"></div>
+ <div>{{lang_capacity}}: {{size}}</div>
+ <div>{{lang_free}}: {{free}}</div>
+ {{/store}}
+ {{^store}}
+ <b>{{lang_vmStoreError}}</b>
+ {{/store}}
+</div>
+<div class="clearfix"></div>
+{{#notConfigured}}
+<div>{{lang_storeNotConfigured}}</div>
+{{/notConfigured}}
+{{#storeMissing}}
+<div>{{lang_storeMissingExpected}} {{storeMissing}}</div>
+{{/storeMissing}}
+{{#wrongStore}}
+<div>{{lang_foundStore}} {{wrongStore}}</div>
+{{/wrongStore}}
+<a href="?do=VmStore">{{lang_goToStoreConf}}</a>
+<script type="text/javascript">
+ {{#store}}
+ Circles.create({
+ id: 'circles-store',
+ radius: 60,
+ value: {{{percent}}},
+ maxValue: 100,
+ width: 10,
+ text: function(value){return value + '%'; },
+ colors: ['#D3B6C6', '#{{color}}'],
+ duration: 400,
+ wrpClass: 'circles-wrp',
+ textClass: 'circles-text'
+ });
+ {{/store}}
+ {{#system}}
+ Circles.create({
+ id: 'circles-system',
+ radius: 60,
+ value: {{{percent}}},
+ maxValue: 100,
+ width: 10,
+ text: function(value){return value + '%'; },
+ colors: ['#D3B6C6', '#{{color}}'],
+ duration: 400,
+ wrpClass: 'circles-wrp',
+ textClass: 'circles-text'
+ });
+ {{/system}}
+</script>
diff --git a/modules-available/systemstatus/templates/services.html b/modules-available/systemstatus/templates/services.html
new file mode 100644
index 00000000..6c4f0b93
--- /dev/null
+++ b/modules-available/systemstatus/templates/services.html
@@ -0,0 +1,6 @@
+{{#ldadpError}}
+<pre>{{ldadpError}}</pre>
+{{/ldadpError}}
+{{^ldadpError}}
+<div class="alert alert-success">LDAP-AD-Proxy: OK</div>
+{{/ldadpError}}
diff --git a/modules-available/systemstatus/templates/systeminfo.html b/modules-available/systemstatus/templates/systeminfo.html
new file mode 100644
index 00000000..ed4a1532
--- /dev/null
+++ b/modules-available/systemstatus/templates/systeminfo.html
@@ -0,0 +1,115 @@
+<div>
+ {{lang_uptimeOS}}: {{uptime}}
+</div>
+
+<div class="slx-storechart">
+ <b>{{lang_cpuLoad}}</b>
+ {{#cpuLoadOk}}
+ <div id="circles-cpuload"></div>
+ <div>{{lang_average}}: {{cpuLoad}}%</div>
+ <div>{{lang_onlyOS}}: {{cpuSystem}}%</div>
+ <div>{{lang_logicCPUs}}: {{cpuCount}}</div>
+ {{/cpuLoadOk}}
+ {{^cpuLoadOk}}
+ {{lang_notDetermined}}
+ {{/cpuLoadOk}}
+</div>
+
+<div class="slx-storechart">
+ <b>{{lang_ramUsage}}</b>
+ {{#memTotal}}
+ <div id="circles-mem"></div>
+ <div>{{lang_total}}: {{memTotal}}</div>
+ <div>{{lang_free}}: {{memFree}}</div>
+ {{/memTotal}}
+ {{^memTotal}}
+ {{lang_notDetermined}}
+ {{/memTotal}}
+</div>
+
+<div class="slx-storechart">
+ <b>{{lang_swapUsage}}</b>
+ {{#memTotal}}
+ <div id="circles-swap"></div>
+ <div>{{lang_total}}: {{swapTotal}}</div>
+ <div>{{lang_occupied}}: {{swapUsed}}</div>
+ {{/memTotal}}
+ {{^memTotal}}
+ {{lang_notDetermined}}
+ {{/memTotal}}
+</div>
+
+<div class="clearfix"></div>
+
+{{#swapWarning}}
+<div>
+ <b>{{lang_attention}}</b> {{lang_swapWarning}}
+</div>
+{{/swapWarning}}
+
+<script type="text/javascript">
+ {{#cpuLoadOk}}
+ var cpuCircle = Circles.create({
+ id: 'circles-cpuload',
+ radius: 60,
+ value: {{{cpuLoad}}},
+ maxValue: 100,
+ width: 10,
+ text: function(value){return value + '%'; },
+ colors: ['#dbc', '#33f'],
+ duration: 400,
+ wrpClass: 'circles-wrp',
+ textClass: 'circles-text'
+ });
+ var lastCpuTotal = {{CpuTotal}};
+ var lastCpuIdle = {{CpuIdle}};
+ var lastCpuPercent = {{cpuLoad}};
+ {{/cpuLoadOk}}
+ {{#memTotal}}
+ var memCircle = Circles.create({
+ id: 'circles-mem',
+ radius: 60,
+ value: {{{memPercent}}},
+ maxValue: 100,
+ width: 10,
+ text: function(value){return value + '%'; },
+ colors: ['#dbc', '#33f'],
+ duration: 400,
+ wrpClass: 'circles-wrp',
+ textClass: 'circles-text'
+ });
+ var swapCircle = Circles.create({
+ id: 'circles-swap',
+ radius: 60,
+ value: {{{swapPercent}}},
+ maxValue: 100,
+ width: 10,
+ text: function(value){return value + '%'; },
+ colors: ['#dbc', '#f33'],
+ duration: 400,
+ wrpClass: 'circles-wrp',
+ textClass: 'circles-text'
+ });
+ {{/memTotal}}
+ function updateSystem() {
+ if (!cpuCircle && !memCircle) return;
+ $.post('?do=SystemStatus&action=SysPoll', { token: TOKEN }, function(data) {
+ if (memCircle && data.MemPercent) memCircle.update(data.MemPercent);
+ if (swapCircle && data.SwapPercent) swapCircle.update(data.SwapPercent);
+ if (cpuCircle && data.CpuIdle) {
+ var total = data.CpuTotal - lastCpuTotal;
+ var load = total - (data.CpuIdle - lastCpuIdle);
+ var percent = Math.round(100 * load / total);
+ cpuCircle.update(percent, Math.abs(percent - lastCpuPercent) < 5 ? 0 : 250);
+ lastCpuTotal = data.CpuTotal;
+ lastCpuIdle = data.CpuIdle;
+ lastCpuPercent = percent;
+ }
+ }, 'json').fail(function(data) {
+ console.log(data);
+ }).always(function() {
+ setTimeout(updateSystem, 1200);
+ });
+ }
+ setTimeout(updateSystem, 1000);
+</script>
diff --git a/modules-available/translation/config.json b/modules-available/translation/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/translation/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/translation/lang/de/templates/_page.json b/modules-available/translation/lang/de/templates/_page.json
new file mode 100644
index 00000000..639697de
--- /dev/null
+++ b/modules-available/translation/lang/de/templates/_page.json
@@ -0,0 +1,9 @@
+{
+ "lang_adminInfo": "In diesem Abschnitt k\u00f6nnen Sie die JSON-Tags, die \u00fcbersetzten Texte durch die Website verwendet wird, enth\u00e4lt zu \u00e4ndern. Um dies zu tun, w\u00e4hlen Sie eine Vorlage aus, um ihre jeweiligen Tags \u00e4ndern. Alternativ klicken Sie auf den Link unten, um die Nachrichten der Website \u00e4ndern.",
+ "lang_editConfigModule": "Konfigurationsmodulbezeichnungsphrasen editieren",
+ "lang_editHardcoded": "Hardcoded-Texte bearbeiten",
+ "lang_editMessages": "Nachrichten bearbeiten",
+ "lang_editSettings": "Einstellungstexte bearbeiten",
+ "lang_editTemplates": "Template-Texte bearbeiten",
+ "lang_langAdministration": "Sprache Verwaltung"
+} \ No newline at end of file
diff --git a/modules-available/translation/lang/de/templates/edit.json b/modules-available/translation/lang/de/templates/edit.json
new file mode 100644
index 00000000..671e51d8
--- /dev/null
+++ b/modules-available/translation/lang/de/templates/edit.json
@@ -0,0 +1,12 @@
+{
+ "lang_back": "Z\u00fcruck",
+ "lang_createTag": "TAG schafen",
+ "lang_deleteTAG": "L\u00f6schen",
+ "lang_englishTAG": "Englisch TAG",
+ "lang_germanTAG": "Deutsch TAG",
+ "lang_newTAG": "Neue Tag",
+ "lang_portugueseTAG": "Portugiesisch TAG",
+ "lang_save": "Speichern",
+ "lang_templateAdminHelp": "Hier k\u00f6nnen Sie die verwendeten Texte und S\u00e4tze \u00fcbersetzen.",
+ "lang_templateHint": "Hinweis: Gelbe Linie zeigt eine \u00dcbersetzung fehlt und roten Linien zeigen ein Tag wird nicht von das Template verwendet."
+} \ No newline at end of file
diff --git a/modules-available/translation/lang/de/templates/template-list.json b/modules-available/translation/lang/de/templates/template-list.json
new file mode 100644
index 00000000..5d6945bc
--- /dev/null
+++ b/modules-available/translation/lang/de/templates/template-list.json
@@ -0,0 +1,5 @@
+{
+ "lang_adminInfo": "Dies ist eine Liste aller Templates. Die \u0022Status\u0022-Spalte zeigt an, wenn f\u00fcr ein Template \u00fcbersetzungen fehlen, oder veraltete Tags definiert sind.",
+ "lang_langAdministration": "Templates",
+ "lang_status": "Status"
+} \ No newline at end of file
diff --git a/modules-available/translation/lang/en/module.json b/modules-available/translation/lang/en/module.json
new file mode 100644
index 00000000..07bdb07e
--- /dev/null
+++ b/modules-available/translation/lang/en/module.json
@@ -0,0 +1,16 @@
+{
+ "lang_adminInfo": "In this section you can change the JSON tags that contains the translated texts used by the site. To do this, select a template below to modify their respective tags. Alternatively, click the link below to change the messages of the site.",
+ "lang_back": "Back",
+ "lang_deleteTAG": "Delete",
+ "lang_editHardcoded": "Edit hardcoded strings",
+ "lang_editMessages": "Edit Messages",
+ "lang_editModules": "Edit Module Translations",
+ "lang_editSettings": "Edit configuration variables related strings",
+ "lang_langAdministration": "Language Administration",
+ "lang_module": "Module",
+ "lang_save": "Save",
+ "lang_status": "Status",
+ "lang_templateAdminHelp": "Here you can translate and edit phrases and texts.",
+ "lang_templateHint": "Hint: Yellow lines indicate a translation is missing and red lines indicate a tag is not being used by the template.",
+ "module_name": "Translation"
+} \ No newline at end of file
diff --git a/modules-available/translation/lang/en/templates/_page.json b/modules-available/translation/lang/en/templates/_page.json
new file mode 100644
index 00000000..5a48b696
--- /dev/null
+++ b/modules-available/translation/lang/en/templates/_page.json
@@ -0,0 +1,9 @@
+{
+ "lang_adminInfo": "In this section you can change the JSON tags that contains the translated texts used by the site. To do this, select a template below to modify their respective tags. Alternatively, click the link below to change the messages of the site.",
+ "lang_editConfigModule": "Edit config module strings",
+ "lang_editHardcoded": "Edit hardcoded strings",
+ "lang_editMessages": "Edit Messages",
+ "lang_editSettings": "Edit configuration variables related strings",
+ "lang_editTemplates": "Edit template strings",
+ "lang_langAdministration": "Language Administration"
+} \ No newline at end of file
diff --git a/modules-available/translation/lang/en/templates/edit.json b/modules-available/translation/lang/en/templates/edit.json
new file mode 100644
index 00000000..05c6697a
--- /dev/null
+++ b/modules-available/translation/lang/en/templates/edit.json
@@ -0,0 +1,12 @@
+{
+ "lang_back": "Back",
+ "lang_createTag": "Create TAG",
+ "lang_deleteTAG": "Delete",
+ "lang_englishTAG": "English TAG",
+ "lang_germanTAG": "German TAG",
+ "lang_newTAG": "New TAG",
+ "lang_portugueseTAG": "Portuguese TAG",
+ "lang_save": "Save",
+ "lang_templateAdminHelp": "Here you can translate and edit phrases and texts.",
+ "lang_templateHint": "Hint: Yellow lines indicate a translation is missing and red lines indicate a tag is not being used by the template."
+} \ No newline at end of file
diff --git a/modules-available/translation/lang/en/templates/template-list.json b/modules-available/translation/lang/en/templates/template-list.json
new file mode 100644
index 00000000..e3feb70b
--- /dev/null
+++ b/modules-available/translation/lang/en/templates/template-list.json
@@ -0,0 +1,5 @@
+{
+ "lang_adminInfo": "This is a list of all templates. The \u0022status\u0022-column tells if there are translations missing, or unused tags are defined.",
+ "lang_langAdministration": "Templates",
+ "lang_status": "Status"
+} \ No newline at end of file
diff --git a/modules-available/translation/lang/pt/module.json b/modules-available/translation/lang/pt/module.json
new file mode 100644
index 00000000..14964c89
--- /dev/null
+++ b/modules-available/translation/lang/pt/module.json
@@ -0,0 +1,16 @@
+{
+ "lang_adminInfo": "Nesta se\u00e7\u00e3o \u00e9 poss\u00edvel alterar as TAGs de JSON que cont\u00e9m os textos traduzidos utilizados no site. Para fazer isto, selecione um template abaixo para modificar suas respectivas TAGs. Alternativamente, clique no link abaixo para alterar as mensagens do site.",
+ "lang_back": "Voltar",
+ "lang_deleteTAG": "Excluir",
+ "lang_editHardcoded": "Editar Textos Hardcoded",
+ "lang_editMessages": "Editar Mensagens",
+ "lang_editModules": "Editar Tradu\u00e7\u00f5es dos M\u00f3dulos",
+ "lang_editSettings": "Editar Textos das Vari\u00e1veis de Configura\u00e7\u00e3o",
+ "lang_langAdministration": "Administra\u00e7\u00e3o da L\u00edngua",
+ "lang_module": "M\u00f3dulo",
+ "lang_save": "Salvar",
+ "lang_status": "Status",
+ "lang_templateAdminHelp": "Aqui voc\u00ea pode traduzir e editar frases e textos.",
+ "lang_templateHint": "Dica: linhas amarelas indicam que uma tradu\u00e7\u00e3o est\u00e1 faltando e linhas vermelhas indicam que uma tag n\u00e3o \u00e9 utilizada pelo template.",
+ "module_name": "Tradu\u00e7\u00f5es"
+} \ No newline at end of file
diff --git a/modules-available/translation/page.inc.php b/modules-available/translation/page.inc.php
new file mode 100644
index 00000000..3548f727
--- /dev/null
+++ b/modules-available/translation/page.inc.php
@@ -0,0 +1,597 @@
+<?php
+
+class Page_Translation extends Page
+{
+
+ /**
+ * The pages where you can administrate the website translations
+ * @var string|boolean holds the target template
+ * @var string|boolean used to choose which page to load
+ * @var string|boolean used check if there should be an update
+ * @var string|boolean used check if there should be a deletion
+ * @var array|boolean holds the tags of the selected template
+ */
+ private $template = false;
+ private $page = false;
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ if (Request::post('update')) {
+ $this->updateJson();
+ Util::redirect('?do=Translation');
+ }
+ if (Request::post('delete')) {
+ $this->deleteTag(Request::post('path'), Request::post('delete'));
+ Util::redirect('?do=Translation'); // TODO: Ajax post for delete so we stay on the page
+ }
+
+ $this->template = Request::get('template');
+ $this->page = Request::get('page');
+ }
+
+ protected function doRender()
+ {
+ $langs = Dictionary::getLanguages(true);
+
+ //load the page accordingly to the link
+ switch ($this->page) {
+ case 'messages':
+ //renders the message edit page
+ Render::addTemplate('edit', array(
+ 'path' => 'messages',
+ 'langs' => $langs,
+ 'tags' => $this->loadMessageEditArray()
+ ));
+ break;
+ case 'hardcoded':
+ //renders the hardcoded messages edit page
+ Render::addTemplate('edit', array(
+ 'path' => 'messages-hardcoded',
+ 'langs' => $langs,
+ 'tags' => $this->loadHardcodedStringEditArray()
+ ));
+ break;
+ case 'settings':
+ //renders the settings related edit page
+ Render::addTemplate('edit', array(
+ 'path' => 'cat_setting',
+ 'langs' => $langs,
+ 'tags' => $this->loadCategoriesArray()
+ ));
+ Render::addTemplate('edit', array(
+ 'path' => 'setting',
+ 'langs' => $langs,
+ 'tags' => $this->loadSettingsArray()
+ ));
+ break;
+ case 'config-module':
+ //renders the hardcoded messages edit page
+ Render::addTemplate('edit', array(
+ 'path' => 'config-module',
+ 'langs' => $langs,
+ 'tags' => $this->buildTranslationTable('config-module')
+ ));
+ break;
+ case 'template':
+ $this->template = Util::safePath($this->template);
+ if ($this->template === false) {
+ Message::addError('invalid-path');
+ Util::redirect('?do=Translation');
+ }
+ //renders the tag edition page
+ Render::addTemplate('edit', array(
+ 'path' => 'modules/' . $this->template,
+ 'langs' => $langs,
+ 'tags' => $this->loadTemplateEditArray($this->template)
+ ));
+ break;
+ case 'templates':
+ //renders the template selection page
+ Render::addTemplate('template-list', array(
+ 'table' => $this->loadTemplatesList(),
+ ));
+ break;
+ case 'modules':
+ Render::addTemplate('module-list', array(
+ 'table' => $this->loadModuleTable()
+ ));
+ break;
+ default:
+ //renders main page with selection of what part to edit
+ Render::addTemplate('_page');
+ }
+ }
+
+ private function loadModuleTable(){
+ $table = array();
+
+ $modules = $this->loadModuleList();
+
+ foreach ($modules as $module) {
+ $msgs = $this->checkModuleTranslation($module);
+ $table[] = array(
+ 'module' => $module,
+ 'status' => $msgs
+ );
+ }
+
+ sort($table);
+ return $table;
+ }
+
+ private function loadModuleEdit(){
+ $table = array();
+ $tags = array_flip($this->loadModuleTags($this->module));
+ foreach ($this->langs as $lang) {
+ $tags = array_merge($tags, Dictionary::getArray($this->module,$lang['cc']));
+ }
+ foreach ($tags as $tag => $value) {
+ $langArray = array();
+ $class = '';
+ foreach ($this->langs as $lang) {
+ $translations = Dictionary::getArray($this->module,$lang['cc']);
+ $langArray[] = array(
+ 'lang' => $lang['cc'],
+ 'placeholder' => 'TAG - ' . $lang['name'],
+ 'translation' => $translations[$tag]
+ );
+ if(!in_array($tag, $this->loadModuleTags($this->module)))
+ $class = 'danger';
+ else if(!$translations[$tag])
+ $class = 'warning';
+ }
+ $table[] = array(
+ 'tag' => $tag,
+ 'class' => $class,
+ 'langs' => $langArray
+ );
+ }
+
+ return $table;
+ }
+
+ private function loadModuleList(){
+ // Return an array with the modules and the tags data
+ $list = array();
+ $list = array_diff(scandir('modules/'), array('..', '.'));
+ return $list;
+ }
+
+ private function loadModuleTags($module){
+ // Return an array with the module language tags
+ $path = "modules/" . $module . "templates/";
+ $files = array_diff(scandir($path), array('..', '.'));
+ $tags = array();
+ foreach ($files as $file) {
+ $content = file_get_contents($path . $file);
+ preg_match_all('/{{(lang_.*?)}}/s', $content, $matches);
+ if (isset($matches[1]) && is_array($matches[1])){
+ $tags = array_merge($tags,array_unique($matches[1]));
+ }
+ }
+ return array_unique($tags);
+ }
+
+ private function checkModuleTranslation($module){
+ $tags = $this->loadModuleTags($module);
+ $translation = array();
+ $msgs = '';
+ foreach ($this->langs as $key => $lang) {
+ $translation = Dictionary::getArray($module,$lang['cc']);
+ $matches = 0;
+ $unused = 0;
+ $expected = count($tags);
+ foreach ($translation as $key => $value) {
+ if(!in_array($key, $tags))
+ $unused ++;
+ else if(!empty($value))
+ $matches ++;
+
+ }
+
+ $diff = $expected - $matches;
+ $msg = "";
+ if ($diff > 0)
+ $msg .= $diff . " JSON tag(s) are missing";
+ if ($diff > 0 && $unused > 0)
+ $msg .= "<br>";
+ if ($unused > 0)
+ $msg .= $unused . " JSON tag(s) are not being used";
+ if(!empty($msg))
+ $msgs .= "<div><span class='badge'>{$lang['name']}:</span> $msg</div>";
+ }
+ if(empty($msgs))
+ $msgs = 'OK';
+ return $msgs;
+ }
+ /**
+ * Load the main table with all the website's templates and it's informations
+ * @return array with the templates' information
+ */
+ private function loadTemplatesList()
+ {
+ $table = array();
+
+ //loads every template
+ $files = $this->listTemplates();
+ $langs = Dictionary::getLanguages(true);
+
+ //checks the JSON tags from every language
+ foreach ($files as $file) {
+ $tags = $this->loadTemplateTags($file['path']);
+ // Don't list templates without lang tags
+ if (empty($tags))
+ continue;
+ $msgs = '';
+ foreach ($langs as $lang) {
+ $msg = $this->checkJson($file['path'], $lang['cc'], $tags);
+ if (!empty($msg))
+ $msgs .= "<div><span class='badge'>{$lang['name']}:</span>$msg</div>";
+ }
+ if (empty($msgs))
+ $msgs = 'OK';
+ $table[] = array(
+ 'template' => $file['name'],
+ 'link' => $file['name'],
+ 'status' => $msgs
+ );
+ }
+ sort($table);
+ return $table;
+ }
+
+ /**
+ * Finds and returns all the website's templates
+ * @return array
+ */
+ private function listTemplates()
+ {
+ $files = array();
+ $dir = 'modules/';
+ $objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
+ foreach ($objects as $name => $object) {
+ if (substr($name, -5) === '.html') {
+ $files[] = array(
+ 'path' => substr($name, 0, -5),
+ 'name' => substr($name, strlen($dir), -5)
+ );
+ }
+ }
+ return $files;
+ }
+
+ /**
+ * Finds and returns all PHP files of slxadmin
+ * @return array of all php files
+ */
+ private function listPhp()
+ {
+ $php = array();
+ $dir = '.';
+ $objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
+ foreach ($objects as $name => $object) {
+ if (substr($name, -4) === '.php') {
+ $php[] = $name;
+ }
+ }
+ return $php;
+ }
+
+ /**
+ * Checks the JSON tags from a template
+ * @param string the template's path
+ * @param string the selected language
+ * @param string tags that should be in the json file
+ * @return string|boolean the information about the JSON tags, false if template has no lang-tags
+ */
+ private function checkJson($path, $lang, $expectedTags)
+ {
+ //if there was not a valid template's path
+ if (!$path) {
+ return "Translation missing";
+ }
+ // How many tags do we expect in the translation
+ $htmlCount = count($expectedTags);
+
+ //initialize the count variables
+ $matchCount = 0;
+ $unusedCount = 0;
+
+ //loads the JSON tags and count the matches
+ $json = Dictionary::getArray(substr($path, strlen("modules/")), $lang);
+ //return print_r($json) . "\nvs\n" . print_r($expectedTags);
+ foreach ($json as $key => $value) {
+ if (!in_array($key, $expectedTags)) {
+ $unusedCount++;
+ } else if (!empty($value)) {
+ $matchCount++;
+ }
+ }
+ $diff = $htmlCount - $matchCount;
+
+ if ($diff == 0 && $unusedCount == 0)
+ return '';
+ //build the return string
+ $str = "";
+ if ($diff > 0)
+ $str .= $diff . " JSON tag(s) are missing";
+ if ($diff > 0 && $unusedCount > 0)
+ $str .= "<br>";
+ if ($unusedCount > 0)
+ $str .= $unusedCount . " JSON tag(s) are not being used";
+ return $str;
+ }
+
+ /**
+ * Get array to pass to edit page with all the tags and translations for the given template
+ * @param string $path the template's path
+ * @return array the information about the JSON tags
+ */
+ private function loadTemplateEditArray($path)
+ {
+ $tags = $this->loadTemplateTags($path);
+ if ($tags === false)
+ return false;
+ return $this->buildTranslationTable("modules/" . $path, $tags);
+ }
+
+ /**
+ * Load array of tags used in given template.
+ * @param string $path the path of the template, relative to templates/, without .html extension.
+ * @return array all tags in template
+ */
+ private function loadTemplateTags($path)
+ {
+ $templateFile = "$path.html";
+ //checks if the template is valid
+ if (!file_exists($templateFile)) {
+ Message::addError('invalid-template', $templateFile);
+ return false;
+ }
+
+ //finds every mustache tag within the html template
+ $htmlTemplate = file_get_contents($templateFile);
+ preg_match_all('/{{(lang_.*?)}}/s', $htmlTemplate, $matches);
+ if (!isset($matches[1]) || !is_array($matches[1]))
+ return array();
+ return array_unique($matches[1]);
+ }
+
+ /**
+ * Load array of tags and translations of all messages
+ * @return array the information about the JSON tags
+ */
+ private function loadMessageEditArray()
+ {
+ $tags = $this->loadTagsFromPhp('/Message\s*::\s*add\w+\s*\(\s*[\'"](.*?)[\'"]\s*[\)\,]/i');
+ if ($tags === false)
+ return false;
+ return $this->buildTranslationTable('messages', $tags);
+ }
+
+ /**
+ * Load array of tags and translations of all strings found in the php files.
+ * @return array the information about the JSON tags
+ */
+ private function loadHardcodedStringEditArray()
+ {
+ $tags = $this->loadTagsFromPhp('/Dictionary\s*::\s*translate\s*\(\s*[\'"]([^\'"]*?)[\'"]\s*\)/i');
+ if ($tags === false)
+ return false;
+ return $this->buildTranslationTable('messages-hardcoded', $tags);
+ }
+
+ /**
+ * Load array of tags used in all the php files, by given regexp. Capture group 1 should return
+ * the exact tag name.
+ * @param string $regexp regular expression matching all tags in capture group 1
+ * @return array of all tags found
+ */
+ private function loadTagsFromPhp($regexp)
+ {
+ // Get all php files, so we can find all strings that need to be translated
+ $php = $this->listPhp();
+ $tags = array();
+ // Now find all tags in all php files. Only works for literal usage, not something like $foo = 'bar'; Dictionary::translate($foo);
+ foreach ($php as $file) {
+ $content = @file_get_contents($file);
+ if ($content === false || preg_match_all($regexp, $content, $out) < 1)
+ continue;
+ foreach ($out[1] as $id) {
+ $tags[$id] = true;
+ }
+ }
+ return array_keys($tags);
+ }
+
+ private function buildTranslationTable($path, $requiredTags = false)
+ {
+ // All languages
+ $langArray = Dictionary::getLanguages();
+
+ $tags = array();
+ if ($requiredTags !== false) {
+ foreach ($requiredTags as $tagName) {
+ $tags[$tagName] = array('tag' => $tagName);
+ foreach ($langArray as $lang) {
+ $tags[$tagName]['langs'][$lang]['lang'] = $lang;
+ $tags[$tagName]['missing'] = count($langArray);
+ }
+ }
+ }
+ // Finds every JSON tag within the JSON language files
+ foreach ($langArray as $lang) {
+ $jsonTags = Dictionary::getArray($path, $lang, true);
+ if (!is_array($jsonTags))
+ continue;
+ foreach ($jsonTags as $tag => $translation) {
+ $tags[$tag]['langs'][$lang]['translation'] = $translation;
+ $tags[$tag]['langs'][$lang]['lang'] = $lang;
+ if (strpos($translation, "\n") !== false)
+ $tags[$tag]['langs'][$lang]['big'] = true;
+ $tags[$tag]['tag'] = $tag;
+ if (!isset($tags[$tag]['missing']))
+ $tags[$tag]['missing'] = 0;
+ if (!empty($translation))
+ $tags[$tag]['missing'] --;
+ }
+ }
+ // Fill the blanks
+ foreach ($langArray as $lang) {
+ foreach (array_keys($tags) as $tagName) {
+ if (!isset($tags[$tagName]['langs'][$lang]))
+ $tags[$tagName]['langs'][$lang]['lang'] = $lang;
+ }
+ }
+ // Finally remove $lang from the keys so mustache will iterate over them via {{#..}}
+ foreach ($tags as &$tag) {
+ $tag['langs'] = array_values($tag['langs']);
+ if ($requiredTags !== false)
+ $tag['class'] = $this->getTagColor($tag['missing']);
+ }
+ return array_values($tags);
+ }
+
+ /**
+ * Change the color of the table line according to the tag status
+ * @param string the JSON's path
+ * @param string the selected tag
+ * @return string the css class of the line
+ */
+ private function getTagColor($missingCount)
+ {
+ //return danger in case the tag is not found in the template
+ if ($missingCount < 0)
+ return 'danger';
+
+ //return warning in case at least one of the tag's values is empty
+ if ($missingCount > 0)
+ return 'warning';
+ //if it's ok don't change the class
+ return '';
+ }
+
+ /**
+ * Updates a JSON file with it's new tags or/and tags values
+ */
+ private function updateJson()
+ {
+ $langArray = Dictionary::getLanguages();
+ foreach ($langArray as $lang) {
+ $json[$lang] = array();
+ }
+
+ //find the tag requests to change the file
+ foreach ($_POST as $key => $value) {
+ $str = explode('#', $key, 3);
+ if (count($str) !== 3 || $str[0] !== 'lang')
+ continue;
+ $lang = $str[1];
+ $tag = trim($str[2]);
+ if (!isset($json[$lang])) {
+ Message::addWarning('i18n-invalid-lang', $lang);
+ continue;
+ }
+ if (empty($tag)) {
+ Message::addWarning('i18n-empty-tag');
+ continue;
+ }
+ $value = trim($value);
+ if ($tag !== 'newtag') {
+ if (empty($value)) {
+ unset($json[$lang][$tag]);
+ } else {
+ $json[$lang][$tag] = $value;
+ }
+ } else {
+ if (!empty($value)) // TODO: Error message if new tag's name collides with existing
+ $json[$lang][$_REQUEST['newtag']] = $value;
+ }
+ }
+
+ // JSON_PRETTY_PRINT is only available starting with php 5.4.0.... Use upgradephp's json_encode
+ require_once('inc/up_json_encode.php');
+
+ //saves the new values on the file
+ foreach ($json as $key => $array) {
+ $path = Util::safePath('lang/' . $key . '/' . Request::post('path') . '.json');
+ if ($path === false) {
+ Message::addError('invalid-path');
+ Util::redirect('?do=Translation');
+ }
+ @mkdir(dirname($path), 0755, true);
+ ksort($array); // Sort by key, so the diff on the output is cleaner
+ $json = up_json_encode($array, JSON_PRETTY_PRINT); // Also for better diffability of the json files, we pretty print
+ //exits the function in case the action was unsuccessful
+ if (@file_put_contents($path, $json) === false) {
+ Message::addError('invalid-template');
+ return;
+ }
+ }
+ Message::addSuccess('updated-tags');
+ }
+
+ /**
+ * Delete a specific JSON tag from a JSON files
+ * @var string the JSON's file path
+ * @var the JSON tag to be deleted
+ * @return boolean if the action was not successful
+ */
+ private function deleteTag($path, $tag)
+ {
+ // JSON_PRETTY_PRINT is only available starting with php 5.4.0.... Use upgradephp's json_encode
+ require_once('inc/up_json_encode.php');
+
+ //delete the tag from every language file
+ $langArray = Dictionary::getLanguages();
+ foreach ($langArray as $lang) {
+ $json = Dictionary::getArray($path, $lang);
+ unset($json[$tag]);
+ $result = file_put_contents('lang/' . $lang . '/' . $path . '.json', up_json_encode($json, JSON_PRETTY_PRINT));
+ //add warning and exit in case the action was unsuccessful
+ if ($result === false) {
+ Message::addWarning('unsuccessful-action');
+ return false;
+ }
+ }
+ Message::addSuccess('deleted-tag');
+ }
+
+ /**
+ * Load all settings categories for editing.
+ *
+ * @return array
+ */
+ private function loadCategoriesArray()
+ {
+ $want = array();
+ $res = Database::simpleQuery("SELECT catid FROM cat_setting ORDER BY catid ASC");
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $want[] = 'cat_' . $row['catid'];
+ }
+ return $this->buildTranslationTable('settings/cat_setting', $want);
+ }
+
+ /**
+ * Load all settings categories for editing.
+ *
+ * @return array
+ */
+ private function loadSettingsArray()
+ {
+ $want = array();
+ $res = Database::simpleQuery("SELECT setting FROM setting ORDER BY setting ASC");
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $want[] = $row['setting'];
+ }
+ return $this->buildTranslationTable('settings/setting', $want);
+ }
+
+}
diff --git a/modules-available/translation/templates/_page.html b/modules-available/translation/templates/_page.html
new file mode 100644
index 00000000..52a4c94b
--- /dev/null
+++ b/modules-available/translation/templates/_page.html
@@ -0,0 +1,17 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_langAdministration}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_adminInfo}}</p>
+ <ul class="nav nav-pills nav-stacked">
+ <!-- new modules page ??? -->
+ <li><a href="?do=Translation&amp;page=modules">{{lang_editModules}}</a></li>
+ <li><a href="?do=Translation&amp;page=templates">{{lang_editTemplates}}</a></li>
+ <li><a href="?do=Translation&amp;page=messages">{{lang_editMessages}}</a></li>
+ <li><a href="?do=Translation&amp;page=hardcoded">{{lang_editHardcoded}}</a></li>
+ <li><a href="?do=Translation&amp;page=settings">{{lang_editSettings}}</a></li>
+ <li><a href="?do=Translation&amp;page=config-module">{{lang_editConfigModule}}</a></li>
+ </ul>
+ </div>
+</div>
diff --git a/modules-available/translation/templates/edit.html b/modules-available/translation/templates/edit.html
new file mode 100644
index 00000000..dc01deb6
--- /dev/null
+++ b/modules-available/translation/templates/edit.html
@@ -0,0 +1,71 @@
+<div class="container">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{path}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_templateAdminHelp}}</p>
+ <div class="alert alert-info">
+ {{lang_templateHint}}
+ </div>
+ <form action="?do=Translation" method="post">
+ <input type="hidden" name="path" value="{{path}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <a class="btn btn-primary" href='?do=Translation' >{{lang_back}}</a>
+ <button class="btn btn-primary" type="button" onclick="addTag()" >{{lang_createTag}}</button>
+ <button type="submit" class="btn btn-primary" name="update" value="true">{{lang_save}}</button>
+ <table class="table table-hover">
+ <thead>
+ <tr>
+ <th>Mustache Tag</th>
+ {{#langs}}
+ <th>{{name}}</th>
+ {{/langs}}
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#tags}}
+ <tr class="{{class}}" id="{{tag}}">
+ <td>{{tag}}</td>
+ {{#langs}}
+ <td>
+ {{^big}}
+ <input type="text" class="form-control switchable" value="{{translation}}" ondblclick="mb(this)" name="lang#{{lang}}#{{tag}}">
+ {{/big}}
+ {{#big}}
+ <textarea rows="3" class="form-control" name="lang#{{lang}}#{{tag}}">{{translation}}</textarea>
+ {{/big}}
+ </td>
+ {{/langs}}
+ <td>
+ <button type="submit" class="btn btn-danger btn-xs" name="delete" value="{{tag}}"><span class="glyphicon glyphicon-remove"></span> {{lang_deleteTAG}}</button>
+ </td>
+ </tr>
+ {{/tags}}
+ <tr id="newTag">
+ </tr>
+ </tbody>
+ </table>
+ <a class="btn btn-primary" href='?do=Translation' >{{lang_back}}</a>
+ <button class="btn btn-primary" type="button" onclick="addTag()" >{{lang_createTag}}</button>
+ <button type="submit" class="btn btn-primary" name="update" value="true">{{lang_save}}</button>
+ </form>
+ </div>
+ </div>
+</div>
+<script type="text/javascript">
+ function addTag()
+ {
+ var target = document.getElementById('newTag');
+ target.innerHTML = "<td> <input type='text' class='form-control' placeholder='{{lang_newTAG}}' name='newtag'> </td> <td style='width:250px;text-align:center;'> <input type='text' class='form-control' placeholder='{{lang_germanTAG}}' name='lang#de#newtag'> </td> <td style='width:250px;text-align:center;'> <input type='text' class='form-control' placeholder='{{lang_englishTAG}}' name='lang#en#newtag'> </td> <td style='width:250px;text-align:center;'> <input type='text' class='form-control' placeholder='{{lang_portugueseTAG}}' name='lang#pt#newtag'> </td><td></td>";
+ }
+
+ function mb(el)
+ {
+ var old = $(el);
+ var ta = $('<textarea name="' + el.name + '" class="form-control" rows="3"></textarea>');
+ ta.val(old.val());
+ old.replaceWith(ta);
+ }
+</script>
diff --git a/modules-available/translation/templates/module-list.html b/modules-available/translation/templates/module-list.html
new file mode 100644
index 00000000..037a21bc
--- /dev/null
+++ b/modules-available/translation/templates/module-list.html
@@ -0,0 +1,32 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_langAdministration}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_adminInfo}}</p>
+ </div>
+</div>
+<div class="panel panel-default">
+ <table class="table table-hover">
+ <thead>
+ <tr>
+ <th width="350">{{lang_module}}</th>
+ <th>{{lang_status}}</th>
+ <tr>
+ </thead>
+ <tbody>
+ {{#table}}
+ <tr onclick="goTo('{{module}}');">
+ <td>{{module}}</td>
+ <td>{{{status}}}</td>
+ </tr>
+ {{/table}}
+ </tbody>
+ </table>
+</div>
+
+<script>
+function goTo(link){
+ window.location.href = "?do=Translation&page=module&module=" + link;
+}
+</script>
diff --git a/modules-available/translation/templates/template-list.html b/modules-available/translation/templates/template-list.html
new file mode 100644
index 00000000..881fc5af
--- /dev/null
+++ b/modules-available/translation/templates/template-list.html
@@ -0,0 +1,32 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_langAdministration}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_adminInfo}}</p>
+ </div>
+</div>
+<div class="panel panel-default">
+ <table class="table table-hover">
+ <thead>
+ <tr>
+ <th>Template</th>
+ <th>{{lang_status}}</th>
+ <tr>
+ </thead>
+ <tbody>
+ {{#table}}
+ <tr onclick="goTo('{{link}}');">
+ <td>{{template}}</td>
+ <td>{{{status}}}</td>
+ </tr>
+ {{/table}}
+ </tbody>
+ </table>
+</div>
+
+<script>
+function goTo(link){
+ window.location.href = "?do=Translation&page=template&template=" + link;
+}
+</script>
diff --git a/modules-available/usermanagement/config.json b/modules-available/usermanagement/config.json
new file mode 100644
index 00000000..b9fc3ad3
--- /dev/null
+++ b/modules-available/usermanagement/config.json
@@ -0,0 +1,5 @@
+{
+ "category":"main.users",
+ "enabled":"true",
+ "permission":"0"
+}
diff --git a/modules-available/usermanagement/lang/en/module.json b/modules-available/usermanagement/lang/en/module.json
new file mode 100644
index 00000000..a8114ffc
--- /dev/null
+++ b/modules-available/usermanagement/lang/en/module.json
@@ -0,0 +1,19 @@
+{
+ "lang_cancelConfirm": "Do you really want to delete this users?",
+ "lang_close": "Close",
+ "lang_create": "Create",
+ "lang_edit": "Edit",
+ "lang_editUser": "Edit User",
+ "lang_email": "Email",
+ "lang_login": "Login",
+ "lang_operations": "Operation",
+ "lang_password": "Password",
+ "lang_remove": "Remove",
+ "lang_save": "Save",
+ "lang_telephone": "Telephone",
+ "lang_userAdmin": "Administrator",
+ "lang_userInfo": "On this section, you will be able to create website users, besides editing or removing existing users.",
+ "lang_userPage": "Users",
+ "lang_username": "Username",
+ "module_name": "Management"
+} \ No newline at end of file
diff --git a/modules-available/usermanagement/lang/en/templates/user-management.json b/modules-available/usermanagement/lang/en/templates/user-management.json
new file mode 100644
index 00000000..61225f55
--- /dev/null
+++ b/modules-available/usermanagement/lang/en/templates/user-management.json
@@ -0,0 +1,18 @@
+{
+ "lang_cancelConfirm": "Do you really want to delete this users?",
+ "lang_close": "Close",
+ "lang_create": "Create",
+ "lang_edit": "Edit",
+ "lang_editUser": "Edit User",
+ "lang_email": "Email",
+ "lang_login": "Login",
+ "lang_operations": "Operation",
+ "lang_password": "Password",
+ "lang_remove": "Remove",
+ "lang_save": "Save",
+ "lang_telephone": "Telephone",
+ "lang_userAdmin": "Administrator",
+ "lang_userInfo": "On this section, you will be able to create website users, besides editing or removing existing users.",
+ "lang_userPage": "Users",
+ "lang_username": "Username"
+} \ No newline at end of file
diff --git a/modules-available/usermanagement/lang/pt/module.json b/modules-available/usermanagement/lang/pt/module.json
new file mode 100644
index 00000000..d892e5c5
--- /dev/null
+++ b/modules-available/usermanagement/lang/pt/module.json
@@ -0,0 +1,19 @@
+{
+ "lang_cancelConfirm": "Deseja realmente remover o usu\u00e1rio?",
+ "lang_close": "Fechar",
+ "lang_create": "Criar",
+ "lang_edit": "Editar",
+ "lang_editUser": "Editar Usu\u00e1rio",
+ "lang_email": "Email",
+ "lang_login": "Login",
+ "lang_operations": "Opera\u00e7\u00f5es",
+ "lang_password": "Senha",
+ "lang_remove": "Remover",
+ "lang_save": "Salvar",
+ "lang_telephone": "Telefone",
+ "lang_userAdmin": "Administrador",
+ "lang_userInfo": "Nesta se\u00e7\u00e3o voc\u00ea poder\u00e1 criar usu\u00e1rios para o site, al\u00e9m de editar as informa\u00e7\u00f5es ou remover usu\u00e1rios existentes.",
+ "lang_userPage": "Usu\u00e1rios",
+ "lang_username": "Nome do Usu\u00e1rio",
+ "module_name": "Gerenciamento"
+} \ No newline at end of file
diff --git a/modules-available/usermanagement/page.inc.php b/modules-available/usermanagement/page.inc.php
new file mode 100644
index 00000000..01b5f755
--- /dev/null
+++ b/modules-available/usermanagement/page.inc.php
@@ -0,0 +1,109 @@
+<?php
+
+class Page_Usermanagement extends Page
+{
+ private $page;
+ private $deb;
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ $p = Request::get('page');
+ if($p != false)
+ $this->page = $p;
+ else
+ $this->page = 1;
+
+ switch(Request::post('action')){
+ case "editAdmin":
+ $this->edit(Request::post('userid'),Request::post('username'),Request::post('phone'),Request::post('email'), 1);
+ break;
+ case "edit":
+ $this->edit(Request::post('userid'),Request::post('username'),Request::post('phone'),Request::post('email'), 4);
+ break;
+ case "create":
+ $this->create(Request::post('login'),Request::post('username'),Request::post('pass'),Request::post('phone'),Request::post('email'), 4);
+ break;
+ case "delete":
+ $this->delete(Request::post('userid'));
+ break;
+ }
+
+ if(isset($_POST['userid']))
+ $this->deb = $_POST['userid'];
+
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ }
+
+ protected function doRender()
+ {
+ // load every user
+ $admin = array();
+ $users = array();
+ $res = Database::simpleQuery("SELECT userid, login, fullname, phone, email, permissions FROM user ORDER BY userid DESC");
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if ($row['permissions'] == 1 )
+ $admin = array($row['userid'],$row['login'],$row['fullname'],$row['phone'],$row['email']);
+ else
+ $users[] = array(
+ 'id' => $row['userid'],
+ 'username' => $row['login'],
+ 'name' => $row['fullname'],
+ 'telephone' => $row['phone'],
+ 'email' => $row['email']
+ );
+ }
+
+ //$pag = new Paginate($users,$this->page);
+
+ Render::addTemplate('user-management', array(
+ 'admin_id' => $admin[0],
+ 'admin_username' => $admin[1],
+ 'admin_name' => $admin[2],
+ 'admin_telephone' => $admin[3],
+ 'admin_email' => $admin[4]
+ //'users' => $pag->getItems(),
+ //'pages' => $pag->getPagination()
+ ));
+ }
+
+ private function edit($userid, $newname, $newphone, $newemail, $newpermissions){
+ $data = array (
+ 'user' => $userid,
+ 'name' => $newname,
+ 'phone' => $newphone,
+ 'email' => $newemail,
+ 'permissions' => $newpermissions
+ );
+ Database::exec ( 'UPDATE user SET fullname = :name, phone = :phone, email = :email, permissions = :permissions WHERE userid = :user', $data );
+ Message::addSuccess('update-user');
+ }
+
+ private function create($login, $username, $password, $phone, $email){
+ $data = array (
+ 'login' => $login,
+ 'pass' => Crypto::hash6 ( $password ),
+ 'name' => $username,
+ 'phone' => $phone,
+ 'email' => $email
+ );
+ User::addUser($data);
+ }
+
+ private function delete($userid){
+ $data = array (
+ 'userid' => $userid
+ );
+ Database::exec ( 'DELETE FROM setting_partition WHERE user = :userid', $data );
+ Database::exec ( 'DELETE FROM setting_user WHERE user = :userid', $data );
+ Database::exec ( 'DELETE FROM setting_values WHERE user = :userid', $data );
+ Database::exec ( 'DELETE FROM user WHERE userid = :userid', $data );
+ Message::addSuccess('delete-user');
+ }
+
+}
diff --git a/modules-available/usermanagement/templates/user-management.html b/modules-available/usermanagement/templates/user-management.html
new file mode 100644
index 00000000..fc14355f
--- /dev/null
+++ b/modules-available/usermanagement/templates/user-management.html
@@ -0,0 +1,127 @@
+<div class="panel panel-primary">
+ <div class="panel-heading" style="background-image: none;" >
+ <div class="panel-title">{{lang_userAdmin}}</div>
+ </div>
+ <table class="table">
+ <tr>
+ <th style="text-align: center;">ID</th>
+ <th style="text-align: center;">{{lang_login}}</th>
+ <th style="text-align: center;">{{lang_username}}</th>
+ <th style="text-align: center;">{{lang_telephone}}</th>
+ <th style="text-align: center;">{{lang_email}}</th>
+ <th style="text-align: center;"></th>
+ </tr>
+ <tr>
+ <form method="post" action="">
+ <input type="hidden" name="action" value="editAdmin">
+ <input type="hidden" name="token" value="{{token}}">
+ <td><input class="form-control" name="userid" type="text" readonly="readonly" value="{{admin_id}}" size="3"></td>
+ <td><input class="form-control" type="text" disabled="disabled" value="{{admin_username}}"></td>
+ <td><input class="form-control" name="username" type="text" value="{{admin_name}}"></td>
+ <td><input class="form-control" name="phone" type="text" value="{{admin_telephone}}"></td>
+ <td><input class="form-control" name="email" type="text" value="{{admin_email}}"></td>
+ <td><input class="btn btn-primary" type="submit" value="{{lang_save}}"></td>
+ </form>
+ </tr>
+ </table>
+</div>
+
+<div class="panel panel-default" style="border-color:#333;">
+ <div class="panel-heading" style="border-color:#333;background-color:#333;background-image: none;color:#FFF;">
+ <div class="panel-title">{{lang_userPage}}</div>
+ </div>
+ <div class="panel-body">
+ <p>
+ {{lang_userInfo}}
+ </p>
+
+ </div>
+ <table class="table">
+ <tr>
+ <form method="post" action="?do=Usermanagement">
+ <input type="hidden" name="action" value="create">
+ <input type="hidden" name="token" value="{{token}}">
+ <td><input class="form-control" name="login" type="text" placeholder="{{lang_login}}"></td>
+ <td><input class="form-control" name="username" type="text" placeholder="{{lang_username}}"></td>
+ <td><input class="form-control" name="pass" type="password" placeholder="{{lang_password}}"></td>
+ <td><input class="form-control" name="phone" type="text" placeholder="{{lang_telephone}}"></td>
+ <td><input class="form-control" name="email" type="text" placeholder="{{lang_email}}"></td>
+ <td><input class="btn btn-primary" type="submit" value="{{lang_create}}"></td>
+ </form>
+ </tr>
+ <tr>
+ <th style="text-align: center;">ID</th>
+ <th style="text-align: center;">{{lang_login}}</th>
+ <th colspan="4">{{lang_operations}}</th>
+ </tr>
+ {{#users}}
+ <tr>
+ <td><input class="form-control" type="text" disabled="disabled" value="{{id}}" size="3"></td>
+ <td><input class="form-control" type="text" disabled="disabled" value="{{username}}"></td>
+ <td colspan="4">
+ <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#usr{{id}}"><span class="glyphicon glyphicon-edit" aria-hidden="true"></span> {{lang_edit}}</button>
+ <form method="post" action="?do=Usermanagement" style="display:inline-block;">
+ <input type="hidden" name="action" value="delete">
+ <input type="hidden" name="userid" value="{{id}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <button class="btn btn-danger" type="submit" onclick="return confirm('{{lang_cancelConfirm}}');"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> {{lang_remove}}</a>
+ </form>
+ </td>
+ </tr>
+ {{/users}}
+ <tr>
+ <td colspan="6">
+ <ul class="pagination" style="margin:10px 0;">
+ {{#pages}}
+ <li class="{{class}}"><a href="?do=Usermanagement&page={{page}}">{{page}}</a></li>
+ {{/pages}}
+ </ul>
+ </td>
+ </tr>
+ </table>
+</div>
+{{#users}}
+<div class="modal fade" id="usr{{id}}" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header"><h4 class="modal-title">{{lang_editUser}}</h4></div>
+ <div class="modal-body">
+ <form method="post" action="">
+ <input type="hidden" name="action" value="edit">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="form-group">
+ <label for="userid">ID</label>
+ <input type="text" class="form-control" name="userid" readonly="readonly" name="userid" value="{{id}}">
+ </div>
+ <div class="form-group">
+ <label for="userid">Login</label>
+ <input type="text" class="form-control" name="login" disabled="disabled" placeholder="{{lang_login}}" value="{{username}}">
+ </div>
+ <div class="form-group">
+ <label for="userid">{{lang_username}}</label>
+ <input type="text" class="form-control" name="username" placeholder="{{lang_username}}" value="{{name}}">
+ </div>
+ <div class="form-group">
+ <label for="userid">{{lang_telephone}}</label>
+ <input type="text" class="form-control" name="phone" placeholder="{{lang_telephone}}" value="{{telephone}}">
+ </div>
+ <div class="form-group">
+ <label for="userid">{{lang_email}}</label>
+ <input type="text" class="form-control" name="email" placeholder="{{lang_email}}" value="{{email}}">
+ </div>
+ <div class="form-group">
+ <b>Permissões:</b>
+ <select name="permissions" class="form-control">
+ <option value="4">Normal</option>
+ </select>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <input class="btn btn-primary" type="submit" value="{{lang_save}}">
+ <a class="btn btn-default" data-dismiss="modal">{{lang_close}}</a>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
+{{/users}} \ No newline at end of file
diff --git a/modules-available/vmstore/config.json b/modules-available/vmstore/config.json
new file mode 100644
index 00000000..f2abe27c
--- /dev/null
+++ b/modules-available/vmstore/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.settings",
+ "enabled":"true"
+}
diff --git a/modules-available/vmstore/lang/de/templates/mount.json b/modules-available/vmstore/lang/de/templates/mount.json
new file mode 100644
index 00000000..dbc75281
--- /dev/null
+++ b/modules-available/vmstore/lang/de/templates/mount.json
@@ -0,0 +1,5 @@
+{
+ "lang_back": "Zur\u00fcck",
+ "lang_configure": "Konfigurieren",
+ "lang_vmLocationConfiguration": "VM Speicherort wird konfiguriert"
+} \ No newline at end of file
diff --git a/modules-available/vmstore/lang/de/templates/page-vmstore.json b/modules-available/vmstore/lang/de/templates/page-vmstore.json
new file mode 100644
index 00000000..bb2c0634
--- /dev/null
+++ b/modules-available/vmstore/lang/de/templates/page-vmstore.json
@@ -0,0 +1,17 @@
+{
+ "lang_close": "Schlie\u00dfen",
+ "lang_intern": "Intern",
+ "lang_nfsHelp1": "Ben\u00f6tigt wird ein NFSv4\/3-Share, der f\u00fcr den Satelliten-Server schreibbar, und f\u00fcr die Arbeitsstationen lesbar ist. Beispielkonfiguration auf dem NFS-Server, wenn der Satelliten-Server die Adresse 1.2.3.4 hat:",
+ "lang_nfsHelp2": "Alternative Konfiguration mittels all_sqash. In diesem Fall muss das Verzeichnis auf dem Server dem Benutzer mit der uid 1234 geh\u00f6ren:",
+ "lang_noAdditionalInformation": "Keine weitere Konfiguration notwendig",
+ "lang_password": "Passwort",
+ "lang_readOnly": "Nur-Lese-Zugangsdaten",
+ "lang_readWrite": "Lese\/Schreib-Zugangsdaten",
+ "lang_save": "Speichern",
+ "lang_username": "Benutzerkennung",
+ "lang_vmLocation": "VM Speicherort",
+ "lang_vmLocationChoose": "Bitte w\u00e4hlen Sie, wo die Images der Virtuellen Maschinen gespeichert werden sollen.",
+ "lang_vmLocationHelp1": "F\u00fcr Testzwecke k\u00f6nnen die VMs direkt auf dem Satellitenserver gespeichert werden. Sofern Sie jedoch die ausgelieferte Satelliten-vmdk betreiben bedenken Sie bitte, dass Sie dann nur ca. 100GB Speicher zur Verf\u00fcgung haben.",
+ "lang_vmLocationHelp2": "Im Produktivbetrieb bietet es sich an, hierf\u00fcr einen performanten Netzwerkspeicher zu benutzen. Dieser Netzwerkspeicher kann per NFS oder CIFS\/SMB eingebunden werden. In jedem Fall muss sichergestellt werden, dass der Satellitenserver zum Hinzuf\u00fcgen neuer Virtueller Maschinen Schreibzugriff auf diesen Netzwerkspeicher hat. Bei der Nutzung von NFSv3 kann dies IP-Basiert eingerichtet werden, f\u00fcr die Nutzung von CIFS\/SMB k\u00f6nnen Sie Zugangsdaten angaben, die zum Schreiben berechtigen.",
+ "lang_vmLocationHelp3": "Die bwLehrpool-Clients brauchen lediglich Lesezugriff auf den Netzwerkspeicher (und sollten aus Sicherheitsgr\u00fcnden auch wirklich nur lesen k\u00f6nnen). Bei CIFS\/SMB erreichen Sie dies am einfachsten, indem Sie passwortlosen Gastzugriff mit Leserechten auf die Freigabe erlauben."
+} \ No newline at end of file
diff --git a/modules-available/vmstore/lang/en/module.json b/modules-available/vmstore/lang/en/module.json
new file mode 100644
index 00000000..9d11656f
--- /dev/null
+++ b/modules-available/vmstore/lang/en/module.json
@@ -0,0 +1,21 @@
+{
+ "lang_back": "Back",
+ "lang_close": "Close",
+ "lang_configure": "Configure",
+ "lang_intern": "Intern",
+ "lang_nfsHelp1": "An NFSv3-Share is required. It should be readable by all the workstations, and writable for the satellite server. An example, assuming the satellite server has IP address 1.2.3.4:",
+ "lang_nfsHelp2": "Alternate configuration using all_squash. The exported directory should be owned (and be writable) by the user with uid 1234.",
+ "lang_noAdditionalInformation": "No additional cofiguration required",
+ "lang_password": "Password",
+ "lang_readOnly": "Read-only Access",
+ "lang_readWrite": "Read\/Write Access",
+ "lang_save": "Save",
+ "lang_username": "Username",
+ "lang_vmLocation": "VM Location",
+ "lang_vmLocationChoose": "Please choose where the images of virtual machines will be stored.",
+ "lang_vmLocationConfiguration": "VM Location is being configured",
+ "lang_vmLocationHelp1": "For test purposes, the VMs can be stored directly on the Satellite server. However, if you operate the delivered satellite vmdk please remember that you have only about 100GB of memory.",
+ "lang_vmLocationHelp2": "In productive operation, it makes sense for this to use a high-performance network storage. This network storage can be integrated via NFS or CIFS \/ SMB. In any case, it must be ensured that the satellite server has write access to this network storage to add a new Virtual Machine . When using NFSv3 this can be set up IP-based, for the use of CIFS \/ SMB, you can access data disclosures that would entitle them to write.",
+ "lang_vmLocationHelp3": "The bwLehrpool clients only need read access to the network storage (and for security reasons, really can only read). In CIFS \/ SMB You can do this most easily by allowing passwordless guest access with read access to the share.",
+ "module_name": "Virtual Machine"
+} \ No newline at end of file
diff --git a/modules-available/vmstore/lang/en/templates/mount.json b/modules-available/vmstore/lang/en/templates/mount.json
new file mode 100644
index 00000000..29814430
--- /dev/null
+++ b/modules-available/vmstore/lang/en/templates/mount.json
@@ -0,0 +1,5 @@
+{
+ "lang_back": "Back",
+ "lang_configure": "Configure",
+ "lang_vmLocationConfiguration": "VM location is configured"
+} \ No newline at end of file
diff --git a/modules-available/vmstore/lang/en/templates/page-vmstore.json b/modules-available/vmstore/lang/en/templates/page-vmstore.json
new file mode 100644
index 00000000..23ddbbd3
--- /dev/null
+++ b/modules-available/vmstore/lang/en/templates/page-vmstore.json
@@ -0,0 +1,17 @@
+{
+ "lang_close": "Close",
+ "lang_intern": "Intern",
+ "lang_nfsHelp1": "An NFSv4\/3-Share is required. It should be readable by all the workstations, and writable for the satellite server. An example, assuming the satellite server has IP address 1.2.3.4:",
+ "lang_nfsHelp2": "Alternate configuration using all_squash. The exported directory should be owned (and be writable) by the user with uid 1234.",
+ "lang_noAdditionalInformation": "No additional cofiguration required",
+ "lang_password": "Password",
+ "lang_readOnly": "Read-only Access",
+ "lang_readWrite": "Read\/Write Access",
+ "lang_save": "Save",
+ "lang_username": "Username",
+ "lang_vmLocation": "VM Location",
+ "lang_vmLocationChoose": "Please choose where the images of virtual machines will be stored.",
+ "lang_vmLocationHelp1": "For test purposes, the VMs can be stored directly on the Satellite server. However, if you operate the delivered satellite vmdk please remember that you have only about 100GB of memory.",
+ "lang_vmLocationHelp2": "In productive operation, it makes sense for this to use a high-performance network storage. This network storage can be integrated via NFS or CIFS \/ SMB. In any case, it must be ensured that the satellite server has write access to this network storage to add a new Virtual Machine . When using NFSv3 this can be set up IP-based, for the use of CIFS \/ SMB, you can access data disclosures that would entitle them to write.",
+ "lang_vmLocationHelp3": "The bwLehrpool clients only need read access to the network storage (and for security reasons, really can only read). In CIFS \/ SMB You can do this most easily by allowing passwordless guest access with read access to the share."
+} \ No newline at end of file
diff --git a/modules-available/vmstore/lang/pt/module.json b/modules-available/vmstore/lang/pt/module.json
new file mode 100644
index 00000000..1aa7fdaf
--- /dev/null
+++ b/modules-available/vmstore/lang/pt/module.json
@@ -0,0 +1,21 @@
+{
+ "lang_back": "Voltar",
+ "lang_close": "Fechar",
+ "lang_configure": "configurar",
+ "lang_intern": "Iterna",
+ "lang_nfsHelp1": "\u00c9 necess\u00e1rio um NFSv3-Share. Ele deve poder ser lido por todas as esta\u00e7\u00f5es de trabalho, e poder ser escrito pelo server. Um exemplo, assumindo que o server possui endere\u00e7o ip 1.2.3.4:",
+ "lang_nfsHelp2": "Configura\u00e7\u00e3o alternativa usando all_squash. O usu\u00e1rio com uid 1234 deve possuir ( e poder escrever em ) o diret\u00f3rio exportado.",
+ "lang_noAdditionalInformation": "Nenhuma configura\u00e7\u00e3o adicional necess\u00e1ria",
+ "lang_password": "Senha",
+ "lang_readOnly": "Acesso Somente Leitura",
+ "lang_readWrite": "Acesso Leitura\/Escrita",
+ "lang_save": "Salvar",
+ "lang_username": "Nome de Usu\u00e1rio",
+ "lang_vmLocation": "Localiza\u00e7\u00e3o da VM",
+ "lang_vmLocationChoose": "Por favor, escolha aonde as imagens das m\u00e1quinas virtuais ser\u00e3o armazenadas.",
+ "lang_vmLocationConfiguration": "Localiza\u00e7\u00e3o da VM \u00e9 configurada",
+ "lang_vmLocationHelp1": "Para fins de teste, as VMs podem ser armazenados diretamente no servidor sat\u00e9lite. No entanto, se voc\u00ea operar o vmdk do sat\u00e9lite entregue por favor lembre-se que voc\u00ea tem apenas cerca de 100 GB de mem\u00f3ria.",
+ "lang_vmLocationHelp2": "Em opera\u00e7\u00e3o, faz sentido para este usar um armazenamento de rede de alto desempenho. Este armazenamento de rede pode ser integrado atrav\u00e9s de NFS ou CIFS \/ SMB. Em qualquer caso, deve-se assegurar de que o servidor de sat\u00e9lite tenha acesso de grava\u00e7\u00e3o para este armazenamento de rede para poder adicionar uma nova m\u00e1quina virtual. Ao utilizar NFSv3 este pode ser configurado com base em IP, para o uso de CIFS \/ SMB, voc\u00ea pode acessar as divulga\u00e7\u00f5es de dados que lhe permitiria escrever.",
+ "lang_vmLocationHelp3": "Os clientes bwLehrpool s\u00f3 precisam ter acesso de leitura ao armazenamento de rede (e por raz\u00f5es de seguran\u00e7a, realmente s\u00f3 pode ler). Em CIFS \/ SMB Voc\u00ea pode fazer isso mais facilmente, permitindo o acesso a visitantes sem senha com acesso de leitura.",
+ "module_name": "M\u00e1quina Virtual"
+} \ No newline at end of file
diff --git a/modules-available/vmstore/page.inc.php b/modules-available/vmstore/page.inc.php
new file mode 100644
index 00000000..81f92ee3
--- /dev/null
+++ b/modules-available/vmstore/page.inc.php
@@ -0,0 +1,63 @@
+<?php
+
+class Page_VmStore extends Page
+{
+ private $mountTask = false;
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ $action = Request::post('action');
+
+ if ($action === 'setstore') {
+ $this->setStore();
+ }
+ }
+
+ protected function doRender()
+ {
+ $action = Request::post('action');
+ if ($action === 'setstore' && !Taskmanager::isFailed($this->mountTask)) {
+ Render::addTemplate('mount', array(
+ 'task' => $this->mountTask['id']
+ ));
+ return;
+ }
+ $vmstore = Property::getVmStoreConfig();
+ if (isset($vmstore['storetype'])) {
+ $vmstore['pre-' . $vmstore['storetype']] = 'checked';
+ }
+ Render::addTemplate('page-vmstore', $vmstore);
+ }
+
+ private function setStore()
+ {
+ foreach (array('storetype', 'nfsaddr', 'cifsaddr', 'cifsuser', 'cifspasswd', 'cifsuserro', 'cifspasswdro') as $key) {
+ $vmstore[$key] = trim(Request::post($key, ''));
+ }
+ $storetype = $vmstore['storetype'];
+ if (!in_array($storetype, array('internal', 'nfs', 'cifs'))) {
+ Message::addError('value-invalid', 'type', $storetype);
+ Util::redirect('?do=VmStore');
+ }
+ // Validate syntax of nfs/cifs
+ if ($storetype === 'nfs' && !preg_match('#^\S+:\S+$#is', $vmstore['nfsaddr'])) {
+ Message::addError('value-invalid', 'nfsaddr', $vmstore['nfsaddr']);
+ Util::redirect('?do=VmStore');
+ }
+ $vmstore['cifsaddr'] = str_replace('\\', '/', $vmstore['cifsaddr']);
+ if ($storetype === 'cifs' && !preg_match('#^//\S+/.+$#is', $vmstore['cifsaddr'])) {
+ Message::addError('value-invalid', 'nfsaddr', $vmstore['nfsaddr']);
+ Util::redirect('?do=VmStore');
+ }
+ $this->mountTask = Trigger::mount($vmstore);
+ TaskmanagerCallback::addCallback($this->mountTask, 'manualMount', $vmstore);
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/vmstore/templates/mount.html b/modules-available/vmstore/templates/mount.html
new file mode 100644
index 00000000..eabee81a
--- /dev/null
+++ b/modules-available/vmstore/templates/mount.html
@@ -0,0 +1,25 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_vmLocationConfiguration}}
+ </div>
+
+ <div class="panel-body">
+ <div data-tm-id="{{task}}" data-tm-log="messages" data-tm-callback="mountCb">{{lang_configure}}</div>
+
+ <br>
+ <div id="finish" class="pull-right" style="display:none">
+ <a href="?do=VmStore" class="btn btn-primary">{{lang_back}}</a>
+ </div>
+ <script type="text/javascript">
+ function mountCb(task)
+ {
+ if (!task || !task.statusCode)
+ return;
+ if (task.statusCode !== 'TASK_WAITING' && task.statusCode !== 'TASK_PROCESSING') {
+ $('#finish').attr('style', '');
+ }
+ }
+ </script>
+
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/vmstore/templates/page-vmstore.html b/modules-available/vmstore/templates/page-vmstore.html
new file mode 100644
index 00000000..fe2c5225
--- /dev/null
+++ b/modules-available/vmstore/templates/page-vmstore.html
@@ -0,0 +1,111 @@
+<form role="form" method="post" action="?do=VmStore">
+ <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;">
+ <input type="password" name="password_fake" id="password_fake" value="" style="display:none;">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="setstore">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_vmLocation}} <a class="btn btn-default" data-toggle="modal" data-target="#help-store"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </div>
+ <div class="panel-body slx-md-width">
+ <p>{{lang_vmLocationChoose}}</p>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <input type="radio" name="storetype" value="internal" {{pre-internal}}> {{lang_intern}}
+ </div>
+ <div class="panel-body">
+ {{lang_noAdditionalInformation}}
+ </div>
+ </div>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <input type="radio" name="storetype" value="nfs" {{pre-nfs}}> NFS
+ <a class="btn btn-default btn-sm" data-toggle="modal" data-target="#help-nfs"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </div>
+ <div class="panel-body">
+ <label for="nfsaddr">NFS-Export</label>
+ <input type="text" class="form-control" name="nfsaddr" value="{{nfsaddr}}" placeholder="1.2.3.4:/export/bwlp" id="nfsaddr">
+ </div>
+ </div>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <input type="radio" name="storetype" value="cifs" {{pre-cifs}}> CIFS
+ </div>
+ <div class="panel-body">
+ <label for="cifsaddr">UNC-Pfad</label>
+ <input type="text" class="form-control" name="cifsaddr" value="{{cifsaddr}}" placeholder="\\samba.server.example.com\bwlp" id="cifsaddr">
+ <br>
+ <label for="cifsuser">{{lang_readWrite}}</label>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">
+ {{lang_username}}
+ </span>
+ <input type="text" class="form-control" name="cifsuser" value="{{cifsuser}}" placeholder="{{lang_username}}" id="cifsuser">
+ <span class="input-group-addon">
+ {{lang_password}}
+ </span>
+ <input type="{{password_type}}" class="form-control" name="cifspasswd" value="{{cifspasswd}}" placeholder="{{lang_password}}">
+ </div>
+ <br>
+ <label for="cifsuserro">{{lang_readOnly}}</label>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">
+ {{lang_username}}
+ </span>
+ <input type="text" class="form-control" name="cifsuserro" value="{{cifsuserro}}" placeholder="{{lang_username}}" id="cifsuserro">
+ <span class="input-group-addon">
+ {{lang_password}}
+ </span>
+ <input type="{{password_type}}" class="form-control" name="cifspasswdro" value="{{cifspasswdro}}" placeholder="{{lang_password}}">
+ </div>
+ </div>
+ </div>
+ <button class="btn btn-primary" type="submit">{{lang_save}}</button>
+ </div>
+ </div>
+</form>
+
+<div class="modal fade" id="help-store" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{lang_vmLocation}}</div>
+ <div class="modal-body">
+ <p>
+ {{lang_vmLocationHelp1}}
+ </p>
+ <p>
+ {{lang_vmLocationHelp2}}
+ </p>
+ <p>
+ {{lang_vmLocationHelp3}}
+ </p>
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div>
+
+<div class="modal fade" id="help-nfs" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">NFS</div>
+ <div class="modal-body">
+ <p>
+ {{lang_nfsHelp1}}
+ </p>
+ <pre>
+/mnt/images 1.2.3.4(rw,no_root_squash,async)
+/mnt/images *(ro,async,nolock)
+ </pre>
+ <p>
+ {{lang_nfsHelp2}}
+ </p>
+ <pre>
+/mnt/images 1.2.3.4(rw,all_squash,anon_uid=1234,async)
+/mnt/images *(ro,async,nolock)
+ </pre>
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div>
diff --git a/modules-available/webinterface/config.json b/modules-available/webinterface/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/webinterface/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/webinterface/lang/de/templates/httpd-restart.json b/modules-available/webinterface/lang/de/templates/httpd-restart.json
new file mode 100644
index 00000000..e995a251
--- /dev/null
+++ b/modules-available/webinterface/lang/de/templates/httpd-restart.json
@@ -0,0 +1,4 @@
+{
+ "lang_applyingSettings": "Anwenden der Einstellungen",
+ "lang_installAndRestart": "Zertifikat installieren und Webserver neustarten"
+} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/de/templates/https.json b/modules-available/webinterface/lang/de/templates/https.json
new file mode 100644
index 00000000..621343e3
--- /dev/null
+++ b/modules-available/webinterface/lang/de/templates/https.json
@@ -0,0 +1,12 @@
+{
+ "lang_HttpsIsDisabled": "HTTPS ist derzeit deaktiviert",
+ "lang_caChain": "Optional k\u00f6nnen Sie hier die zum Zertifikat geh\u00f6rende Zertifikatkette (CA-Chain) einf\u00fcgen. Dies wird ben\u00f6tigt, wenn das Zertifikat nicht direkt von einer der in Browsern mitgeliferten CAs signiert wurde. Die Datei enth\u00e4lt ein oder meherere Zertifikatsbl\u00f6cke, im gleichen Format wie das oben gezeigte Zertifikat.",
+ "lang_certificate": "Bitte f\u00fcgen Sie hier das Zertifikat ein. Das Zertifikat wird im Base64-codierten x509-Format erwartet (manchmal pem genannt). Es sieht in etwa wie folgt aus:",
+ "lang_customCert": "Eigenes Zertifikat verwenden",
+ "lang_description": "Hier k\u00f6nnen Sie festlegen, ob das Web-Interface auch per HTTPS erreichbar sein soll, und welches Zertifikat daf\u00fcr verwendet werden soll.",
+ "lang_httpsSettings": "HTTPS-Konfiguration",
+ "lang_noHttps": "HTTPS wieder deaktivieren, aktuelles Zertifikat l\u00f6schen",
+ "lang_privateKey": "Bitte f\u00fcgen Sie hier den privaten Schl\u00fcssel ein, der zum obigen Zertifikat geh\u00f6rt. Er muss ebenfalls im \"pem\"-Format vorliegen, und sieht wie folgt aus:",
+ "lang_randomCert": "Neues selbstsigniertes Zertifikat generieren",
+ "lang_save": "Speichern"
+} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/de/templates/passwords.json b/modules-available/webinterface/lang/de/templates/passwords.json
new file mode 100644
index 00000000..17c156c3
--- /dev/null
+++ b/modules-available/webinterface/lang/de/templates/passwords.json
@@ -0,0 +1,7 @@
+{
+ "lang_description": "Legen Sie fest, ob Passwortfelder in der Web-Schnittstelle maskiert werden, oder ob Ihr Inhalt sichtbar sein soll. Wenn Sie die Schnittstelle in einer sicheren Umgebung nutzen (keine neugierigen Augen), kann dies den Komfort erh\u00f6hen. Das Passwortfeld der Anmeldemaske ist von dieser Einstellung ausgenommen.",
+ "lang_hidePasswords": "Passw\u00f6rter maskieren",
+ "lang_passwordFields": "Passwortfelder",
+ "lang_save": "Speichern",
+ "lang_showPasswords": "Passw\u00f6rter anzeigen"
+} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/en/module.json b/modules-available/webinterface/lang/en/module.json
new file mode 100644
index 00000000..dde5ac86
--- /dev/null
+++ b/modules-available/webinterface/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Web Interface"
+} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/en/templates/httpd-restart.json b/modules-available/webinterface/lang/en/templates/httpd-restart.json
new file mode 100644
index 00000000..0a7d4aea
--- /dev/null
+++ b/modules-available/webinterface/lang/en/templates/httpd-restart.json
@@ -0,0 +1,4 @@
+{
+ "lang_applyingSettings": "Applying settings",
+ "lang_installAndRestart": "Installing certificate and restarting web server"
+} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/en/templates/https.json b/modules-available/webinterface/lang/en/templates/https.json
new file mode 100644
index 00000000..64631a9a
--- /dev/null
+++ b/modules-available/webinterface/lang/en/templates/https.json
@@ -0,0 +1,12 @@
+{
+ "lang_HttpsIsDisabled": "HTTPS is currently disabled",
+ "lang_caChain": "Here you can paste an optional certificate chain. It should only be required if you have a certificate that was not directly signed by a certificate authority known by the browsers. It should contain one or more certificate blocks, looking just like the certificate above.",
+ "lang_certificate": "Please paste your certificate below. It has to be in base64 encoded x509 format (sometimes called pem). It should look something like this:",
+ "lang_customCert": "Supply own certificate",
+ "lang_description": "Here you can set whether the web interface should be accessible via https. You can chose if you want to use a random self signed certificate, or supply your own.",
+ "lang_httpsSettings": "HTTPS settings",
+ "lang_noHttps": "Disable HTTPS, delete current certificate",
+ "lang_privateKey": "Please paste the private key belonging to the certificate here. It has to be in \"pem\" format too, which should look like this:",
+ "lang_randomCert": "Genenrate new self-signed certificate",
+ "lang_save": "Save"
+} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/en/templates/passwords.json b/modules-available/webinterface/lang/en/templates/passwords.json
new file mode 100644
index 00000000..2db88ae2
--- /dev/null
+++ b/modules-available/webinterface/lang/en/templates/passwords.json
@@ -0,0 +1,7 @@
+{
+ "lang_description": "Set whether password fields should be masked or not. The password field of the login page to the web interface is always masked.",
+ "lang_hidePasswords": "Mask passwords",
+ "lang_passwordFields": "Password fields",
+ "lang_save": "Save",
+ "lang_showPasswords": "Show passwords"
+} \ No newline at end of file
diff --git a/modules-available/webinterface/page.inc.php b/modules-available/webinterface/page.inc.php
new file mode 100644
index 00000000..c301dec9
--- /dev/null
+++ b/modules-available/webinterface/page.inc.php
@@ -0,0 +1,85 @@
+<?php
+
+class Page_WebInterface extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ switch (Request::post('action')) {
+ case 'https':
+ $this->actionConfigureHttps();
+ break;
+ case 'password':
+ $this->actionShowHidePassword();
+ break;
+ }
+ }
+
+ private function actionConfigureHttps()
+ {
+ $task = false;
+ switch (Request::post('mode')) {
+ case 'off':
+ $task = $this->setHttpsOff();
+ break;
+ case 'random':
+ $task = $this->setHttpsRandomCert();
+ break;
+ case 'custom':
+ $task = $this->setHttpsCustomCert();
+ break;
+ }
+ if (isset($task['id'])) {
+ Session::set('https-id', $task['id']);
+ Util::redirect('?do=WebInterface&show=httpsupdate');
+ }
+ }
+
+ private function actionShowHidePassword()
+ {
+ Property::setPasswordFieldType(Request::post('mode') === 'show' ? 'text' : 'password');
+ Util::redirect('?do=WebInterface');
+ }
+
+ protected function doRender()
+ {
+ Render::setTitle(Dictionary::translate('lang_titleWebinterface'));
+ if (Request::get('show') === 'httpsupdate') {
+ Render::addTemplate('httpd-restart', array('taskid' => Session::get('https-id')));
+ }
+ Render::addTemplate('https', array('httpsEnabled' => file_exists('/etc/lighttpd/server.pem')));
+ $data = array();
+ if (Property::getPasswordFieldType() === 'text')
+ $data['selected_show'] = 'checked';
+ else
+ $data['selected_hide'] = 'checked';
+ Render::addTemplate('passwords', $data);
+ }
+
+ private function setHttpsOff()
+ {
+ return Taskmanager::submit('LighttpdHttps', array());
+ }
+
+ private function setHttpsRandomCert()
+ {
+ return Taskmanager::submit('LighttpdHttps', array(
+ 'proxyip' => Property::getServerIp()
+ ));
+ }
+
+ private function setHttpsCustomCert()
+ {
+ return Taskmanager::submit('LighttpdHttps', array(
+ 'importcert' => Request::post('certificate', 'bla'),
+ 'importkey' => Request::post('privatekey', 'bla'),
+ 'importchain' => Request::post('cachain', '')
+ ));
+ }
+
+}
diff --git a/modules-available/webinterface/templates/httpd-restart.html b/modules-available/webinterface/templates/httpd-restart.html
new file mode 100644
index 00000000..cc84aafb
--- /dev/null
+++ b/modules-available/webinterface/templates/httpd-restart.html
@@ -0,0 +1,6 @@
+<div class="panel panel-default">
+ <div class="panel-heading">{{lang_applyingSettings}}</div>
+ <div class="panel-body">
+ <div data-tm-id="{{taskid}}" data-tm-log="error">{{lang_installAndRestart}}</div>
+ </div>
+</div>
diff --git a/modules-available/webinterface/templates/https.html b/modules-available/webinterface/templates/https.html
new file mode 100644
index 00000000..c6161cd6
--- /dev/null
+++ b/modules-available/webinterface/templates/https.html
@@ -0,0 +1,60 @@
+<form action="?do=WebInterface" method="post">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="https">
+ <div class="panel panel-default">
+ <div class="panel-heading">{{lang_httpsSettings}}</div>
+ <div class="panel-body">
+ <p>{{lang_description}}</p>
+ {{^httpsEnabled}}
+ <p>{{lang_HttpsIsDisabled}}</p>
+ {{/httpsEnabled}}
+ {{#httpsEnabled}}
+ <div class="input-group" onclick="$('#moff').prop('checked', true);
+ $('#wcustom').hide()">
+ <span class="input-group-addon"><input id="moff" type="radio" name="mode" value="off"></span>
+ <span class="form-control">
+ {{lang_noHttps}}
+ </span>
+ </div>
+ {{/httpsEnabled}}
+ <div class="input-group" onclick="$('#mrandom').prop('checked', true);
+ $('#wcustom').hide()">
+ <span class="input-group-addon"><input id="mrandom" type="radio" name="mode" value="random"></span>
+ <span class="form-control">
+ {{lang_randomCert}}
+ </span>
+ </div>
+ <div class="input-group" onclick="$('#mcustom').prop('checked', true);
+ $('#wcustom').show()">
+ <span class="input-group-addon"><input id="mcustom" type="radio" name="mode" value="custom"></span>
+ <span class="form-control">
+ {{lang_customCert}}
+ </span>
+ </div>
+ <div class="well well-sm" style="display:none" id="wcustom">
+ {{lang_certificate}}
+ <pre class="small">
+-----BEGIN CERTIFICATE-----
+MIIFfTCCA...
+.....
+-----END CERTIFICATE-----</pre>
+ <textarea name="certificate" class="form-control small" cols="101" rows="10"></textarea>
+ <hr>
+ {{lang_privateKey}}
+ <pre class="small">
+-----BEGIN PRIVATE KEY-----
+MIIFfTCCA...
+.....
+-----END PRIVATE KEY-----</pre>
+ <textarea name="privatekey" class="form-control small" cols="101" rows="10"></textarea>
+ <hr>
+ {{lang_caChain}}
+ <textarea name="cachain" class="form-control small" cols="101" rows="10"></textarea>
+ <hr>
+ </div>
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ </div>
+ </div>
+ </div>
+</form>
diff --git a/modules-available/webinterface/templates/passwords.html b/modules-available/webinterface/templates/passwords.html
new file mode 100644
index 00000000..f9fda016
--- /dev/null
+++ b/modules-available/webinterface/templates/passwords.html
@@ -0,0 +1,25 @@
+<form action="?do=WebInterface" method="post">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="password">
+ <div class="panel panel-default">
+ <div class="panel-heading">{{lang_passwordFields}}</div>
+ <div class="panel-body">
+ <p>{{lang_description}}</p>
+ <div class="input-group" onclick="$('#pmshow').prop('checked', true)">
+ <span class="input-group-addon"><input id="pmshow" type="radio" name="mode" value="show" {{selected_show}}></span>
+ <span class="form-control">
+ {{lang_showPasswords}}
+ </span>
+ </div>
+ <div class="input-group" onclick="$('#pmhide').prop('checked', true)">
+ <span class="input-group-addon"><input id="pmhide" type="radio" name="mode" value="hide" {{selected_hide}}></span>
+ <span class="form-control">
+ {{lang_hidePasswords}}
+ </span>
+ </div>
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ </div>
+ </div>
+ </div>
+</form>