openSUSE:WebYaST ExamplePlugin
Developing an example plugin
This tutorial will show you how to create a module for WebYast. The module will a simple text editor which reads and writes the file located in /var/log/YaST2/example_file. Please consider that only the user root has access rights to /var/log/YaST2. So this example modules also shows how other users have access rights to this file too.
Example plugin for WebYaST >= 0.3
Setup the Environment
Currently there are different possibilities getting a running development:
- Install WebYaST with RPM packages on your locale machine.
- Install WebYaST from GIT on your locale machine.
The shorter way is the first one. So we are taking this one.
The short way...
... would be to install the example plugins via RPM and play with them. The needed package can be found here:
Please download and install the RPM which fits to your SuSE version and restart WebYaST with:
rcwebyast restart
The example plugin is ready for use now.
The long way...
...would be to create all plugin files manually, which are included in the RPM packages described above. This way takes more effort but shows more intense how WebYaST works.
Defining PolkitPolicies
Every Yast Module provides it's own policy file, which defines the actions a user can be granted. They are stored in /usr/share/polkit-1/actions/ and they usually have the form org.opensuse.yast.system.module_name.policy. For the example module we will need something like file write and read access, so create the org.opensuse.yast.system.example.policy file and add these lines to it:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> <policyconfig> <vendor>YaST Webservice Project</vendor> <vendor_url>http://en.opensuse.org/YAST</vendor_url> <action id="org.opensuse.yast.system.example.read"> <description>Description of action</description> <message>Authentication message which appear on desktop (not affect webyast)</message> <defaults> <allow_inactive>no</allow_inactive> <allow_active>no</allow_active> </defaults> </action> <action id="org.opensuse.yast.system.example.write"> <description>Description of action</description> <message>Authentication message which appear on desktop (not affect webyast)</message> <defaults> <allow_inactive>no</allow_inactive> <allow_active>no</allow_active> </defaults> </action> </policyconfig>
Granting PolkitPolicies
If you want to watch your module during development, you need to give some permissions to a user who is logged in into WebYaST, eg. root.
/usr/sbin/grantwebyastrights --user root --action grant --policy org.opensuse.yast.system.example.read /usr/sbin/grantwebyastrights --user root --action grant --policy org.opensuse.yast.system.example.write
As the WebYaST HTTP server will also access the file we will have to grant these permissions to the HTTP server user ( The WebYaST server is running under the user webyast).
/usr/sbin/grantwebyastrights --user webyast --action grant --policy org.opensuse.yast.system.example.read /usr/sbin/grantwebyastrights --user webyast --action grant --policy org.opensuse.yast.system.example.write
D-Bus Service
The D-Bus Service is running under root. This service handles the "real" file acess to /var/log/YaST2/example_file.
There are two ways of creating a D-Bus service. You can use existing YaST module and only create a D-Bus interface or you can write a standalone service.
As there is no YaST module for reading/writing files, a standalone, YaST-independent D-Bus service is used here.
D-Bus service: example.service
D-Bus interface: example.service.Interface
D-Bus object path: /org/example/service/Interface
Create the D-Bus service
The D-Bus service can be written in various languages. As WebYaST itself is written in Ruby, the example below is also in Ruby.
Put the following skeleton into your binary path of your choice, in order to match examples below, into /usr/local/sbin/exampleService.rb
The first part connects to the system bus and registers the service.
#!/usr/bin/env ruby require 'rubygems' require 'dbus' # Choose the bus (could also be DBus::session_bus, which is not suitable for a system service) bus = DBus::system_bus # Define the service name service = bus.request_service("example.service")
Create the object class and its interface. In the interface, create all methods.
In the example below, the interface provides two methods:
- read, which reads specified file and returns it as a string (note that enclose in array is needed as DBus has multiple return values so it returns array of return values)
- write, which writes specified string to the file.
class ExampleService < DBus::Object FILENAME = "/var/log/YaST2/example_file" # Create an interface. dbus_interface "example.service.Interface" do # Define D-Bus methods of the service # This method reads whole file which name it gets as a parameter and returns its contents dbus_method :read, "out contents:s" do out = "" begin File.open(FILENAME, "r") {|f| out = f.read } rescue out = "<empty>" end [out] #return value must be array, as DBus allow multiple return value, so it expect array of return values end # This method dumps a string into a file dbus_method :write, "in contents:s" do |contents| File.open(FILENAME, 'w') {|f| f.write(contents) } [] end end end
Create the object and export it through its path.
# Set the object path obj = ExampleService.new("/org/example/service/Interface") # Export it! service.export(obj)
Run the event loop (in this example, the event loop never finishes).
# Now listen to incoming requests main = DBus::Main.new main << bus main.run
Make this file executeable:
chmod 755 /usr/local/sbin/exampleService.rb
Create needed permissions
Edit /etc/dbus-1/system.d/example.service.conf and add following contents in order to grant permission to access and register the service:
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> <busconfig> <policy user="root"> <allow own="example.service" /> <allow send_destination="example.service" /> </policy> <!-- yastws user is one which runs yast webservice and he sends messages to service --> <policy user="webyast"> <allow send_destination="example.service" /> <!-- introspection is allowed --> <allow send_destination="example.service" send_interface="org.freedesktop.DBus.Introspectable" /> </policy> <policy context="default"> <deny send_destination="example.service"/> <deny send_destination="example.service" send_interface="org.freedesktop.DBus.Introspectable" /> </policy> </busconfig>
Without this file, you cannot even register the service in order to test it.
The configuration above allows root to register the service and the user webyast to use it. Therefore, the service cannot be used by any other users except webyast, whose permissions WebYaST uses.
NOTE: Please adapt this file if you want to run the WebYaST server under another user.
Starting the Service
To start the service automatically, create the service activation config file /usr/share/dbus-1/system-services/example.service.service with following contents:
# DBus service activation config [D-BUS Service] Name=example.service Exec=/usr/local/sbin/exampleService.rb User=root
Keep the [D-Bus Service] section name, only update the name of the service and the binary to execute.
Testing the service
To test the service, just call its methods. Following example shows how to call a D-Bus service. (Run this under user root)
#!/usr/bin/ruby require 'rubygems' require 'dbus' bus = DBus.system_bus ruby_service = bus.service("example.service") obj = ruby_service.object("/org/example/service/Interface") obj.introspect obj.default_iface = "example.service.Interface" puts obj.read
The service must be started - unless it is done automatically (see below), just run /usr/local/sbin/exampleService.rb as user root.
WebYaST rails plugin
Preparing
To start developing normally you will need the sources by check out the git repos and install the required packages. For more information have a look to the installation from source. In our example we are using the real WebYaST service which has been installed in /srv/www directory due the package installation.
All modules of WebYast are stored in the plug-ins directory, so open a terminal and create a new directory for your modul
cd /srv/www/webyast/vendor/plugins mkdir example/ cd example/
At least your new module should have the directories app config lib package tasks and test. Go on and create them.
mkdir app config lib package tasks test
Now change directory to app/ and create directories for the model, view and controller.
cd app mkdir models views controllers
During development it can be helpful to open the production log in a second terminal.
tail -fn 100 /srv/www/webyast/log/passenger.log
The model
In the model you will define the example class with all it's attributes and methods. We will need only one variable for the content of the file.
Change directory to models/ and create the file example.rb.
The access to the variable can be defined with Ruby's attr_accessor. We will need read AND write access here.
class Example < BaseModel::Base attr_accessor :content
We don't inherit Example from ActiveRecord::Base, as we don't need a database. But in many cases with complex datastructure we need to define our own to_xml and to_json methods:
def to_xml(options = {}) xml = options[:builder] ||= Builder::XmlMarkup.new(options) xml.instruct! unless options[:skip_instruct] .. .. end def to_json(options = {}) hash = Hash.from_xml(to_xml()) return hash.to_json end
These methods convert the attributes of the given class into xml or json format and provide them as return value. Have a look to other plugins for more information.
In our example these methods are not needed cause the data structure is too simple. So we can use the standard to_xml and to_json methods of the BaseModel::Base class Now let's provide some functionality:
def initialize load_content end def load_content @content = dbus_obj.read end
With Example.new() one can get an instance of Example class. At that point the content of the file has been already read and is available.
def self.find(what=:one,options={}) ret = Example.new ret.load_content ret end
Every model class should have a find methods in order to get the data. In our case it is the same like the initialize method but there are some other cases e.g. an user model where the find method is very useful:
- User.find(:all) returns a array with all users
- User.find('tux') returns an object of the user 'tux'.
def update dbus_obj.write @content end
This method is used to write the content to the file. As you see we are using the DBUS-service for writing the file. Don't use IO methods at this point. It will bring you into trouble. As WebYast will run as a service it will have it's own user webyast. This user won't have root permissions, but get access to the system via the DBUS-service and the PolicyKit permissions we have defined before.
And finally, the dbus_obj helper, which reads and writes via the D-Bus service defined above:
def dbus_obj bus = DBus.system_bus ruby_service = bus.service("example.service") obj = ruby_service.object("/org/example/service/Interface") obj.introspect obj.default_iface = "example.service.Interface" obj end end # the end of the model class
Now, as we can read/write the file /var/log/YaST2/example_file we can go on with the controller.
The controller
The controller connects the model (described above) with the representation view (next chapter). The web-browser sends a GET-Request to WebYaST and expects the file content as response. This can be either in html,xml or json format like we defined in our model before. On the other hand the web-browser can send a POST-Request to WebYaST in order to change the file.
Change directory to the controllers and create the file example_controller.rb
cd ../controllers vi example_controller.rb
class ExampleController < ApplicationController #require login before do any action before_filter :login_required
ApplicationController is part of WebYaST and provides some methods which we will need. So define the class ExampleController and derivate it from the ApplicationController. The before_filter will execute login_required when any action has been called. It checks if the user is logged in and redirects to the login screen if not.
# Initialize GetText and Content-Type. init_gettext "webyast-example"
This part is only needed if you want to provide other languages/translations for your UI. For more information have a look to the Internationalization section.
Now let's define the show/index method which has to respond to GET requests and collects the data.
def index permission_check "org.opensuse.yast.system.example.read" @example = Example.find respond_to do |format| format.xml { render :xml => @example.to_xml} format.json { render :json => @example.to_json } format.html { @write_permission = permission_granted? "org.opensuse.yast.system.example.write" render :index } end end def show index end
First permission_check checks the user for his rights. If he isn't allowed to read security informations, the show/index action rise an error 403 and stops. Else it creates a new Example object by calling its find method.
The show/index action responds the Example object by calling the view (described in the next chapter) if the request has the html format or returns the Example object in xml or json format.
And the second part would be to write values into the file via a POST request.
def update permission_check "org.opensuse.yast.system.example.write" value = params["example"] if value.empty? raise InvalidParameters.new :example => "Missing" end @example = Example.find @example.content = value["content"] || "" error = nil begin @example.save rescue Exception => error Rails.logger.error "Error while saving file: #{error.inspect}" end respond_to do |format| format.xml { render :xml => @example.to_xml} format.json { render :json => @example.to_json } format.html { unless error flash[:notice] = _("File saved") else flash[:error] = _("Error while saving file: %s") % error.inspect end index } end end # See update def create update end end #close the ExampleController class
After the permission check an parameter error will be raised if no parameter has been defined. In the case of a HTML request the method index will be called after writing the values in order to show it again. If it has been a XML or JSON request the current content of the saved file will be returned.
The view
Create a new directory called example inside the view and write a view for the index action.
cd ../views mkdir example vi example/index.erb
The view contains an text area only, which is showing the current content of the file. This value can be changed and saved by the Save button.
<div class="plugin-content plugin-border"> <% form_tag '/example/update', :class => "webyast_form" do %> <div class="plugin-header"> <div class="left"> <label class="plugin-icon-container"><img class="plugin-icon" src="/icons/example_plugin.png"></label> <label class="plugin-name"><%=_("Example")%></label> </div> <div class="right" > <span id="questionMark" style="margin:2px 5px; float:none;">?</span> </div> <div class="clearfix"></div> </div> <div class="clearfix"> </div> <fieldset class="webyast_fieldset"> <div class="fieldset_header"> <span class="fieldset_header_title" > <%=_("Content of /var/log/YaST2/example_file")%> </span> </div> <div class="fieldset_body"> <div class="row"> <%= text_area(:example, :content, :cols => 125, :rows => 10) %> </div> <br> </div> </fieldset> <div class="button_container"> <div class="hr"> </div> <div class="nav-buttons"> <%= form_send_buttons :disabled => !@write_permission %> </div> </div> <% end %> </div>
The views/example/index.erb is nearly plain html. Only few variables for the text_area, translations and permissions are embedded within ruby-code. By calling permission_granted? in the controller, we have set the @write_permission variable which is either true or false and decides if the user is able to edit the text area or not.
Defining Routes
Normally routing will be handled in the central file config/routes.rb. As this file belongs to the WebYaST base package we do not want to edit manually. So each plugin has his own rooting description defined in config/resources/example.yml inside the plugin/example directory.
cd ../../config mkdir resources vi resources/example.yml
The content of this file is:
#interface can be anything interface: org.opensuse.yast.system.example #controller is path on which can be service located controller: example #singular set if it is singular REST service or not singular: true
Adding an startup icon for WebYaST
Go to plugins/example and create file called shortcuts.yml with following contents to add an icon for your module to the control center.
cd .. vi shortcuts.yml
The file content would be:
main: icon: '/icons/example_plugin.png' url: /example groups: [ System ] title: _("Example") description: _("This is an example plugin only") read_permissions: [ org.opensuse.yast.system.example.read ]
Copy your own Icon to /srv/www/webyast/vendor/plugins/example/public/icons/example_plugin.png
You can reuse some WebYaST icons stored in /srv/www/webyast/public/icons .
The "_(....)" around each text string
This mechanism is used for tagging strings which are shown in the UI and which have to be provided in multiple languages. For more information have a look to the Internationalization section.
Testing
Now you can restart the WebYaST server by:
rcwebyast restart
Open a browser and go to http://localhost:4984, login with user root and choose the example module:
Example plugin for WebYaST < 0.3
While the REST service takes care of the functionality, the web-client gives the user a fine GUI for the browser.
Setup the Environment
There are different possibilities getting a running development:
- Install WebYaST on your locale machine.
- Using WebYaST in an appliance.
We are taking the second one (via appliance) cause it does not disturb your local environment. So you have to create an appliance and boot with the prefer utility (VMware, VirtualBox,...).
This tutorial describes two ways in order to show how an example plugin works.
The short way...
... would be to install the example plugins via RPM and play with them. The two packages are:
- webyast-example-ws for the REST service
- webyast-example-ui for the UI
Please download and install the RPM which fits to your SuSE version and restart the services with:
rcyastws restart rcyastwc restart
The example plugin is ready for use now.
The long way...
...would be to create all plugin files manually, which are included in the RPM packages described above. This way takes more effort but shows more intense how WebYaST works.
PolkitPolicit
Before we start with the program, we will define the permissions for the new module.
Defining PolkitPolicies
Every Yast Module provides it's own policy file, which defines the actions a user can be granted. They are stored in /usr/share/PolicyKit/policy/ and they usually have the form org.opensuse.yast.system.module_name.policy. For the example module we will need something like file write and read access, so create the org.opensuse.yast.system.example.policy file and add these lines to it:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> <policyconfig> <vendor>YaST Webservice Project</vendor> <vendor_url>http://en.opensuse.org/YAST</vendor_url> <action id="org.opensuse.yast.system.example.read"> <description>Description of action</description> <message>Authentication message which appear on desktop (not affect webyast)</message> <defaults> <allow_inactive>no</allow_inactive> <allow_active>no</allow_active> </defaults> </action> <action id="org.opensuse.yast.system.example.write"> <description>Description of action</description> <message>Authentication message which appear on desktop (not affect webyast)</message> <defaults> <allow_inactive>no</allow_inactive> <allow_active>no</allow_active> </defaults> </action> </policyconfig>
Granting PolkitPolicies
If you want to watch your module during development, you need to give some permissions to a user who is logged in into WebYaST, eg. root.
polkit-auth --user root --grant org.opensuse.yast.system.example.write polkit-auth --user root --grant org.opensuse.yast.system.example.read
As the WebYaST REST service will also access the file we will have to grant these permissions to the REST service user ( The REST service is running under the user yastws).
polkit-auth --user yastws --grant org.opensuse.yast.system.example.write polkit-auth --user yastws --grant org.opensuse.yast.system.example.read
D-Bus Service
The D-Bus Service is running under root. This service handles the "real" file acess to /var/log/YaST2/example_file.
There are two ways of creating a D-Bus service. You can use existing YaST module and only create a D-Bus interface or you can write a standalone service.
As there is no YaST module for reading/writing files, a standalone, YaST-independent D-Bus service is used here.
D-Bus service: example.service
D-Bus interface: example.service.Interface
D-Bus object path: /org/example/service/Interface
Create the D-Bus service
The D-Bus service can be written in various languages. As WebYaST itself is written in Ruby, the example below is also in Ruby.
Put the following skeleton into your binary path of your choice, in order to match examples below, into /usr/local/sbin/exampleService.rb
The first part connects to the system bus and registers the service.
#!/usr/bin/env ruby require 'rubygems' require 'dbus' # Choose the bus (could also be DBus::session_bus, which is not suitable for a system service) bus = DBus::system_bus # Define the service name service = bus.request_service("example.service")
Create the object class and its interface. In the interface, create all methods.
In the example below, the interface provides two methods:
- read, which reads specified file and returns it as a string (note that enclose in array is needed as DBus has multiple return values so it returns array of return values)
- write, which writes specified string to the file
class ExampleService < DBus::Object FILENAME = "/var/log/YaST2/example_file" # Create an interface. dbus_interface "example.service.Interface" do # Define D-Bus methods of the service # This method reads whole file which name it gets as a parameter and returns its contents dbus_method :read, "out contents:s" do out = "" begin File.open(FILENAME, "r") {|f| out = f.read } rescue out = "<empty>" end [out] #return value must be array, as DBus allow multiple return value, so it expect array of return values end # This method dumps a string into a file dbus_method :write, "in contents:s" do |contents| File.open(FILENAME, 'w') {|f| f.write(contents) } [] end end end
Create the object and export it through its path
# Set the object path obj = ExampleService.new("/org/example/service/Interface") # Export it! service.export(obj)
Run the even loop (in this example, the event loop never finishes.
# Now listen to incoming requests main = DBus::Main.new main << bus main.run
Make this file executeable:
chmod 755 /usr/local/sbin/exampleService.rb
Create needed permissions
Edit /etc/dbus-1/system.d/example.service.conf and add following contents in order to grant permission to access and register the service:
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> <busconfig> <policy user="root"> <allow own="example.service" /> <allow send_destination="example.service" /> </policy> <!-- yastws user is one which runs yast webservice and he sends messages to service --> <policy user="yastws"> <allow send_destination="example.service" /> <!-- introspection is allowed --> <allow send_destination="example.service" send_interface="org.freedesktop.DBus.Introspectable" /> </policy> <policy context="default"> <deny send_destination="example.service"/> <deny send_destination="example.service" send_interface="org.freedesktop.DBus.Introspectable" /> </policy> </busconfig>
Without this file, you cannot even register the service in order to test it.
The configuration above allows root to register the service and yastws to use it. Therefore, the service cannot be used by any other users except yastws, whose permissions the YaST web service uses.
Starting the Service
To start the service automatically, create the service activation config file /usr/share/dbus-1/system-services/example.service.service with following contents:
# DBus service activation config [D-BUS Service] Name=example.service Exec=/usr/local/sbin/exampleService.rb User=root
Keep the [D-Bus Service] section name, only update the name of the service and the binary to execute.
Testing the service
To test the service, just call its methods. Following example shows how to call a D-Bus service.
#!/usr/bin/ruby require 'rubygems' require 'dbus' bus = DBus.system_bus ruby_service = bus.service("example.service") obj = ruby_service.object("/org/example/service/Interface") obj.introspect obj.default_iface = "example.service.Interface" puts obj.read
The service must be started - unless it is done automatically (see below), just run /usr/local/sbin/exampleService.rb as root.
Rest-Service:
Preparing
To start developing normally you will need the sources by check out the git repos and install the required packages. For more information have a look to the installation from source. In our example we are using the real WebYaST services which have been installed in /srv/www directory due the package installation.
All modules of WebYast are stored in the plug-ins directory, so open a terminal and create a new directory for your modul
cd /srv/www/yastws/vendor/plugins mkdir example/ cd example/
At least your new module should have the directories app config lib package tasks and test. Go on and create them.
mkdir app config lib package tasks test
Now change directory to app/ and create directories for the model, view and controller.
cd app mkdir models views controllers
During development it can be helpful to open the production log in a second terminal.
tail -fn 100 /srv/www/yastws/log/passenger.log
The model
In the model you will define the example class with all it's attributes and methods. We will need only one variable for the content of the file.
Change directory to models/ and create the file example.rb.
The access to the variable can be defined with Ruby's attr_accessor. We will need read AND write access here.
class Example < BaseModel::Base attr_accessor :content
We don't inherit Example from ActiveRecord::Base, as we don't need a database. But in many cases with complex datastructure we need to define our own to_xml and to_json methods:
def to_xml(options = {}) xml = options[:builder] ||= Builder::XmlMarkup.new(options) xml.instruct! unless options[:skip_instruct] .. .. .. end def to_json(options = {}) hash = Hash.from_xml(to_xml()) return hash.to_json end
These methods convert the attributes of the given class into xml or json format and provide them as return value. Have a look to other plugins for more information.
In our example these methods are not needed cause the data structure is too simple. So we can use the standard to_xml and to_json methods of the BaseModel::Base class
Now let's provide some functionality:
def initialize load_content end def load_content @content = dbus_obj.read end
With Example.new() one can get an instance of Example class. At that point the content of the file has been already read and is available.
def self.find(what=:one,options={}) ret = Example.new ret.load_content ret end
Every model class should have a find methods in order to get the data. In our case it is the same like the initialize method but there are some other cases e.g. an user model where the find method is very useful:
- User.find(:all) returns a array with all users
- User.find('tux') returns an object of the user 'tux'.
def update dbus_obj.write @content end
This method is used to write the content to the file. As you see we are using the DBUS-service for writing the file. Don't use IO methods at this point. It will bring you into trouble. As WebYast will run as a service it will have it's own user yastws. This user won't have root permissions, but get access to the system via the DBUS-service and the PolicyKit permissions we have defined before.
And finally, the dbus_obj helper, which reads and writes via the D-Bus service defined above:
def dbus_obj bus = DBus.system_bus ruby_service = bus.service("example.service") obj = ruby_service.object("/org/example/service/Interface") obj.introspect obj.default_iface = "example.service.Interface" obj end end # the end of the model class
Now, as we can read/write the file /var/log/YaST2/example_file we can go on with the controller.
The controller
The controller connects the model on the service-side with the representation on the web-client side. The web-client sends a GET-Request to the rest-service and expects the file content as response. This can be either in xml or in json format like we defined in our model before. On the other hand the web-client can send a POST-Request to the rest-service in order to change the file.
Change directory to the controllers and create the file example_controller.rb
cd ../controllers vi example_controller.rb
class ExampleController < ApplicationController #require login before do any action before_filter :login_required
ApplicationController is part of the rest-service and provides some methods which we will need. So define the class ExampleController and derivate it from the ApplicationController. The before_filter will execute login_required when any action where called. It checks if the user is logged in and redirects to the login screen if not.
Now let's define the show method which has to respond to GET requests and collects the data.
def show permission_check "org.opensuse.yast.system.example.read" example = Example.find respond_to do |format| format.xml { render :xml => example.to_xml} format.json { render :json => example.to_json } end end
First permission_check checks the user for his rights. If he isn't allowed to read security informations, the show action rise an error 403 and stops. Else it creates a new Example object by calling its find method.
The show action responds the Example object to the web-client, which has to handle the display inside the browser. This will happen here. But first you have to define routes for your new module.
And the second part would be to write values into the file via a POST request.
def update permission_check "org.opensuse.yast.system.example.write" value = params["example"] if value.empty? raise InvalidParameters.new :example => "Missing" end example = Example.find example.content = value["content"] || "" example.save show end end #close the ExampleController class
After the permission check an parameter error will be raised if no parameter has been defined. After writing the value the method show will be called in order to return the current content of the saved file.
Defining Routes
The routes for every module is defined in config/resources/example.yml inside the plugin/example directory.
#interface can be anything interface: org.opensuse.yast.system.example #controller is path on which can be service located controller: example #singular set if it is singular REST service or not singular: true
Testing
You can restart the REST service via calling rcyastws restart with root permissions.
Now you are able to watch your new module at http://localhost:4984 with your browser. The link name is the value of interface. You can also go directly to http://localhost:4984/example or type http://localhost:4984/example.xml to get a xml tree.
Another possibility would be to use curl from the command line:
curl -u root http://localhost:4984/example.xml
to get the values.
Web-Client
Like on the service all modules are stored in the plugins directory. Create a example directory in the plugins directory and at least the subdirectories view,controller and models in the app directory.
cd /srv/www/yast/vendor/plugins mkdir example cd example mkdir app package tasks test config cd app mkdir models views controllers
The model
Create a simple model which only maps to the REST service:
cd models vi example.rb
and add following contents
class Example < ActiveResource::Base extend YastModel::Base model_interface :"org.opensuse.yast.system.example" end
The controller
Change to the controller directory and create the example_controller.rb file.
cd ../controllers/ vi example_controller.rb
Now define the ExampleController class and derivate it from ApplicationController and include yast/service_resource. Create a before_filter as well.
require 'yast/service_resource' require 'example' class ExampleController < ApplicationController before_filter :login_required, :read_permissions
This will start the login_required action like on the controller of the rest-service, but also starts the read_permissions action before executing any other action inside the security controller.
private def read_permissions @permissions = Example.permissions end
This method reads the permissions and stores them in an instance variable so that they can be accessed by the other functions or by the view.
public # Initialize GetText and Content-Type. init_gettext "webyast-example-ui"
This part is only needed if you want to provide other languages/translations for your UI. For more information have a look to the Internationalization section.
# GET /example def index @example = Example.find(:one) end
This defines the 'index' function which reads the file content via the model and the REST service and sets internal variables accordingly.
# PUT /example def update @example = Example.find(:one) @example.content = params["example"]["content"] begin @example.save flash[:notice] = _("File saved") rescue Exception => error flash[:error] = _("Error while saving file: %s") % error.inspect end render :index end end
By calling Example.find we request a new Example instance from the rest-service. The 'params' hash contains the POST parameter of the view. Then the update action (in the web-client controller, example-controller.rb) stores the 'params' which contains the new file content into the Example instance. The save method then sends a POST with the Example instance to the example module of the rest-service. If it responds with an error, the web-client flashes an error message.
At the end user got redirected to index page of the example plugin again.
The view
Create a new directory called security inside the view and write a view for the index action.
cd ../views mkdir example vi example/index.erb
On the web-client side the view contains an text area only, which is showing the current content of the file. This value can be changed and saved by the Save button.
<div class="plugin-content plugin-border"> <% form_tag '/example/update', :class => "webyast_form" do %> <div class="plugin-header"> <div class="left"> <label class="plugin-icon-container"><img class="plugin-icon" src="/icons/example_plugin.png"></label> <label class="plugin-name"><%=_("Example")%></label> </div> <div class="right" > <span id="questionMark" style="margin:2px 5px; float:none;">?</span> </div> <div class="clearfix"></div> </div> <div class="clearfix"> </div> <fieldset class="webyast_fieldset"> <div class="fieldset_header"> <span class="fieldset_header_title" > <%=_("Content of /var/log/YaST2/example_file")%> </span> </div> <div class="fieldset_body"> <div class="row"> <%= text_area(:example, :content, :cols => 125, :rows => 10) %> </div> <br> </div> </fieldset> <div class="button_container"> <div class="hr"> </div> <div class="nav-buttons"> <%= form_send_buttons :disabled => !@permissions[:write] %> </div> </div> <% end %> </div>
The views/example/index.erb is nearly plain html. Only few variables for the text_area, translations and permissions are embedded within ruby-code. By calling Example.permissions in the controller, we have set @permissions variable. The @permissions[:write] contains either true or false and decides if the user is able to edit the text area or not.
Adding an startup icon for WebYaST
Go to plugins/example and create file called shortcuts.yml with following contents to add an icon for your module to the control center.
main: icon: '/icons/example_plugin.png' url: /example groups: [ System ] title: _("Example") description: _("This is an example plugin only") read_permissions: [ org.opensuse.yast.system.example.read ]
Copy your own Icon to /srv/www/yast/vendor/plugins/example/public/icons/example_plugin.png
The "_(....)" around each text string
This mechanism is used for tagging strings which are shown in the UI and which has to be provided in multiple languages. For more information have a look to the Internationalization section.
Testing
Now you can restart the web-client.
rcyastwc restart
Make sure that the rest-service is still running.
rcyastws status
Open a browser and go to http://localhost:54984, login with user root and choose the example module: