<!DOCTYPE html>
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml" class="bg-naas">

<head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vue-good-table@2.21.1/dist/vue-good-table.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@1.9.5/dist/tailwind.min.css">
    <link rel="icon" href="asset/naas_fav.svg" />
    <meta charset="utf-8">
    <meta name="theme-color" content="#000000">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="apple-mobile-web-app-title" content="Naas | Manager">
    <meta name="mobile-web-app-capable" content="yes">
    <title>Naas | Manager</title>
    <style>
        .bg-naas {
            background-color: #191b1d;
        }
        #cs_loader_wrap {
            display: flex;
            justify-content: center;
            flex-direction: column;
            height: 100vh;
            width: 100vw;
            position: absolute;
            background-color: white;
            z-index: 4210;
        }

        #cs_logo_wrap {
            display: flex;
            width: 20vw;
            border-radius: 50%;
            margin: 0 auto;
        }

        #cs_logo_loading {
            display: flex;
            margin: auto;
            opacity: 1;
        }

        #cs_spinner_loading {
            width: 20vw;
            height: 20vw;
            position: absolute;
            border-top: 5px solid #3edb7c;
            border-right: 1px solid rgba(0, 0, 0, 0);
            border-left: 1px solid rgba(0, 0, 0, 0);
            border-radius: 50%;
            animation: spin-bot 1s ease-in-out infinite;
            z-index: 4213;
        }

        @keyframes spin-bot {
            0% {
                transform: rotate(0deg);
            }

            100% {
                transform: rotate(360deg);
            }
        }
    </style>
    
    <script src="https://cdn.jsdelivr.net/npm/vue-loading-overlay@3"></script>
    <link href="https://cdn.jsdelivr.net/npm/vue-loading-overlay@3/dist/vue-loading.css" rel="stylesheet">
    <script type="module">
        import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.esm.browser.js';
        import axios from 'https://cdn.jsdelivr.net/npm/@bundled-es-modules/axios@0.18.1/axios.js';
        import VueGoodTablePlugin from 'https://cdn.jsdelivr.net/npm/vue-good-table@2.21.1/dist/vue-good-table.esm.js';
        import * as VModal from 'https://cdn.jsdelivr.net/npm/vue-js-modal@2.0.0-rc.6/dist/index.min.js';
        import VueCodeHighlight from 'https://cdn.jsdelivr.net/npm/vue-code-highlight@0.7.6/dist/vue-code-highlight.esm.min.js';

        const debouceTime = 800;
        Vue.use(window['vue-js-modal'].default);
        Vue.use(VueLoading);
        Vue.component('loading', VueLoading)
        Vue.use(VueCodeHighlight);
        Vue.use(VueGoodTablePlugin);
        const app = new Vue({
            el: '#app',
            components: {
                VueCodeHighlight,
            },
            data: {
                filter: '',
                loading: true,
                loadingLog: true,
                loadingFull: false,
                columns_logs: [
                    {
                        label: 'Date',
                        field: 'asctime',
                    },
                    {
                        label: 'ID',
                        field: 'id',
                    },
                    {
                        label: 'Service',
                        field: 'type',
                    },
                    {
                        label: 'File',
                        field: 'filepath',
                    },
                    {
                        label: 'Status',
                        field: 'status',
                    },
                    {
                        label: 'Error',
                        field: 'error',
                        hidden: true,
                    },
                ],
                columns_jobs: [
                    {
                        label: 'Type',
                        field: 'type',
                    },
                    {
                        label: 'File',
                        field: 'path',
                    },
                    {
                        label: 'Value',
                        field: 'value',
                    },
                    {
                        label: 'Status',
                        field: 'status',
                    },
                    {
                        label: 'Date',
                        field: 'lastUpdate',
                    },
                    {
                        label: 'ID',
                        field: 'id',
                    },
                    {
                        label: 'Quantity',
                        field: 'lastRun',
                    },
                    {
                        label: 'Credit',
                        field: 'lastCredit',
                    },
                ],
                env: {},
                logs: [],
                totalLogRecords: 0,
                debouceFilter: null,
                serverLogParams: {
                    columnFilters: "",
                    sort: [{ field: 'asctime', type: 'desc' }],
                    page: 1,
                    perPage: 15
                },
                jobs: []
            },
            watch: {
                filter(val) {
                    if (this.debouceFilter) {
                        clearTimeout(this.debouceFilter);
                    }
                    this.debouceFilter = setTimeout(() => {
                        this.onColumnLogFilter({ "columnFilters": val });
                        this.debouceFilter = null;
                    }, debouceTime);
                },
            },
            methods: {
                openOutput(path) {
                    const url = this.linkTojupOutput(path);
                    if (url.includes('.ipynb')) {
                        window.open(url, '_blank');
                    }
                },
                openError(error, path) {
                    const url = this.linkTojupOutput(path);
                    this.$modal.show({
                        methods: {
                            openOutput(url) {
                                if (url.includes('.ipynb')) {
                                    window.open(url, '_blank');
                                }
                                this.$emit('close');
                            },
                        },
                        template: `
                        <div class="w-full bg-white rounded-lg shadow-lg">
                            <div class="flex justify-between px-5 py-4 border-b border-gray-100">
                                <div>
                                    <span class="text-lg font-bold text-gray-700">Error</span>
                                </div>
                                <div>
                                    <button><i class="text-red-500 transition duration-150 fa fa-times-circle hover:text-red-600"></i></button>
                                </div>
                            </div>
                        
                            <div class="px-10 text-gray-600">
                                <pre class="-my-12 break-words whitespace-pre-wrap">
                                    <code>
                                        {{ error }}
                                    </code>
                                </pre>
                            </div>
                        
                            <div class="flex justify-end px-5 py-4">
                            <button @click="$emit('close')" class="px-3 py-2 text-sm text-gray-500 transition duration-150 hover:text-gray-600">Close</button>
                            <button @click="openOutput(url)" class="px-3 py-2 text-sm text-gray-500 transition duration-150 hover:text-gray-600">Open output</button>
                            </div>
                        </div>
                        `,
                        props: ['error', 'url']
                    }, { error, url }, { width: "70%", scrollable: true, height: 'auto' });
                },
                updateLogParams(newProps) {
                    this.serverLogParams = Object.assign({}, this.serverLogParams, newProps);
                },
                onPageLogChange(params) {
                    this.updateLogParams({ page: params.currentPage });
                    this.loadLogItems();
                },

                onPerPageLogChange(params) {
                    this.updateLogParams({ perPage: params.currentPerPage });
                    this.loadLogItems();
                },

                onSortLogChange(params) {
                    this.updateLogParams({ sort: params });
                    this.loadLogItems();
                },

                onColumnLogFilter(params) {
                    this.updateLogParams(params);
                    this.loadLogItems();
                },
                convertApiArgs() {
                    const query = {
                        limit: this.serverLogParams.perPage,
                        skip: this.serverLogParams.perPage * (this.serverLogParams.page - 1),
                    }
                    if (this.serverLogParams.columnFilters !== "") {
                        query['search'] = this.serverLogParams.columnFilters;
                    }
                    if (this.serverLogParams.sort.length > 0) {
                        query['sort'] = JSON.stringify(this.serverLogParams.sort);
                    }
                    query['filters'] = JSON.stringify(['scheduler', 'asset', 'notebook'])
                    console.log('query', query);
                    return query
                },
                loadLogItems() {
                    this.loadingLog = !this.loading;
                    return axios.get('log', { params: this.convertApiArgs() }).then((response) => {
                        if (response && !response.error) {
                            try {
                                this.logs = response.data.data;
                                this.totalLogRecords = response.data.totalRecords;
                            } catch (err) {
                                console.error(err);
                            }
                        }
                    }).then(() => {
                        this.loadingLog = false;
                    }).catch((err) => {
                        console.error(err);
                    })
                },
                stripNaasPath(path) {
                    return path.replace(this.env.NAAS_BASE_PATH, '').replace(`${this.env.JUPYTER_SERVER_ROOT}/`, '')
                },
                openUrl(value, type, autoOpen=false) {
                    let urlList = window.location.href.split('/');
                    urlList.pop()
                    let newUrl = `${urlList.join('/')}`
                    if (type === 'notebook') {
                        newUrl = `${newUrl}/notebook/${value}`
                    } else {
                        newUrl = `${newUrl}/asset/${value}`
                    }
                    if (!autoOpen) {
                        return newUrl
                    } else {
                        window.open(newUrl, '_blank');
                    }
                },
                load_data() {
                    this.loading = true;
                    const all_loads = [];

                    all_loads.push(axios.get('env').then((response) => {
                        if (response && !response.error) {
                            try {
                                this.env = response.data;
                            } catch (err) {
                                console.error(err);
                            }
                        }
                    }));
                    all_loads.push(axios.get('job').then((response) => {
                        if (response && !response.error) {
                            try {
                                this.jobs = [];
                                response.data.forEach((job) => {
                                    if (job.type == 'scheduler' || job.type == 'scheduler') {
                                        job['lastCredit'] = (job['lastRun'] / 60).toFixed(2); 
                                    } else {
                                        job['lastCredit'] = job['lastRun'];
                                    }
                                    if (job.status !== 'delete') {
                                        this.jobs.push(job)
                                    }
                                })
                            } catch (err) {
                                console.error(err);
                            }
                        }
                    }));
                    all_loads.push(this.loadLogItems());
                    return Promise.all(all_loads).then(()=> {
                        this.loading = false;
                    });
                },
                setFilter(filter) {
                    this.filter = filter;
                },
                toMMSS(naasType, sec_str) {
                    if (naasType != 'notebook' && naasType != 'scheduler') {
                        return `${sec_str} call`;
                    }
                    const sec_num = parseInt(sec_str, 10);
                    let minutes = Math.floor((sec_num) / 60);
                    let seconds = sec_num - (minutes * 60);

                    if (minutes < 10) {
                        minutes = `0${minutes}`;
                    }
                    if (seconds < 10) {
                        seconds = `0${seconds}`;
                    }
                    return `${minutes}:${seconds}`;
                },
                linkToJup() {
                    let base = `${this.env.JUPYTERHUB_URL}`
                    if (this.env.JUPYTERHUB_USER && this.env.JUPYTERHUB_USER !== '') {
                        return `${this.env.JUPYTERHUB_URL}/user/${this.env.JUPYTERHUB_USER}`
                    }
                    if (base.search('://') === -1) {
                        base = `${window.location.protocol}://${base}`
                    }
                    return base
                },
                linkToLab() {
                    return `${this.linkToJup()}/lab`
                },
                linkTojupOutput(link) {
                    const partial = link.replace(this.env.JUPYTER_SERVER_ROOT, '');
                    const partialList = partial.split('/');
                    partialList[partialList.length - 1] = `output__${partialList[partialList.length - 1]}`
                    const partialOutput = partialList.join('/');
                    const fileUrl = `${this.linkToJup()}/naas/downloader?url=${partialOutput}`;
                    return fileUrl
                },
                linkTojup(link) {
                    const partial = link.replace(this.env.JUPYTER_SERVER_ROOT, '');
                    const fileUrl = `${this.linkToJup()}/naas/downloader?url=${partial}`;
                    window.open(fileUrl, '_blank');
                },
            },
            created: function () {
                const uri = window.location.href.split('?');
                if (uri.length == 2) {
                    const vars = uri[1].split('&');
                    const getVars = {};
                    let tmp = '';
                    vars.forEach(function (v) {
                        tmp = v.split('=');
                        if (tmp.length == 2) {
                            getVars[tmp[0]] = decodeURIComponent(tmp[1]);
                        }
                    });
                    // console.log('getVars', getVars);
                    if (getVars["filter"]) {
                        this.filter = getVars["filter"]
                    }
                }
                this.load_data();
            }
        });
    </script>
    <script>!function(w,d,i,s){function l(){if(!d.getElementById(i)){var f=d.getElementsByTagName(s)[0],e=d.createElement(s);e.type="text/javascript",e.async=!0,e.src="https://canny.io/sdk.js",f.parentNode.insertBefore(e,f)}}if("function"!=typeof w.Canny){var c=function(){c.q.push(arguments)};c.q=[],w.Canny=c,"complete"===d.readyState?l():w.attachEvent?w.attachEvent("onload",l):w.addEventListener("load",l,!1)}}(window,document,"canny-jssdk","script");</script>
    <script> Canny('initChangelog', {appID: '5f81748112b5d73b2faf4b15', position: 'top', align: 'left'});</script>
    <script
        type="text/javascript">window.$crisp = []; window.CRISP_WEBSITE_ID = "a64b999e-e44c-44ee-928f-5cd0233f9586"; (function () { d = document; s = d.createElement("script"); s.src = "https://client.crisp.chat/l.js"; s.async = 1; d.getElementsByTagName("head")[0].appendChild(s); })();</script>
</head>

<body class="h-full">
    <div id="app" class="w-full bg-naas h-full">
        <div id="cs_loader_wrap" v-if="loading">
            <div id="cs_logo_wrap">
                <img id="cs_logo_loading" alt="Naas logo" src="asset/naas_fav.svg" />
                <div id="cs_spinner_loading"></div>
            </div>
        </div>
        <nav class="bg-naas shadow-lg">
            <div class="items-center justify-between px-8 py-2 md:flex md:px-12">
                <div class="flex items-center justify-between">
                    <div class="flex flex-row mb-2 sm:mb-0 ">
                        <div class="self-center mr-2 h-70 w-70">
                            <img alt="Naas logo" src="asset/naas_fav.svg" width="70" height="70" />
                        </div>
                        <div class="my-auto">
                            <a href="#" :href="linkToLab()"
                                class="font-sans text-2xl font-bold text-gray-100 no-underline hover:text-#4299e1-dark">Manager</a>
                        </div>
                    </div>
                    <div class="md:hidden">
                        <button type="button"
                            class="block text-gray-800 hover:text-gray-700 focus:text-gray-700 focus:outline-none">
                            <svg class="w-6 h-6 fill-current" viewBox="0 0 24 24">
                                <path class="hidden"
                                    d="M16.24 14.83a1 1 0 0 1-1.41 1.41L12 13.41l-2.83 2.83a1 1 0 0 1-1.41-1.41L10.59 12 7.76 9.17a1 1 0 0 1 1.41-1.41L12 10.59l2.83-2.83a1 1 0 0 1 1.41 1.41L13.41 12l2.83 2.83z" />
                                <path
                                    d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z" />
                            </svg>
                        </button>
                    </div>
                </div>
                <div class="flex items-center justify-center w-full">
                    <div class="relative w-1/4 text-gray-900">
                        <input type="text" name="search" v-model="filter" placeholder="Search"
                            class="w-full h-10 px-5 pr-10 text-sm text-gray-900 bg-gray-100 rounded-full focus:outline-none">
                        <button type="submit" class="absolute top-0 right-0 mt-3 mr-4">
                            <svg v-if="filter == ''" class="w-4 h-4 fill-current" xmlns="http://www.w3.org/2000/svg"
                                xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px"
                                viewBox="0 0 56.966 56.966" style="enable-background:new 0 0 56.966 56.966;"
                                xml:space="preserve" width="512px" height="512px">
                                <path
                                    d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23  s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92  c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17  s-17-7.626-17-17S14.61,6,23.984,6z" />
                            </svg>
                            <svg v-if="filter != ''" @click="filter= ''" class="w-4 h-4 fill-current"
                                viewBox="0 0 20 20">
                                <path
                                    d="M10.185,1.417c-4.741,0-8.583,3.842-8.583,8.583c0,4.74,3.842,8.582,8.583,8.582S18.768,14.74,18.768,10C18.768,5.259,14.926,1.417,10.185,1.417 M10.185,17.68c-4.235,0-7.679-3.445-7.679-7.68c0-4.235,3.444-7.679,7.679-7.679S17.864,5.765,17.864,10C17.864,14.234,14.42,17.68,10.185,17.68 M10.824,10l2.842-2.844c0.178-0.176,0.178-0.46,0-0.637c-0.177-0.178-0.461-0.178-0.637,0l-2.844,2.841L7.341,6.52c-0.176-0.178-0.46-0.178-0.637,0c-0.178,0.176-0.178,0.461,0,0.637L9.546,10l-2.841,2.844c-0.178,0.176-0.178,0.461,0,0.637c0.178,0.178,0.459,0.178,0.637,0l2.844-2.841l2.844,2.841c0.178,0.178,0.459,0.178,0.637,0c0.178-0.176,0.178-0.461,0-0.637L10.824,10z">
                                </path>
                            </svg>
                        </button>
                    </div>
                </div>
                <div class="flex flex-col hidden -mx-2 md:flex-row md:block">
                    <a href="#" v-on:click="load_data()"
                        class="px-2 py-2 text-gray-900 bg-gray-100 rounded hover:bg-gray-900 hover:text-gray-100 hover:font-medium md:mx-2">Reload</a>
                </div>
            </div>
        </nav>
        <div class="w-full py-8 md:px-12">
            <h2 class="text-2xl font-semibold text-gray-800"><span class="text-white">Jobs</span></h2>
            <vue-good-table :columns="columns_jobs" :rows="jobs" :search-options="{
                    enabled: true,
                    externalQuery: filter
                }" :pagination-options="{
                    enabled: filter === '',
                    mode: 'records',
                    perPage: 10,
                    position: 'top',
                    perPageDropdown: [5, 15, 20],
                    dropdownAllowAll: true,
                    nextLabel: 'next',
                    prevLabel: 'prev',
                    rowsPerPageLabel: 'Rows per page',
                    ofLabel: 'of',
                    pageLabel: 'page', // for 'pages' mode
                    allLabel: 'All',
                }">
                <template slot="table-row" slot-scope="props">
                    <span v-if="props.column.field == 'status'">
                        <span v-if="props.row.status === 'healthy'"
                            style="font-weight: bold; color: #22BC66;">{{props.formattedRow[props.column.field]}}</span>
                        <span
                            v-else-if="props.row.status === 'error' || props.row.status === 'exception' || props.row.status === 'not_found'"
                            style="font-weight: bold; color: #f56565;">{{props.formattedRow[props.column.field]}}</span>
                        <span v-else
                            style="font-weight: bold; color: #ed8936;">{{props.formattedRow[props.column.field]}}</span>
                    </span>
                    <span v-else-if="props.column.field == 'value'">
                        <a :href="openUrl(props.formattedRow[props.column.field], props.row.type)" target="_blank" style="font-weight: bold; color: #4299e1;"
                            v-if="props.row.type == 'asset' || props.row.type == 'notebook'"
                            >
                            {{props.formattedRow[props.column.field].substring(0,6)}}
                        </a>
                        <span v-else>{{props.formattedRow[props.column.field]}}</span>
                    </span>
                    <span v-else-if="props.column.field == 'id'">
                        <a href="#" style="font-weight: bold; color: #4299e1;"
                            v-on:click="setFilter(props.formattedRow[props.column.field])">
                            {{props.formattedRow[props.column.field].substring(0,6)}}
                        </a>
                    </span>
                    <span v-else-if="props.column.field == 'path'">
                        <a href="#" v-on:click="linkTojup(props.formattedRow[props.column.field])" target="_blank"
                            style="font-weight: bold; color: gray;">{{stripNaasPath(props.formattedRow[props.column.field])}}</a>
                    </span>
                    <span v-else-if="props.column.field == 'lastRun'">
                        {{toMMSS(props.row.type, props.formattedRow[props.column.field])}}
                    </span>
                    <span v-else>
                        {{props.formattedRow[props.column.field]}}
                    </span>
                </template>
            </vue-good-table>
            <h2 class="pt-8 text-2xl font-semibold text-gray-800"><span class="text-white">History</span></h2>
            <!-- <vue-loaders-line-scale v-if="loadingLog"/> -->
            <div class="vld-parent">
                <loading :active.sync="loadingLog" 
                :is-full-page="loadingFull"></loading>
                <vue-good-table mode="remote" :columns="columns_logs" @on-page-change="onPageLogChange"
                    @on-sort-change="onSortLogChange" @on-column-filter="onColumnLogFilter"
                    @on-per-page-change="onPerPageLogChange" :total-rows="totalLogRecords" :rows="logs" :search-options="{
                    enabled: true,
                    externalQuery: filter
                }" :sort-options="{
                    enabled: true,
                }" :pagination-options="{
                    enabled: true,
                    mode: 'records',
                    perPage: 15,
                    position: 'top',
                    perPageDropdown: [5, 10, 20],
                    dropdownAllowAll: true,
                    nextLabel: 'next',
                    prevLabel: 'prev',
                    rowsPerPageLabel: 'Rows per page',
                    ofLabel: 'of',
                    pageLabel: 'page', // for 'pages' mode
                    allLabel: 'All',
                }">
                    <template slot="table-row" slot-scope="props">
                        <span v-if="props.column.field == 'id'">
                            <a href="#" style="font-weight: bold; color: #4299e1;"
                                v-on:click="setFilter(props.formattedRow[props.column.field])">{{props.formattedRow[props.column.field].substring(0,
                                4)}}</a>
                        </span>
                        <span v-else-if="props.column.field == 'filepath'">
                            {{stripNaasPath(props.formattedRow[props.column.field])}}
                        </span>
                        <span v-else-if="props.column.field == 'status'">
                            <a href="#" v-if="props.formattedRow['error']" style="font-weight: bold; color: #f56565;"
                                v-on:click="openError(props.formattedRow['error'], props.formattedRow['filepath'])">error</a>
                            <a href="#" v-else-if="props.formattedRow[props.column.field] == 'healthy'"
                                style="font-weight: bold; color: #22BC66;"
                                v-on:click="openOutput(props.formattedRow['filepath'])">healthy</a>
                            <p v-else>{{props.formattedRow[props.column.field]}}</p>
                        </span>
                        <span v-else>
                            {{props.formattedRow[props.column.field]}}
                        </span>
                    </template>
                </vue-good-table>
            </div>

            <div class="col-start-2 py-4 pt-8" v-if="env && env.version">
                <h4 class="text-2xl font-semibold text-gray-800"><span class="text-white">Version</span></h4>
                <span
                    class="inline-block px-3 py-1 mr-2 text-sm font-semibold text-gray-700 bg-gray-200 rounded-full">{{env.version}}</span>
                <button class="px-2 py-2 text-gray-900 bg-gray-100 rounded hover:bg-gray-900 hover:text-gray-100 hover:font-medium md:mx-2" data-canny-changelog>
                        What's new !
                </button>
            </div>
        </div>
    </div>
    </div>
</body>

</html>