basic-report 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- basic_report/__init__.py +10 -0
- basic_report/configs/default.yaml +185 -0
- basic_report/css_and_scripts/bootstrap.min.css +7 -0
- basic_report/css_and_scripts/bootstrap.min.js +7 -0
- basic_report/css_and_scripts/dataTables.bootstrap4.min.js +8 -0
- basic_report/css_and_scripts/jquery-3.3.1.slim.min.js +2 -0
- basic_report/css_and_scripts/jquery.dataTables.min.js +166 -0
- basic_report/css_and_scripts/popper.min.js +5 -0
- basic_report/page.py +827 -0
- basic_report/report.py +404 -0
- basic_report/templates/accordion.css +20 -0
- basic_report/templates/accordion.html +20 -0
- basic_report/templates/base.html +59 -0
- basic_report/templates/colors.css +30 -0
- basic_report/templates/columns.html +9 -0
- basic_report/templates/global_links.html +7 -0
- basic_report/templates/header.html +5 -0
- basic_report/templates/image.html +16 -0
- basic_report/templates/list.html +7 -0
- basic_report/templates/navbar.css +17 -0
- basic_report/templates/navbar_left.html +32 -0
- basic_report/templates/navbar_right.html +32 -0
- basic_report/templates/navbar_top.html +25 -0
- basic_report/templates/plot.html +4 -0
- basic_report/templates/report_ball_section.html +80 -0
- basic_report/templates/report_header.html +6 -0
- basic_report/templates/site.css +5 -0
- basic_report/templates/sublevel.html +7 -0
- basic_report/templates/table.css +51 -0
- basic_report/templates/table.html +55 -0
- basic_report/templates/tabs.css +20 -0
- basic_report/templates/tabs.html +29 -0
- basic_report/templates/text.html +3 -0
- basic_report/utils.py +246 -0
- basic_report-0.1.0.dist-info/METADATA +194 -0
- basic_report-0.1.0.dist-info/RECORD +38 -0
- basic_report-0.1.0.dist-info/WHEEL +4 -0
- basic_report-0.1.0.dist-info/licenses/LICENSE.md +21 -0
basic_report/report.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import shutil
|
|
3
|
+
import yaml
|
|
4
|
+
from loguru import logger
|
|
5
|
+
from pandas import DataFrame
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Union, Optional, Literal, Callable
|
|
8
|
+
|
|
9
|
+
from . import CONFIG_DIR, SCRIPTS_DIR
|
|
10
|
+
from .page import ReportPage
|
|
11
|
+
from .utils import default, verify_color_mode, ColorMap
|
|
12
|
+
|
|
13
|
+
#----------------------------------------------------------------------------------------------------------------------#
|
|
14
|
+
# region REPORT
|
|
15
|
+
#----------------------------------------------------------------------------------------------------------------------#
|
|
16
|
+
class Report:
|
|
17
|
+
""" Main report object """
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
report_dir: Union[str, Path],
|
|
21
|
+
report_title: Optional[str]=None,
|
|
22
|
+
report_date: Optional[Union[str, int, datetime.date, datetime.datetime]]=None,
|
|
23
|
+
config_file: Optional[Union[str, Path]]=None,
|
|
24
|
+
color_mode: str='light',
|
|
25
|
+
text_color: Optional[str]=None,
|
|
26
|
+
):
|
|
27
|
+
""" Initialize a new report
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
report_dir (Union[str, Path]): The main output dir which will contain the report
|
|
31
|
+
report_title (Optional[str]): The report title, if None is given it will be called `Unnamed Report`
|
|
32
|
+
report_date (Optional[Union[str, int, datetime.date, datetime.datetime]]): The report date, if none is given
|
|
33
|
+
today will be used
|
|
34
|
+
config_file (Optional[Union[str, Path]]): Path to the user config file that will be used. If none is given
|
|
35
|
+
the default config file will be read.
|
|
36
|
+
color_mode (str): Either `light` or `dark.
|
|
37
|
+
text_color (Optional[str]): Override for the text color of the report. For a more granular approach use the
|
|
38
|
+
config file
|
|
39
|
+
"""
|
|
40
|
+
self.report_date = default(report_date, datetime.date.today())
|
|
41
|
+
self.report_title = default(report_title, 'Unnamed Report')
|
|
42
|
+
self.report_dir = Path(report_dir)
|
|
43
|
+
self.config = self._load_config(config_file)
|
|
44
|
+
self.color_map = ColorMap(self.config, color_mode, text_color)
|
|
45
|
+
self.color_mode = color_mode
|
|
46
|
+
self.text_color = text_color
|
|
47
|
+
|
|
48
|
+
self.pages: dict[str, ReportPage] = {}
|
|
49
|
+
self.add_page('main', subpage=False, color_mode=color_mode)
|
|
50
|
+
self.set_current_page('main')
|
|
51
|
+
|
|
52
|
+
self.global_links = []
|
|
53
|
+
|
|
54
|
+
#---------------#
|
|
55
|
+
# Magic Methods #
|
|
56
|
+
#---------------#
|
|
57
|
+
def __getitem__(self, key: str) -> ReportPage:
|
|
58
|
+
""" Simple access to the subpages
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
key (str): The page name to access
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
ReportPage - The page object for the given page name
|
|
65
|
+
"""
|
|
66
|
+
return self.pages[key]
|
|
67
|
+
|
|
68
|
+
#---------#
|
|
69
|
+
# Private #
|
|
70
|
+
#---------#
|
|
71
|
+
def _load_config(self, cfg_file: Optional[Union[str, Path]]=None) -> dict[str, Any]:
|
|
72
|
+
""" Load a report config file
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
cfg_file (Optional[Union[str, Path]]): The config file to load. If none is given the default config file
|
|
76
|
+
will be read
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
dict[str, Any] - The config file content as dict
|
|
80
|
+
"""
|
|
81
|
+
# Read default config
|
|
82
|
+
default_cfg_file = CONFIG_DIR / 'default.yaml'
|
|
83
|
+
if not default_cfg_file.exists():
|
|
84
|
+
msg = f'Could not find default config file {default_cfg_file}!'
|
|
85
|
+
logger.error(msg)
|
|
86
|
+
raise RuntimeError(msg)
|
|
87
|
+
with open(default_cfg_file, 'r') as f:
|
|
88
|
+
default_cfg = yaml.load(f, Loader=yaml.BaseLoader)
|
|
89
|
+
|
|
90
|
+
# Read custom config if given
|
|
91
|
+
if cfg_file is not None:
|
|
92
|
+
cfg_file = Path(cfg_file)
|
|
93
|
+
if not cfg_file.exists():
|
|
94
|
+
msg = f'Could not find default config file {default_cfg_file}!'
|
|
95
|
+
logger.error(msg)
|
|
96
|
+
raise RuntimeError(msg)
|
|
97
|
+
with open(cfg_file, 'r') as f:
|
|
98
|
+
custom_cfg = yaml.load(f, Loader=yaml.BaseLoader)
|
|
99
|
+
else:
|
|
100
|
+
custom_cfg = {}
|
|
101
|
+
|
|
102
|
+
cfg = default_cfg
|
|
103
|
+
for k,v in custom_cfg.items():
|
|
104
|
+
if k != 'custom_colors':
|
|
105
|
+
cfg[k] = v
|
|
106
|
+
else:
|
|
107
|
+
for ck,cv in v.items():
|
|
108
|
+
if ck not in cfg['custom_colors']:
|
|
109
|
+
cfg['custom_colors'][ck] = cv
|
|
110
|
+
else:
|
|
111
|
+
msg = (f'Tried to set color with name {ck}, which is already defined by the report class itself'
|
|
112
|
+
f'Please rename the color and try again.')
|
|
113
|
+
logger.error(msg)
|
|
114
|
+
raise RuntimeError(msg)
|
|
115
|
+
|
|
116
|
+
return cfg
|
|
117
|
+
|
|
118
|
+
#-----#
|
|
119
|
+
# API #
|
|
120
|
+
#-----#
|
|
121
|
+
# Report structure methods
|
|
122
|
+
# ------------------------
|
|
123
|
+
def add_page(self, name: str, subpage: bool=True, color_mode: Optional[str]=None):
|
|
124
|
+
""" Add a new page to the report
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
name (str): The name of the report page
|
|
128
|
+
subpage (bool): Indicates this is a subpage of the report
|
|
129
|
+
color_mode (Optional[str]): Color mode of the page, either `light` or `dark`
|
|
130
|
+
"""
|
|
131
|
+
color_mode = verify_color_mode(default(color_mode, self.color_mode))
|
|
132
|
+
self.pages[name] = ReportPage(self.report_dir, name, self.config, self.color_map, subpage, color_mode)
|
|
133
|
+
|
|
134
|
+
def dump(self):
|
|
135
|
+
""" Save the report to file and add the CSS files """
|
|
136
|
+
# Dump all of the report pages
|
|
137
|
+
for page in self.pages.values():
|
|
138
|
+
if self.global_links:
|
|
139
|
+
page._make_global_link_navbar(self.global_links) #noqa: SLF001
|
|
140
|
+
page._dump() #noqa: SLF001
|
|
141
|
+
|
|
142
|
+
# Add the common css files and scripts from the package and the ones created by the color map
|
|
143
|
+
src = SCRIPTS_DIR
|
|
144
|
+
dst = self.report_dir / 'css_and_scripts'
|
|
145
|
+
shutil.copytree(src, dst)
|
|
146
|
+
|
|
147
|
+
self.color_map.make_css_files(dst)
|
|
148
|
+
|
|
149
|
+
def set_current_page(self, page_name: str):
|
|
150
|
+
""" Set the current page that is being worked on
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
page_name (str): The page you want to switch to
|
|
154
|
+
"""
|
|
155
|
+
if page_name not in self.pages:
|
|
156
|
+
msg = f'Unkown page {page_name}. Known: {self.pages.keys()}'
|
|
157
|
+
logger.error(msg)
|
|
158
|
+
raise RuntimeError(msg)
|
|
159
|
+
|
|
160
|
+
self.current: ReportPage = self.pages[page_name]
|
|
161
|
+
|
|
162
|
+
# Headers & special sections
|
|
163
|
+
# --------------------------
|
|
164
|
+
def add_report_header(self, include_date: bool=True, include_created_at: bool=True, color: Optional[str]=None):
|
|
165
|
+
""" Add a report header to the current page
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
include_date (bool): Add the current date to the report
|
|
169
|
+
include_created_at (bool): Include a subheader with the exact creation time of the report
|
|
170
|
+
color (Optional[str]): The backgroound color for the header
|
|
171
|
+
"""
|
|
172
|
+
self.current.add_report_header(self.report_title, self.report_date, include_date, include_created_at, color)
|
|
173
|
+
|
|
174
|
+
def add_error_warning_info_section(self, **kwargs):
|
|
175
|
+
""" Add a section detailing errors, warnings, and info """
|
|
176
|
+
self.current.add_error_warning_info_section(**kwargs)
|
|
177
|
+
|
|
178
|
+
def add_header(self, header_text: str, color: Optional[str]=None):
|
|
179
|
+
""" Add a header to the current page
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
header_text (str): Header text. You can use any valid HTML code inside the text, e.g., links and such.
|
|
183
|
+
color (Optional[str]): Color of the header box
|
|
184
|
+
"""
|
|
185
|
+
self.current.add_header(header_text, color)
|
|
186
|
+
|
|
187
|
+
def add_sub_header(self, header_text: str, color: Optional[str]=None, sub_level: Optional[int]=None):
|
|
188
|
+
""" Add a sub header to the current page
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
header_text (str): Header text. You can use any valid HTML code inside the text, e.g., links and such.
|
|
192
|
+
color (Optional[str]): Color of the header box
|
|
193
|
+
sub_level (Optional[int]): Shrinks the width of the header box. Levels can be 1 to 5. The higher the level
|
|
194
|
+
the smaller the box
|
|
195
|
+
"""
|
|
196
|
+
self.current.add_sub_header(header_text, color, sub_level)
|
|
197
|
+
|
|
198
|
+
# Miscellaneous elements
|
|
199
|
+
# ----------------------
|
|
200
|
+
def add_text(self, text: str, color: Optional[str]=None, align: Optional[Literal['left', 'center', 'right']]=None):
|
|
201
|
+
""" Add (colored) text to the page
|
|
202
|
+
|
|
203
|
+
This will create a new paragraph. So if you want to mix colors within
|
|
204
|
+
one paragraph you'll have to do this yourself for now
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
text (str): Your text
|
|
208
|
+
color (Optional[str]): One of the supported colors
|
|
209
|
+
align (Optional[Literal['left', 'center', 'right']]): Alignment of text
|
|
210
|
+
"""
|
|
211
|
+
self.current.add_text(text, color, align)
|
|
212
|
+
|
|
213
|
+
def add_table(
|
|
214
|
+
self,
|
|
215
|
+
table_data: Union[DataFrame, list[list[Any]]],
|
|
216
|
+
formatters: Optional[Union[str, Callable, list[str], list[Callable]]]=None,
|
|
217
|
+
options: Optional[list[str]]=None,
|
|
218
|
+
size: Optional[int]=None,
|
|
219
|
+
align: Optional[Literal['left','center','right']]=None,
|
|
220
|
+
caption: Optional[str]=None,
|
|
221
|
+
footers: Optional[list[list[Any]]]=None,
|
|
222
|
+
order: Optional[list[list[Any]]]=None,
|
|
223
|
+
color: Optional[str]=None,
|
|
224
|
+
):
|
|
225
|
+
""" Add a table to the page.
|
|
226
|
+
|
|
227
|
+
``table_data`` can either be a pandas ``DataFrame`` or a list of lists.
|
|
228
|
+
For the former we assume indices and column labels are already properly
|
|
229
|
+
formatted. For the latter we assume the first item contains the headers.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
table_data (Union[DataFrame, list[list[Any]]]):
|
|
233
|
+
Data to tabelize.
|
|
234
|
+
|
|
235
|
+
formatters (Optional[Union[str, Callable, list[str], list[Callable]]]):
|
|
236
|
+
Formatters applied to each table element. If a list is provided it
|
|
237
|
+
must match the number of columns. A single formatter is applied
|
|
238
|
+
to all columns. The default formatter is ``str()``.
|
|
239
|
+
|
|
240
|
+
options (Optional[list[str]]):
|
|
241
|
+
Turn optional elements of the DataTable on. Supported options:
|
|
242
|
+
|
|
243
|
+
- ``page`` - Split long tables into pages of 10 (configurable)
|
|
244
|
+
- ``info`` - Show number of rows
|
|
245
|
+
- ``search`` - Add a search field
|
|
246
|
+
- ``no_sort`` - Disable initial sorting
|
|
247
|
+
- ``color_negative_values`` - Highlight values < 0 in red
|
|
248
|
+
- ``color_positive_values`` - Highlight values > 0 in green
|
|
249
|
+
- ``full_width`` - Table spans full window width
|
|
250
|
+
|
|
251
|
+
size (Optional[int]):
|
|
252
|
+
Sets the width of the table (1-12). ``12`` uses full container width.
|
|
253
|
+
|
|
254
|
+
align (Optional[str]):
|
|
255
|
+
Alignment of cell content.
|
|
256
|
+
|
|
257
|
+
caption (Optional[str]):
|
|
258
|
+
Text shown below the table.
|
|
259
|
+
|
|
260
|
+
footers (Optional[list[list[Any]]]):
|
|
261
|
+
Rows always shown at the bottom of the table.
|
|
262
|
+
|
|
263
|
+
order (Optional[list[list[Any]]]):
|
|
264
|
+
Default table order in the form ``[[col_idx, 'asc' | 'desc'], ...]``.
|
|
265
|
+
|
|
266
|
+
color (Optional[str]):
|
|
267
|
+
Text color. Automatically determined if omitted.
|
|
268
|
+
"""
|
|
269
|
+
self.current.add_table(table_data, formatters, options, size, align, caption, footers, order, color)
|
|
270
|
+
|
|
271
|
+
def add_list(self, list_items: list[str]):
|
|
272
|
+
""" Add a list to the current page
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
list_items (list[str]): A list to be added to the page
|
|
276
|
+
"""
|
|
277
|
+
self.current.add_list(list_items)
|
|
278
|
+
|
|
279
|
+
def add_image(self,
|
|
280
|
+
image_source: Union[str, Path],
|
|
281
|
+
remove_source: Optional[bool]=None,
|
|
282
|
+
responsive: Optional[bool]=None,
|
|
283
|
+
center: Optional[bool]=None,
|
|
284
|
+
):
|
|
285
|
+
""" Add an image to the current page
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
image_source (Union[str, Path]): The path to the image to add
|
|
289
|
+
remove_source (Optional[bool]): If true the source will be deleted and only acopy in the report dir is kept
|
|
290
|
+
responsive (Optional[bool]): If true the image will automatically be resized to match the outer container
|
|
291
|
+
center (Optional[bool]): If true the image will be centered
|
|
292
|
+
"""
|
|
293
|
+
self.current.add_image(image_source, remove_source, responsive, center)
|
|
294
|
+
|
|
295
|
+
def add_local_link_to_page(self, page_name: str, link_name: str):
|
|
296
|
+
""" Add a link to another page to the current page
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
page_name (str): The key/name of the page to link to
|
|
300
|
+
link_name (str): The text that will be displayed
|
|
301
|
+
"""
|
|
302
|
+
link = self.get_local_link_to_page(page_name, link_name)
|
|
303
|
+
self.current.add_local_link(link)
|
|
304
|
+
|
|
305
|
+
def get_local_link_to_page(self, page_name: str, link_name: str):
|
|
306
|
+
""" Get a link to another page to the current page
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
page_name (str): The key/name of the page to link to
|
|
310
|
+
link_name (str): The text that will be displayed
|
|
311
|
+
"""
|
|
312
|
+
return self.pages[page_name].get_link_to_page(link_name)
|
|
313
|
+
|
|
314
|
+
def add_global_link_to_page(self, page_name: str, link_name: str):
|
|
315
|
+
""" Add a link to another page to the navbar
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
page_name (str): The key/name of the page to link to
|
|
319
|
+
link_name (str): The text that will be displayed
|
|
320
|
+
"""
|
|
321
|
+
link = self.pages[page_name].get_link_to_page(link_name, for_navbar=True)
|
|
322
|
+
self.global_links.append(link)
|
|
323
|
+
|
|
324
|
+
def get_link_to_page(self, **kwargs):
|
|
325
|
+
""" Get a link to the current page """
|
|
326
|
+
self.current.get_link_to_page(**kwargs)
|
|
327
|
+
|
|
328
|
+
def add_custom_html_code(self, html: str):
|
|
329
|
+
""" Add custom / generic html code
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
html (str): The HTML string.
|
|
333
|
+
"""
|
|
334
|
+
self.current.add_custom_html_code(html)
|
|
335
|
+
|
|
336
|
+
# Layouting
|
|
337
|
+
# ---------
|
|
338
|
+
def open_columns(self):
|
|
339
|
+
""" Start a column layout """
|
|
340
|
+
self.current.open_columns()
|
|
341
|
+
|
|
342
|
+
def add_column(self):
|
|
343
|
+
""" Add another column to the current column layout """
|
|
344
|
+
self.current.add_column()
|
|
345
|
+
|
|
346
|
+
def close_columns(self):
|
|
347
|
+
""" Close the current column layout """
|
|
348
|
+
self.current.close_columns()
|
|
349
|
+
|
|
350
|
+
def open_tabs(self):
|
|
351
|
+
""" Open a tab layout"""
|
|
352
|
+
self.current.open_tabs()
|
|
353
|
+
|
|
354
|
+
def add_tab(self, name: str):
|
|
355
|
+
""" Add another tab to the current tab layout
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
name (str): The text shown on the tab
|
|
359
|
+
"""
|
|
360
|
+
self.current.add_tab(name)
|
|
361
|
+
|
|
362
|
+
def close_tabs(self):
|
|
363
|
+
""" Close the current tab layout"""
|
|
364
|
+
self.current.close_tabs()
|
|
365
|
+
|
|
366
|
+
def open_navbar(self, **kwargs):
|
|
367
|
+
""" Open a navbar layout """
|
|
368
|
+
self.current.open_navbar(**kwargs)
|
|
369
|
+
|
|
370
|
+
def add_navbar_item(self, name: str):
|
|
371
|
+
""" Add another navbar item to the current navbar layout
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
name (str): The text shown on the navbar pill
|
|
375
|
+
"""
|
|
376
|
+
self.current.add_navbar_item(name)
|
|
377
|
+
|
|
378
|
+
def close_navbar(self):
|
|
379
|
+
""" Close the current navbar layout """
|
|
380
|
+
self.current.close_navbar()
|
|
381
|
+
|
|
382
|
+
def open_accordion(self, **kwargs):
|
|
383
|
+
""" Open a accordion layout"""
|
|
384
|
+
self.current.open_accordion(**kwargs)
|
|
385
|
+
|
|
386
|
+
def add_accordion_item(self, name: str):
|
|
387
|
+
""" Add another accordion item to the current accordion layout
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
name (str): The text shown on the accordion
|
|
391
|
+
"""
|
|
392
|
+
self.current.add_accordion_item(name)
|
|
393
|
+
|
|
394
|
+
def close_accordion(self):
|
|
395
|
+
""" Close the current accorion layout """
|
|
396
|
+
self.current.close_accordion()
|
|
397
|
+
|
|
398
|
+
def open_sublevel(self, **kwargs):
|
|
399
|
+
""" Open a sub-level layout """
|
|
400
|
+
self.current.open_sublevel(**kwargs)
|
|
401
|
+
|
|
402
|
+
def close_sublevel(self):
|
|
403
|
+
""" Close the current sub-level layout """
|
|
404
|
+
self.current.close_sublevel()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.accordion .card {
|
|
2
|
+
background-color: {{ accordion_body_background_color }};
|
|
3
|
+
border: {{ accordion_border_size }} solid {{ accordion_border_color }};
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* Remove default header bottom border */
|
|
7
|
+
.accordion .card-header {
|
|
8
|
+
background-color: {{ accordion_header_background_color }};
|
|
9
|
+
border-bottom: none;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Control the border between header and body */
|
|
13
|
+
.accordion .card-body {
|
|
14
|
+
border-top: {{ accordion_body_top_border_size }} solid {{ accordion_body_top_border_color }}; /* ← your desired color */
|
|
15
|
+
color: {{ accordion_body_font_color }};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.accordion .btn-link {
|
|
19
|
+
color: {{ accordion_link_color }};
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<div class="container p-1">
|
|
2
|
+
<div class="accordion" id="{{ accordion_id }}">
|
|
3
|
+
{% for item in accordion_items %}
|
|
4
|
+
<div class="card">
|
|
5
|
+
<div class="card-header">
|
|
6
|
+
<h5 class="mb-0">
|
|
7
|
+
<button class="btn btn-link collapsed w-100" type="button" data-toggle="collapse" data-target="#{{ item[1] }}">
|
|
8
|
+
{{ item[0] }}
|
|
9
|
+
</button>
|
|
10
|
+
</h5>
|
|
11
|
+
</div>
|
|
12
|
+
<div id="{{ item[1] }}" class="collapse" data-parent="#{{ accordion_id }}">
|
|
13
|
+
<div class="card-body">
|
|
14
|
+
{{ item[2] }}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
{% endfor %}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<!-- META TAGS --!>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
7
|
+
|
|
8
|
+
<!-- BOOTSTRAP --!>
|
|
9
|
+
<link rel="stylesheet" href="css_and_scripts/bootstrap.min.css">
|
|
10
|
+
|
|
11
|
+
<!-- BOOTSTRAP - OPTIONAL JavaScript --!>
|
|
12
|
+
<script src="css_and_scripts/jquery-3.3.1.slim.min.js"></script>
|
|
13
|
+
<script src="css_and_scripts/popper.min.js"></script>
|
|
14
|
+
<script src="css_and_scripts/bootstrap.min.js"></script>
|
|
15
|
+
|
|
16
|
+
<!-- DATATABLES --!>
|
|
17
|
+
<script src="css_and_scripts/jquery.dataTables.min.js"></script>
|
|
18
|
+
<script src="css_and_scripts/dataTables.bootstrap4.min.js"></script>
|
|
19
|
+
|
|
20
|
+
<link rel="stylesheet" type="text/css" href="css_and_scripts/custom_colors.css" />
|
|
21
|
+
<link rel="stylesheet" type="text/css" href="css_and_scripts/tabs.css" />
|
|
22
|
+
<link rel="stylesheet" type="text/css" href="css_and_scripts/site.css" />
|
|
23
|
+
<link rel="stylesheet" type="text/css" href="css_and_scripts/accordion.css" />
|
|
24
|
+
<link rel="stylesheet" type="text/css" href="css_and_scripts/table.css" />
|
|
25
|
+
<link rel="stylesheet" type="text/css" href="css_and_scripts/navbar.css" />
|
|
26
|
+
|
|
27
|
+
{{ head_content }}
|
|
28
|
+
|
|
29
|
+
</head>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
/* BOOTSTRAP HACK: fix content width inside hidden tabs */
|
|
33
|
+
.tab-content > .tab-pane:not(.active),
|
|
34
|
+
.pill-content > .pill-pane:not(.active) {
|
|
35
|
+
display: block;
|
|
36
|
+
height: 0;
|
|
37
|
+
overflow-y: hidden;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* TABLE classes to highlight positive (negative) numbers in green (red) */
|
|
41
|
+
.positive {
|
|
42
|
+
color: #28a745bf;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.negative {
|
|
46
|
+
color: #dc3545bf;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* TABLE class to separate footer from the rest of the table */
|
|
50
|
+
.double-top-border {
|
|
51
|
+
border-top: 3px double #c5c9c7;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
</style>
|
|
55
|
+
|
|
56
|
+
<body>
|
|
57
|
+
{{ body_content }}
|
|
58
|
+
</body>
|
|
59
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{% for color in color_items %}
|
|
2
|
+
/*------------------------------------
|
|
3
|
+
COLOR {{ color.name }}
|
|
4
|
+
------------------------------------*/
|
|
5
|
+
.alert-{{ color.name }} {
|
|
6
|
+
color: {{ color.face_color }};
|
|
7
|
+
background-color: {{ color.font_and_background }};
|
|
8
|
+
{%- if color_mode == 'light' %}
|
|
9
|
+
border-color: {{ color.border_light }};
|
|
10
|
+
{%- else %}
|
|
11
|
+
border-color: {{ color.border_dark }};
|
|
12
|
+
{%- endif %}
|
|
13
|
+
border-width: 1px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.alert-{{ color.name }} a {
|
|
17
|
+
color: {{ color.link }};
|
|
18
|
+
text-decoration: underline;
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.alert-{{ color.name }} a:hover {
|
|
23
|
+
color: {{ color.hover }};
|
|
24
|
+
text-decoration-thickness: 2px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.text-{{ color.name }} {
|
|
28
|
+
color: {{ color.font_and_background }}
|
|
29
|
+
}
|
|
30
|
+
{% endfor %}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div class="container p-1">
|
|
2
|
+
{% if center %}
|
|
3
|
+
<center>
|
|
4
|
+
{% endif %}
|
|
5
|
+
<a href="{{ source }}">
|
|
6
|
+
{% if responsive %}
|
|
7
|
+
<img src="{{ source }}" class="img-fluid">
|
|
8
|
+
{% else %}
|
|
9
|
+
<img src="{{ source }}">
|
|
10
|
+
{% endif %}
|
|
11
|
+
</a>
|
|
12
|
+
{% if center %}
|
|
13
|
+
</center>
|
|
14
|
+
{% endif %}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{% for color in color_items %}
|
|
2
|
+
/*------------------------------------
|
|
3
|
+
COLOR {{ color.name }}
|
|
4
|
+
------------------------------------*/
|
|
5
|
+
.nav-pills .pill-{{ color.name }} .nav-link:not(.active) {
|
|
6
|
+
background-color: {{ color.font_and_background }}51;
|
|
7
|
+
color: {{ color.font_and_background }};
|
|
8
|
+
font-weight: normal;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
.nav-pills .pill-{{ color.name }} .nav-link {
|
|
13
|
+
background-color: {{ color.font_and_background }}bf;
|
|
14
|
+
color: {{ color.face_color }};
|
|
15
|
+
font-weight: bolder;
|
|
16
|
+
}
|
|
17
|
+
{% endfor %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<div class="container p-2">
|
|
2
|
+
<div class="row">
|
|
3
|
+
<div class="col-2">
|
|
4
|
+
<ul class="nav nav-pills nav-stacked nav-fill" role="tablist">
|
|
5
|
+
{% for item in navbar_items %}
|
|
6
|
+
<li class="nav-item p-1 pill-{{ color }}">
|
|
7
|
+
{% if loop.index == 1 %}
|
|
8
|
+
<a class="nav-link active" data-toggle="pill" href="#{{ item[1] }}" role="tab">{{ item[0] }}</a>
|
|
9
|
+
{% else %}
|
|
10
|
+
<a class="nav-link" data-toggle="pill" href="#{{ item[1] }}" role="tab">{{ item[0] }}</a>
|
|
11
|
+
{% endif %}
|
|
12
|
+
</li>
|
|
13
|
+
{% endfor %}
|
|
14
|
+
</ul>
|
|
15
|
+
</div>
|
|
16
|
+
<div class = "col-10">
|
|
17
|
+
<div class="tab-content">
|
|
18
|
+
{% for item in navbar_items %}
|
|
19
|
+
{% if loop.index == 1 %}
|
|
20
|
+
<div class="tab-pane fade show active" id="{{ item[1] }}">
|
|
21
|
+
{% else %}
|
|
22
|
+
<div class="tab-pane fade" id="{{ item[1] }}">
|
|
23
|
+
{% endif %}
|
|
24
|
+
<div class="container">
|
|
25
|
+
{{ item[2] }}
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
{% endfor %}
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<div class="container p-2">
|
|
2
|
+
<div class="row">
|
|
3
|
+
<div class = "col-10">
|
|
4
|
+
<div class="tab-content">
|
|
5
|
+
{% for item in navbar_items %}
|
|
6
|
+
{% if loop.index == 1 %}
|
|
7
|
+
<div class="tab-pane fade show active" id="{{ item[1] }}">
|
|
8
|
+
{% else %}
|
|
9
|
+
<div class="tab-pane fade" id="{{ item[1] }}">
|
|
10
|
+
{% endif %}
|
|
11
|
+
<div class="container">
|
|
12
|
+
{{ item[2] }}
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
{% endfor %}
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="col-2">
|
|
19
|
+
<ul class="nav nav-pills nav-stacked nav-fill" role="tablist">
|
|
20
|
+
{% for item in navbar_items %}
|
|
21
|
+
<li class="nav-item p-1 pill-{{ color }}">
|
|
22
|
+
{% if loop.index == 1 %}
|
|
23
|
+
<a class="nav-link active" data-toggle="pill" href="#{{ item[1] }}" role="tab">{{ item[0] }}</a>
|
|
24
|
+
{% else %}
|
|
25
|
+
<a class="nav-link" data-toggle="pill" href="#{{ item[1] }}" role="tab">{{ item[0] }}</a>
|
|
26
|
+
{% endif %}
|
|
27
|
+
</li>
|
|
28
|
+
{% endfor %}
|
|
29
|
+
</ul>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|