Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
slapos
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kwabena Antwi-Boasiako
slapos
Commits
e4adff53
Commit
e4adff53
authored
Dec 28, 2015
by
Alain Takoudjou
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
cleanup monitor2 stack
parent
48d970f4
Changes
33
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
397 additions
and
1257 deletions
+397
-1257
stack/monitor2/buildout.cfg
stack/monitor2/buildout.cfg
+94
-139
stack/monitor2/cgi-httpd.conf.in
stack/monitor2/cgi-httpd.conf.in
+0
-85
stack/monitor2/index.html
stack/monitor2/index.html
+0
-10
stack/monitor2/instance-monitor.cfg.jinja2.in
stack/monitor2/instance-monitor.cfg.jinja2.in
+24
-58
stack/monitor2/monitor-service-run.in
stack/monitor2/monitor-service-run.in
+4
-0
stack/monitor2/monitor.cfg.in
stack/monitor2/monitor.cfg.in
+0
-290
stack/monitor2/monitor.py.in
stack/monitor2/monitor.py.in
+0
-141
stack/monitor2/scripts/monitor-password-promise.py
stack/monitor2/scripts/monitor-password-promise.py
+2
-1
stack/monitor2/scripts/monitor.py
stack/monitor2/scripts/monitor.py
+203
-0
stack/monitor2/scripts/run-promise.py
stack/monitor2/scripts/run-promise.py
+1
-2
stack/monitor2/scripts/status2rss.py
stack/monitor2/scripts/status2rss.py
+0
-0
stack/monitor2/templates/monitor-httpd.conf.in
stack/monitor2/templates/monitor-httpd.conf.in
+20
-0
stack/monitor2/templates/monitor-service.cfg.in
stack/monitor2/templates/monitor-service.cfg.in
+0
-0
stack/monitor2/templates/monitor.conf.in
stack/monitor2/templates/monitor.conf.in
+0
-0
stack/monitor2/templates/wrapper.in
stack/monitor2/templates/wrapper.in
+0
-0
stack/monitor2/web/default-promise-interface.html
stack/monitor2/web/default-promise-interface.html
+0
-0
stack/monitor2/web/index.html
stack/monitor2/web/index.html
+22
-0
stack/monitor2/web/logout.html
stack/monitor2/web/logout.html
+0
-0
stack/monitor2/web/monitor-password-interface.html
stack/monitor2/web/monitor-password-interface.html
+0
-0
stack/monitor2/web/monitor.css
stack/monitor2/web/monitor.css
+7
-0
stack/monitor2/web/monitor.js
stack/monitor2/web/monitor.js
+20
-7
stack/monitor2/webfile-directory/ansible-report.cgi.in
stack/monitor2/webfile-directory/ansible-report.cgi.in
+0
-0
stack/monitor2/webfile-directory/index.cgi.in
stack/monitor2/webfile-directory/index.cgi.in
+0
-190
stack/monitor2/webfile-directory/index.html.jinja2
stack/monitor2/webfile-directory/index.html.jinja2
+0
-35
stack/monitor2/webfile-directory/monitor-password.cgi.in
stack/monitor2/webfile-directory/monitor-password.cgi.in
+0
-29
stack/monitor2/webfile-directory/settings.cgi.in
stack/monitor2/webfile-directory/settings.cgi.in
+0
-64
stack/monitor2/webfile-directory/static/monitor-register.js
stack/monitor2/webfile-directory/static/monitor-register.js
+0
-17
stack/monitor2/webfile-directory/static/pure-min.css
stack/monitor2/webfile-directory/static/pure-min.css
+0
-11
stack/monitor2/webfile-directory/static/script.js
stack/monitor2/webfile-directory/static/script.js
+0
-35
stack/monitor2/webfile-directory/static/style.css
stack/monitor2/webfile-directory/static/style.css
+0
-31
stack/monitor2/webfile-directory/static/welcome.html
stack/monitor2/webfile-directory/static/welcome.html
+0
-11
stack/monitor2/webfile-directory/status-history.cgi.in
stack/monitor2/webfile-directory/status-history.cgi.in
+0
-44
stack/monitor2/webfile-directory/status.cgi.in
stack/monitor2/webfile-directory/status.cgi.in
+0
-57
No files found.
stack/monitor2/buildout.cfg
View file @
e4adff53
This diff is collapsed.
Click to expand it.
stack/monitor2/cgi-httpd.conf.in
deleted
100644 → 0
View file @
48d970f4
PidFile "{{ httpd_configuration.get('pid-file') }}"
StartServers 1
ServerLimit 1
ThreadLimit 4
ThreadsPerChild 4
ServerName example.com
ServerAdmin someone@email
<IfDefine !MonitorPort>
Listen [{{ httpd_configuration.get('listening-ip') }}]:{{ monitor_parameters.get('port') }}
Define MonitorPort
</IfDefine>
DocumentRoot "{{ directory.get('www') }}"
ErrorLog "{{ httpd_configuration.get('error-log') }}"
LoadModule unixd_module modules/mod_unixd.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule mime_module modules/mod_mime.so
LoadModule cgid_module modules/mod_cgid.so
LoadModule dir_module modules/mod_dir.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule alias_module modules/mod_alias.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule rewrite_module modules/mod_rewrite.so
# SSL Configuration
<IfDefine !SSLConfigured>
Define SSLConfigured
SSLCertificateFile {{ httpd_configuration.get('certificate') }}
SSLCertificateKeyFile {{ httpd_configuration.get('key') }}
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
SSLRandomSeed startup /dev/urandom 256
SSLRandomSeed connect builtin
SSLProtocol -ALL +SSLv3 +TLSv1
SSLHonorCipherOrder On
SSLCipherSuite RC4-SHA:HIGH:!ADH
</IfDefine>
SSLEngine On
ScriptSock {{ httpd_configuration.get('cgid-pid-file') }}
<Directory {{ directory.get('www') }}>
SSLVerifyDepth 1
SSLRequireSSL
SSLOptions +StrictRequire
# XXX: security????
Options +ExecCGI
AddHandler cgi-script .cgi
DirectoryIndex {{ monitor_parameters.get('index-filename') }}
</Directory>
Alias /private/ {{ directory.get('private-directory') }}/
<Directory {{ directory.get('private-directory') }}>
Order Deny,Allow
Deny from env=AUTHREQUIRED
<Files ".??*">
Order Allow,Deny
Deny from all
</Files>
AuthType Basic
AuthName "Private access"
AuthUserFile "{{ monitor_parameters.get('htaccess-file') }}"
Require valid-user
Options Indexes FollowSymLinks
Satisfy all
</Directory>
<Location /rewrite>
AuthType Basic
AuthName "Private access"
AuthUserFile "{{ monitor_parameters.get('htaccess-file') }}"
Require valid-user
</Location>
ProxyVia On
RewriteEngine On
{% for key, value in monitor_rewrite_rule.iteritems() %}
RewriteRule ^/rewrite/{{ key }}($|/.*) {{ value }}/$1 [P,L]
{% endfor %}
stack/monitor2/index.html
deleted
100644 → 0
View file @
48d970f4
<!DOCTYPE html>
<html>
<head>
<link
rel=
"stylesheet"
href=
"monitor.css"
/>
<script
src=
"monitor.js"
></script>
</head>
<body>
<noscript>
Please enable javascript on your browser to make this application to work.
</noscript>
</body>
</html>
stack/monitor2/instance-monitor.cfg.jinja2.in
View file @
e4adff53
...
@@ -115,14 +115,18 @@ wrapper = ${directory:services}/monitor-httpd
...
@@ -115,14 +115,18 @@ wrapper = ${directory:services}/monitor-httpd
[monitor-conf-parameters]
[monitor-conf-parameters]
title = ${monitor-instance-parameter:monitor-title}
title = ${monitor-instance-parameter:monitor-title}
service-executable-dir = ${monitor-directory:run}
template-service-run = {{ monitor_service_run }}
public-folder = ${monitor-directory:public}
public-folder = ${monitor-directory:public}
private-folder = ${monitor-directory:private}
private-folder = ${monitor-directory:private}
web-folder = ${monitor-directory:web-dir}
web-folder = ${monitor-directory:web-dir}
monitor-hal-json = ${monitor-directory:web-dir}/monitor.haljson
monitor-hal-json = ${monitor-directory:web-dir}/monitor.haljson
service-pid-folder = ${monitor-directory:pids}
service-pid-folder = ${monitor-directory:pids}
crond-folder = ${logrotate-directory:cron-entries}
crond-folder = ${logrotate-directory:cron-entries}
wraper-folder = ${monitor-directory:promise-wrapper}
promise-runner = {{ promise_executor_py }}
promise-folder-list =
${directory:promises}
${directory:monitor-promise}
public-path-list =
public-path-list =
${directory:log}
${directory:log}
private-path-list =
private-path-list =
...
@@ -136,6 +140,19 @@ rendered = ${directory:etc}/${:filename}
...
@@ -136,6 +140,19 @@ rendered = ${directory:etc}/${:filename}
filename = monitor.conf
filename = monitor.conf
context = section parameter_dict monitor-conf-parameters
context = section parameter_dict monitor-conf-parameters
[python-symlink]
recipe = plone.recipe.command
target = ${directory:bin}
command = ln -sf {{ python_executable }} ${:target}/python
update-command = ${:command}
[start-monitor]
recipe = slapos.cookbook:wrapper
command-line = {{ python_executable }} {{ monitor_bin }} --config_file ${monitor-conf:rendered}
wrapper-path = ${directory:scripts}/bootstrap-monitor
environment =
PATH=${python-symlink:target}:/usr/local/bin:/usr/bin:/bin
[httpd-monitor-htpasswd]
[httpd-monitor-htpasswd]
recipe = plone.recipe.command
recipe = plone.recipe.command
stop-on-error = true
stop-on-error = true
...
@@ -195,30 +212,11 @@ name = monitor-status2rss
...
@@ -195,30 +212,11 @@ name = monitor-status2rss
frequency = * * * * *
frequency = * * * * *
command = ${monitor-status2rss-wrapper:wrapper-path}
command = ${monitor-status2rss-wrapper:wrapper-path}
[monitor-web-default-promise-interface]
[monitor-web-directory]
recipe = slapos.recipe.template:jinja2
recipe = plone.recipe.command
template = {{ monitor_web_default_promise_interface }}
command = cp -f {{ monitor_web_directory }}/* ${monitor-directory:web-dir}
rendered = ${monitor-directory:web-dir}/default-promise-interface.html
update-command = ${:command}
context =
[monitor-web-index-html]
recipe = slapos.recipe.template:jinja2
template = {{ monitor_web_index_html }}
rendered = ${monitor-directory:web-dir}/index.html
context =
[monitor-web-monitor-css]
recipe = slapos.recipe.template:jinja2
template = {{ monitor_web_monitor_css }}
rendered = ${monitor-directory:web-dir}/monitor.css
context =
[monitor-web-monitor-js]
recipe = slapos.recipe.template:jinja2
template = {{ monitor_web_monitor_js }}
rendered = ${monitor-directory:web-dir}/monitor.js
context =
key monitor_title monitor-instance-parameter:monitor-title
[monitor-web-monitor-logout-cgi]
[monitor-web-monitor-logout-cgi]
recipe = slapos.recipe.template:jinja2
recipe = slapos.recipe.template:jinja2
...
@@ -227,12 +225,6 @@ rendered = ${monitor-directory:cgi-bin}/monitor-logout.cgi
...
@@ -227,12 +225,6 @@ rendered = ${monitor-directory:cgi-bin}/monitor-logout.cgi
mode = 0755
mode = 0755
context =
context =
[monitor-web-monitor-logout-page]
recipe = slapos.recipe.template:jinja2
template = {{ monitor_web_monitor_logout_page }}
rendered = ${monitor-directory:web-dir}/logout
context =
[monitor-web-monitor-promise-runner-cgi]
[monitor-web-monitor-promise-runner-cgi]
recipe = slapos.recipe.template:jinja2
recipe = slapos.recipe.template:jinja2
template = {{ monitor_web_monitor_promise_runner_cgi }}
template = {{ monitor_web_monitor_promise_runner_cgi }}
...
@@ -242,28 +234,6 @@ context =
...
@@ -242,28 +234,6 @@ context =
raw python_executable {{ python_executable }}
raw python_executable {{ python_executable }}
key promise_wrapper_folder monitor-directory:promise-wrapper
key promise_wrapper_folder monitor-directory:promise-wrapper
[start-monitor]
recipe = slapos.recipe.template:jinja2
template = {{ monitor_bin }}
rendered = ${directory:scripts}/bootstrap-monitor
context =
raw python_executable {{ python_executable }}
key public_folder monitor-directory:public
key private_folder monitor-directory:private
key monitor_configuration_path monitor-conf:rendered
key promise_runner_path monitor-run-promise:rendered
key promise_folder directory:promises
key monitor_promise_folder directory:monitor-promise
key promise_wrapper_folder monitor-directory:promise-wrapper
[monitor-run-promise]
recipe = slapos.recipe.template:jinja2
template = {{ promise_executor_py }}
rendered = ${directory:bin}/monitor-run-promise
mode = 700
context =
raw python_executable {{ python_executable }}
[monitor-httpd-promise]
[monitor-httpd-promise]
recipe = slapos.cookbook:check_url_available
recipe = slapos.cookbook:check_url_available
path = ${directory:promises}/${:filename}
path = ${directory:promises}/${:filename}
...
@@ -337,12 +307,8 @@ monitor-title = Monitoring interface
...
@@ -337,12 +307,8 @@ monitor-title = Monitoring interface
[buildout]
[buildout]
parts =
parts =
monitor-web-default-promise-interface
monitor-web-directory
monitor-web-index-html
monitor-web-monitor-css
monitor-web-monitor-js
monitor-web-monitor-logout-cgi
monitor-web-monitor-logout-cgi
monitor-web-monitor-logout-page
monitor-web-monitor-promise-runner-cgi
monitor-web-monitor-promise-runner-cgi
cron-entry-logrotate
cron-entry-logrotate
certificate-authority
certificate-authority
...
...
stack/monitor2/monitor-service-run.in
View file @
e4adff53
...
@@ -34,4 +34,8 @@ def main():
...
@@ -34,4 +34,8 @@ def main():
pidfile
.
write
(
str
(
process
.
pid
))
pidfile
.
write
(
str
(
process
.
pid
))
if
__name__
==
"__main__"
:
if
__name__
==
"__main__"
:
if
len
(
sys
.
argv
)
==
1
:
print
"Use: %s Monitor_Config_File"
sys
.
exit
(
1
)
sys
.
exit
(
main
())
sys
.
exit
(
main
())
\ No newline at end of file
stack/monitor2/monitor.cfg.in
deleted
100644 → 0
View file @
48d970f4
[slap-parameters]
recipe = slapos.cookbook:slapconfiguration
computer = $${slap-connection:computer-id}
partition = $${slap-connection:partition-id}
url = $${slap-connection:server-url}
key = $${slap-connection:key-file}
cert = $${slap-connection:cert-file}
[monitor-parameters]
json-filename = monitor.json
json-path = $${monitor-directory:monitor-result}/$${:json-filename}
rss-filename = rssfeed.html
rss-path = $${monitor-directory:public-cgi}/$${:rss-filename}
executable = $${monitor-directory:bin}/monitor.py
port = 9685
htaccess-file = $${monitor-directory:etc}/.htaccess-monitor
url = https://[$${slap-parameters:ipv6-random}]:$${:port}
index-filename = index.cgi
index-path = $${monitor-directory:www}/$${:index-filename}
db-path = $${monitor-directory:etc}/monitor.db
monitor-password-path = $${monitor-directory:etc}/.monitor.shadow
[monitor-directory]
recipe = slapos.cookbook:mkdirectory
# Standard directory needed by monitoring stack
home = $${buildout:directory}
etc = $${:home}/etc
bin = $${:home}/bin
srv = $${:home}/srv
var = $${:home}/var
log = $${:var}/log
run = $${:var}/run
service = $${:etc}/service/
etc-run = $${:etc}/run/
tmp = $${:home}/tmp
promise = $${:etc}/promise
cron-entries = $${:etc}/cron.d
crontabs = $${:etc}/crontabs
cronstamps = $${:etc}/cronstamps
ca-dir = $${:srv}/ssl
www = $${:var}/www
cgi-bin = $${:var}/cgi-bin
monitoring-cgi = $${:cgi-bin}/monitoring
knowledge0-cgi = $${:cgi-bin}/zero-knowledge
public-cgi = $${:cgi-bin}/monitor-public
monitor-custom-scripts = $${:etc}/monitor
monitor-result = $${:var}/monitor
private-directory = $${:srv}/monitor-private
[public-symlink]
recipe = cns.recipe.symlink
symlink = $${monitor-directory:public-cgi} = $${monitor-directory:www}/monitor-public
autocreate = true
[cron]
recipe = slapos.cookbook:cron
dcrond-binary = ${dcron:location}/sbin/crond
cron-entries = $${monitor-directory:cron-entries}
crontabs = $${monitor-directory:crontabs}
cronstamps = $${monitor-directory:cronstamps}
catcher = $${cron-simplelogger:wrapper}
binary = $${monitor-directory:service}/crond
# Add log to cron
[cron-simplelogger]
recipe = slapos.cookbook:simplelogger
wrapper = $${monitor-directory:bin}/cron_simplelogger
log = $${monitor-directory:log}/cron.log
[cron-entry-monitor]
<= cron
recipe = slapos.cookbook:cron.d
name = launch-monitor
frequency = */5 * * * *
command = $${deploy-monitor-script:rendered} -a
[cron-entry-rss]
<= cron
recipe = slapos.cookbook:cron.d
name = build-rss
frequency = */5 * * * *
command = $${make-rss:rendered}
[setup-static-files]
recipe = plone.recipe.command
command = ln -s ${download-monitor-jquery:destination} $${monitor-directory:www}/static
update-command = $${:command}
[deploy-index]
recipe = slapos.recipe.template:jinja2
template = ${index:location}/${index:filename}
rendered = $${monitor-parameters:index-path}
update-apache-access = ${apache:location}/bin/htpasswd -cb $${monitor-parameters:htaccess-file} admin
mode = 0744
context =
key cgi_directory monitor-directory:cgi-bin
raw index_template $${deploy-index-template:location}/$${deploy-index-template:filename}
key monitor_password_path monitor-parameters:monitor-password-path
key monitor_password_script_path deploy-monitor-password-cgi:rendered
key apache_update_command :update-apache-access
raw extra_eggs_interpreter ${buildout:directory}/bin/${extra-eggs:interpreter}
raw default_page /static/welcome.html
section rewrite_element monitor-rewrite-rule
[deploy-index-template]
recipe = hexagonit.recipe.download
url = ${index-template:location}/$${:filename}
destination = $${monitor-directory:www}
filename = ${index-template:filename}
download-only = true
mode = 0644
[deploy-status-cgi]
recipe = slapos.recipe.template:jinja2
template = ${status-cgi:location}/${status-cgi:filename}
rendered = $${monitor-directory:monitoring-cgi}/$${:filename}
filename = status.cgi
mode = 0744
context =
key json_file monitor-parameters:json-path
key monitor_bin monitor-parameters:executable
key pwd monitor-directory:monitoring-cgi
key this_file :filename
raw python_executable ${buildout:executable}
[deploy-status-history-cgi]
recipe = slapos.recipe.template:jinja2
template = ${status-history-cgi:location}/${status-history-cgi:filename}
rendered = $${monitor-directory:monitoring-cgi}/$${:filename}
filename = status-history.cgi
mode = 0744
context =
key monitor_db_path monitor-parameters:db-path
key status_history_length zero-parameters:status-history-length
raw python_executable ${buildout:executable}
[deploy-settings-cgi]
recipe = slapos.recipe.template:jinja2
template = ${settings-cgi:location}/${settings-cgi:filename}
rendered = $${monitor-directory:knowledge0-cgi}/$${:filename}
filename = settings.cgi
mode = 0744
context =
raw config_cfg $${buildout:directory}/knowledge0.cfg
raw timestamp $${buildout:directory}/.timestamp
raw python_executable ${buildout:executable}
key pwd monitor-directory:knowledge0-cgi
key this_file :filename
[deploy-monitor-password-cgi]
recipe = slapos.recipe.template:jinja2
template = ${monitor-password-cgi:location}/${monitor-password-cgi:filename}
rendered = $${monitor-directory:knowledge0-cgi}/$${:filename}
filename = monitor-password.cgi
mode = 0744
context =
raw python_executable ${buildout:executable}
key pwd monitor-directory:knowledge0-cgi
key this_file :filename
[deploy-monitor-script]
recipe = slapos.recipe.template:jinja2
template = ${monitor-bin:location}/${monitor-bin:filename}
rendered = $${monitor-parameters:executable}
mode = 0744
context =
section directory monitor-directory
section monitor_parameter monitor-parameters
key monitoring_file_json monitor-parameters:json-path
raw python_executable ${buildout:executable}
[make-rss]
recipe = slapos.recipe.template:jinja2
template = ${make-rss-script:output}
rendered = $${monitor-directory:bin}/make-rss.sh
mode = 0744
context =
section directory monitor-directory
section monitor_parameters monitor-parameters
[monitor-directory-access]
recipe = plone.recipe.command
command = ln -s $${:source} $${monitor-directory:private-directory}
source =
[monitor-instance-log-access]
recipe = plone.recipe.command
command = if [ -d $${:source} ]; then ln -s $${:source} $${monitor-directory:private-directory}/instance-logs; fi
update-command = if [ -d $${:source} ]; then ln -s $${:source} $${monitor-directory:private-directory}/instance-logs; fi
source = $${monitor-directory:home}/.slapgrid/log/
location = $${:source}
[cadirectory]
recipe = slapos.cookbook:mkdirectory
requests = $${monitor-directory:ca-dir}/requests/
private = $${monitor-directory:ca-dir}/private/
certs = $${monitor-directory:ca-dir}/certs/
newcerts = $${monitor-directory:ca-dir}/newcerts/
crl = $${monitor-directory:ca-dir}/crl/
[certificate-authority]
recipe = slapos.cookbook:certificate_authority
openssl-binary = ${openssl:location}/bin/openssl
ca-dir = $${monitor-directory:ca-dir}
requests-directory = $${cadirectory:requests}
wrapper = $${monitor-directory:service}/certificate_authority
ca-private = $${cadirectory:private}
ca-certs = $${cadirectory:certs}
ca-newcerts = $${cadirectory:newcerts}
ca-crl = $${cadirectory:crl}
[ca-httpd]
<= certificate-authority
recipe = slapos.cookbook:certificate_authority.request
key-file = $${cadirectory:certs}/httpd.key
cert-file = $${cadirectory:certs}/httpd.crt
executable = $${monitor-directory:bin}/cgi-httpd
wrapper = $${monitor-directory:service}/cgi-httpd
# Put domain name
name = example.com
###########
# Deploy a webserver running cgi scripts for monitoring
###########
[public]
recipe = slapos.cookbook:zero-knowledge.write
filename = knowledge0.cfg
status-history-length = 5
[zero-parameters]
recipe = slapos.cookbook:zero-knowledge.read
filename = $${public:filename}
[monitor-rewrite-rule]
# XXX could it be something lighter?
[monitor-httpd-configuration]
pid-file = $${monitor-directory:run}/cgi-httpd.pid
cgid-pid-file = $${monitor-directory:run}/cgi-httpd-cgid.pid
error-log = $${monitor-directory:log}/cgi-httpd-error-log
listening-ip = $${slap-parameters:ipv6-random}
certificate = $${ca-httpd:cert-file}
key = $${ca-httpd:key-file}
[monitor-httpd-configuration-file]
recipe = slapos.recipe.template:jinja2
template = ${monitor-httpd-template:destination}/${monitor-httpd-template:filename}
rendered = $${monitor-directory:etc}/cgi-httpd.conf
mode = 0744
context =
section directory monitor-directory
section monitor_parameters monitor-parameters
section httpd_configuration monitor-httpd-configuration
section monitor_rewrite_rule monitor-rewrite-rule
[cgi-httpd-wrapper]
recipe = slapos.cookbook:wrapper
apache-executable = ${apache:location}/bin/httpd
command-line = $${:apache-executable} -f $${monitor-httpd-configuration-file:rendered} -DFOREGROUND
wrapper-path = $${ca-httpd:executable}
wait-for-files =
$${cadirectory:certs}/httpd.key
$${cadirectory:certs}/httpd.crt
[cgi-httpd-graceful-wrapper]
recipe = slapos.recipe.template:jinja2
template = ${template-wrapper:output}
rendered = $${monitor-directory:etc-run}/cgi-httpd-graceful
mode = 0700
context =
key content :command
command = kill -USR1 $(cat $${monitor-httpd-configuration:pid-file})
[monitor-promise]
recipe = slapos.cookbook:check_url_available
path = $${monitor-directory:promise}/monitor
url = $${monitor-parameters:url}/$${monitor-parameters:index-filename}
check-secure = 1
dash_path = ${dash:location}/bin/dash
curl_path = ${curl:location}/bin/curl
[publish-connection-informations]
recipe = slapos.cookbook:publish
monitor_url = $${monitor-parameters:url}
stack/monitor2/monitor.py.in
deleted
100644 → 0
View file @
48d970f4
#!{{ python_executable }}
# Put this file in the software release
promise_runner_path = "{{ promise_runner_path }}"
public_folder = "{{ public_folder }}"
private_folder = "{{ private_folder }}"
monitor_configuration_path = "{{ monitor_configuration_path }}"
promise_folder = "{{ promise_folder }}"
monitor_promise_folder = "{{ monitor_promise_folder }}"
promise_wrapper_folder = "{{ promise_wrapper_folder }}"
import sys
import os
import stat
import subprocess
import threading
import json
import ConfigParser
import traceback
def main():
# initialisation
config = loadConfig([monitor_configuration_path])
# get promises in monitor_promise_folder
promise_dict = {}
fillPromiseDictFromFolder(promise_dict, monitor_promise_folder)
# get promises in promise_folder
fillPromiseDictFromFolder(promise_dict, promise_folder)
# get promises configurations
for filename in os.listdir(monitor_promise_folder):
path = os.path.join(monitor_promise_folder, filename)
if os.path.isfile(path) and filename[-4:] == ".cfg":
promise_name = filename[:-4]
if promise_name in promise_dict:
loadConfig([path], promise_dict[promise_name]["configuration"])
promise_items = promise_dict.items()
# create symlinks from service configurations
for service_name, promise in promise_items:
service_config = promise["configuration"]
createSymlinksFromConfig((config, "monitor", "public-folder"), (service_config, "service", "public-path-list"), service_name)
createSymlinksFromConfig((config, "monitor", "private-folder"), (service_config, "service", "private-path-list"), service_name)
# create symlinks from monitor.conf
createSymlinksFromConfig((config, "monitor", "public-folder"), (config, "monitor", "public-path-list"))
createSymlinksFromConfig((config, "monitor", "private-folder"), (config, "monitor", "private-path-list"))
# generate monitor.json
monitor_dict = {}
tmp = softConfigGet(config, "monitor", "title")
if tmp:
monitor_dict["title"] = tmp
tmp = softConfigGet(config, "monitor", "monitor-url-list")
if tmp:
monitor_dict["_links"] = {"related_monitor": [{"href": url} for url in tmp.split()]}
if promise_items:
service_list = []
monitor_dict["_embedded"] = {"service": service_list}
for service_name, promise in promise_items:
service_config = promise["configuration"]
service_dict = {}
service_list.append(service_dict)
service_dict["id"] = service_name
service_dict["_links"] = {"status": {"href": "/public/%s.status.json" % service_name}} # hardcoded
tmp = softConfigGet(service_config, "service", "title")
if tmp:
service_dict["title"] = tmp
interface_path = os.path.join(private_folder, service_name, "interface/index.html") # hardcoded
if os.path.isfile(interface_path):
service_dict["_links"]["interface"] = {"href": "/private/%s/interface/" % service_name} # hardcoded
else:
service_dict["_links"]["interface"] = {"href": "/default-promise-interface.html?service_name=%s" % service_name} # XXX hardcoded
with open(config.get("monitor", "monitor-hal-json"), "w") as fp:
json.dump(monitor_dict, fp)
# put promises to a cron file
# XXX only if at least one configuration file is modified, then write in the cron
service_pid_folder = config.get("monitor", "service-pid-folder")
crond_folder = config.get("monitor", "crond-folder")
cron_line_list = []
for service_name, promise in promise_items:
service_status_path = "%s/%s.status.json" % (public_folder, service_name) # hardcoded
mkdirAll(os.path.dirname(service_status_path))
command = ("%s %s %s " % (
promise_runner_path,
os.path.join(service_pid_folder, "%s.pid" % service_name),
service_status_path,
)) + promise["path"]
cron_line_list.append("%s %s" % (
softConfigGet(service_config, "service", "frequency") or "* * * * *",
command.replace("%", "\\%"),
))
wrapper_path = os.path.join(promise_wrapper_folder, service_name)
with open(wrapper_path, "w") as fp:
fp.write("#!/bin/sh\n%s" % command) # XXX hardcoded, use dash, sh or bash binary!
os.chmod(wrapper_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IROTH )
with open(crond_folder + "/monitor-promises", "w") as fp:
fp.write("\n".join(cron_line_list))
return 0
def loadConfig(pathes, config=None):
if config is None:
config = ConfigParser.ConfigParser()
try:
config.read(pathes)
except ConfigParser.MissingSectionHeaderError:
traceback.print_exc()
return config
def fillPromiseDictFromFolder(promise_dict, folder):
for filename in os.listdir(folder):
path = os.path.join(folder, filename)
if os.path.isfile(path) and os.access(path, os.X_OK):
promise_dict[filename] = {"path": path, "configuration": ConfigParser.ConfigParser()}
def softConfigGet(config, *args, **kwargs):
try:
return config.get(*args, **kwargs)
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
return None
def createSymlinksFromConfig(destination_folder_config_tuple, source_list_config_tuple, service_name=""):
destination_folder = softConfigGet(*destination_folder_config_tuple)
if destination_folder:
source_path_str = softConfigGet(*source_list_config_tuple)
if source_path_str:
for path in source_path_str.split():
dirname = os.path.join(destination_folder, service_name)
try:
mkdirAll(dirname) # could also raise OSError
os.symlink(path, os.path.join(dirname, os.path.basename(path)))
except OSError, e:
if e.errno != os.errno.EEXIST:
raise
def mkdirAll(path):
try:
os.makedirs(path)
except OSError, e:
if e.errno == os.errno.EEXIST and os.path.isdir(path):
pass
else: raise
if __name__ == "__main__":
sys.exit(main())
stack/monitor2/
monitor-password-promise.py.in
→
stack/monitor2/
scripts/monitor-password-promise.py
View file @
e4adff53
#!{{ python_executable }}
#!/usr/bin/env python
password_changed_once_path
=
"{{ password_changed_once_path }}"
password_changed_once_path
=
"{{ password_changed_once_path }}"
import
os
import
os
...
...
stack/monitor2/scripts/monitor.py
0 → 100644
View file @
e4adff53
#!/usr/bin/env python
import
sys
import
os
import
stat
import
subprocess
import
threading
import
json
import
ConfigParser
import
traceback
import
argparse
def
parseArguments
():
"""
Parse arguments for monitor instance.
"""
parser
=
argparse
.
ArgumentParser
()
parser
.
add_argument
(
'--config_file'
,
default
=
'monitor.cfg'
,
help
=
'Monitor Configuration file'
)
parser
.
add_argument
(
'--promise-folder'
,
action
=
'append'
,
dest
=
'promise_folder_list'
,
default
=
[],
help
=
'The path to get promise executable files'
)
parser
.
add_argument
(
'--public-folder'
,
action
=
'append'
,
dest
=
'public_folder'
,
help
=
'The path of public folder. All files in this folders will have public acess'
)
parser
.
add_argument
(
'--private-folder'
,
action
=
'append'
,
dest
=
'private_folder'
,
help
=
'The path of private folder. All files in this folders will be accessible with password'
)
parser
.
add_argument
(
'--promise-runner'
,
help
=
'The path of promise runner, use to run promise files'
)
parser
.
add_argument
(
'--wrapper-path'
,
help
=
'Path of monitor generated promise scripts files.'
)
return
parser
.
parse_args
()
def
mkdirAll
(
path
):
try
:
os
.
makedirs
(
path
)
except
OSError
,
e
:
if
e
.
errno
==
os
.
errno
.
EEXIST
and
os
.
path
.
isdir
(
path
):
pass
else
:
raise
def
softConfigGet
(
config
,
*
args
,
**
kwargs
):
try
:
return
config
.
get
(
*
args
,
**
kwargs
)
except
(
ConfigParser
.
NoOptionError
,
ConfigParser
.
NoSectionError
):
return
None
class
Monitoring
(
object
):
def
__init__
(
self
,
configuration_file
):
config
=
self
.
loadConfig
([
configuration_file
])
# Set Monitor variables
self
.
monitor_hal_json
=
config
.
get
(
"monitor"
,
"monitor-hal-json"
)
self
.
title
=
config
.
get
(
"monitor"
,
"title"
)
self
.
service_pid_folder
=
config
.
get
(
"monitor"
,
"service-pid-folder"
)
self
.
crond_folder
=
config
.
get
(
"monitor"
,
"crond-folder"
)
self
.
wraper_folder
=
config
.
get
(
"monitor"
,
"wraper-folder"
)
self
.
promise_runner
=
config
.
get
(
"monitor"
,
"promise-runner"
)
self
.
promise_folder_list
=
config
.
get
(
"monitor"
,
"promise-folder-list"
).
split
()
self
.
public_folder
=
config
.
get
(
"monitor"
,
"public-folder"
)
self
.
private_folder
=
config
.
get
(
"monitor"
,
"private-folder"
)
self
.
public_path_list
=
config
.
get
(
"monitor"
,
"public-path-list"
).
split
()
self
.
private_path_list
=
config
.
get
(
"monitor"
,
"private-path-list"
).
split
()
self
.
monitor_url_list
=
config
.
get
(
"monitor"
,
"monitor-url-list"
).
split
()
self
.
promise_dict
=
{}
for
promise_folder
in
self
.
promise_folder_list
:
self
.
setupPromiseDictFromFolder
(
promise_folder
)
def
loadConfig
(
self
,
pathes
,
config
=
None
):
if
config
is
None
:
config
=
ConfigParser
.
ConfigParser
()
try
:
config
.
read
(
pathes
)
except
ConfigParser
.
MissingSectionHeaderError
:
traceback
.
print_exc
()
return
config
def
setupPromiseDictFromFolder
(
self
,
folder
):
for
filename
in
os
.
listdir
(
folder
):
path
=
os
.
path
.
join
(
folder
,
filename
)
if
os
.
path
.
isfile
(
path
)
and
os
.
access
(
path
,
os
.
X_OK
):
self
.
promise_dict
[
filename
]
=
{
"path"
:
path
,
"configuration"
:
ConfigParser
.
ConfigParser
()}
# get promises configurations
#for filename in os.listdir(monitor_promise_folder):
# path = os.path.join(monitor_promise_folder, filename)
# if os.path.isfile(path) and filename[-4:] == ".cfg":
# promise_name = filename[:-4]
# if promise_name in promise_dict:
# loadConfig([path], promise_dict[promise_name]["configuration"])
def
createSymlinksFromConfig
(
self
,
destination_folder
,
source_path_list
,
service_name
=
""
):
if
destination_folder
:
if
source_path_list
:
for
path
in
source_path_list
:
dirname
=
os
.
path
.
join
(
destination_folder
,
service_name
)
try
:
mkdirAll
(
dirname
)
# could also raise OSError
os
.
symlink
(
path
,
os
.
path
.
join
(
dirname
,
os
.
path
.
basename
(
path
)))
except
OSError
,
e
:
if
e
.
errno
!=
os
.
errno
.
EEXIST
:
raise
def
generateMonitorHalJson
(
self
):
if
self
.
title
:
self
.
monitor_dict
[
"title"
]
=
self
.
title
if
self
.
monitor_url_list
:
self
.
monitor_dict
[
"_links"
]
=
{
"related_monitor"
:
[{
"href"
:
url
}
for
url
in
self
.
monitor_url_list
]}
if
self
.
promise_items
:
service_list
=
[]
for
service_name
,
promise
in
self
.
promise_items
:
service_config
=
promise
[
"configuration"
]
service_dict
=
{}
service_dict
[
"id"
]
=
service_name
service_dict
[
"_links"
]
=
{
"status"
:
{
"href"
:
"/public/%s.status.json"
%
service_name
}}
# hardcoded
tmp
=
softConfigGet
(
service_config
,
"service"
,
"title"
)
if
tmp
:
service_dict
[
"title"
]
=
tmp
interface_path
=
os
.
path
.
join
(
self
.
private_folder
,
service_name
,
"interface/index.html"
)
# hardcoded
if
os
.
path
.
isfile
(
interface_path
):
service_dict
[
"_links"
][
"interface"
]
=
{
"href"
:
"/private/%s/interface/"
%
service_name
}
# hardcoded
else
:
service_dict
[
"_links"
][
"interface"
]
=
{
"href"
:
"/default-promise-interface.html?service_name=%s"
%
service_name
}
# XXX hardcoded
service_list
.
append
(
service_dict
)
self
.
monitor_dict
[
"_embedded"
]
=
{
"service"
:
service_list
}
with
open
(
self
.
monitor_hal_json
,
"w"
)
as
fp
:
json
.
dump
(
self
.
monitor_dict
,
fp
)
def
generateServiceCronEntries
(
self
):
# XXX only if at least one configuration file is modified, then write in the cron
cron_line_list
=
[]
for
service_name
,
promise
in
self
.
promise_items
:
service_config
=
promise
[
"configuration"
]
service_status_path
=
"%s/%s.status.json"
%
(
self
.
public_folder
,
service_name
)
# hardcoded
mkdirAll
(
os
.
path
.
dirname
(
service_status_path
))
command
=
(
"%s %s %s "
%
(
self
.
promise_runner
,
os
.
path
.
join
(
self
.
service_pid_folder
,
"%s.pid"
%
service_name
),
service_status_path
,)
)
+
promise
[
"path"
]
cron_line_list
.
append
(
"%s %s"
%
(
softConfigGet
(
service_config
,
"service"
,
"frequency"
)
or
"* * * * *"
,
command
.
replace
(
"%"
,
"
\
\
%"
),
))
wrapper_path
=
os
.
path
.
join
(
self
.
wraper_folder
,
service_name
)
with
open
(
wrapper_path
,
"w"
)
as
fp
:
fp
.
write
(
"#!/bin/sh
\
n
%s"
%
command
)
# XXX hardcoded, use dash, sh or bash binary!
os
.
chmod
(
wrapper_path
,
stat
.
S_IRUSR
|
stat
.
S_IWUSR
|
stat
.
S_IXUSR
|
stat
.
S_IRGRP
|
stat
.
S_IROTH
)
with
open
(
self
.
crond_folder
+
"/monitor-promises"
,
"w"
)
as
fp
:
fp
.
write
(
"
\
n
"
.
join
(
cron_line_list
))
def
bootstrapMonitor
(
self
):
# create symlinks from service configurations
self
.
promise_items
=
self
.
promise_dict
.
items
()
for
service_name
,
promise
in
self
.
promise_items
:
service_config
=
promise
[
"configuration"
]
public_path_list
=
softConfigGet
(
service_config
,
"service"
,
"public-path-list"
)
private_path_list
=
softConfigGet
(
service_config
,
"service"
,
"private-path-list"
)
if
public_path_list
:
self
.
createSymlinksFromConfig
(
self
.
public_folder
,
public_path_list
.
split
(),
service_name
)
if
private_path_list
:
self
.
createSymlinksFromConfig
(
self
.
private_folder
,
private_path_list
.
split
(),
service_name
)
# create symlinks from monitor.conf
self
.
createSymlinksFromConfig
(
self
.
public_folder
,
self
.
public_path_list
)
self
.
createSymlinksFromConfig
(
self
.
private_folder
,
self
.
private_path_list
)
# generate monitor.json
self
.
monitor_dict
=
{}
self
.
generateMonitorHalJson
()
# put promises to a cron file
self
.
generateServiceCronEntries
()
return
0
if
__name__
==
"__main__"
:
parser
=
parseArguments
()
monitor
=
Monitoring
(
parser
.
config_file
)
sys
.
exit
(
monitor
.
bootstrapMonitor
())
stack/monitor2/run-promise.py
→
stack/monitor2/
scripts/
run-promise.py
View file @
e4adff53
#!
{{ python_executable }}
#!
/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
import
sys
import
sys
import
os
import
os
import
subprocess
import
subprocess
import
json
import
json
from
cStringIO
import
StringIO
def
main
():
def
main
():
if
len
(
sys
.
argv
)
<
4
:
if
len
(
sys
.
argv
)
<
4
:
...
...
stack/monitor2/status2rss.py
→
stack/monitor2/s
cripts/s
tatus2rss.py
View file @
e4adff53
File moved
stack/monitor2/monitor-httpd.conf.in
→
stack/monitor2/
templates/
monitor-httpd.conf.in
View file @
e4adff53
...
@@ -30,6 +30,7 @@ LoadModule authn_file_module modules/mod_authn_file.so
...
@@ -30,6 +30,7 @@ LoadModule authn_file_module modules/mod_authn_file.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule headers_module modules/mod_headers.so
# SSL Configuration
# SSL Configuration
<IfDefine !SSLConfigured>
<IfDefine !SSLConfigured>
...
@@ -47,6 +48,24 @@ SSLCipherSuite RC4-SHA:HIGH:!ADH
...
@@ -47,6 +48,24 @@ SSLCipherSuite RC4-SHA:HIGH:!ADH
AddType application/hal+json .haljson
AddType application/hal+json .haljson
SSLEngine On
SSLEngine On
{% if parameter_dict.has_key('monitor-url-list') -%}
RewriteEngine on
SSLProxyEngine on
ProxyPreserveHost On
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
{% set index=1 -%}
{% set monitor_url_list = parameter_dict.get('monitor-url-list').split('\n') -%}
{% for url in monitor_url_list -%}
{% if url.strip() -%}
RewriteRule /monitor{{ index }}/(.*) {{ url }}/$1 [L,P]
{% set index = index + 1 -%}
{% endif -%}
{% endfor -%}
{% endif -%}
ScriptSock {{ parameter_dict.get('cgid-pid-file') }}
ScriptSock {{ parameter_dict.get('cgid-pid-file') }}
<Directory {{ directory.get('www') }}>
<Directory {{ directory.get('www') }}>
SSLVerifyDepth 1
SSLVerifyDepth 1
...
@@ -55,6 +74,7 @@ ScriptSock {{ parameter_dict.get('cgid-pid-file') }}
...
@@ -55,6 +74,7 @@ ScriptSock {{ parameter_dict.get('cgid-pid-file') }}
# XXX: security????
# XXX: security????
DirectoryIndex index.html
DirectoryIndex index.html
Options FollowSymLinks
Options FollowSymLinks
AllowOverride All
Order Deny,Allow
Order Deny,Allow
AuthType Basic
AuthType Basic
AuthName "Private access"
AuthName "Private access"
...
...
stack/monitor2/monitor-service.cfg.in
→
stack/monitor2/
templates/
monitor-service.cfg.in
View file @
e4adff53
File moved
stack/monitor2/monitor.conf.in
→
stack/monitor2/
templates/
monitor.conf.in
View file @
e4adff53
File moved
stack/monitor2/wrapper.in
→
stack/monitor2/
templates/
wrapper.in
View file @
e4adff53
File moved
stack/monitor2/default-promise-interface.html
→
stack/monitor2/
web/
default-promise-interface.html
View file @
e4adff53
File moved
stack/monitor2/web/index.html
0 → 100644
View file @
e4adff53
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"utf-8"
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"IE=edge"
>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script
src=
"https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"
></script>
<!-- Latest compiled and minified CSS -->
<link
rel=
"stylesheet"
href=
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
integrity=
"sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7"
crossorigin=
"anonymous"
>
<!-- Optional theme -->
<link
rel=
"stylesheet"
href=
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css"
integrity=
"sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r"
crossorigin=
"anonymous"
>
<!-- Latest compiled and minified JavaScript -->
<script
src=
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
integrity=
"sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS"
crossorigin=
"anonymous"
></script>
<link
rel=
"stylesheet"
href=
"monitor.css"
/>
<script
src=
"monitor.js"
></script>
</head>
<body>
<noscript>
Please enable javascript on your browser to make this application to work.
</noscript>
</body>
</html>
stack/monitor2/
monitor-
logout.html
→
stack/monitor2/
web/
logout.html
View file @
e4adff53
File moved
stack/monitor2/monitor-password-interface.html
→
stack/monitor2/
web/
monitor-password-interface.html
View file @
e4adff53
File moved
stack/monitor2/monitor.css
→
stack/monitor2/
web/
monitor.css
View file @
e4adff53
...
@@ -48,3 +48,10 @@ a.as-button:active, button:active {
...
@@ -48,3 +48,10 @@ a.as-button:active, button:active {
a
.as-button
:hover
,
button
:hover
{
a
.as-button
:hover
,
button
:hover
{
border-color
:
#777777
;
border-color
:
#777777
;
}
}
.monitor-section
{
float
:
left
;
table-layout
:
fixed
;
margin-top
:
30px
;
margin-right
:
30px
;
}
\ No newline at end of file
stack/monitor2/
monitor.js.in
→
stack/monitor2/
web/monitor.js
View file @
e4adff53
/*jslint indent:
2 */
/*jslint indent:2 */
(
function
()
{
(
function
()
{
"
use strict
"
;
"
use strict
"
;
var monitor_title = '
{{ dumps(monitor_title)[5:-1] }}
',
var
monitor_title
=
'
Monitoring interface
'
,
RSS_ICON_DATA_URI
=
[
RSS_ICON_DATA_URI
=
[
"
data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjwhRE9DVFlQR
"
,
"
data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjwhRE9DVFlQR
"
,
"
SBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cu
"
,
"
SBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cu
"
,
...
@@ -25,7 +25,8 @@
...
@@ -25,7 +25,8 @@
var
response
=
event
.
target
;
var
response
=
event
.
target
;
if
(
response
.
status
<
400
)
{
if
(
response
.
status
<
400
)
{
try
{
try
{
resolve(JSON.parse(response.responseText));
var
data
=
(
response
.
responseType
===
'
text
'
||
response
.
responseType
===
''
)
?
JSON
.
parse
(
response
.
responseText
)
:
response
.
response
;
resolve
(
data
);
}
catch
(
e
)
{
}
catch
(
e
)
{
reject
(
e
);
reject
(
e
);
}
}
...
@@ -95,6 +96,16 @@
...
@@ -95,6 +96,16 @@
return
url
.
href
;
return
url
.
href
;
}
}
function
joinUrl
(
url
,
path
)
{
if
(
path
&&
path
[
0
]
===
'
/
'
)
{
path
=
path
.
slice
(
1
);
}
if
(
url
.
indexOf
(
'
/
'
,
url
.
length
-
1
)
===
-
1
)
{
return
url
+
'
/
'
+
path
;
}
return
url
+
escapeHtml
(
path
);
}
function
escapeHtml
(
html
)
{
function
escapeHtml
(
html
)
{
return
html
.
replace
(
/&/g
,
"
&
"
).
replace
(
/</g
,
"
<
"
).
replace
(
/>/g
,
"
>
"
).
replace
(
/"/g
,
"
"
"
).
replace
(
/'/g
,
"
'
"
);
return
html
.
replace
(
/&/g
,
"
&
"
).
replace
(
/</g
,
"
<
"
).
replace
(
/>/g
,
"
>
"
).
replace
(
/"/g
,
"
"
"
).
replace
(
/'/g
,
"
'
"
);
}
}
...
@@ -106,6 +117,7 @@
...
@@ -106,6 +117,7 @@
return
;
return
;
}
}
table
=
document
.
createElement
(
"
table
"
);
table
=
document
.
createElement
(
"
table
"
);
table
.
className
=
"
monitor-section
"
;
root
.
appendChild
(
table
);
root
.
appendChild
(
table
);
return
Promise
.
all
(
service_list
.
map
(
function
(
service_dict
)
{
return
Promise
.
all
(
service_list
.
map
(
function
(
service_dict
)
{
var
interface_url
=
softGetProperty
(
service_dict
,
[
"
_links
"
,
"
interface
"
,
"
href
"
]),
var
interface_url
=
softGetProperty
(
service_dict
,
[
"
_links
"
,
"
interface
"
,
"
href
"
]),
...
@@ -118,7 +130,8 @@
...
@@ -118,7 +130,8 @@
row
[
5
].
textContent
=
"
No status
"
;
row
[
5
].
textContent
=
"
No status
"
;
return
;
return
;
}
}
return loadJson(resolveUrl(monitor_url, status_url)).then(function (status_dict) {
var
full_status_url
=
(
monitor_url
===
undefined
)
?
resolveUrl
(
monitor_url
,
status_url
):
joinUrl
(
monitor_url
,
status_url
);
return
loadJson
(
full_status_url
).
then
(
function
(
status_dict
)
{
if
(
status_dict
.
description
)
{
if
(
status_dict
.
description
)
{
row
[
2
].
title
=
status_dict
.
description
;
row
[
2
].
title
=
status_dict
.
description
;
}
}
...
@@ -144,8 +157,8 @@
...
@@ -144,8 +157,8 @@
if
(
link
.
href
[
link
.
href
.
length
-
1
]
!==
"
/
"
)
{
if
(
link
.
href
[
link
.
href
.
length
-
1
]
!==
"
/
"
)
{
link
.
href
+=
"
/
"
;
link
.
href
+=
"
/
"
;
}
}
link.href
= resolveUrl(link.href, "monitor.haljson");
var
haljson_link
=
resolveUrl
(
link
.
href
,
"
monitor.haljson
"
);
return loadJson(
link.href
).catch(function (reason) {
return
loadJson
(
haljson_link
).
catch
(
function
(
reason
)
{
div
.
textContent
=
(
reason
&&
(
reason
.
name
+
"
:
"
+
reason
.
message
));
div
.
textContent
=
(
reason
&&
(
reason
.
name
+
"
:
"
+
reason
.
message
));
}).
then
(
function
(
monitor_dict
)
{
}).
then
(
function
(
monitor_dict
)
{
//monitor_json_list.push(monitor_dict);
//monitor_json_list.push(monitor_dict);
...
@@ -160,7 +173,7 @@
...
@@ -160,7 +173,7 @@
var
element_list
=
htmlToElementList
([
var
element_list
=
htmlToElementList
([
"
<header>
"
,
"
<header>
"
,
"
<a href=
\"\"
class=
\"
as-button
\"
>Refresh</a>
"
,
"
<a href=
\"\"
class=
\"
as-button
\"
>Refresh</a>
"
,
" <a href=\"/logout\" class=\"as-button\">Logout</a>",
"
<a href=
\"
/logout
.html
\"
class=
\"
as-button
\"
>Logout</a>
"
,
"
<a href=
\"
/feed
\"
><img src=
\"
"
+
RSS_ICON_DATA_URI
+
"
\"
style=
\"
width: 10mm; height: 10mm; vertical-align: middle;
\"
alt=
\"
[RSS Feed]
\"
/></a>
"
,
"
<a href=
\"
/feed
\"
><img src=
\"
"
+
RSS_ICON_DATA_URI
+
"
\"
style=
\"
width: 10mm; height: 10mm; vertical-align: middle;
\"
alt=
\"
[RSS Feed]
\"
/></a>
"
,
"
</header>
"
,
"
</header>
"
,
"
<h1>
"
+
monitor_title
+
"
</h1>
"
,
"
<h1>
"
+
monitor_title
+
"
</h1>
"
,
...
...
stack/monitor2/webfile-directory/ansible-report.cgi.in
deleted
100644 → 0
View file @
48d970f4
stack/monitor2/webfile-directory/index.cgi.in
deleted
100755 → 0
View file @
48d970f4
#!{{ extra_eggs_interpreter }}
import cgi
import cgitb
import Cookie
import base64
import hashlib
import hmac
import jinja2
import os
import subprocess
import urllib
cgitb.enable(display=0, logdir="/tmp/cgi.log")
form = cgi.FieldStorage()
cookie = Cookie.SimpleCookie()
cgi_path = "{{ cgi_directory }}"
monitor_password_path = "{{ monitor_password_path }}"
monitor_password_script_path = "{{ monitor_password_script_path }}"
monitor_apache_password_command = "{{ apache_update_command }}"
monitor_rewrite = "{{ ' '.join(rewrite_element.keys()) }}"
########
# Password functions
#######
def crypt(word, salt="$$"):
salt = salt.split("$")
algo = salt[0] or 'sha1'
if algo in hashlib.algorithms:
H = getattr(hashlib, algo)
elif algo == "plain":
return "%s$%s" % (algo, word)
else:
raise ValueError
rounds = min(max(0, int(salt[1])), 30) if salt[1] else 9
salt = salt[2] or base64.b64encode(os.urandom(12), "./")
h = hmac.new(salt, word, H).digest()
for x in xrange(1, 1
<
<
rounds
)
:
h =
H(h).digest()
return
"%
s
$%
s
$%
s
$%
s
"
%
(
algo
,
rounds
,
salt
,
base64
.
b64encode
(
h
,
"./").
rstrip
("="))
def
is_password_set
()
:
if
not
os
.
path
.
exists
(
monitor_password_path
)
:
return
False
hashed_password =
open(monitor_password_path,
'
r
').
read
()
try:
void
,
algo
,
salt
,
hsh =
hashed_password.split('$')
except
ValueError:
return
False
return
True
def
set_password
(
raw_password
)
:
hashed_password =
crypt(raw_password)
subprocess
.
check_call
(
monitor_apache_password_command
+
"
%
s
"
%
raw_password
,
shell=
True)
open
(
monitor_password_path
,
'
w
').
write
(
hashed_password
)
def
check_password
(
raw_password
)
:
"""
Returns
a
boolean
of
whether
the
raw_password
was
correct
.
Handles
encryption
formats
behind
the
scenes
.
"""
if
not
os
.
path
.
exists
(
monitor_password_path
)
or
not
raw_password:
return
False
hashed_password =
open(monitor_password_path,
'
r
').
read
()
return
hashed_password =
=
crypt
(
raw_password
,
hashed_password
)
###
End
of
password
functions
def
forward_form
()
:
command =
os.path.join(cgi_path,
form
['
posting-script
'].
value
)
params_dict =
{}
for
f
in
form:
params_dict[f] =
form[f].value
del
params_dict
['
posting-script
']
os
.
environ
['
QUERY_STRING
'
] =
urllib.urlencode(params_dict)
try:
if
os
.
access
(
command
,
os
.
X_OK
)
:
print
'\
n
',
subprocess
.
check_output
([
command
])
except
subprocess
.
CalledProcessError:
print
"
There
is
a
problem
with
sub-process
"
pass
def
return_document(command=
None):
if
not
command:
script =
form['script'].value
command =
os.path.join(cgi_path,
script
)
#XXX
this
functions
should
be
called
only
for
display
,
#so
a
priori
it
doesn
'
t
need
form
data
os
.
environ
['
QUERY_STRING
'
] =
''
try:
if
os
.
access
(
command
,
os
.
X_OK
)
:
print
'\
n
',
subprocess
.
check_output
([
command
])
elif
os
.
access
(
command
,
os
.
R_OK
)
:
print
open
(
command
).
read
()
else:
raise
OSError
except
(
subprocess
.
CalledProcessError
,
OSError
)
as
e:
print
"<
p
>
Error :
</p><pre>
%s
</pre>
" % e
def make_menu():
# Transform deep-2 tree in json
folder_list = {}
for folder in os.listdir(cgi_path):
if os.path.isdir(os.path.join(cgi_path, folder)):
folder_list[folder] = []
for folder in folder_list:
for file in os.listdir(os.path.join(cgi_path, folder)):
if os.path.isfile(os.path.join(cgi_path, folder, file)):
folder_list[folder].append(file)
return folder_list
def get_cookie_password():
cookie_string = os.environ.get('HTTP_COOKIE')
if cookie_string:
cookie.load(cookie_string)
try:
return cookie['password'].value
except KeyError:
pass
return None
def set_cookie_password(password):
cookie['password'] = password
print cookie, "; Path=/; HttpOnly"
# Beginning of response
print "Content-Type: text/html"
password = None
# Check if user is logged
if "password_2" in form and "password" in form:
password_2 = form['password_2'].value
password_1 = form['password'].value
password = get_cookie_password()
if not is_password_set() or check_password(password):
if password_2 == password_1:
password = password_1
set_password(password)
set_cookie_password(password)
elif "password" in form:
password = form['password'].value
if is_password_set() and check_password(password):
set_cookie_password(password)
else:
password = get_cookie_password()
print '\n'
if not is_password_set():
return_document(monitor_password_script_path)
elif not check_password(password):
print "
<html><head>
"
print """
<link
rel=
"stylesheet"
href=
"static/pure-min.css"
>
<link
rel=
"stylesheet"
href=
"static/style.css"
>
"""
print "
</head><body>
"
if password is None:
print "
<h1>
This is the monitoring interface
</h1>
"
else:
print "
<h1>
Error
</h1><p>
Wrong password
</p>
"
print """
<p>
Please enter the monitor_password in the next field to access the data
</p>
<form
action=
"/index.cgi"
method=
"post"
class=
"pure-form-aligned"
>
Password :
<input
type=
"password"
name=
"password"
>
<button
type=
"submit"
class=
"pure-button pure-button-primary"
>
Access
</button>
</form>
</body></html>
"""
# redirection to the required script/page
else:
print
if "posting-script" in form:
forward_form()
elif "script" in form:
return_document()
else:
html_base = jinja2.Template(open('{{ index_template }}').read())
print
print html_base.render(tree=make_menu(), default_page="{{ default_page }}", monitor_rewrite=monitor_rewrite)
stack/monitor2/webfile-directory/index.html.jinja2
deleted
100644 → 0
View file @
48d970f4
<html>
<head>
<title>
Monitoring Interface
</title>
<link
rel=
"stylesheet"
href=
"static/pure-min.css"
>
<link
rel=
"stylesheet"
href=
"static/style.css"
>
<script
src=
"static/jquery-1.10.2.min.js"
></script>
<script
src=
"static/script.js"
></script>
</head>
<body>
<div
id=
"div-menu"
>
<h1>
Monitoring
</h1>
<div
id=
"script-categories"
class=
"pure-menu pure-menu-open"
>
<ul>
{% for category in tree %}
<li
class=
"pure-menu-heading category"
>
{{ category }}
</li>
{% for script in tree[category] %}
<li><a
href=
"{{ category }}/{{ script }}"
class=
"script"
>
{{ script }}
</a></li>
{% endfor %}
{% endfor %}
<li
class=
"pure-menu-heading category"
>
Files
</li>
<li><a
href=
"./private/"
class=
"link"
>
User: admin
</br>
Password is yours
</a></li>
<li
class=
"pure-menu-heading category"
>
Local Service
</li>
{% set rewrite_list = monitor_rewrite.split() %}
{% for path in rewrite_list %}
<li><a
href=
"./rewrite/{{path}}/"
class=
"link"
>
{{path}}
</a></li>
{% endfor %}
</ul>
</div>
</div>
<div
id=
"content"
>
<iframe
src=
"{{ default_page }}"
>
</iframe>
</div>
</body>
</html>
stack/monitor2/webfile-directory/monitor-password.cgi.in
deleted
100644 → 0
View file @
48d970f4
#!{{ python_executable }}
import cgitb
cgitb.enable()
print "
<html><head>
"
print """
<script
type=
"text/javascript"
src=
"static/jquery-1.10.2.min.js"
></script>
<link
rel=
"stylesheet"
href=
"static/pure-min.css"
>
<link
rel=
"stylesheet"
href=
"static/style.css"
>
"""
print "
</head><body>
"
print "
<h1>
This is the monitoring interface
</h1>
"
print "
<h2>
Please set your password for later access
</h2>
"
print """
<form
action=
"/index.cgi"
method=
"post"
class=
"pure-form-aligned"
>
<div
class=
"pure-control-group"
>
<label
for=
"password"
>
Password*:
</label>
<input
placeholder=
"Set your password"
type=
"password"
name=
"password"
id=
"password"
></br>
</div><div
class=
"pure-control-group"
>
<label
for=
"password"
>
Verify Password*:
</label>
<input
placeholder=
"Verify password"
type=
"password"
name=
"password_2"
id=
"password_2"
></br>
</div><p
id=
"validate-status"
style=
"color:red"
></p>
<div
class=
"pure-controls"
>
<button
id=
"register-button"
type=
"submit"
class=
"pure-button pure-button-primary"
disabled
>
Access
</button></div>
</form>
<script
type=
"text/javascript"
src=
"static/monitor-register.js"
></script>
</body></html>
"""
stack/monitor2/webfile-directory/settings.cgi.in
deleted
100755 → 0
View file @
48d970f4
#!{{ python_executable }}
import cgi
import cgitb
import ConfigParser
import os
cgitb.enable()
form = cgi.FieldStorage()
print "
<html><head>
"
print "
<link
rel=
\"stylesheet\"
href=
\"static/pure-min.css\"
>
"
print "
<link
rel=
\"stylesheet\"
href=
\"static/style.css\"
>
"
print "
</head><body>
"
config_file = "{{ config_cfg }}"
if not os.path.exists(config_file):
print "Your software does
<b>
not
</b>
embed 0-knowledge. \
This interface is useless in this case
</body></html>
"
exit(0)
parser = ConfigParser.ConfigParser()
parser.read(config_file)
if not parser.has_section('public'):
print "
<p>
Your software does not use 0-knowledge settings.
</p></body></html>
"
exit(0)
for name in form:
if parser.has_option('public', name):
parser.set('public', name, form[name].value)
with open(config_file, 'w') as file:
parser.write(file)
if len(form) > 0:
try:
os.remove("{{ timestamp }}")
except OSError:
pass
print "
<h1>
Values that can be defined :
</h1>
"
print "
<form
action=
\"/index.cgi\"
method=
\"post\"
class=
\"pure-form-aligned\"
>
"
print "
<input
type=
\"hidden\"
name=
\"posting-script\"
value=
\"{{
pwd
}}/{{
this_file
}}\"
>
"
for option in parser.options("public"):
print "
<div
class=
\"pure-control-group\"
>
"
print "
<label
for=
\"%s\"
>
%s
</label>
" % (cgi.escape(option, quote=True), cgi.escape(option))
print "
<input
type=
\"text\"
name=
\"%s\"
value=
\"%s\"
>
" % (cgi.escape(option, quote=True), cgi.escape(parser.get('public', option), quote=True))
print "
</div>
"
print "
<div
class=
\"pure-controls\"
><button
type=
\"submit\"
class=
\"pure-button
\
pure-button-primary
\"
>
Save
</button></div></form>
"
print "
<br><h1>
Other values :
</h1>
"
print "
<form
class=
\"pure-form-aligned\"
>
"
for section in parser.sections():
if section != 'public':
for option in parser.options(section):
print "
<div
class=
\"pure-control-group\"
>
"
print "
<label
for=
\"%s\"
>
%s
</label>
" % (cgi.escape(option, quote=True), cgi.escape(option))
print "
<input
type=
\"text\"
name=
\"%s\"
value=
\"%s\"
readonly
>
" %(cgi.escape(option, quote=True), cgi.escape(parser.get(section, option), quote=True))
print "
</div>
"
print "
</form>
"
print "
</body></html>
"
stack/monitor2/webfile-directory/static/monitor-register.js
deleted
100644 → 0
View file @
48d970f4
$
(
window
).
load
(
function
(){
$
(
document
).
ready
(
function
()
{
$
(
"
#password_2
"
).
keyup
(
validate
);
});
function
validate
()
{
var
password1
=
$
(
"
#password
"
).
val
();
var
password2
=
$
(
"
#password_2
"
).
val
();
if
(
password1
==
password2
)
{
$
(
"
#register-button
"
).
removeAttr
(
"
disabled
"
);
$
(
"
#validate-status
"
).
attr
(
"
style
"
,
"
display:none
"
);
}
else
{
$
(
"
#register-button
"
).
attr
(
"
disabled
"
,
"
disabled
"
);
$
(
"
#validate-status
"
).
attr
(
"
style
"
,
""
).
text
(
"
Passwords do not match
"
);
}
}
});
\ No newline at end of file
stack/monitor2/webfile-directory/static/pure-min.css
deleted
100644 → 0
View file @
48d970f4
This diff is collapsed.
Click to expand it.
stack/monitor2/webfile-directory/static/script.js
deleted
100644 → 0
View file @
48d970f4
$
(
document
).
ready
(
function
()
{
function
doDataUrl
(
data
)
{
var
frame_content
=
document
.
getElementsByTagName
(
"
iframe
"
)[
0
].
contentWindow
;
var
b64
=
btoa
(
data
);
dataurl
=
'
data:text/html;base64,
'
+
b64
;
$
(
"
iframe
"
).
attr
(
'
src
'
,
dataurl
);
}
if
(
window
.
self
===
window
.
top
)
{
//not in an iframe
$
(
"
.script
"
).
click
(
function
(
e
)
{
e
.
preventDefault
();
var
message
=
$
(
this
).
attr
(
'
href
'
);
var
slash_pos
=
message
.
search
(
'
/
'
);
//let's differenciate kind of script called
if
(
slash_pos
===
-
1
||
slash_pos
===
0
)
{
url
=
message
;
}
else
{
url
=
'
/index.cgi
'
;
}
$
(
"
iframe
"
).
attr
(
'
src
'
,
url
+
'
?script=
'
+
encodeURIComponent
(
message
));
});
$
(
"
.link
"
).
click
(
function
(
e
)
{
e
.
preventDefault
();
var
url
=
$
(
this
).
attr
(
'
href
'
);
$
(
"
iframe
"
).
attr
(
'
src
'
,
url
);
});
}
else
{
//in an iframe
$
(
"
body
"
).
empty
();
}
});
stack/monitor2/webfile-directory/static/style.css
deleted
100644 → 0
View file @
48d970f4
body
{
padding
:
15px
;
}
.pure-menu
.pure-menu-heading
{
font-size
:
120%
;
}
#content
{
display
:
inline-block
;
min-width
:
72%
;
height
:
97%
;
margin-left
:
30px
;
}
#div-menu
{
display
:
inline-block
;
vertical-align
:
top
;
}
#div-menu
h1
{
text-align
:
center
;
}
iframe
{
width
:
100%
;
height
:
100%
;
margin
:
0px
;
padding
:
0px
;
border-style
:
none
;
}
stack/monitor2/webfile-directory/static/welcome.html
deleted
100644 → 0
View file @
48d970f4
<html>
<head>
<title>
Welcome to the Monitoring Interface
</title>
<link
rel=
"stylesheet"
href=
"pure-min.css"
>
<link
rel=
"stylesheet"
href=
"style.css"
>
</head>
<body>
<h1>
Welcome to your monitoring interface
</h1>
<p>
From this interface you can monitor, configure your instance
</p>
</body>
</html>
stack/monitor2/webfile-directory/status-history.cgi.in
deleted
100644 → 0
View file @
48d970f4
#!{{ python_executable }}
import cgi
import datetime
import os
import sqlite3
db_path = '{{ monitor_db_path }}'
status_history_length = '{{ status_history_length }}'
db = sqlite3.connect(db_path)
print """
<html><head>
<link
rel=
"stylesheet"
href=
"static/pure-min.css"
>
<link
rel=
"stylesheet"
href=
"static/style.css"
>
</head><body>
<h1>
Monitor Status History :
</h1>
"""
def get_date_from_timestamp(timestamp):
return datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
def print_individual_status(timestamp):
print "
<div><h3>
Failure on %s
</h3><ul>
" % get_date_from_timestamp(timestamp)
rows = db.execute("select status, element, output from individual_status where timestamp=?", (timestamp,))
for row in rows:
status, element, output = row
print "
<li>
%s , %s :
</br><pre>
%s
</pre></li>
" % (status, cgi.escape(element), cgi.escape(output))
print "
</ul></div>
"
if not os.path.exists(db_path):
print """No status history found
</p></body></html>
"""
exit(0)
failure_row_list = db.execute("select timestamp from status where status='FAILURE' order by timestamp desc limit ?", status_history_length )
for failure_row in failure_row_list:
timestamp, = failure_row
print_individual_status(timestamp)
print "
</body></html>
"
stack/monitor2/webfile-directory/status.cgi.in
deleted
100755 → 0
View file @
48d970f4
#!{{ python_executable }}
import cgi
import cgitb
import json
import os
import subprocess
def refresh():
command = ["{{ monitor_bin }}", "-a"]
subprocess.call(command)
cgitb.enable(display=0, logdir="/tmp/cgi.log")
form = cgi.FieldStorage()
json_file = "{{ json_file }}"
if not os.path.exists(json_file) or "refresh" in form:
refresh()
if not os.path.exists(json_file):
print """
<html><head>
<link
rel=
"stylesheet"
href=
"static/pure-min.css"
>
<link
rel=
"stylesheet"
href=
"static/style.css"
>
</head><body>
<h1>
Monitoring :
</h1>
No status file found
</p></body></html>
"""
exit(0)
result = json.load(open(json_file))
print "
<html><head>
"
print "
<link
rel=
\"stylesheet\"
href=
\"static/pure-min.css\"
>
"
print "
<link
rel=
\"stylesheet\"
href=
\"static/style.css\"
>
"
print "
</head><body>
"
print "
<h1>
Monitoring :
</h1>
"
print "
<form
action=
\"/index.cgi\"
method=
\"post\"
class=
\"pure-form-aligned\"
>
"
print "
<input
type=
\"hidden\"
name=
\"posting-script\"
value=
\"{{
pwd
}}/{{
this_file
}}\"
>
"
print "
<p><em>
Last time of monitoring process : %s
</em></p>
" % (result['datetime'])
del result['datetime']
print "
<div
class=
\"pure-controls\"
><button
type=
\"submit\"
class=
\"pure-button
\
pure-button-primary
\"
name=
\"refresh\"
value=
\"refresh\"
>
Refresh
</button></div></form>
"
print "
<br/>
"
print "
<h2>
These scripts and promises have failed :
</h2>
"
for r in result:
if result[r] != '':
print "
<h3>
%s
</h3><pre
style=
\"padding-left:30px;\"
>
%s
</pre>
" % (cgi.escape(r), cgi.escape(result[r]))
print "
<br/>
"
print "
<h2>
These scripts and promises were successful :
</h2>
"
print "
<ul>
"
for r in result:
if result[r] == '':
print "
<li>
%s
</li>
" % (r)
print "
</ul>
"
print "
</body></html>
"
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment