Merge pull request #926 from NMD03/relations
Mkdocs relations and statistics
4
tools/mkdocs/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/site/docs/*
|
||||||
|
!/site/docs/01_attachements
|
||||||
|
|
||||||
|
/site/site
|
|
@ -1,4 +0,0 @@
|
||||||
validators
|
|
||||||
|
|
||||||
mkdocs-git-committers-plugin
|
|
||||||
mkdocs-rss-plugin
|
|
|
@ -1,6 +1,21 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
requirements_path="requirements.txt"
|
||||||
|
|
||||||
|
pip freeze > installed.txt
|
||||||
|
diff -u <(sort $requirements_path) <(sort installed.txt)
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "All dependencies are installed with correct versions."
|
||||||
|
else
|
||||||
|
echo "Dependencies missing or with incorrect versions. Please install all dependencies from $requirements_path into your environment."
|
||||||
|
rm installed.txt # Clean up
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm installed.txt # Clean up
|
||||||
|
|
||||||
python3 generator.py
|
python3 generator.py
|
||||||
cd site
|
cd ./site/ || exit
|
||||||
mkdocs build
|
mkdocs build
|
||||||
rsync --include ".*" -v -rz --checksum site/ circl@cppz.circl.lu:/var/www/misp-galaxy.org
|
rsync --include ".*" -v -rz --checksum site/ circl@cppz.circl.lu:/var/www/misp-galaxy.org
|
||||||
|
|
|
@ -1,24 +1,28 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import operator
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import validators
|
import validators
|
||||||
|
|
||||||
pathClusters = '../../clusters'
|
CLUSTER_PATH = '../../clusters'
|
||||||
pathSite = './site/docs'
|
SITE_PATH = './site/docs'
|
||||||
|
|
||||||
galaxies_fnames = []
|
FILES_TO_IGNORE = [] # if you want to skip a specific cluster in the generation
|
||||||
files_to_ignore = [] # if you want to skip a specific cluster in the generation
|
|
||||||
|
|
||||||
for f in os.listdir(pathClusters):
|
# Variables for statistics
|
||||||
if '.json' in f and f not in files_to_ignore:
|
public_relations_count = 0
|
||||||
galaxies_fnames.append(f)
|
private_relations_count = 0
|
||||||
|
private_clusters = []
|
||||||
|
public_clusters_dict = {}
|
||||||
|
relation_count_dict = {}
|
||||||
|
synonyms_count_dict = {}
|
||||||
|
empty_uuids_dict = {}
|
||||||
|
|
||||||
galaxies_fnames.sort()
|
INTRO = """
|
||||||
|
|
||||||
index_output = ""
|
|
||||||
index_output += """
|
|
||||||
# MISP Galaxy
|
# MISP Galaxy
|
||||||
|
|
||||||
The MISP galaxy offers a streamlined approach for representing large entities, known as clusters, which can be linked to MISP events or attributes. Each cluster consists of one or more elements, represented as key-value pairs. MISP galaxy comes with a default knowledge base, encompassing areas like Threat Actors, Tools, Ransomware, and ATT&CK matrices. However, users have the flexibility to modify, update, replace, or share these elements according to their needs.
|
The MISP galaxy offers a streamlined approach for representing large entities, known as clusters, which can be linked to MISP events or attributes. Each cluster consists of one or more elements, represented as key-value pairs. MISP galaxy comes with a default knowledge base, encompassing areas like Threat Actors, Tools, Ransomware, and ATT&CK matrices. However, users have the flexibility to modify, update, replace, or share these elements according to their needs.
|
||||||
|
@ -36,7 +40,15 @@ Clusters serve as an open and freely accessible knowledge base, which can be uti
|
||||||
## Publicly available clusters
|
## Publicly available clusters
|
||||||
|
|
||||||
"""
|
"""
|
||||||
index_contributing = """
|
|
||||||
|
STATISTICS= """
|
||||||
|
## Statistics
|
||||||
|
|
||||||
|
You can find some statistics about MISP galaxies [here](./statistics.md).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONTRIBUTING = """
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
@ -46,89 +58,414 @@ We encourage collaboration and contributions to the [MISP Galaxy JSON files](htt
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
galaxy_output = {}
|
class Galaxy():
|
||||||
|
def __init__(self, cluster_list: List[dict], authors, description, name, json_file_name):
|
||||||
|
self.cluster_list = cluster_list
|
||||||
|
self.authors = authors
|
||||||
|
self.description = description
|
||||||
|
self.name = name
|
||||||
|
self.json_file_name = json_file_name
|
||||||
|
self.clusters = self._create_clusters()
|
||||||
|
self.entry = ""
|
||||||
|
|
||||||
for f in galaxies_fnames:
|
def _create_metadata_entry(self):
|
||||||
with open(os.path.join(pathClusters, f)) as fr:
|
self.entry += "---\n"
|
||||||
cluster = json.load(fr)
|
self.entry += f'title: {self.name}\n'
|
||||||
cluster_filename = f.split('.')[0]
|
meta_description = self.description.replace("\"", "-")
|
||||||
index_output += f'- [{cluster["name"]}](./{cluster_filename}/index.md)\n'
|
self.entry += f'description: {meta_description}\n'
|
||||||
galaxy_output[cluster_filename] = "---\n"
|
self.entry += "---\n"
|
||||||
galaxy_output[cluster_filename] += f'title: {cluster["name"]}\n'
|
|
||||||
meta_description = cluster["description"].replace("\"", "-")
|
|
||||||
galaxy_output[cluster_filename] += f'description: {meta_description}\n'
|
|
||||||
galaxy_output[cluster_filename] += "---\n"
|
|
||||||
galaxy_output[cluster_filename] += f'# {cluster["name"]}\n'
|
|
||||||
galaxy_output[cluster_filename] += f'{cluster["description"]}\n'
|
|
||||||
|
|
||||||
if 'authors' in cluster:
|
def _create_title_entry(self):
|
||||||
if cluster["authors"]:
|
self.entry += f'# {self.name}\n'
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
|
||||||
galaxy_output[cluster_filename] += f'??? info "Authors"\n'
|
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
|
||||||
galaxy_output[cluster_filename] += f' | Authors and/or Contributors|\n'
|
|
||||||
galaxy_output[cluster_filename] += f' |----------------------------|\n'
|
|
||||||
for author in cluster["authors"]:
|
|
||||||
galaxy_output[cluster_filename] += f' |{author}|\n'
|
|
||||||
|
|
||||||
for value in cluster["values"]:
|
def _create_description_entry(self):
|
||||||
galaxy_output[cluster_filename] += f'## {value["value"]}\n'
|
self.entry += f'{self.description}\n'
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
|
||||||
if 'description' in value:
|
|
||||||
galaxy_output[cluster_filename] += f'{value["description"]}\n'
|
|
||||||
|
|
||||||
if 'meta' in value:
|
def _create_authors_entry(self):
|
||||||
if 'synonyms' in value['meta']:
|
if self.authors:
|
||||||
if value['meta']['synonyms']: # some cluster have an empty list of synomyms
|
self.entry += f'\n'
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
self.entry += f'??? info "Authors"\n'
|
||||||
galaxy_output[cluster_filename] += f'??? info "Synonyms"\n'
|
self.entry += f'\n'
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
self.entry += f' | Authors and/or Contributors|\n'
|
||||||
galaxy_output[cluster_filename] += f' "synonyms" in the meta part typically refer to alternate names or labels that are associated with a particular {cluster["name"]}.\n\n'
|
self.entry += f' |----------------------------|\n'
|
||||||
galaxy_output[cluster_filename] += f' | Known Synonyms |\n'
|
for author in self.authors:
|
||||||
galaxy_output[cluster_filename] += f' |---------------------|\n'
|
self.entry += f' |{author}|\n'
|
||||||
for synonym in sorted(value['meta']['synonyms']):
|
|
||||||
galaxy_output[cluster_filename] += f' | `{synonym}` |\n'
|
|
||||||
|
|
||||||
if 'uuid' in value:
|
def _create_clusters(self):
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
clusters = []
|
||||||
galaxy_output[cluster_filename] += f'??? tip "Internal MISP references"\n'
|
for cluster in self.cluster_list:
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
clusters.append(Cluster(
|
||||||
galaxy_output[cluster_filename] += f' UUID `{value["uuid"]}` which can be used as unique global reference for `{value["value"]}` in MISP communities and other software using the MISP galaxy\n'
|
value=cluster.get('value', None),
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
description=cluster.get('description', None),
|
||||||
|
uuid=cluster.get('uuid', None),
|
||||||
|
date=cluster.get('date', None),
|
||||||
|
related_list=cluster.get('related', None),
|
||||||
|
meta=cluster.get('meta', None),
|
||||||
|
galaxie=self
|
||||||
|
))
|
||||||
|
return clusters
|
||||||
|
|
||||||
if 'meta' in value:
|
def _create_clusters_entry(self, cluster_dict):
|
||||||
if 'refs' in value['meta']:
|
for cluster in self.clusters:
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
self.entry += cluster.create_entry(cluster_dict)
|
||||||
galaxy_output[cluster_filename] += f'??? info "External references"\n'
|
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
|
||||||
|
|
||||||
for ref in value["meta"]["refs"]:
|
def create_entry(self, cluster_dict):
|
||||||
if validators.url(ref): # some ref are not actual URL (TODO: check galaxy cluster sources)
|
self._create_metadata_entry()
|
||||||
galaxy_output[cluster_filename] += f' - [{ref}]({ref}) - :material-archive: :material-arrow-right: [webarchive](https://web.archive.org/web/*/{ref})\n'
|
self._create_title_entry()
|
||||||
else:
|
self._create_description_entry()
|
||||||
galaxy_output[cluster_filename] += f' - {ref}\n'
|
self._create_authors_entry()
|
||||||
|
self._create_clusters_entry(cluster_dict)
|
||||||
|
return self.entry
|
||||||
|
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
def write_entry(self, path, cluster_dict):
|
||||||
|
self.create_entry(cluster_dict)
|
||||||
|
galaxy_path = os.path.join(path, self.json_file_name)
|
||||||
|
if not os.path.exists(galaxy_path):
|
||||||
|
os.mkdir(galaxy_path)
|
||||||
|
with open(os.path.join(galaxy_path, 'index.md'), "w") as index:
|
||||||
|
index.write(self.entry)
|
||||||
|
|
||||||
|
class Cluster():
|
||||||
|
def __init__(self, description, uuid, date, value, related_list, meta, galaxie):
|
||||||
|
self.description = description
|
||||||
|
self.uuid = uuid
|
||||||
|
self.date = date
|
||||||
|
self.value = value
|
||||||
|
self.related_list = related_list
|
||||||
|
self.meta = meta
|
||||||
|
self.entry = ""
|
||||||
|
self.galaxie = galaxie
|
||||||
|
|
||||||
|
global public_clusters_dict
|
||||||
|
if self.galaxie:
|
||||||
|
public_clusters_dict[self.uuid] = self.galaxie
|
||||||
|
|
||||||
|
def _create_title_entry(self):
|
||||||
|
self.entry += f'## {self.value}\n'
|
||||||
|
self.entry += f'\n'
|
||||||
|
|
||||||
|
def _create_description_entry(self):
|
||||||
|
if self.description:
|
||||||
|
self.entry += f'{self.description}\n'
|
||||||
|
|
||||||
|
def _create_synonyms_entry(self):
|
||||||
|
if isinstance(self.meta, dict) and self.meta.get('synonyms'):
|
||||||
|
self.entry += f'\n'
|
||||||
|
self.entry += f'??? info "Synonyms"\n'
|
||||||
|
self.entry += f'\n'
|
||||||
|
self.entry += f' "synonyms" in the meta part typically refer to alternate names or labels that are associated with a particular {self.value}.\n\n'
|
||||||
|
self.entry += f' | Known Synonyms |\n'
|
||||||
|
self.entry += f' |---------------------|\n'
|
||||||
|
global synonyms_count_dict
|
||||||
|
synonyms_count = 0
|
||||||
|
for synonym in sorted(self.meta['synonyms']):
|
||||||
|
synonyms_count += 1
|
||||||
|
self.entry += f' | `{synonym}` |\n'
|
||||||
|
synonyms_count_dict[self.uuid] = synonyms_count
|
||||||
|
|
||||||
|
def _create_uuid_entry(self):
|
||||||
|
if self.uuid:
|
||||||
|
self.entry += f'\n'
|
||||||
|
self.entry += f'??? tip "Internal MISP references"\n'
|
||||||
|
self.entry += f'\n'
|
||||||
|
self.entry += f' UUID `{self.uuid}` which can be used as unique global reference for `{self.value}` in MISP communities and other software using the MISP galaxy\n'
|
||||||
|
self.entry += f'\n'
|
||||||
|
|
||||||
|
def _create_refs_entry(self):
|
||||||
|
if isinstance(self.meta, dict) and self.meta.get('refs'):
|
||||||
|
self.entry += f'\n'
|
||||||
|
self.entry += f'??? info "External references"\n'
|
||||||
|
self.entry += f'\n'
|
||||||
|
|
||||||
|
for ref in self.meta['refs']:
|
||||||
|
if validators.url(ref):
|
||||||
|
self.entry += f' - [{ref}]({ref}) - :material-archive: :material-arrow-right: [webarchive](https://web.archive.org/web/*/{ref})\n'
|
||||||
|
else:
|
||||||
|
self.entry += f' - {ref}\n'
|
||||||
|
|
||||||
|
self.entry += f'\n'
|
||||||
|
|
||||||
|
def _create_associated_metadata_entry(self):
|
||||||
|
if isinstance(self.meta, dict):
|
||||||
excluded_meta = ['synonyms', 'refs']
|
excluded_meta = ['synonyms', 'refs']
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
self.entry += f'\n'
|
||||||
galaxy_output[cluster_filename] += f'??? info "Associated metadata"\n'
|
self.entry += f'??? info "Associated metadata"\n'
|
||||||
galaxy_output[cluster_filename] += f'\n'
|
self.entry += f'\n'
|
||||||
galaxy_output[cluster_filename] += f' |Metadata key |Value|\n'
|
self.entry += f' |Metadata key {{ .no-filter }} |Value|\n'
|
||||||
galaxy_output[cluster_filename] += f' |---------------------|-----|\n'
|
self.entry += f' |-----------------------------------|-----|\n'
|
||||||
for meta in sorted(value['meta']):
|
for meta in sorted(self.meta.keys()):
|
||||||
if meta in excluded_meta:
|
if meta not in excluded_meta:
|
||||||
|
self.entry += f' | {meta} | {self.meta[meta]} |\n'
|
||||||
|
|
||||||
|
def get_related_clusters(self, cluster_dict, depth=-1, visited=None, level=1):
|
||||||
|
global public_relations_count
|
||||||
|
global private_relations_count
|
||||||
|
global private_clusters
|
||||||
|
global empty_uuids_dict
|
||||||
|
empty_uuids = 0
|
||||||
|
|
||||||
|
if visited is None:
|
||||||
|
visited = {}
|
||||||
|
|
||||||
|
related_clusters = []
|
||||||
|
if depth == 0 or not self.related_list:
|
||||||
|
return related_clusters
|
||||||
|
|
||||||
|
if self.uuid in visited and visited[self.uuid] <= level:
|
||||||
|
return related_clusters
|
||||||
|
else:
|
||||||
|
visited[self.uuid] = level
|
||||||
|
|
||||||
|
for cluster in self.related_list:
|
||||||
|
dest_uuid = cluster["dest-uuid"]
|
||||||
|
|
||||||
|
# Cluster is private
|
||||||
|
if dest_uuid not in cluster_dict:
|
||||||
|
# Check if UUID is empty
|
||||||
|
if not dest_uuid:
|
||||||
|
empty_uuids += 1
|
||||||
continue
|
continue
|
||||||
galaxy_output[cluster_filename] += f' | `{meta}` |{value["meta"][meta]}|\n'
|
private_relations_count += 1
|
||||||
|
if dest_uuid not in private_clusters:
|
||||||
|
private_clusters.append(dest_uuid)
|
||||||
|
related_clusters.append((self, Cluster(value="Private Cluster", uuid=dest_uuid, date=None, description=None, related_list=None, meta=None, galaxie=None), level))
|
||||||
|
continue
|
||||||
|
|
||||||
index_output += index_contributing
|
related_cluster = cluster_dict[dest_uuid]
|
||||||
|
|
||||||
with open(os.path.join(pathSite, 'index.md'), "w") as index:
|
public_relations_count += 1
|
||||||
index.write(index_output)
|
|
||||||
|
|
||||||
for f in galaxies_fnames:
|
related_clusters.append((self, related_cluster, level))
|
||||||
cluster_filename = f.split('.')[0]
|
|
||||||
pathSiteCluster = os.path.join(pathSite, cluster_filename)
|
if (depth > 1 or depth == -1) and (cluster["dest-uuid"] not in visited or visited[cluster["dest-uuid"]] > level + 1):
|
||||||
if not os.path.exists(pathSiteCluster):
|
new_depth = depth - 1 if depth > 1 else -1
|
||||||
os.mkdir(pathSiteCluster)
|
if cluster["dest-uuid"] in cluster_dict:
|
||||||
with open(os.path.join(pathSiteCluster, 'index.md'), "w") as index:
|
related_clusters += cluster_dict[cluster["dest-uuid"]].get_related_clusters(cluster_dict, new_depth, visited, level+1)
|
||||||
index.write(galaxy_output[cluster_filename])
|
|
||||||
|
if empty_uuids > 0:
|
||||||
|
empty_uuids_dict[self.value] = empty_uuids
|
||||||
|
|
||||||
|
# Remove duplicates
|
||||||
|
to_remove = set()
|
||||||
|
cluster_dict = {}
|
||||||
|
for cluster in related_clusters:
|
||||||
|
key1 = (cluster[0], cluster[1])
|
||||||
|
key2 = (cluster[1], cluster[0])
|
||||||
|
|
||||||
|
if key1 in cluster_dict:
|
||||||
|
if cluster_dict[key1][2] > cluster[2]:
|
||||||
|
to_remove.add(cluster_dict[key1])
|
||||||
|
cluster_dict[key1] = cluster
|
||||||
|
else:
|
||||||
|
to_remove.add(cluster)
|
||||||
|
|
||||||
|
elif key2 in cluster_dict:
|
||||||
|
if cluster_dict[key2][2] > cluster[2]:
|
||||||
|
to_remove.add(cluster_dict[key2])
|
||||||
|
cluster_dict[key2] = cluster
|
||||||
|
else:
|
||||||
|
to_remove.add(cluster)
|
||||||
|
|
||||||
|
else:
|
||||||
|
cluster_dict[key1] = cluster
|
||||||
|
related_clusters = [cluster for cluster in related_clusters if cluster not in to_remove]
|
||||||
|
|
||||||
|
return related_clusters
|
||||||
|
|
||||||
|
def _create_related_entry(self):
|
||||||
|
self.entry += f'\n'
|
||||||
|
self.entry += f'??? info "Related clusters"\n'
|
||||||
|
self.entry += f'\n'
|
||||||
|
self.entry += f' To see the related clusters, click [here](./relations/{self.uuid}.md).\n'
|
||||||
|
|
||||||
|
def _get_related_entry(self, relations):
|
||||||
|
output = ""
|
||||||
|
output += f'## Related clusters for {self.value}\n'
|
||||||
|
output += f'\n'
|
||||||
|
output += f'| Cluster A | Cluster B | Level {{ .graph }} |\n'
|
||||||
|
output += f'|-----------|-----------|-------|\n'
|
||||||
|
for relation in relations:
|
||||||
|
placeholder = "__TMP__"
|
||||||
|
|
||||||
|
cluster_a_section = (relation[0].value.lower()
|
||||||
|
.replace(" - ", placeholder) # Replace " - " first
|
||||||
|
.replace(" ", "-")
|
||||||
|
.replace("/", "")
|
||||||
|
.replace(":", "")
|
||||||
|
.replace(placeholder, "-")) # Replace the placeholder with "-"
|
||||||
|
|
||||||
|
cluster_b_section = (relation[1].value.lower()
|
||||||
|
.replace(" - ", placeholder) # Replace " - " first
|
||||||
|
.replace(" ", "-")
|
||||||
|
.replace("/", "")
|
||||||
|
.replace(":", "")
|
||||||
|
.replace(placeholder, "-")) # Replace the placeholder with "-"
|
||||||
|
|
||||||
|
if cluster_b_section != "private-cluster":
|
||||||
|
output += f'| [{relation[0].value} ({relation[0].uuid})](../../{relation[0].galaxie.json_file_name}/index.md#{cluster_a_section}) | [{relation[1].value} ({relation[1].uuid})](../../{relation[1].galaxie.json_file_name}/index.md#{cluster_b_section}) | {relation[2]} |\n'
|
||||||
|
else:
|
||||||
|
output += f'| [{relation[0].value} ({relation[0].uuid})](../../{relation[0].galaxie.json_file_name}/index.md#{cluster_a_section}) | {relation[1].value} ({relation[1].uuid}) | {relation[2]} |\n'
|
||||||
|
return output
|
||||||
|
|
||||||
|
def create_entry(self, cluster_dict):
|
||||||
|
self._create_title_entry()
|
||||||
|
self._create_description_entry()
|
||||||
|
self._create_synonyms_entry()
|
||||||
|
self._create_uuid_entry()
|
||||||
|
self._create_refs_entry()
|
||||||
|
self._create_associated_metadata_entry()
|
||||||
|
if self.related_list:
|
||||||
|
self._create_related_entry()
|
||||||
|
self._write_relations(cluster_dict, SITE_PATH)
|
||||||
|
return self.entry
|
||||||
|
|
||||||
|
def _write_relations(self, cluster_dict, path):
|
||||||
|
related_clusters = self.get_related_clusters(cluster_dict)
|
||||||
|
global relation_count_dict
|
||||||
|
relation_count_dict[self.uuid] = len(related_clusters)
|
||||||
|
galaxy_path = os.path.join(path, self.galaxie.json_file_name)
|
||||||
|
if not os.path.exists(galaxy_path):
|
||||||
|
os.mkdir(galaxy_path)
|
||||||
|
relation_path = os.path.join(galaxy_path, 'relations')
|
||||||
|
if not os.path.exists(relation_path):
|
||||||
|
os.mkdir(relation_path)
|
||||||
|
with open(os.path.join(relation_path, ".pages"), "w") as index:
|
||||||
|
index.write(f'hide: true\n')
|
||||||
|
with open(os.path.join(relation_path, f'{self.uuid}.md'), "w") as index:
|
||||||
|
index.write(self._get_related_entry(related_clusters))
|
||||||
|
|
||||||
|
def create_index(galaxies):
|
||||||
|
index_output = INTRO
|
||||||
|
index_output += STATISTICS
|
||||||
|
for galaxie in galaxies:
|
||||||
|
index_output += f'- [{galaxie.name}](./{galaxie.json_file_name}/index.md)\n'
|
||||||
|
index_output += CONTRIBUTING
|
||||||
|
return index_output
|
||||||
|
|
||||||
|
def get_top_x(dict, x, big_to_small=True):
|
||||||
|
sorted_dict = sorted(dict.items(), key=operator.itemgetter(1), reverse=big_to_small)[:x]
|
||||||
|
top_x = [key for key, value in sorted_dict]
|
||||||
|
top_x_values = sorted(dict.values(), reverse=big_to_small)[:x]
|
||||||
|
return top_x, top_x_values
|
||||||
|
|
||||||
|
def name_to_section(name):
|
||||||
|
placeholder = "__TMP__"
|
||||||
|
return (name.lower()
|
||||||
|
.replace(" - ", placeholder) # Replace " - " first
|
||||||
|
.replace(" ", "-")
|
||||||
|
.replace("/", "")
|
||||||
|
.replace(":", "")
|
||||||
|
.replace(placeholder, "-")) # Replace the placeholder with "-"
|
||||||
|
|
||||||
|
def create_statistics(cluster_dict):
|
||||||
|
statistic_output = ""
|
||||||
|
statistic_output += f'# MISP Galaxy statistics\n'
|
||||||
|
statistic_output +='The MISP galaxy statistics are automatically generated based on the MISP galaxy JSON files. Therefore the statistics only include detailed infomration about public clusters and relations. Some statistics about private clusters and relations is included but only as an approximation based on the information gathered from the public clusters.\n'
|
||||||
|
|
||||||
|
statistic_output += f'# Cluster statistics\n'
|
||||||
|
statistic_output += f'## Number of clusters\n'
|
||||||
|
statistic_output += f'Here you can find the total number of clusters including public and private clusters. The number of public clusters has been calculated based on the number of unique Clusters in the MISP galaxy JSON files. The number of private clusters could only be approximated based on the number of relations to non-existing clusters. Therefore the number of private clusters is not accurate and only an approximation.\n'
|
||||||
|
statistic_output += f'\n'
|
||||||
|
statistic_output += f'| No. | Type | Count {{ .pie-chart }}|\n'
|
||||||
|
statistic_output += f'|----|------|-------|\n'
|
||||||
|
statistic_output += f'| 1 | Public clusters | {len(public_clusters_dict)} |\n'
|
||||||
|
statistic_output += f'| 2 | Private clusters | {len(private_clusters)} |\n'
|
||||||
|
statistic_output += f'\n'
|
||||||
|
|
||||||
|
statistic_output += f'## Galaxies with the most clusters\n'
|
||||||
|
galaxy_counts = {}
|
||||||
|
for galaxy in public_clusters_dict.values():
|
||||||
|
galaxy_counts[galaxy] = galaxy_counts.get(galaxy, 0) + 1
|
||||||
|
top_galaxies, top_galaxies_values = get_top_x(galaxy_counts, 20)
|
||||||
|
statistic_output += f' | No. | Galaxy | Count {{ .log-bar-chart }}|\n'
|
||||||
|
statistic_output += f' |----|--------|-------|\n'
|
||||||
|
for i, galaxy in enumerate(top_galaxies, 1):
|
||||||
|
galaxy_section = name_to_section(galaxy.json_file_name)
|
||||||
|
statistic_output += f' | {i} | [{galaxy.name}](../{galaxy_section}) | {top_galaxies_values[i-1]} |\n'
|
||||||
|
statistic_output += f'\n'
|
||||||
|
|
||||||
|
statistic_output += f'## Galaxies with the least clusters\n'
|
||||||
|
flop_galaxies, flop_galaxies_values = get_top_x(galaxy_counts, 20, False)
|
||||||
|
statistic_output += f' | No. | Galaxy | Count {{ .bar-chart }}|\n'
|
||||||
|
statistic_output += f' |----|--------|-------|\n'
|
||||||
|
for i, galaxy in enumerate(flop_galaxies, 1):
|
||||||
|
galaxy_section = name_to_section(galaxy.json_file_name)
|
||||||
|
statistic_output += f' | {i} | [{galaxy.name}](../{galaxy_section}) | {flop_galaxies_values[i-1]} |\n'
|
||||||
|
statistic_output += f'\n'
|
||||||
|
|
||||||
|
statistic_output += f'# Relation statistics\n'
|
||||||
|
statistic_output += f'Here you can find the total number of relations including public and private relations. The number includes relations between public clusters and relations between public and private clusters. Therefore relatons between private clusters are not included in the statistics.\n'
|
||||||
|
statistic_output += f'\n'
|
||||||
|
statistic_output += f'## Number of relations\n'
|
||||||
|
statistic_output += f'| No. | Type | Count {{ .pie-chart }}|\n'
|
||||||
|
statistic_output += f'|----|------|-------|\n'
|
||||||
|
statistic_output += f'| 1 | Public relations | {public_relations_count} |\n'
|
||||||
|
statistic_output += f'| 2 | Private relations | {private_relations_count} |\n'
|
||||||
|
statistic_output += f'\n'
|
||||||
|
|
||||||
|
statistic_output += f'**Average number of relations per cluster**: {int(sum(relation_count_dict.values()) / len(relation_count_dict))}\n'
|
||||||
|
|
||||||
|
statistic_output += f'## Cluster with the most relations\n'
|
||||||
|
relation_count_dict_names = {cluster_dict[uuid].value: count for uuid, count in relation_count_dict.items()}
|
||||||
|
top_25_relation, top_25_relation_values = get_top_x(relation_count_dict_names, 20)
|
||||||
|
statistic_output += f' | No. | Cluster | Count {{ .bar-chart }}|\n'
|
||||||
|
statistic_output += f' |----|--------|-------|\n'
|
||||||
|
relation_count_dict_galaxies = {cluster_dict[uuid].value: cluster_dict[uuid].galaxie.json_file_name for uuid in relation_count_dict.keys()}
|
||||||
|
for i, cluster in enumerate(top_25_relation, 1):
|
||||||
|
cluster_section = name_to_section(cluster)
|
||||||
|
statistic_output += f' | {i} | [{cluster}](../{relation_count_dict_galaxies[cluster]}/#{cluster_section}) | {top_25_relation_values[i-1]} |\n'
|
||||||
|
statistic_output += f'\n'
|
||||||
|
|
||||||
|
statistic_output += f'# Synonyms statistics\n'
|
||||||
|
statistic_output += f'## Cluster with the most synonyms\n'
|
||||||
|
synonyms_count_dict_names = {cluster_dict[uuid].value: count for uuid, count in synonyms_count_dict.items()}
|
||||||
|
top_synonyms, top_synonyms_values = get_top_x(synonyms_count_dict_names, 20)
|
||||||
|
statistic_output += f' | No. | Cluster | Count {{ .bar-chart }}|\n'
|
||||||
|
statistic_output += f' |----|--------|-------|\n'
|
||||||
|
synonyms_count_dict_galaxies = {cluster_dict[uuid].value: cluster_dict[uuid].galaxie.json_file_name for uuid in synonyms_count_dict.keys()}
|
||||||
|
for i, cluster in enumerate(top_synonyms, 1):
|
||||||
|
cluster_section = name_to_section(cluster)
|
||||||
|
statistic_output += f' | {i} | [{cluster}](../{synonyms_count_dict_galaxies[cluster]}/#{cluster_section}) | {top_synonyms_values[i-1]} |\n'
|
||||||
|
statistic_output += f'\n'
|
||||||
|
|
||||||
|
return statistic_output
|
||||||
|
|
||||||
|
def main():
|
||||||
|
start_time = time.time()
|
||||||
|
galaxies_fnames = []
|
||||||
|
for f in os.listdir(CLUSTER_PATH):
|
||||||
|
if '.json' in f and f not in FILES_TO_IGNORE:
|
||||||
|
galaxies_fnames.append(f)
|
||||||
|
galaxies_fnames.sort()
|
||||||
|
|
||||||
|
galaxies = []
|
||||||
|
for galaxy in galaxies_fnames:
|
||||||
|
with open(os.path.join(CLUSTER_PATH, galaxy)) as fr:
|
||||||
|
galaxie_json = json.load(fr)
|
||||||
|
galaxies.append(Galaxy(galaxie_json['values'], galaxie_json['authors'], galaxie_json['description'], galaxie_json['name'], galaxy.split('.')[0]))
|
||||||
|
|
||||||
|
cluster_dict = {}
|
||||||
|
for galaxy in galaxies:
|
||||||
|
for cluster in galaxy.clusters:
|
||||||
|
cluster_dict[cluster.uuid] = cluster
|
||||||
|
|
||||||
|
# Write files
|
||||||
|
if not os.path.exists(SITE_PATH):
|
||||||
|
os.mkdir(SITE_PATH)
|
||||||
|
|
||||||
|
for galaxy in galaxies:
|
||||||
|
galaxy.write_entry(SITE_PATH, cluster_dict)
|
||||||
|
|
||||||
|
index_output = create_index(galaxies)
|
||||||
|
statistic_output = create_statistics(cluster_dict=cluster_dict)
|
||||||
|
|
||||||
|
with open(os.path.join(SITE_PATH, 'index.md'), "w") as index:
|
||||||
|
index.write(index_output)
|
||||||
|
|
||||||
|
with open(os.path.join(SITE_PATH, 'statistics.md'), "w") as index:
|
||||||
|
index.write(statistic_output)
|
||||||
|
|
||||||
|
print(f"Finished file creation in {time.time() - start_time} seconds")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
48
tools/mkdocs/requirements.txt
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
Babel==2.14.0
|
||||||
|
bracex==2.4
|
||||||
|
certifi==2023.11.17
|
||||||
|
cffi==1.16.0
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
click==8.1.7
|
||||||
|
colorama==0.4.6
|
||||||
|
cryptography==42.0.1
|
||||||
|
Deprecated==1.2.14
|
||||||
|
ghp-import==2.1.0
|
||||||
|
gitdb==4.0.11
|
||||||
|
GitPython==3.1.41
|
||||||
|
graphviz==0.20.1
|
||||||
|
idna==3.6
|
||||||
|
Jinja2==3.1.3
|
||||||
|
Markdown==3.5.2
|
||||||
|
MarkupSafe==2.1.4
|
||||||
|
mergedeep==1.3.4
|
||||||
|
mkdocs==1.5.3
|
||||||
|
mkdocs-awesome-pages-plugin==2.9.2
|
||||||
|
mkdocs-git-committers-plugin==0.2.3
|
||||||
|
mkdocs-material==9.5.6
|
||||||
|
mkdocs-material-extensions==1.3.1
|
||||||
|
mkdocs-rss-plugin==1.12.0
|
||||||
|
natsort==8.4.0
|
||||||
|
packaging==23.2
|
||||||
|
paginate==0.5.6
|
||||||
|
pathspec==0.12.1
|
||||||
|
platformdirs==4.1.0
|
||||||
|
pycparser==2.21
|
||||||
|
PyGithub==2.2.0
|
||||||
|
Pygments==2.17.2
|
||||||
|
PyJWT==2.8.0
|
||||||
|
pymdown-extensions==10.7
|
||||||
|
PyNaCl==1.5.0
|
||||||
|
python-dateutil==2.8.2
|
||||||
|
PyYAML==6.0.1
|
||||||
|
pyyaml_env_tag==0.1
|
||||||
|
regex==2023.12.25
|
||||||
|
requests==2.31.0
|
||||||
|
six==1.16.0
|
||||||
|
smmap==5.0.1
|
||||||
|
typing_extensions==4.9.0
|
||||||
|
urllib3==2.1.0
|
||||||
|
validators==0.22.0
|
||||||
|
watchdog==3.0.0
|
||||||
|
wcmatch==8.5
|
||||||
|
wrapt==1.16.0
|
266
tools/mkdocs/site/docs/01_attachements/javascripts/graph.js
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
document$.subscribe(function () {
|
||||||
|
|
||||||
|
const NODE_RADIUS = 8;
|
||||||
|
const NODE_COLOR = "#69b3a2";
|
||||||
|
const Parent_Node_COLOR = "#ff0000";
|
||||||
|
|
||||||
|
function parseFilteredTable(tf) {
|
||||||
|
var data = [];
|
||||||
|
tf.getFilteredData().forEach((row, i) => {
|
||||||
|
data.push({ source: row[1][0], target: row[1][1], level: row[1][2] });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTable(table) {
|
||||||
|
var data = [];
|
||||||
|
table.querySelectorAll("tr").forEach((row, i) => {
|
||||||
|
if (i > 1) {
|
||||||
|
var cells = row.querySelectorAll("td");
|
||||||
|
data.push({ source: cells[0].textContent, target: cells[1].textContent, level: cells[2].textContent });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processNewData(newData) {
|
||||||
|
var newNodes = Array.from(new Set(newData.flatMap(d => [d.source, d.target])))
|
||||||
|
.map(id => ({ id }));
|
||||||
|
|
||||||
|
var newLinks = newData.map(d => ({ source: d.source, target: d.target }));
|
||||||
|
return { newNodes, newLinks };
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterTableAndGraph(tf, simulation) {
|
||||||
|
var filteredData = parseFilteredTable(tf);
|
||||||
|
var { newNodes, newLinks } = processNewData(filteredData);
|
||||||
|
|
||||||
|
simulation.update({ newNodes: newNodes, newLinks: newLinks });
|
||||||
|
}
|
||||||
|
|
||||||
|
function createForceDirectedGraph(data, elementId) {
|
||||||
|
// Extract nodes and links
|
||||||
|
var nodes = Array.from(new Set(data.flatMap(d => [d.source, d.target])))
|
||||||
|
.map(id => ({ id }));
|
||||||
|
|
||||||
|
var links = data.map(d => ({ source: d.source, target: d.target }));
|
||||||
|
|
||||||
|
var tooltip = d3.select("body").append("div")
|
||||||
|
.attr("class", "tooltip") // Add relevant classes for styling
|
||||||
|
.style("opacity", 0);
|
||||||
|
|
||||||
|
// Set up the dimensions of the graph
|
||||||
|
var width = 1000, height = 1000;
|
||||||
|
|
||||||
|
var svg = d3.select(elementId).append("svg")
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height);
|
||||||
|
|
||||||
|
// Create a force simulation
|
||||||
|
var simulation = d3.forceSimulation(nodes)
|
||||||
|
.force("link", d3.forceLink(links).id(d => d.id).distance(10))
|
||||||
|
.force("charge", d3.forceManyBody().strength(-20))
|
||||||
|
.force("center", d3.forceCenter(width / 2, height / 2))
|
||||||
|
.alphaDecay(0.02); // A lower value, adjust as needed
|
||||||
|
|
||||||
|
// Create links
|
||||||
|
var link = svg.append("g")
|
||||||
|
.attr("stroke", "#999")
|
||||||
|
.attr("stroke-opacity", 0.6)
|
||||||
|
.selectAll("line")
|
||||||
|
.data(links)
|
||||||
|
.enter().append("line")
|
||||||
|
.attr("stroke-width", d => Math.sqrt(d.value));
|
||||||
|
|
||||||
|
// Create nodes
|
||||||
|
var node = svg.append("g")
|
||||||
|
.attr("stroke", "#fff")
|
||||||
|
.attr("stroke-width", 1.5)
|
||||||
|
.selectAll("circle")
|
||||||
|
.data(nodes)
|
||||||
|
.enter().append("circle")
|
||||||
|
.attr("r", function (d, i) {
|
||||||
|
return i === 0 ? NODE_RADIUS + 5 : NODE_RADIUS;
|
||||||
|
})
|
||||||
|
.attr("fill", function (d, i) {
|
||||||
|
return i === 0 ? Parent_Node_COLOR : NODE_COLOR;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply tooltip on nodes
|
||||||
|
node.on("mouseover", function (event, d) {
|
||||||
|
tooltip.transition()
|
||||||
|
.duration(200)
|
||||||
|
.style("opacity", .9);
|
||||||
|
tooltip.html(d.id)
|
||||||
|
.style("left", (event.pageX) + "px")
|
||||||
|
.style("top", (event.pageY - 28) + "px");
|
||||||
|
})
|
||||||
|
.on("mousemove", function (event) {
|
||||||
|
tooltip.style("left", (event.pageX) + "px")
|
||||||
|
.style("top", (event.pageY - 28) + "px");
|
||||||
|
})
|
||||||
|
.on("mouseout", function (d) {
|
||||||
|
tooltip.transition()
|
||||||
|
.duration(500)
|
||||||
|
.style("opacity", 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define drag behavior
|
||||||
|
var drag = d3.drag()
|
||||||
|
.on("start", dragstarted)
|
||||||
|
.on("drag", dragged)
|
||||||
|
.on("end", dragended);
|
||||||
|
|
||||||
|
// Apply drag behavior to nodes
|
||||||
|
node.call(drag);
|
||||||
|
|
||||||
|
function dragstarted(event, d) {
|
||||||
|
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||||
|
d.fx = d.x;
|
||||||
|
d.fy = d.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragged(event, d) {
|
||||||
|
d.fx = event.x;
|
||||||
|
d.fy = event.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragended(event, d) {
|
||||||
|
// Do not reset the fixed positions
|
||||||
|
if (!event.active) simulation.alphaTarget(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update positions on each simulation 'tick'
|
||||||
|
simulation.on("tick", () => {
|
||||||
|
nodes.forEach(d => {
|
||||||
|
d.x = Math.max(NODE_RADIUS, Math.min(width - NODE_RADIUS, d.x));
|
||||||
|
d.y = Math.max(NODE_RADIUS, Math.min(height - NODE_RADIUS, d.y));
|
||||||
|
});
|
||||||
|
link
|
||||||
|
.attr("x1", d => d.source.x)
|
||||||
|
.attr("y1", d => d.source.y)
|
||||||
|
.attr("x2", d => d.target.x)
|
||||||
|
.attr("y2", d => d.target.y);
|
||||||
|
|
||||||
|
node
|
||||||
|
.attr("cx", d => d.x)
|
||||||
|
.attr("cy", d => d.y);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.assign(svg.node(), {
|
||||||
|
update({ newNodes, newLinks }) {
|
||||||
|
// Process new nodes and maintain the existing ones
|
||||||
|
const oldNodesMap = new Map(node.data().map(d => [d.id, d]));
|
||||||
|
nodes = newNodes.map(d => Object.assign(oldNodesMap.get(d.id) || {}, d));
|
||||||
|
|
||||||
|
// Update nodes with new data
|
||||||
|
node = node.data(nodes, d => d.id)
|
||||||
|
.join(
|
||||||
|
enter => enter.append("circle")
|
||||||
|
.attr("r", function (d, i) {
|
||||||
|
return i === 0 ? NODE_RADIUS + 5 : NODE_RADIUS;
|
||||||
|
})
|
||||||
|
.attr("fill", function (d, i) {
|
||||||
|
return i === 0 ? Parent_Node_COLOR : NODE_COLOR;
|
||||||
|
}),
|
||||||
|
update => update,
|
||||||
|
exit => exit.remove()
|
||||||
|
);
|
||||||
|
|
||||||
|
node.call(drag);
|
||||||
|
|
||||||
|
// Apply tooltip on nodes
|
||||||
|
node.on("mouseover", function (event, d) {
|
||||||
|
tooltip.transition()
|
||||||
|
.duration(200)
|
||||||
|
.style("opacity", .9);
|
||||||
|
tooltip.html(d.id)
|
||||||
|
.style("left", (event.pageX) + "px")
|
||||||
|
.style("top", (event.pageY - 28) + "px");
|
||||||
|
})
|
||||||
|
.on("mousemove", function (event) {
|
||||||
|
tooltip.style("left", (event.pageX) + "px")
|
||||||
|
.style("top", (event.pageY - 28) + "px");
|
||||||
|
})
|
||||||
|
.on("mouseout", function (d) {
|
||||||
|
tooltip.transition()
|
||||||
|
.duration(500)
|
||||||
|
.style("opacity", 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process new links
|
||||||
|
const oldLinksMap = new Map(link.data().map(d => [`${d.source.id},${d.target.id}`, d]));
|
||||||
|
links = newLinks.map(d => Object.assign(oldLinksMap.get(`${d.source.id},${d.target.id}`) || {}, d));
|
||||||
|
|
||||||
|
// Update links with new data
|
||||||
|
link = link.data(links, d => `${d.source.id},${d.target.id}`)
|
||||||
|
.join(
|
||||||
|
enter => enter.append("line")
|
||||||
|
.attr("stroke-width", d => Math.sqrt(d.value)),
|
||||||
|
update => update,
|
||||||
|
exit => exit.remove()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restart the simulation with new data
|
||||||
|
simulation.nodes(nodes);
|
||||||
|
simulation.force("link").links(links);
|
||||||
|
simulation.alpha(1).restart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all tables that have a th with the class .graph and generate Force-Directed Graphs
|
||||||
|
document.querySelectorAll("table").forEach((table, index) => {
|
||||||
|
var graphHeader = table.querySelector("th.graph");
|
||||||
|
if (graphHeader) {
|
||||||
|
// Initialize TableFilter for the table
|
||||||
|
var tf = new TableFilter(table, {
|
||||||
|
base_path: "../../../../01_attachements/modules/tablefilter/",
|
||||||
|
highlight_keywords: true,
|
||||||
|
col_2: "checklist",
|
||||||
|
col_widths: ["350px", "350px", "100px"],
|
||||||
|
col_types: ["string", "string", "number"],
|
||||||
|
grid_layout: false,
|
||||||
|
responsive: false,
|
||||||
|
watermark: ["Filter table ...", "Filter table ..."],
|
||||||
|
auto_filter: {
|
||||||
|
delay: 100 //milliseconds
|
||||||
|
},
|
||||||
|
filters_row_index: 1,
|
||||||
|
state: false,
|
||||||
|
rows_counter: true,
|
||||||
|
status_bar: true,
|
||||||
|
themes: [{
|
||||||
|
name: "transparent",
|
||||||
|
}],
|
||||||
|
btn_reset: {
|
||||||
|
tooltip: "Reset",
|
||||||
|
toolbar_position: "right",
|
||||||
|
},
|
||||||
|
toolbar: true,
|
||||||
|
extensions: [{
|
||||||
|
name: "sort",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'filtersVisibility',
|
||||||
|
description: 'Sichtbarkeit der Filter',
|
||||||
|
toolbar_position: 'right',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
tf.init();
|
||||||
|
var data = parseTable(table);
|
||||||
|
var graphId = "graph" + index;
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.id = graphId;
|
||||||
|
table.parentNode.insertBefore(div, table);
|
||||||
|
var simulation = createForceDirectedGraph(data, "#" + graphId);
|
||||||
|
|
||||||
|
// Listen for table filtering events
|
||||||
|
tf.emitter.on(['after-filtering'], function () {
|
||||||
|
filterTableAndGraph(tf, simulation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
168
tools/mkdocs/site/docs/01_attachements/javascripts/statistics.js
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
document$.subscribe(function () {
|
||||||
|
|
||||||
|
function parseTable(table) {
|
||||||
|
var data = [];
|
||||||
|
table.querySelectorAll("tr").forEach((row, i) => {
|
||||||
|
if (i > 0) {
|
||||||
|
var cells = row.querySelectorAll("td");
|
||||||
|
data.push({ name: cells[1].textContent, value: Number(cells[2].textContent) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPieChart(data, elementId) {
|
||||||
|
// Set up the dimensions of the graph
|
||||||
|
var width = 500, height = 500;
|
||||||
|
|
||||||
|
// Append SVG for the graph
|
||||||
|
var svg = d3.select(elementId).append("svg")
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height);
|
||||||
|
|
||||||
|
// Set up the dimensions of the graph
|
||||||
|
var radius = Math.min(width, height) / 2 - 20;
|
||||||
|
|
||||||
|
// Append a group to the SVG
|
||||||
|
var g = svg.append("g")
|
||||||
|
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
|
||||||
|
|
||||||
|
// Set up the color scale
|
||||||
|
var color = d3.scaleOrdinal()
|
||||||
|
.domain(data.map(d => d.name))
|
||||||
|
.range(d3.quantize(t => d3.interpolateSpectral(t * 0.8 + 0.1), data.length).reverse());
|
||||||
|
|
||||||
|
// Compute the position of each group on the pie
|
||||||
|
var pie = d3.pie()
|
||||||
|
.value(d => d.value);
|
||||||
|
var data_ready = pie(data);
|
||||||
|
|
||||||
|
// Build the pie chart
|
||||||
|
g.selectAll('whatever')
|
||||||
|
.data(data_ready)
|
||||||
|
.enter()
|
||||||
|
.append('path')
|
||||||
|
.attr('d', d3.arc()
|
||||||
|
.innerRadius(0)
|
||||||
|
.outerRadius(radius)
|
||||||
|
)
|
||||||
|
.attr('fill', d => color(d.data.name))
|
||||||
|
.attr("stroke", "black")
|
||||||
|
.style("stroke-width", "2px")
|
||||||
|
.style("opacity", 0.7);
|
||||||
|
|
||||||
|
// Add labels
|
||||||
|
g.selectAll('whatever')
|
||||||
|
.data(data_ready)
|
||||||
|
.enter()
|
||||||
|
.append('text')
|
||||||
|
.text(d => d.data.name)
|
||||||
|
.attr("transform", d => "translate(" + d3.arc().innerRadius(0).outerRadius(radius).centroid(d) + ")")
|
||||||
|
.style("text-anchor", "middle")
|
||||||
|
.style("font-size", 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBarChart(data, elementId, mode) {
|
||||||
|
// Set up the dimensions of the graph
|
||||||
|
var svgWidth = 1000, svgHeight = 1000;
|
||||||
|
var margin = { top: 20, right: 200, bottom: 350, left: 60 }, // Increase bottom margin for x-axis labels
|
||||||
|
width = svgWidth - margin.left - margin.right,
|
||||||
|
height = svgHeight - margin.top - margin.bottom;
|
||||||
|
|
||||||
|
// Append SVG for the graph
|
||||||
|
var svg = d3.select(elementId).append("svg")
|
||||||
|
.attr("width", svgWidth)
|
||||||
|
.attr("height", svgHeight)
|
||||||
|
.append("g")
|
||||||
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||||
|
|
||||||
|
// Set up the scales
|
||||||
|
var x = d3.scaleBand()
|
||||||
|
.range([0, width])
|
||||||
|
.padding(0.2)
|
||||||
|
.domain(data.map(d => d.name));
|
||||||
|
|
||||||
|
var maxYValue = d3.max(data, d => d.value);
|
||||||
|
if (mode == "log") {
|
||||||
|
var minYValue = d3.min(data, d => d.value);
|
||||||
|
if (minYValue <= 0) {
|
||||||
|
console.error("Logarithmic scale requires strictly positive values");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var y = mode == "log" ? d3.scaleLog().range([height, 0]).domain([1, maxYValue]) : d3.scaleLinear().range([height, 0]).domain([0, maxYValue + maxYValue * 0.1]);
|
||||||
|
|
||||||
|
// Set up the color scale
|
||||||
|
var color = d3.scaleOrdinal()
|
||||||
|
.range(d3.schemeCategory10);
|
||||||
|
|
||||||
|
// Set up the axes
|
||||||
|
var xAxis = d3.axisBottom(x)
|
||||||
|
.tickSize(0)
|
||||||
|
.tickPadding(6);
|
||||||
|
|
||||||
|
var yAxis = d3.axisLeft(y);
|
||||||
|
|
||||||
|
// Add the bars
|
||||||
|
svg.selectAll(".bar")
|
||||||
|
.data(data)
|
||||||
|
.enter().append("rect")
|
||||||
|
.attr("class", "bar")
|
||||||
|
.attr("x", d => x(d.name))
|
||||||
|
.attr("y", d => {
|
||||||
|
if (mode == "log") {
|
||||||
|
return y(Math.max(1, d.value));
|
||||||
|
} else if (mode == "linear") {
|
||||||
|
return y(d.value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.attr("width", x.bandwidth())
|
||||||
|
.attr("height", d => {
|
||||||
|
if (mode == "log") {
|
||||||
|
return height - y(Math.max(1, d.value));
|
||||||
|
} else if (mode == "linear") {
|
||||||
|
return height - y(d.value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.attr("fill", d => color(d.name));
|
||||||
|
|
||||||
|
|
||||||
|
// Add and rotate x-axis labels
|
||||||
|
svg.append("g")
|
||||||
|
.attr("transform", "translate(0," + height + ")")
|
||||||
|
.call(xAxis)
|
||||||
|
.selectAll("text")
|
||||||
|
.style("text-anchor", "end")
|
||||||
|
.attr("dx", "-.8em")
|
||||||
|
.attr("dy", ".15em")
|
||||||
|
.attr("transform", "rotate(-65)"); // Rotate the labels
|
||||||
|
|
||||||
|
// Add the y-axis
|
||||||
|
svg.append("g")
|
||||||
|
.call(yAxis);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.querySelectorAll("table").forEach((table, index) => {
|
||||||
|
var pieChart = table.querySelector("th.pie-chart");
|
||||||
|
var barChart = table.querySelector("th.bar-chart");
|
||||||
|
var logBarChart = table.querySelector("th.log-bar-chart");
|
||||||
|
graphId = "graph" + index;
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.id = graphId;
|
||||||
|
table.parentNode.insertBefore(div, table);
|
||||||
|
if (pieChart) {
|
||||||
|
var data = parseTable(table);
|
||||||
|
createPieChart(data, "#" + graphId);
|
||||||
|
}
|
||||||
|
if (barChart) {
|
||||||
|
var data = parseTable(table);
|
||||||
|
createBarChart(data, "#" + graphId, "linear");
|
||||||
|
}
|
||||||
|
if (logBarChart) {
|
||||||
|
var data = parseTable(table);
|
||||||
|
createBarChart(data, "#" + graphId, "log");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,53 @@
|
||||||
|
document$.subscribe(function () {
|
||||||
|
var tables = document.querySelectorAll("article table")
|
||||||
|
tables.forEach(function (table) {
|
||||||
|
var excludeTable = table.querySelector("td.no-filter, th.no-filter");
|
||||||
|
if (!excludeTable) {
|
||||||
|
var tf = new TableFilter(table, {
|
||||||
|
base_path: "https://unpkg.com/tablefilter@0.7.3/dist/tablefilter/",
|
||||||
|
highlight_keywords: true,
|
||||||
|
// col_0: "select",
|
||||||
|
// col_1: "select",
|
||||||
|
col_2: "checklist",
|
||||||
|
col_widths: ["350px", "350px", "100px"],
|
||||||
|
col_types: ["string", "string", "number"],
|
||||||
|
grid_layout: false,
|
||||||
|
responsive: false,
|
||||||
|
watermark: ["Filter table ...", "Filter table ..."],
|
||||||
|
|
||||||
|
auto_filter: {
|
||||||
|
delay: 100 //milliseconds
|
||||||
|
},
|
||||||
|
filters_row_index: 1,
|
||||||
|
state: true,
|
||||||
|
// alternate_rows: true,
|
||||||
|
rows_counter: true,
|
||||||
|
status_bar: true,
|
||||||
|
|
||||||
|
themes: [{
|
||||||
|
name: "transparent",
|
||||||
|
}],
|
||||||
|
|
||||||
|
btn_reset: {
|
||||||
|
tooltip: "Reset",
|
||||||
|
toolbar_position: "right",
|
||||||
|
},
|
||||||
|
// no_results_message: {
|
||||||
|
// content: "No matching records found",
|
||||||
|
// },
|
||||||
|
toolbar: true,
|
||||||
|
extensions: [{
|
||||||
|
name: "sort",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'filtersVisibility',
|
||||||
|
description: 'Sichtbarkeit der Filter',
|
||||||
|
toolbar_position: 'right',
|
||||||
|
},],
|
||||||
|
})
|
||||||
|
tf.init()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
2
tools/mkdocs/site/docs/01_attachements/modules/d3.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
span.colVisSpan{text-align:left;}span.colVisSpan a.colVis{display:inline-block;padding:7px 5px 0;font-size:inherit;font-weight:inherit;vertical-align:top}div.colVisCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;border:1px solid #ccc;height:auto;width:250px;background-color:#fff;margin:35px 0 0 -100px;z-index:10000;padding:10px 10px 10px 10px;text-align:left;font-size:inherit;}div.colVisCont:after,div.colVisCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.colVisCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.colVisCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.colVisCont p{margin:6px auto 6px auto}div.colVisCont a.colVis{display:initial;font-weight:inherit}ul.cols_checklist{padding:0;margin:0;list-style-type:none;}ul.cols_checklist label{display:block}ul.cols_checklist input{vertical-align:middle;margin:2px 5px 2px 1px}li.cols_checklist_item{padding:4px;margin:0;}li.cols_checklist_item:hover{background-color:#335ea8;color:#fff}.cols_checklist_slc_item{background-color:#335ea8;color:#fff}
|
|
@ -0,0 +1 @@
|
||||||
|
span.expClpFlt a.btnExpClpFlt{width:35px;height:35px;display:inline-block;}span.expClpFlt a.btnExpClpFlt:hover{background-color:#f4f4f4}span.expClpFlt img{padding:8px 11px 11px 11px}
|
After Width: | Height: | Size: 144 B |
After Width: | Height: | Size: 360 B |
After Width: | Height: | Size: 325 B |
After Width: | Height: | Size: 63 B |
After Width: | Height: | Size: 61 B |
After Width: | Height: | Size: 59 B |
After Width: | Height: | Size: 58 B |
|
@ -0,0 +1 @@
|
||||||
|
table.TF{border-left:1px solid #ccc;border-top:none;border-right:none;border-bottom:none;}table.TF th{background:#ebecee url("images/bg_th.jpg") left top repeat-x;border-bottom:1px solid #d0d0d0;border-right:1px solid #d0d0d0;border-left:1px solid #fff;border-top:1px solid #fff;color:#333}table.TF td{border-bottom:1px dotted #999;padding:5px}.fltrow{background-color:#ebecee !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #666 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #999 !important}input.flt{width:99% !important}.inf{height:$min-height;background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important}input.reset{background:transparent url("images/btn_eraser.gif") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;}.nextPage:hover{background:transparent url("images/btn_over_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important;}.previousPage:hover{background:transparent url("images/btn_over_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;}.firstPage:hover{background:transparent url("images/btn_over_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;}.lastPage:hover{background:transparent url("images/btn_over_last_page.gif") center center no-repeat !important}div.grd_Cont{background-color:#ebecee !important;border:1px solid #ccc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#d5d5d5}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important;}div.grd_headTblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#ebecee url("images/bg_th.jpg") left top repeat-x !important;border-bottom:1px solid #d0d0d0 !important;border-right:1px solid #d0d0d0 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #999 !important}.grd_inf{background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important;border-top:1px solid #d0d0d0 !important}.loader{border:1px solid #999}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#fff}.odd{background-color:#d5d5d5}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.activeHeader{background:#999 !important}
|
After Width: | Height: | Size: 303 B |
After Width: | Height: | Size: 326 B |
After Width: | Height: | Size: 356 B |
After Width: | Height: | Size: 332 B |
After Width: | Height: | Size: 331 B |
After Width: | Height: | Size: 187 B |
After Width: | Height: | Size: 440 B |
After Width: | Height: | Size: 640 B |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 393 B |
After Width: | Height: | Size: 395 B |
After Width: | Height: | Size: 290 B |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 201 B |
After Width: | Height: | Size: 441 B |
After Width: | Height: | Size: 469 B |
After Width: | Height: | Size: 68 B |
After Width: | Height: | Size: 78 B |
After Width: | Height: | Size: 300 B |
After Width: | Height: | Size: 303 B |
After Width: | Height: | Size: 928 B |
After Width: | Height: | Size: 63 B |
After Width: | Height: | Size: 61 B |
After Width: | Height: | Size: 59 B |
After Width: | Height: | Size: 58 B |
After Width: | Height: | Size: 8.6 KiB |
|
@ -0,0 +1 @@
|
||||||
|
table.TF{border-left:1px dotted #81963b;border-top:none;border-right:0;border-bottom:none;}table.TF th{background:#39424b url("images/bg_headers.jpg") left top repeat-x;border-bottom:0;border-right:1px dotted #d0d0d0;border-left:0;border-top:0;color:#fff}table.TF td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b;padding:5px}.fltrow{background-color:#81963b !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #687830 !important}input.flt{width:99% !important}.inf{background:#d8d8d8;height:$min-height}input.reset{width:53px;background:transparent url("images/btn_filter.png") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important}div.grd_Cont{background:#81963b url("images/bg_headers.jpg") left top repeat-x !important;border:1px solid #ccc !important;padding:0 1px 1px 1px !important;}div.grd_Cont .even{background-color:#bccd83}div.grd_Cont .odd{background-color:#fff}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important;}div.grd_tblCont table td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b}div.grd_tblCont table th,div.grd_headTblCont table th{background:transparent url("images/bg_headers.jpg") 0 0 repeat-x !important;border-bottom:0 !important;border-right:1px dotted #d0d0d0 !important;border-left:0 !important;border-top:0 !important;padding:0 4px 0 4px !important;color:#fff !important;height:35px !important}div.grd_headTblCont table td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;background-color:#81963b !important;padding:1px 3px 1px 3px !important}.grd_inf{background-color:#d8d8d8;border-top:1px solid #d0d0d0 !important}.loader{border:0 !important;background:#81963b !important}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#bccd83}.odd{background-color:#fff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.activeHeader{background:#81963b !important}
|
After Width: | Height: | Size: 554 B |
After Width: | Height: | Size: 118 B |
After Width: | Height: | Size: 118 B |
After Width: | Height: | Size: 97 B |
After Width: | Height: | Size: 97 B |
After Width: | Height: | Size: 601 B |
After Width: | Height: | Size: 847 B |
|
@ -0,0 +1 @@
|
||||||
|
table.TF{padding:0;color:#000;border-right:1px solid #a4bed4;border-top:1px solid #a4bed4;border-left:1px solid #a4bed4;border-bottom:0;}table.TF th{margin:0;color:inherit;background:#d1e5fe url("images/bg_skyblue.gif") 0 0 repeat-x;border-color:#fdfdfd #a4bed4 #a4bed4 #fdfdfd;border-width:1px;border-style:solid}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid #a4bed4;border-left:0;border-top:0;border-right:0}.fltrow{background-color:#d1e5fe !important;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4 !important}input.flt{width:99% !important}.inf{background-color:#e3efff !important;border:1px solid #a4bed4;height:$min-height;color:#004a6f}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#ffe4ab url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#ffe4ab url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#ffe4ab url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#ffe4ab url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.activeHeader{background:#ffe4ab !important;border:1px solid #ffb552 !important;color:inherit !important}div.grd_Cont{background-color:#d9eaed !important;border:1px solid #9cc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#e3efff}div.grd_headTblCont{background-color:#d9eaed !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#d9eaed url("images/bg_skyblue.gif") left top repeat-x;border-bottom:1px solid #a4bed4;border-right:1px solid #a4bed4 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #a4bed4 !important;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:#cce2fe;color:#004a6f;border-top:1px solid #9cc !important;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#2d8eef;border:1px solid #cce2fe;border-radius:5px}.even{background-color:#fff}.odd{background-color:#e3efff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ffdc61 !important;color:inherit}.ezSelectedRow{background-color:#ffe4ab !important;color:inherit}.ezActiveCell{background-color:#fff !important;color:#000 !important;font-weight:bold}.ezETSelectedCell{background-color:#fff !important;font-weight:bold;color:#000 !important}
|
After Width: | Height: | Size: 63 B |
After Width: | Height: | Size: 61 B |
After Width: | Height: | Size: 59 B |
After Width: | Height: | Size: 58 B |
After Width: | Height: | Size: 601 B |
After Width: | Height: | Size: 847 B |
|
@ -0,0 +1 @@
|
||||||
|
table.TF{padding:0;color:inherit;border-right:1px solid transparent;border-top:1px solid transparent;border-left:1px solid transparent;border-bottom:0;}table.TF th{margin:0;color:inherit;background-color:transparent;border-color:transparent;border-width:1px;border-style:solid;}table.TF th:last-child{border-right:1px solid transparent}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid transparent;border-left:0;border-top:0;border-right:0}.fltrow{background-color:transparent;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px;border-bottom:1px solid transparent !important;}.fltrow th:last-child,.fltrow td:last-child{border-right:1px solid transparent}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4}input.flt{width:99% !important}.inf{background-color:transparent;border:1px solid transparent;height:$min-height;color:inherit}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#f7f7f7 url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#f7f7f7 url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#f7f7f7 url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#f7f7f7 url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.activeHeader{background:#f7f7f7 !important;border:1px solid transparent;color:inherit !important}div.grd_Cont{-webkit-box-shadow:0 0 0 0 rgba(50,50,50,0.75);-moz-box-shadow:0 0 0 0 rgba(50,50,50,0.75);box-shadow:0 0 0 0 rgba(50,50,50,0.75);background-color:transparent;border:1px solid transparent;padding:0 !important;}div.grd_Cont .even{background-color:transparent}div.grd_Cont .odd{background-color:#f7f7f7}div.grd_headTblCont{background-color:transparent;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:transparent;border-bottom:1px solid transparent;border-right:1px solid transparent !important;border-left:1px solid transparent;border-top:1px solid transparent}div.grd_tblCont table td{border-bottom:1px solid transparent;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:transparent;color:inherit;border-top:1px solid transparent;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#f7f7f7;border:1px solid #f7f7f7;border-radius:5px;color:#000;text-shadow:none}.even{background-color:transparent}.odd{background-color:#f7f7f7}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ccc !important;color:inherit}.ezSelectedRow{background-color:#ccc !important;color:inherit}.ezActiveCell{background-color:transparent;color:inherit;font-weight:bold}.ezETSelectedCell{background-color:transparent;font-weight:bold;color:inherit}
|
After Width: | Height: | Size: 201 B |
10
tools/mkdocs/site/docs/01_attachements/stylesheets/graph.css
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px;
|
||||||
|
background: lightgrey;
|
||||||
|
border: 0px;
|
||||||
|
border-radius: 4px;
|
||||||
|
pointer-events: none;
|
||||||
|
color: black;
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ theme:
|
||||||
- navigation.footer
|
- navigation.footer
|
||||||
- search.highlight
|
- search.highlight
|
||||||
- search.share
|
- search.share
|
||||||
|
- navigation.instant.preview
|
||||||
palette:
|
palette:
|
||||||
|
|
||||||
# Palette toggle for automatic mode
|
# Palette toggle for automatic mode
|
||||||
|
@ -46,7 +47,10 @@ theme:
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- admonition
|
- admonition
|
||||||
- pymdownx.details
|
- pymdownx.details
|
||||||
- pymdownx.superfences
|
- pymdownx.superfences:
|
||||||
|
custom_fences:
|
||||||
|
- name: mermaid
|
||||||
|
class: mermaid
|
||||||
- tables
|
- tables
|
||||||
- attr_list
|
- attr_list
|
||||||
- pymdownx.emoji:
|
- pymdownx.emoji:
|
||||||
|
@ -61,8 +65,23 @@ extra:
|
||||||
link: https://github.com/misp
|
link: https://github.com/misp
|
||||||
generator: false
|
generator: false
|
||||||
|
|
||||||
|
extra_javascript:
|
||||||
|
# - javascripts/tablefilter.js
|
||||||
|
# - "https://unpkg.com/tablefilter@0.7.3/dist/tablefilter/tablefilter.js"
|
||||||
|
# - "https://d3js.org/d3.v6.min.js"
|
||||||
|
- 01_attachements/javascripts/graph.js
|
||||||
|
- 01_attachements/javascripts/statistics.js
|
||||||
|
# - node_modules/tablefilter/dist/tablefilter/tablefilter.js
|
||||||
|
# - node_modules/d3/dist/d3.min.js
|
||||||
|
- 01_attachements/modules/d3.min.js
|
||||||
|
- 01_attachements/modules/tablefilter/tablefilter.js
|
||||||
|
|
||||||
|
extra_css:
|
||||||
|
- 01_attachements/stylesheets/graph.css
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- search
|
- search
|
||||||
- rss
|
- rss
|
||||||
|
- awesome-pages
|
||||||
#- git-committers:
|
#- git-committers:
|
||||||
# branch: main
|
# branch: main
|
||||||
|
|