function()

in source/html/js/app/ui/tile_view.js [5:577]


    function($, channels, model, ui_util, event_alerts, alarms, settings, diagrams, _, confirmation, alert, channels_menu) {

        const tile_row_div_id = "channel-tile-row-zkjewrvwdqywhwx";

        let click_listeners = [];

        const tile_outer_div = "channel-tiles-outer";

        const content_div = "channel-tiles-diagram";

        const tab_id = "channel-tiles-tab";

        // interval in millis to check the cloud for tile changes
        let update_interval;

        let intervalID;

        const settings_key = "app-tile-update-interval";

        const tile_width_px = 240;
        const tile_height_px = 175;

        const tile_view_key = "tile-view";

        const add_selection_callback = function(callback) {
            if (!click_listeners.includes(callback)) {
                click_listeners.push(callback);
            }
        };

        const event_alert_callback = function() {
            update_tile_info();
        };

        const alarm_callback = function() {
            update_tile_info();
        };

        const selection_listener = function(name) {
            toggle_tile(name);
            // select(name);
        };

        const selected = function() {
            const tile = $(".selected-channel-tile");
            return tile.attr("data-channel-name");
        };

        const toggle_tile = function(name) {
            if (name === selected()) {
                unselect_all();
            } else {
                select(name);
            }
        };

        const blink = function(blinks, tile) {
            const interval_ms = 500;
            if (blinks > 0) {
                setTimeout(
                    function() {
                        if (blinks % 2 === 0) {
                            select(tile);
                        } else {
                            unselect(tile);
                        }
                        blink(blinks - 1, tile);
                    }, interval_ms);
            } else {
                select(tile);
            }
        };

        const scroll_to_tile = function(name) {
            // scroll to the selected item
            const query = `[data-channel-name='${name}']`;
            const selected = $(query);
            $("#" + tile_row_div_id).animate({
                scrollTop: selected.offset().top
            }, "slow");
        };

        const select = function(name) {
            // query = `[data-channel-name][data-channel-name!='${name}']`;
            const unselected_tiles = $(".selected-channel-tile");
            unselected_tiles.removeClass("selected-channel-tile");
            const query = `[data-channel-name='${name}']`;
            const selected_tile = $(query);
            selected_tile.addClass("selected-channel-tile");
        };

        const unselect = function(name) {
            const query = `[data-channel-name='${name}']`;
            const selected_tile = $(query);
            selected_tile.removeClass("selected-channel-tile");
        };

        const unselect_all = function() {
            const query = `[data-channel-name]`;
            const unselected_tiles = $(query);
            unselected_tiles.removeClass("selected-channel-tile");
        };

        const shown = function() {
            return $("#" + tab_id).attr("aria-selected") === "true";
        };

        function tab_alert(state) {
            if (state) {
                $("#channel-tiles-tab-icon").text("warning");
            } else {
                $("#channel-tiles-tab-icon").text("grid_on");
            }
        }

        const show_edit_dialog = function(name, members) {
            // console.log(members);
            $("#channel_edit_name").val(name);
            $("#channel_edit_name").attr("data-original-name", name);
            $("#channel_edit_modal_items").empty();
            let channel_content = "";
            let index = 0;
            for (let member of members) {
                const node = model.nodes.get(member.id);
                const checkbox_id = ui_util.makeid();
                if (node) {
                    channel_content += `
                                        <tr><th scope="row">${index+1}</th>
                                        <td>
                                        <div class="form-check form-check-inline">
                                            <input class="form-check-input" type="checkbox" id="${checkbox_id}" value="${member.id}">
                                        </div>
                                        </td>
                                        <td>${node.title}</td><td>${node.id}</td></tr>
                                    `;
                } else {
                    channel_content += `
                                        <tr><th scope="row">${index+1}</th>
                                        <td>
                                        <div class="form-check form-check-inline">
                                            <input class="form-check-input" type="checkbox" id="${checkbox_id}" value="${member.id}">
                                        </div>
                                        </td>
                                        <td>Expired</td><td>Expired</td></tr>
                                    `;
                }
                index++;
            }
            const html = `
                        <table id="channel_edit_members_table" class="table table-sm table-hover">
                            <thead>
                                <tr>
                                    <th scope="col">#</th>
                                    <th scope="col">Remove</th>
                                    <th scope="col">Type</th>
                                    <th scope="col">ARN</th>
                                </tr>
                            </thead>
                            <tbody>
                                ${channel_content}
                            </tbody>
                        </table>`;
            $("#channel_edit_modal_items").html(html);
            $("#channel_edit_modal").modal("show");
        };

        const update_tile_info = async function() {
            // console.log("update_tile_info");
            const cached_events = event_alerts.get_cached_events();
            const cached_alarming_subscribers = alarms.get_subscribers_with_alarms();
            const channel_list = await channels.channel_list();
            for (let channel_name of channel_list) {
                const channel_members = await channels.retrieve_channel(channel_name);
                if (channel_members) {
                    const query = `[data-channel-name='${channel_name}']`;
                    const tile_id = $(query).attr("id");
                    const service_count_id = tile_id + "_services";
                    const event_count_id = tile_id + "_events";
                    const alarm_count_id = tile_id + "_alarms";
                    // console.log(channel_members);
                    const service_count = channel_members.length;
                    let alert_count = 0;
                    let alarm_count = 0;
                    let border_class = "border-success";
                    for (let member of channel_members) {
                        filtered_events = _.filter(cached_events.current, { resource_arn: member.id });
                        alert_count += filtered_events.length;
                        filtered_alarms = _.filter(cached_alarming_subscribers.current, { ResourceArn: member.id });
                        alarm_count += filtered_alarms.length;
                    }
                    if (alert_count + alarm_count) {
                        border_class = "border-danger";
                        $("#" + tile_id).removeClass("border-success");
                    } else {
                        $("#" + tile_id).removeClass("border-danger");
                    }
                    if (channel_name == selected()) {
                        border_class = `${border_class} selected-channel-tile`;
                    }
                    $("#" + tile_id).attr("data-alert-count", alert_count);
                    $("#" + tile_id).attr("data-alarm-count", alarm_count);
                    $("#" + tile_id).addClass(border_class);
                    $("#" + service_count_id).html(`${service_count} cloud services`);
                    $("#" + event_count_id).html(`${alert_count} alert event${(alert_count === 1 ? "" : "s")}`);
                    $("#" + alarm_count_id).html(`${alarm_count} alarm${(alarm_count === 1 ? "" : "s")}`);
                }
            }
            sort_tiles();
            filter_tiles();
        };

        const sort_tiles = function() {
            const tiles = $("[data-channel-name]");
            tiles.sort(function(a, b) {
                let compare = 0;
                let compA = Number.parseInt($(a).attr("data-alert-count")) + Number.parseInt($(a).attr("data-alarm-count"));
                let compB = Number.parseInt($(b).attr("data-alert-count")) + Number.parseInt($(b).attr("data-alarm-count"));
                compare = (compA < compB ? 1 : (compA > compB ? -1 : 0));
                if (compare === 0) {
                    compA = $(a).attr("data-channel-name");
                    compB = $(b).attr("data-channel-name");
                    compare = (compA < compB ? -1 : (compA > compB ? 1 : 0));
                }
                return compare;
            });
            for (let tile of tiles) {
                $("[data-tile-row]").append(tile);
            }
        };

        const filter_tiles = function() {
            let tiles = $("[data-channel-name]");
            load_tile_view().then(function(tile_settings) {
                update_filter_mode(tile_settings.tile_filter_text);
                const show_alarm_tiles = tile_settings.show_alarm_tiles;
                const show_normal_tiles = tile_settings.show_normal_tiles;
                for (let tile of tiles) {
                    const total = (Number.parseInt($(tile).attr("data-alert-count")) + Number.parseInt($(tile).attr("data-alarm-count")));
                    const alarming = (total > 0);
                    if (show_alarm_tiles && show_normal_tiles) {
                        $(tile).removeClass("d-none");
                    } else if (show_alarm_tiles) {
                        if (alarming) {
                            $(tile).removeClass("d-none");
                        } else {
                            $(tile).addClass("d-none");
                        }
                    } else if (show_normal_tiles) {
                        if (alarming) {
                            $(tile).addClass("d-none");
                        } else {
                            $(tile).removeClass("d-none");
                        }
                    }
                }
                $("#" + tile_outer_div).removeClass("d-none");
                tab_alert($("#" + content_div + " .border-danger").length > 0);
            });
        };

        const redraw_tiles = async function() {
            const local_lodash = _;
            const local_jquery = $;
            $("#" + tile_outer_div).addClass("d-none");
            $("#" + content_div).html(`<div id="${tile_row_div_id}" data-tile-row="true" class="row ml-3">`);
            const channel_list = await channels.channel_list();
            const cached_events = event_alerts.get_cached_events();
            const cached_alarming_subscribers = alarms.get_subscribers_with_alarms();
            for (let channel_name of channel_list) {
                const local_channel_name = channel_name;
                let border_class = "border-success";
                const channel_members = await channels.retrieve_channel(local_channel_name);
                // console.log(channel_members);
                const service_count = channel_members.length;
                let alert_count = 0;
                let alarm_count = 0;
                for (let member of channel_members) {
                    filtered_events = _.filter(cached_events.current, { resource_arn: member.id });
                    alert_count += filtered_events.length;
                    filtered_alarms = _.filter(cached_alarming_subscribers.current, { ResourceArn: member.id });
                    alarm_count += filtered_alarms.length;
                }
                if (alert_count + alarm_count) {
                    border_class = "border-danger";
                }
                if (local_channel_name == selected()) {
                    border_class = `${border_class} selected-channel-tile`;
                }
                const channel_card_id = ui_util.makeid();
                const model_button_id = channel_card_id + "_model_button";
                const header_id = channel_card_id + "_header";
                const tile = `
                        <div draggable="true" class="card ${border_class} ml-4 mb-4" id="${channel_card_id}" data-alert-count="${alert_count}" data-alarm-count="${alarm_count}" data-channel-name="${channel_name}" data-tile-name="${channel_name}" style="border-width: 3px; width: ${tile_width_px}px; min-width: ${tile_width_px}px; max-width: ${tile_width_px}px; height: ${tile_height_px}px; min-height: ${tile_height_px}px; max-height: ${tile_height_px}px;">
                            <div class="card-header" style="cursor: pointer;" title="Click to Select, Doubleclick to Edit" id="${header_id}">${local_channel_name}</div>
                            <div class="card-body text-info my-0 py-1">
                                <h5 class="card-title my-0 py-0" id="${channel_card_id}_events">${alert_count} alert event${(alert_count === 1 ? "" : "s")}</h5>
                                <h5 class="card-title my-0 py-0" id="${channel_card_id}_alarms">${alarm_count} alarm${(alarm_count === 1 ? "" : "s")}</h5>
                                <p class="card-text small my-0 py-0" id="${channel_card_id}_services">${service_count} cloud services</p>
                            </div>
                            <div class="btn-group btn-group-sm mb-1 mx-auto" role="group" aria-label="Tile Buttons">
                                <button type="button" id="${model_button_id}" title="Navigate to Diagram" class="btn btn-light px-2">Diagram</button>
                            </div>
                        </div>
                    `;
                $("#" + tile_row_div_id).append(tile);
                const header_click_closure = function(hc_console, hc_name, hc_channel_members, hc_click_listeners) {
                    return function() {
                        selection_listener(hc_name, hc_channel_members);
                        for (let listener of hc_click_listeners) {
                            const local_listener = listener;
                            try {
                                local_listener(hc_name, hc_channel_members);
                            } catch (error) {
                                hc_console.log(error);
                            }
                        }
                    };
                };
                $("#" + header_id).on("click", header_click_closure(console, local_channel_name, channel_members, click_listeners));
                $("#" + header_id).dblclick((function() {
                    const name = local_channel_name;
                    const members = channel_members;
                    return function() {
                        show_edit_dialog(name, members);
                    };
                })());
                const local_alert = alert;
                const model_click_closure = function(diagrams, confirmation, _, model) {
                    const mc_diagrams = diagrams;
                    const mc_tile_name = local_channel_name;
                    const node_ids = local_lodash.map(channel_members, "id");
                    return function() {
                        let html;
                        const matches = mc_diagrams.have_all(node_ids);
                        // show tile diagram dialog
                        local_jquery("#view_tile_diagram_selected_diagram").empty();
                        if (node_ids.length === 0) {
                            local_alert.show("Add resources to the tile");
                        } else
                        if (matches.length === 0) {
                            html = `This tile's contents were not found on any diagram. Would you like to generate a new one?`;
                            confirmation.show(html, function() {
                                const diagram = mc_diagrams.add(mc_tile_name, _.snakeCase(mc_tile_name), true);
                                // populate
                                const nodes = local_lodash.compact(model.nodes.get(node_ids));
                                diagram.nodes.update(nodes);
                                diagram.show();
                            });
                        } else {
                            for (let diagram of matches) {
                                html = `<option value="${diagram.name}">${diagram.name}</option>`;
                                local_jquery("#view_tile_diagram_selected_diagram").append(html);
                            }
                            local_jquery("#view_tile_diagram_dialog").attr("data-node-ids", JSON.stringify(node_ids));
                            local_jquery("#view_tile_diagram_dialog").attr("data-tile-name", mc_tile_name);
                            local_jquery("#view_tile_diagram_dialog").modal("show");
                        }
                    };
                };
                $("#" + model_button_id).on("click", model_click_closure(diagrams, confirmation, _, model));
            }
            sort_tiles();
            filter_tiles();
        };

        $("#view_tile_diagram_show").on("click", function() {
            const diagram_name = $("#view_tile_diagram_selected_diagram").val();
            const node_ids = JSON.parse($("#view_tile_diagram_dialog").attr("data-node-ids"));
            const diagram = diagrams.get_by_name(diagram_name);
            diagram.network.once("afterDrawing", function() {
                diagram.network.fit({
                    nodes: node_ids,
                    animation: true
                });
                diagram.blink(10, node_ids);
            });
            $("#view_tile_diagram_dialog").modal("hide");
            diagram.show();
        });

        $("#view_tile_diagram_generate_diagram_button").on("click", function() {
            const tile_name = $("#view_tile_diagram_dialog").attr("data-tile-name");
            const node_ids = JSON.parse($("#view_tile_diagram_dialog").attr("data-node-ids"));
            const diagram = diagrams.add(tile_name, _.snakeCase(tile_name), true);
            // populate
            const nodes = _.compact(model.nodes.get(node_ids));
            diagram.nodes.update(nodes);
            // diagram.network.once("afterDrawing", function() {
            //     // layout
            //     diagram.layout_vertical(true);
            // });
            // show
            diagram.show();
            $("#view_tile_diagram_dialog").modal("hide");
        });

        $("#save_channel_edit").on("click", function() {
            const edited_name = $("#channel_edit_name").val();
            // console.log(edited_name);
            const original_name = $("#channel_edit_name").attr("data-original-name");
            // console.log(original_name);
            const member_checkboxes = $("#channel_edit_members_table input[type='checkbox']");
            // console.log(member_checkboxes);
            channels.delete_channel(original_name).then(function() {
                console.log("removed channel members");
                const members = [];
                for (let item of member_checkboxes) {
                    if (item.checked === false) {
                        members.push(item.value);
                    }
                }
                return channels.create_channel(edited_name, members);
            }).then(function() {
                console.log("added channel members");
                console.log("channel " + original_name + " -> " + edited_name + " updated");
                redraw_tiles();
            }).catch(function(error) {
                console.log(error);
            });
        });

        $("#tiles_duplicate_selected_tile_button").on("click", function() {
            const tile_name = selected();
            if (shown() && tile_name && tile_name !== "") {
                channels.retrieve_channel(tile_name).then(function(source_contents) {
                    source_node_ids = _.map(source_contents, "id").sort();
                    channels_menu.show_quick_new_tile(source_node_ids);
                });
            }
        });

        $("#tiles_edit_selected_tile_button").on("click", function() {
            const tile_name = selected();
            if (shown() && tile_name && tile_name !== "") {
                channels.retrieve_channel(tile_name).then(function(contents) {
                    show_edit_dialog(tile_name, contents);
                });
            }
        });

        $("#tiles_delete_selected_tile_button").on("click", function() {
            const tile_name = selected();
            if (shown() && tile_name && tile_name !== "") {
                $("#confirmation_dialog_proceed").on("click", function(event) {
                    channels.delete_channel(tile_name).then(function(response) {
                        redraw_tiles();
                    });
                });
                $("#confirmation_dialog").on("hide.bs.modal", function(event) {
                    console.log(event);
                    $("#confirmation_dialog_proceed").unbind("click");
                });
                $("#confirmation_dialog_body").html("<p>Delete the tile named " + tile_name + "?</p>");
                $("#confirmation_dialog").modal("show");

            }
        });

        const cache_update = redraw_tiles;

        const load_update_interval = function() {
            return new Promise(function(resolve) {
                settings.get(settings_key).then(function(value) {
                    const seconds = Number.parseInt(value);
                    update_interval = seconds * 1000;
                    resolve();
                });
            });
        };

        const set_update_interval = function(seconds) {
            // create a default
            update_interval = seconds * 1000;
            return settings.put(settings_key, seconds);
        };

        const schedule_interval = function() {
            if (intervalID) {
                clearInterval(intervalID);
            }
            intervalID = setInterval(cache_update, update_interval);
            console.log("tile view: interval scheduled " + update_interval + "ms");
        };

        const load_tile_view = function() {
            return new Promise(function(resolve) {
                settings.get(tile_view_key).then(function(value) {
                    resolve(value);
                });
            });
        };

        const update_tile_view = function(tile_view) {
            return new Promise(function(resolve) {
                settings.put(tile_view_key, tile_view).then(function() {
                    console.log("tile view saved");
                    resolve();
                }).catch(function(error) {
                    console.log(error);
                });
            });
        };

        const update_filter_mode = function(text) {
            $("#tile-filter-dropdown").text(text);
        };

        $("#tile-filter-show-all").click(function(event) {
            const tile_filter_text = "Showing All Tiles";
            update_filter_mode(tile_filter_text);
            update_tile_view({ "show_alarm_tiles": true, "show_normal_tiles": true, "tile_filter_text": tile_filter_text }).then(function() {
                update_tile_info();
            });
        });

        $("#tile-filter-show-alarm").click(function(event) {
            const tile_filter_text = "Showing Alarm/Alert Tiles";
            update_filter_mode(tile_filter_text);
            update_tile_view({ "show_alarm_tiles": true, "show_normal_tiles": false, "tile_filter_text": tile_filter_text }).then(function() {
                update_tile_info();
            });
        });

        $("#tile-filter-show-normal").click(function(event) {
            const tile_filter_text = "Showing Normal Tiles";
            update_filter_mode(tile_filter_text);
            update_tile_view({ "show_alarm_tiles": false, "show_normal_tiles": true, "tile_filter_text": tile_filter_text }).then(function() {
                update_tile_info();
            });
        });

        // check for any tiles, create a default if needed, finish initialization
        channels.channel_list().then(function(channel_list) {
            if (channel_list.length === 0) {
                channels.create_channel("Default", []).then(function() {
                    redraw_tiles();
                });
            } else {
                redraw_tiles();
            }
            return load_update_interval();
        }).then(function() {
            schedule_interval();
            event_alerts.add_callback(event_alert_callback);
            alarms.add_callback(alarm_callback);
            // add_selection_callback(selection_listener);
        });

        return {
            "add_selection_callback": add_selection_callback,
            "redraw_tiles": redraw_tiles,
            "update_tile_info": update_tile_info,
            "select": function(name) {
                scroll_to_tile(name);
                select(name);
            },
            "unselect": unselect,
            "selected": selected,
            "set_update_interval": function(seconds) {
                set_update_interval(seconds).then(function() {
                    schedule_interval();
                });
            },
            "get_update_interval": function() {
                return update_interval;
            },
            "blink": function(name) {
                scroll_to_tile(name);
                blink(10, name);
            },
            "shown": shown
        };
    });