failmap/admin

View on GitHub
websecmap/map/static/js/components/report_content.vue

Summary

Maintainability
Test Coverage
{% verbatim %}
<style>
    .report_controls {
        margin-bottom: 30px;
    }
</style>
<template type="x-template" id="report_content_template">
    <div id="report_content">

        <loading v-if="loading"></loading>

        <template v-if='!loading && (reported_organization.id || reported_organization.name)'>

            <div class="row report_controls">
                <div  class="col-md-4">
                    <a @click="printreport('report_content')" class="btn btn-primary" style="color:white; width: 100%"><svg aria-hidden="true" data-prefix="fas" data-icon="print" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" data-fa-i2svg="" class="svg-inline--fa fa-print fa-w-16"><path fill="currentColor" d="M464 192h-16V81.941a24 24 0 0 0-7.029-16.97L383.029 7.029A24 24 0 0 0 366.059 0H88C74.745 0 64 10.745 64 24v168H48c-26.51 0-48 21.49-48 48v132c0 6.627 5.373 12 12 12h52v104c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24V384h52c6.627 0 12-5.373 12-12V240c0-26.51-21.49-48-48-48zm-80 256H128v-96h256v96zM128 224V64h192v40c0 13.2 10.8 24 24 24h40v96H128zm304 72c-13.254 0-24-10.746-24-24s10.746-24 24-24 24 10.746 24 24-10.746 24-24 24z"></path></svg>
                        {{ $t("report_content.controls.print_report") }}</a>
                </div>
                <div  class="col-md-4">
                    <a :href="'/data/updates_on_organization_feed/' + selected.id" target="_blank" class="btn btn-warning" style="color:white;  width: 100%"><svg aria-hidden="true" data-prefix="fas" data-icon="rss" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg="" class="svg-inline--fa fa-rss fa-w-14"><path fill="currentColor" d="M128.081 415.959c0 35.369-28.672 64.041-64.041 64.041S0 451.328 0 415.959s28.672-64.041 64.041-64.041 64.04 28.673 64.04 64.041zm175.66 47.25c-8.354-154.6-132.185-278.587-286.95-286.95C7.656 175.765 0 183.105 0 192.253v48.069c0 8.415 6.49 15.472 14.887 16.018 111.832 7.284 201.473 96.702 208.772 208.772.547 8.397 7.604 14.887 16.018 14.887h48.069c9.149.001 16.489-7.655 15.995-16.79zm144.249.288C439.596 229.677 251.465 40.445 16.503 32.01 7.473 31.686 0 38.981 0 48.016v48.068c0 8.625 6.835 15.645 15.453 15.999 191.179 7.839 344.627 161.316 352.465 352.465.353 8.618 7.373 15.453 15.999 15.453h48.068c9.034-.001 16.329-7.474 16.005-16.504z"></path></svg>
                        {{ $t("report_content.controls.rss_feed") }}</a>
                </div>
                <div  class="col-md-4">
                    <a class="btn btn-secondary" style="color:white; width: 100%" :href="'mailto:' + send_in_email_address + '?subject=' + encodeURIComponent($t('report_content.controls.send_in_mail.subject')) + '&body=' + encodeURIComponent($t('report_content.controls.send_in_mail.body'))">
                    {{ $t("report_content.controls.send_in_domains") }}</a>
                </div>
            </div>


            <div class="page-header" v-if="name">
                <h3>{{ $t("report_content.report_of") }} {{ name }}</h3>
                <p>{{ $t("report_content.data_from") }}: {{ humanize(when) }}.</p>
            </div>

            <div class="row" v-if="name" style="margin-bottom: 30px;">
                <div class="col-md-4">
                    <div class="score high">
                        <span class="score_value high_text">{{ high }}</span><br/>
                        <span class="score_label">{{ $t("report_content.high_risk") }}</span>
                    </div>
                </div>
                <div class="col-md-4">
                    <div class="score medium">
                        <span class="score_value medium_text">{{ medium }}</span><br/>
                        <span class="score_label">{{ $t("report_content.medium_risk") }}</span>
                    </div>
                </div>
                <div class="col-md-4">
                    <div class="score low">
                        <span class="score_value low_text">{{ low }}</span><br/>
                        <span class="score_label">{{ $t("report_content.low_risk") }}</span>
                    </div>
                </div>
            </div>

            <plus_info></plus_info>

            <template v-if="charts_loading">
                <div class="row" v-if="name" style="page-break-before: always;">
                    <div class="col-md-12">
                        <h4>{{ $t("report_content.timeline.title") }}</h4>
                        <loading></loading>
                    </div>
                </div>
            </template>
            <template v-else>
                <div class="row" v-if="name" style="page-break-before: always;">
                    <div class="col-md-12">
                        <h4>{{ $t("report_content.timeline.title") }}</h4>
                        <div class="chart-container" style="position: relative; height:555px; width:100%">
                            <vulnerability-chart
                                :color_scheme="color_scheme"
                                :data="timeline"
                                :axis="['high', 'medium', 'low']"
                                :translation="$i18n.t('report_content.vulnerability_graph')"
                            ></vulnerability-chart>
                        </div>
                    </div>
                </div>

                <div class="row" v-if="name" style="margin-bottom: 30px;">

                    <div class="col-md-4">
                        <div class="chart-container" style="position: relative; height:200px; width:100%">
                            <vulnerability-chart
                                :color_scheme="color_scheme"
                                :data="timeline"
                                :axis="['high']"
                                :display_title="false"
                                :display_legend="false"
                                :translation="$i18n.t('report_content.vulnerability_graph')"
                            ></vulnerability-chart>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="chart-container" style="position: relative; height:200px; width:100%">
                            <vulnerability-chart
                                :color_scheme="color_scheme"
                                :data="timeline"
                                :axis="['medium']"
                                :display_title="false"
                                :display_legend="false"
                                :translation="$i18n.t('report_content.vulnerability_graph')"
                            ></vulnerability-chart>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="chart-container" style="position: relative; height:200px; width:100%">
                            <vulnerability-chart
                                :color_scheme="color_scheme"
                                :data="timeline"
                                :axis="['low']"
                                :display_title="false"
                                :display_legend="false"
                                :translation="$i18n.t('report_content.vulnerability_graph')"
                            ></vulnerability-chart>
                        </div>
                    </div>

                </div>

                <div class="row" v-if="name" style="margin-bottom: 30px;">
                    <div  class="col-md-12">
                        <div class="chart-container" style="position: relative; height:555px; width:100%">
                            <connectivity-chart :color_scheme="color_scheme" :data="timeline"></connectivity-chart>
                        </div>
                    </div>
                </div>
            </template>


            <div class="row" v-if="name" style="margin-bottom: 30px;" style="page-break-before: always;">
                <div  class="col-md-12">
                    <h4>{{ $t("report_content.risksummary.title") }}</h4>
                    <p></p>

                    <div class="table-responsive">
                    <table class="table table-striped table-bordered table-hover table-sm">
                        <thead>
                        <tr>
                            <th style="width:40%"></th>
                            <th style="white-space: nowrap; vertical-align: bottom; height: 240px;" v-for="issue in issues">
                                <div style="transform: translate(21px, -10px) rotate(315deg); width: 30px;">
                                    <span v-html="translate(issue['name'])"></span>
                                </div>
                            </th>
                        </thead>
                        <tbody style="max-height: 240px; overflow-y: scroll">
                        <template v-for="url in urls" >
                            <tr v-html="total_summary_row(url)"></tr>
                        </template>
                        <template v-if="!urls.length">
                            <tr>
                                <td>-</td>
                                <td colspan="100">{{ $t("report_content.risksummary.no_data") }}</td>
                            </tr>
                        </template>
                        </tbody>
                    </table>
                    </div>

                </div>
            </div>


            <div class="row" v-if="name" style="page-break-before: always;">
                <div  class="col-md-12">
                    <h4>{{ $t("report_content.report.title") }}</h4>
                </div>
            </div>

            <plus_info></plus_info>

            <div v-if="!urls.length" class="perurl" :class="colorizebg(0, 0, 0, 0)">
                {{ $t("report_content.report.no_data") }}
            </div>

            <div v-if="name" v-for="url in urls" class="perurl" :class="colorizebg(url.high, url.medium, url.low)">

                <div class="row">
                    <div class="col-md-4">
                        &nbsp;
                    </div>
                    <div class="col-md-8">
                        <a :name="'report_url_'+url.url"></a>
                        <span v-html="total_awarded_points(url.high, url.medium, url.low)"> </span>
                        <span :class="'faildomain report_' + colorize(url.high, url.medium, url.low, false)+'_text'" :data-tooltip-content="idizetag(url.url)">{{ url.url }}</span><br/>
                        <a :href="'mailto:' + incorrect_finding_mail + '?subject=' + encodeURIComponent($t('report_content.report.incorrect_finding_mail.title', [url.url])) + '&body=' + encodeURIComponent($t('report_content.report.incorrect_finding_mail.body'))" class="btn btn-secondary btn-sm" style="margin-top: 11px;" role="button">
                            {{ $t("report_content.report.report_incorrect_finding") }}</a>
                    </div>
                </div>

                <!-- url reports -->
                <div v-if="url.ratings.length > 0" class="row">
                    <div class="col-md-4">
                        &nbsp;
                    </div>
                    <div class="col-md-8 giveroom_url">
                        <h4>{{ $t("report_content.report.url_level_findings") }}</h4>

                        <div v-for="rating in url.ratings">
                            <h5>&nbsp; {{ create_header(rating) }}</h5>
                            <div class="finding_block">
                                <span v-html="awarded_points(rating.high, rating.medium, rating.low, rating.error_in_test)"></span> {{ translate(rating.explanation) }}<br/>
                                {{ $t("report_content.report.since") }}: {{ humanize(rating.since) }}, {{ $t("report_content.report.last_check") }}: {{ humanize(rating.last_scan) }}
                                <div class="finding_references">
                                    <span v-html="second_opinion_links(rating, url)"> </span>
                                    <span v-if="show_comply_or_explain" class="explain_link" v-html="explain_link(comply_or_explain_email_address, rating, url)"></span>
                                    <explain v-if="authenticated" :url="url" :rating="rating"></explain>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- endpoint reports -->
                <div v-if="name" v-for="endpoint in url.endpoints" class="row">
                    <div class="col-md-4 giveroom photobar">
                        <a :href="endpoint.protocol + '://' + url.url + ':' + endpoint.port"
                           target="_blank"
                           :title="'Visit ' + endpoint.protocol + '://' + url.url + ':' + endpoint.port">
                            <img src="data:image/gif;base64,R0lGODdhAQABAPAAAMPDwwAAACwAAAAAAQABAAACAkQBADs="
                                 class="img-fluid rounded lazyload clippedimage" :data-src="'/images/screenshot/' + endpoint.id + '/'"
                                :alt="'Latest screenshot of '+ endpoint.protocol + idize(url.url) + endpoint.port"
                            />
                        </a>
                    </div>
                    <div class="col-md-8 giveroom">
                        <h4>{{ $t("report_content.report.service") }}: {{endpoint_type(endpoint) }}</h4>

                        <div>
                            <div v-for="rating in endpoint.ratings">
                                <h5>&nbsp; {{ create_header(rating) }}</h5>
                                <div class="finding_block" v-if="rating.comply_or_explain_valid_at_time_of_report">
                                    <span class="awarded_points_explained">{{ $t("report_content.report.explained") }}</span> {{ rating.comply_or_explain_explanation }}<br />
                                    {{ $t("report_content.report.explained_on") }}: {{ humanize(rating.comply_or_explain_explained_on) }}, {{ $t("report_content.report.explanation_expires") }}: {{ humanize(rating.comply_or_explain_explanation_valid_until) }}<br>
                                    <del style="font-size: 0.8em"><span v-html="awarded_points(rating.high, rating.medium, rating.low, rating.error_in_test)"></span> <span v-html="create_rating_explanation(rating)"></span> <br/>
                                    {{ $t("report_content.report.since") }}: {{ humanize(rating.since) }}, {{ $t("report_content.report.last_check") }}: {{ humanize(rating.last_scan) }}</del>
                                    <div class="finding_references" v-html="second_opinion_links(rating, url)"> </div>
                                </div>
                                <div class="finding_block" v-else>
                                    <span v-html="awarded_points(rating.high, rating.medium, rating.low, rating.error_in_test)"></span> <span v-html="create_rating_explanation(rating)"></span> <br/>
                                    {{ $t("report_content.report.since") }}: {{ humanize(rating.since) }}, {{ $t("report_content.report.last_check") }}: {{ humanize(rating.last_scan) }}

                                    <div class="finding_references">
                                        <span v-html="second_opinion_links(rating, url)"> </span>
                                        <span v-if="show_comply_or_explain" class="explain_link" v-html="explain_link(comply_or_explain_email_address, rating, url)"></span>
                                        <explain v-if="authenticated" :endpoint="endpoint" :rating="rating"></explain>
                                    </div>

                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </template>
    </div>
</template>
{% endverbatim %}

<script>
Vue.component('report_content', {
    store,
    i18n: { // `i18n` option, setup locale info for component
        messages: {
            en: {
                report_content: {

                    vulnerability_graph: {
                        title: "Total amount of issues over time",
                        xAxis_label: "Month",
                        yAxis_label: "Risk",
                        amount_high: "# High Risk",
                        amount_medium: "# Medium Risk",
                        amount_low: "# Low Risk",
                        amount_good: "# Good",
                    },

                    explain: {
                        explain: "Explain",
                        subject: "Explanation of finding",
                        body: "Hi!,\n" +
                            "\n" +
                            "I would like to explain the below finding.\n" +
                            "\n" +
                            "Address: %{url}\n" +
                            "Scan Type: %{scan_type}\n" +
                            "Scan ID: %{scan_id}\n" +
                            "Impact: High: %{high}, Medium %{medium}, Low: %{low}.\n" +
                            "\n" +
                            "I believe the finding to be incorrect. This is why:\n" +
                            "[... please enter your explanation for review here ...]\n" +
                            "\n" +
                            "I acknowledge that this finding will be published together with my organizations name.\n" +
                            "\n" +
                            "tip: please refer to documentation or standards where possible. Be aware that an explanation is valid " +
                            "for one year by default.\n" +
                            "\n" +
                            "Kind regards,\n" +
                            "",
                    },

                    controls: {
                        close_report: "Close",
                        print_report: "Print",
                        rss_feed: "RSS feed",
                        send_in_domains: "Send in more domains",

                        send_in_mail: {
                            subject: "New subdomains for ...",
                            body: "Dear,\n\n" +
                                "Please add the following domains to the map:\n\n" +
                                "Tip: send a zonefile with all domains if possible.\n\n" +
                                "Kind regards,\n\n",
                        }
                    },

                    report_of: "Report of",
                    data_from: "Data from",
                    high_risk: "High risk",
                    medium_risk: "Medium risk",
                    low_risk: "Low risk",
                    timeline: {
                        title: "Timeline of risks and available services",
                    },

                    risksummary: {
                        title: "Risk summary table",
                        no_data: "This report contains no data / urls.",
                    },

                    report: {
                        title: "Measurements per domain",
                        no_data: "This report contains no data / urls.",
                        report_incorrect_finding: "Report incorrect finding",
                        incorrect_finding_mail: {
                            title: 'Incorrect finding on {0}',
                            body: 'Notice: be aware that everything is scanned and updated routinely. This can take a few days.\n\n\n' +
                                'Dear,\n\n' +
                                'It seems there is some incorrect data on the map, the following is incorrect:\n\n' +
                                '1: ...\n' +
                                '2: ...\n' +
                                '3: ...\n' +
                                '\n\n' +
                                'Tip: include an explanation why the measurement is wrong, to speed up resolving this issue.\n\n\n' +
                                'Kind regards,\n' +
                                '',
                        },
                        url_level_findings: "Url level findings",
                        service: "Service",
                        since: "Since",
                        explained_on: "Explained on",
                        last_check: "Last check",
                        explained: "Explained",
                        explanation_expires: "Explanation expired on",

                        score_error_in_test: "test error",
                        score_perfect: "perfect",
                        score_high: "high",
                        score_medium: "medium",
                        score_low: "low",
                        second_opinion: "Second opinion",
                        documentation: "Documentation",
                    },


                }
            },
            nl: {
                report_content: {

                    vulnerability_graph: {
                        title: "Totaal aantal risico's over tijd.",
                        xAxis_label: "Maand",
                        yAxis_label: "Risico",
                        amount_high: "# Hoog risico",
                        amount_medium: "# Midden risico",
                        amount_low: "# Laag risico",
                        amount_good: "# Goed",
                    },

                    explain: {
                        explain: "Verklaar",
                        subject: "Verklaring",
                        body: "Beste,\n" +
                            "\n" +
                            "Ik wil graag een verklaring geven over onderstaande bevinding.\n" +
                            "\n" +
                            "Adres: %{url}\n" +
                            "Scan soort: %{scan_type}\n" +
                            "Scan nummer: %{scan_id}\n" +
                            "Impact: Hoog: %{high}, Midden %{medium}, Laag: %{low}.\n" +
                            "\n" +
                            "Deze bevinding is incorrect omdat:\n" +
                            "[...  vul hier uw verklaring in  ...]\n" +
                            "\n" +
                            "Ik ga er mee akkoord dat deze verklaring gepubliceerd mag worden onder de naam van mijn organisatie.\n" +
                            "\n" +
                            "tip: graag verwijzen naar documentatie, standaarden, second opinions etc. Een verklaring is meestal gelding voor een jaar. \n" +
                            "\n" +
                            "Met vriendelijke groet,\n" +
                            "",
                    },

                    controls: {
                        close_report: "Sluiten",
                        print_report: "Printen",
                        rss_feed: "RSS feed",
                        send_in_domains: "Domeinen insturen",

                        send_in_mail: {
                            subject: "Nieuwe domeinen voor ...",
                            body: "Beste,\n\n" +
                                "Graag de onderstaande domeinen toevoegen aan de kaart:\n\n" +
                                "Tip: geef aan voor welke organisatie(s) de domeinen relevant zijn..\n\n" +
                                "Met vriendelijke groet,\n\n",
                        }
                    },

                    report_of: "Rapportage van",
                    data_from: "Gegevens van",
                    high_risk: "Hoog risico",
                    medium_risk: "Midden risico",
                    low_risk: "Laag risico",
                    timeline: {
                        title: "Tijdlijn van risico's en beschikbare diensten in het afgelopen jaar",
                    },

                    risksummary: {
                        title: "Risico opsomming",
                        no_data: "Dit verslag bevat geen gegevens.",
                    },

                    report: {
                        title: "Metingen per domein",
                        no_data: "Dit verslag bevat geen gegevens.",
                        report_incorrect_finding: "Meld incorrecte bevinding",
                        incorrect_finding_mail: {
                            title: 'Incorrecte bevinding op {0}',
                            body: 'Belangrijk: Er wordt automatisch regelmatig opnieuw gescand. Dit kan een paar dagen duren.\n\n\n' +
                                'Beste,\n\n' +
                                'Het lijkt dat de volgende bevinding niet juist is:\n\n' +
                                '1: ...\n' +
                                '2: ...\n' +
                                '3: ...\n' +
                                '\n\n' +
                                'Tip: graag verwijzen naar documentatie, standaarden, second opinions waar mogelijk.\n\n\n' +
                                'Met vriendelijke groet,\n' +
                                '',
                        },
                        url_level_findings: "Algemene bevindingen bij dit adres",
                        service: "Dienst",
                        since: "Sinds",
                        explained_on: "Verklaard op",
                        last_check: "Laatst getest op",
                        explained: "Verklaard",
                        explanation_expires: "Verklaring vervalt op",

                        score_perfect: "perfect",
                        score_high: "hoog",
                        score_medium: "midden",
                        score_low: "laag",
                        second_opinion: "Second opinion",
                        documentation: "Documentatie",
                    },
                }
            }
        },
    },
    template: "#report_content_template",
    mixins: [new_state_mixin, translation_mixin],

    data: function () {
        return {
            calculation: '',
            rating: 0,
            points: 0,
            high: 0,
            medium: 0,
            low: 0,
            when: 0,
            twitter_handle: '',
            name: "",
            urls: Array,
            selected: {'id': null, 'label': null, 'name': null},
            loading: false,
            charts_loading: false,
            visible: false,  // fullscreenreport
            promise: false,
            timeline: [],
        }
    },

    props: {
        issues: Array,
        url_issue_names: Array,
        organization: [String, Number],
        color_scheme: Object,
        incorrect_finding_mail: String,
        show_comply_or_explain: Boolean,
        comply_or_explain_email_address: String,
        send_in_email_address: String,

        authenticated: Boolean,

        // When requesting a report directly...
        url_report_id: String,
    },

    // https://vuejs.org/v2/api/#updated
    // When this has been updated, call the lazyload again, as the images below need to be lazyloaded.
    updated: function () {
        this.$nextTick(function () {
          lazyload()
        })
    },

    methods: {
        closereport: function(){
            store.commit('change', {reported_organization: {
                id: null,
                name: null,
            }});
        },
        printreport: function(divId){
            css1 = '<link href="/static/css/vendor/bootstrap.min.css" rel="stylesheet" type="text/css">';
            css4 = '<link href="/static/css/overrides.css" rel="stylesheet" type="text/css">';
            window.frames["print_frame"].document.body.innerHTML=css1 + css4 + document.getElementById(divId).innerHTML;

            // there is no real guarantee that the content / css has loaded...
            setTimeout(this.startprinting,1000);
        },
        startprinting: function(){
            window.frames["print_frame"].window.focus();
            window.frames["print_frame"].window.print();
        },

        reset: function() {
            console.log("Resetting report");
            this.timeline = null;
            this.calculation = '';
            this.rating = 0;
            this.points = 0;
            this.high = 0;
            this.medium = 0;
            this.low = 0;
            this.when = 0;
            this.twitter_handle = '';
            this.name = null;
            this.urls = Array;
            this.selected = {'id': null, 'label': null, 'name': null};
            this.loading = false;
            this.charts_loading = false;
            this.visible = false;
            this.promise = false;
            this.timeline = [];
        },

        load: function () {
            // against symptom of autoloading when setting state, this doesn't have the right parameters.
            if (!this.reported_organization.id && !this.reported_organization.name) {
                this.reset();
                return;
            }

            this.loading = true;
            this.charts_loading = true;

            let org = this.reported_organization.id ? this.reported_organization.id : this.reported_organization.name;

            // first update the graphs, doing this around, the graph will show the previous data, not the current stuff
            fetch(`/data/organization_vulnerability_timeline/${org}/${this.state.layer}/${this.state.country}`).then(response => response.json()).then(timelinedata => {
                if (!$.isEmptyObject(timelinedata)){
                    this.timeline = timelinedata;
                }
                this.charts_loading = false;
            }).catch((fail) => {console.log('An error occurred on report content: ' + fail)});

            let url = "";
            if (this.reported_organization.id){
                url = `/data/report/${this.state.country}/${this.state.layer}/${this.reported_organization.id}/${this.state.week}`;
            } else {
                url = `/data/report/${this.state.country}/${this.state.layer}/${this.reported_organization.name}/${this.state.week}`;
            }

            console.log(`Retrieving report data at ${url}.`);
            fetch(url)
                .then(response => response.json()).then(data => {

                    this.urls = data.calculation["organization"]["urls"];
                    this.points = data.rating;
                    this.high = data.calculation["organization"]["high"];
                    this.medium = data.calculation["organization"]["medium"];
                    this.low = data.calculation["organization"]["low"];
                    this.when = data.when;
                    this.name = data.name;
                    this.twitter_handle = data.twitter_handle;
                    this.promise = data.promise;
                    this.slug = data.slug;
                    this.selected = {id: data.id, name: data.slug};

                    // include id in anchor to allow url sharing
                    let newHash = '/report/' + data.id;
                    $('a#report-anchor').attr('name', newHash);
                    history.replaceState({}, '', '#' + newHash);

                    this.loading = false;
            }).catch((fail) => {console.log('An error occurred on report content: ' + fail)});
        },
        total_summary_row: function(url){

            let worst = {};

            self = this;
            this.issues.forEach((issue) => {
                if (self.url_issue_names.includes(issue['name']))
                    worst[issue['name']] = this.worstof(issue['name'], [url]);
                else
                    worst[issue['name']] = this.worstof(issue['name'], url.endpoints);
            });

            let text = `<td><b><a href="#report_url_${url.url}">🔎 ${url.url}</a></b></td>`;

            let findings = "";

            this.issues.forEach(function (issue) {
                findings += `<td class='text-center ${worst[issue['name']].bgclass}'>${worst[issue['name']].text}</td>`;
            });

            return text + findings;
        },

        worstof: function(risk, endpoints){
            let high = 0, medium = 0, low = 0;
            let risk_found = false;
            let explained = false;

            for(let i=0; i<endpoints.length; i++) {
                let endpoint = endpoints[i];
                for (let i = 0; i < endpoint.ratings.length; i++) {
                    let rating = endpoint.ratings[i];

                    if (rating.type === risk) {
                        risk_found = true;
                        high += rating.high;
                        medium += rating.medium;
                        low += rating.low;
                        if (rating.comply_or_explain_valid_at_time_of_report)
                            explained = true;
                    }
                }
            }

            let text = "";
            let bgclass = "";

            if (high){
                text = "❌";
                bgclass = "high_background_light";
            } else if (medium){
                text = "⚠️";
                bgclass = "medium_background_light";
            } else if (low){
                text = "✅";
                bgclass = "low_background_light";
            } else if (risk_found) {
                text = "✅";
                bgclass = "good_background_light";
            }

            if (explained) {
                // if this is a string with "", translations say unterminated string. As ES6 template it's fine.
                text = "💬";
                bgclass = "good_background_light";
            }

            return {'bgclass': bgclass, 'text': text}

        },

        colorize: function (high, medium, low, error_in_test) {
            if (error_in_test) return "error_in_test";
            if (high > 0) return "high";
            if (medium > 0) return "medium";
            if (low > 0) return "low";
            return "good";
        },
        colorizebg: function (high, medium, low) {
            if (high > 0) return "report_url_background_high";
            if (medium > 0) return "report_url_background_medium";
            return "report_url_background_good";
        },
        idize: function (url) {
            url = url.toLowerCase();
            return url.replace(/[^0-9a-z]/gi, '')
        },
        idizetag: function (url) {
            url = url.toLowerCase();
            return "#" + url.replace(/[^0-9a-z]/gi, '')
        },
        humanize: function (date) {
            // It's better to show how much time was between the last scan and now. This is easier to understand.
            return moment(date).fromNow();
        },
        create_header: function (rating) {
            if (rating.type.startsWith('internet_nl'))
                return this.translate(rating.type + "_label");
            return this.translate(rating.type);
        },
        create_rating_explanation: function (rating) {
            if (rating.type.startsWith('internet_nl')) {
                // has various translations, in the translation key:
                if (rating.translation !== undefined) {
                    if (rating.translation === "not-tested") {
                        return this.translate('internet_nl_not_tested')
                    }

                    return this.translate(rating.type + "_verdict_" + rating.translation.replace("-", "_"))
                }
            }

            return this.translate(rating.explanation)
        },
        second_opinion_links: function (rating, url) {

            // todo: no documentation links are found...
            // console.log(this.issues);
            let selected_issue = {};
            this.issues.forEach((issue) => {
                if (issue.name === rating.type){
                    selected_issue = issue;
                }
            });

            if (!selected_issue) {
                return ""
            }

            let links = "";

            // all extra issues that can be created / selected, but are not supported yet...
            if (selected_issue['second opinion links'] === undefined)
                return "";

            // todo: take in account language.
            // todo: this should be part of the template, not a weird function...
            self = this;
            selected_issue['second opinion links'].forEach(function (item){
                let filled_url = item.url;
                filled_url = filled_url.replace("${url.url}", url.url);
                links += `<a href="${filled_url}" target="_blank" class="btn-sm"><svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="clipboard-check" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" class="svg-inline--fa fa-clipboard-check fa-w-12"><path fill="currentColor" d="M336 64h-80c0-35.3-28.7-64-64-64s-64 28.7-64 64H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48zM192 40c13.3 0 24 10.7 24 24s-10.7 24-24 24-24-10.7-24-24 10.7-24 24-24zm121.2 231.8l-143 141.8c-4.7 4.7-12.3 4.6-17-.1l-82.6-83.3c-4.7-4.7-4.6-12.3.1-17L99.1 285c4.7-4.7 12.3-4.6 17 .1l46 46.4 106-105.2c4.7-4.7 12.3-4.6 17 .1l28.2 28.4c4.7 4.8 4.6 12.3-.1 17z" class=""></path></svg>&nbsp;` +
                    self.$t("report_content.report.second_opinion") + ` (${item.provider}) </a> `;
            });

            selected_issue['documentation links'].forEach(function (item){
                links += `<a href="${item.url}" target="_blank" class="btn-sm"><svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="book" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-book fa-w-14"><path fill="currentColor" d="M448 360V24c0-13.3-10.7-24-24-24H96C43 0 0 43 0 96v320c0 53 43 96 96 96h328c13.3 0 24-10.7 24-24v-16c0-7.5-3.5-14.3-8.9-18.7-4.2-15.4-4.2-59.3 0-74.7 5.4-4.3 8.9-11.1 8.9-18.6zM128 134c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm0 64c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm253.4 250H96c-17.7 0-32-14.3-32-32 0-17.6 14.4-32 32-32h285.4c-1.9 17.1-1.9 46.9 0 64z" class=""></path></svg>&nbsp;` +
                    self.$t("report_content.report.documentation") + ` (${item.provider})</a> `;
            });

            return links;

        },
        explain_link: function(address, rating, url) {
            let subject = this.$t("report_content.explain.subject");
            let explain = this.$t("report_content.explain.explain");
            let body = this.$t("report_content.explain.body", {
                url: url.url, scan_type: rating.type, scan_id: rating.scan, high: rating.high, medium: rating.medium, low: rating.low
            });
            return `<a href='mailto:${address}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}' class='btn-sm'>
                        <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="comments" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" class="svg-inline--fa fa-comments fa-w-18"><path fill="currentColor" d="M416 192c0-88.4-93.1-160-208-160S0 103.6 0 192c0 34.3 14.1 65.9 38 92-13.4 30.2-35.5 54.2-35.8 54.5-2.2 2.3-2.8 5.7-1.5 8.7S4.8 352 8 352c36.6 0 66.9-12.3 88.7-25 32.2 15.7 70.3 25 111.3 25 114.9 0 208-71.6 208-160zm122 220c23.9-26 38-57.7 38-92 0-66.9-53.5-124.2-129.3-148.1.9 6.6 1.3 13.3 1.3 20.1 0 105.9-107.7 192-240 192-10.8 0-21.3-.8-31.7-1.9C207.8 439.6 281.8 480 368 480c41 0 79.1-9.2 111.3-25 21.8 12.7 52.1 25 88.7 25 3.2 0 6.1-1.9 7.3-4.8 1.3-2.9.7-6.3-1.5-8.7-.3-.3-22.4-24.2-35.8-54.5z" class=""></path>
                    </svg> ${explain}</a>`;
        },
        total_awarded_points: function (high, medium, low) {
            let marker = this.make_marker(high, medium, low);
            return '<span class="total_awarded_points_' + this.colorize(high, medium, low, false) + '">' + marker + '</span>'
        },
        awarded_points: function (high, medium, low, error_in_test) {
            let marker = this.make_marker(high, medium, low, error_in_test);
            return '<span class="awarded_points_' + this.colorize(high, medium, low, error_in_test) + '">' + marker + '</span>'
        },
        make_marker: function (high, medium, low, error_in_test) {
            if (error_in_test)
                return this.$t("report_content.report.score_error_in_test");

            if (high === 0 && medium === 0 && low === 0)
                return this.$t("report_content.report.score_perfect");
            else if (high > 0)
                return this.$t("report_content.report.score_high");
            else if (medium > 0)
                return this.$t("report_content.report.score_medium");
            else
                return this.$t("report_content.report.score_low");
        },
        endpoint_type: function (endpoint) {
            if (endpoint.port && endpoint.ip_version)
                return this.translate(endpoint.protocol) + "/" + endpoint.port + " (IPv" + endpoint.ip_version + ")";
            return this.translate(endpoint.protocol)
        },
    },

    watch: {
        reported_organization: function (new_value, old_value) {
            this.load()
        }
    },

    mounted: function(){
        // when a report is directly requested via the url string
        if (this.url_report_id)
            store.commit('change', {reported_organization: {
                id: parseInt(this.url_report_id),
                name: null,
            }});

        this.load();
    },

    computed: {
        reported_organization: function() {
            return store.state.reported_organization
        }
    }
});
</script>