Align Sahana Spotter template with the current default template
This commit is contained in:
parent
6f52ea0f1a
commit
967ccc0d44
@ -1,91 +1,91 @@
|
|||||||
uid,role,description,controller,function,table,entity,uacl,oacl,hidden,system,protected,Notes
|
uid,role,description,controller,function,table,entity,uacl,oacl,Notes
|
||||||
ANONYMOUS,Anonymous,Unauthenticated users,,,org_organisation,,READ,READ,,,,Required for self-registration
|
ANONYMOUS,Anonymous,Unauthenticated users,,,org_organisation,,READ,READ,Required for self-registration
|
||||||
ANONYMOUS,Anonymous,,org,sites_for_org,,,READ,READ,,,,Required for self-registration
|
ANONYMOUS,Anonymous,,org,sites_for_org,,,READ,READ,Required for self-registration
|
||||||
ANONYMOUS,Anonymous,,gis,,,,READ,READ,,,,
|
ANONYMOUS,Anonymous,,gis,,,,READ,READ,
|
||||||
ANONYMOUS,Anonymous,,vulnerability,,,,READ,READ,,,,
|
ANONYMOUS,Anonymous,,vulnerability,,,,READ,READ,
|
||||||
ANONYMOUS,Anonymous,,water,,,,READ,READ,,,,
|
ANONYMOUS,Anonymous,,water,,,,READ,READ,
|
||||||
ANONYMOUS,Anonymous,,delphi,,,,READ,READ,,,,
|
ANONYMOUS,Anonymous,,delphi,,,,READ,READ,
|
||||||
AUTHENTICATED,Authenticated,Authenticated - all logged-in users,gis,,,,READ,READ,,,,
|
AUTHENTICATED,Authenticated,Authenticated - all logged-in users,gis,,,,READ,READ,
|
||||||
AUTHENTICATED,Authenticated,,vulnerability,,,,READ,READ,,,,
|
AUTHENTICATED,Authenticated,,vulnerability,,,,READ,READ,
|
||||||
AUTHENTICATED,Authenticated,,water,,,,READ,READ,,,,
|
AUTHENTICATED,Authenticated,,water,,,,READ,READ,
|
||||||
AUTHENTICATED,Authenticated,,delphi,,,,READ,READ,,,,
|
AUTHENTICATED,Authenticated,,delphi,,,,READ,READ,
|
||||||
AUTHENTICATED,Authenticated,,impact,,,,READ,READ,,,,
|
AUTHENTICATED,Authenticated,,impact,,,,READ,READ,
|
||||||
AUTHENTICATED,Authenticated,,doc,,,,READ,READ,,,,
|
AUTHENTICATED,Authenticated,,doc,,,,READ,READ,
|
||||||
EDITOR,Editor,Editor - can access & make changes to any unprotected data,pr,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,Editor - can access & make changes to any unprotected data,pr,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,vulnerability,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,vulnerability,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,survey,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,survey,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,security,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,security,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,fire,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,fire,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,deploy,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,deploy,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,water,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,water,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,budget,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,budget,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,cap,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,cap,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,vol,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,vol,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,delphi,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,delphi,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,org,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,org,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,scenario,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,scenario,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,vehicle,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,vehicle,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,member,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,member,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,cr,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,cr,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,gis,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,gis,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,asset,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,asset,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,patient,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,patient,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,transport,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,transport,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,po,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,po,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,impact,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,impact,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,dvr,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,dvr,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,event,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,event,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,msg,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,msg,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,supply,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,supply,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,project,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,project,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,doc,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,doc,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,hrm,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,hrm,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,appadmin,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,appadmin,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,hms,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,hms,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,req,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,req,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,mpr,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,mpr,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,sync,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,sync,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,inv,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,inv,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,stats,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,stats,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,edu,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,edu,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,dvi,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,dvi,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
EDITOR,Editor,,cms,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,,,,
|
EDITOR,Editor,,cms,,,,ALL,CREATE|READ|UPDATE|REVIEW|APPROVE,
|
||||||
MAP_ADMIN,Map Admin,"Complementary role: FULL access mode only to the maps and their configurations
|
MAP_ADMIN,Map Admin,"Complementary role: FULL access mode only to the maps and their configurations
|
||||||
When to assign: Staff Directors.",gis,,,,ALL,ALL,,,,
|
When to assign: Staff Directors.",gis,,,,ALL,ALL,
|
||||||
ORG_ADMIN,Organization Admin,,org,,,,ALL,ALL,,,,
|
ORG_ADMIN,Organization Admin,,org,,,,ALL,ALL,
|
||||||
ORG_GROUP_ADMIN,Organziation Group Admin,,,,,,,,,,,
|
ORG_GROUP_ADMIN,Organzation Group Admin,,,,,,,,
|
||||||
medical_admin,Medical Details Admin,"Complementary role: FULL access but only to medical data of registered evacuees. It must be assigned with other roles to grant access to the other types of data.
|
medical_admin,Medical Details Admin,"Complementary role: FULL access but only to medical data of registered evacuees. It must be assigned with other roles to grant access to the other types of data.
|
||||||
When to assign: Medical Staff.",patient,,,,ALL,ALL,,,,
|
When to assign: Medical Staff.",patient,,,,ALL,ALL,
|
||||||
medical_admin,Medical Details Admin,,hms,,,,ALL,ALL,,,,
|
medical_admin,Medical Details Admin,,hms,,,,ALL,ALL,
|
||||||
medical_admin,Medical Details Admin,,dvi,,,,ALL,ALL,,,,
|
medical_admin,Medical Details Admin,,dvi,,,,ALL,ALL,
|
||||||
notification_sender,Notification Sender,,msg,,,,ALL,ALL,,,,
|
notification_sender,Notification Sender,,msg,,,,ALL,ALL,
|
||||||
org_reader,Organization Reader,"Complementary role: Access in ""READ only"" mode to Organization data and its branches
|
org_reader,Organization Reader,"Complementary role: Access in ""READ only"" mode to Organization data and its branches
|
||||||
When to assign: Staff Directors",org,,,,READ,READ,,,,
|
When to assign: Staff Directors",org,,,,READ,READ,
|
||||||
private_user_editor,Private User Editor,"FULL access to modules Evacuees and Shelter.
|
private_user_editor,Private User Editor,"FULL access to modules Evacuees and Shelter.
|
||||||
When to assign: private user member of a shelter / Organization and committed to provide assistance to some evacuees",cr,,,,ALL,ALL,,,,
|
When to assign: private user member of a shelter / Organization and committed to provide assistance to some evacuees",cr,,,,ALL,ALL,
|
||||||
private_user_editor,Private User Editor,,dvr,,,,ALL,ALL,,,,
|
private_user_editor,Private User Editor,,dvr,,,,ALL,ALL,
|
||||||
private_user_reader,Private User Reader,"Access in ""READ only"" mode to modules Evacuees and Shelter.
|
private_user_reader,Private User Reader,"Access in ""READ only"" mode to modules Evacuees and Shelter.
|
||||||
When to assign: private user member of a Shelter / Organization and committed to provide assistance to some evacuees.",cr,,,,READ,READ,,,,
|
When to assign: private user member of a Shelter / Organization and committed to provide assistance to some evacuees.",cr,,,,READ,READ,
|
||||||
private_user_reader,Private User Reader,,dvr,,,,READ,READ,,,,
|
private_user_reader,Private User Reader,,dvr,,,,READ,READ,
|
||||||
public_auth_editor,Public Authority Editor,"FULL access to modules Evacuees and Shelter.
|
public_auth_editor,Public Authority Editor,"FULL access to modules Evacuees and Shelter.
|
||||||
When to assign: supervisor user member of local government office with the rights to register data.",cr,,,,ALL,ALL,,,,
|
When to assign: supervisor user member of local government office with the rights to register data.",cr,,,,ALL,ALL,
|
||||||
public_auth_editor,Public Authority Editor,,dvr,,,,ALL,ALL,,,,
|
public_auth_editor,Public Authority Editor,,dvr,,,,ALL,ALL,
|
||||||
public_auth_editor,Public Authority Editor,,mpr,,,,READ,READ,,,,
|
public_auth_editor,Public Authority Editor,,mpr,,,,READ,READ,
|
||||||
public_auth_reader,Public Authority Reader,"Access in ""READ only"" mode to modules Evacuees and Shelter.
|
public_auth_reader,Public Authority Reader,"Access in ""READ only"" mode to modules Evacuees and Shelter.
|
||||||
When to assign: supervisor user member of a local government office.",dvr,,,,READ,READ,,,,
|
When to assign: supervisor user member of a local government office.",dvr,,,,READ,READ,
|
||||||
staff_admin,Staff Admin,"FULL access to modules Staff, Volunteer, Evacuees and Shelter.
|
staff_admin,Staff Admin,"FULL access to modules Staff, Volunteer, Evacuees and Shelter.
|
||||||
When to assign: Staff assigned to manage one or more shelters.",vol,,,,ALL,ALL,,,,
|
When to assign: Staff assigned to manage one or more shelters.",vol,,,,ALL,ALL,
|
||||||
staff_admin,Staff Admin,,cr,,,,ALL,ALL,,,,
|
staff_admin,Staff Admin,,cr,,,,ALL,ALL,
|
||||||
staff_admin,Staff Admin,,dvr,,,,ALL,ALL,,,,
|
staff_admin,Staff Admin,,dvr,,,,ALL,ALL,
|
||||||
staff_admin,Staff Admin,,hrm,,,,ALL,ALL,,,,
|
staff_admin,Staff Admin,,hrm,,,,ALL,ALL,
|
||||||
staff_reader,Staff Reader,"Access in ""READ only"" mode to modules Staff, Volunteer, Evacuees and Shelter.
|
staff_reader,Staff Reader,"Access in ""READ only"" mode to modules Staff, Volunteer, Evacuees and Shelter.
|
||||||
When to assign: Staff assigned to manage one or more shelters.",vol,,,,READ,READ,,,,
|
When to assign: Staff assigned to manage one or more shelters.",vol,,,,READ,READ,
|
||||||
staff_reader,Staff Reader,,cr,,,,READ,READ,,,,
|
staff_reader,Staff Reader,,cr,,,,READ,READ,
|
||||||
staff_reader,Staff Reader,,dvr,,,,READ,READ,,,,
|
staff_reader,Staff Reader,,dvr,,,,READ,READ,
|
||||||
staff_reader,Staff Reader,,hrm,,,,READ,READ,,,,
|
staff_reader,Staff Reader,,hrm,,,,READ,READ,
|
||||||
vol_editor,Volunteer Editor,"FULL access to modules Evacuees and Shelter.
|
vol_editor,Volunteer Editor,"FULL access to modules Evacuees and Shelter.
|
||||||
When to assign: volunteer (trusted person) volunteer who provides assistance to some evacuees inside a well-defined group of shelters.",cr,,,,ALL,ALL,,,,
|
When to assign: volunteer (trusted person) volunteer who provides assistance to some evacuees inside a well-defined group of shelters.",cr,,,,ALL,ALL,
|
||||||
vol_editor,Volunteer Editor,,dvr,,,,ALL,ALL,,,,
|
vol_editor,Volunteer Editor,,dvr,,,,ALL,ALL,
|
||||||
vol_reader,Volunteer Reader,"Access in ""READ only"" mode to modules Evacuees and Shelter.
|
vol_reader,Volunteer Reader,"Access in ""READ only"" mode to modules Evacuees and Shelter.
|
||||||
When to assign: volunteer who provides assistance to some evacuees inside a well- defined group of shelters.",cr,,,,READ,READ,,,,
|
When to assign: volunteer who provides assistance to some evacuees inside a well- defined group of shelters.",cr,,,,READ,READ,
|
||||||
vol_reader,Volunteer Reader,,dvr,,,,READ,READ,,,,
|
vol_reader,Volunteer Reader,,dvr,,,,READ,READ,
|
||||||
|
|
@ -27,6 +27,7 @@ def config(settings):
|
|||||||
#settings.base.theme = "default"
|
#settings.base.theme = "default"
|
||||||
|
|
||||||
# Enable Guided Tours
|
# Enable Guided Tours
|
||||||
|
# - defaults to module enabled or not
|
||||||
#settings.base.guided_tour = True
|
#settings.base.guided_tour = True
|
||||||
|
|
||||||
# Authentication settings
|
# Authentication settings
|
||||||
@ -128,6 +129,7 @@ def config(settings):
|
|||||||
# Uncomment this to enable the creation of new locations if a user logs in from an unknown location. Warning: This may lead to many useless location entries
|
# Uncomment this to enable the creation of new locations if a user logs in from an unknown location. Warning: This may lead to many useless location entries
|
||||||
#settings.auth.create_unknown_locations = True
|
#settings.auth.create_unknown_locations = True
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
# L10n settings
|
# L10n settings
|
||||||
# Languages used in the deployment (used for Language Toolbar, GIS Locations, etc)
|
# Languages used in the deployment (used for Language Toolbar, GIS Locations, etc)
|
||||||
# http://www.loc.gov/standards/iso639-2/php/code_list.php
|
# http://www.loc.gov/standards/iso639-2/php/code_list.php
|
||||||
@ -347,8 +349,8 @@ def config(settings):
|
|||||||
# 6: Apply Controller, Function, Table ACLs and Entity Realm
|
# 6: Apply Controller, Function, Table ACLs and Entity Realm
|
||||||
# 7: Apply Controller, Function, Table ACLs and Entity Realm + Hierarchy
|
# 7: Apply Controller, Function, Table ACLs and Entity Realm + Hierarchy
|
||||||
# 8: Apply Controller, Function, Table ACLs, Entity Realm + Hierarchy and Delegations
|
# 8: Apply Controller, Function, Table ACLs, Entity Realm + Hierarchy and Delegations
|
||||||
#
|
|
||||||
settings.security.policy = 7 # Organisation-ACLs
|
settings.security.policy = 7 # Organization ACLs
|
||||||
|
|
||||||
# Ownership-rule for records without owner:
|
# Ownership-rule for records without owner:
|
||||||
# True = not owned by any user (strict ownership, default)
|
# True = not owned by any user (strict ownership, default)
|
||||||
@ -787,7 +789,7 @@ def config(settings):
|
|||||||
# Make Services Hierarchical
|
# Make Services Hierarchical
|
||||||
settings.org.services_hierarchical = True
|
settings.org.services_hierarchical = True
|
||||||
# Set the length of the auto-generated org/site code the default is 10
|
# Set the length of the auto-generated org/site code the default is 10
|
||||||
#settings.org.site_code_len = 10
|
#settings.org.site_code_len = 3
|
||||||
# Set the label for Sites
|
# Set the label for Sites
|
||||||
settings.org.site_label = "Facility"
|
settings.org.site_label = "Facility"
|
||||||
# Uncomment to show the date when a Site (Facilities-only for now) was last contacted
|
# Uncomment to show the date when a Site (Facilities-only for now) was last contacted
|
||||||
@ -936,7 +938,7 @@ def config(settings):
|
|||||||
settings.inv.recv_shortname = "ARDR"
|
settings.inv.recv_shortname = "ARDR"
|
||||||
# Types common to both Send and Receive
|
# Types common to both Send and Receive
|
||||||
settings.inv.shipment_types = {
|
settings.inv.shipment_types = {
|
||||||
0: T(""),
|
0: "", # current.messages["NONE"] but current.messages is defined only after 000_config.py is executed
|
||||||
1: T("Other Warehouse"),
|
1: T("Other Warehouse"),
|
||||||
2: T("Donation"),
|
2: T("Donation"),
|
||||||
3: T("Foreign Donation"),
|
3: T("Foreign Donation"),
|
||||||
@ -952,7 +954,7 @@ def config(settings):
|
|||||||
# 34: T("Purchase"),
|
# 34: T("Purchase"),
|
||||||
# }
|
# }
|
||||||
#settings.inv.item_status = {
|
#settings.inv.item_status = {
|
||||||
# 0: current.messages["NONE"],
|
# 0: "", # current.messages["NONE"] but current.messages is defined only after 000_config.py is executed
|
||||||
# 1: T("Dump"),
|
# 1: T("Dump"),
|
||||||
# 2: T("Sale"),
|
# 2: T("Sale"),
|
||||||
# 3: T("Reject"),
|
# 3: T("Reject"),
|
||||||
@ -1168,9 +1170,8 @@ def config(settings):
|
|||||||
settings.modules = OrderedDict([
|
settings.modules = OrderedDict([
|
||||||
# Core modules which shouldn't be disabled
|
# Core modules which shouldn't be disabled
|
||||||
("default", Storage(
|
("default", Storage(
|
||||||
name_nice = T("Home"),
|
name_nice = T("Default"),
|
||||||
restricted = False, # Use ACLs to control access to this module
|
restricted = False, # Use ACLs to control access to this module
|
||||||
access = None, # All Users (inc Anonymous) can see this module in the default menu & access the controller
|
|
||||||
module_type = None # This item is not shown in the menu
|
module_type = None # This item is not shown in the menu
|
||||||
)),
|
)),
|
||||||
("admin", Storage(
|
("admin", Storage(
|
||||||
@ -1211,7 +1212,7 @@ def config(settings):
|
|||||||
module_type = None,
|
module_type = None,
|
||||||
)),
|
)),
|
||||||
("translate", Storage(
|
("translate", Storage(
|
||||||
name_nice = T("Translation Functionality"),
|
name_nice = T("Translation"),
|
||||||
#description = "Selective translation of strings based on module.",
|
#description = "Selective translation of strings based on module.",
|
||||||
module_type = None,
|
module_type = None,
|
||||||
)),
|
)),
|
||||||
@ -1281,7 +1282,6 @@ def config(settings):
|
|||||||
#("proc", Storage(
|
#("proc", Storage(
|
||||||
# name_nice = T("Procurement"),
|
# name_nice = T("Procurement"),
|
||||||
# #description = "Ordering & Purchasing of Goods & Services",
|
# #description = "Ordering & Purchasing of Goods & Services",
|
||||||
# restricted = True,
|
|
||||||
# module_type = 10
|
# module_type = 10
|
||||||
#)),
|
#)),
|
||||||
("asset", Storage(
|
("asset", Storage(
|
||||||
@ -1309,47 +1309,11 @@ def config(settings):
|
|||||||
restricted = True,
|
restricted = True,
|
||||||
module_type = 2
|
module_type = 2
|
||||||
)),
|
)),
|
||||||
#("survey", Storage(
|
("stats", Storage(
|
||||||
# name_nice = T("Surveys"),
|
name_nice = T("Statistics"),
|
||||||
# #description = "Create, enter, and manage surveys.",
|
#description = "Manages statistics",
|
||||||
# restricted = True,
|
|
||||||
# module_type = 5,
|
|
||||||
#)),
|
|
||||||
("dc", Storage(
|
|
||||||
name_nice = T("Assessments"),
|
|
||||||
#description = "Data collection tool",
|
|
||||||
restricted = True,
|
restricted = True,
|
||||||
module_type = 5
|
module_type = None,
|
||||||
)),
|
|
||||||
("cr", Storage(
|
|
||||||
name_nice = T("Shelters"),
|
|
||||||
#description = "Tracks the location, capacity and breakdown of victims in Shelters",
|
|
||||||
restricted = True,
|
|
||||||
module_type = 9
|
|
||||||
)),
|
|
||||||
("hms", Storage(
|
|
||||||
name_nice = T("Hospitals"),
|
|
||||||
#description = "Helps to monitor status of hospitals",
|
|
||||||
restricted = True,
|
|
||||||
module_type = 9
|
|
||||||
)),
|
|
||||||
#("disease", Storage(
|
|
||||||
# name_nice = T("Disease Tracking"),
|
|
||||||
# #description = "Helps to track cases and trace contacts in disease outbreaks",
|
|
||||||
# restricted = True,
|
|
||||||
# module_type = 10
|
|
||||||
#)),
|
|
||||||
#("br", Storage(
|
|
||||||
# name_nice = T("Beneficiary Registry"),
|
|
||||||
# #description = "Beneficiary Registry and Case Management",
|
|
||||||
# restricted = True,
|
|
||||||
# module_type = 10,
|
|
||||||
#)),
|
|
||||||
("dvr", Storage(
|
|
||||||
name_nice = T("Disaster Victim Registry"),
|
|
||||||
#description = "Allow affected individuals & households to register to receive compensation and distributions",
|
|
||||||
restricted = True,
|
|
||||||
module_type = 10,
|
|
||||||
)),
|
)),
|
||||||
("event", Storage(
|
("event", Storage(
|
||||||
name_nice = T("Events"),
|
name_nice = T("Events"),
|
||||||
@ -1357,16 +1321,29 @@ def config(settings):
|
|||||||
restricted = True,
|
restricted = True,
|
||||||
module_type = 10,
|
module_type = 10,
|
||||||
)),
|
)),
|
||||||
("transport", Storage(
|
("br", Storage(
|
||||||
name_nice = T("Transport"),
|
name_nice = T("Beneficiary Registry"),
|
||||||
|
#description = "Allow affected individuals & households to register to receive compensation and distributions",
|
||||||
restricted = True,
|
restricted = True,
|
||||||
module_type = 10,
|
module_type = 10,
|
||||||
)),
|
)),
|
||||||
("stats", Storage(
|
("cr", Storage(
|
||||||
name_nice = T("Statistics"),
|
name_nice = T("Shelters"),
|
||||||
#description = "Manages statistics",
|
#description = "Tracks the location, capacity and breakdown of victims in Shelters",
|
||||||
restricted = True,
|
restricted = True,
|
||||||
module_type = None,
|
module_type = 9
|
||||||
|
)),
|
||||||
|
("dc", Storage(
|
||||||
|
name_nice = T("Assessments"),
|
||||||
|
#description = "Data collection tool",
|
||||||
|
restricted = True,
|
||||||
|
module_type = 5
|
||||||
|
)),
|
||||||
|
("hms", Storage(
|
||||||
|
name_nice = T("Hospitals"),
|
||||||
|
#description = "Helps to monitor status of hospitals",
|
||||||
|
restricted = True,
|
||||||
|
module_type = 9
|
||||||
)),
|
)),
|
||||||
("member", Storage(
|
("member", Storage(
|
||||||
name_nice = T("Members"),
|
name_nice = T("Members"),
|
||||||
@ -1386,45 +1363,28 @@ def config(settings):
|
|||||||
restricted = True,
|
restricted = True,
|
||||||
module_type = 10,
|
module_type = 10,
|
||||||
)),
|
)),
|
||||||
# Deprecated: Replaced by event
|
#("disease", Storage(
|
||||||
#("irs", Storage(
|
# name_nice = T("Disease Tracking"),
|
||||||
# name_nice = T("Incidents"),
|
# #description = "Helps to track cases and trace contacts in disease outbreaks",
|
||||||
# #description = "Incident Reporting System",
|
|
||||||
# restricted = True,
|
|
||||||
# module_type = 10
|
# module_type = 10
|
||||||
#)),
|
#)),
|
||||||
("dvi", Storage(
|
|
||||||
name_nice = T("Disaster Victim Identification"),
|
|
||||||
#description = "Disaster Victim Identification",
|
|
||||||
restricted = True,
|
|
||||||
module_type = 10,
|
|
||||||
#access = "|DVI|", # Only users with the DVI role can see this module in the default menu & access the controller
|
|
||||||
)),
|
|
||||||
("edu", Storage(
|
("edu", Storage(
|
||||||
name_nice = T("Schools"),
|
name_nice = T("Schools"),
|
||||||
#description = "Helps to monitor status of schools",
|
#description = "Helps to monitor status of schools",
|
||||||
restricted = True,
|
restricted = True,
|
||||||
module_type = 10
|
module_type = 10
|
||||||
)),
|
)),
|
||||||
("mpr", Storage(
|
|
||||||
name_nice = T("Missing Person Registry"),
|
|
||||||
#description = "Helps to report and search for missing persons",
|
|
||||||
restricted = True,
|
|
||||||
module_type = 10,
|
|
||||||
)),
|
|
||||||
# https://github.com/sahana/eden/issues/1562
|
|
||||||
#("vulnerability", Storage(
|
|
||||||
# name_nice = T("Vulnerability"),
|
|
||||||
# #description = "Manages vulnerability indicators",
|
|
||||||
# restricted = True,
|
|
||||||
# module_type = 10,
|
|
||||||
#)),
|
|
||||||
("fire", Storage(
|
("fire", Storage(
|
||||||
name_nice = T("Fire Stations"),
|
name_nice = T("Fire Stations"),
|
||||||
#description = "Fire Station Management",
|
#description = "Fire Station Management",
|
||||||
restricted = True,
|
restricted = True,
|
||||||
module_type = 1,
|
module_type = 1,
|
||||||
)),
|
)),
|
||||||
|
("transport", Storage(
|
||||||
|
name_nice = T("Transport"),
|
||||||
|
restricted = True,
|
||||||
|
module_type = 10,
|
||||||
|
)),
|
||||||
("water", Storage(
|
("water", Storage(
|
||||||
name_nice = T("Water"),
|
name_nice = T("Water"),
|
||||||
#description = "Flood Gauges show water levels in various parts of the country",
|
#description = "Flood Gauges show water levels in various parts of the country",
|
||||||
@ -1449,6 +1409,36 @@ def config(settings):
|
|||||||
restricted = True,
|
restricted = True,
|
||||||
module_type = 10,
|
module_type = 10,
|
||||||
)),
|
)),
|
||||||
|
# Vulnerability temporarily disabled - https://github.com/sahana/eden/issues/1562
|
||||||
|
#("vulnerability", Storage(
|
||||||
|
# name_nice = T("Vulnerability"),
|
||||||
|
# #description = "Manages vulnerability indicators",
|
||||||
|
# module_type = 10,
|
||||||
|
# )),
|
||||||
|
("work", Storage(
|
||||||
|
name_nice = T("Jobs"),
|
||||||
|
#description = "Simple Volunteer Jobs Management",
|
||||||
|
restricted = False,
|
||||||
|
module_type = None,
|
||||||
|
)),
|
||||||
|
# Deprecated: Replaced by BR
|
||||||
|
#("dvr", Storage(
|
||||||
|
# name_nice = T("Beneficiary Registry"),
|
||||||
|
# #description = "Disaster Victim Registry",
|
||||||
|
# module_type = 10
|
||||||
|
#)),
|
||||||
|
# Deprecated: Replaced by event
|
||||||
|
#("irs", Storage(
|
||||||
|
# name_nice = T("Incidents"),
|
||||||
|
# #description = "Incident Reporting System",
|
||||||
|
# module_type = 10
|
||||||
|
#)),
|
||||||
|
# Deprecated: Replaced by DC
|
||||||
|
#("survey", Storage(
|
||||||
|
# name_nice = T("Surveys"),
|
||||||
|
# #description = "Create, enter, and manage surveys.",
|
||||||
|
# module_type = 5,
|
||||||
|
#)),
|
||||||
# These are specialist modules
|
# These are specialist modules
|
||||||
("cap", Storage(
|
("cap", Storage(
|
||||||
name_nice = T("CAP"),
|
name_nice = T("CAP"),
|
||||||
@ -1456,11 +1446,23 @@ def config(settings):
|
|||||||
restricted = True,
|
restricted = True,
|
||||||
module_type = 10,
|
module_type = 10,
|
||||||
)),
|
)),
|
||||||
|
("dvi", Storage(
|
||||||
|
name_nice = T("Disaster Victim Identification"),
|
||||||
|
#description = "Disaster Victim Identification",
|
||||||
|
restricted = True,
|
||||||
|
module_type = 10,
|
||||||
|
#access = "|DVI|", # Only users with the DVI role can see this module in the default menu & access the controller
|
||||||
|
)),
|
||||||
|
("mpr", Storage(
|
||||||
|
name_nice = T("Missing Person Registry"),
|
||||||
|
#description = "Helps to report and search for missing persons",
|
||||||
|
restricted = True,
|
||||||
|
module_type = 10,
|
||||||
|
)),
|
||||||
# Requires RPy2 & PostgreSQL
|
# Requires RPy2 & PostgreSQL
|
||||||
#("climate", Storage(
|
#("climate", Storage(
|
||||||
# name_nice = T("Climate"),
|
# name_nice = T("Climate"),
|
||||||
# #description = "Climate data portal",
|
# #description = "Climate data portal",
|
||||||
# restricted = True,
|
|
||||||
# module_type = 10,
|
# module_type = 10,
|
||||||
#)),
|
#)),
|
||||||
("delphi", Storage(
|
("delphi", Storage(
|
||||||
@ -1473,7 +1475,6 @@ def config(settings):
|
|||||||
#("building", Storage(
|
#("building", Storage(
|
||||||
# name_nice = T("Building Assessments"),
|
# name_nice = T("Building Assessments"),
|
||||||
# #description = "Building Safety Assessments",
|
# #description = "Building Safety Assessments",
|
||||||
# restricted = True,
|
|
||||||
# module_type = 10,
|
# module_type = 10,
|
||||||
#)),
|
#)),
|
||||||
# Deprecated by Surveys module
|
# Deprecated by Surveys module
|
||||||
@ -1481,13 +1482,11 @@ def config(settings):
|
|||||||
#("assess", Storage(
|
#("assess", Storage(
|
||||||
# name_nice = T("Assessments"),
|
# name_nice = T("Assessments"),
|
||||||
# #description = "Rapid Assessments & Flexible Impact Assessments",
|
# #description = "Rapid Assessments & Flexible Impact Assessments",
|
||||||
# restricted = True,
|
|
||||||
# module_type = 10,
|
# module_type = 10,
|
||||||
#)),
|
#)),
|
||||||
#("impact", Storage(
|
#("impact", Storage(
|
||||||
# name_nice = T("Impacts"),
|
# name_nice = T("Impacts"),
|
||||||
# #description = "Used by Assess",
|
# #description = "Used by Assess",
|
||||||
# restricted = True,
|
|
||||||
# module_type = None,
|
# module_type = None,
|
||||||
#)),
|
#)),
|
||||||
#("ocr", Storage(
|
#("ocr", Storage(
|
||||||
@ -1496,12 +1495,6 @@ def config(settings):
|
|||||||
# restricted = False,
|
# restricted = False,
|
||||||
# module_type = None,
|
# module_type = None,
|
||||||
#)),
|
#)),
|
||||||
("work", Storage(
|
|
||||||
name_nice = T("Jobs"),
|
|
||||||
#description = "Simple Volunteer Jobs Management",
|
|
||||||
restricted = False,
|
|
||||||
module_type = None,
|
|
||||||
)),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
# END =========================================================================
|
# END =========================================================================
|
||||||
|
@ -13,6 +13,8 @@ plugins/jquery.tagit.css
|
|||||||
ui/core.css
|
ui/core.css
|
||||||
ui/autocomplete.css
|
ui/autocomplete.css
|
||||||
ui/button.css
|
ui/button.css
|
||||||
|
#ui/checkboxradio.css
|
||||||
|
#ui/controlgroup.css
|
||||||
ui/datepicker.css
|
ui/datepicker.css
|
||||||
ui/dialog.css
|
ui/dialog.css
|
||||||
# Needed for Delphi
|
# Needed for Delphi
|
||||||
@ -29,6 +31,7 @@ ui/tabs.css
|
|||||||
ui/fgtimepicker.css
|
ui/fgtimepicker.css
|
||||||
ui/multiselect.css
|
ui/multiselect.css
|
||||||
ui/timepicker-addon.css
|
ui/timepicker-addon.css
|
||||||
|
#calendars/ui.calendars.picker.css
|
||||||
#calendars/ui-smoothness.calendars.picker.css
|
#calendars/ui-smoothness.calendars.picker.css
|
||||||
../themes/foundation/jquery-ui.theme.css
|
../themes/foundation/jquery-ui.theme.css
|
||||||
gis/style.css
|
gis/style.css
|
||||||
|
@ -10,8 +10,8 @@ Epidemic,,
|
|||||||
Explosion,,
|
Explosion,,
|
||||||
Extreme Winter Conditions,,
|
Extreme Winter Conditions,,
|
||||||
Fire,,
|
Fire,,
|
||||||
Flash Floods,,
|
Flash Flood,,
|
||||||
Floods,,
|
Flood,,
|
||||||
Food Security,,
|
Food Security,,
|
||||||
Heat Wave,,
|
Heat Wave,,
|
||||||
Land Slide,,
|
Land Slide,,
|
||||||
|
|
@ -26,7 +26,7 @@ SITE_DEFAULT,,"State / Province","County / District","City / Town / Village","Vi
|
|||||||
,CO,Department,Municipality,Corregimiento,
|
,CO,Department,Municipality,Corregimiento,
|
||||||
,CR,Province,Canton,District,
|
,CR,Province,Canton,District,
|
||||||
,CU,Province,Municipality,
|
,CU,Province,Municipality,
|
||||||
,DE,"Federal State","Rural District / District","Town / Municipality",Village,
|
,DE,"Federal State","Rural District / District","Town / Municipality",Locality,
|
||||||
,DO,Province,Municipality,,,,
|
,DO,Province,Municipality,,,,
|
||||||
,EC,Province,Canton,Parish,,,
|
,EC,Province,Canton,Parish,,,
|
||||||
,FI,Regional State Administrative Agency,Region,Sub-region,Municipality,,
|
,FI,Regional State Administrative Agency,Region,Sub-region,Municipality,,
|
||||||
@ -44,12 +44,14 @@ SITE_DEFAULT,,"State / Province","County / District","City / Town / Village","Vi
|
|||||||
,IN,State,District,Sub-District,,,
|
,IN,State,District,Sub-District,,,
|
||||||
,IQ,Province,District,,,,
|
,IQ,Province,District,,,,
|
||||||
,IT,Region,Province,Commune,,,
|
,IT,Region,Province,Commune,,,
|
||||||
|
,JO,Governorate,District,Subdistrict,,,
|
||||||
,KE,County,Constituency,Location,SubLocation,
|
,KE,County,Constituency,Location,SubLocation,
|
||||||
,KG,"Oblast / State City","District (Rayon) / Oblast City","Town / Village Group",Village,,
|
,KG,"Oblast / State City","District (Rayon) / Oblast City","Town / Village Group",Village,,
|
||||||
,KH,Province,District,Commune,Village,
|
,KH,Province,District,Commune,Village,
|
||||||
,KI,Island Council,,,,
|
,KI,Island Council,,,,
|
||||||
,KZ,Province,District,,,,
|
,KZ,Province,District,,,,
|
||||||
,LA,Province,District,Village,,,
|
,LA,Province,District,Village,,,
|
||||||
|
,LB,Governorate,District,,,,
|
||||||
,LK,Province,District,Divisional Secretariat,Grama Niladhari,,
|
,LK,Province,District,Divisional Secretariat,Grama Niladhari,,
|
||||||
,LR,County,District,Clan,,,
|
,LR,County,District,Clan,,,
|
||||||
,LT,County,Municipality,Eldership,,,
|
,LT,County,Municipality,Eldership,,,
|
||||||
@ -70,6 +72,8 @@ SITE_DEFAULT,,"State / Province","County / District","City / Town / Village","Vi
|
|||||||
,PG,Province,District,LLG,Village,,
|
,PG,Province,District,LLG,Village,,
|
||||||
,PH,Region,Province,"City / Municipality",Barangay,,
|
,PH,Region,Province,"City / Municipality",Barangay,,
|
||||||
,PK,Province,District,Tehsil,Union Council,Village,
|
,PK,Province,District,Tehsil,Union Council,Village,
|
||||||
|
,PL,Province,County,Municipality,,,
|
||||||
|
,PR,Municipality,Barrio,,,,
|
||||||
,PY,Department,District,"City / Town / Village",,,
|
,PY,Department,District,"City / Town / Village",,,
|
||||||
,RS,District,"Municipality / City",,,,
|
,RS,District,"Municipality / City",,,,
|
||||||
,SB,Province,Ward,,
|
,SB,Province,Ward,,
|
||||||
@ -77,18 +81,18 @@ SITE_DEFAULT,,"State / Province","County / District","City / Town / Village","Vi
|
|||||||
,SG,Region,,,
|
,SG,Region,,,
|
||||||
,SL,Province,District,Chiefdom,,,
|
,SL,Province,District,Chiefdom,,,
|
||||||
,SV,Department,Municipality,
|
,SV,Department,Municipality,
|
||||||
|
,SY,Governorate,District,Subdistrict
|
||||||
,TD,Region,Department,Sub-Prefecture,Canton,
|
,TD,Region,Department,Sub-Prefecture,Canton,
|
||||||
,TH,Province,District,Sub-District,,,
|
,TH,Province,District,Sub-District,,,
|
||||||
,TJ,Province,District,Jamoat,Village,,
|
,TJ,Province,District,Jamoat,Village,,
|
||||||
,TL,District,SubDistrict,Suco,Aldeia,,
|
,TL,District,SubDistrict,Suco,Aldeia,,
|
||||||
,TM,Province,District,,,,
|
,TM,Province,District,,,,
|
||||||
,TO,Island Group,District,,,,
|
,TO,Island Group,District,,,,
|
||||||
,TR,City,Town,District,,,
|
,TR,Province,District / Town,Neighborhood / Village,,,
|
||||||
,TV,Island Council,,,,
|
,TV,Island Council,,,,
|
||||||
,PR,Municipality,Barrio,,,,
|
|
||||||
,US,State,County,City,Neighborhood,,False
|
,US,State,County,City,Neighborhood,,False
|
||||||
,UZ,Province,District,,,,
|
,UZ,Province,District,,,,
|
||||||
,VU,Province,Area Council,,
|
|
||||||
,VN,Province,District,Commune,,,
|
,VN,Province,District,Commune,,,
|
||||||
|
,VU,Province,Area Council,,
|
||||||
,XK,District,Municipality,,,,
|
,XK,District,Municipality,,,,
|
||||||
,YE,Governorate,District,Sub-District,Village,,
|
,YE,Governorate,District,Sub-District,Village,,
|
||||||
|
Can't render this file because it has a wrong number of fields in line 6.
|
@ -12,4 +12,4 @@ Population Density 2010 (Alternate server),GPWv3,http://sedac.ciesin.columbia.ed
|
|||||||
Population Density 2000 (Persons per km2),GRUMPv1,http://beta.sedac.ciesin.columbia.edu:8080/geoserver/wms?,grump-v1:grump-v1-population-density_2000 ,Population,WGS84,True,False,False,True,0.8,image/png,False,,,
|
Population Density 2000 (Persons per km2),GRUMPv1,http://beta.sedac.ciesin.columbia.edu:8080/geoserver/wms?,grump-v1:grump-v1-population-density_2000 ,Population,WGS84,True,False,False,True,0.8,image/png,False,,,
|
||||||
Precipitation forecast,,http://geo.weatheroffice.gc.ca/geomet/?,GDPS.ETA_PR,Weather,,True,False,False,True,0.4,image/png,False,http://geo.weatheroffice.gc.ca/geomet/?LANG=E%26SERVICE=WMS%26VERSION=1.1.1%26REQUEST=GetLegendGraphic%26STYLE=PRECIPMM%26LAYER=GDPS.ETA_PR%26format=image/png,PRECIPMM,
|
Precipitation forecast,,http://geo.weatheroffice.gc.ca/geomet/?,GDPS.ETA_PR,Weather,,True,False,False,True,0.4,image/png,False,http://geo.weatheroffice.gc.ca/geomet/?LANG=E%26SERVICE=WMS%26VERSION=1.1.1%26REQUEST=GetLegendGraphic%26STYLE=PRECIPMM%26LAYER=GDPS.ETA_PR%26format=image/png,PRECIPMM,
|
||||||
Cloud forecast,,http://geo.weatheroffice.gc.ca/geomet/?,GDPS.ETA_NT,Weather,,True,False,False,True,0.4,image/png,False,http://geo.weatheroffice.gc.ca/geomet/?LANG=E%26SERVICE=WMS%26VERSION=1.1.1%26REQUEST=GetLegendGraphic%26STYLE=CLOUD%26LAYER=GDPS.ETA_NT%26format=image/png,CLOUD,
|
Cloud forecast,,http://geo.weatheroffice.gc.ca/geomet/?,GDPS.ETA_NT,Weather,,True,False,False,True,0.4,image/png,False,http://geo.weatheroffice.gc.ca/geomet/?LANG=E%26SERVICE=WMS%26VERSION=1.1.1%26REQUEST=GetLegendGraphic%26STYLE=CLOUD%26LAYER=GDPS.ETA_NT%26format=image/png,CLOUD,
|
||||||
Sea Level: Rise of 2m,Data from https://www.cresis.ku.edu/data/sea-level-rise-maps,http://lacrmt.sahanafoundation.org:8080/geoserver/wms?,lacrmt:inund2,Hazards,,True,False,False,True,0.4,image/png,False,,,
|
Sea Level: Rise of 2m,Data from https://www.cresis.ku.edu/data/sea-level-rise-maps,http://lacrmt.sahanafoundation.org:8080/geoserver/wms?,lacrmt:inund2,Hazards,,False,False,False,True,0.4,image/png,False,,,
|
||||||
|
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
Template-specific Monitoring Tasks are defined here.
|
Template-specific Monitoring Tasks are defined here.
|
||||||
|
|
||||||
@copyright: 2014-2019 (c) Sahana Software Foundation
|
@copyright: 2014-2020 (c) Sahana Software Foundation
|
||||||
@license: MIT
|
@license: MIT
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
Permission is hereby granted, free of charge, to any person
|
||||||
@ -33,11 +33,26 @@
|
|||||||
__all__ = ("S3Monitor",)
|
__all__ = ("S3Monitor",)
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
from gluon import current
|
from gluon import current
|
||||||
#from gluon.tools import fetch
|
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
except ImportError:
|
||||||
|
REQUESTS = None
|
||||||
|
else:
|
||||||
|
REQUESTS = True
|
||||||
|
|
||||||
|
INSTANCE_TYPES = {"prod": 1,
|
||||||
|
"setup": 2,
|
||||||
|
"test": 3,
|
||||||
|
"demo": 4,
|
||||||
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
class S3Monitor(object):
|
class S3Monitor(object):
|
||||||
@ -45,11 +60,366 @@ class S3Monitor(object):
|
|||||||
Monitoring Check Scripts
|
Monitoring Check Scripts
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def diskspace(task_id, run_id):
|
||||||
|
"""
|
||||||
|
Test the free diskspace
|
||||||
|
"""
|
||||||
|
|
||||||
|
db = current.db
|
||||||
|
s3db = current.s3db
|
||||||
|
|
||||||
|
# Read the Task Options
|
||||||
|
ttable = s3db.setup_monitor_task
|
||||||
|
task = db(ttable.id == task_id).select(ttable.options,
|
||||||
|
ttable.server_id,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
options = task.options or {}
|
||||||
|
options_get = options.get
|
||||||
|
|
||||||
|
partition = options_get("partition", "/") # Root Partition by default
|
||||||
|
space_min = options_get("space_min", 1000000000) # 1 Gb
|
||||||
|
|
||||||
|
stable = s3db.setup_server
|
||||||
|
server = db(stable.id == task.server_id).select(stable.host_ip,
|
||||||
|
stable.remote_user,
|
||||||
|
stable.private_key,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if server.host_ip == "127.0.0.1":
|
||||||
|
result = os.statvfs(partition)
|
||||||
|
space = result.f_bavail * result.f_frsize
|
||||||
|
percent = float(result.f_bavail) / float(result.f_blocks) * 100
|
||||||
|
if space < space_min:
|
||||||
|
return {"result": "Warning: %s free (%d%%)" % \
|
||||||
|
(_bytes_to_size_string(space), percent),
|
||||||
|
"status": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"result": "OK. %s free (%d%%)" % \
|
||||||
|
(_bytes_to_size_string(space), percent),
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh = _ssh(server)
|
||||||
|
if isinstance(ssh, dict):
|
||||||
|
# We failed to login
|
||||||
|
return ssh
|
||||||
|
|
||||||
|
command = "import os;result=os.statvfs('%s');print(result.f_bavail);print(result.f_frsize);print(result.f_blocks)" % partition
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('python -c "%s"' % command)
|
||||||
|
outlines = stdout.readlines()
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
f_bavail = int(outlines[0])
|
||||||
|
f_frsize = int(outlines[1])
|
||||||
|
f_blocks = int(outlines[2])
|
||||||
|
|
||||||
|
space = f_bavail * f_frsize
|
||||||
|
percent = float(f_bavail) / float(f_blocks) * 100
|
||||||
|
if space < space_min:
|
||||||
|
return {"result": "Warning: %s free (%d%%)" % \
|
||||||
|
(_bytes_to_size_string(space), percent),
|
||||||
|
"status": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"result": "OK. %s free (%d%%)" % \
|
||||||
|
(_bytes_to_size_string(space), percent),
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def eden(task_id, run_id):
|
||||||
|
"""
|
||||||
|
Test that we can retrieve the public_url, which checks:
|
||||||
|
- DNS must resolve to correct IP
|
||||||
|
- Server must be up
|
||||||
|
- Firewall cannot be blocking
|
||||||
|
- Web server is running
|
||||||
|
- UWSGI is running
|
||||||
|
- Database is running
|
||||||
|
- Eden can connect to Database
|
||||||
|
"""
|
||||||
|
|
||||||
|
if REQUESTS is None:
|
||||||
|
return {"result": "Critical: Requests library not installed",
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
db = current.db
|
||||||
|
s3db = current.s3db
|
||||||
|
|
||||||
|
# Read the Task Options
|
||||||
|
ttable = s3db.setup_monitor_task
|
||||||
|
task = db(ttable.id == task_id).select(ttable.options,
|
||||||
|
ttable.deployment_id,
|
||||||
|
ttable.server_id,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
options = task.options or {}
|
||||||
|
options_get = options.get
|
||||||
|
|
||||||
|
appname = options_get("appname", "eden")
|
||||||
|
public_url = options_get("public_url")
|
||||||
|
timeout = options_get("timeout", 60) # 60s (default is no timeout!)
|
||||||
|
|
||||||
|
if not public_url:
|
||||||
|
deployment_id = task.deployment_id
|
||||||
|
if deployment_id:
|
||||||
|
# Read from the instance
|
||||||
|
itable = s3db.setup_instance
|
||||||
|
query = (itable.deployment_id == deployment_id) & \
|
||||||
|
(itable.type == 1)
|
||||||
|
instance = db(query).select(itable.url,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
if instance:
|
||||||
|
public_url = instance.url
|
||||||
|
if not public_url:
|
||||||
|
# Use the server name
|
||||||
|
stable = s3db.setup_server
|
||||||
|
server = db(stable.id == task.server_id).select(stable.name,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
public_url = "https://%s" % server.name
|
||||||
|
|
||||||
|
url = "%(public_url)s/%(appname)s/default/public_url" % {"appname": appname,
|
||||||
|
"public_url": public_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(url, timeout = timeout) # verify=True
|
||||||
|
except requests.exceptions.SSLError:
|
||||||
|
# e.g. Expired Certificate
|
||||||
|
import traceback
|
||||||
|
tb_parts = sys.exc_info()
|
||||||
|
tb_text = "".join(traceback.format_exception(tb_parts[0],
|
||||||
|
tb_parts[1],
|
||||||
|
tb_parts[2]))
|
||||||
|
return {"result": "Critical: SSL Error\n\n%s" % tb_text,
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
import traceback
|
||||||
|
tb_parts = sys.exc_info()
|
||||||
|
tb_text = "".join(traceback.format_exception(tb_parts[0],
|
||||||
|
tb_parts[1],
|
||||||
|
tb_parts[2]))
|
||||||
|
return {"result": "Critical: Timeout Error\n\n%s" % tb_text,
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
except requests.exceptions.TooManyRedirects:
|
||||||
|
import traceback
|
||||||
|
tb_parts = sys.exc_info()
|
||||||
|
tb_text = "".join(traceback.format_exception(tb_parts[0],
|
||||||
|
tb_parts[1],
|
||||||
|
tb_parts[2]))
|
||||||
|
return {"result": "Critical: TooManyRedirects Error\n\n%s" % tb_text,
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
# e.g. DNS Error
|
||||||
|
import traceback
|
||||||
|
tb_parts = sys.exc_info()
|
||||||
|
tb_text = "".join(traceback.format_exception(tb_parts[0],
|
||||||
|
tb_parts[1],
|
||||||
|
tb_parts[2]))
|
||||||
|
return {"result": "Critical: Connection Error\n\n%s" % tb_text,
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
return {"result": "Critical: HTTP Error. Status = %s" % r.status_code,
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.text != public_url:
|
||||||
|
return {"result": "Critical: Page returned '%s' instead of '%s'" % \
|
||||||
|
(r.text, public_url),
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
latency = int(r.elapsed.microseconds / 1000)
|
||||||
|
latency_max = options_get("latency_max", 2000) # 2 seconds
|
||||||
|
if latency > latency_max:
|
||||||
|
return {"result": "Warning: Latency of %s exceeded threshold of %s." % \
|
||||||
|
(latency, latency_max),
|
||||||
|
"status": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"result": "OK. Latency: %s" % latency,
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def email_round_trip(task_id, run_id):
|
||||||
|
"""
|
||||||
|
Check that a Mailbox is being Polled & Parsed OK and can send replies
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Read the Task Options
|
||||||
|
ttable = current.s3db.setup_monitor_task
|
||||||
|
task = current.db(ttable.id == task_id).select(ttable.options,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
options = task.options or {}
|
||||||
|
options_get = options.get
|
||||||
|
|
||||||
|
to = options_get("to", None)
|
||||||
|
if not to:
|
||||||
|
return {"result": "Critical: No recipient address specified",
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
subject = options_get("subject", "")
|
||||||
|
message = options_get("message", "")
|
||||||
|
reply_to = options_get("reply_to")
|
||||||
|
if not reply_to:
|
||||||
|
# Use the outbound email address
|
||||||
|
reply_to = current.deployment_settings.get_mail_sender()
|
||||||
|
if not reply_to:
|
||||||
|
return {"result": "Critical: No reply_to specified",
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Append the run_id for the remote parser to identify as a monitoring message & return to us to be able to match the run
|
||||||
|
message = "%s\n%s" % (message, ":run_id:%s:" % run_id)
|
||||||
|
|
||||||
|
# Append the reply_to address for the remote parser
|
||||||
|
message = "%s\n%s" % (message, ":reply_to:%s:" % reply_to)
|
||||||
|
|
||||||
|
# Send the Email
|
||||||
|
result = current.msg.send_email(to,
|
||||||
|
subject,
|
||||||
|
message,
|
||||||
|
reply_to = reply_to)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
# Schedule a task to see if the reply has arrived
|
||||||
|
wait = options_get("wait", 60) # Default = 60 minutes
|
||||||
|
start_time = datetime.datetime.utcnow() + \
|
||||||
|
datetime.timedelta(minutes = wait)
|
||||||
|
current.s3task.schedule_task("setup_monitor_check_email_reply",
|
||||||
|
args = [run_id],
|
||||||
|
start_time = start_time,
|
||||||
|
timeout = 300, # seconds
|
||||||
|
repeats = 1 # one-time
|
||||||
|
)
|
||||||
|
return {"result": "OK so far: Waiting for Reply",
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"result": "Critical: Unable to send Email",
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def http(task_id, run_id):
|
||||||
|
"""
|
||||||
|
Test that HTTP is accessible
|
||||||
|
"""
|
||||||
|
|
||||||
|
if REQUESTS is None:
|
||||||
|
return {"result": "Critical: Requests library not installed",
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def https(task_id, run_id):
|
||||||
|
"""
|
||||||
|
Test that HTTP is accessible
|
||||||
|
"""
|
||||||
|
|
||||||
|
if REQUESTS is None:
|
||||||
|
return {"result": "Critical: Requests library not installed",
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def load_average(task_id, run_id):
|
||||||
|
"""
|
||||||
|
Test the Load Average
|
||||||
|
"""
|
||||||
|
|
||||||
|
db = current.db
|
||||||
|
s3db = current.s3db
|
||||||
|
|
||||||
|
# Read the Task Options
|
||||||
|
ttable = s3db.setup_monitor_task
|
||||||
|
task = db(ttable.id == task_id).select(ttable.options,
|
||||||
|
ttable.server_id,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
options = task.options or {}
|
||||||
|
options_get = options.get
|
||||||
|
|
||||||
|
which = options_get("which", 2) # 15 min average
|
||||||
|
load_max = options_get("load_max", 2)
|
||||||
|
|
||||||
|
stable = s3db.setup_server
|
||||||
|
server = db(stable.id == task.server_id).select(stable.host_ip,
|
||||||
|
stable.remote_user,
|
||||||
|
stable.private_key,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if server.host_ip == "127.0.0.1":
|
||||||
|
loadavg = os.getloadavg()
|
||||||
|
if loadavg[which] > load_max:
|
||||||
|
return {"result": "Warning: load average: %0.2f, %0.2f, %0.2f" % \
|
||||||
|
(loadavg[0], loadavg[1], loadavg[2]),
|
||||||
|
"status": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"result": "OK. load average: %0.2f, %0.2f, %0.2f" % \
|
||||||
|
(loadavg[0], loadavg[1], loadavg[2]),
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh = _ssh(server)
|
||||||
|
if isinstance(ssh, dict):
|
||||||
|
# We failed to login
|
||||||
|
return ssh
|
||||||
|
|
||||||
|
command = "import os;loadavg=os.getloadavg();print(loadavg[0]);print(loadavg[1]);print(loadavg[2])"
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('python -c "%s"' % command)
|
||||||
|
outlines = stdout.readlines()
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
loadavg = {0: float(outlines[0]),
|
||||||
|
1: float(outlines[1]),
|
||||||
|
2: float(outlines[2]),
|
||||||
|
}
|
||||||
|
|
||||||
|
if loadavg[which] > load_max:
|
||||||
|
return {"result": "Warning: load average: %0.2f, %0.2f, %0.2f" % \
|
||||||
|
(loadavg[0], loadavg[1], loadavg[2]),
|
||||||
|
"status": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"result": "OK. load average: %0.2f, %0.2f, %0.2f" % \
|
||||||
|
(loadavg[0], loadavg[1], loadavg[2]),
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ping(task_id, run_id):
|
def ping(task_id, run_id):
|
||||||
"""
|
"""
|
||||||
Ping a server
|
ICMP Ping a server
|
||||||
|
- NB AWS instances don't respond to ICMP Ping by default, but this can be enabled in the Firewall
|
||||||
"""
|
"""
|
||||||
|
|
||||||
s3db = current.s3db
|
s3db = current.s3db
|
||||||
@ -66,83 +436,323 @@ class S3Monitor(object):
|
|||||||
host_ip = row.host_ip
|
host_ip = row.host_ip
|
||||||
|
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output("ping -{} 1 {}".format("n" if platform.system().lower == "windows" else "c", host_ip), shell=True)
|
# @ToDo: Replace with socket?
|
||||||
except Exception as e:
|
# - we want to see the latency
|
||||||
# Critical: Ping failed
|
if platform.system().lower == "windows":
|
||||||
return 3
|
_format = "n"
|
||||||
else:
|
else:
|
||||||
# OK
|
_format = "c"
|
||||||
return 1
|
output = subprocess.check_output("ping -{} 1 {}".format(_format,
|
||||||
|
host_ip),
|
||||||
|
shell = True)
|
||||||
|
except Exception:
|
||||||
|
import traceback
|
||||||
|
tb_parts = sys.exc_info()
|
||||||
|
tb_text = "".join(traceback.format_exception(tb_parts[0],
|
||||||
|
tb_parts[1],
|
||||||
|
tb_parts[2]))
|
||||||
|
return {"result": "Critical: Ping failed\n\n%s" % tb_text,
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
return {"result": "OK",
|
||||||
#@staticmethod
|
"status": 1,
|
||||||
#def http(task_id, run_id):
|
}
|
||||||
# """
|
|
||||||
# Test that HTTP is accessible
|
|
||||||
# """
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
#@staticmethod
|
|
||||||
#def https(task_id, run_id):
|
|
||||||
# """
|
|
||||||
# Test that HTTPS is accessible
|
|
||||||
# @ToDo: Check that SSL certificate hasn't expired
|
|
||||||
# """
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def email_round_trip(task_id, run_id):
|
def scheduler(task_id, run_id):
|
||||||
"""
|
"""
|
||||||
Check that a Mailbox is being Polled & Parsed OK and can send replies
|
Test whether the scheduler is running
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
db = current.db
|
||||||
|
s3db = current.s3db
|
||||||
|
|
||||||
# Read the Task Options
|
# Read the Task Options
|
||||||
otable = current.s3db.setup_monitor_task_option
|
ttable = s3db.setup_monitor_task
|
||||||
query = (otable.task_id == task_id) & \
|
task = db(ttable.id == task_id).select(ttable.options,
|
||||||
(otable.deleted == False)
|
ttable.server_id,
|
||||||
rows = current.db(query).select(otable.tag,
|
limitby = (0, 1)
|
||||||
otable.value,
|
).first()
|
||||||
)
|
options = task.options or {}
|
||||||
options = dict((row.tag, row.value) for row in rows)
|
options_get = options.get
|
||||||
|
|
||||||
to = options.get("to", None)
|
stable = s3db.setup_server
|
||||||
if not to:
|
server = db(stable.id == task.server_id).select(stable.host_ip,
|
||||||
return False
|
stable.remote_user,
|
||||||
|
stable.private_key,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
|
||||||
subject = options.get("subject", "")
|
earliest = current.request.utcnow - datetime.timedelta(seconds = 900) # 15 minutes
|
||||||
message = options.get("message", "")
|
|
||||||
reply_to = options.get("reply_to")
|
|
||||||
if not reply_to:
|
|
||||||
# Use the outbound email address
|
|
||||||
reply_to = current.deployment_settings.get_mail_sender()
|
|
||||||
if not reply_to:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Append the run_id for the remote parser to identify as a monitoring message & return to us to be able to match the run
|
if server.host_ip == "127.0.0.1":
|
||||||
message = "%s\n%s" % (message, ":run_id:%s:" % run_id)
|
# This shouldn't make much sense as a check, since this won't run if the scheduler has died
|
||||||
|
# - however in practise, it can actually provide useful warning!
|
||||||
|
|
||||||
# Append the reply_to address for the remote parser
|
wtable = s3db.scheduler_worker
|
||||||
message = "%s\n%s" % (message, ":reply_to:%s:" % reply_to)
|
worker = db(wtable.status == "ACTIVE").select(wtable.last_heartbeat,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
|
||||||
# Send the Email
|
error = None
|
||||||
result = current.msg.send_email(to,
|
if worker is None:
|
||||||
subject,
|
error = "Warning: Scheduler not ACTIVE"
|
||||||
message,
|
|
||||||
reply_to=reply_to)
|
|
||||||
|
|
||||||
if result:
|
elif worker.last_heartbeat < earliest:
|
||||||
# Schedule a task to see if the reply has arrived after 1 hour
|
error = "Warning: Scheduler stalled since %s" % worker.last_heartbeat.strftime("%H:%M %a %d %b")
|
||||||
start_time = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
|
|
||||||
current.s3task.schedule_task("setup_monitor_check_email_reply",
|
if error:
|
||||||
args = [run_id],
|
appname = options_get("appname", "eden")
|
||||||
start_time = start_time,
|
instance = options_get("instance", "prod")
|
||||||
timeout = 300, # seconds
|
|
||||||
repeats = 1 # one-time
|
# Restart uwsgi
|
||||||
)
|
error += "\n\nAttempting to restart:\n"
|
||||||
# Warning: No reply received yet
|
# Note this needs to actually run after last task as it kills us ;)
|
||||||
return 2
|
# NB Need to ensure the web2py user has permission to run sudo
|
||||||
|
command = 'echo "sudo service uwsgi-%s restart" | at now + 1 minutes' % instance
|
||||||
|
output = subprocess.check_output(command,
|
||||||
|
stderr = subprocess.STDOUT,
|
||||||
|
shell = True)
|
||||||
|
error += output.decode("utf-8")
|
||||||
|
# Restart Monitoring Scripts
|
||||||
|
command = 'echo "cd /home/%s;python web2py.py --no-banner -S %s -M -R applications/%s/static/scripts/tools/restart_monitor_tasks.py" | at now + 5 minutes' % \
|
||||||
|
(instance, appname, appname)
|
||||||
|
output = subprocess.check_output(command,
|
||||||
|
stderr = subprocess.STDOUT,
|
||||||
|
shell = True)
|
||||||
|
error += output.decode("utf-8")
|
||||||
|
return {"result": error,
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"result": "OK",
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ssh = _ssh(server)
|
||||||
|
if isinstance(ssh, dict):
|
||||||
|
# We failed to login
|
||||||
|
return ssh
|
||||||
|
|
||||||
|
appname = options_get("appname", "eden")
|
||||||
|
instance = options_get("instance", "prod")
|
||||||
|
|
||||||
|
command = "cd /home/%s;python web2py.py --no-banner -S %s -M -R applications/%s/static/scripts/tools/check_scheduler.py -A '%s'" % \
|
||||||
|
(instance, appname, appname, earliest)
|
||||||
|
stdin, stdout, stderr = ssh.exec_command(command)
|
||||||
|
outlines = stdout.readlines()
|
||||||
|
|
||||||
|
if outlines:
|
||||||
|
error = outlines[0]
|
||||||
|
# Restart uwsgi
|
||||||
|
error += "\n\nAttempting to restart:\n"
|
||||||
|
command = "sudo service uwsgi-%s restart" % instance
|
||||||
|
stdin, stdout, stderr = ssh.exec_command(command)
|
||||||
|
outlines = stdout.readlines()
|
||||||
|
if outlines:
|
||||||
|
error += "\n".join(outlines)
|
||||||
else:
|
else:
|
||||||
# Critical: Unable to send Email
|
# Doesn't usually give any output
|
||||||
return 3
|
error += "OK"
|
||||||
|
ssh.close()
|
||||||
|
return {"result": error,
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
return {"result": "OK",
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def tcp(task_id, run_id):
|
||||||
|
"""
|
||||||
|
Test that a TCP port is accessible
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def tickets(task_id, run_id):
|
||||||
|
"""
|
||||||
|
Test whether there are new tickets today
|
||||||
|
- designed to be run daily (period 86400s)
|
||||||
|
"""
|
||||||
|
|
||||||
|
db = current.db
|
||||||
|
s3db = current.s3db
|
||||||
|
|
||||||
|
# Read the Task Options
|
||||||
|
ttable = s3db.setup_monitor_task
|
||||||
|
task = db(ttable.id == task_id).select(ttable.options,
|
||||||
|
#ttable.period,
|
||||||
|
ttable.server_id,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
options = task.options or {}
|
||||||
|
options_get = options.get
|
||||||
|
|
||||||
|
stable = s3db.setup_server
|
||||||
|
server = db(stable.id == task.server_id).select(stable.host_ip,
|
||||||
|
stable.remote_user,
|
||||||
|
stable.private_key,
|
||||||
|
stable.deployment_id,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
|
||||||
|
request = current.request
|
||||||
|
today = request.utcnow.date().isoformat()
|
||||||
|
|
||||||
|
if server.host_ip == "127.0.0.1":
|
||||||
|
appname = request.application
|
||||||
|
public_url = current.deployment_settings.get_base_public_url()
|
||||||
|
tickets = os.listdir("applications/%s/errors" % appname)
|
||||||
|
new = []
|
||||||
|
for ticket in tickets:
|
||||||
|
#if os.stat(ticket).st_mtime < now - task.period:
|
||||||
|
if today in ticket:
|
||||||
|
url = "%s/%s/admin/ticket/%s/%s" % (public_url,
|
||||||
|
appname,
|
||||||
|
appname,
|
||||||
|
ticket,
|
||||||
|
)
|
||||||
|
new.append(url)
|
||||||
|
|
||||||
|
if new:
|
||||||
|
return {"result": "Warning: New tickets:\n\n%s" % "\n".join(new),
|
||||||
|
"status": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"result": "OK",
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh = _ssh(server)
|
||||||
|
if isinstance(ssh, dict):
|
||||||
|
# We failed to login
|
||||||
|
return ssh
|
||||||
|
|
||||||
|
appname = options_get("appname", "eden")
|
||||||
|
instance = options_get("instance", "prod")
|
||||||
|
|
||||||
|
command = "import os;ts=os.listdir('/home/%s/applications/%s/errors');for t in ts:print(t) if '%s' in t" % \
|
||||||
|
(instance, appname, today)
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('python -c "%s"' % command)
|
||||||
|
outlines = stdout.readlines()
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
if outlines:
|
||||||
|
itable = s3db.setup_instance
|
||||||
|
query = (itable.deployment_id == server.deployment_id) & \
|
||||||
|
(itable.type == INSTANCE_TYPES[instance])
|
||||||
|
instance = db(query).select(itable.url,
|
||||||
|
limitby = (0, 1)
|
||||||
|
).first()
|
||||||
|
public_url = instance.url
|
||||||
|
new = []
|
||||||
|
for ticket in outlines:
|
||||||
|
url = "%s/%s/admin/ticket/%s/%s" % (public_url,
|
||||||
|
appname,
|
||||||
|
appname,
|
||||||
|
ticket,
|
||||||
|
)
|
||||||
|
new.append(url)
|
||||||
|
return {"result": "Warning: New tickets:\n\n%s" % "\n".join(new),
|
||||||
|
"status": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"result": "OK",
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
def _bytes_to_size_string(b):
|
||||||
|
#def _bytes_to_size_string(b: int) -> str:
|
||||||
|
"""
|
||||||
|
Convert a number in bytes to a sensible unit.
|
||||||
|
|
||||||
|
From https://github.com/jamesoff/simplemonitor/blob/develop/simplemonitor/Monitors/host.py#L35
|
||||||
|
"""
|
||||||
|
|
||||||
|
kb = 1024
|
||||||
|
mb = kb * 1024
|
||||||
|
gb = mb * 1024
|
||||||
|
tb = gb * 1024
|
||||||
|
|
||||||
|
if b > tb:
|
||||||
|
return "%0.2fTiB" % (b / float(tb))
|
||||||
|
elif b > gb:
|
||||||
|
return "%0.2fGiB" % (b / float(gb))
|
||||||
|
elif b > mb:
|
||||||
|
return "%0.2fMiB" % (b / float(mb))
|
||||||
|
elif b > kb:
|
||||||
|
return "%0.2fKiB" % (b / float(kb))
|
||||||
|
else:
|
||||||
|
return str(b)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
def _ssh(server):
|
||||||
|
"""
|
||||||
|
SSH into a Server
|
||||||
|
"""
|
||||||
|
|
||||||
|
remote_user = server.remote_user
|
||||||
|
private_key = server.private_key
|
||||||
|
if not private_key or not remote_user:
|
||||||
|
if remote_user:
|
||||||
|
return {"result": "Critical. Missing Private Key",
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
elif private_key:
|
||||||
|
return {"result": "Critical. Missing Remote User",
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {"result": "Critical. Missing Remote User & Private Key",
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
# SSH in & run check
|
||||||
|
try:
|
||||||
|
import paramiko
|
||||||
|
except ImportError:
|
||||||
|
return {"result": "Critical. Paramiko required.",
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
keyfile = open(os.path.join(current.request.folder, "uploads", private_key), "r")
|
||||||
|
mykey = paramiko.RSAKey.from_private_key(keyfile)
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
try:
|
||||||
|
ssh.connect(hostname = server.host_ip,
|
||||||
|
username = remote_user,
|
||||||
|
pkey = mykey)
|
||||||
|
except paramiko.ssh_exception.AuthenticationException:
|
||||||
|
import traceback
|
||||||
|
tb_parts = sys.exc_info()
|
||||||
|
tb_text = "".join(traceback.format_exception(tb_parts[0],
|
||||||
|
tb_parts[1],
|
||||||
|
tb_parts[2]))
|
||||||
|
return {"result": "Critical. Authentication Error\n\n%s" % tb_text,
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
except paramiko.ssh_exception.SSHException:
|
||||||
|
import traceback
|
||||||
|
tb_parts = sys.exc_info()
|
||||||
|
tb_text = "".join(traceback.format_exception(tb_parts[0],
|
||||||
|
tb_parts[1],
|
||||||
|
tb_parts[2]))
|
||||||
|
return {"result": "Critical. SSH Error\n\n%s" % tb_text,
|
||||||
|
"status": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssh
|
||||||
|
|
||||||
# END =========================================================================
|
# END =========================================================================
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
Template-specific Message Parsers are defined here.
|
Template-specific Message Parsers are defined here.
|
||||||
|
|
||||||
@copyright: 2012-2019 (c) Sahana Software Foundation
|
@copyright: 2012-2020 (c) Sahana Software Foundation
|
||||||
@license: MIT
|
@license: MIT
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
Permission is hereby granted, free of charge, to any person
|
||||||
@ -55,6 +55,53 @@ class S3Parser(object):
|
|||||||
Message Parsing Template
|
Message Parsing Template
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def parse_email(message):
|
||||||
|
"""
|
||||||
|
Parse Responses
|
||||||
|
- parse responses to mails from the Monitor service
|
||||||
|
"""
|
||||||
|
|
||||||
|
reply = None
|
||||||
|
|
||||||
|
db = current.db
|
||||||
|
s3db = current.s3db
|
||||||
|
|
||||||
|
# Need to use Raw currently as not showing in Body
|
||||||
|
message_id = message.message_id
|
||||||
|
table = s3db.msg_email
|
||||||
|
record = db(table.message_id == message_id).select(table.raw,
|
||||||
|
limitby=(0, 1)
|
||||||
|
).first()
|
||||||
|
if not record:
|
||||||
|
return reply
|
||||||
|
|
||||||
|
message_body = record.raw
|
||||||
|
if not message_body:
|
||||||
|
return reply
|
||||||
|
|
||||||
|
# What type of message is this?
|
||||||
|
if ":run_id:" in message_body:
|
||||||
|
# Response to Monitor Check
|
||||||
|
|
||||||
|
# Parse Mail
|
||||||
|
try:
|
||||||
|
run_id = S3Parser._parse_value(message_body, "run_id")
|
||||||
|
run_id = int(run_id)
|
||||||
|
except:
|
||||||
|
return reply
|
||||||
|
|
||||||
|
# Update the Run entry to show that we have received the reply OK
|
||||||
|
rtable = s3db.monitor_run
|
||||||
|
db(rtable.id == run_id).update(result = "Reply Received",
|
||||||
|
status = 1)
|
||||||
|
return reply
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Don't know what this is: ignore
|
||||||
|
return reply
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_twitter(message):
|
def parse_twitter(message):
|
||||||
@ -250,37 +297,6 @@ class S3Parser(object):
|
|||||||
# No reply here
|
# No reply here
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
@staticmethod
|
|
||||||
def _parse_keywords(message_body):
|
|
||||||
"""
|
|
||||||
Parse Keywords
|
|
||||||
- helper function for search_resource, etc
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Equivalent keywords in one list
|
|
||||||
primary_keywords = ["get", "give", "show"]
|
|
||||||
contact_keywords = ["email", "mobile", "facility", "clinical",
|
|
||||||
"security", "phone", "status", "hospital",
|
|
||||||
"person", "organisation"]
|
|
||||||
|
|
||||||
pkeywords = primary_keywords + contact_keywords
|
|
||||||
keywords = message_body.split(" ")
|
|
||||||
pquery = []
|
|
||||||
name = ""
|
|
||||||
for word in keywords:
|
|
||||||
match = None
|
|
||||||
for key in pkeywords:
|
|
||||||
if soundex(key) == soundex(word):
|
|
||||||
match = key
|
|
||||||
break
|
|
||||||
if match:
|
|
||||||
pquery.append(match)
|
|
||||||
else:
|
|
||||||
name = word
|
|
||||||
|
|
||||||
return pquery, name
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
def search_resource(self, message):
|
def search_resource(self, message):
|
||||||
"""
|
"""
|
||||||
@ -310,7 +326,7 @@ class S3Parser(object):
|
|||||||
"""
|
"""
|
||||||
Search for People
|
Search for People
|
||||||
- can be called direct
|
- can be called direct
|
||||||
- can be called from search_by_keyword
|
- can be called from search_resource
|
||||||
"""
|
"""
|
||||||
|
|
||||||
message_body = message.body
|
message_body = message.body
|
||||||
@ -385,7 +401,7 @@ class S3Parser(object):
|
|||||||
"""
|
"""
|
||||||
Search for Hospitals
|
Search for Hospitals
|
||||||
- can be called direct
|
- can be called direct
|
||||||
- can be called from search_by_keyword
|
- can be called from search_resource
|
||||||
"""
|
"""
|
||||||
|
|
||||||
message_body = message.body
|
message_body = message.body
|
||||||
@ -458,7 +474,7 @@ class S3Parser(object):
|
|||||||
"""
|
"""
|
||||||
Search for Organisations
|
Search for Organisations
|
||||||
- can be called direct
|
- can be called direct
|
||||||
- can be called from search_by_keyword
|
- can be called from search_resource
|
||||||
"""
|
"""
|
||||||
|
|
||||||
message_body = message.body
|
message_body = message.body
|
||||||
@ -600,6 +616,49 @@ class S3Parser(object):
|
|||||||
reply = "Incident Report Logged!"
|
reply = "Incident Report Logged!"
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def _parse_keywords(message_body):
|
||||||
|
"""
|
||||||
|
Parse Keywords
|
||||||
|
- helper function for search_resource, etc
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Equivalent keywords in one list
|
||||||
|
primary_keywords = ["get", "give", "show"]
|
||||||
|
contact_keywords = ["email", "mobile", "facility", "clinical",
|
||||||
|
"security", "phone", "status", "hospital",
|
||||||
|
"person", "organisation"]
|
||||||
|
|
||||||
|
pkeywords = primary_keywords + contact_keywords
|
||||||
|
keywords = message_body.split(" ")
|
||||||
|
pquery = []
|
||||||
|
name = ""
|
||||||
|
for word in keywords:
|
||||||
|
match = None
|
||||||
|
for key in pkeywords:
|
||||||
|
if soundex(key) == soundex(word):
|
||||||
|
match = key
|
||||||
|
break
|
||||||
|
if match:
|
||||||
|
pquery.append(match)
|
||||||
|
else:
|
||||||
|
name = word
|
||||||
|
|
||||||
|
return pquery, name
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def _parse_value(text, fieldname):
|
||||||
|
"""
|
||||||
|
Parse a value from a piece of text
|
||||||
|
"""
|
||||||
|
|
||||||
|
parts = text.split(":%s:" % fieldname, 1)
|
||||||
|
parts = parts[1].split(":", 1)
|
||||||
|
result = parts[0]
|
||||||
|
return result
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _respond_drequest(message, report_id, response, text):
|
def _respond_drequest(message, report_id, response, text):
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
Name,Comments
|
||||||
|
Received,
|
|
@ -14,13 +14,16 @@
|
|||||||
# zzz_1st_run
|
# zzz_1st_run
|
||||||
# s3import::S3BulkImporter
|
# s3import::S3BulkImporter
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Authentication
|
# Roles
|
||||||
*,import_role,auth_roles.csv
|
*,import_role,auth_roles.csv
|
||||||
auth,user,masterUsers.csv,user.xsl
|
auth,user,masterUsers.csv,user.xsl
|
||||||
# GIS
|
# GIS
|
||||||
|
# Markers
|
||||||
gis,marker,gis_marker.csv,marker.xsl
|
gis,marker,gis_marker.csv,marker.xsl
|
||||||
|
# Config
|
||||||
gis,config,gis_config.csv,config.xsl
|
gis,config,gis_config.csv,config.xsl
|
||||||
gis,hierarchy,gis_hierarchy.csv,hierarchy.xsl
|
gis,hierarchy,gis_hierarchy.csv,hierarchy.xsl
|
||||||
|
# Layers
|
||||||
gis,layer_feature,gis_layer_feature.csv,layer_feature.xsl
|
gis,layer_feature,gis_layer_feature.csv,layer_feature.xsl
|
||||||
gis,layer_config,gis_layer_openstreetmap.csv,layer_openstreetmap.xsl
|
gis,layer_config,gis_layer_openstreetmap.csv,layer_openstreetmap.xsl
|
||||||
gis,layer_config,gis_layer_openweathermap.csv,layer_openweathermap.xsl
|
gis,layer_config,gis_layer_openweathermap.csv,layer_openweathermap.xsl
|
||||||
@ -31,16 +34,15 @@ gis,layer_config,gis_layer_tms.csv,layer_tms.xsl
|
|||||||
gis,layer_geojson,gis_layer_geojson.csv,layer_geojson.xsl
|
gis,layer_geojson,gis_layer_geojson.csv,layer_geojson.xsl
|
||||||
gis,layer_georss,gis_layer_georss.csv,layer_georss.xsl
|
gis,layer_georss,gis_layer_georss.csv,layer_georss.xsl
|
||||||
gis,layer_config,gis_layer_coordinate.csv,layer_coordinate.xsl
|
gis,layer_config,gis_layer_coordinate.csv,layer_coordinate.xsl
|
||||||
# CMS
|
# -----------------------------------------------------------------------------
|
||||||
cms,post,cms_post.csv,post.xsl
|
cms,post,cms_post.csv,post.xsl
|
||||||
# Organisations
|
# -----------------------------------------------------------------------------
|
||||||
org,sector,org_sector.csv,sector.xsl
|
org,sector,org_sector.csv,sector.xsl
|
||||||
org,organisation_type,organisation_type.csv,organisation_type.xsl
|
org,organisation_type,organisation_type.csv,organisation_type.xsl
|
||||||
org,office_type,office_type.csv,office_type.xsl
|
org,office_type,office_type.csv,office_type.xsl
|
||||||
# Supplies
|
|
||||||
supply,catalog_item,DefaultItems.csv,catalog_item.xsl
|
supply,catalog_item,DefaultItems.csv,catalog_item.xsl
|
||||||
supply,catalog_item,StandardItems.csv,catalog_item.xsl
|
supply,catalog_item,StandardItems.csv,catalog_item.xsl
|
||||||
# Human Resource Management
|
supply,person_item_status,supply_person_item_status.csv,person_item_status.xsl
|
||||||
hrm,skill,DefaultSkillList.csv,skill.xsl
|
hrm,skill,DefaultSkillList.csv,skill.xsl
|
||||||
hrm,skill,DrivingSkillList.csv,skill.xsl
|
hrm,skill,DrivingSkillList.csv,skill.xsl
|
||||||
hrm,skill,DrivingSkillList_EU.csv,skill.xsl
|
hrm,skill,DrivingSkillList_EU.csv,skill.xsl
|
||||||
@ -48,20 +50,15 @@ hrm,skill,LanguageSkillList.csv,skill.xsl
|
|||||||
hrm,competency_rating,DefaultSkillCompetency.csv,competency_rating.xsl
|
hrm,competency_rating,DefaultSkillCompetency.csv,competency_rating.xsl
|
||||||
hrm,competency_rating,LanguageCompetency.csv,competency_rating.xsl
|
hrm,competency_rating,LanguageCompetency.csv,competency_rating.xsl
|
||||||
hrm,certificate,certificate.csv,certificate.xsl
|
hrm,certificate,certificate.csv,certificate.xsl
|
||||||
# Events
|
|
||||||
event,event_type,event_type.csv,event_type.xsl
|
|
||||||
event,incident_type,incident_type.csv,incident_type.xsl
|
|
||||||
# Projects
|
|
||||||
project,status,project_status.csv,status.xsl
|
project,status,project_status.csv,status.xsl
|
||||||
project,activity_type,project_activity_type.csv,activity_type.xsl
|
project,activity_type,project_activity_type.csv,activity_type.xsl
|
||||||
project,hazard,project_hazard.csv,hazard.xsl
|
project,hazard,project_hazard.csv,hazard.xsl
|
||||||
project,theme,project_theme.csv,theme.xsl
|
project,theme,project_theme.csv,theme.xsl
|
||||||
project,beneficiary_type,project_beneficiary_type.csv,beneficiary_type.xsl
|
project,beneficiary_type,project_beneficiary_type.csv,beneficiary_type.xsl
|
||||||
# Shelters
|
# Spotter
|
||||||
cr,shelter_type,shelter_type.csv,shelter_type.xsl
|
cr,shelter_type,cr_shelter_type.csv,shelter_type.xsl
|
||||||
# Disaster Victim Registry
|
event,event_type,event_type.csv,event_type.xsl
|
||||||
dvr,case_status,dvr_case_status.csv,case_status.xsl
|
event,incident_type,incident_type.csv,incident_type.xsl
|
||||||
# Members
|
|
||||||
member,membership_type,membership_type.csv,membership_type.xsl
|
member,membership_type,membership_type.csv,membership_type.xsl
|
||||||
# Jobs
|
|
||||||
work,job_type,work_job_type.csv,job_type.xsl
|
work,job_type,work_job_type.csv,job_type.xsl
|
||||||
|
# =============================================================================
|
||||||
|
Loading…
Reference in New Issue
Block a user