text:
#
# Copyright 2022 Centreon (http://www.centreon.com/)
#
# Centreon is a full-fledged industry-strength solution that meets
# the needs in IT infrastructure and application monitoring for
# service performance.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

package storage::emc::vplex::restapi::custom::apiv1;

use strict;
use warnings;
use centreon::plugins::http;
use JSON::XS;

sub new {
    my ($class, %options) = @_;
    my $self  = {};
    bless $self, $class;

    if (!defined($options{output})) {
        print "Class Custom: Need to specify 'output' argument.\n";
        exit 3;
    }
    if (!defined($options{options})) {
        $options{output}->add_option_msg(short_msg => "Class Custom: Need to specify 'options' argument.");
        $options{output}->option_exit();
    }

    if (!defined($options{noptions})) {
        $options{options}->add_options(arguments => {
            'hostname:s'       => { name => 'hostname' },
            'port:s'           => { name => 'port' },
            'proto:s'          => { name => 'proto' },
            'vplex-username:s' => { name => 'vplex_username' },
            'vplex-password:s' => { name => 'vplex_password' },
            'timeout:s'        => { name => 'timeout' }
        });
    }
    $options{options}->add_help(package => __PACKAGE__, sections => 'REST API V1 OPTIONS', once => 1);

    $self->{output} = $options{output};
    $self->{http} = centreon::plugins::http->new(%options, default_backend => 'curl');

    return $self;
}

sub set_options {
    my ($self, %options) = @_;

    $self->{option_results} = $options{option_results};
}

sub set_defaults {}

sub check_options {
    my ($self, %options) = @_;

    $self->{hostname} = defined($self->{option_results}->{hostname}) ? $self->{option_results}->{hostname} : '';
    $self->{port} = defined($self->{option_results}->{port}) ? $self->{option_results}->{port} : 443;
    $self->{proto} = defined($self->{option_results}->{proto}) ? $self->{option_results}->{proto} : 'https';
    $self->{vplex_username} = defined($self->{option_results}->{vplex_username}) ? $self->{option_results}->{vplex_username} : '';
    $self->{vplex_password} = defined($self->{option_results}->{vplex_password}) ? $self->{option_results}->{vplex_password} : '';
    $self->{timeout} = defined($self->{option_results}->{timeout}) ? $self->{option_results}->{timeout} : 30;

    if ($self->{hostname} eq '') {
        $self->{output}->add_option_msg(short_msg => "Need to specify --hostname option.");
        $self->{output}->option_exit();
    }
    if ($self->{vplex_username} eq '') {
        $self->{output}->add_option_msg(short_msg => "Need to specify --vplex-username option.");
        $self->{output}->option_exit();
    }
    if ($self->{vplex_password} eq '') {
        $self->{output}->add_option_msg(short_msg => "Need to specify --vplex-password option.");
        $self->{output}->option_exit();
    }

    return 0;
}

sub build_options_for_httplib {
    my ($self, %options) = @_;

    $self->{option_results}->{hostname} = $self->{hostname};
    $self->{option_results}->{timeout} = $self->{timeout};
    $self->{option_results}->{port} = $self->{port};
    $self->{option_results}->{proto} = $self->{proto};
}

sub settings {
    my ($self, %options) = @_;

    $self->build_options_for_httplib();
    $self->{http}->add_header(key => 'Username', value => $self->{vplex_username});
    $self->{http}->add_header(key => 'Password', value => $self->{vplex_password});
    $self->{http}->set_options(%{$self->{option_results}});
}

sub request_api {
    my ($self, %options) = @_;

    $self->settings();

    my $content = $self->{http}->request(url_path => $options{endpoint});

    my $decoded;
    eval {
        $decoded = JSON::XS->new->utf8->decode($content);
    };
    if ($@) {
        $self->{output}->add_option_msg(short_msg => "Cannot decode json response");
        $self->{output}->option_exit();
    }

    return $decoded;
}

sub get_cluster_communication {
    my ($self, %options) = @_;

    my $items = $self->request_api(endpoint => '/vplex/cluster-witness/components/*');

    my $results = [];
    foreach my $context (@{$items->{response}->{context}}) {
        my $entry = {};
        foreach my $attribute (@{$context->{attributes}}) {
            $attribute->{name} =~ s/-/_/g;
            $entry->{ $attribute->{name} } = $attribute->{value};
        }

        push @$results, $entry;
    }

    return $results;
}

sub get_distributed_devices {
    my ($self, %options) = @_;

    my $items = $self->request_api(endpoint => '/vplex/distributed-storage/distributed-devices/*');

    my $results = [];
    foreach my $context (@{$items->{response}->{context}}) {
        my $entry = {};
        foreach my $attribute (@{$context->{attributes}}) {
            $attribute->{name} =~ s/-/_/g;
            $entry->{ $attribute->{name} } = $attribute->{value};
        }

        push @$results, $entry;
    }

    return $results;
}

sub get_storage_volumes {
    my ($self, %options) = @_;

    my $items = $self->request_api(endpoint => '/vplex/clusters/*/storage-elements/storage-volumes/*');

    my $results = [];
    foreach my $context (@{$items->{response}->{context}}) {
        my $entry = {};
        foreach my $attribute (@{$context->{attributes}}) {
            $attribute->{name} =~ s/-/_/g;
            $entry->{ $attribute->{name} } = $attribute->{value};
        }
        my $cluster_name = 'unknown';
        $cluster_name = $1 if ($context->{parent} =~ /^\/clusters\/(.*?)\//);
        $entry->{cluster_name} = $cluster_name;

        push @$results, $entry;
    }

    return $results;
}

sub get_devices {
    my ($self, %options) = @_;

    my $items = $self->request_api(endpoint => '/vplex/clusters/*/devices/*');

    my $results = [];
    foreach my $context (@{$items->{response}->{context}}) {
        my $entry = {};
        foreach my $attribute (@{$context->{attributes}}) {
            $attribute->{name} =~ s/-/_/g;
            $entry->{ $attribute->{name} } = $attribute->{value};
        }
        my $cluster_name = 'unknown';
        $cluster_name = $1 if ($context->{parent} =~ /^\/clusters\/(.*?)\//);
        $entry->{cluster_name} = $cluster_name;

        push @$results, $entry;
    }

    return $results;
}

sub get_fans {
    my ($self, %options) = @_;

    my $items = $self->request_api(endpoint => '/vplex/engines/*/fans/*');

    my $results = [];
    foreach my $context (@{$items->{response}->{context}}) {
        my $entry = {};
        foreach my $attribute (@{$context->{attributes}}) {
            $attribute->{name} =~ s/-/_/g;
            $entry->{ $attribute->{name} } = $attribute->{value};
        }
        my $engine_name = 'unknown';
        $engine_name = $1 if ($context->{parent} =~ /^\/engines\/engine-(.*?)\//);
        $entry->{engine_id} = $engine_name;

        push @$results, $entry;
    }

    return $results;
}

sub get_psus {
    my ($self, %options) = @_;

    my $items = $self->request_api(endpoint => '/vplex/engines/*/power-supplies/*');

    my $results = [];
    foreach my $context (@{$items->{response}->{context}}) {
        my $entry = {};
        foreach my $attribute (@{$context->{attributes}}) {
            $attribute->{name} =~ s/-/_/g;
            $entry->{ $attribute->{name} } = $attribute->{value};
        }
        my $engine_name = 'unknown';
        $engine_name = $1 if ($context->{parent} =~ /^\/engines\/engine-(.*?)\//);
        $entry->{engine_id} = $engine_name;

        push @$results, $entry;
    }

    return $results;
}

sub get_directors {
    my ($self, %options) = @_;

    my $items = $self->request_api(endpoint => '/vplex/engines/*/directors/*');

    my $results = [];
    foreach my $context (@{$items->{response}->{context}}) {
        my $entry = {};
        foreach my $attribute (@{$context->{attributes}}) {
            $attribute->{name} =~ s/-/_/g;
            $entry->{ $attribute->{name} } = $attribute->{value};
        }
        my $engine_name = 'unknown';
        $engine_name = $1 if ($context->{parent} =~ /^\/engines\/engine-(.*?)\//);
        $entry->{engine_id} = $engine_name;

        push @$results, $entry;
    }

    return $results;
}

1;

__END__

=head1 NAME

VPLEX REST API V1

=head1 SYNOPSIS

Vplex rest api v1

=head1 REST API V1 OPTIONS

=over 8

=item B<--hostname>

Hostname.

=item B<--port>

Port used (Default: 443)

=item B<--proto>

Specify https if needed (Default: 'https')

=item B<--vplex-username>

API Username.

=item B<--vplex-password>

API Password.

=item B<--timeout>

Set HTTP timeout

=back

=head1 DESCRIPTION

B<custom>.

=cut


text:
var sortableSubitems = function () {

    var confObj;
    var labelsObj;
    var createGroups;
    var createOptions;
    var dataTable;
    var shownColumns;

    //Retrieves multiple values delimited by '|'
    function getCookieSubMultiValue(subName) {
        var sub = YAHOO.util.Cookie.getSub(confObj.cookieName, subName);
        if (sub) { sub = unescape(sub).split('|'); }
        return sub;
    }

    // Adds or replaces subcookie with multiple delimited ('|') values
    // Sets expiry date 10 years from now
    function setCookieSubMultiValue(subName, values) {
        var joined = values.join('|');
        var expiresDate = new Date();
        expiresDate.setFullYear(expiresDate.getFullYear() + 10);

        YAHOO.util.Cookie.setSub(confObj.cookieName, subName, escape(joined), {
            path : "/",
            expires : expiresDate,
            secure : confObj.cookieSecure,
            domain : confObj.cookieDomain
        });
    }

    function initDataTable(extraColumnDefs){
        var formatName = function(cell, record, column, data) {
            cell.innerHTML =  '' + record.getData('class_icon') + '' + ' ' + '' + data + '';
        }

        var customCheckbox = function(cell, record, column, data) {
            cell.innerHTML = '<input type="checkbox" name="DeleteIDArray[]" value="' + record.getData('node_id') + '" />';
        }

        var customMenu = function(cell, rec, column, data) {
            var createhereMenu = (confObj.classesString != '') ? -1 : "\'child-menu-create-here\'";
            var translationArray = [];
            jQuery(rec.getData('translations')).each(function(i, e) {
                translationArray.push( { 'locale': e,
                                         'name': confObj.languages[e] } );
            });
            var a = new YAHOO.util.Element(document.createElement('a'));
            a.on('click', function(e) {
                ezpopmenu_showTopLevel(e, 'SubitemsContextMenu', { '%nodeID%': rec.getData('node_id'),
                                                                   '%objectID%': rec.getData('contentobject_id'),
                                                                   '%version%': rec.getData('version'),
                                                                   '%languages%': translationArray,
                                                                   '%classList%': confObj.classesString }, rec.getData('name'), rec.getData('node_id'), createhereMenu );
            });
            var div = new YAHOO.util.Element(document.createElement('div'));
            div.addClass('crankfield');
            div.appendTo(a);

            a.appendTo(cell);
        }

        var thumbView = function(cell, record, column, data) {
            var url = record.getData('thumbnail_url');
            if (url) {
                var thBack = 'background: url(' + url + ') no-repeat;';
                var thWidth = ' width: ' + record.getData('thumbnail_width') + 'px;';
                var thHeight = ' height: ' + record.getData('thumbnail_height') + 'px;';
                cell.innerHTML = '

';
            }
            else {
                cell.innerHTML = '';
            }
        }

        var translationView = function(cell, record, column, data) {
            var html = '';
            jQuery(data).each(function(i, e) {
                if (record.getData('can_edit') === true) {
                    html += '';
                }
                html += '';
                if (record.getData('can_edit') === true) {
                    html += ''
                }
            });
            cell.innerHTML = html;
        }

        var updatePriority = function(callback, v) {
            var record = this.getRecord(), dataTable = this.getDataTable(), sortedBy = dataTable.get('sortedBy'), paginator = dataTable.get('paginator');

            var onSuccess = function(data) {
                dataTable.getDataSource().flushCache();
                if (sortedBy.key == 'priority') {
                    dataTable.onPaginatorChangeRequest(paginator.getState({'page':paginator.getCurrentPage()}));
                }
            }

            jQuery.ez('ezjscnode::updatepriority', {
                ContentNodeID: record.getData('parent_node_id'),
                ContentObjectID: record.getData('contentobject_id'),
                PriorityID: [record.getData('node_id')],
                Priority: [v]
            }, onSuccess);
            callback(true, v);
        }

        var columnDefs = [
            {key:"checkbox", label:"", formatter:customCheckbox, resizeable:false},
            {key:"crank", label:"", sortable:false, formatter:customMenu, resizeable:false},
            {key:"thumbnail", label:labelsObj.DATA_TABLE_COLS.thumbnail, sortable:false, formatter:thumbView, resizeable:false},
            {key:"name", label:labelsObj.DATA_TABLE_COLS.name, sortable:true, resizeable:true, formatter:formatName},
            {key:"hidden_status_string", label: labelsObj.DATA_TABLE_COLS.visibility, sortable:false, resizeable:true},
            {key:"class_name", label:labelsObj.DATA_TABLE_COLS.type, sortable:true, resizeable:true},
            {key:"creator", label:labelsObj.DATA_TABLE_COLS.modifier, sortable:false, resizeable:true},
            {key:"modified_date", label:labelsObj.DATA_TABLE_COLS.modified, sortable:true, resizeable:true},
            {key:"published_date", label:labelsObj.DATA_TABLE_COLS.published, sortable:true, resizeable:true},
            {key:"translations", label:labelsObj.DATA_TABLE_COLS.translations, sortable:false, resizeable:true, formatter:translationView},
            {key:"section", label:labelsObj.DATA_TABLE_COLS.section, sortable:true, resizeable:true},
            {key:"node_id", label:labelsObj.DATA_TABLE_COLS.nodeid, sortable:true, resizeable:true},
            {key:"node_remote_id", label:labelsObj.DATA_TABLE_COLS.noderemoteid, sortable:false, resizeable:true},
            {key:"contentobject_id", label:labelsObj.DATA_TABLE_COLS.objectid, sortable:true, resizeable:true},
            {key:"contentobject_remote_id", label:labelsObj.DATA_TABLE_COLS.objectremoteid, sortable:false, resizeable:true},
            {key:"contentobject_state", label:labelsObj.DATA_TABLE_COLS.objectstate, sortable:false, resizeable:true},
            {key:"priority", label: labelsObj.DATA_TABLE_COLS.priority, sortable:true, resizeable:true, editor: new YAHOO.widget.TextboxCellEditor({asyncSubmitter: updatePriority, disableBtns:true, validator:YAHOO.widget.DataTable.validateNumber}) }
        ];
        for (var i=0; i<extraColumnDefs.length; i++) {
        	columnDefs[columnDefs.length] = extraColumnDefs[i];
        }
        // Hide columns based on cookie with ini setting as fallback
        // If neither cookie or ini is set: show all columns
        // Thumbnail column header has label, but is hidden w/CSS
        if (shownColumns && shownColumns.length != 0) {
            var defsLength = columnDefs.length;
            for (var i = 0, l = defsLength; i < l; i++) {
                var columnDef = columnDefs[i];
                if ((jQuery.inArray(columnDef.key, shownColumns) == -1) && columnDef.label != '')
                    columnDef.hidden = true;
            }
        }

        var sectionParser = function(section) {
            if ( section && section.name )
                return section.name;
            return '?';
        };

        var translationsParser = function(translations) {
            return translations.language_list;
        };

        var creatorParser = function(creator) {
            if ( creator && creator.name )
                return creator.name;
            return '?';
        };

        var dataSource = new YAHOO.util.DataSource(confObj.dataSourceURL);
        dataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
        dataSource.maxCacheEntries = 20;    // Caches between paginations. Requires a refresh after async updates to priorities
        dataSource.responseSchema = {
            resultsList: "content.list",
            fields: [
                {key:"name"},
                {key:"hidden_status_string"},
                {key:"class_name"},
                {key:"creator", parser:creatorParser},
                {key:"modified_date"},
                {key:"published_date"},
                {key:"section", parser:sectionParser},
                {key:"translations", parser:translationsParser},
                {key:"version"},
                {key:"node_id"},
                {key:"node_remote_id"},
                {key:"contentobject_id"},
                {key:"contentobject_remote_id"},
                {key:"contentobject_state"},
                {key:"priority"},
                {key:"class_icon"},
                {key:"thumbnail_url"},
                {key:"thumbnail_height"},
                {key:"thumbnail_width"},
                {key:"url"},
                {key:"parent_node_id"},
                {key:"can_edit"},
                // INSERT CUSTOM VALUES HERE!
                {key:"startdate"}
            ],
            metaFields: {
                totalRecords: "content.total_count" // Access to value in the server response
            }
        };
        // insert custom fields into keys
        for (var i=0; i<extraColumnDefs.length; i++) {
        	dataSource.responseSchema["fields"].push({key: extraColumnDefs[i].key});
        }

        var paginator = new YAHOO.widget.Paginator({ rowsPerPage:confObj.rowsPrPage,
                                                     containers: ["bpg"],
                                                     firstPageLinkLabel : labelsObj.ACTION_BUTTONS.first_page,
                                                     lastPageLinkLabel : labelsObj.ACTION_BUTTONS.last_page,
                                                     previousPageLinkLabel : labelsObj.ACTION_BUTTONS.previous_page,
                                                     nextPageLinkLabel : labelsObj.ACTION_BUTTONS.next_page,
                                                     template : '
 {FirstPageLink} {PreviousPageLink}
' +
                                                                '{PageLinks}' +
                                                                '
 {NextPageLink} {LastPageLink}
'
                                                     });

        paginator.subscribe('render', function () {
            var prevPageLink, prevPageLink, prevPageLinkNode, nextPageLinkNode, tpg;

            tpg = YAHOO.util.Dom.get('tpg');

            // Instantiate the UI Component
            prevPageLink = new YAHOO.widget.Paginator.ui.PreviousPageLink(this);
            nextPageLink = new YAHOO.widget.Paginator.ui.NextPageLink(this);

            // render the UI Component
            prevPageLinkNode = prevPageLink.render(tpg);
            nextPageLinkNode = nextPageLink.render(tpg);

            // Append the generated node into the container
            tpg.appendChild(prevPageLinkNode);
            tpg.appendChild(nextPageLinkNode);
        });

        var buildQueryString = function (state,dt) {
            return "::" + state.pagination.rowsPerPage +
                   "::" + state.pagination.recordOffset +
                   "::" + state.sortedBy.key +
                   "::" + ((state.sortedBy.dir === YAHOO.widget.DataTable.CLASS_ASC) ? "1" : "0") +
                   "::" + confObj.nameFilter +
                   "?ContentType=json";
        }

        var tableConfig = {
            initialRequest: "::" + confObj.rowsPrPage + "::0" + "::" + confObj.sortKey + "::" + confObj.sortOrder + "::" + confObj.nameFilter + "?ContentType=json",   // Initial request for first page of data
            dynamicData: true,                                                                                // Enables dynamic server-driven data
            generateRequest: buildQueryString,
            sortedBy : {key:confObj.sortKey,
                        dir:((confObj.sortOrder === 1) ? YAHOO.widget.DataTable.CLASS_ASC : YAHOO.widget.DataTable.CLASS_DESC) },          // Sets UI initial sort arrow
            paginator: paginator,                                                                                // Enables pagination
            MSG_LOADING: labelsObj.DATA_TABLE.msg_loading
        };

        subItemsTable = new YAHOO.widget.DataTable( "content-sub-items-list",
                                                        columnDefs,
                                                        dataSource,
                                                        tableConfig );

        // Enables the cell editing when row has can_edit=true
        subItemsTable.subscribe("cellClickEvent", function (oArgs) {
            var target = oArgs.target,
            record = this.getRecord(target);
            if (record.getData('can_edit') === true) {
                this.showCellEditor(target);
            }
        });

        // Update totalRecords on the fly with value from server
        subItemsTable.handleDataReturnPayload = function(oRequest, oResponse, oPayload) {
            oPayload.totalRecords = oResponse.meta.totalRecords;
            return oPayload;
        }

        // Table options

        // Shows dialog, creating one when necessary
        var colLayoutHasChanged = true;
        var showTblOptsDialog = function(e) {
            YAHOO.util.Event.stopEvent(e);

            if (colLayoutHasChanged) {
                // Populate Dialog
                var colOptionsHTML = '<fieldset>';
                colOptionsHTML += '<legend>' + labelsObj.TABLE_OPTIONS.header_noipp + '</legend>
';

                var rowsPerPageDef = [ {id:1, count:10}, {id:2, count:25}, {id:3, count:50} ];

                var rowsPerPageDefLen = rowsPerPageDef.length;
                for (var i = 0, l = rowsPerPageDefLen; i < l ; i++) {
                    var rowDef = rowsPerPageDef[i];
                    colOptionsHTML += '
'+ rowDef.count + '';
                    colOptionsHTML += '<input id="table-option-row-btn-' + rowDef.id + '" type="radio" name="TableOptionValue" value="' + rowDef.count + '"' + ( confObj.rowsPrPage == rowDef.count ? ' checked="checked"' : '' ) + ' />
';

                    YAHOO.util.Event.on("table-option-row-btn-" + rowDef.id, "click", function(e, a) {
                        paginator.setRowsPerPage(a.count);
                        $.ez.setPreference('admin_list_limit', a.id);
                    }, rowDef);
                }

                colOptionsHTML += '
</fieldset>
';
                colOptionsHTML += '<fieldset>';

                var columns = subItemsTable.getColumnSet().keys;
                colOptionsHTML += '<legend>' + labelsObj.TABLE_OPTIONS.header_vtc + '</legend>
';

                // Create one section in the SimpleDialog for each column
                var columnsLength = columns.length;
                for (var i = 0, l = columnsLength; i < l; i++) {
                    var column = columns[i], label = column.getDefinition().label, key = column.getDefinition().key;

                    // Skip empty columns
                    if (!label || !key)
                        continue;

                    colOptionsHTML += '
'+ label + '';
                    colOptionsHTML += '<input id="table-option-col-btn-' + i + '" type="checkbox" name="TableOptionColumn" value="' + key + '"' + ( jQuery.inArray( key, shownColumns ) != -1 ? ' checked="checked"' : ''  ) + ' />
';

                    YAHOO.util.Event.on("table-option-col-btn-" + i, "click", function(e, a) {
                        if (this.checked) {
                            subItemsTable.showColumn(a);
                        }
                        else {
                            subItemsTable.hideColumn(a);
                        }
                        var shownKeys = [];
                        $('#to-dialog-container input[name=TableOptionColumn]').each(function(i, e) {
                            if ( $(this).prop('checked') == true )
                                shownKeys.push( $(this).prop('value') );
                        });

                        // Update cookie and local variable
                        setCookieSubMultiValue(confObj.navigationPart, shownKeys);
                        shownColumns = shownKeys;
                    }, key);
                }

                colOptionsHTML += '
</fieldset>';

                tblOptsDialog.setBody(colOptionsHTML);
                colLayoutHasChanged = false;
            }
            tblOptsDialog.show();
        };

        var hideTblOptsDialog = function(e) {
            this.hide();
        };

        // SimpleDialog for Table options

        var tblOptsDialog = new YAHOO.widget.SimpleDialog("to-dialog-container", { width: "25em",
                                                                                visible: false,
                                                                                modal: true,
                                                                                buttons: [ { text: "Close",
                                                                                handler: hideTblOptsDialog } ],
                                                                                fixedcenter: "contained",
                                                                                constrainToViewport: true });

        var escKeyListener = new YAHOO.util.KeyListener(document, { keys:27 },
                                                                  { fn:tblOptsDialog.hide,
                                                                    scope:tblOptsDialog,
                                                                    correctScope:true } );

        tblOptsDialog.cfg.queueProperty("keylisteners", escKeyListener);
        tblOptsDialog.setHeader(labelsObj.TABLE_OPTIONS.header);
        tblOptsDialog.render();

        // Toolbar buttons: Select, Create new, More actions

        var selectItemsBtnAction = function( type, args, item ) {
            $('#content-sub-items-list').find(':checkbox').prop('checked', item.value);
        }

        var selectItemsBtnInvert = function( type, args, item ) {
            var checks = $('#content-sub-items-list').find(':checkbox');
            checks.each(function(){this.checked = !this.checked;});
        }

        var selectItemsBtnActions = [
            { text: labelsObj.ACTION_BUTTONS.select_sav, id: "ezopt-menu-check", value: 1, onclick: { fn: selectItemsBtnAction } },
            { text: labelsObj.ACTION_BUTTONS.select_sn, id: "ezopt-menu-uncheck", value: 0, onclick: { fn: selectItemsBtnAction } },
            { text: labelsObj.ACTION_BUTTONS.select_inv, id: "ezopt-menu-toggle", onclick: { fn: selectItemsBtnInvert } }
        ];

        var selectItemsBtn = new YAHOO.widget.Button({ type: "menu",
                                                       id: "ezbtn-items",
                                                       label: labelsObj.ACTION_BUTTONS.select,
                                                       name: "select-items-button",
                                                       menu: selectItemsBtnActions,
                                                       container:"action-controls" });

        var createNewBtnAction = function( type, args ) {
            var event = args[0], item = args[1];
            $('form[name=children]').append($('<input type="hidden" name="ClassID" value="' + item.value + '" />')).append($('<input type="hidden" name="NewButton" />')).submit();
        }

        var createNewBtn = new YAHOO.widget.Button({ type: "menu",
                                                     id: "ezbtn-new",
                                                     label: labelsObj.ACTION_BUTTONS.create_new,
                                                     name: "create-new-button",
                                                     menu: createOptions,
                                                     container:"action-controls" });

        // Disable button if user has no available content classes to create objects of
        if (createGroups.length === 0) createNewBtn.set('disabled',true);

        var createNewBtnMenu  = createNewBtn.getMenu();
        createNewBtnMenu.cfg.setProperty("scrollincrement", 5);
        createNewBtnMenu.subscribe("click", createNewBtnAction);

        var createNewBthGroupsLength = createGroups.length;
        for (var i = 0, l = createNewBthGroupsLength; i < l; i++) {
            var groupName = createGroups[i];
            createNewBtnMenu.setItemGroupTitle(groupName, i);
        }

        var moreActBtnAction = function( type, args, item ) {
            if ($('form[name=children] input[name=DeleteIDArray[]]:checked').length == 0)
                return;

            if (item.value == 0) {
                $('form[name=children]').append($('<input type="hidden" name="RemoveButton" />')).submit();
            } else {
                $('form[name=children]').append($('<input type="hidden" name="MoveButton" />')).submit();
            }
        }

        var moreActBtnActions = [
            { text: labelsObj.ACTION_BUTTONS.more_actions_rs, id: "ezopt-menu-remove", value: 0, onclick: { fn: moreActBtnAction }, disabled: false },
            { text: labelsObj.ACTION_BUTTONS.more_actions_ms, id: "ezopt-menu-move", value: 1, onclick: { fn: moreActBtnAction }, disabled: false }
        ];

        var noMoreActBtnActions = [
            { text: labelsObj.ACTION_BUTTONS.more_actions_no, disabled: true }
        ];

        var moreActBtn = new YAHOO.widget.Button({ type: "menu",
                                                   id: "ezbtn-more",
                                                   label: labelsObj.ACTION_BUTTONS.more_actions,
                                                   name: "more-actions-button",
                                                   menu: noMoreActBtnActions,
                                                   container:"action-controls" });

        //  enable 'more actions' when rows are checked
        moreActBtn.getMenu().subscribe("beforeShow", function () {
            if ($('form[name=children] input[name=DeleteIDArray[]]:checked').length == 0) {
                this.clearContent();
                this.addItems(noMoreActBtnActions);
                this.render();
            } else {
                this.clearContent();
                this.addItems(moreActBtnActions);
                this.render();
            }
        });

        var tableOptionsBtn = new YAHOO.widget.Button({ label: labelsObj.ACTION_BUTTONS.table_options,
                                                        id:"ezbtn-options",
                                                        container:"action-controls",
                                                        onclick: { fn: showTblOptsDialog, obj: this, scope: true } });

    return subItemsTable;
    }

    return {
        init: function (conf, labels, groups, options, extraColumnDefs) {
            confObj = conf
            labelsObj = labels;
            createGroups = groups;
            createOptions = options;

            shownColumns = getCookieSubMultiValue(confObj.navigationPart);
            if (shownColumns == null) shownColumns = confObj.defaultShownColumns[confObj.navigationPart];

            dataTable = initDataTable(extraColumnDefs);
        }
    };

}();

redpajama_set_name: RedPajamaGithub


text:
using ARKBreedingStats.species;
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using ARKBreedingStats.Library;
using ARKBreedingStats.utils;

namespace ARKBreedingStats.uiControls
{
    public partial class RegionColorChooser : UserControl
    {
        public event Action RegionColorChosen;
        private readonly NoPaddingButton[] _buttonColors;
        private byte[] _selectedRegionColorIds;
        private byte[] _selectedColorIdsAlternative;
        public bool[] ColorRegionsUseds;
        private readonly MyColorPicker _colorPicker;
        private ColorRegion[] _colorRegions;
        private readonly ToolTip _tt = new ToolTip();
        /// <summary>
        /// If true, the button text will display the region and color id.
        /// </summary>
        public bool VerboseButtonTexts { get; set; }

        public RegionColorChooser()
        {
            InitializeComponent();

            _buttonColors = new NoPaddingButton[Ark.ColorRegionCount];
            for (int i = 0; i < Ark.ColorRegionCount; i++)
            {
                var b = new NoPaddingButton { Width = 27, Height = 27, Margin = new Padding(1), Text = i.ToString() };
                var ii = i;
                b.Click += (s, e) => ChooseColor(ii, b);
                _buttonColors[i] = b;
                flowLayoutPanel1.Controls.Add(b);
            }

            _selectedRegionColorIds = new byte[Ark.ColorRegionCount];
            _selectedColorIdsAlternative = new byte[Ark.ColorRegionCount];

            _colorPicker = new MyColorPicker();
            _tt.AutoPopDelay = 7000;
            Disposed += RegionColorChooser_Disposed;
        }

        public void SetOneButtonPerRow(bool onePerRow)
        {
            foreach (var b in _buttonColors)
                flowLayoutPanel1.SetFlowBreak(b, onePerRow);
        }

        public void SetSpecies(Species species, byte[] colorIDs)
        {
            _selectedRegionColorIds = colorIDs.ToArray();
            _selectedColorIdsAlternative = null;

            if (species?.colors != null)
            {
                _colorRegions = species.colors;
                ColorRegionsUseds = species.EnabledColorRegions;
            }
            else
            {
                // species-info is not available, show all region-buttons
                ColorRegionsUseds = new bool[Ark.ColorRegionCount];
                _colorRegions = new ColorRegion[Ark.ColorRegionCount];
                for (int i = 0; i < Ark.ColorRegionCount; i++)
                {
                    _colorRegions[i] = new ColorRegion();
                    ColorRegionsUseds[i] = true;
                }
            }

            for (int r = 0; r < _buttonColors.Length; r++)
            {
                _buttonColors[r].Visible = ColorRegionsUseds[r];

                if (ColorRegionsUseds[r])
                {
                    _buttonColors[r].AlternativeColorPossible = false;
                    SetColorButton(_buttonColors[r], r);
                }
            }
        }

        public byte[] ColorIds => _selectedRegionColorIds.ToArray();
        public byte[] ColorIdsAlsoPossible
        {
            get => _selectedColorIdsAlternative?.ToArray();
            set
            {
                _selectedColorIdsAlternative = value;
                if (_selectedColorIdsAlternative == null)
                {
                    foreach (var bt in _buttonColors)
                        bt.AlternativeColorPossible = false;

                    return;
                }
                for (int i = 0; i < _buttonColors.Length; i++)
                    _buttonColors[i].AlternativeColorPossible = _selectedColorIdsAlternative.Length < i && _selectedColorIdsAlternative[i] != 0;
            }
        }

        public void Clear()
        {
            _selectedColorIdsAlternative = null;
            SetColorIds(new byte[_buttonColors.Length]);
        }

        /// <summary>
        /// Set colors to random ids of the available colors.
        /// </summary>
        internal void RandomColors()
        {
            _selectedColorIdsAlternative = null;
            SetColorIds(values.Values.V.Colors.GetRandomColors());
        }

        /// <summary>
        /// Set colors to random values in the set of natural occurring colors of the species.
        /// </summary>
        internal void RandomNaturalColors(Species species)
        {
            _selectedColorIdsAlternative = null;
            SetColorIds(species?.RandomSpeciesColors());
        }

        private void SetColorIds(byte[] colorIds)
        {
            if (colorIds == null)
            {
                Clear();
                return;
            }

            for (int r = 0; r < _buttonColors.Length; r++)
            {
                _selectedRegionColorIds[r] = colorIds.Length > r ? colorIds[r] : (byte)0;
                _buttonColors[r].AlternativeColorPossible = false;
                SetColorButton(_buttonColors[r], r);
            }
            RegionColorChosen?.Invoke();
        }

        private void ChooseColor(int region, Button sender)
        {
            if (!_colorPicker.isShown && _colorRegions != null && region < Ark.ColorRegionCount)
            {
                _colorPicker.PickColor(_selectedRegionColorIds[region], _colorRegions[region].name + " (region " + region + ")", _colorRegions[region]?.naturalColors, _selectedColorIdsAlternative?[region] ?? 0);
                if (_colorPicker.ShowDialog() == DialogResult.OK)
                {
                    // color was chosen
                    _selectedRegionColorIds[region] = _colorPicker.SelectedColorId;
                    if (_colorPicker.SelectedColorIdAlternative != 0)
                    {
                        if (_selectedColorIdsAlternative == null)
                            _selectedColorIdsAlternative = new byte[Ark.ColorRegionCount];
                        _selectedColorIdsAlternative[region] = _colorPicker.SelectedColorIdAlternative;
                        _buttonColors[region].AlternativeColorPossible = true;
                    }
                    else
                    {
                        _buttonColors[region].AlternativeColorPossible = false;
                        if (_selectedColorIdsAlternative != null)
                            _selectedColorIdsAlternative[region] = 0;
                    }
                    SetColorButton(sender, region);
                    RegionColorChosen?.Invoke();
                }
            }
        }

        private void SetColorButton(Button bt, int region)
        {
            byte colorId = _selectedRegionColorIds[region];
            bt.SetBackColorAndAccordingForeColor(CreatureColors.CreatureColor(colorId));
            if (VerboseButtonTexts)
                bt.Text = $"[{region}]: {colorId}";
            // tooltip
            if (_colorRegions?[region] != null)
                _tt.SetToolTip(bt, $"[{region}] {_colorRegions[region].name}:\n{colorId}: {CreatureColors.CreatureColorName(colorId)}");
        }

        private void RegionColorChooser_Disposed(object sender, EventArgs e)
        {
            _tt.RemoveAll();
            _tt.Dispose();
        }

        /// <summary>
        /// True if a color is new in this species.
        /// </summary>
        internal bool ColorNewInSpecies;
        /// <summary>
        /// True if color is new in this region (but exists in other region in this species).
        /// </summary>
        internal bool ColorNewInRegion;

        internal void SetRegionColorsExisting(CreatureCollection.ColorExisting[] colorAlreadyAvailable)
        {
            ColorNewInRegion = false;
            ColorNewInSpecies = false;

            var parameter = CreatureCollection.ColorExisting.Unknown;
            for (int ci = 0; ci < Ark.ColorRegionCount; ci++)
            {
                if (colorAlreadyAvailable != null)
                    parameter = colorAlreadyAvailable[ci];
                switch (parameter)
                {
                    case CreatureCollection.ColorExisting.ColorIsNew:
                        _buttonColors[ci].ColorStatus = CreatureCollection.ColorExisting.ColorIsNew;
                        ColorNewInSpecies = true;
                        break;
                    case CreatureCollection.ColorExisting.ColorExistingInOtherRegion:
                        _buttonColors[ci].ColorStatus = CreatureCollection.ColorExisting.ColorExistingInOtherRegion;
                        ColorNewInRegion = true;
                        break;
                    default:
                        _buttonColors[ci].ColorStatus = CreatureCollection.ColorExisting.ColorExistingInRegion;
                        break;
                }
                _buttonColors[ci].Invalidate();
            }
        }

        private class NoPaddingButton : Button
        {
            public CreatureCollection.ColorExisting ColorStatus { get; set; }
            public bool AlternativeColorPossible { get; set; }

            protected override void OnPaint(PaintEventArgs pe)
            {
                Color statusColor;
                switch (ColorStatus)
                {
                    case CreatureCollection.ColorExisting.ColorIsNew:
                        statusColor = Color.Gold;
                        break;
                    case CreatureCollection.ColorExisting.ColorExistingInOtherRegion:
                        statusColor = Color.DarkGreen;
                        break;
                    default:
                        statusColor = SystemColors.Control;
                        break;
                }

                using (var b = new SolidBrush(statusColor))
                {
                    var defaultVisibleRectangle = ClientRectangle;
                    var shrinkStatus = -4;
                    if (AlternativeColorPossible)
                    {
                        b.Color = Color.Red;
                        pe.Graphics.FillRectangle(b, defaultVisibleRectangle);
                        defaultVisibleRectangle.Inflate(-1, -1);
                        shrinkStatus = -3;
                    }
                    b.Color = statusColor;
                    pe.Graphics.FillRectangle(b, defaultVisibleRectangle);

                    b.Color = Color.Gray;
                    defaultVisibleRectangle.Inflate(shrinkStatus, shrinkStatus);
                    pe.Graphics.FillRectangle(b, defaultVisibleRectangle);
                    defaultVisibleRectangle.Inflate(-1, -1);
                    b.Color = BackColor;
                    pe.Graphics.FillRectangle(b, defaultVisibleRectangle);
                }

                if (string.IsNullOrEmpty(Text)) return;
                StringFormat stringFormat = new StringFormat();
                stringFormat.Alignment = StringAlignment.Center;
                stringFormat.LineAlignment = StringAlignment.Center;
                using (var b = new SolidBrush(ForeColor))
                    pe.Graphics.DrawString(Text, Font, b, ClientRectangle, stringFormat);
            }
        }

    }
}


text:
package org.apache.hadoop.hdfs.server.datanode;

import java.io.File;
import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.DFSTestUtil;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
import org.apache.hadoop.hdfs.DFSConfigKeys;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.impl.Log4JLogger;
import org.apache.log4j.Level;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;

/**
 * Test the ability of a DN to tolerate volume failures.
 */
public class TestDataNodeVolumeFailureToleration {

  private static final Log LOG = LogFactory.getLog(TestDataNodeVolumeFailureToleration.class);
  {
    ((Log4JLogger)TestDataNodeVolumeFailureToleration.LOG).getLogger().setLevel(Level.ALL);
  }

  private FileSystem fs;
  private MiniDFSCluster cluster;
  private Configuration conf;
  private String dataDir;

  // Sleep at least 3 seconds (a 1s heartbeat plus padding) to allow
  // for heartbeats to propagate from the datanodes to the namenode.
  final int WAIT_FOR_HEARTBEATS = 3000;

  // Wait at least (2 * re-check + 10 * heartbeat) seconds for
  // a datanode to be considered dead by the namenode.
  final int WAIT_FOR_DEATH = 15000;

  @Before
  public void setUp() throws Exception {
    conf = new Configuration();
    conf.setLong("dfs.block.size", 512L);
    /*
     * Lower the DN heartbeat, DF rate, and recheck interval to one second
     * so state about failures and datanode death propagates faster.
     */
    conf.setInt(DFSConfigKeys.DFS_HEARTBEAT_INTERVAL_KEY, 1);
    conf.setInt(DFSConfigKeys.DFS_DF_INTERVAL_KEY, 1000);
    conf.setInt("heartbeat.recheck.interval", 1000);
    // Allow a single volume failure (there are two volumes)
    conf.setInt("dfs.datanode.failed.volumes.tolerated", 1);
    cluster = new MiniDFSCluster(conf, 1, true, null);
    cluster.waitActive();
    fs = cluster.getFileSystem();
    dataDir = cluster.getDataDirectory();
  }

  @After
  public void tearDown() throws Exception {
    for (int i = 0; i < 3; i++) {
      new File(dataDir, "data"+(2*i+1)).setExecutable(true);
      new File(dataDir, "data"+(2*i+2)).setExecutable(true);
    }
    cluster.shutdown();
  }

  /**
   * Test the DFS_DATANODE_FAILED_VOLUMES_TOLERATED_KEY configuration
   * option, ie the DN shuts itself down when the number of failures
   * experienced drops below the tolerated amount.
   */
  @Test
  public void testConfigureMinValidVolumes() throws Exception {
    assumeTrue(!System.getProperty("os.name").startsWith("Windows"));

    // Bring up two additional datanodes that need both of their volumes
    // functioning in order to stay up.
    conf.setInt("dfs.datanode.failed.volumes.tolerated", 0);
    cluster.startDataNodes(conf, 2, true, null, null);
    cluster.waitActive();
    FSNamesystem ns = cluster.getNameNode().getNamesystem();
    long origCapacity = DFSTestUtil.getLiveDatanodeCapacity(ns);
    long dnCapacity = DFSTestUtil.getDatanodeCapacity(ns, 0);

    // Fail a volume on the 2nd DN
    File dn2Vol1 = new File(dataDir, "data"+(2*1+1));
    assertTrue("Couldn't chmod local vol", dn2Vol1.setExecutable(false));

    // Should only get two replicas (the first DN and the 3rd)
    Path file1 = new Path("/test1");
    DFSTestUtil.createFile(fs, file1, 1024, (short)3, 1L);
    DFSTestUtil.waitReplication(fs, file1, (short)2);

    // Check that this single failure caused a DN to die.
    DFSTestUtil.waitForDatanodeStatus(ns, 2, 1, 0,
        origCapacity - (1*dnCapacity), WAIT_FOR_HEARTBEATS);

    // If we restore the volume we should still only be able to get
    // two replicas since the DN is still considered dead.
    assertTrue("Couldn't chmod local vol", dn2Vol1.setExecutable(true));
    Path file2 = new Path("/test2");
    DFSTestUtil.createFile(fs, file2, 1024, (short)3, 1L);
    DFSTestUtil.waitReplication(fs, file2, (short)2);
  }

  /**
   * Restart the datanodes in the cluster with a new volume tolerated value.
   * @param volTolerated
   * @param manageDfsDirs
   * @throws IOException
   */
  private void restartDatanodes(int volTolerated, boolean manageDfsDirs)
      throws IOException {
    cluster.shutdownDataNodes();
    conf.setInt("dfs.datanode.failed.volumes.tolerated", volTolerated);
    cluster.startDataNodes(conf, 1, manageDfsDirs, null, null);
    cluster.waitActive();
  }

  /**
   * Test for different combination of volume configs and volumes
   * tolerated values.
   */
  @Test
  public void testInvalidFailedVolumesConfig() throws Exception {
    assumeTrue(!System.getProperty("os.name").startsWith("Windows"));

    // Check if DN exits for an invalid conf value.
    testVolumeConfig(-1, 0, false, true);

    testVolumeConfig(100, 0, false, true);

    // Test for one failed volume
    testVolumeConfig(0, 1, false, false);

    // Test for one failed volume with 1 tolerable volume
    testVolumeConfig(1, 1, true, false);

    // Test all good volumes
    testVolumeConfig(0, 0, true, false);

    // Test all failed volumes
    testVolumeConfig(0, 2, false, false);
  }

  /**
   * Tests for a given volumes to be tolerated and volumes failed.
   */
  private void testVolumeConfig(int volumesTolerated, int volumesFailed,
      boolean expectDnUp, boolean manageDfsDirs)
      throws IOException, InterruptedException {
    File dir0 = new File(dataDir, "data1");
    File dir1 = new File(dataDir, "data2");

    // Fail the current directory since invalid storage directory perms
    // get fixed up automatically on datanode startup.
    File[] currDirs = { new File(dir0, "/current"),
                        new File(dir1, "/current") };

    try {
      for (int i = 0; i < volumesFailed; i++) {
        prepareDirToFail(currDirs[i]);
      }
      try {
        restartDatanodes(volumesTolerated, manageDfsDirs);
        assertEquals(expectDnUp, cluster.getDataNodes().get(0).isDatanodeUp());
      } catch (IOException ioe) {
        assertFalse("Expected successful restart but got " + ioe, expectDnUp);
      }
    } finally {
      for (File dir : currDirs) {
        FileUtil.chmod(dir.toString(), "755");
      }
    }
  }

  /**
   * Prepare directories for a failure, set dir permission to 000
   * @param dir
   * @throws IOException
   * @throws InterruptedException
   */
  private void prepareDirToFail(File dir) throws IOException,
      InterruptedException {
    dir.mkdirs();
    assertEquals("Couldn't chmod local vol", 0,
        FileUtil.chmod(dir.toString(), "000"));
  }

  /**
   * Test that a volume that is considered failed on startup is seen as
   *  a failed volume by the NN.
   */
  @Test
  public void testFailedVolumeOnStartupIsCounted() throws Exception {
    assumeTrue(!System.getProperty("os.name").startsWith("Windows"));

    FSNamesystem ns = cluster.getNameNode().getNamesystem();
    long origCapacity = DFSTestUtil.getLiveDatanodeCapacity(ns);
    File dir = new File(dataDir, "data1/current");

    try {
      prepareDirToFail(dir);
      restartDatanodes(1, false);
      // The cluster is up..
      assertTrue(cluster.getDataNodes().get(0).isDatanodeUp());
      // but there has been a single volume failure
      DFSTestUtil.waitForDatanodeStatus(ns, 1, 0, 1,
          origCapacity / 2, WAIT_FOR_HEARTBEATS);
    } finally {
      FileUtil.chmod(dir.toString(), "755");
    }
  }

}