library/log/src/modules/LogView.rb
# ***************************************************************************
#
# Copyright (c) 2002 - 2012 Novell, Inc.
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, contact Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail,
# you may find current contact information at www.novell.com
#
# ***************************************************************************
# File: modules/LogView.ycp
# Package: YaST2
# Summary: Displaying a log with additional functionality
# Authors: Jiri Srain <jsrain@suse.cz>
#
# $Id$
#
# All of these functions watch the log file and display
# added lines as the log grows.
# <pre>
# LogView::DisplaySimple ("/var/log/messages");
# LogView::DisplayFiltered ("/var/log/messages", "\\(tftp\\|TFTP\\)");
# LogView::Display ($[
# "file" : "/var/log/messages",
# "grep" : "dhcpd",
# "save" : true,
# "actions" : [ // menu buttons
# [ _("Restart DHCP Server"),
# RestartDhcpDaemon ],
# [ _("Save Settings and Restart DHCP Server"),
# DhcpServer::Write ],
# ],
# ]);
# </pre>
require "yast"
module Yast
class LogViewClass < Module
def main
Yast.import "UI"
textdomain "base"
Yast.import "CWM"
Yast.import "Popup"
Yast.import "Label"
Yast.import "Report"
Yast.import "LogViewCore"
# fallback settings variables
# configuration variables
# global parameters for the log displaying widget
@param = {}
# list of all the logs that can be displayed
@logs = []
# index of currently selected log
@current_index = 0
# list of actions that can be processed on the logs
@mb_actions = []
end
# local functions
# Get the map describing the particular log file from its index
# @param [Fixnum] index integer index of the log file
# @return a map describing the log file
def Index2Descr(index)
Ops.get(@logs, index, {})
end
# Starts the log reading command via process agent
# @param [Fixnum] index integer the index of the log file
def InitLogReading(index)
log_descr = Index2Descr(index)
LogViewCore.Start(Id(:_cwm_log), log_descr)
nil
end
# Kill processes running on the backgrouns
# @param [String] key log widget key
def KillBackgroundProcess(_key)
LogViewCore.Stop
nil
end
# Get the help for the log in case of multiple logs
# @return [String] part of the log
def LogSelectionHelp
# help for the log widget, part 1, alt. 1
_(
"<p><b><big>Displayed Log</big></b><br>\n" \
"Use <b>Log</b> to select the log to display. It will be displayed in\n" \
"the field below.</p>\n"
)
end
# Get the help for the log in case of a single log
# @return [String] part of the log
def SingleLogHelp
# help for the log widget, part 1, alt. 2
_("<p><b><big>The Log</big></b><br>\nThis screen displays the log.</p>")
end
# Get the second part of the help for the log in case of advanced functions
# and save support
# @param [String] label tge label of the menu button
# @return [String] part of the log
def AdvancedSaveHelp(label)
# help for the log widget, part 2, alt. 1, %1 is a menu button label
Builtins.sformat(
_(
"<p>\n" \
"To process advanced actions or save the log into a file, click <b>%1</b>\n" \
"and select the action to process.</p>"
),
label
)
end
# Get the second part of the help for the log in case of advanced functions
# @param [String] label tge label of the menu button
# @return [String] part of the log
def AdvancedHelp(label)
# help for the log widget, part 2, alt. 2, %1 is a menu button label
Builtins.sformat(
_(
"<p>\n" \
"To process advanced actions, click <b>%1</b>\n" \
"and select the action to process.</p>"
),
label
)
end
# Get the second part of the help for the log in case of save support
# @return [String] part of the log
def SaveHelp
# help for the log widget, part 2, alt. 3
_(
"<p>\n" \
"To save the log into a file, click <b>Save Log</b> and select the file\n" \
"to which to save the log.</p>\n"
)
end
# Get the help of the widget
# @param [Fixnum] logs integer count of displayed logs
# @param [Hash] parameters map parameters of the log to display
# @return [String] help to the widget
def CreateHelp(logs, parameters)
parameters = deep_copy(parameters)
help = Ops.get_string(parameters, "help", "")
return help if help != "" && !help.nil?
adv_button = Ops.get_string(parameters, "mb_label", "")
if adv_button == "" || adv_button.nil?
# menu button
adv_button = _("Ad&vanced")
end
adv_button = Builtins.regexpsub(adv_button, "^(.*)&(.*)$", "\\1\\2") if Builtins.regexpmatch(adv_button, "^.*&.*$")
save = Ops.get_boolean(parameters, "save", false)
save = false if save.nil?
actions_lst = Ops.get_list(parameters, "actions", [])
actions_lst = [] if actions_lst.nil?
actions = Builtins.size(actions_lst)
actions = Ops.add(actions, 1) if save
if Ops.greater_than(logs, 1)
help = LogSelectionHelp()
elsif Ops.greater_or_equal(actions, 1) || save
help = SingleLogHelp()
else
return ""
end
if Ops.greater_or_equal(actions, 2)
help = if save
Ops.add(help, AdvancedSaveHelp(adv_button))
else
Ops.add(help, AdvancedHelp(adv_button))
end
elsif save
help = Ops.add(help, SaveHelp())
end
help
end
# Get the combo box of the available log files
# @param [Array<Hash{String => Object>}] log_maps a list of maps describing all the logs
# @return [Yast::Term] the combo box widget
def GetLogSelectionCombo(log_maps)
log_maps = deep_copy(log_maps)
selection_combo = Empty()
if Ops.greater_than(Builtins.size(log_maps), 0)
index = -1
items = Builtins.maplist(log_maps) do |m|
index = Ops.add(index, 1)
Item(
Id(index),
Ops.get_locale(
# combo box entry (only used as fallback in case
# of error in the YaST code)
m,
"log_label",
Ops.get_locale(m, "command", Ops.get_locale(m, "file", _("Log")))
)
)
end
selection_combo = ComboBox(
Id(:cwm_log_files),
Opt(:notify, :hstretch),
_("&Log"),
items
)
end
deep_copy(selection_combo)
end
# Get the widget with the menu button with actions to be processed on the log
# @param [Array<Array>] actions a list of all actions
# @param [Boolean] save boolean true if the log should be offered to be saved
# @param [String] mb_label label of the menu button, may be empty for default
# @return [Yast::Term] widget with the menu button
def GetMenuButtonWidget(actions, save, mb_label)
actions = deep_copy(actions)
menubutton = []
if save
# menubutton entry
menubutton = Builtins.add(menubutton, [:_cwm_log_save, _("&Save Log")])
end
if Ops.greater_than(Builtins.size(actions), 0)
index = 0
Builtins.foreach(actions) do |a|
menubutton = Builtins.add(
menubutton,
[index, Ops.get_string(a, 0, "")]
)
index = Ops.add(index, 1)
end
end
if Ops.greater_than(Builtins.size(menubutton), 1)
menubutton = Builtins.filter(menubutton) do |m|
m.is_a?(Array) && m.first
end
menubutton = Builtins.maplist(menubutton) do |m|
ml = Convert.to_list(m)
Item(Id(Ops.get(ml, 0)), Ops.get_string(ml, 1, ""))
end
mb_label = _("Ad&vanced") if mb_label == "" || mb_label.nil?
return MenuButton(Id(:_cwm_log_menu), mb_label, menubutton)
elsif Builtins.size(menubutton) == 1
return PushButton(
Id(Ops.get(menubutton, [0, 0], "")),
Ops.get_string(menubutton, [0, 1], "")
)
end
Empty()
end
# Get the buttons below the box with the log
# @param [Boolean] popup boolean true if running in popup (and Close is needed)
# @param [Hash{String => Object}] glob_param a map of global parameters of the log widget
# @param [Array<Hash{String => Object>}] log_maps a list of maps describing all the logs
# @return [Yast::Term] the widget with buttons
def GetButtonsBelowLog(popup, glob_param, _log_maps)
glob_param = deep_copy(glob_param)
left = Empty()
center = Empty()
if popup
center = PushButton(Id(:close), Opt(:key_F9), Label.CloseButton)
if Builtins.haskey(glob_param, "help") &&
Ops.get_string(glob_param, "help", "") != ""
left = PushButton(Id(:help), Label.HelpButton)
end
end
save = Ops.get_boolean(glob_param, "save", false)
mb_label = Ops.get_locale(glob_param, "mb_label", _("Ad&vanced"))
actions = Ops.get_list(glob_param, "actions", [])
right = GetMenuButtonWidget(actions, save, mb_label)
HBox(
HWeight(1, left),
HStretch(),
HWeight(1, center),
HStretch(),
HWeight(1, right)
)
end
# Get the default entry for the combo box with logs
# @param [Array<Hash{String => Object>}] log_maps a list of maps describing all the logs
# @return [Fixnum] the index of the default entry in the combo box
def GetDefaultItemForLogsCombo(log_maps)
log_maps = deep_copy(log_maps)
default_log = 0
if Ops.greater_than(Builtins.size(log_maps), 0)
index = -1
Builtins.foreach(log_maps) do |m|
index = Ops.add(index, 1)
default_log = index if Builtins.haskey(m, "default") && default_log == 0
end
end
default_log
end
# Switch the displayed log
# @param [Fixnum] index integer index of the log to display
def LogSwitch(index)
@current_index = index
log_descr = Index2Descr(index)
# logview caption
caption = Ops.get_locale(
log_descr,
"log_label",
Ops.get_locale(@param, "log_label", _("&Log"))
)
UI.ReplaceWidget(:_cwm_log_rp, LogView(Id(:_cwm_log), caption, 15, 0))
InitLogReading(index)
nil
end
# Initialize the displayed log
# @param [String] key log widget key
# @param [String] key table widget key
def LogInit(_key)
@param = CWM.GetProcessedWidget
@current_index = Ops.get_integer(@param, "_cwm_default_index", 0)
@mb_actions = Ops.get_list(@param, "_cwm_button_actions", [])
@logs = Ops.get_list(@param, "_cwm_log_files", [])
UI.ChangeWidget(Id(:cwm_log_files), :value, @current_index) if UI.WidgetExists(Id(:cwm_log_files))
LogSwitch(@current_index)
nil
end
# Handle the event on the log view widget
# @param [String] key log widget key
# @param [Hash] event map event to handle
# @return [Symbol] always nil
def LogHandle(_key, event)
event = deep_copy(event)
@param = CWM.GetProcessedWidget
LogViewCore.Update(Id(:_cwm_log))
ret = Ops.get(event, "ID")
# save the displayed log to file
if ret == :_cwm_log_save
filename = UI.AskForSaveFileName(
# popup caption, save into home directory by default (bnc#653601)
"~",
"*.log",
_("Save Log as...")
)
if !filename.nil?
SCR.Write(
path(".target.string"),
filename,
Ops.add(Builtins.mergestring(LogViewCore.GetLines, "\n"), "\n")
)
end
# other operation specified by user
elsif !ret.nil? && Ops.is_integer?(ret)
iret = Convert.to_integer(ret)
func = Convert.convert(
Ops.get(@mb_actions, [iret, 1]),
from: "any",
to: "void ()"
)
func&.call
if Ops.get(@mb_actions, [iret, 2]) == true
KillBackgroundProcess(nil)
UI.ChangeWidget(Id(:_cwm_log), :Value, "")
InitLogReading(@current_index)
end
# switch displayed log file
elsif ret == :cwm_log_files
index = Convert.to_integer(UI.QueryWidget(Id(:cwm_log_files), :Value))
LogSwitch(index)
end
nil
end
# Get the map with the log view widget
# @param [Hash{String => Object}] parameters map parameters of the widget to be created, will be
# unioned with the generated map
# <pre>
# - "save" -- boolean, if true, then log saving is possible
# - "actions" -- list, allows to specify additional actions.
# Each member is a 2- or 3-entry list, first entry is a
# label for the menubutton, the second one is a function
# that will be called when the entry is selected,
# the signature of the function must be void(),
# optional 3rd argument, if set to true, forces
# restarting of the log displaying command after the
# action is performed
# - "mb_label" -- string, label of the menubutton, if not specified,
# then "Advanced" is used
# - "max_lines" -- integer, maximum of lines to be displayed. If 0,
# then display whole file. Default is 100.
# - "help" -- string for a rich text, help to be offered via a popup
# when user clicks the "Help" button. If not present,
# default help is shown or Help button is hidden.
# - "widget_height" -- height of the LogView widget, to be adjusted
# so that the widget fits into the dialog well.
# Test it to find the best value, 15 seems to be
# good value (is default if not specified)
# </pre>
# @param [Array<Hash{String => Object>}] log_files a list of logs that will be displayed
# <pre>
# - "file" -- string, filename with the log
# - "grep" -- string, basic regular expression to be grepped
# in the log (for getting relevant parts of
# /var/log/messages. If empty or not present, whole file
# is used
# - "command" -- allows to specify comand to get the log for cases
# where grep isn't enough. If used, file and grep entries
# are ignored
# - "log_label" -- header of the LogView widget, if not set, then the file
# name or the command is used
# - "default" -- define and set to true to make this log be active after
# widget is displayed. If not defiend for any log, the
# first log is automatically default. If defined for multiple
# logs, the first one is active
# </pre>
# @return [Hash] the log widget
def CreateWidget(parameters, log_files)
parameters = deep_copy(parameters)
log_files = deep_copy(log_files)
# logview caption
caption = Ops.get_locale(@param, "log_label", _("&Log"))
height = Ops.get_integer(@param, "widget_height", 15)
default_index = GetDefaultItemForLogsCombo(log_files)
top_bar = GetLogSelectionCombo(log_files)
bottom_bar = GetButtonsBelowLog(false, parameters, log_files)
Builtins.union(
{
"widget" => :custom,
"custom_widget" => VBox(
top_bar,
ReplacePoint(
Id(:_cwm_log_rp),
LogView(Id(:_cwm_log), caption, height, 0)
),
bottom_bar
),
"init" => fun_ref(method(:LogInit), "void (string)"),
"handle" => fun_ref(
method(:LogHandle),
"symbol (string, map)"
),
"cleanup" => fun_ref(
method(:KillBackgroundProcess),
"void (string)"
),
"ui_timeout" => 1000,
"_cwm_default_index" => default_index,
"_cwm_log_files" => log_files,
"_cwm_button_actions" => [],
"help" => CreateHelp(
Builtins.size(log_files),
parameters
)
},
parameters
)
end
# old functions for displaying log as a popup
# Main function for displaying logs
# @param [Hash{String => Object}] parameters map description of parameters, with following keys
# <pre>
# - "file" -- string, filename with the log
# - "grep" -- string, basic regular expression to be grepped
# in the log (for getting relevant parts of
# /var/log/messages. If empty or not present, whole file
# is used
# - "command" -- allows to specify command to get the log for cases
# where grep isn't enough. If used, file and grep entries
# are ignored
# - "save" -- boolean, if true, then log saving is possible
# - "actions" -- list, allows to specify additional actions.
# Each member is a 2- or 3-entry list, first entry is a
# label for the menubutton, the second one is a function
# that will be called when the entry is selected,
# the signature of the function must be void(),
# optional 3rd argument, if set to true, forces
# restarting of the log displaying command after the
# action is performed
# - "help" -- string for a rich text, help to be offered via a popup
# when user clicks the "Help" button. If not present,
# Help button isn't shown
# - "mb_label" -- string, label of the menubutton, if not specified,
# then "Advanced" is used
# - "max_lines" -- integer, maximum of lines to be displayed. If 0,
# then display whole file. Default is 100.
# - "log_label" -- header of the LogView widget, if not set, then "Log"
# is used
# </pre>
def Display(parameters)
parameters = deep_copy(parameters)
@param = deep_copy(parameters)
# menubutton
log_label = Ops.get_locale(@param, "log_label", _("&Log"))
@logs = [@param]
button_line = GetButtonsBelowLog(true, @param, [@param])
UI.OpenDialog(
HBox(
HSpacing(1),
VBox(
VSpacing(1),
HSpacing(70),
# log view header
LogView(Id(:_cwm_log), log_label, 19, 0),
VSpacing(1),
button_line,
VSpacing(1)
),
HSpacing(1)
)
)
UI.ReplaceWidget(Id(:rep_left), PushButton(Id(:help), Label.HelpButton)) if Ops.get_string(@param, "help", "") != ""
@mb_actions = Ops.get_list(@param, "actions", [])
InitLogReading(0)
ret = nil
while ret != :close && ret != :cancel
event = UI.WaitForEvent(1000)
ret = Ops.get(event, "ID")
if ret == :help
UI.OpenDialog(
VBox(
RichText(Id(:help_text), Ops.get_string(@param, "help", "")),
HBox(
HStretch(),
PushButton(Id(:close), Label.CloseButton),
HStretch()
)
)
)
ret = UI.UserInput while ret != :close && ret != :cancel
ret = nil
UI.CloseDialog
else
LogHandle("", event)
end
end
LogViewCore.Stop
UI.CloseDialog
nil
end
# Display specified file, list 100 lines
# @param [String] file string filename of file with the log
def DisplaySimple(file)
Display("file" => file)
nil
end
# Display log with filtering with 100 lines
# @param [String] file string filename of file with the log
# @param [String] grep string basic regular expression to be grepped in file
def DisplayFiltered(file, grep)
Display("file" => file, "grep" => grep)
nil
end
publish function: :LogSelectionHelp, type: "string ()"
publish function: :SingleLogHelp, type: "string ()"
publish function: :AdvancedSaveHelp, type: "string (string)"
publish function: :AdvancedHelp, type: "string (string)"
publish function: :SaveHelp, type: "string ()"
publish function: :LogInit, type: "void (string)"
publish function: :LogHandle, type: "symbol (string, map)"
publish function: :CreateWidget, type: "map (map <string, any>, list <map <string, any>>)"
publish function: :Display, type: "void (map <string, any>)"
publish function: :DisplaySimple, type: "void (string)"
publish function: :DisplayFiltered, type: "void (string, string)"
end
LogView = LogViewClass.new
LogView.main
end