1package autotest.afe;
2
3import autotest.afe.create.CreateJobViewPresenter.JobCreateListener;
4import autotest.common.CustomHistory.HistoryToken;
5import autotest.common.SimpleCallback;
6import autotest.common.Utils;
7import autotest.common.table.DataSource;
8import autotest.common.table.DataSource.DataCallback;
9import autotest.common.table.DataSource.Query;
10import autotest.common.table.DataSource.SortDirection;
11import autotest.common.table.DataTable;
12import autotest.common.table.DatetimeSegmentFilter;
13import autotest.common.table.DynamicTable;
14import autotest.common.table.DynamicTable.DynamicTableListener;
15import autotest.common.table.JSONObjectSet;
16import autotest.common.table.RpcDataSource;
17import autotest.common.table.SelectionManager;
18import autotest.common.table.SimpleFilter;
19import autotest.common.table.TableDecorator;
20import autotest.common.ui.ContextMenu;
21import autotest.common.ui.DetailView;
22import autotest.common.ui.NotifyManager;
23import autotest.common.ui.TableActionsPanel.TableActionsListener;
24
25import com.google.gwt.event.dom.client.ClickEvent;
26import com.google.gwt.event.dom.client.ClickHandler;
27import com.google.gwt.event.dom.client.KeyCodes;
28import com.google.gwt.event.dom.client.KeyPressEvent;
29import com.google.gwt.event.dom.client.KeyPressHandler;
30import com.google.gwt.event.logical.shared.ValueChangeEvent;
31import com.google.gwt.event.logical.shared.ValueChangeHandler;
32import com.google.gwt.json.client.JSONArray;
33import com.google.gwt.json.client.JSONBoolean;
34import com.google.gwt.json.client.JSONNumber;
35import com.google.gwt.json.client.JSONObject;
36import com.google.gwt.json.client.JSONString;
37import com.google.gwt.json.client.JSONValue;
38import com.google.gwt.user.client.Command;
39import com.google.gwt.user.client.ui.Button;
40import com.google.gwt.user.client.ui.CheckBox;
41import com.google.gwt.user.client.ui.TextBox;
42
43import java.util.List;
44import java.util.Map;
45import java.util.Set;
46
47public class HostDetailView extends DetailView implements DataCallback, TableActionsListener {
48    private static final String[][] HOST_JOBS_COLUMNS = {
49            {DataTable.WIDGET_COLUMN, ""}, {"type", "Type"}, {"job__id", "Job ID"},
50            {"job_owner", "Job Owner"}, {"job_name", "Job Name"}, {"started_on", "Time started"},
51            {"status", "Status"}
52    };
53    public static final int JOBS_PER_PAGE = 20;
54
55    public interface HostDetailListener {
56        public void onJobSelected(int jobId);
57    }
58
59    private static class HostQueueEntryDataSource extends RpcDataSource {
60        public HostQueueEntryDataSource() {
61            super("get_host_queue_entries", "get_num_host_queue_entries");
62        }
63
64        @Override
65        protected List<JSONObject> handleJsonResult(JSONValue result) {
66            List<JSONObject> resultArray = super.handleJsonResult(result);
67            for (JSONObject row : resultArray) {
68                // get_host_queue_entries() doesn't return type, so fill it in for consistency with
69                // get_host_queue_entries_and_special_tasks()
70                row.put("type", new JSONString("Job"));
71            }
72            return resultArray;
73        }
74    }
75
76    private static class HostJobsTable extends DynamicTable {
77        private static final DataSource normalDataSource = new HostQueueEntryDataSource();
78        private static final DataSource dataSourceWithSpecialTasks =
79            new RpcDataSource("get_host_queue_entries_and_special_tasks",
80                              "get_num_host_queue_entries_and_special_tasks");
81
82        private SimpleFilter hostFilter = new SimpleFilter();
83        private String hostId;
84        private String startTime;
85        private String endTime;
86
87        public HostJobsTable() {
88            super(HOST_JOBS_COLUMNS, normalDataSource);
89            addFilter(hostFilter);
90        }
91
92        public void setHostId(String hostId) {
93            this.hostId = hostId;
94            updateFilter();
95        }
96
97        public void setStartTime(String startTime) {
98            this.startTime = startTime;
99            updateFilter();
100        }
101
102        public void setEndTime(String endTime) {
103            this.endTime = endTime;
104            updateFilter();
105        }
106
107        private void updateFilter() {
108            if (getDataSource() == normalDataSource) {
109                sortOnColumn("job__id", SortDirection.DESCENDING);
110            } else {
111                clearSorts();
112            }
113
114            hostFilter.clear();
115            hostFilter.setParameter("host",
116                                    new JSONNumber(Double.parseDouble(hostId)));
117            if (startTime != null && startTime != "")
118                hostFilter.setParameter("start_time", new JSONString(startTime));
119            if (endTime != null && endTime != "")
120                hostFilter.setParameter("end_time", new JSONString(endTime));
121        }
122
123        public void setSpecialTasksEnabled(boolean enabled) {
124            if (enabled) {
125                setDataSource(dataSourceWithSpecialTasks);
126            } else {
127                setDataSource(normalDataSource);
128            }
129
130            updateFilter();
131        }
132
133        @Override
134        protected void preprocessRow(JSONObject row) {
135            JSONObject job = row.get("job").isObject();
136            JSONString blank = new JSONString("");
137            JSONString jobId = blank, owner = blank, name = blank;
138            if (job != null) {
139                int id = (int) job.get("id").isNumber().doubleValue();
140                jobId = new JSONString(Integer.toString(id));
141                owner = job.get("owner").isString();
142                name = job.get("name").isString();
143            }
144
145            row.put("job__id", jobId);
146            row.put("job_owner", owner);
147            row.put("job_name", name);
148        }
149    }
150
151    private String hostname = "";
152    private String hostId = "";
153    private DataSource hostDataSource = new HostDataSource();
154    private HostJobsTable jobsTable = new HostJobsTable();
155    private TableDecorator tableDecorator = new TableDecorator(jobsTable);
156    private HostDetailListener hostDetailListener = null;
157    private JobCreateListener jobCreateListener = null;
158    private SelectionManager selectionManager;
159
160    private JSONObject currentHostObject;
161
162    private Button lockButton = new Button();
163    private Button reverifyButton = new Button("Reverify");
164    private Button repairButton = new Button("Repair");
165    private CheckBox showSpecialTasks = new CheckBox();
166    private DatetimeSegmentFilter startedTimeFilter = new DatetimeSegmentFilter();
167    private TextBox hostnameInput = new TextBox();
168    private Button hostnameFetchButton = new Button("Go");
169    private TextBox lockReasonInput = new TextBox();
170
171    public HostDetailView(HostDetailListener hostDetailListener,
172                          JobCreateListener jobCreateListener) {
173        this.hostDetailListener = hostDetailListener;
174        this.jobCreateListener = jobCreateListener;
175    }
176
177    @Override
178    public String getElementId() {
179        return "view_host";
180    }
181
182    @Override
183    protected String getFetchControlsElementId() {
184        return "view_host_fetch_controls";
185    }
186
187    private String getFetchByHostnameControlsElementId() {
188        return "view_host_fetch_by_hostname_controls";
189    }
190
191    @Override
192    protected String getDataElementId() {
193        return "view_host_data";
194    }
195
196    @Override
197    protected String getTitleElementId() {
198        return "view_host_title";
199    }
200
201    @Override
202    protected String getNoObjectText() {
203        return "No host selected";
204    }
205
206    @Override
207    protected String getObjectId() {
208        return hostId;
209    }
210
211    protected String getHostname() {
212        return hostname;
213    }
214
215    @Override
216    protected void setObjectId(String id) {
217        if (id.length() == 0) {
218            throw new IllegalArgumentException();
219        }
220        this.hostId = id;
221    }
222
223    @Override
224    protected void fetchData() {
225        JSONObject params = new JSONObject();
226        params.put("id", new JSONString(getObjectId()));
227        fetchDataCommmon(params);
228    }
229
230    private void fetchDataByHostname(String hostname) {
231        JSONObject params = new JSONObject();
232        params.put("hostname", new JSONString(hostname));
233        fetchDataCommmon(params);
234    }
235
236    private void fetchDataCommmon(JSONObject params) {
237        params.put("valid_only", JSONBoolean.getInstance(false));
238        hostDataSource.query(params, this);
239    }
240
241    private void fetchByHostname(String hostname) {
242        fetchDataByHostname(hostname);
243        updateHistory();
244    }
245
246    @Override
247    public void handleTotalResultCount(int totalCount) {}
248
249    @Override
250    public void onQueryReady(Query query) {
251        query.getPage(null, null, null, this);
252    }
253
254    public void handlePage(List<JSONObject> data) {
255        try {
256            currentHostObject = Utils.getSingleObjectFromList(data);
257        }
258        catch (IllegalArgumentException exc) {
259            NotifyManager.getInstance().showError("No such host found");
260            resetPage();
261            return;
262        }
263
264        setObjectId(currentHostObject.get("id").toString());
265
266        String lockedText = Utils.jsonToString(currentHostObject.get(HostDataSource.LOCKED_TEXT));
267        if (currentHostObject.get("locked").isBoolean().booleanValue()) {
268            String lockedBy = Utils.jsonToString(currentHostObject.get("locked_by"));
269            String lockedTime = Utils.jsonToString(currentHostObject.get("lock_time"));
270            String lockReasonText = Utils.jsonToString(currentHostObject.get("lock_reason"));
271            lockedText += ", by " + lockedBy + " on " + lockedTime;
272            lockedText += ", reason: " + lockReasonText;
273        }
274
275        showField(currentHostObject, "status", "view_host_status");
276        showField(currentHostObject, "platform", "view_host_platform");
277        showField(currentHostObject, HostDataSource.HOST_ACLS, "view_host_acls");
278        showField(currentHostObject, HostDataSource.OTHER_LABELS, "view_host_labels");
279        showText(lockedText, "view_host_locked");
280        String shard_url = Utils.jsonToString(currentHostObject.get("shard")).trim();
281        String host_id = Utils.jsonToString(currentHostObject.get("id")).trim();
282        if (shard_url.equals("<null>")){
283            shard_url = "";
284        } else {
285            shard_url = "http://" + shard_url;
286        }
287        shard_url = shard_url + "/afe/#tab_id=view_host&object_id=" + host_id;
288        showField(currentHostObject, "shard", "view_host_shard");
289        getElementById("view_host_shard").setAttribute("href", shard_url);
290
291        String job_id = Utils.jsonToString(currentHostObject.get("current_job")).trim();
292        if (!job_id.equals("<null>")){
293            String job_url = "#tab_id=view_job&object_id=" + job_id;
294            showField(currentHostObject, "current_job", "view_host_current_job");
295            getElementById("view_host_current_job").setAttribute("href", job_url);
296        }
297        hostname = currentHostObject.get("hostname").isString().stringValue();
298
299        String task = Utils.jsonToString(currentHostObject.get("current_special_task")).trim();
300        if (!task.equals("<null>")){
301            String task_url = Utils.getRetrieveLogsUrl("hosts/" + hostname + "/" + task);
302            showField(currentHostObject, "current_special_task", "view_host_current_special_task");
303            getElementById("view_host_current_special_task").setAttribute("href", task_url);
304        }
305
306        showField(currentHostObject, "protection", "view_host_protection");
307        String pageTitle = "Host " + hostname;
308        hostnameInput.setText(hostname);
309        hostnameInput.setWidth("240px");
310        updateLockButton();
311        updateLockReasonInput();
312        displayObjectData(pageTitle);
313
314        jobsTable.setHostId(getObjectId());
315        jobsTable.refresh();
316    }
317
318    @Override
319    public void initialize() {
320        super.initialize();
321
322        // Replace fetch by id with fetch by hostname
323        addWidget(hostnameInput, getFetchByHostnameControlsElementId());
324        addWidget(hostnameFetchButton, getFetchByHostnameControlsElementId());
325
326        hostnameInput.addKeyPressHandler(new KeyPressHandler() {
327            public void onKeyPress (KeyPressEvent event) {
328                if (event.getCharCode() == (char) KeyCodes.KEY_ENTER)
329                    fetchByHostname(hostnameInput.getText());
330            }
331        });
332        hostnameFetchButton.addClickHandler(new ClickHandler() {
333            public void onClick(ClickEvent event) {
334                fetchByHostname(hostnameInput.getText());
335            }
336        });
337
338        jobsTable.setRowsPerPage(JOBS_PER_PAGE);
339        jobsTable.setClickable(true);
340        jobsTable.addListener(new DynamicTableListener() {
341            public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) {
342                if (isJobRow(row)) {
343                    JSONObject job = row.get("job").isObject();
344                    int jobId = (int) job.get("id").isNumber().doubleValue();
345                    hostDetailListener.onJobSelected(jobId);
346                } else {
347                    String resultsPath = Utils.jsonToString(row.get("execution_path"));
348                    Utils.openUrlInNewWindow(Utils.getRetrieveLogsUrl(resultsPath));
349                }
350            }
351
352            public void onTableRefreshed() {}
353        });
354        tableDecorator.addPaginators();
355        selectionManager = tableDecorator.addSelectionManager(false);
356        jobsTable.setWidgetFactory(selectionManager);
357        tableDecorator.addTableActionsPanel(this, true);
358        tableDecorator.addControl("Show verifies, repairs, cleanups and resets",
359                                  showSpecialTasks);
360        tableDecorator.addFilter("Filter by time started:",
361                                 startedTimeFilter);
362        addWidget(tableDecorator, "view_host_jobs_table");
363
364        showSpecialTasks.addClickHandler(new ClickHandler() {
365            public void onClick(ClickEvent event) {
366                selectionManager.deselectAll();
367                jobsTable.setSpecialTasksEnabled(showSpecialTasks.getValue());
368                jobsTable.refresh();
369            }
370        });
371
372        startedTimeFilter.addValueChangeHandler(
373            new ValueChangeHandler() {
374                public void onValueChange(ValueChangeEvent event) {
375                    String value = (String) event.getValue();
376                    jobsTable.setStartTime(value);
377                    if (value == "")
378                        startedTimeFilter.setStartTimeToPlaceHolderValue();
379                    jobsTable.refresh();
380                }
381            },
382            new ValueChangeHandler() {
383                public void onValueChange(ValueChangeEvent event) {
384                    String value = (String) event.getValue();
385                    jobsTable.setEndTime(value);
386                    if (value == "")
387                        startedTimeFilter.setEndTimeToPlaceHolderValue();
388                    jobsTable.refresh();
389                }
390            }
391        );
392
393        addWidget(lockButton, "view_host_lock_button");
394        addWidget(lockReasonInput, "view_host_lock_reason_input");
395
396        lockButton.addClickHandler(new ClickHandler() {
397            public void onClick(ClickEvent event) {
398               boolean locked = currentHostObject.get("locked").isBoolean().booleanValue();
399               changeLock(!locked);
400            }
401        });
402        lockReasonInput.addKeyPressHandler(new KeyPressHandler() {
403            public void onKeyPress (KeyPressEvent event) {
404                if (event.getCharCode() == (char) KeyCodes.KEY_ENTER) {
405                    boolean locked = currentHostObject.get("locked").isBoolean().booleanValue();
406                    changeLock(!locked);
407                }
408            }
409        });
410
411        reverifyButton.addClickHandler(new ClickHandler() {
412            public void onClick(ClickEvent event) {
413                JSONObject params = new JSONObject();
414
415                params.put("id", currentHostObject.get("id"));
416                AfeUtils.callReverify(params, new SimpleCallback() {
417                    public void doCallback(Object source) {
418                       refresh();
419                    }
420                }, "Host " + hostname);
421            }
422        });
423        addWidget(reverifyButton, "view_host_reverify_button");
424
425        repairButton.addClickHandler(new ClickHandler() {
426            public void onClick(ClickEvent event) {
427                JSONObject params = new JSONObject();
428
429                params.put("id", currentHostObject.get("id"));
430                AfeUtils.callRepair(params, new SimpleCallback() {
431                    public void doCallback(Object source) {
432                       refresh();
433                    }
434                }, "Host " + hostname);
435            }
436        });
437        addWidget(repairButton, "view_host_repair_button");
438    }
439
440    public void onError(JSONObject errorObject) {
441        // RPC handler will display error
442    }
443
444    public ContextMenu getActionMenu() {
445        ContextMenu menu = new ContextMenu();
446        menu.addItem("Abort", new Command() {
447            public void execute() {
448                abortSelectedQueueEntriesAndSpecialTasks();
449            }
450        });
451        if (selectionManager.isEmpty())
452            menu.setEnabled(false);
453        return menu;
454    }
455
456    private void abortSelectedQueueEntriesAndSpecialTasks() {
457        Set<JSONObject> selectedEntries = selectionManager.getSelectedObjects();
458        Set<JSONObject> selectedQueueEntries = new JSONObjectSet<JSONObject>();
459        JSONArray selectedSpecialTaskIds = new JSONArray();
460        for (JSONObject entry : selectedEntries) {
461            if (isJobRow(entry))
462                selectedQueueEntries.add(entry);
463            else
464                selectedSpecialTaskIds.set(selectedSpecialTaskIds.size(),
465                                           entry.get("oid"));
466        }
467        if (!selectedQueueEntries.isEmpty()) {
468            AfeUtils.abortHostQueueEntries(selectedQueueEntries, new SimpleCallback() {
469                public void doCallback(Object source) {
470                    refresh();
471                }
472            });
473        }
474        if (selectedSpecialTaskIds.size() > 0) {
475            AfeUtils.abortSpecialTasks(selectedSpecialTaskIds, new SimpleCallback() {
476                public void doCallback(Object source) {
477                    refresh();
478                }
479            });
480        }
481    }
482
483    private void updateLockButton() {
484        boolean locked = currentHostObject.get("locked").isBoolean().booleanValue();
485        if (locked) {
486            lockButton.setText("Unlock");
487        } else {
488            lockButton.setText("Lock");
489        }
490    }
491
492    private void updateLockReasonInput() {
493        boolean locked = currentHostObject.get("locked").isBoolean().booleanValue();
494        if (locked) {
495            lockReasonInput.setText("");
496            lockReasonInput.setEnabled(false);
497        } else {
498            lockReasonInput.setEnabled(true);
499        }
500    }
501
502    private void changeLock(final boolean lock) {
503        JSONArray hostIds = new JSONArray();
504        hostIds.set(0, currentHostObject.get("id"));
505
506        AfeUtils.changeHostLocks(hostIds, lock, lockReasonInput.getText(),
507                                 "Host " + hostname, new SimpleCallback() {
508            public void doCallback(Object source) {
509                refresh();
510            }
511        });
512    }
513
514    private boolean isJobRow(JSONObject row) {
515        String type = Utils.jsonToString(row.get("type"));
516        return type.equals("Job");
517    }
518
519    public boolean isRowSelectable(JSONObject row) {
520        return isJobRow(row);
521    }
522
523    @Override
524    public void handleHistoryArguments(Map<String, String> arguments) {
525        String hostname = arguments.get("hostname");
526        String objectId = arguments.get("object_id");
527
528        if (objectId != null) {
529            try {
530                updateObjectId(objectId);
531            }
532            catch (IllegalArgumentException exc) {
533                return;
534            }
535        } else if (hostname != null) {
536            fetchDataByHostname(hostname);
537        } else {
538            resetPage();
539        }
540    }
541}
542