Welcome to docs.opsview.com

REST API: Config

This handles all the REST api endpoints regarding the configuration database for Opsview.

The endpoints are of the form: /rest/config/{objecttype}.

Request

The following verbs are allowed:

  • GET - retrieve either an object information or a list of objects
  • PUT - update. Will also create if the object does not currently exist. Will return the object after update
  • POST - create when POSTed to an objecttype URL, clone when POSTed to an object URL. However, creations will update if the object already exists. Will return the object after creation
  • DELETE - delete object. Will return a hash with response {“success”:1} (from Opsview 3.11 onwards)

The data passed to the API is in the form of key/value pairs. The key is a string, but the value can be a string, an array or an associative hash. Examples are below.

When updating data, keys that are unexpected are silently ignored.

Values in an array are expected to be related objects in a hash format. If the related object is not in a hash format, you will get an HTTP 400 error with the message: Error when parsing data, and detail: Not a HASH: name.

An error will be raised if related objects cannot be found.

Response

If the request was successful, the response will be a status code of 200. If data is requested, this will be in the content in the requested format.

Single object

The response format for a single object is (in JSON format):

{
  "object" : { ... }
}

List

The response format for a list of items is (in JSON format):

{
   "summary" : {
     "rows" : "30",                # This is the number of rows in the response. If this equals allrows, then you have all results
     "page" : 1,                   # Current page number
     "totalrows" : "100",          # This is the count of all the rows based on the search parameters
     "totalpages" : 4,             # Based on the pages and the number of rows per page, this is the total number of pages
     "allrows" : "153",            # This is the count of all rows for this datatype (restricted by user access, eg hosts)
   },
   "list" : [
      {
         "ref" : "/rest/config/servicecheck/47",
         "name" : "/",
         "id" : "47",
         "description" : "Utilisation of / partition"
      },
      ....
   ]
}

The object hash is the same as a single object.

The value for the ref key provides a way of accessing the related object. Note: some of the ref links are not provided yet - this will be detailed in the documentation for each object type.

URL parameters that can be used on list pages:

  • rows - number of rows to return. Defaults to 50. If rows=0, then only a summary will be returned. If rows=all, all rows are returned
  • page - page number to return. Defaults to 1
  • cols - which columns to return. This is a comma separated list. Only valid columns per object will be used. If no columns specified, will use all available columns for this object. Can prefix column name with a ”-” to remove the column from the available list
  • s.{columnname} - will search using LIKE for parameter. Add % for wildcards if required - this should be %25 to be encoded in the URL (see this article regarding URLs and encoded values). If parameter is specified multiple times, will be considered as an OR for that column. If more than 1 column is specified, will consider to be an AND with other columns
  • json_filter - this allows complex searching of fields. See here for more information

JSON Filter

Available from Opsview 3.11.0.

You can specify a search criteria in JSON format using the json_filter URL parameter. The contents of this is converted to a perl hash format, which is expected to be in SQL::Abstract format.

For instance, to search for all objects where id is not equal to 1, you would have:

....?json_filter={"id":{"!=":1}}

and to search for all objects who's name contains 'slave' or FQDN contains 'der', you would have:

....?json_filter={"-or":[{"name":{"-like":"%25slave%25C"}},{"ip":{"-like":"%25der%25"}}]}

(note the '%' SQL search regexp is url encoded to be %25)

Note: As this option is more low level, you may cause errors when invoking the API. If there are errors with the search, you will get a message of Error executing search with detail giving the full reason.

Note: Only the current table search column comparisons are supported at the moment. All column names are prefixed with me., which means only a search on the current table is possible.

Errors

If there was an error, an appropriate status code will be set. Data will be sent with the format in the form of:

{ 
  message => "Text explaining the error",
  detail => "Text with further detail. This may not exist",
}

Note: The content returned maybe in a different format than the one requested. For instance, invalid data structures (400 bad request) could return content-type text.

Common errors

400: Bad Request

This can mean that the input parameters can not be parsed. There should be more information in the content.

If the contents shows:

Content-Type application/json had a problem with your request.
***ERROR***
malformed JSON string, neither array, object, number, string or atom, at character offset 180 (before "],\n      "all_servi...") at /usr/local/nagios/perl/lib/Catalyst/Action/Deserialize/JSON.pm line 26, <$fh> line 21.

Then this means there is some invalid JSON data. JSON input is set to “relaxed”, so additional commas at end of lists are valid.

Interface

General Configuration Data

These are the general URLs for each object type.

URL: /rest/config/{object_type}

  • GET - list object type. Can pass in search attributes
  • POST - add a new object or a list of object type
  • PUT - create or update (based on unique keys) object or a list of objects
  • DELETE - unimplemented

URL: /rest/config/{object_type}/{id}

  • GET - get object details
  • POST - clone this object with merged incoming data to create new object
  • PUT - update this object's details
  • DELETE - delete object

Updating a single object

When POST or PUTting a single object, the API expects a hash input with key value pairs. If successful, the API will return back the new serialised object.

All ref attributes are ignored when updating - the ref key is for clients that may want further information about the foreign object.

Updating a list of objects

When POST or PUTting a list of objects, the API expects a hash with a key of list which is an array of hash objects. Each object will then be POST or PUT in turn.

If successful, the data returned will be of the format:

{
  "objects_updated" : 3
}

There is an extra parameter that can be used:

  • synchronise - this means that any objects left after the individual PUTs will be deleted (available from Opsview 3.10.1)

If an error occurs, the behaviour is different based on the version of Opsview.

Opsview 3.10.1 or above

The whole request is wrapped in a transaction. If there is a failure for any item, a rollback will occur and no changes will have been made to the system. The response will be:

{
  message:"Rollback. Error trying to synchronise object: 2",
  detail:"detail about the error",
  objects_updated:0
}
Opsview 3.10.0 or 3.9.1 or earlier

Objects up to the failure point will have been committed to the system. No further objects will be attempted in the list. The data returned will be of the format:

{
  "message" : "Error trying to synchronise object: 2",
  "detail" : "more information",
  "objects_updated" : 1
}

You can use the objects_updated value to remove items in the list before re-attempting the request again.

Object Types

Contacts

Object type: contact

Example contact:

{
   "object" : {
      "language" : "",
      "servicegroups" : [
         {
            "ref" : "/rest/config/servicegroup/2",
            "name" : "freshness checks"
         },
      ],
      "all_servicegroups" : "1",
      "name" : "admin",
      "encrypted_password" : "$apr1$dEX5UaQz$D6fCyI197d33YcQQqe1Yj.",
      "variables" : [
         {
            "value" : "1",
            "name" : "RSS_COLLAPSED"
         },
      ],
      "all_hostgroups" : "1",
      "fullname" : "admin",
      "keywords" : [
         {
            "ref" : "/rest/config/keyword/8",
            "name" : "allservicechecks"
         }
      ],
      "description" : "Initial admin user",
      "hostgroups" : [
         {
            "ref" : "/rest/config/hostgroup/4",
            "name" : "Build"
         },
         {
            "ref" : "/rest/config/hostgroup/2",
            "name" : "Monitoring Servers"
         },
      ],
      "notificationprofiles" : [
         {
            "host_notification_options" : "u,d,r,f",
            "servicegroups" : [],
            "ref" : "/rest/config/notificationprofile/4",
            "name" : "Default",
            "all_servicegroups" : "1",
            "all_hostgroups" : "0",
            "hostgroups" : [
               {
                  "ref" : "/rest/config/hostgroup/5",
                  "name" : "Leaf2"
               }
            ],
            "service_notification_options" : "w,c,r,u,f",
            "id" : "4",
            "notification_level" : "1",
            "uncommitted" : "1",
            "notification_period" : {
               "ref" : "/rest/config/timeperiod/1",
               "name" : "24x7"
            }
         }
      ],
      "realm" : "local",
      "id" : "1",
      "role" : {
         "ref" : "/rest/config/role/10",
         "name" : "Admin"
      },
      "uncommitted" : "0"
   }
}

Note: From Opsview 3.11.0, the fields all_hostgroup, all_servicegroups, hostgroups, servicegroups and keywords will no longer be returned with the contact information. This is because it will be migrated to the role object type instead.

Note: The reference to the notificationprofile object type is not currently available.

Hosts

Object type: host

Example host response:

{
   "object" : {
      "snmpv3_privprotocol" : null,
      "flap_detection_enabled" : "1",
      "hosttemplates" : [
         {
            "ref" : "/rest/config/hosttemplate/4",
            "name" : "Blank"
         }
      ],
      "keywords" : [],
      "check_period" : {
         "ref" : "/rest/config/timeperiod/3",
         "name" : "nonworkhours"
      },
      "hostattributes" : [
         {
            "arg1" : null,
            "arg2" : null,
            "arg3" : null,
            "arg4" : null,
            "value" : "/remote",
            "name" : "DISK"
         },
         {
            "arg1" : null,
            "arg2" : null,
            "arg3" : null,
            "arg4" : null,
            "value" : "opsviewd",
            "name" : "PROCESS"
         }
      ],
      "id" : "22",
      "notification_period" : {
         "ref" : "/rest/config/timeperiod/1",
         "name" : "24x7"
      },
      "notification_options" : "d,r",
      "name" : "monitored_by_cluster",
      "rancid_vendor" : null,
      "snmp_community" : "slave1234",
      "hostgroup" : {
         "ref" : "/rest/config/hostgroup/5",
         "name" : "Leaf2"
      },
      "enable_snmp" : "1",
      "monitored_by" : {
         "ref" : "/rest/config/monitoringserver/3",
         "name" : "Cluster"
      },
      "alias" : "Host to be monitored by slave",
      "uncommitted" : "1",
      "parents" : [],
      "retry_check_interval" : "4",
      "icon" : {
         "name" : "SYMBOL - Wireless network",
         "path" : "/images/logos/wireless_small.png"
      },
      "ip" : "monitored_by_clusterip",
      "use_mrtg" : "0",
      "servicechecks" : [
         {
            "ref" : "/rest/config/servicecheck/45",
            "name" : "Check Loadavg",
            "exception":"-w 5,5,5 -c 9,9,9",
            "timed_exception":null,
            "event_handler":null,
            "remove_servicecheck":0,
         },
         {
            "ref" : "/rest/config/servicecheck/4",
            "name" : "DNS",
            "exception":"--other --args",
            "timed_exception":null,
            "event_handler":null,
            "remove_servicecheck":1,
         },
         {
            "ref" : "/rest/config/servicecheck/6",
            "name" : "HTTP",
            "timed_exception":{
                "timeperiod" : { "name" : "workhours" },
                "args" : "--url=/about -w 2 -c 4",
            },
            "exception":null,
            "remove_servicecheck":0,
            "event_hander":"restart_http",
      ],
      "use_rancid" : "0",
      "nmis_node_type" : "server",
      "snmpv3_authpassword" : "",
      "snmp_version" : "2c",
      "use_nmis" : "1",
      "rancid_connection_type" : "ssh",
      "snmpv3_authprotocol" : null,
      "rancid_username" : null,
      "rancid_password" : null,
      "check_command" : {
         "ref" : "/rest/config/hostcheckcommand/7",
         "name" : "NRPE (on port 5666)"
      },
      "check_attempts" : "15",
      "check_interval" : "25",
      "notification_interval" : "60",
      "snmpv3_privpassword" : "",
      "snmpv3_username" : "",
      "other_addresses" : ""
   }
}

Warning: If you change the list of servicechecks and do not specify the exception/timed_exception/remove_servicecheck parameters, this will default to undef, undef and 0 respectively.

Note: When applying a timed exception, if the related timeperiod is not found, the timed exception is silently dropped.

Note: The reference to the monitored_by related object is available from 3.11.0 onwards.

Roles

Object type: role

Example role:

{
   "object" : {
      "contacts" : [
         {
            "ref" : "/rest/config/contact/1",
            "name" : "admin"
         }
      ],
      "monitoringservers" : [],
      "hostgroups" : [
         {
            "ref" : "/rest/config/hostgroup/1",
            "name" : "Opsview"
         }
      ],
      "name" : "Admin",
      "id" : "10",
      "description" : "Administrator access",
      "accesses" : [
         {
            "ref" : "/rest/config/access/1",
            "name" : "VIEWALL"
         },
         {
            "ref" : "/rest/config/access/14",
            "name" : "PASSWORDSAVE"
         }
      ],
      "access_hostgroups": [],
      "access_servicegroups": [],
      "access_keywords" : [],
      "all_hostgroups" : "0",
      "all_servicegroups" : "0",
      "all_keywords" : "0",
      "uncommitted" : "0"
   }
}

Note: The reference to access is not currently available.

Note: From Opsview 3.11.0, the fields access_hostgroups, access_servicegroups, access_keywords, all_hostgroups, all_servicegroups and all_keywords are available, as this data is moved from the contact level.

Service Checks

Object type: servicecheck

Example GET:

{
   "object" : {
      "flap_detection_enabled" : "1",
      "check_freshness" : "1",
      "invertresults" : "0",
      "dependencies" : [
         {
            "ref" : "/rest/config/servicecheck/46",
            "name" : "Check Swap"
         },
         {
            "ref" : "/rest/config/servicecheck/3",
            "name" : "DHCP"
         }
      ],
      "keywords" : [
         {
            "ref" : "/rest/config/keyword/2",
            "name" : "cisco"
         },
         {
            "ref" : "/rest/config/keyword/4",
            "name" : "cisco_gp2"
         }
      ],
      "attribute" : null,
      "check_period" : {
         "ref" : "/rest/config/timeperiod/2",
         "name" : "workhours"
      },
      "id" : "79",
      "notification_period" : null,
      "snmptraprules" : [
         {
            "alertlevel" : "1",
            "ref" : "/rest/config/snmptraprule/1",
            "name" : "Check coldstart",
            "message" : "Device coldstarted",
            "process" : "1",
            "code" : "\"${TRAPNAME}\" =~ /SNMPv2-MIB::coldstart/i",
            "uncommitted" : "1"
         },
         {
            "alertlevel" : "0",
            "ref" : "/rest/config/snmptraprule/2",
            "name" : "Otherwise Ok",
            "message" : "OK",
            "process" : "1",
            "code" : "1",
            "uncommitted" : "1"
         }
      ],
      "notification_options" : "w,c,r,u",
      "name" : "Coldstart",
      "stale_threshold_seconds" : "3750",
      "description" : "",
      "markdown_filter" : "0",
      "critical_comparison" : null,
      "warning_value" : null,
      "label" : null,
      "uncommitted" : "1",
      "freshness_type" : "set_stale",
      "retry_check_interval" : null,
      "calculate_rate" : "",
      "oid" : null,
      "args" : "",
      "event_handler" : "",
      "stale_text" : "Set to critical!!",
      "critical_value" : null,
      "plugin" : null,
      "volatile" : "0",
      "stalking" : null,
      "checktype" : {
         "ref" : "/rest/config/checktype/4",
         "name" : "SNMP trap"
      },
      "servicegroup" : {
         "ref" : "/rest/config/servicegroup/1",
         "name" : "Operations"
      },
      "stale_state" : "2",
      "check_attempts" : "5",
      "check_interval" : "25",
      "warning_comparison" : null,
      "notification_interval" : null
   }
}

If you set a parameter of order with the value dependency, the service checks will be listed based on their level, which means that if there are service check dependencies, the most dependent items will be listed first.

Note: The reference to plugin, checktype and snmptraprule is not available.

Note: In Opsview prior to 3.11.0, the snmp trap rule objects were returning an id field - this is incorrect as it may not be possible to push the resultant data back into Opsview. This field should be removed before attempting to PUT or POST.

Host Templates

Object type: hosttemplate

Example GET:

{
   "object" : {
      "hosts" : [
         {
            "ref" : "/rest/config/host/7",
            "name" : "cisco"
         }
      ],
      "name" : "Cisco Mgt",
      "description" : "Cisco device Management URLs",
      "id" : "3",
      "servicechecks" : [
         {
            "ref" : "/rest/config/servicecheck/45",
            "name" : "Check Loadavg",
            "exception":"-w 5,5,5 -c 9,9,9",
            "timed_exception":null,
         },
         {
            "ref" : "/rest/config/servicecheck/4",
            "name" : "DNS",
            "exception":"--other --args",
            "timed_exception":null,
         },
         {
            "ref" : "/rest/config/servicecheck/6",
            "name" : "HTTP",
            "timed_exception":{
                "timeperiod" : { "name" : "workhours" },
                "args" : "--url=%URL% -w 15 -c 20",
            },
            "exception":null,
      ],
      "managementurls" : [
         {
            "url" : "ssh://$HOSTADDRESS$",
            "name" : "SSH",
            "id" : "4"
         },
         {
            "url" : "telnet://$HOSTADDRESS$",
            "name" : "Telnet",
            "id" : "5"
         }
      ],
      "uncommitted" : "1"
   }
}

From Opsview 3.11.1 onwards, there is a new attribute called remove_hostservicechecks. See the host template configuration for information about this attribute.

Attribute

Object type: attribute

Example GET:

{
   "object" : {
      "name" : "PROCESS",
      "id" : "6",
      "arg1" : "",
      "arg2" : "",
      "arg3" : "",
      "arg4" : "",
      "value" : "",
      "servicechecks" : [
         {
            "ref" : "/rest/config/servicecheck/92",
            "name" : "Processes"
         },
         {
            "ref" : "/rest/config/servicecheck/93",
            "name" : "Windows Processes"
         }
      ]
   }
}

Note: When saving attributes, you cannot add a host attribute nor amend the list of service checks using an attribute. If you want to amend a service check so that it is no longer using multiple attributes, you have to edit the service check itself.

Note: arg1 to arg4 and value are available from Opsview 3.11.0.

Time Period

Object type: timeperiod

Example GET:

{
   "object" : {
      "sunday" : "00:00-24:00",
      "friday" : "00:00-09:00,17:00-24:00",
      "name" : "nonworkhours",
      "servicecheck_notification_periods" : [
         {
            "ref" : "/rest/config/servicecheck/26",
            "name" : "TFTP"
         },
         {
            "ref" : "/rest/config/servicecheck/43",
            "name" : "Whois"
         }
      ],
      "servicecheck_check_periods" : [
         {
            "ref" : "/rest/config/servicecheck/43",
            "name" : "Whois"
         }
      ],
      "monday" : "00:00-09:00,17:00-24:00",
      "tuesday" : "00:00-09:00,17:00-24:00",
      "saturday" : "00:00-24:00",
      "wednesday" : "00:00-09:00,17:00-24:00",
      "thursday" : "00:00-09:00,17:00-24:00",
      "id" : "3",
      "host_check_periods" : [
         {
            "ref" : "/rest/config/host/22",
            "name" : "monitored_by_cluster"
         },
         {
            "ref" : "/rest/config/host/4",
            "name" : "monitored_by_slave"
         }
      ],
      "alias" : "Non-work Hours",
      "host_notification_periods" : [
         {
            "ref" : "/rest/config/host/12",
            "name" : "toclone"
         }
      ],
      "uncommitted" : "0"
   }
}

Note: When PUTing, you cannot change the related host/service check period/notification periods. If you want to change those, you have to change the related host/service check itself.

Host group

Object type: hostgroup

Example GET:

{
   "object" : {
      "hosts" : [],
      "parent" : {
            "name" : "Opsview",
            "matpath" : "Opsview,",
            "ref" : "/rest/config/hostgroup/1"
         },
      "name" : "Production",
      "id" : "2",
      "children" : [
         {
            "name" : "London",
            "ref": "/rest/config/hostgroup/7"
         },
         {
            "name" : "Paris",
            "ref": "/rest/config/hostgroup/3"
         },
         {
            "name" : "New York",
            "ref": "/rest/config/hostgroup/6"
         }
      ],
      "uncommitted" : "0",
      "matpath" : "Opsview,Production,",
      "is_leaf" : 0
   }
}

The matpath is a comma separated list of host groups that define this hostgroup's location in the topology.

The parent contains a the “matpath” attribute to uniquely identify the parent for a host group as it is possible to have duplicated names in the host group hierarchy (from Opsview 3.11.2+).

If you GET with a parameter of order=dependency, the host groups will be listed based on a traversal of the hierarchy, so the top host group will be first and then it will go down a branch until it comes back up again (from Opsview 3.11.2+).

Note: When PUTing, you cannot change the children - you can only change the topology by amending the parent. Also, the is_leaf and matpath field is calculated, so cannot be changed.

Warning: The name column in NOT unique in host groups. Opsview has additional logic so that if you use a name and it cannot be found, then it will search based on the matpath (but the matpath has a 255 character limit). Use the id field where possible as that is unique.

Service group

Object type: servicegroup

Example GET:

{
   "object" : {
      "name" : "Application - web server",
      "id" : "2",
      "servicechecks" : [
         {
            "ref" : "/rest/config/servicecheck/85",
            "name" : "HTTP"
         },
      ],
      "uncommitted" : "0"
  }
}

Note: When PUTing, you cannot change the related service checks. If you want to change those, you have to change the related service check itself.

Notification Method

Object type: notificationmethod

Example GET:

{
   "object" : {
      "namespace" : "com.opsview.notificationmethods.aql",
      "master" : "0",
      "name" : "AQL",
      "active" : "1",
      "notificationprofiles" : [
         {
            "ref" : "/rest/config/notificationprofile/12",
            "name" : "Non work hours sms"
         },
         {
            "ref" : "/rest/config/notificationprofile/3",
            "name" : "Default"
         }
      ],
      "id" : "1",
      "uncommitted" : "0",
      "command" : "submit_sms_aql -u '' -p '' -P ''",
      "contact_variables" : "PAGER"
   }
}

Note: Notification method variables are currently missing.

Host Check Command

Object type: hostcheckcommand

Example GET:

{
   "object" : {
      "priority" : "1",
      "plugin" : {
         "ref" : "/rest/config/plugin/check_icmp",
         "name" : "check_icmp"
      },
      "hosts" : [
         {
            "ref" : "/rest/config/host/14",
            "name" : "doesnt_exist_1"
         },
         {
            "ref" : "/rest/config/host/15",
            "name" : "doesnt_exist_2"
         },
         {
            "ref" : "/rest/config/host/16",
            "name" : "singlehostgroup"
         }
      ],
      "name" : "ping",
      "args" : "-H $HOSTADDRESS$ -t 3 -w 500.0,80% -c 1000.0,100%",
      "id" : "15",
      "uncommitted" : "0"
   }
}

Note: You cannot PUT the list of hosts. The reference to plugin is currently unavailable.

Keyword

Object type: keyword

Example GET:

{
   "object" : {
      "all_hosts" : "0",
      "hosts" : [
         {
            "ref" : "/rest/config/host/7",
            "name" : "cisco"
         },
         {
            "ref" : "/rest/config/host/8",
            "name" : "cisco1"
         }
      ],
      "roles" : [
         {
            "ref" : "/rest/config/role/14",
            "name" : "View some, change none"
         },
         {
            "ref" : "/rest/config/role/15",
            "name" : "View some, change none, no notify"
         }
      ],
      "all_servicechecks" : "0",
      "style" : "group_by_host",
      "name" : "cisco",
      "description" : "cisco devices",
      "servicechecks" : [
         {
            "ref" : "/rest/config/servicecheck/82",
            "name" : "Another exception"
         },
         {
            "ref" : "/rest/config/servicecheck/81",
            "name" : "Test exceptions"
         }
      ],
      "id" : "2",
      "enabled" : "1",
      "uncommitted" : "1"
   }
}

Monitoring servers

Object type: monitoringserver

This object type is available from 3.11.0 onwards.

Example GET:

{
   "object" : {
      "roles" : [
         {
            "ref" : "/rest/config/role/12",
            "name" : "View some, change some"
         }
      ],
      "activated" : "1",
      "monitors" : [
         {
            "ref" : "/rest/config/host/10",
            "name" : "cisco3"
         },
         {
            "ref" : "/rest/config/host/4",
            "name" : "monitored_by_slave"
         }
      ],
      "name" : "ClusterA",
      "nodes" : [
         {
            "host" : {
               "ref" : "/rest/config/host/5",
               "name" : "opslave"
            }
         }
      ],
      "id" : "2",
      "uncommitted" : "1"
   }
}

If id=1, this is the master monitoring server.

Note: You cannot PUT, POST or DELETE to monitoringservers.

Examples

Updating Multiple Service Checks

The restapi can be used to update multiple servicechecks in one go:

  • Obtain the list of check to be modified and store in a file, i.e. all service checks with 'foo' in the name
    opsview_rest --username=USER --password=PASS --pretty --data-format=json GET 'config/servicecheck?json_filter={"name":{"-like":"%25foo%25"}}' > checks.json
    
  • Edit the checks.json file as necessary
  • Import the contents of the file back into Opsview
    opsview_rest --username=USER --password=PASS --pretty --data-format=json --content-file=checks.json --pretty PUT config/servicecheck
    

Note: the ID's within the checks.json file must be left unchanged else new checks will be created, not current checks updated.

Note: Amending service check names could lose historical (graphing) information

Navigation
Print/export
Toolbox