Add application files.

This commit is contained in:
root 2014-11-17 18:34:58 +01:00
parent a705608605
commit 2e89a950f1
9 changed files with 916 additions and 0 deletions

126
saltinventory.py Executable file
View file

@ -0,0 +1,126 @@
#!/usr/bin/python
#
# Requirements:
#
# * salt
# * python-netaddr
#
import salt.client
from netaddr import *
from jinja2 import Template, BaseLoader, TemplateNotFound, FileSystemLoader, Environment
# settings
config = {
'base_path': 'www/',
'host_page_path': 'www/hosts/',
'network_page_path': 'www/networks/',
'template_path': 'templates/',
}
# class definitions
class Inventorizer:
config = None
saltcmd = None
host_list = {}
network_list = {}
def __init__(self, config):
self.config = config
self.saltcmd = salt.client.LocalClient()
self.process()
def process(self):
self.collectHostData()
self.createHostList()
self.collectNetworkData()
self.createHostPages()
self.createNetworkList()
self.createNetworkPages()
def collectHostData(self):
self.host_list = self.saltcmd.cmd('*', 'grains.items')
def collectNetworkData(self):
result = self.saltcmd.cmd('*', 'network.interfaces')
for hostname, hostdata in result.iteritems():
for interface, interfacedata in hostdata.iteritems():
if interfacedata.has_key('inet'):
for netdata in interfacedata['inet']:
ip = IPNetwork(netdata['address'] + '/' + netdata['netmask'])
host = {'hostname': hostname, 'address': netdata['address'], 'netobj': ip}
self.addNetworkHost(ip, host)
if interfacedata.has_key('secondary'):
for netdata in interfacedata['secondary']:
ip = IPNetwork(netdata['address'] + '/' + netdata['netmask'])
host = {'hostname': hostname, 'address': netdata['address'], 'netobj': ip}
self.addNetworkHost(ip, host)
if interfacedata.has_key('inet6'):
for netdata in interfacedata['inet6']:
ip = IPNetwork(netdata['address'] + '/' + netdata['prefixlen'])
host = {'hostname': hostname, 'address': netdata['address'], 'netobj': ip}
self.addNetworkHost(ip, host)
def addNetworkHost(self, ip, host):
if self.network_list.has_key(ip.cidr):
self.network_list[ip.cidr].append(host)
else:
self.network_list[ip.cidr] = [host]
def createHostList(self):
fo = open(self.config['base_path'] + "hostlist.html", "wb")
env = Environment(loader = FileSystemLoader(config['template_path']))
template = env.get_template('hostlist_template.html')
fo.write(template.render({'hostlist': self.host_list}))
fo.close
def createHostPages(self):
for hostname, hostdata in self.host_list.iteritems():
self.createHostPage(hostname, hostdata)
def createHostPage(self, hostname, hostdata):
fo = open(self.config['host_page_path'] + hostname + ".html", "wb")
env = Environment(loader = FileSystemLoader(config['template_path']))
template = env.get_template('host_template.html')
fo.write(template.render(hostdata))
fo.close
def createNetworkList(self):
fo = open(self.config['base_path'] + "networklist.html", "wb")
env = Environment(loader = FileSystemLoader(config['template_path']))
env.filters['len'] = self.lenFilter
template = env.get_template('networklist_template.html')
fo.write(template.render({'networklist': self.network_list}))
fo.close
def createNetworkPages(self):
for network, networkdata in self.network_list.iteritems():
self.createNetworkPage(network, networkdata)
def createNetworkPage(self, network, networkdata):
fo = open(self.config['network_page_path'] + str(network.cidr).replace('/', '_') + ".html", "wb")
env = Environment(loader = FileSystemLoader(config['template_path']))
env.filters['replace'] = self.replaceFilter
template = env.get_template('network_template.html')
fo.write(template.render({'network': network, 'data': networkdata}))
fo.close
def lenFilter(self, list):
return len(list)
def replaceFilter(self, str, old, new):
return str(str).replace(old, new)
# main program
inv = Inventorizer(config)

View file

@ -0,0 +1,59 @@
<html>
<head>
<title>{{ id }}</title>
<link rel="stylesheet" type="text/css" href="http://cdn.datatables.net/1.10.4/css/jquery.dataTables.css">
<link rel="stylesheet" type="text/css" href="../res/style.css">
<script type="text/javascript" language="javascript" src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script type="text/javascript" language="javascript" src="../res/jquery.number.min.js"></script>
<script type="text/javascript" language="javascript" src="http://cdn.datatables.net/1.10.4/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" class="init">
</script>
</head>
<body>
<div class="container">
<header class="header clearfix">
<div class="logo">Inventory | {{ id }}</div>
<nav class="menu_main">
<ul>
<li><a href="../hostlist.html">Hostlist</a></li>
<li><a href="../networklist.html">Networks</a></li>
</ul>
</nav>
</header>
</div>
<article class="article clearfix">
<h2>System information</h2>
<h3>Generic</h3>
<ul>
<li>Operating System: {{ kernel }} ({{ kernelrelease }})</li>
<li>Distribution: {{ lsb_distrib_description }}</li>
<li>Machine type: {% if virtual != "physical" %} Virtual ({{ virtual }}) {% else %} Physical {% endif %}</li>
<li>Architecture: {{ osarch }}</li>
<li>Salt version: {{ saltversion }}</li>
</ul>
<h3>CPU</h3>
<ul>
<li>Numer of CPUs: {{ num_cpus }}</li>
<li>CPU architecture: {{ cpuarch }}</li>
<li>CPU model: {{ cpu_model }}</li>
</ul>
<h3>Memory</h3>
<ul>
<li>Total memory: {{ mem_total }}</li>
</ul>
<h3>Storage</h3>
<ul>
<li></li>
<li></li>
</ul>
<h3>Network</h3>
<ul>
<li></li>
<li></li>
</ul>
</article>
</body>
</html>

View file

@ -0,0 +1,137 @@
<html>
<head>
<title>Host list</title>
<link rel="stylesheet" type="text/css" href="http://cdn.datatables.net/1.10.4/css/jquery.dataTables.css">
<link rel="stylesheet" type="text/css" href="res/style.css">
<script type="text/javascript" language="javascript" src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script type="text/javascript" language="javascript" src="res/jquery.number.min.js"></script>
<script type="text/javascript" language="javascript" src="http://cdn.datatables.net/1.10.4/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" class="init">
$(document).ready(function() {
// search box for each column (http://www.datatables.net/examples/api/multi_filter.html) - Part 1
$('#hostlist thead td').each( function () {
var title = $('#hostlist thead th').eq( $(this).index() ).text();
$(this).html( '<input type="text" placeholder="Search '+title+'" />' );
} );
var table = $('#hostlist').DataTable({
"footerCallback": function ( row, data, start, end, display ) {
var api = this.api(), data;
// Remove the formatting to get integer data for summation
var intVal = function ( i ) {
return typeof i === 'string' ?
i.replace(/[\$,]/g, '')*1 :
typeof i === 'number' ?
i : 0;
};
// Total over this page
// mem
memTotal = api
.column( 2, { page: 'current'} )
.data()
.reduce( function (a, b) {
return intVal(a) + intVal(b);
}, 0 );
// cpu
cpuTotal = api
.column( 3, { page: 'current'} )
.data()
.reduce( function (a, b) {
return intVal(a) + intVal(b);
}, 0 );
// Update footer
$( api.column( 2 ).footer() ).html(
$.number(memTotal, 0, ",", ".")
);
$( api.column( 3 ).footer() ).html(
cpuTotal
);
}
});
// search box for each column (http://www.datatables.net/examples/api/multi_filter.html) - Part 2
// Apply the search
table.columns().eq( 0 ).each( function ( colIdx ) {
$( 'input', table.column( colIdx ).header() ).on( 'keyup change', function () {
table
.column( colIdx )
.search( this.value, true )
.draw();
} );
} );
$('td.number').number( true, 0, ",", "." );
} );
</script>
</head>
<body>
<div class="container">
<header class="header clearfix">
<div class="logo">Inventory | Host list</div>
<nav class="menu_main">
<ul>
<li class="active"><a href="hostlist.html">Hostlist</a></li>
<li><a href="networklist.html">Networks</a></li>
</ul>
</nav>
</header>
</div>
<article class="article clearfix">
<h2>Hosts</h2>
<table id="hostlist" class="display">
<thead>
<tr>
<th>Name</th>
<th>OS</th>
<th>RAM (MB)</th>
<th># CPUs</th>
</tr>
<tr>
<td>Name</th>
<td>OS</th>
<td>RAM</th>
<td># CPUs</th>
</tr>
</thead>
<tbody>
{% for name, data in hostlist.iteritems() %}
<tr>
<td><a href = 'hosts/{{ name }}.html'>{{ name }}</a></td>
<td>{{ data.lsb_distrib_description }}</td>
<td class="right number">{{ data.mem_total }}</td>
<td class="right">{{ data.num_cpus }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th></th>
<th></th>
<th class="right"></th>
<th class="right"></th>
</tr>
</tfoot>
</table>
</article>
</body>
</html>

View file

@ -0,0 +1,60 @@
<html>
<head>
<title>{{ network.cidr }}</title>
<link rel="stylesheet" type="text/css" href="http://cdn.datatables.net/1.10.4/css/jquery.dataTables.css">
<link rel="stylesheet" type="text/css" href="../res/style.css">
<script type="text/javascript" language="javascript" src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script type="text/javascript" language="javascript" src="../res/jquery.number.min.js"></script>
<script type="text/javascript" language="javascript" src="http://cdn.datatables.net/1.10.4/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" class="init">
$(document).ready(function() {
var table = $('#adresses').DataTable();
});
</script>
</head>
<body>
<div class="container">
<header class="header clearfix">
<div class="logo">Inventory | {{ network.cidr }}</div>
<nav class="menu_main">
<ul>
<li><a href="../hostlist.html">Hostlist</a></li>
<li><a href="../networklist.html">Networks</a></li>
</ul>
</nav>
</header>
</div>
<article class="article clearfix">
<h2>Adresses</h2>
<table id="adresses" class="display">
<thead>
<tr>
<th>Address</th>
<th>Host</th>
</tr>
<tr>
<td>Address</th>
<td>Host</th>
</tr>
</thead>
<tbody>
{% for netdata in data %}
<tr>
<td>{{ netdata['address'] }}</td>
<td><a href="../hosts/{{ netdata['hostname'] }}.html">{{ netdata['hostname'] }}</a></td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th></th>
<th></th>
</tr>
</tfoot>
</table>
</article>
</body>
</html>

View file

@ -0,0 +1,67 @@
<html>
<head>
<title>Network list</title>
<link rel="stylesheet" type="text/css" href="http://cdn.datatables.net/1.10.4/css/jquery.dataTables.css">
<link rel="stylesheet" type="text/css" href="res/style.css">
<script type="text/javascript" language="javascript" src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script type="text/javascript" language="javascript" src="res/jquery.number.min.js"></script>
<script type="text/javascript" language="javascript" src="http://cdn.datatables.net/1.10.4/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" class="init">
$(document).ready(function() {
var table = $('#networklist').DataTable();
});
</script>
</head>
<body>
<div class="container">
<header class="header clearfix">
<div class="logo">Inventory | Network list</div>
<nav class="menu_main">
<ul>
<li><a href="hostlist.html">Hostlist</a></li>
<li class="active"><a href="networklist.html">Networks</a></li>
</ul>
</nav>
</header>
</div>
<article class="article clearfix">
<h2>Networks</h2>
<table id="networklist" class="display">
<thead>
<tr>
<th>Network</th>
<th>Usage</th>
</tr>
<tr>
<td>Network</th>
<td>Usage</th>
</tr>
</thead>
<tbody>
{% for network, data in networklist.iteritems() %}
<tr>
<td><a href = 'networks/{{ network.cidr|replace('/', '_') }}.html'>{{ network.cidr }}</a></td>
<td>{{ data|len }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th></th>
<th></th>
</tr>
</tfoot>
</table>
</article>
</body>
</html>

2
www/res/jquery.number.min.js vendored Normal file

File diff suppressed because one or more lines are too long

48
www/res/reset.css Normal file
View file

@ -0,0 +1,48 @@
/* CSS reset. Based on HTML5 boilerplate reset http://html5boilerplate.com/ */
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display:block; }
audio[controls], canvas, video { display:inline-block; *display:inline; *zoom:1; }
html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
body { margin: 0; font-size: 14px; line-height: 1.4; }
body, button, input, select, textarea { font-family:sans-serif; }
a:focus { outline:thin dotted; }
a:hover, a:active { outline:0; }
abbr[title] { border-bottom:1px dotted; }
b, strong { font-weight:bold; }
blockquote { margin:1em 40px; }
dfn { font-style:italic; }
hr { display:block; height:1px; border:0; border-top:1px solid #ccc; margin:1em 0; padding:0; }
ins { background:#ff9; color:#000; text-decoration:none; }
mark { background:#ff0; color:#000; font-style:italic; font-weight:bold; }
pre, code, kbd, samp { font-family:monospace, monospace; _font-family:'courier new', monospace; font-size:1em; }
pre { white-space:pre; white-space:pre-wrap; word-wrap:break-word; }
q { quotes:none; }
q:before, q:after { content:""; content:none; }
small { font-size:85%; }
sub, sup { font-size:75%; line-height:0; position:relative; vertical-align:baseline; }
sup { top:-0.5em; }
sub { bottom:-0.25em; }
ul, ol { margin:1em 0; padding:0 0 0 2em; }
dd { margin:0 0 0 40px; }
nav ul, nav ol { list-style:none; margin:0; padding:0; }
img { border:0; -ms-interpolation-mode:bicubic; }
svg:not(:root) { overflow:hidden;}
figure { margin:0; }
form { margin:0; }
fieldset { border:0; margin:0; padding:0; }
legend { border:0; *margin-left:-7px; padding:0; }
label { cursor:pointer; }
button, input, select, textarea { font-size:100%; margin:0; vertical-align:baseline; *vertical-align:middle; }
button, input { line-height:normal; *overflow:visible; }
button, input[type="button"], input[type="reset"], input[type="submit"] { cursor:pointer; -webkit-appearance:button; }
input[type="checkbox"], input[type="radio"] { box-sizing:border-box; }
input[type="search"] { -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; }
button::-moz-focus-inner, input::-moz-focus-inner { border:0; padding:0; }
textarea { overflow:auto; vertical-align:top; }
input:valid, textarea:valid { }
input:invalid, textarea:invalid { background-color:#f0dddd; }
table { border-collapse:collapse; border-spacing:0; }
.hidden { display:none; visibility:hidden; }
.clearfix:before, .clearfix:after { content:""; display:table; }
.clearfix:after { clear:both; }
.clearfix { zoom:1; }
/* End CSS reset */

255
www/res/skin.css Normal file
View file

@ -0,0 +1,255 @@
/* Skin "iSimple" by Renat Rafikov */
body {
background:#f2f2f2;
font-family:arial, sans-serif;
}
a { color:#0085c5; }
a:hover { text-decoration:none; }
a:visited { color:#4a00c5; }
ul li, ol li {
padding:0 0 0.4em 0;
}
.container {
max-width:1300px;
margin:0 auto;
}
.header {
margin:1px 0 0.5em 0;
padding:1.5em 3% 0 3%;
}
.logo {
float:left;
display:inline-block;
font-size:18px;
text-shadow:1px 1px 1px #ffffff;
}
.menu_main {
width:50%;
float:right;
text-align:right;
margin:0.3em 0 0 0;
font-size:12px;
}
.menu_main a,
.menu_main a:hover {
color:#0085c5;
}
.menu_main li {
display:inline-block;
margin:0 0 0 4px;
}
.menu_main li.active,
.menu_main li.active a {
color:#000;
text-decoration:none;
cursor:default;
}
.info {
padding:0 1% 1em 1%;
}
.hero {
background:#fff;
border:1px solid #fff;
-webkit-border-radius:5px;
-moz-border-radius:5px;
border-radius:5px;
-webkit-box-shadow:#8b8b8b 0px 0px 5px inset;
-moz-box-shadow:#8b8b8b 0px 0px 5px inset;
box-shadow:#8b8b8b 0px 0px 5px inset;
padding:15px 0 15px 2%;
margin:0 0 15px 0;
}
.hero h1 {
font-size:24px;
font-size:18px;
color:#3d3d3d;
}
.article {
background:#fff;
border:1px solid #cbcbcb;
-webkit-border-radius:5px;
-moz-border-radius:5px;
border-radius:5px;
-webkit-box-shadow:#8b8b8b 0px 0px 3px;
-moz-box-shadow:#8b8b8b 0px 0px 3px;
box-shadow:#8b8b8b 0px 0px 3px;
padding:15px 0 15px 2%;
}
.footer {
padding:1em 3% 3em 3%;
color:#717171;
font-size:12px;
}
.copyright {
width:49%;
float:left;
text-shadow:1px 1px 1px #ffffff;
}
.menu_bottom {
width:50%;
float:right;
text-align:right;
margin:0;
padding:0;
font-size:12px;
}
.menu_bottom a,
.menu_bottom a:hover {
color:#0085c5;
}
.menu_bottom li {
display:inline-block;
margin:0 0 0 4px;
}
.menu_bottom li.active,
.menu_bottom li.active a {
color:#666;
text-decoration:none;
cursor:default;
}
h1 {
font-size:22px;
}
h1, h2, h3, h4 {
font-weight:normal;
}
h5, h6 {
font-weight:bold;
}
.form label {
display:inline-block;
padding:0 0 4px 0;
}
a.button,
.button {
border:0;
text-align:center;
text-decoration:none;
-webkit-border-radius:4px;
-moz-border-radius:4px;
border-radius:4px;
-webkit-box-shadow:#999 0px 0px 1px;
-moz-box-shadow:#999 0px 0px 1px;
box-shadow:#999 0px 0px 1px;
background:#4aa6d6;
background:-webkit-gradient(linear, 0 0, 0 bottom, from(#1f7daa), to(#4aa6d6));
background:-webkit-linear-gradient(#1f7daa, #4aa6d6);
background:-moz-linear-gradient(#1f7daa, #4aa6d6);
background:-ms-linear-gradient(#1f7daa, #4aa6d6);
background:-o-linear-gradient(#1f7daa, #4aa6d6);
background:linear-gradient(#1f7daa, #4aa6d6);
color:#fff;
padding:10px 20px;
font-family:verdana, sans-serif;
text-shadow:1px 1px 1px #12455d;
display:inline-block;
}
a.button:hover,
.button:hover {
color:#fff;
background:-webkit-gradient(linear, 0 0, 0 bottom, from(#4aa6d6), to(#1f7daa));
background:-webkit-linear-gradient(#4aa6d6, #1f7daa);
background:-moz-linear-gradient(#4aa6d6, #1f7daa);
background:-ms-linear-gradient(#4aa6d6, #1f7daa);
background:-o-linear-gradient(#4aa6d6, #1f7daa);
background:linear-gradient(#4aa6d6, #1f7daa);
}
a.button:active,
.button:active {
color:#093950;
text-shadow:1px 1px 1px #7ac8f0;
-webkit-box-shadow:#093950 0px 2px 3px inset;
-moz-box-shadow:#093950 0px 2px 3px inset;
box-shadow:#093950 0px 2px 3px inset;
}
.table {
width:100%;
}
.table th {
padding:5px 7px;
font-weight:bold;
text-align:left;
font-size:0.9em;
border-bottom:1px solid #ddd;
}
.table td {
padding:9px 7px;
border-left:1px solid #ddd;
}
.table tr td:first-child {border-left:0;}
.table tr {
border-bottom:1px solid #fbfbfb;
}
.table tr:nth-child(even) {
background:#F2F2F2;
}
.table tr:last-child {
border:0;
}
.warning {
border:1px solid #ec252e;
background:#ec252e;
color:#fff;
padding:8px 14px;
background:-webkit-gradient(linear, 0 0, 0 bottom, from(#ec252e), to(#F05057));
background:-webkit-linear-gradient(#ec252e, #F05057);
background:-moz-linear-gradient(#ec252e, #F05057);
background:-ms-linear-gradient(#ec252e, #F05057);
background:-o-linear-gradient(#ec252e, #F05057);
background:linear-gradient(#ec252e, #F05057);
-webkit-border-radius:8px;
-moz-border-radius:8px;
border-radius:8px;
}
.success {
border:1px solid #6e9e30;
color:#fff;
background:#0bbe2e;
padding:8px 14px;
background:-webkit-gradient(linear, 0 0, 0 bottom, from(#6e9e30), to(#87c03b));
background:-webkit-linear-gradient(#6e9e30, #87c03b);
background:-moz-linear-gradient(#6e9e30, #87c03b);
background:-ms-linear-gradient(#6e9e30, #87c03b);
background:-o-linear-gradient(#6e9e30, #87c03b);
background:linear-gradient(#6e9e30, #87c03b);
-webkit-border-radius:8px;
-moz-border-radius:8px;
border-radius:8px;
}
.message {
border:1px solid #2180ff;
color:#1f49bf;
background:#bcd9ff;
padding:8px 14px;
-webkit-border-radius:8px;
-moz-border-radius:8px;
border-radius:8px;
}
@media only screen and (max-width:480px) { /* Smartphone custom styles */
}
@media only screen and (max-width:768px) { /* Tablet custom styles */
}

162
www/res/style.css Normal file
View file

@ -0,0 +1,162 @@
/* "Simpliste" template. Renat Rafikov. http://cssr.ru/simpliste/ */
@import url('reset.css');
@import url('skin.css');
/* Columns
-------
.col_33 | .col_33 | .col_33
.clearfix
-------
.col_75 | .col_25
.clearfix
-------
.col_66 | .col_33
.clearfix
-------
.col_50 | .col_50
.clearfix
-------
.col_100
-------
*/
.col_25 {
width:23%;
margin:0 2% 0 0;
float:left;
}
.col_33 {
width:31%;
margin:0 2% 0 0;
float:left;
}
.col_50 {
width:48%;
margin:0 2% 0 0;
float:left;
}
.col_66 {
width:64%;
margin:0 2% 0 0;
float:left;
}
.col_75 {
width:73%;
margin:0 2% 0 0;
float:left;
}
.col_100 {
width:98%;
margin:0 2% 0 0;
}
.col_25.wrap { width:25%; margin:0;}
.col_33.wrap { width:33%; margin:0;}
.col_50.wrap { width:50%; margin:0;}
.col_66.wrap { width:66%; margin:0;}
.col_75.wrap { width:75%; margin:0;}
.col_100.wrap { width:100%; margin:0;}
/* End columns */
/* Helper classes */
.center {text-align:center;}
.left {text-align:left;}
.right {text-align:right;}
.img_floatleft {float:left; margin:0 10px 5px 0;}
.img_floatright {float:right; margin:0 0 5px 10px;}
.img {max-width:100%;}
/* End helper classes */
a.button { color:auto; }
@media only screen and (max-width:480px) { /* Smartphone */
.header {
margin-bottom:0;
}
.logo{
display:block;
float:none;
text-align:center;
}
.menu_main {
width:100%;
text-align:center;
float:none;
padding:0;
margin:1em 0 0 0;
}
.menu_main a {
display:inline-block;
padding:7px;
}
.copyright {
width:100%;
float:none;
text-align:center;
}
.footer {
padding-bottom:0;
}
.menu_bottom {
width:100%;
float:none;
text-align:center;
margin:1em 0 0 0;
padding:0;
}
.menu_bottom a {
display:inline-block;
padding:6px;
}
.form textarea {
width:100%;
}
.form label {
padding:10px 0 8px 0;
}
}
@media only screen and (max-width:768px) { /* Tablet */
.col_25,
.col_33,
.col_66,
.col_50 ,
.col_75 {
width:98%;
float:none;
}
.form label {
padding:10px 0 8px 0;
}
}
@media print { /* Printer */
* { background:transparent !important; color:black !important; text-shadow:none !important; filter:none !important; -ms-filter:none !important; }
a, a:visited { color:#444 !important; text-decoration:underline; }
a[href]:after { content:" (" attr(href) ")"; }
abbr[title]:after { content:" (" attr(title) ")"; }
pre, blockquote { border:1px solid #999; page-break-inside:avoid; }
thead { display:table-header-group; }
tr, img { page-break-inside:avoid; }
img { max-width:100% !important; }
@page { margin:0.5cm; }
p, h2, h3 { orphans:3; widows:3; }
h2, h3{ page-break-after:avoid; }
.header, .footer, .form {display:none;}
.col_33, .col_66, .col_50 { width:98%; float:none; }
}