Kimchi-devel
Threads by month
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- 3775 discussions
[PATCH WOK] Add fontawesome-fonts as dependency and expose .ttf file on web server
by Aline Manera 11 Sep '15
by Aline Manera 11 Sep '15
11 Sep '15
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
contrib/DEBIAN/control.in | 3 ++-
contrib/wok.spec.fedora.in | 1 +
contrib/wok.spec.suse.in | 1 +
src/wok/config.py.in | 19 +++++++++++++++++++
4 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/contrib/DEBIAN/control.in b/contrib/DEBIAN/control.in
index ef4e0c2..5d65bd0 100644
--- a/contrib/DEBIAN/control.in
+++ b/contrib/DEBIAN/control.in
@@ -15,7 +15,8 @@ Depends: python-cherrypy3 (>= 3.2.0),
python-lxml,
nginx,
python-ldap,
- spice-html5
+ spice-html5,
+ fonts-font-awesome
Build-Depends: libxslt,
openssl,
python-lxml
diff --git a/contrib/wok.spec.fedora.in b/contrib/wok.spec.fedora.in
index 30319a9..e82f946 100644
--- a/contrib/wok.spec.fedora.in
+++ b/contrib/wok.spec.fedora.in
@@ -19,6 +19,7 @@ Requires: python-jsonschema >= 1.3.0
Requires: python-lxml
Requires: nginx
Requires: python-ldap
+Requires: fontawesome-fonts
BuildRequires: libxslt
BuildRequires: openssl
BuildRequires: python-lxml
diff --git a/contrib/wok.spec.suse.in b/contrib/wok.spec.suse.in
index 63bdce3..0e9117e 100644
--- a/contrib/wok.spec.suse.in
+++ b/contrib/wok.spec.suse.in
@@ -20,6 +20,7 @@ Requires: python-ldap
Requires: python-lxml
Requires: python-xml
Requires: nginx
+Requires: fontawesome-fonts
BuildRequires: libxslt-tools
BuildRequires: openssl
BuildRequires: python-lxml
diff --git a/src/wok/config.py.in b/src/wok/config.py.in
index 08da028..6b8951d 100644
--- a/src/wok/config.py.in
+++ b/src/wok/config.py.in
@@ -60,6 +60,18 @@ class Paths(object):
self.plugins_dir = self.add_prefix('plugins')
self.mo_dir = self.add_prefix('mo')
+ self.fontawesome_file = None
+ for path in ['/usr/share/fonts/fontawesome',
+ '/usr/share/fonts/truetype/font-awesome',
+ '/usr/share/fonts/truetype']:
+ if os.path.exists(path):
+ self.fontawesome_file = os.path.join(path,
+ 'fontawesome-webfont.ttf')
+ break
+
+ if self.fontawesome_file is None:
+ raise Exception("Font Awesome is not installed.")
+
def get_prefix(self):
if __file__.startswith("/"):
base = os.path.dirname(__file__)
@@ -120,6 +132,13 @@ class UIConfig(dict):
ui_configs['/' + sub_dir].update({
'tools.expires.on': True,
'tools.expires.secs': self.CACHEEXPIRES})
+
+ ui_configs['/fontawesome/font/fontawesome-webfont.ttf'] = {
+ 'tools.staticfile.on': True,
+ 'tools.staticfile.filename': paths.fontawesome_file,
+ 'tools.wokauth.on': False,
+ 'tools.nocache.on': False}
+
self.update(ui_configs)
--
2.1.0
1
0
11 Sep '15
Changes:
v2:
Remove fuzzy translations
Signed-off-by: Ramon Medeiros <ramonn(a)linux.vnet.ibm.com>
---
po/de_DE.po | 132 ++++++++++++++++++++++++-----------------
po/en_US.po | 104 +++++++++++++++++++-------------
po/es_ES.po | 132 ++++++++++++++++++++++++-----------------
po/fr_FR.po | 134 +++++++++++++++++++++++++-----------------
po/it_IT.po | 132 ++++++++++++++++++++++++-----------------
po/ja_JP.po | 128 ++++++++++++++++++++++++----------------
po/kimchi.pot | 105 +++++++++++++++++++--------------
po/ko_KR.po | 130 +++++++++++++++++++++++-----------------
po/pt_BR.po | 132 ++++++++++++++++++++++++-----------------
po/ru_RU.po | 130 +++++++++++++++++++++++-----------------
po/zh_CN.po | 126 +++++++++++++++++++++++----------------
po/zh_TW.po | 129 +++++++++++++++++++++++-----------------
ui/pages/guest-edit.html.tmpl | 8 +--
13 files changed, 900 insertions(+), 622 deletions(-)
diff --git a/po/de_DE.po b/po/de_DE.po
index 439dce4..64ce72a 100644
--- a/po/de_DE.po
+++ b/po/de_DE.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -310,13 +310,6 @@ msgstr ""
"Virtuelle Maschine %(name)s konnte nicht heruntergefahren werden. Details: "
"%(err)s"
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr ""
-"Zugriff auf die Metadaten der virtuellen Maschine %(name)s nicht möglich. "
-"Details: %(err)s"
-
msgid "The guest console password must be a string."
msgstr "Das Konsolenkennwort des Gastes muss eine Zeichenfolge sein."
@@ -407,6 +400,21 @@ msgstr ""
msgid "Error attaching memory device. Details: %(error)s"
msgstr "Fehler beim Anschließen der Speichereinheit. Details: %(error)s"
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
@@ -1520,6 +1528,12 @@ msgstr "MAC-Adresse"
msgid "Available system users and groups"
msgstr "Verfügbare Systembenutzer und -gruppen"
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr "Ausgewählte Systembenutzer und -gruppen"
@@ -1562,37 +1576,6 @@ msgstr "Abbrechen"
msgid "revert"
msgstr "zurücksetzen"
-msgid "Add a Storage Device to VM"
-msgstr "Speichereinheit zur virtuellen Maschine hinzufügen"
-
-msgid "Device Type"
-msgstr "Einheitentyp"
-
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
-msgstr ""
-"Der Einheitentyp. Derzeit werden nur \"cdrom\" und \"disk\" unterstützt."
-
-msgid "Storage Pool"
-msgstr "Speicherpool"
-
-msgid "Storage pool which volume located in"
-msgstr "Speicherpool, in dem sich der Datenträger befindet"
-
-msgid "Storage Volume"
-msgstr "Speicherdatenträger"
-
-msgid "Storage volume to be attached"
-msgstr "Anzuhängender Speicherdatenträger"
-
-msgid "File Path"
-msgstr "Dateipfad"
-
-msgid "The ISO file path in the server for CDROM."
-msgstr "Der ISO-Dateipfad auf dem Server für die CD-ROM."
-
-msgid "Attach"
-msgstr "Anhängen"
-
msgid "Start"
msgstr "Starten"
@@ -1626,6 +1609,37 @@ msgstr "Herunterfahren"
msgid "Delete"
msgstr "Löschen"
+msgid "Add a Storage Device to VM"
+msgstr "Speichereinheit zur virtuellen Maschine hinzufügen"
+
+msgid "Device Type"
+msgstr "Einheitentyp"
+
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgstr ""
+"Der Einheitentyp. Derzeit werden nur \"cdrom\" und \"disk\" unterstützt."
+
+msgid "Storage Pool"
+msgstr "Speicherpool"
+
+msgid "Storage pool which volume located in"
+msgstr "Speicherpool, in dem sich der Datenträger befindet"
+
+msgid "Storage Volume"
+msgstr "Speicherdatenträger"
+
+msgid "Storage volume to be attached"
+msgstr "Anzuhängender Speicherdatenträger"
+
+msgid "File Path"
+msgstr "Dateipfad"
+
+msgid "The ISO file path in the server for CDROM."
+msgstr "Der ISO-Dateipfad auf dem Server für die CD-ROM."
+
+msgid "Attach"
+msgstr "Anhängen"
+
msgid "The username or password you entered is incorrect. Please try again."
msgstr ""
"Der Benutzername oder das Kennwort, den bzw. das Sie eingegeben haben, ist "
@@ -1649,6 +1663,9 @@ msgstr "Gäste"
msgid "Templates"
msgstr "Vorlagen"
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr "Anwendungskonfiguration konnte nicht abgerufen werden"
@@ -2182,21 +2199,6 @@ msgstr "Ja"
msgid "No"
msgstr "Nein"
-msgid "Add a Volume to Storage Pool"
-msgstr "Datenträger zu Speicherpool hinzufügen"
-
-msgid "Fetch from remote URL"
-msgstr "Über Remote URL abrufen"
-
-msgid "Enter the remote URL here."
-msgstr "Remote URL hier eingeben."
-
-msgid "Upload a file"
-msgstr "Datei hochladen"
-
-msgid "Choose the file you want to upload."
-msgstr "Wählen Sie die hochzuladende Datei aus."
-
msgid "Define a New Storage Pool"
msgstr "Neuen Speicherpool definieren"
@@ -2275,6 +2277,21 @@ msgstr "SCSI-Adapter"
msgid "Please, wait..."
msgstr "Bitte warten..."
+msgid "Add a Volume to Storage Pool"
+msgstr "Datenträger zu Speicherpool hinzufügen"
+
+msgid "Fetch from remote URL"
+msgstr "Über Remote URL abrufen"
+
+msgid "Enter the remote URL here."
+msgstr "Remote URL hier eingeben."
+
+msgid "Upload a file"
+msgstr "Datei hochladen"
+
+msgid "Choose the file you want to upload."
+msgstr "Wählen Sie die hochzuladende Datei aus."
+
msgid "Add Template"
msgstr "Vorlage hinzufügen"
@@ -2476,3 +2493,10 @@ msgstr "Zuordnung"
msgid "No templates found."
msgstr "Keine Vorlagen gefunden."
+
+#~ msgid ""
+#~ "Unable to get access metadata of virtual machine %(name)s. Details: "
+#~ "%(err)s"
+#~ msgstr ""
+#~ "Zugriff auf die Metadaten der virtuellen Maschine %(name)s nicht möglich. "
+#~ "Details: %(err)s"
diff --git a/po/en_US.po b/po/en_US.po
index 26dbaac..3539989 100644
--- a/po/en_US.po
+++ b/po/en_US.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -267,11 +267,6 @@ msgstr ""
msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr ""
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr ""
-
msgid "The guest console password must be a string."
msgstr ""
@@ -340,6 +335,22 @@ msgid "Error attaching memory device. Details: %(error)s"
msgstr ""
#, python-format
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
+#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
msgstr ""
@@ -1271,6 +1282,12 @@ msgstr ""
msgid "Available system users and groups"
msgstr ""
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr ""
@@ -1313,67 +1330,67 @@ msgstr ""
msgid "revert"
msgstr ""
-msgid "Add a Storage Device to VM"
+msgid "Start"
msgstr ""
-msgid "Device Type"
+msgid "Reset"
msgstr ""
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgid "Pause"
msgstr ""
-msgid "Storage Pool"
+msgid "Resume"
msgstr ""
-msgid "Storage pool which volume located in"
+msgid "Power Off"
msgstr ""
-msgid "Storage Volume"
+msgid "Actions"
msgstr ""
-msgid "Storage volume to be attached"
+msgid "Connect"
msgstr ""
-msgid "File Path"
+msgid "Clone"
msgstr ""
-msgid "The ISO file path in the server for CDROM."
+msgid "Edit"
msgstr ""
-msgid "Attach"
+msgid "Shut Down"
msgstr ""
-msgid "Start"
+msgid "Delete"
msgstr ""
-msgid "Reset"
+msgid "Add a Storage Device to VM"
msgstr ""
-msgid "Pause"
+msgid "Device Type"
msgstr ""
-msgid "Resume"
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
msgstr ""
-msgid "Power Off"
+msgid "Storage Pool"
msgstr ""
-msgid "Actions"
+msgid "Storage pool which volume located in"
msgstr ""
-msgid "Connect"
+msgid "Storage Volume"
msgstr ""
-msgid "Clone"
+msgid "Storage volume to be attached"
msgstr ""
-msgid "Edit"
+msgid "File Path"
msgstr ""
-msgid "Shut Down"
+msgid "The ISO file path in the server for CDROM."
msgstr ""
-msgid "Delete"
+msgid "Attach"
msgstr ""
msgid "The username or password you entered is incorrect. Please try again."
@@ -1397,6 +1414,9 @@ msgstr ""
msgid "Templates"
msgstr ""
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr ""
@@ -1878,21 +1898,6 @@ msgstr ""
msgid "No"
msgstr ""
-msgid "Add a Volume to Storage Pool"
-msgstr ""
-
-msgid "Fetch from remote URL"
-msgstr ""
-
-msgid "Enter the remote URL here."
-msgstr ""
-
-msgid "Upload a file"
-msgstr ""
-
-msgid "Choose the file you want to upload."
-msgstr ""
-
msgid "Define a New Storage Pool"
msgstr ""
@@ -1963,6 +1968,21 @@ msgstr ""
msgid "Please, wait..."
msgstr ""
+msgid "Add a Volume to Storage Pool"
+msgstr ""
+
+msgid "Fetch from remote URL"
+msgstr ""
+
+msgid "Enter the remote URL here."
+msgstr ""
+
+msgid "Upload a file"
+msgstr ""
+
+msgid "Choose the file you want to upload."
+msgstr ""
+
msgid "Add Template"
msgstr ""
diff --git a/po/es_ES.po b/po/es_ES.po
index 173af93..1e29291 100644
--- a/po/es_ES.po
+++ b/po/es_ES.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -304,13 +304,6 @@ msgstr "No existe el grupo (o grupos) '%(groups)s'"
msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr "No se puede apagar la máquina virtual %(name)s. Detalles: %(err)s"
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr ""
-"No se puede obtener acceso a los metadatos de la máquina virtual %(name)s. "
-"Detalles: %(err)s"
-
msgid "The guest console password must be a string."
msgstr "La contraseña de la consola del invitado debe ser una serie. "
@@ -393,6 +386,21 @@ msgstr ""
msgid "Error attaching memory device. Details: %(error)s"
msgstr "Error al conectar el dispositivo de memoria. Detalles: %(error)s"
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
@@ -1518,6 +1526,12 @@ msgstr "Dirección MAC"
msgid "Available system users and groups"
msgstr "Usuarios y grupos del sistema disponibles"
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr "Usuarios y grupos del sistema seleccionados"
@@ -1560,37 +1574,6 @@ msgstr "Cancelar"
msgid "revert"
msgstr "revertir"
-msgid "Add a Storage Device to VM"
-msgstr "Añadir un dispositivo de almacenamiento a VM"
-
-msgid "Device Type"
-msgstr "Tipo de dispositivo"
-
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
-msgstr ""
-"El tipo de dispositivo. Actualmente se da soporte a \"cdrom\" y \"disk\"."
-
-msgid "Storage Pool"
-msgstr "Agrupación de almacenamiento"
-
-msgid "Storage pool which volume located in"
-msgstr "Agrupación de almacenamiento con volumen ubicado en"
-
-msgid "Storage Volume"
-msgstr "Volumen de almacenamiento"
-
-msgid "Storage volume to be attached"
-msgstr "Volumen de almacenamiento que se va a adjuntar"
-
-msgid "File Path"
-msgstr "Vía de acceso de archivo"
-
-msgid "The ISO file path in the server for CDROM."
-msgstr "La vía de acceso del archivo ISO en el servidor para el CDROM."
-
-msgid "Attach"
-msgstr "Conectar"
-
msgid "Start"
msgstr "Iniciar"
@@ -1624,6 +1607,37 @@ msgstr "Concluir"
msgid "Delete"
msgstr "Suprimir"
+msgid "Add a Storage Device to VM"
+msgstr "Añadir un dispositivo de almacenamiento a VM"
+
+msgid "Device Type"
+msgstr "Tipo de dispositivo"
+
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgstr ""
+"El tipo de dispositivo. Actualmente se da soporte a \"cdrom\" y \"disk\"."
+
+msgid "Storage Pool"
+msgstr "Agrupación de almacenamiento"
+
+msgid "Storage pool which volume located in"
+msgstr "Agrupación de almacenamiento con volumen ubicado en"
+
+msgid "Storage Volume"
+msgstr "Volumen de almacenamiento"
+
+msgid "Storage volume to be attached"
+msgstr "Volumen de almacenamiento que se va a adjuntar"
+
+msgid "File Path"
+msgstr "Vía de acceso de archivo"
+
+msgid "The ISO file path in the server for CDROM."
+msgstr "La vía de acceso del archivo ISO en el servidor para el CDROM."
+
+msgid "Attach"
+msgstr "Conectar"
+
msgid "The username or password you entered is incorrect. Please try again."
msgstr ""
"El nombre de usuario o contraseña que ha especificado es incorrecto. Por "
@@ -1647,6 +1661,9 @@ msgstr "Invitados"
msgid "Templates"
msgstr "Plantillas"
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr "No se ha podido obtener la configuración de la aplicación"
@@ -2182,21 +2199,6 @@ msgstr "Sí"
msgid "No"
msgstr "No"
-msgid "Add a Volume to Storage Pool"
-msgstr "Añadir un volumen a la agrupación de almacenamiento"
-
-msgid "Fetch from remote URL"
-msgstr "Captar de URL remoto"
-
-msgid "Enter the remote URL here."
-msgstr "Escriba el URL remoto aquí. "
-
-msgid "Upload a file"
-msgstr "Cargar un archivo"
-
-msgid "Choose the file you want to upload."
-msgstr "Elija el archivo que desee cargar. "
-
msgid "Define a New Storage Pool"
msgstr "Definir una agrupación de almacenamiento nueva"
@@ -2273,6 +2275,21 @@ msgstr "Adaptador SCSI"
msgid "Please, wait..."
msgstr "Por favor, espere..."
+msgid "Add a Volume to Storage Pool"
+msgstr "Añadir un volumen a la agrupación de almacenamiento"
+
+msgid "Fetch from remote URL"
+msgstr "Captar de URL remoto"
+
+msgid "Enter the remote URL here."
+msgstr "Escriba el URL remoto aquí. "
+
+msgid "Upload a file"
+msgstr "Cargar un archivo"
+
+msgid "Choose the file you want to upload."
+msgstr "Elija el archivo que desee cargar. "
+
msgid "Add Template"
msgstr "Añadir plantilla"
@@ -2475,3 +2492,10 @@ msgstr "Asignación"
msgid "No templates found."
msgstr "No se han encontrado plantillas."
+
+#~ msgid ""
+#~ "Unable to get access metadata of virtual machine %(name)s. Details: "
+#~ "%(err)s"
+#~ msgstr ""
+#~ "No se puede obtener acceso a los metadatos de la máquina virtual "
+#~ "%(name)s. Detalles: %(err)s"
diff --git a/po/fr_FR.po b/po/fr_FR.po
index 42bb004..2a6642a 100644
--- a/po/fr_FR.po
+++ b/po/fr_FR.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -314,13 +314,6 @@ msgstr "Le ou les groupes '%(groups)s' n'existent pas"
msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr "Impossible d'arrêter la machine virtuelle %(name)s. Détails : %(err)s"
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr ""
-"Impossible d'obtenir l'accès aux métadonnées de la machine virtuelle "
-"%(name)s. Détails : %(err)s"
-
msgid "The guest console password must be a string."
msgstr "Le mot de passe de la console invité doit être une chaîne."
@@ -409,6 +402,21 @@ msgstr ""
msgid "Error attaching memory device. Details: %(error)s"
msgstr "Erreur d'attachement de l'unité de mémoire. Détails : %(error)s"
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
@@ -1522,6 +1530,12 @@ msgstr "Adresse MAC"
msgid "Available system users and groups"
msgstr "Groupes et utilisateurs système disponibles"
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr "Groupes et utilisateurs système sélectionnés"
@@ -1564,38 +1578,6 @@ msgstr "Annuler"
msgid "revert"
msgstr "rétablir"
-msgid "Add a Storage Device to VM"
-msgstr "Ajouter une unité de stockage à la machine virtuelle"
-
-msgid "Device Type"
-msgstr "Type d'unité"
-
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
-msgstr ""
-"Type d'unité. Actuellement, \"cdrom\" et \"disk\" seulement sont pris en "
-"charge."
-
-msgid "Storage Pool"
-msgstr "Pool de stockage"
-
-msgid "Storage pool which volume located in"
-msgstr "Pool de stockage dans lequel le volume est situé"
-
-msgid "Storage Volume"
-msgstr "Volume de stockage"
-
-msgid "Storage volume to be attached"
-msgstr "Volume de stockage à attacher"
-
-msgid "File Path"
-msgstr "Chemin d'accès au fichier"
-
-msgid "The ISO file path in the server for CDROM."
-msgstr "Chemin d'accès au fichier ISO sur le serveur pour le CD-ROM."
-
-msgid "Attach"
-msgstr "Attacher"
-
msgid "Start"
msgstr "Démarrer"
@@ -1629,6 +1611,38 @@ msgstr "Arrêter"
msgid "Delete"
msgstr "Supprimer"
+msgid "Add a Storage Device to VM"
+msgstr "Ajouter une unité de stockage à la machine virtuelle"
+
+msgid "Device Type"
+msgstr "Type d'unité"
+
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgstr ""
+"Type d'unité. Actuellement, \"cdrom\" et \"disk\" seulement sont pris en "
+"charge."
+
+msgid "Storage Pool"
+msgstr "Pool de stockage"
+
+msgid "Storage pool which volume located in"
+msgstr "Pool de stockage dans lequel le volume est situé"
+
+msgid "Storage Volume"
+msgstr "Volume de stockage"
+
+msgid "Storage volume to be attached"
+msgstr "Volume de stockage à attacher"
+
+msgid "File Path"
+msgstr "Chemin d'accès au fichier"
+
+msgid "The ISO file path in the server for CDROM."
+msgstr "Chemin d'accès au fichier ISO sur le serveur pour le CD-ROM."
+
+msgid "Attach"
+msgstr "Attacher"
+
msgid "The username or password you entered is incorrect. Please try again."
msgstr ""
"Le nom d'utilisateur ou le mot de passe entré est incorrect. Veuillez "
@@ -1652,6 +1666,9 @@ msgstr "Invités"
msgid "Templates"
msgstr "Modèles"
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr "Echec d'obtention de la configuration d'application"
@@ -2188,21 +2205,6 @@ msgstr "Oui"
msgid "No"
msgstr "Non"
-msgid "Add a Volume to Storage Pool"
-msgstr "Ajouter un volume au pool de stockage"
-
-msgid "Fetch from remote URL"
-msgstr "Extraire de l'URL distante"
-
-msgid "Enter the remote URL here."
-msgstr "Indiquez ici l'URL distante."
-
-msgid "Upload a file"
-msgstr "Télécharger un fichier"
-
-msgid "Choose the file you want to upload."
-msgstr "Sélectionnez le fichier à télécharger."
-
msgid "Define a New Storage Pool"
msgstr "Définir un nouveau pool de stockage"
@@ -2280,6 +2282,21 @@ msgstr "Adaptateur SCSI"
msgid "Please, wait..."
msgstr "Veuillez patienter..."
+msgid "Add a Volume to Storage Pool"
+msgstr "Ajouter un volume au pool de stockage"
+
+msgid "Fetch from remote URL"
+msgstr "Extraire de l'URL distante"
+
+msgid "Enter the remote URL here."
+msgstr "Indiquez ici l'URL distante."
+
+msgid "Upload a file"
+msgstr "Télécharger un fichier"
+
+msgid "Choose the file you want to upload."
+msgstr "Sélectionnez le fichier à télécharger."
+
msgid "Add Template"
msgstr "Ajouter un modèle"
@@ -2480,3 +2497,10 @@ msgstr "Allocation"
msgid "No templates found."
msgstr "Aucun modèle trouvé."
+
+#~ msgid ""
+#~ "Unable to get access metadata of virtual machine %(name)s. Details: "
+#~ "%(err)s"
+#~ msgstr ""
+#~ "Impossible d'obtenir l'accès aux métadonnées de la machine virtuelle "
+#~ "%(name)s. Détails : %(err)s"
diff --git a/po/it_IT.po b/po/it_IT.po
index 5d0b407..8500df5 100644
--- a/po/it_IT.po
+++ b/po/it_IT.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -298,13 +298,6 @@ msgstr "Il gruppo o i gruppi '%(groups)s' non esistono"
msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr "Impossibile arrestare la macchina virtuale %(name)s. Dettagli: %(err)s"
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr ""
-"Impossibile ottenere i metadati di accesso della macchina virtuale %(name)s. "
-"Dettagli: %(err)s"
-
msgid "The guest console password must be a string."
msgstr ""
"La password della console della macchina guest deve essere una stringa. "
@@ -388,6 +381,21 @@ msgstr ""
"Errore durante il collegamento del dispositivo di memoria. Dettagli: "
"%(error)s"
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
@@ -1481,6 +1489,12 @@ msgstr "Indirizzo MAC"
msgid "Available system users and groups"
msgstr "Utenti e gruppi di sistema disponibili"
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr "Utenti e gruppi di sistema selezionati"
@@ -1523,37 +1537,6 @@ msgstr "Annulla"
msgid "revert"
msgstr "Inverti"
-msgid "Add a Storage Device to VM"
-msgstr "Aggiungi un dispositivo di memoria alla VM"
-
-msgid "Device Type"
-msgstr "Tipo dispositivo"
-
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
-msgstr ""
-"Il tipo di dispositivo. Attualmente, sono supportati \"cdrom\" e \"disk\"."
-
-msgid "Storage Pool"
-msgstr "Pool di memoria"
-
-msgid "Storage pool which volume located in"
-msgstr "Pool di memoria in cui si trova il volume"
-
-msgid "Storage Volume"
-msgstr "Volume di memoria"
-
-msgid "Storage volume to be attached"
-msgstr "Volume di memoria oggetto del collegamento"
-
-msgid "File Path"
-msgstr "Percorso file"
-
-msgid "The ISO file path in the server for CDROM."
-msgstr "Il percorso file ISO nel server per CDROM."
-
-msgid "Attach"
-msgstr "Allega"
-
msgid "Start"
msgstr "Avvia"
@@ -1587,6 +1570,37 @@ msgstr "Arresta"
msgid "Delete"
msgstr "Elimina"
+msgid "Add a Storage Device to VM"
+msgstr "Aggiungi un dispositivo di memoria alla VM"
+
+msgid "Device Type"
+msgstr "Tipo dispositivo"
+
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgstr ""
+"Il tipo di dispositivo. Attualmente, sono supportati \"cdrom\" e \"disk\"."
+
+msgid "Storage Pool"
+msgstr "Pool di memoria"
+
+msgid "Storage pool which volume located in"
+msgstr "Pool di memoria in cui si trova il volume"
+
+msgid "Storage Volume"
+msgstr "Volume di memoria"
+
+msgid "Storage volume to be attached"
+msgstr "Volume di memoria oggetto del collegamento"
+
+msgid "File Path"
+msgstr "Percorso file"
+
+msgid "The ISO file path in the server for CDROM."
+msgstr "Il percorso file ISO nel server per CDROM."
+
+msgid "Attach"
+msgstr "Allega"
+
msgid "The username or password you entered is incorrect. Please try again."
msgstr ""
"Il nome utente o la password immessi non sono corretti. Ripetere "
@@ -1610,6 +1624,9 @@ msgstr "Guest"
msgid "Templates"
msgstr "Modelli"
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr "Richiamo della configurazione dell'applicazione non riuscito"
@@ -2143,21 +2160,6 @@ msgstr "Sì"
msgid "No"
msgstr "No"
-msgid "Add a Volume to Storage Pool"
-msgstr "Aggiungi un volume al pool di memoria"
-
-msgid "Fetch from remote URL"
-msgstr "Richiama da URL remoto"
-
-msgid "Enter the remote URL here."
-msgstr "Immettere qui l'URL remoto. "
-
-msgid "Upload a file"
-msgstr "Carica un file"
-
-msgid "Choose the file you want to upload."
-msgstr "Selezionare il file da caricare. "
-
msgid "Define a New Storage Pool"
msgstr "Definisci un nuovo pool di memoria"
@@ -2237,6 +2239,21 @@ msgstr "Adattatore SCSI"
msgid "Please, wait..."
msgstr "Attendere..."
+msgid "Add a Volume to Storage Pool"
+msgstr "Aggiungi un volume al pool di memoria"
+
+msgid "Fetch from remote URL"
+msgstr "Richiama da URL remoto"
+
+msgid "Enter the remote URL here."
+msgstr "Immettere qui l'URL remoto. "
+
+msgid "Upload a file"
+msgstr "Carica un file"
+
+msgid "Choose the file you want to upload."
+msgstr "Selezionare il file da caricare. "
+
msgid "Add Template"
msgstr "Aggiungi modello"
@@ -2438,3 +2455,10 @@ msgstr "Assegnazione"
msgid "No templates found."
msgstr "Nessun modello trovato."
+
+#~ msgid ""
+#~ "Unable to get access metadata of virtual machine %(name)s. Details: "
+#~ "%(err)s"
+#~ msgstr ""
+#~ "Impossibile ottenere i metadati di accesso della macchina virtuale "
+#~ "%(name)s. Dettagli: %(err)s"
diff --git a/po/ja_JP.po b/po/ja_JP.po
index a95a210..5da8882 100644
--- a/po/ja_JP.po
+++ b/po/ja_JP.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -286,12 +286,6 @@ msgstr "グループ「%(groups)s」は存在しません"
msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr "仮想マシン %(name)s をシャットダウンできません。詳細: %(err)s"
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr ""
-"仮想マシン %(name)s のアクセス・メタデータを取得できません。詳細: %(err)s"
-
msgid "The guest console password must be a string."
msgstr "ゲスト・コンソール・パスワードはストリングでなければなりません。"
@@ -367,6 +361,21 @@ msgstr ""
msgid "Error attaching memory device. Details: %(error)s"
msgstr "メモリー・デバイスの接続エラーです。詳細: %(error)s"
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
@@ -1472,6 +1481,12 @@ msgstr "MAC アドレス"
msgid "Available system users and groups"
msgstr "使用可能なシステム・ユーザーおよびグループ"
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr "選択されたシステム・ユーザーおよびグループ"
@@ -1514,36 +1529,6 @@ msgstr "取消"
msgid "revert"
msgstr "戻す"
-msgid "Add a Storage Device to VM"
-msgstr "VM にストレージ・デバイスを追加"
-
-msgid "Device Type"
-msgstr "デバイス・タイプ"
-
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
-msgstr "デバイス・タイプ。現在、「cdrom」と「disk」がサポートされています。"
-
-msgid "Storage Pool"
-msgstr "ストレージ・プール"
-
-msgid "Storage pool which volume located in"
-msgstr "ボリュームが含まれているストレージ・プール"
-
-msgid "Storage Volume"
-msgstr "ストレージ・ボリューム"
-
-msgid "Storage volume to be attached"
-msgstr "接続されるストレージ・ボリューム"
-
-msgid "File Path"
-msgstr "ファイル・パス"
-
-msgid "The ISO file path in the server for CDROM."
-msgstr "サーバー内での CDROM の ISO ファイル・パス。"
-
-msgid "Attach"
-msgstr "接続"
-
msgid "Start"
msgstr "開始"
@@ -1577,6 +1562,36 @@ msgstr "シャットダウン"
msgid "Delete"
msgstr "削除"
+msgid "Add a Storage Device to VM"
+msgstr "VM にストレージ・デバイスを追加"
+
+msgid "Device Type"
+msgstr "デバイス・タイプ"
+
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgstr "デバイス・タイプ。現在、「cdrom」と「disk」がサポートされています。"
+
+msgid "Storage Pool"
+msgstr "ストレージ・プール"
+
+msgid "Storage pool which volume located in"
+msgstr "ボリュームが含まれているストレージ・プール"
+
+msgid "Storage Volume"
+msgstr "ストレージ・ボリューム"
+
+msgid "Storage volume to be attached"
+msgstr "接続されるストレージ・ボリューム"
+
+msgid "File Path"
+msgstr "ファイル・パス"
+
+msgid "The ISO file path in the server for CDROM."
+msgstr "サーバー内での CDROM の ISO ファイル・パス。"
+
+msgid "Attach"
+msgstr "接続"
+
msgid "The username or password you entered is incorrect. Please try again."
msgstr "入力したユーザー名またはパスワードが誤っています。やり直してください。"
@@ -1598,6 +1613,9 @@ msgstr "ゲスト"
msgid "Templates"
msgstr "テンプレート"
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr "アプリケーション構成を取得できませんでした"
@@ -2123,21 +2141,6 @@ msgstr " はい"
msgid "No"
msgstr " いいえ"
-msgid "Add a Volume to Storage Pool"
-msgstr "ボリュームをストレージ・プールに追加"
-
-msgid "Fetch from remote URL"
-msgstr "リモート URL からフェッチ"
-
-msgid "Enter the remote URL here."
-msgstr "ここにリモート URL を入力します。"
-
-msgid "Upload a file"
-msgstr "ファイルのアップロード"
-
-msgid "Choose the file you want to upload."
-msgstr "アップロードしたいファイルを選択してください。"
-
msgid "Define a New Storage Pool"
msgstr "新規ストレージ・プールの定義"
@@ -2213,6 +2216,21 @@ msgstr "SCSI アダプター"
msgid "Please, wait..."
msgstr "お待ちください..."
+msgid "Add a Volume to Storage Pool"
+msgstr "ボリュームをストレージ・プールに追加"
+
+msgid "Fetch from remote URL"
+msgstr "リモート URL からフェッチ"
+
+msgid "Enter the remote URL here."
+msgstr "ここにリモート URL を入力します。"
+
+msgid "Upload a file"
+msgstr "ファイルのアップロード"
+
+msgid "Choose the file you want to upload."
+msgstr "アップロードしたいファイルを選択してください。"
+
msgid "Add Template"
msgstr "テンプレートの追加"
@@ -2413,3 +2431,9 @@ msgstr "割り振り"
msgid "No templates found."
msgstr "テンプレートが見つかりません。"
+
+#~ msgid ""
+#~ "Unable to get access metadata of virtual machine %(name)s. Details: "
+#~ "%(err)s"
+#~ msgstr ""
+#~ "仮想マシン %(name)s のアクセス・メタデータを取得できません。詳細: %(err)s"
diff --git a/po/kimchi.pot b/po/kimchi.pot
index 1179357..91a70f0 100755
--- a/po/kimchi.pot
+++ b/po/kimchi.pot
@@ -3,12 +3,11 @@
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
-#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL(a)li.org>\n"
@@ -267,11 +266,6 @@ msgstr ""
msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr ""
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr ""
-
msgid "The guest console password must be a string."
msgstr ""
@@ -340,6 +334,22 @@ msgid "Error attaching memory device. Details: %(error)s"
msgstr ""
#, python-format
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
+#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
msgstr ""
@@ -1271,6 +1281,12 @@ msgstr ""
msgid "Available system users and groups"
msgstr ""
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr ""
@@ -1313,67 +1329,67 @@ msgstr ""
msgid "revert"
msgstr ""
-msgid "Add a Storage Device to VM"
+msgid "Start"
msgstr ""
-msgid "Device Type"
+msgid "Reset"
msgstr ""
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgid "Pause"
msgstr ""
-msgid "Storage Pool"
+msgid "Resume"
msgstr ""
-msgid "Storage pool which volume located in"
+msgid "Power Off"
msgstr ""
-msgid "Storage Volume"
+msgid "Actions"
msgstr ""
-msgid "Storage volume to be attached"
+msgid "Connect"
msgstr ""
-msgid "File Path"
+msgid "Clone"
msgstr ""
-msgid "The ISO file path in the server for CDROM."
+msgid "Edit"
msgstr ""
-msgid "Attach"
+msgid "Shut Down"
msgstr ""
-msgid "Start"
+msgid "Delete"
msgstr ""
-msgid "Reset"
+msgid "Add a Storage Device to VM"
msgstr ""
-msgid "Pause"
+msgid "Device Type"
msgstr ""
-msgid "Resume"
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
msgstr ""
-msgid "Power Off"
+msgid "Storage Pool"
msgstr ""
-msgid "Actions"
+msgid "Storage pool which volume located in"
msgstr ""
-msgid "Connect"
+msgid "Storage Volume"
msgstr ""
-msgid "Clone"
+msgid "Storage volume to be attached"
msgstr ""
-msgid "Edit"
+msgid "File Path"
msgstr ""
-msgid "Shut Down"
+msgid "The ISO file path in the server for CDROM."
msgstr ""
-msgid "Delete"
+msgid "Attach"
msgstr ""
msgid "The username or password you entered is incorrect. Please try again."
@@ -1397,6 +1413,9 @@ msgstr ""
msgid "Templates"
msgstr ""
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr ""
@@ -1878,21 +1897,6 @@ msgstr ""
msgid "No"
msgstr ""
-msgid "Add a Volume to Storage Pool"
-msgstr ""
-
-msgid "Fetch from remote URL"
-msgstr ""
-
-msgid "Enter the remote URL here."
-msgstr ""
-
-msgid "Upload a file"
-msgstr ""
-
-msgid "Choose the file you want to upload."
-msgstr ""
-
msgid "Define a New Storage Pool"
msgstr ""
@@ -1963,6 +1967,21 @@ msgstr ""
msgid "Please, wait..."
msgstr ""
+msgid "Add a Volume to Storage Pool"
+msgstr ""
+
+msgid "Fetch from remote URL"
+msgstr ""
+
+msgid "Enter the remote URL here."
+msgstr ""
+
+msgid "Upload a file"
+msgstr ""
+
+msgid "Choose the file you want to upload."
+msgstr ""
+
msgid "Add Template"
msgstr ""
diff --git a/po/ko_KR.po b/po/ko_KR.po
index 4504a4b..4421f46 100644
--- a/po/ko_KR.po
+++ b/po/ko_KR.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -281,13 +281,6 @@ msgstr "그룹 '%(groups)s'이(가) 없습니다."
msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr "가상 머신 %(name)s을(를) 종료할 수 없습니다. 세부사항: %(err)s"
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr ""
-"가상 머신 %(name)s의 액세스 권한 메타데이터를 가져올 수 없습니다. 세부사항: "
-"%(err)s"
-
msgid "The guest console password must be a string."
msgstr "게스트 콘솔 비밀번호는 문자열이어야 합니다."
@@ -362,6 +355,21 @@ msgstr ""
msgid "Error attaching memory device. Details: %(error)s"
msgstr "메모리 장치를 연결하는 중 오류가 발생했습니다. 세부사항: %(error)s"
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
@@ -1376,6 +1384,12 @@ msgstr "MAC 주소"
msgid "Available system users and groups"
msgstr "사용 가능한 시스템 사용자 및 그룹"
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr "선택한 시스템 사용자 및 그룹"
@@ -1418,36 +1432,6 @@ msgstr "취소"
msgid "revert"
msgstr "되돌리기"
-msgid "Add a Storage Device to VM"
-msgstr "스토리지 장치를 VM에 추가"
-
-msgid "Device Type"
-msgstr "장치 유형"
-
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
-msgstr "장치 유형. 현재 \"cdrom\" 및 \"disk\"가 지원됩니다."
-
-msgid "Storage Pool"
-msgstr "스토리지 풀"
-
-msgid "Storage pool which volume located in"
-msgstr "볼륨이 위치한 스토리지 풀"
-
-msgid "Storage Volume"
-msgstr "스토리지 볼륨"
-
-msgid "Storage volume to be attached"
-msgstr "연결될 스토리지 볼륨"
-
-msgid "File Path"
-msgstr "파일 경로"
-
-msgid "The ISO file path in the server for CDROM."
-msgstr "CDROM을 위한 서버의 ISO 파일 경로입니다."
-
-msgid "Attach"
-msgstr "연결"
-
msgid "Start"
msgstr "시작"
@@ -1481,6 +1465,36 @@ msgstr "종료"
msgid "Delete"
msgstr "삭제"
+msgid "Add a Storage Device to VM"
+msgstr "스토리지 장치를 VM에 추가"
+
+msgid "Device Type"
+msgstr "장치 유형"
+
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgstr "장치 유형. 현재 \"cdrom\" 및 \"disk\"가 지원됩니다."
+
+msgid "Storage Pool"
+msgstr "스토리지 풀"
+
+msgid "Storage pool which volume located in"
+msgstr "볼륨이 위치한 스토리지 풀"
+
+msgid "Storage Volume"
+msgstr "스토리지 볼륨"
+
+msgid "Storage volume to be attached"
+msgstr "연결될 스토리지 볼륨"
+
+msgid "File Path"
+msgstr "파일 경로"
+
+msgid "The ISO file path in the server for CDROM."
+msgstr "CDROM을 위한 서버의 ISO 파일 경로입니다."
+
+msgid "Attach"
+msgstr "연결"
+
msgid "The username or password you entered is incorrect. Please try again."
msgstr ""
"입력한 사용자 이름 또는 비밀번호가 올바르지 않습니다. 다시 시도하십시오."
@@ -1503,6 +1517,9 @@ msgstr "게스트"
msgid "Templates"
msgstr "템플리트"
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr "애플리케이션 구성을 가져오지 못했습니다."
@@ -2017,21 +2034,6 @@ msgstr "예"
msgid "No"
msgstr "아니오"
-msgid "Add a Volume to Storage Pool"
-msgstr "스토리지 풀에 볼륨 추가"
-
-msgid "Fetch from remote URL"
-msgstr "원격 URL에서 페치"
-
-msgid "Enter the remote URL here."
-msgstr "여기에 원격 URL을 입력하십시오."
-
-msgid "Upload a file"
-msgstr "파일 업로드"
-
-msgid "Choose the file you want to upload."
-msgstr "업로드할 파일을 선택하십시오."
-
msgid "Define a New Storage Pool"
msgstr "새 스토리지 풀 정의"
@@ -2105,6 +2107,21 @@ msgstr "SCSI 어댑터"
msgid "Please, wait..."
msgstr "잠시 기다려 주십시오."
+msgid "Add a Volume to Storage Pool"
+msgstr "스토리지 풀에 볼륨 추가"
+
+msgid "Fetch from remote URL"
+msgstr "원격 URL에서 페치"
+
+msgid "Enter the remote URL here."
+msgstr "여기에 원격 URL을 입력하십시오."
+
+msgid "Upload a file"
+msgstr "파일 업로드"
+
+msgid "Choose the file you want to upload."
+msgstr "업로드할 파일을 선택하십시오."
+
msgid "Add Template"
msgstr "템플리트 추가"
@@ -2305,3 +2322,10 @@ msgstr "할당"
msgid "No templates found."
msgstr "템플리트가 없습니다."
+
+#~ msgid ""
+#~ "Unable to get access metadata of virtual machine %(name)s. Details: "
+#~ "%(err)s"
+#~ msgstr ""
+#~ "가상 머신 %(name)s의 액세스 권한 메타데이터를 가져올 수 없습니다. 세부사"
+#~ "항: %(err)s"
diff --git a/po/pt_BR.po b/po/pt_BR.po
index 61ffe9e..0064985 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -295,13 +295,6 @@ msgstr "O(s) grupo(s) '%(groups)s' não existem"
msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr "Não é possível encerrar a máquina virtual %(name)s. Detalhes: %(err)s "
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr ""
-"Não é possível obter metadados de acesso da máquina virtual %(name)s. "
-"Detalhes: %(err)s "
-
msgid "The guest console password must be a string."
msgstr "A senha do console do convidado deve ser uma sequência."
@@ -378,6 +371,21 @@ msgstr ""
msgid "Error attaching memory device. Details: %(error)s"
msgstr "Erro ao anexar dispositivo de memória. Detalhes: %(error)s "
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr "Não é possível ligar a VM '%(name)s' porque ela está em execução."
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
@@ -1487,6 +1495,12 @@ msgstr "Endereço MAC"
msgid "Available system users and groups"
msgstr "Usuários e grupos do sistema disponível"
+msgid "Users"
+msgstr "Usuários"
+
+msgid "Groups"
+msgstr "Grupos"
+
msgid "Selected system users and groups"
msgstr "Usuários e grupos do sistema selecionado"
@@ -1529,37 +1543,6 @@ msgstr "Cancelar"
msgid "revert"
msgstr "reverter"
-msgid "Add a Storage Device to VM"
-msgstr "Incluir um Dispositivo de Armazenamento na VM"
-
-msgid "Device Type"
-msgstr "Tipo de Dispositivo"
-
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
-msgstr ""
-"O tipo de dispositivo. Atualmente, \"cdrom\" e \"disk\" são suportados."
-
-msgid "Storage Pool"
-msgstr "storage pool"
-
-msgid "Storage pool which volume located in"
-msgstr "Conjunto de armazenamentos no qual o volume está localizado"
-
-msgid "Storage Volume"
-msgstr "Volume de Armazenamento"
-
-msgid "Storage volume to be attached"
-msgstr "Volume de armazenamento a ser conectado"
-
-msgid "File Path"
-msgstr "Caminho do arquivo"
-
-msgid "The ISO file path in the server for CDROM."
-msgstr "O caminho do arquivo ISO no servidor para o CDROM."
-
-msgid "Attach"
-msgstr "Anexar"
-
msgid "Start"
msgstr "Iniciar"
@@ -1593,6 +1576,37 @@ msgstr "Encerrar"
msgid "Delete"
msgstr "Excluir"
+msgid "Add a Storage Device to VM"
+msgstr "Incluir um Dispositivo de Armazenamento na VM"
+
+msgid "Device Type"
+msgstr "Tipo de Dispositivo"
+
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgstr ""
+"O tipo de dispositivo. Atualmente, \"cdrom\" e \"disk\" são suportados."
+
+msgid "Storage Pool"
+msgstr "storage pool"
+
+msgid "Storage pool which volume located in"
+msgstr "Conjunto de armazenamentos no qual o volume está localizado"
+
+msgid "Storage Volume"
+msgstr "Volume de Armazenamento"
+
+msgid "Storage volume to be attached"
+msgstr "Volume de armazenamento a ser conectado"
+
+msgid "File Path"
+msgstr "Caminho do arquivo"
+
+msgid "The ISO file path in the server for CDROM."
+msgstr "O caminho do arquivo ISO no servidor para o CDROM."
+
+msgid "Attach"
+msgstr "Anexar"
+
msgid "The username or password you entered is incorrect. Please try again."
msgstr "O nome de usuário ou senha inserido está incorreto. Tente novamente."
@@ -1614,6 +1628,9 @@ msgstr "Máquinas Virtuais"
msgid "Templates"
msgstr "Modelos"
+msgid "Administration"
+msgstr "Administração"
+
msgid "Failed to get application configuration"
msgstr "Falha ao obter a configuração do aplicativo"
@@ -2144,21 +2161,6 @@ msgstr "Sim"
msgid "No"
msgstr "Não"
-msgid "Add a Volume to Storage Pool"
-msgstr "Incluir um volume no conjunto de armazenamentos"
-
-msgid "Fetch from remote URL"
-msgstr "Buscar a partir da URL remota"
-
-msgid "Enter the remote URL here."
-msgstr "Inserir a URL remota aqui."
-
-msgid "Upload a file"
-msgstr "Fazer upload de um arquivo"
-
-msgid "Choose the file you want to upload."
-msgstr "Escolher o arquivo do qual você deseja fazer upload."
-
msgid "Define a New Storage Pool"
msgstr "Definir um novo storage pool"
@@ -2235,6 +2237,21 @@ msgstr "Adaptador SCSI"
msgid "Please, wait..."
msgstr "Aguarde..."
+msgid "Add a Volume to Storage Pool"
+msgstr "Incluir um volume no conjunto de armazenamentos"
+
+msgid "Fetch from remote URL"
+msgstr "Buscar a partir da URL remota"
+
+msgid "Enter the remote URL here."
+msgstr "Inserir a URL remota aqui."
+
+msgid "Upload a file"
+msgstr "Fazer upload de um arquivo"
+
+msgid "Choose the file you want to upload."
+msgstr "Escolher o arquivo do qual você deseja fazer upload."
+
msgid "Add Template"
msgstr "Incluir modelo"
@@ -2435,3 +2452,10 @@ msgstr "Alocação"
msgid "No templates found."
msgstr "Nenhum modelo localizado."
+
+#~ msgid ""
+#~ "Unable to get access metadata of virtual machine %(name)s. Details: "
+#~ "%(err)s"
+#~ msgstr ""
+#~ "Não é possível obter metadados de acesso da máquina virtual %(name)s. "
+#~ "Detalhes: %(err)s "
diff --git a/po/ru_RU.po b/po/ru_RU.po
index 804ff61..1dc4421 100644
--- a/po/ru_RU.po
+++ b/po/ru_RU.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -286,13 +286,6 @@ msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr ""
"Не удалось завершить работу виртуальной машины %(name)s. Сведения: %(err)s"
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr ""
-"Не удалось получить доступ к метаданным виртуальной машины %(name)s. "
-"Сведения: %(err)s"
-
msgid "The guest console password must be a string."
msgstr "Пароль консоли гостевой системы должен быть строкой."
@@ -378,6 +371,21 @@ msgstr ""
msgid "Error attaching memory device. Details: %(error)s"
msgstr "Ошибка подключения устройства памяти. Сведения: %(error)s"
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
@@ -1421,6 +1429,12 @@ msgstr "MAC-адрес"
msgid "Available system users and groups"
msgstr "Доступные системные пользователи и группы"
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr "Выбранные системные пользователи и группы"
@@ -1463,36 +1477,6 @@ msgstr "Отмена"
msgid "revert"
msgstr "восстановить"
-msgid "Add a Storage Device to VM"
-msgstr "Добавить устройство хранения в VM"
-
-msgid "Device Type"
-msgstr "Тип устройства"
-
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
-msgstr "Тип устройства. В данный момент поддерживается \"cdrom\" и \"disk\"."
-
-msgid "Storage Pool"
-msgstr "Пул памяти"
-
-msgid "Storage pool which volume located in"
-msgstr "Том пула памяти находится в"
-
-msgid "Storage Volume"
-msgstr "Том памяти"
-
-msgid "Storage volume to be attached"
-msgstr "Том подключен"
-
-msgid "File Path"
-msgstr "Путь к файлу"
-
-msgid "The ISO file path in the server for CDROM."
-msgstr "Путь к файлу ISO для CDROM на сервере."
-
-msgid "Attach"
-msgstr "Подключить"
-
msgid "Start"
msgstr "Запустить"
@@ -1526,6 +1510,36 @@ msgstr "Завершить работу"
msgid "Delete"
msgstr "Удалить"
+msgid "Add a Storage Device to VM"
+msgstr "Добавить устройство хранения в VM"
+
+msgid "Device Type"
+msgstr "Тип устройства"
+
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgstr "Тип устройства. В данный момент поддерживается \"cdrom\" и \"disk\"."
+
+msgid "Storage Pool"
+msgstr "Пул памяти"
+
+msgid "Storage pool which volume located in"
+msgstr "Том пула памяти находится в"
+
+msgid "Storage Volume"
+msgstr "Том памяти"
+
+msgid "Storage volume to be attached"
+msgstr "Том подключен"
+
+msgid "File Path"
+msgstr "Путь к файлу"
+
+msgid "The ISO file path in the server for CDROM."
+msgstr "Путь к файлу ISO для CDROM на сервере."
+
+msgid "Attach"
+msgstr "Подключить"
+
msgid "The username or password you entered is incorrect. Please try again."
msgstr "Указано неверное имя пользователя или пароль. Введите еще раз."
@@ -1547,6 +1561,9 @@ msgstr "Гостевые системы"
msgid "Templates"
msgstr "Шаблоны"
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr "Не удалось получить конфигурацию приложения"
@@ -2069,21 +2086,6 @@ msgstr "Да"
msgid "No"
msgstr "Нет"
-msgid "Add a Volume to Storage Pool"
-msgstr "Добавить том в пул памяти"
-
-msgid "Fetch from remote URL"
-msgstr "Загрузить с удаленного URL"
-
-msgid "Enter the remote URL here."
-msgstr "Введите сюда удаленный URL."
-
-msgid "Upload a file"
-msgstr "Передать файл"
-
-msgid "Choose the file you want to upload."
-msgstr "Выберите файл для передачи."
-
msgid "Define a New Storage Pool"
msgstr "Создать пул памяти"
@@ -2156,6 +2158,21 @@ msgstr "Адаптер SCSI"
msgid "Please, wait..."
msgstr "Подождите..."
+msgid "Add a Volume to Storage Pool"
+msgstr "Добавить том в пул памяти"
+
+msgid "Fetch from remote URL"
+msgstr "Загрузить с удаленного URL"
+
+msgid "Enter the remote URL here."
+msgstr "Введите сюда удаленный URL."
+
+msgid "Upload a file"
+msgstr "Передать файл"
+
+msgid "Choose the file you want to upload."
+msgstr "Выберите файл для передачи."
+
msgid "Add Template"
msgstr "Добавить шаблон"
@@ -2356,3 +2373,10 @@ msgstr "Выделение"
msgid "No templates found."
msgstr "Не найдены шаблоны."
+
+#~ msgid ""
+#~ "Unable to get access metadata of virtual machine %(name)s. Details: "
+#~ "%(err)s"
+#~ msgstr ""
+#~ "Не удалось получить доступ к метаданным виртуальной машины %(name)s. "
+#~ "Сведения: %(err)s"
diff --git a/po/zh_CN.po b/po/zh_CN.po
index 7d3723b..97ddf56 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -273,11 +273,6 @@ msgstr "组“%(groups)s”不存在"
msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr "无法关闭虚拟机 %(name)s。详细信息:%(err)s"
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr "无法访问虚拟机 %(name)s 的元数据。详细信息:%(err)s"
-
msgid "The guest console password must be a string."
msgstr "访客控制台密码必须是字符串。"
@@ -346,6 +341,21 @@ msgstr "主机的 Libvirt 版本不支持内存设备。Libvirt 必须 >= 1.2.14
msgid "Error attaching memory device. Details: %(error)s"
msgstr "连接内存设备时出错。详细信息:%(error)s"
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
@@ -1292,6 +1302,12 @@ msgstr "MAC 地址"
msgid "Available system users and groups"
msgstr "可用系统用户和组"
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr "所选系统用户和组"
@@ -1334,36 +1350,6 @@ msgstr "取消"
msgid "revert"
msgstr "还原"
-msgid "Add a Storage Device to VM"
-msgstr "将存储设备添加至 VM"
-
-msgid "Device Type"
-msgstr "设备类型"
-
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
-msgstr "设备类型。当前支持“cdrom”和“disk”。"
-
-msgid "Storage Pool"
-msgstr "存储池"
-
-msgid "Storage pool which volume located in"
-msgstr "卷位于以下位置的存储池"
-
-msgid "Storage Volume"
-msgstr "存储卷"
-
-msgid "Storage volume to be attached"
-msgstr "要连接的存储卷"
-
-msgid "File Path"
-msgstr "文件路径"
-
-msgid "The ISO file path in the server for CDROM."
-msgstr "CDROM 服务器中的 ISO 文件。"
-
-msgid "Attach"
-msgstr "连接"
-
msgid "Start"
msgstr "启动"
@@ -1397,6 +1383,36 @@ msgstr "关闭"
msgid "Delete"
msgstr "删除"
+msgid "Add a Storage Device to VM"
+msgstr "将存储设备添加至 VM"
+
+msgid "Device Type"
+msgstr "设备类型"
+
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgstr "设备类型。当前支持“cdrom”和“disk”。"
+
+msgid "Storage Pool"
+msgstr "存储池"
+
+msgid "Storage pool which volume located in"
+msgstr "卷位于以下位置的存储池"
+
+msgid "Storage Volume"
+msgstr "存储卷"
+
+msgid "Storage volume to be attached"
+msgstr "要连接的存储卷"
+
+msgid "File Path"
+msgstr "文件路径"
+
+msgid "The ISO file path in the server for CDROM."
+msgstr "CDROM 服务器中的 ISO 文件。"
+
+msgid "Attach"
+msgstr "连接"
+
msgid "The username or password you entered is incorrect. Please try again."
msgstr "您所输入的用户名或密码不正确。请重试。"
@@ -1418,6 +1434,9 @@ msgstr "访客"
msgid "Templates"
msgstr "模板"
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr "未能获取应用程序配置"
@@ -1909,21 +1928,6 @@ msgstr "是"
msgid "No"
msgstr "否"
-msgid "Add a Volume to Storage Pool"
-msgstr "将卷添加至存储池"
-
-msgid "Fetch from remote URL"
-msgstr "从远程 URL 中访存"
-
-msgid "Enter the remote URL here."
-msgstr "在此处输入远程 URL。"
-
-msgid "Upload a file"
-msgstr "上载文件"
-
-msgid "Choose the file you want to upload."
-msgstr "选择要上载的文件。"
-
msgid "Define a New Storage Pool"
msgstr "定义新存储池"
@@ -1994,6 +1998,21 @@ msgstr "SCSI 适配器"
msgid "Please, wait..."
msgstr "请稍等..."
+msgid "Add a Volume to Storage Pool"
+msgstr "将卷添加至存储池"
+
+msgid "Fetch from remote URL"
+msgstr "从远程 URL 中访存"
+
+msgid "Enter the remote URL here."
+msgstr "在此处输入远程 URL。"
+
+msgid "Upload a file"
+msgstr "上载文件"
+
+msgid "Choose the file you want to upload."
+msgstr "选择要上载的文件。"
+
msgid "Add Template"
msgstr "添加模板"
@@ -2194,3 +2213,8 @@ msgstr "分配"
msgid "No templates found."
msgstr "找不到任何模板。"
+
+#~ msgid ""
+#~ "Unable to get access metadata of virtual machine %(name)s. Details: "
+#~ "%(err)s"
+#~ msgstr "无法访问虚拟机 %(name)s 的元数据。详细信息:%(err)s"
diff --git a/po/zh_TW.po b/po/zh_TW.po
index c772f99..e169167 100644
--- a/po/zh_TW.po
+++ b/po/zh_TW.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-08-21 09:52-0300\n"
+"POT-Creation-Date: 2015-09-10 10:00-0300\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
@@ -85,9 +85,8 @@ msgstr "\"_passthrough\" 應該是 \"true\" 或 \"false\""
msgid "\"_passthrough_affected_by\" should be a device name string"
msgstr "\"_passthrough_affected_by\" 應該是裝置名稱字串"
-#, fuzzy
msgid "\"_available_only\" should be \"true\" or \"false\""
-msgstr "\"_passthrough\" 應該是 \"true\" 或 \"false\""
+msgstr ""
#, python-format
msgid "Error while getting block devices. Details: %(err)s"
@@ -274,11 +273,6 @@ msgstr "群組 '%(groups)s' 不存在"
msgid "Unable to shutdown virtual machine %(name)s. Details: %(err)s"
msgstr "無法關閉虛擬機器 %(name)s。詳細資料:%(err)s"
-#, python-format
-msgid ""
-"Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"
-msgstr "無法取得虛擬機器 %(name)s 的存取 meta 資料。詳細資料:%(err)s"
-
msgid "The guest console password must be a string."
msgstr "客體主控台密碼必須是字串。"
@@ -348,6 +342,21 @@ msgstr "主機的 libvirt 版本不支援記憶體裝置。Libvirt 必須大於
msgid "Error attaching memory device. Details: %(error)s"
msgstr "連接記憶體裝置時發生錯誤。詳細資料:%(error)s"
+msgid "Cannot start %(name)s. Virtual machine is already running."
+msgstr ""
+
+#, python-format
+msgid "Cannot power off %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot shutdown %(name)s. Virtual machine is shut off."
+msgstr ""
+
+#, python-format
+msgid "Cannot reset %(name)s. Virtual machine is already shut off."
+msgstr ""
+
#, python-format
msgid ""
"VM %(vmid)s does not contain directly assigned host device %(dev_name)s."
@@ -1293,6 +1302,12 @@ msgstr "MAC 位址"
msgid "Available system users and groups"
msgstr "可用的系統使用者及群組"
+msgid "Users"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
msgid "Selected system users and groups"
msgstr "選定的系統使用者及群組"
@@ -1335,36 +1350,6 @@ msgstr "取消 "
msgid "revert"
msgstr "回復"
-msgid "Add a Storage Device to VM"
-msgstr "將儲存裝置新增至 VM"
-
-msgid "Device Type"
-msgstr "裝置類型"
-
-msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
-msgstr "裝置類型。目前支援 \"cdrom\" 和 \"disk\"。"
-
-msgid "Storage Pool"
-msgstr "儲存區"
-
-msgid "Storage pool which volume located in"
-msgstr "磁區所在的儲存區"
-
-msgid "Storage Volume"
-msgstr "儲存磁區"
-
-msgid "Storage volume to be attached"
-msgstr "要連接的儲存磁區"
-
-msgid "File Path"
-msgstr "檔案路徑"
-
-msgid "The ISO file path in the server for CDROM."
-msgstr "CDROM 的 ISO 檔案路徑在伺服器中。"
-
-msgid "Attach"
-msgstr "連接"
-
msgid "Start"
msgstr "開始"
@@ -1398,6 +1383,36 @@ msgstr "關閉"
msgid "Delete"
msgstr "刪除"
+msgid "Add a Storage Device to VM"
+msgstr "將儲存裝置新增至 VM"
+
+msgid "Device Type"
+msgstr "裝置類型"
+
+msgid "The device type. Currently, \"cdrom\" and \"disk\" are supported."
+msgstr "裝置類型。目前支援 \"cdrom\" 和 \"disk\"。"
+
+msgid "Storage Pool"
+msgstr "儲存區"
+
+msgid "Storage pool which volume located in"
+msgstr "磁區所在的儲存區"
+
+msgid "Storage Volume"
+msgstr "儲存磁區"
+
+msgid "Storage volume to be attached"
+msgstr "要連接的儲存磁區"
+
+msgid "File Path"
+msgstr "檔案路徑"
+
+msgid "The ISO file path in the server for CDROM."
+msgstr "CDROM 的 ISO 檔案路徑在伺服器中。"
+
+msgid "Attach"
+msgstr "連接"
+
msgid "The username or password you entered is incorrect. Please try again."
msgstr "您輸入的使用者名稱或密碼不正確。請重試。"
@@ -1419,6 +1434,9 @@ msgstr "客體"
msgid "Templates"
msgstr "範本"
+msgid "Administration"
+msgstr ""
+
msgid "Failed to get application configuration"
msgstr "無法取得應用程式配置"
@@ -1911,21 +1929,6 @@ msgstr "是"
msgid "No"
msgstr "否"
-msgid "Add a Volume to Storage Pool"
-msgstr "將磁區新增至儲存區"
-
-msgid "Fetch from remote URL"
-msgstr "從遠端 URL 提取"
-
-msgid "Enter the remote URL here."
-msgstr "在這裡輸入遠端 URL。"
-
-msgid "Upload a file"
-msgstr "上傳檔案"
-
-msgid "Choose the file you want to upload."
-msgstr "選擇要上傳的檔案。"
-
msgid "Define a New Storage Pool"
msgstr "定義新的儲存區"
@@ -1996,6 +1999,21 @@ msgstr "SCSI 配接卡"
msgid "Please, wait..."
msgstr "請稍候..."
+msgid "Add a Volume to Storage Pool"
+msgstr "將磁區新增至儲存區"
+
+msgid "Fetch from remote URL"
+msgstr "從遠端 URL 提取"
+
+msgid "Enter the remote URL here."
+msgstr "在這裡輸入遠端 URL。"
+
+msgid "Upload a file"
+msgstr "上傳檔案"
+
+msgid "Choose the file you want to upload."
+msgstr "選擇要上傳的檔案。"
+
msgid "Add Template"
msgstr "新增範本"
@@ -2196,3 +2214,8 @@ msgstr "配置"
msgid "No templates found."
msgstr "找不到範本。"
+
+#~ msgid ""
+#~ "Unable to get access metadata of virtual machine %(name)s. Details: "
+#~ "%(err)s"
+#~ msgstr "無法取得虛擬機器 %(name)s 的存取 meta 資料。詳細資料:%(err)s"
diff --git a/ui/pages/guest-edit.html.tmpl b/ui/pages/guest-edit.html.tmpl
index 389f3c5..a56d96c 100644
--- a/ui/pages/guest-edit.html.tmpl
+++ b/ui/pages/guest-edit.html.tmpl
@@ -122,8 +122,8 @@
<input type="text" id="permission-avail-searchBox">
<div class="body">
<div class="head">
- <div class="column column-user"><div class="item">Users</div></div>
- <div class="column column-group"><div class="item">Groups</div></div>
+ <div class="column column-user"><div class="item">$_("Users")</div></div>
+ <div class="column column-group"><div class="item">$_("Groups")</div></div>
</div>
<div id="permission-avail-users" class="column column-user"></div>
<div id="permission-avail-groups" class="column column-group"></div>
@@ -138,8 +138,8 @@
<input type="text" id="permission-sel-searchBox">
<div class="body">
<div class="head">
- <div class="column column-user"><div class="item">Users</div></div>
- <div class="column column-group"><div class="item">Groups</div></div>
+ <div class="column column-user"><div class="item">$_("Users")</div></div>
+ <div class="column column-group"><div class="item">$_("Groups")</div></div>
</div>
<div id="permission-sel-users" class="column column-user"></div>
<div id="permission-sel-groups" class="column column-group"></div>
--
2.1.0
2
1
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
configure.ac | 1 +
ui/Makefile.am | 2 +-
ui/base64/Makefile.am | 19 +++++++++++++++++++
3 files changed, 21 insertions(+), 1 deletion(-)
create mode 100644 ui/base64/Makefile.am
diff --git a/configure.ac b/configure.ac
index 0261b5f..9eee3c7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -105,6 +105,7 @@ AC_CONFIG_FILES([
plugins/sample/ui/js/Makefile
plugins/sample/ui/pages/Makefile
ui/Makefile
+ ui/base64/Makefile
ui/css/Makefile
ui/images/Makefile
ui/images/theme-default/Makefile
diff --git a/ui/Makefile.am b/ui/Makefile.am
index d541355..2209ada 100644
--- a/ui/Makefile.am
+++ b/ui/Makefile.am
@@ -15,7 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-SUBDIRS = css images js libs pages spice-html5
+SUBDIRS = base64 css images js libs pages spice-html5
uidir = $(datadir)/kimchi/ui
diff --git a/ui/base64/Makefile.am b/ui/base64/Makefile.am
new file mode 100644
index 0000000..6ce3ca8
--- /dev/null
+++ b/ui/base64/Makefile.am
@@ -0,0 +1,19 @@
+#
+# Kimchi
+#
+# Copyright IBM, Corp. 2015
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+jquerybase64dir = $(datadir)/kimchi/ui/base64
+dist_jquerybase64_DATA = $(wildcard *.js) $(NULL)
--
2.1.0
2
2
Hi team,
As we discussed on the chat today, I need help creating a widget for the new-ui while I'm still working on the current Grid widget for Hosts, Storage and Network tabs.
According to the new-ui design spec doc, in the Host tab, Debug Reports and Repositories panels shouldn't be presented as tables but as lists with each line having a drop-down button on the right instead of one on the top below the panel header:
[cid:image001.png@01D0E586.91066C40]
This is the current json response for Repositories:
[
{
"config":{
"gpgcheck":false,
"gpgkey":"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch",
"metalink":"https://mirrors.fedoraproject.org/metalink?repo=fedora-$releasever&arch=$ba…",
"mirrorlist":"",
"repo_name":"Fedora $releasever - $basearch"
},
"repo_id":"fedora",
"enabled":true,
"baseurl":""
}
]
And Debug Reports:
[
{
"time":"2015-08-31-15:13:10",
"name":"test2",
"uri":"plugins/kimchi/data/debugreports/test2.tar.xz"
},
{
"time":"2015-08-31-15:12:57",
"name":"test",
"uri":"plugins/kimchi/data/debugreports/test.tar.xz"
}
]
I've planned the following HTML output for this widget:
- A <i class="fa fa-power-off"></i> tag, empty when repository is not enabled;
- repo_id: Displays the repository name
- repo_name: Repository description
- Bootstrap modal button (don't worry about styles since I'm still customizing it)
Reference: http://plnkr.co/edit/SgQcpfu5b5vVXWD9imND?p=preview
For debug reports lists:
-name;
-time;
- Bootstrap modal button (don't worry about styles since I'm still customizing it)
It is very important that we keep the same model from kimchi.hosts.js by replacing only the current widget with the new one (example: repositoriesGrid = new kimchi.widget.Grid({ }); to repositoriesGrid = new kimchi.widget.List({ }); ).
I don't think it is necessary to fetch the whole branch and apply the patches to edit kimchi files, this can be done using www.jsfiddle.net<http://www.jsfiddle.net>, www.jsbin.com<http://www.jsbin.com> , http://plnkr.co/ or http://codepen.io/ (I suggest plnkr or jsfiddle since we can easily include jQuery, FonAwesome and Bootstrap from their CDN servers).
Thanks,
Samuel
2
2
[PATCH 16/17] Ginger Base : base plugin ui/config, make and robot files
by chandra@linux.vnet.ibm.com 11 Sep '15
by chandra@linux.vnet.ibm.com 11 Sep '15
11 Sep '15
From: chandrureddy <chandra(a)linux.vnet.ibm.com>
---
plugins/gingerbase/ui/Makefile.am | 20 ++++++++++++++++++++
plugins/gingerbase/ui/config/Makefile.am | 22 ++++++++++++++++++++++
plugins/gingerbase/ui/config/tab-ext.xml | 10 ++++++++++
plugins/gingerbase/ui/robots.txt | 2 ++
4 files changed, 54 insertions(+)
create mode 100644 plugins/gingerbase/ui/Makefile.am
create mode 100644 plugins/gingerbase/ui/config/Makefile.am
create mode 100644 plugins/gingerbase/ui/config/tab-ext.xml
create mode 100644 plugins/gingerbase/ui/robots.txt
diff --git a/plugins/gingerbase/ui/Makefile.am b/plugins/gingerbase/ui/Makefile.am
new file mode 100644
index 0000000..e9d7b94
--- /dev/null
+++ b/plugins/gingerbase/ui/Makefile.am
@@ -0,0 +1,20 @@
+#
+# Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+SUBDIRS = config css images js libs pages
+
+uidir = $(datadir)/wok/plugins/gingerbase/ui
diff --git a/plugins/gingerbase/ui/config/Makefile.am b/plugins/gingerbase/ui/config/Makefile.am
new file mode 100644
index 0000000..d750a99
--- /dev/null
+++ b/plugins/gingerbase/ui/config/Makefile.am
@@ -0,0 +1,22 @@
+#
+# Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+xmldir = $(datadir)/wok/plugins/gingerbase/ui/config
+
+dist_xml_DATA = \
+ tab-ext.xml \
+ $(NULL)
diff --git a/plugins/gingerbase/ui/config/tab-ext.xml b/plugins/gingerbase/ui/config/tab-ext.xml
new file mode 100644
index 0000000..2d2d9bd
--- /dev/null
+++ b/plugins/gingerbase/ui/config/tab-ext.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<tabs-ext>
+ <tab>
+ <access role="admin" mode="admin"/>
+ <access role="user" mode="none"/>
+
+ <title>Host</title>
+ <path>plugins/gingerbase/host.html</path>
+ </tab>
+</tabs-ext>
diff --git a/plugins/gingerbase/ui/robots.txt b/plugins/gingerbase/ui/robots.txt
new file mode 100644
index 0000000..1f53798
--- /dev/null
+++ b/plugins/gingerbase/ui/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
--
2.1.0
3
2
11 Sep '15
From: chandrureddy <chandra(a)linux.vnet.ibm.com>
---
plugins/gingerbase/ui/images/Makefile.am | 22 ++++++++++++++++++++++
plugins/gingerbase/ui/images/icon-centos.png | Bin 0 -> 4734 bytes
plugins/gingerbase/ui/images/icon-debian.png | Bin 0 -> 4239 bytes
plugins/gingerbase/ui/images/icon-fedora.png | Bin 0 -> 4449 bytes
plugins/gingerbase/ui/images/icon-gentoo.png | Bin 0 -> 15307 bytes
plugins/gingerbase/ui/images/icon-opensuse.png | Bin 0 -> 3046 bytes
plugins/gingerbase/ui/images/icon-ubuntu.png | Bin 0 -> 4818 bytes
plugins/gingerbase/ui/images/icon-vm.png | Bin 0 -> 2976 bytes
plugins/gingerbase/ui/images/logo.ico | Bin 0 -> 1214 bytes
9 files changed, 22 insertions(+)
create mode 100644 plugins/gingerbase/ui/images/Makefile.am
create mode 100644 plugins/gingerbase/ui/images/icon-centos.png
create mode 100644 plugins/gingerbase/ui/images/icon-debian.png
create mode 100644 plugins/gingerbase/ui/images/icon-fedora.png
create mode 100644 plugins/gingerbase/ui/images/icon-gentoo.png
create mode 100644 plugins/gingerbase/ui/images/icon-opensuse.png
create mode 100644 plugins/gingerbase/ui/images/icon-ubuntu.png
create mode 100644 plugins/gingerbase/ui/images/icon-vm.png
create mode 100644 plugins/gingerbase/ui/images/logo.ico
diff --git a/plugins/gingerbase/ui/images/Makefile.am b/plugins/gingerbase/ui/images/Makefile.am
new file mode 100644
index 0000000..99defe8
--- /dev/null
+++ b/plugins/gingerbase/ui/images/Makefile.am
@@ -0,0 +1,22 @@
+#
+# Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+SUBDIRS = theme-default
+
+imagedir = $(datadir)/wok/plugins/gingerbase/ui/images
+
+dist_image_DATA = *.png *.ico
diff --git a/plugins/gingerbase/ui/images/icon-centos.png b/plugins/gingerbase/ui/images/icon-centos.png
new file mode 100644
index 0000000000000000000000000000000000000000..5afb7b4b63462412d825f63542beb1f7e26b3d7f
GIT binary patch
literal 4734
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
M07*qoM6N<$f~4IfuK)l5
literal 0
HcmV?d00001
diff --git a/plugins/gingerbase/ui/images/icon-debian.png b/plugins/gingerbase/ui/images/icon-debian.png
new file mode 100644
index 0000000000000000000000000000000000000000..ff49a39696fdfa5f232b0e79de04d90af6557111
GIT binary patch
literal 4239
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
D^V29c
literal 0
HcmV?d00001
diff --git a/plugins/gingerbase/ui/images/icon-fedora.png b/plugins/gingerbase/ui/images/icon-fedora.png
new file mode 100644
index 0000000000000000000000000000000000000000..7b9dd06d1f766a3285cd0fb587c0be4114bfac47
GIT binary patch
literal 4449
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
S9$oyI0D9VncWN{pBmW0TFKp8Q
literal 0
HcmV?d00001
diff --git a/plugins/gingerbase/ui/images/icon-gentoo.png b/plugins/gingerbase/ui/images/icon-gentoo.png
new file mode 100644
index 0000000000000000000000000000000000000000..50d928fbfa30560fe873cf3de438a8f0c10a8f96
GIT binary patch
literal 15307
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
literal 0
HcmV?d00001
diff --git a/plugins/gingerbase/ui/images/icon-opensuse.png b/plugins/gingerbase/ui/images/icon-opensuse.png
new file mode 100644
index 0000000000000000000000000000000000000000..83fe4d5df0f59b52dd609b3bd16838f4d2570ab8
GIT binary patch
literal 3046
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
literal 0
HcmV?d00001
diff --git a/plugins/gingerbase/ui/images/icon-ubuntu.png b/plugins/gingerbase/ui/images/icon-ubuntu.png
new file mode 100644
index 0000000000000000000000000000000000000000..d92c00f357b6b0e1542413f6aaf0de8ef4b8d130
GIT binary patch
literal 4818
zcmb7Ig;!Kv_a1VFPU&ut7*J4<Zs`<6YUl>(W>CbDj+c1pW*7<S96}_eTUzNFy6YQ%
z@dtc&t$oir=j^l3UF+`W+3`h3ON9uZ4j%vj5WQAa)I*QZzXHNVzq_R&KhXoWm;7r3
z5c&xQ*}%|qJP*~kUTFQ<zk(splj)CMr1e%Z_SSc|^Y(*y+5-Ii`~)4{oV~0e9=3w+
zp7xn2Njd<4T>G`6oPmGVL3V(@!Sqs}Os#FrXATWaMN$=b)07FBLtU{UzX%&9N-x^P
zuR8vzp=_|DfH9SBZi_*klK;~tgHLRPM~JBk1O6sS7z>z<6>lIR!lq`#cSQ!pX_q;a
z-MlrTk$x>XDoLH>HF`ZPzLFI*D&BGW3ioxAYGNDBnyYNoRT8;uQu2Qx)^Uv(fxkWH
zPM~CiTLv2@gzJD`jFo^`XlZvn{v1(og{j{{HtNQBp7|+Z-%rnPKOxTmh2aB(ThF_c
z_AzJpa$S8(Z=2vv(gJ|AZaW-7KnWlj8ZOM<(H!$5^g?wd5+yXY?{e3n2JefH@s@
zXCxhd*m~?g4suaM(axZ4x4c}SAPZ6cyASjSG%@!_)r#jkU@`$y=d#_j+hTPUz7uqA
ze0vLBfHuqOW)17AiyrYDwJq&j^em(=s~ThQ2>BRcoQ5Z_xoFkx12+LxoV)7Q^7!a8
zu~<}?>}#&kEDcyB^VulBHCI(5ay9f#uApy(O+3pD+dsHg-?UDKfR`f~*df@fo^xv9
zjS1Y&arOvknQv*3a~n}O_P_YP;x3cn0t}C_Bqx501;JZQ6thfd^R(f8(3PHH*i{WE
z-PW(=<FN>f8>WZ2%dbwpb=o!?J4Wy@%KJrc;ZYL^txcr#@W=i~n+GGfsF*$AUA;{;
zMY@e&3kj@RRZ5r|`V&I`aq+4lor<4S-<&l--<{k3?s<&(HO&xJ8}M@M?6=IZi+OBE
z3x9^oQa{_3;<sibxjqI@*!9A8cWkcsu6)$i9nnY8!oC$Com1J(y-ywL=6S~cQs=hu
z1-38+^kis7jTv=O(LG`__Q!{2;#rU;cfh-{EUzaOuL|jQ4zSg%&$URCA$cba9Z}MY
zR{*8jN4kb%xlnGNw(GD0flWfo6g~SEimw6CzU?*l+Zp|lUJ(xm>>ZV)_cL=2(U}pZ
z_AQ02fPe7frDaA&CETrkTo&a9#-*}_QAZE3nc?JV912d>#u!6X_pXbpUc@xxm_(bE
zOy#?6Z&D)o_uFuc&-oOHaFc2GV2SC`pDhLXx)wXIy~gp<T~DSh8%U3)$j%rzx68VX
zPzA1sTXi9)w98l)?AIF`ca{pS>z{z4+DC808%>E99FSBAXJ`24$_!f<6{T647dS>q
z_orp)C-^~{&Vme}Pc^R~HzcE+_TPN~*iOV=N0GIOchYSusHnNfO>HqHH-EpV#68as
zu%VWMpJ1K0qhTuky$~=7jr_>9QGyhTkDt>jSQW9kqB^PK{;{inbxY=l$Q2NQBA7Bf
zU8)KNC4F;zAUHqbCVe2z^FQII%&ww4;aX9CID7k@j+tdPzwPsDIj6Kyqv^b)6_xX(
zRz)%CPWfWDPX&GItA;-YllvTY&sJIt)j?ukmO2uT*DNe)y~zQZ?_TKCuX1s?3z@HW
z0aS9@(oz}UMa+krmKuE-F@OUofg{&!r-Yx%^3vthXA*@^?k;k1q<p3-%=sEvK89n&
zw?{QQfB~A!NX;IswAFyE6fLV{%uvZpBYoe!X8+|7!*ZgPXBK7F5KssZ?Z$o>VN|IQ
zP&xYuq;oM1{DgAt>B?ySLLb{T5<tW6NNC>h<P^#(|MQHwEYI`e$<^RR!{d;pR=4Dl
z;)hrjJh3TOoIClSgkA0jT9=arA`~-NJfXK<N*mNsS$G|GV}I(?@z*gTi`Q*V1X~O5
zFOx>q4^C7*r8%QyU~<v3i2)-o3AqA}IIfXs%j9scKEyG`CZCT0Hbe>ymRqSKSi+F}
z*x_yvPZLC|F;~4}_@5=cs7F<$+flU~4=cueM=n8*uHM4uVK;)qkH1tG+J(R*FUiyg
z28`9TdW|lKi1sWdiWiCRx=`LK$hk2xR!p4+=al@lxa{)kWZuwi9Prs4+u~Ih9fG;F
z`2KOHG}dZtjb4HqANOdMp*O7PLuaDeTW}Yk<c{rHUq@p;ff*{N(8u%IK%nVMN&Jjb
zu8MyKw2Q4F|4Rla0~QzSE@jK1bSXnobBs~br`1&p!4a?s#+Kcy)ONgaVEnh3*reks
zq4#oj?Cg*Tn?C-+B$c-+VaQ96!|P0FfX0WlHwM&^+9f+aWM2lCR$O?ItK?f1Sd*}K
zcWCmAd>X5b*fd|fkO@ppRI*++oPKjok~tIPL9*fZP;ejMvOCMn@kKU7)4(><lc2xV
z3O68anS26+gEG9uY4ETa6n35uAAHj#v#)iTV)AU9A7C4OL3TD*><rd9`Jr51npJU=
znnBH!|1i82kpUTO)b;4@UXRJ88V_*|k}17IMJsB97z@d^t9+bWV7i?++jUB>whAni
zrmm72i5?-x&RpRVFHqd5lEC9^RaXJTVJ)#tw5hF`9Iz&F&zyn{8>}SDv;?->_-hz3
z38atL6TkJ$ctDzycmW)O4}As?nV1v%PpWcbg`a8*B|o1<a$)dzO}6;oP-=wn?DH)P
z-%Wq!>2PPOu-G}&*}Hdpky0zft+VweW%(Z?)d!!J>u!yS7U{ftAWbYW<n2Ucjv9*d
z1w!p<y_mlEo=C9m3QUZhqkRyIA#|PYgTU^cm>2iG@;RSX1finaNBB4&bb#*gFp;6}
z%#A=QA0F_YsXfa599(ELe-ZK}=r|iOL8oEo$-SJH#+xzE@*CR{{O`cSwSl4Iy9W1s
z$i3${7reyrYjR2a%9|9ipnb~eg1Ce$knERkZV5_yTJ}++^L-Mr>9x>5ZsR>F8pB?-
zcw(PPfHs4TX#B&G5kYIuiv<eg)k(ne-ALAfcG;yx<?hIoD`&oQFe}@gXv);1!qOkp
zxWPjz9Nk7GdAv?bk!10}Hbh;{@K;4@W>KO{BRMdsn3>&956#2``j8;zOVJ5>eokM{
zUJLa{T7n%JYgX~KsvbQn_eUh<k=NNYclYLG%c;y2@j-N*{}O15q2zk<2o=~H!#@$J
zZ0hO+lh7B4mO~iYe=E}UxOWLyYTr}Kq%1<a2;8h1FtD|;{Z-qF@q)c3H1Ku*on)qY
z+`I$w=x)VHq%YzS$^Y^5UbQM^8k20w&&IB#-n?S16T#f^Q<1M-s>>VCXzHfEbS2?J
zgLcf2aeJ8!5{0$KDkgre`YD135x)Z|nd@(nR2c9RHQuIBu^EVcq^3QiFBi`2C@#y~
zgktxC6v^QS&GYY!GD@WIuW#yi9cT-p0R<PN6Kv3L1hT2*LEL^79_wblDz~rX;2k_+
zh6Y{KRv#`Gs=k-JrY}dUn#W9@5R<;~*(r_xGSu2@$#H)sd~(4*YouB+ywqTQiy-Iv
zVgADDK?JBDnz$cNdgRxPn}8Y0k!uuRP>8~)1F@9vmUoXvz0}`3sy%WsC+Y%J1T`cd
z7v}ohgveh4Rt_Q$eftWx;X%*{7)3f;sKX8hUAXzBOyj}5<R6WNL*Wcnnc`bqcCX`A
z*lElYNA5VCa}Z1fX5g;KsHGeYkc=Rc-80JVN9j1NU=>;3X0%U-2FCw3=I^n35ezFj
zy10fEez+QJ&)=UpRitJ-|AlSBgaI+!dg`aNo@?~D>2)<|1}Ms%ITi6ke-3vxlUQtH
z_Xu0K;u4kv8I*5&y3$w{O`6>$&I^~E^-rn#m2GW4d4q)QHg<Nbo`|Ap&+uz{`^Od#
zi=dQjN`@kuz<WZ7cUSyN><^83RqyFXb>8O|hQMrE4|mL^9BR}U$KRH-=LeQo`jhOi
z0SsNoDNL3zd<!RN<xT?1(10QuNh!*eE(!6UYq<0>>9{FEE&u~=jokMDEI|Z8%K@B$
zKYduX(q<8ATa#O4q~~+`(SY{2GhB;i0*hvKrmah8mrcpa^ku{__kB#Rr8va+dpRfr
zADAHSWIE^FmCDryWUCYtRm?6+Re>JSlJKsU1}xYaI<hYn<#RW$m6my4IxV-zIqU4`
zC@xXqaRL%T4yO}>j=ja3^s|Jii8&6VS`CWN;dgGjq(K-w_B+Z<5>E*Q78>CuhR^Ia
zAg!0Yf3f?UkpOHLnHxni)$Dk^k0qrsSG-|rU7_FQEV{fxgue9nzR{p~m?l8FnPU2Y
zdF*4k9`Nnf`NQ|Xm|l_@xF>xyrlxCx2PWX?r4gdC{CnnBJ@9=LC)&rYGOx=Kb$Xpu
z_G7Rnd`tQhxk343rYnge1yv)$oqq{~_cf8e%V#j#RUO>B-%ny_cd35U&D?8NWvi$3
zyON4#VCQ8;mZ|f^%%_CuA*)tQy)ik}XMa8bC6ayrfN%x<vGm(po(W-|i%#8#B${uQ
zPX@A;!8TS!F;BLYzm?_8r#-+WkixCZSk!HDGtG%A=Wo=}$(7#8)FzBAVY<RNuaqEj
zvxS-!dO7xuW}H4pQ)sfm{`2lx<nymbM;<JPyVXm#luFNXV|nyc7nVmI=iuT|>O6FR
zBR~dDo+5k>E}F}f$bq0*)L7jnGaNvoGv99)pDrFVZ_bU7J#E|?%{Y{Ic7h72?(zPA
zcy)|4r<vWSo=Si^l`F>OWK9#`_1l!}{T61uzXN849mY)v9g!+IRRI_ITE{mxqsElC
zEV}*nxQsb|**VAIvST3rCtQL9-9L&km6W@VDS7nP!^za43*l_x0lt6G>_rZ*%72WJ
zfq+ZvAKTo{C$wU7rF^3VW0~PqT}T^Tfn4Ouj_%smtJGA+M{;v>5F&BU1oXY_!+};K
zkd(Qoksv4bQN3qq0Ncry8Dw$&pyg(dt4@dWB8uegzKot+&(Q1VkC#gqy)V)yzY#LU
z@vh7;$-KmsL`>C_D!#jy^dMnD{fK08Sh%+!zV~J5ArUG?ZS))@e#h_>LcYDy88bQ-
zNU0Tczuc@xN7F@hcebxn<ER`ioV(}kw-PEAK{d%Aqb)DkXRE;x%(QEsbCtMOYn9f|
zeN3K#H`kxpT$fsf;BtPc$?U7^V;#KN;iMeCQ{N_g4Mwx>QeZZ%N9@uLRYo-vy@EMb
z!(M{BUkg9YhIWu_Q@6uU`^TI1V|Aet{|k#C<~TadvyLr1kr8Pb?~LuaIMYUXGC_c?
z+l}1#H6lOs;B=}Yn9$-n6cQlz(rEAzOvBssJ`TZJ(B+c99bQ^b#NB$DHxuxfGj?#~
zkk;Km)})#?WJo$t{XT!&-^KLJP7kF_4O_wOQ|2uBpJ~i0hQ8Edzp~xT;$kOHr_bN4
z7Y{aNG0rvE16_lBby{626F#y}YDG!1m~3iu!ANDFc$)2gaKrreY6)mYykKBS@Lp2N
z0RL*^z>coFU)hJyMQDp2UPP3Rm@UiCJ4IQxP9mQ$PSdM%RNe6W?t|?%{Tf+B{4G@4
zr4~`%RoLFAFxV4KW)-GSZ9efOmO9b@v`nTL_W;vBY2_$X7r2fR2d4np)T(|T_51Yu
zl)MrqgD&k5V@^V|t9i7A5Y&qiQ}YaB+3T*wn>_*JRJ9c8B~$;Re(=k~mUi3}rQgY#
z<=tZ!xvr$0j8`MG=*S)5#hx>^b*Px#@dI2gUY{ov-kCtqr~j!Kd&*7`8nPMzpVl>z
z*;&Vv=>KXBes{#Qs4)5Y@kecdPaT<ToM`()skt}HCME!#)+;0`c_<GN%wi~*w9H6_
zy-K8igwjM$QKYfx4pIvg$9K>$DOj&nVX3W+Uy=6lKnA_s@cX(*b+MwMyw$;*ew>2w
z(8?x;hMi84KQFCmtmhNB1H75HKC`o81OB&whE2}P5thm4EKmDHY75{|@aUB#jCYp!
zFBGt6LdU;MTqVd#$*Q0WwU?HxV^VrHD>koA0z<F#IjuClp8*(k`J5;{h-#QkEo@~@
z{Af~Ou_So&Sl&PWuLrQb?J1b<WJ9vEg+`gsa_^b)cY;-#|L-0!`ms0`;rTNdvrYKC
z)4quT#O1pvWWpE<JuoG*O_?xa`!lU)G53^QW}`UKO$o;L;x2F2*{4cbgr4{CxfpYv
z5H8cLN|CjCuPNnH$*TOLU#G%;NtjF)hS(8Y?>MRN6qAzlUh8_#4z1+rD~r>SF6EoG
zT3<7)d1Wex>Xl-wH0-awY=2YPS|4GhZWH$oqew?Zegvxs#>k7oA^zXUBZ(xz@KQls
T?|XFr3GiA;OR-Yk^22`sn~zF<
literal 0
HcmV?d00001
diff --git a/plugins/gingerbase/ui/images/icon-vm.png b/plugins/gingerbase/ui/images/icon-vm.png
new file mode 100644
index 0000000000000000000000000000000000000000..50dac50c5a0ee59f4e79170434f53bb3b94390e3
GIT binary patch
literal 2976
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
literal 0
HcmV?d00001
diff --git a/plugins/gingerbase/ui/images/logo.ico b/plugins/gingerbase/ui/images/logo.ico
new file mode 100644
index 0000000000000000000000000000000000000000..446143f066a60f479b2cb8f8d04ab3c97ff60e56
GIT binary patch
literal 1214
zcma)*3rLeu6vyu=X_;nPV)$&PPF)r(wTQ|dTGOUtnOdf0mZ=cR#99xbub1{BqnD9^
z^Z>PV6h)fUny6#CN{3di2P{*h2ek@Oq0a4o*=I}2Iv?krbAR`oKi_vQA0faVCr83o
zF{!X2#Dx$Nz}$(sm^sgR<Jj30yCMGpMl1z837jm3H1$<DU4IV>Tbdw<FM-;Hi<#et
z=I}n~iB^qh8{XzCNyvT<I;$&znmT~?eFXZgAE@Om?1h(6Iq+2e1-ca!zOAO7n3*};
zXq6n?2kF<!f%cez;-Fgx4$e+xo{WR;oqhc_OW;#nF!Zm|JK}m8gi6aC+##`QA5PmJ
zfeZ(o(}%&&1P5#*1vVbnssk~XhN5bNz=T)>5^+BS=V54SJ|^z)5I7%kU;>-skiq*{
z5B^=eF<T)pK9+)bV;9UCK>t-HOi+bkR-wK1xpfXL;S<?ho1pQ5gYzxiHR~=e+XHS(
zYe7QMZb3IjWV72H-JpD~6z(tbgwn1o$D2s!YteWNJS!i9l%iW?+34Lvr*KL(6{_X|
zobEjAb{lD|4qsmYvGF;~r0Aunc#>=c)0W4s|D{#(q9~vaX{rGms=((Wn*~LeWqIu}
zkGvlzwaxjL25hey2bVweu<xK*t+@Oa_)UKTK^x4IwmKMOjm@oL`l)}_NGPy}tdb}w
zHa>H`ji2N7m}Xsj#j7-`#=Dv}`)h&)9th^&^dR%>WH_MB8KcIXVf8p&s1xD|_k)R-
zrk$Z0-|Gf}&4Au$KhU$k?3YR5by~D9M&-q$=ocNz4!IY&p7%OxuJf&+Ei-G(23hU;
zHTLhMtjFR9ExVbPQVLzBFlROcIZ|#jmX6fm_hsVa3XP<aKAeAt8dqp&oBW=V^+d>Q
zkcNnm%Ds?ggd^Rv2~9_o82cv;p6LSBD_g_s-n1uZ8#<EeLwTm~LE9J!Zdo@M&*jFU
zoM+Z%F5)(8$8y`Wz~%Pd;Nlx@aH{<lk}}CcRMRXZMD&%Uhz3Ze$jpg#uec1IlFMPW
MDQ4iZEk#_;cNAJoqyPW_
literal 0
HcmV?d00001
--
2.1.0
3
2
11 Sep '15
From: chandrureddy <chandra(a)linux.vnet.ibm.com>
---
plugins/gingerbase/ui/js/Makefile.am | 27 +
plugins/gingerbase/ui/js/src/gingerbase.api.js | 371 +++++++++
plugins/gingerbase/ui/js/src/gingerbase.form.js | 48 ++
plugins/gingerbase/ui/js/src/gingerbase.grid.js | 528 +++++++++++++
plugins/gingerbase/ui/js/src/gingerbase.host.js | 859 +++++++++++++++++++++
.../gingerbase/ui/js/src/gingerbase.line-chart.js | 202 +++++
plugins/gingerbase/ui/js/src/gingerbase.main.js | 26 +
.../ui/js/src/gingerbase.report_add_main.js | 72 ++
.../ui/js/src/gingerbase.report_rename_main.js | 66 ++
.../ui/js/src/gingerbase.repository_add_main.js | 96 +++
.../ui/js/src/gingerbase.repository_edit_main.js | 74 ++
plugins/gingerbase/ui/js/src/gingerbase.select.js | 50 ++
plugins/gingerbase/ui/js/widgets/circleGauge.js | 100 +++
plugins/kimchi/ui/js/src/kimchi.host.js | 858 --------------------
plugins/kimchi/ui/js/src/kimchi.report_add_main.js | 72 --
.../kimchi/ui/js/src/kimchi.report_rename_main.js | 66 --
.../kimchi/ui/js/src/kimchi.repository_add_main.js | 96 ---
.../ui/js/src/kimchi.repository_edit_main.js | 74 --
18 files changed, 2519 insertions(+), 1166 deletions(-)
create mode 100644 plugins/gingerbase/ui/js/Makefile.am
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.api.js
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.form.js
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.grid.js
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.host.js
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.line-chart.js
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.main.js
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.report_add_main.js
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.report_rename_main.js
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.repository_add_main.js
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.repository_edit_main.js
create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.select.js
create mode 100644 plugins/gingerbase/ui/js/widgets/circleGauge.js
delete mode 100644 plugins/kimchi/ui/js/src/kimchi.host.js
delete mode 100644 plugins/kimchi/ui/js/src/kimchi.report_add_main.js
delete mode 100644 plugins/kimchi/ui/js/src/kimchi.report_rename_main.js
delete mode 100644 plugins/kimchi/ui/js/src/kimchi.repository_add_main.js
delete mode 100644 plugins/kimchi/ui/js/src/kimchi.repository_edit_main.js
diff --git a/plugins/gingerbase/ui/js/Makefile.am b/plugins/gingerbase/ui/js/Makefile.am
new file mode 100644
index 0000000..142ccdb
--- /dev/null
+++ b/plugins/gingerbase/ui/js/Makefile.am
@@ -0,0 +1,27 @@
+#
+# Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+EXTRA_DIST = src widgets
+
+jsdir = $(datadir)/wok/plugins/gingerbase/ui/js
+
+dist_js_DATA = gingerbase.min.js $(filter-out gingerbase.min.js, $(wildcard *.js))
+
+gingerbase.min.js: widgets/*.js src/*.js
+ cat $(sort $^) > $@
+
+CLEANFILES = gingerbase.min.js
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.api.js b/plugins/gingerbase/ui/js/src/gingerbase.api.js
new file mode 100644
index 0000000..480f6fb
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.api.js
@@ -0,0 +1,371 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013-2015
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var kimchi = {
+
+ widget: {},
+
+ trackingTasks: [],
+
+ /**
+ *
+ * Get host capabilities
+ * suc: callback if succeed err: callback if failed
+ */
+ getCapabilities : function(suc, err, done) {
+ done = typeof done !== 'undefined' ? done: function(){};
+ wok.requestJSON({
+ url : "plugins/gingerbase/host/capabilities",
+ type : "GET",
+ contentType : "application/json",
+ dataType : "json",
+ success: suc,
+ error: err,
+ complete: done
+ });
+ },
+
+ /**
+ * Get the i18 strings.
+ */
+ getI18n: function(suc, err, url, sync) {
+ wok.requestJSON({
+ url : url ? url : 'plugins/gingerbase/i18n.json',
+ type : 'GET',
+ resend: true,
+ dataType : 'json',
+ async : !sync,
+ success : suc,
+ error: err
+ });
+ },
+
+ /**
+ * Get the host static information.
+ */
+ getHost: function(suc, err) {
+ wok.requestJSON({
+ url : 'plugins/gingerbase/host',
+ type : 'GET',
+ resend: true,
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error: err
+ });
+ },
+
+ /**
+ * Get the dynamic host stats (usually used for monitoring).
+ */
+ getHostStats : function(suc, err) {
+ wok.requestJSON({
+ url : 'plugins/gingerbase/host/stats',
+ type : 'GET',
+ contentType : 'application/json',
+ headers: {'Wok-Robot': 'wok-robot'},
+ dataType : 'json',
+ success : suc,
+ error: err
+ });
+ },
+
+ /**
+ * Get the historic host stats.
+ */
+ getHostStatsHistory : function(suc, err) {
+ wok.requestJSON({
+ url : 'plugins/gingerbase/host/stats/history',
+ type : 'GET',
+ resend: true,
+ contentType : 'application/json',
+ headers: {'Wok-Robot': 'wok-robot'},
+ dataType : 'json',
+ success : suc,
+ error: err
+ });
+ },
+
+ getTask : function(taskId, suc, err) {
+ wok.requestJSON({
+ url : 'plugins/gingerbase/tasks/' + encodeURIComponent(taskId),
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ getTasksByFilter : function(filter, suc, err, sync) {
+ wok.requestJSON({
+ url : 'plugins/gingerbase/tasks?' + filter,
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ async : !sync,
+ success : suc,
+ error : err
+ });
+ },
+
+ listReports : function(suc, err) {
+ wok.requestJSON({
+ url : 'plugins/gingerbase/debugreports',
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ resend: true,
+ success : suc,
+ error : err
+ });
+ },
+
+ trackTask : function(taskID, suc, err, progress) {
+ var onTaskResponse = function(result) {
+ var taskStatus = result['status'];
+ switch(taskStatus) {
+ case 'running':
+ progress && progress(result);
+ setTimeout(function() {
+ kimchi.trackTask(taskID, suc, err, progress);
+ }, 2000);
+ break;
+ case 'finished':
+ suc && suc(result);
+ break;
+ case 'failed':
+ err && err(result);
+ break;
+ default:
+ break;
+ }
+ };
+
+ kimchi.getTask(taskID, onTaskResponse, err);
+ if(kimchi.trackingTasks.indexOf(taskID) < 0)
+ kimchi.trackingTasks.push(taskID);
+ },
+
+ createReport: function(settings, suc, err, progress) {
+ var onResponse = function(data) {
+ taskID = data['id'];
+ kimchi.trackTask(taskID, suc, err, progress);
+ };
+
+ wok.requestJSON({
+ url : 'plugins/gingerbase/debugreports',
+ type : "POST",
+ contentType : "application/json",
+ data : JSON.stringify(settings),
+ dataType : "json",
+ success : onResponse,
+ error : err
+ });
+ },
+
+ renameReport : function(name, settings, suc, err) {
+ $.ajax({
+ url : "plugins/gingerbase/debugreports/" + encodeURIComponent(name),
+ type : 'PUT',
+ contentType : 'application/json',
+ data : JSON.stringify(settings),
+ dataType : 'json',
+ success: suc,
+ error: err
+ });
+ },
+
+ deleteReport: function(settings, suc, err) {
+ var reportName = encodeURIComponent(settings['name']);
+ wok.requestJSON({
+ url : 'plugins/gingerbase/debugreports/' + reportName,
+ type : 'DELETE',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ downloadReport: function(settings, suc, err) {
+ window.open(settings['file']);
+ },
+
+ shutdown: function(settings, suc, err) {
+ var reboot = settings && settings['reboot'] === true;
+ var url = 'plugins/gingerbase/host/' + (reboot ? 'reboot' : 'shutdown');
+ wok.requestJSON({
+ url : url,
+ type : 'POST',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ listHostPartitions : function(suc, err) {
+ wok.requestJSON({
+ url : 'plugins/gingerbase/host/partitions',
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ listSoftwareUpdates : function(suc, err) {
+ wok.requestJSON({
+ url : 'plugins/gingerbase/host/packagesupdate',
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ resend: true,
+ success : suc,
+ error : err
+ });
+ },
+
+ updateSoftware : function(suc, err, progress) {
+ var taskID = -1;
+ var onResponse = function(data) {
+ taskID = data['id'];
+ trackTask();
+ };
+
+ var trackTask = function() {
+ kimchi.getTask(taskID, onTaskResponse, err);
+ };
+
+ var onTaskResponse = function(result) {
+ var taskStatus = result['status'];
+ switch(taskStatus) {
+ case 'running':
+ progress && progress(result);
+ setTimeout(function() {
+ trackTask();
+ }, 200);
+ break;
+ case 'finished':
+ case 'failed':
+ suc(result);
+ break;
+ default:
+ break;
+ }
+ };
+
+ wok.requestJSON({
+ url : 'plugins/gingerbase/host/swupdate',
+ type : "POST",
+ contentType : "application/json",
+ dataType : "json",
+ success : onResponse,
+ error : err
+ });
+ },
+
+ createRepository : function(settings, suc, err) {
+ wok.requestJSON({
+ url : "plugins/gingerbase/host/repositories",
+ type : "POST",
+ contentType : "application/json",
+ data : JSON.stringify(settings),
+ dataType : "json",
+ success: suc,
+ error: err
+ });
+ },
+
+ retrieveRepository : function(repository, suc, err) {
+ var reposID = encodeURIComponent(repository);
+ wok.requestJSON({
+ url : "plugins/gingerbase/host/repositories/" + reposID,
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ updateRepository : function(name, settings, suc, err) {
+ var reposID = encodeURIComponent(name);
+ $.ajax({
+ url : "plugins/gingerbase/host/repositories/" + reposID,
+ type : 'PUT',
+ contentType : 'application/json',
+ data : JSON.stringify(settings),
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ enableRepository : function(name, enable, suc, err) {
+ var reposID = encodeURIComponent(name);
+ $.ajax({
+ url : "plugins/gingerbase/host/repositories/" + reposID +
+ '/' + (enable === true ? 'enable' : 'disable'),
+ type : 'POST',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ deleteRepository : function(repository, suc, err) {
+ var reposID = encodeURIComponent(repository);
+ wok.requestJSON({
+ url : 'plugins/gingerbase/host/repositories/' + reposID,
+ type : 'DELETE',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ listRepositories : function(suc, err) {
+ wok.requestJSON({
+ url : 'plugins/gingerbase/host/repositories',
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ resend: true,
+ success : suc,
+ error : err
+ });
+ },
+
+ getCPUInfo : function(suc, err) {
+ wok.requestJSON({
+ url : 'plugins/gingerbase/host/cpuinfo',
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ resend : true,
+ success : suc,
+ error : err ? err : function(data) {
+ wok.message.error(data.responseJSON.reason);
+ }
+ });
+ }
+};
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.form.js b/plugins/gingerbase/ui/js/src/gingerbase.form.js
new file mode 100644
index 0000000..0bb7c4b
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.form.js
@@ -0,0 +1,48 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013-2014
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function($) {
+ $.fn.serializeObject = function() {
+ var formDataArray = $(this).serializeArray();
+ var formData = new Object();
+ $.each(formDataArray, function(index, data) {
+ formData.setDeepValue(data.name, data.value);
+ });
+ return formData;
+ };
+}(jQuery));
+
+(function($) {
+ $.fn.fillWithObject = function(obj) {
+ $(this).find("input").each(function(){
+ switch($(this).attr('type')) {
+ case 'text':
+ $(this).val(obj.getDeepValue($(this).attr("name")));
+ break;
+ case 'radio':
+ case 'checkbox':
+ var a=String($(this).val());
+ var b=String(obj.getDeepValue($(this).attr("name")));
+ $(this).prop("checked",(a==b));
+ break;
+ default:
+ break;
+ }
+ });
+ };
+}(jQuery));
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.grid.js b/plugins/gingerbase/ui/js/src/gingerbase.grid.js
new file mode 100644
index 0000000..4553135
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.grid.js
@@ -0,0 +1,528 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013-2015
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+kimchi.widget.Grid = function(opts) {
+ this.opts = $.extend({}, this.opts, opts);
+ this.createDOM();
+ this.reload();
+};
+
+kimchi.widget.Grid.prototype = (function() {
+ var htmlStr = [
+ '<div id="{id}" class="grid">',
+ '<div class="grid-content">',
+ '<div class="grid-header">',
+ '<div class="grid-frozen-header-view">',
+ '<table class="grid-frozen-header-container">',
+ '</table>',
+ '</div>',
+ '<div class="grid-header-view">',
+ '<div class="grid-header-wrapper">',
+ '<table class="grid-header-container">',
+ '</table>',
+ '</div>',
+ '</div>',
+ '</div>',
+ '<div class="grid-body">',
+ '<div class="grid-frozen-body-view">',
+ '<div class="grid-frozen-body-wrapper">',
+ '<table class="grid-frozen-body-container">',
+ '</table>',
+ '</div>',
+ '</div>',
+ '<div class="grid-body-view">',
+ '<div class="grid-body-wrapper">',
+ '<table class="grid-body-container">',
+ '</table>',
+ '</div>',
+ '</div>',
+ '</div>',
+ '<div class="grid-resizer-leftmost hidden"></div>',
+ '<div class="grid-resizer hidden"></div>',
+ '</div>',
+ '<div class="grid-footer"></div>',
+ '<div class="grid-mask hidden">',
+ '<div class="grid-loading">',
+ '<div class="grid-loading-icon"></div>',
+ '<div class="grid-loading-text">',
+ '{loading}',
+ '</div>',
+ '</div>',
+ '</div>',
+ '<div class="grid-message hidden">',
+ '<div class="grid-message-text">',
+ '{message}',
+ '<button class="retry-button btn-small">',
+ '{buttonLabel}',
+ '</button>',
+ '</div>',
+ '<div class="detailed-title">',
+ '{detailedLabel}',
+ '</div>',
+ '<div class="detailed-text"></div>',
+ '</div>',
+ '</div>'
+ ].join('');
+
+ var CONTAINER_NORMAL = 0, CONTAINER_FROZEN = 1;
+
+ var setupHeaders = function(header, body, fields) {
+ var colGroup = $('<colgroup></colgroup>').appendTo(header);
+ var headerHeader = $('<thead></thead>');
+ var headerRow = $('<tr></tr>').appendTo(headerHeader);
+ $.each(fields || [], function(i, field) {
+ $('<col class="' +
+ field['class'] +
+ '"/>')
+ .appendTo(colGroup);
+ $('<th><div class="cell-text-wrapper">' +
+ field['label'] +
+ '</div></th>').appendTo(headerRow);
+ });
+ headerHeader.appendTo(header);
+
+ var totalWidth = 0;
+ $('col', colGroup).each(function(index, col) {
+ var width = $(col).width();
+ totalWidth += width;
+ $(col).css('width', width + 'px');
+ });
+ $(body).append(colGroup.clone());
+ return totalWidth;
+ };
+
+ var getValue = function(name, obj) {
+ var result=undefined;
+ if(!Array.isArray(name)) {
+ name=name.parseKey();
+ }
+ if(name.length!=0) {
+ var tmpName=name.shift();
+ if(obj[tmpName]!=undefined) {
+ result=obj[tmpName];
+ }
+ if(name.length!=0) {
+ result=getValue(name,obj[tmpName]);
+ }
+ }
+ return(result);
+ };
+
+ var fillBody = function(container, fields) {
+ var data = this.data;
+ var tbody = ($('tbody', container).length && $('tbody', container))
+ || $('<tbody></tbody>').appendTo(container);
+ tbody.empty();
+ $.each(data, function(i, row) {
+ var rowNode = $('<tr></tr>').appendTo(tbody);
+ $.each(fields, function(fi, field) {
+ var value = getValue(field['name'], row);
+ $('<td><div class="cell-text-wrapper"' +
+ (field['makeTitle'] === true
+ ? ' title="' + value + '"'
+ : ''
+ ) + '>' + value.toString() + '</div></td>'
+ ).appendTo(rowNode);
+ });
+ });
+ };
+
+ var fixTableLayout = function(style) {
+ $.each([
+ this.frozenHeaderContainer,
+ this.headerContainer,
+ this.frozenBodyContainer,
+ this.bodyContainer
+ ], function(i, tableNode) {
+ $(tableNode).css('table-layout', style || 'fixed');
+ });
+ };
+
+ var initResizing = function(event) {
+ var resizer = event.data.resizer;
+ var pageX = event.pageX;
+ var tailPos = $(this).width() + $(this).offset()['left'];
+ var atResizer = Math.abs(pageX - tailPos) <= 2;
+ var isResizing = !$(resizer).hasClass('hidden');
+ $('body')[(atResizer || isResizing)
+ ? 'addClass'
+ : 'removeClass'
+ ]('resizing');
+ };
+
+ var clearResizing = function(event) {
+ $(event.data.resizer).hasClass('hidden') &&
+ $('body').removeClass('resizing');
+ };
+
+ var stylingRow = function(row, className, add) {
+ var index = $(row).index() + 1;
+ $('tr', this.frozenBodyContainer)
+ .removeClass(className);
+ $('tr', this.bodyContainer)
+ .removeClass(className);
+
+ if(add === false) {
+ return;
+ }
+
+ $('tr:nth-child(' + index + ')', this.frozenBodyContainer)
+ .addClass(className);
+ $('tr:nth-child(' + index + ')', this.bodyContainer)
+ .addClass(className);
+ };
+
+ var setBodyListeners = function() {
+ if(this['opts']['rowSelection'] != 'disabled') {
+ $('tr', this.gridBody).on('mouseover', {
+ grid: this
+ }, function(event) {
+ if (! $(this).hasClass('no-hover'))
+ stylingRow.call(event.data.grid, this, 'hover');
+ });
+
+ $('tr', this.gridBody).on('mouseout', {
+ grid: this
+ }, function(event) {
+ stylingRow.call(event.data.grid, this, 'hover', false);
+ });
+
+ $('tr', this.gridBody).on('click', {
+ grid: this
+ }, function(event) {
+ var grid = event.data.grid;
+ grid.selectedIndex = $(this).index();
+ stylingRow.call(grid, this, 'selected');
+ grid['opts']['onRowSelected'] && grid['opts']['onRowSelected']();
+ });
+ }
+
+ $('.grid-body-view', this.domNode).on('scroll', {
+ grid: this
+ }, function(event) {
+ var grid = event.data.grid;
+ $('.grid-header .grid-header-view', grid.domNode)
+ .prop('scrollLeft', this.scrollLeft);
+ $('.grid-body .grid-frozen-body-view', grid.domNode)
+ .prop('scrollTop', this.scrollTop);
+ });
+ };
+
+ var setData = function(data) {
+ this.data = data;
+ fillBody.call(this, this.frozenBodyContainer, this['opts']['frozenFields']);
+ fillBody.call(this, this.bodyContainer, this['opts']['fields']);
+ setBodyListeners.call(this);
+ };
+
+ var getSelected = function() {
+ return this.selectedIndex >= 0
+ ? this.data[this.selectedIndex]
+ : null;
+ };
+
+ var startResizing = function(container, event) {
+ var grid = event.data.grid;
+ kimchi.widget.Grid.beingResized = grid;
+ if(!($('body').hasClass('resizing')
+ && $(grid.resizer).hasClass('hidden'))) {
+ return;
+ }
+
+ grid.columnBeingResized = container;
+ var pageX = event.pageX;
+ var gridOffsetX = grid.domNode.offset()['left'];
+ var leftmostOffsetX = $(container).offset()['left'] - gridOffsetX;
+ var left = pageX - gridOffsetX;
+ var contentHeight = $('.grid-content', grid.domNode).height();
+ $(grid.resizerLeftmost).css({
+ left: leftmostOffsetX + 'px',
+ height: contentHeight + 'px'
+ });
+ $(grid.resizer).css({
+ left: left + 'px',
+ height: contentHeight + 'px'
+ });
+ $(grid.resizerLeftmost).removeClass('hidden');
+ $(grid.resizer).removeClass('hidden');
+ event.preventDefault();
+ };
+
+ var endResizing = function(event) {
+ var grid = kimchi.widget.Grid.beingResized;
+ if(!$('body').hasClass('resizing')) {
+ return;
+ }
+ $(grid.resizerLeftmost).addClass('hidden');
+ $(grid.resizer).addClass('hidden');
+ $('body').removeClass('resizing');
+ var leftmostOffset = $(grid.columnBeingResized).offset()['left'];
+ var left = event.pageX;
+ if(leftmostOffset > left) {
+ return;
+ }
+ resizeColumnWidth.call(
+ grid,
+ $(grid.columnBeingResized).index(),
+ left - leftmostOffset
+ );
+ fixTableLayout.call(grid);
+ grid.columnBeingResized = null;
+ kimchi.widget.Grid.beingResized = null;
+ };
+
+ var resizeColumnWidth = function(index, width) {
+ var width = Math.ceil(width);
+ var widthArray = [];
+ var totalWidth = 0;
+ var header = this.headerContainer;
+ var body = this.bodyContainer;
+ if(this.containerBeingResized === CONTAINER_FROZEN) {
+ header = this.frozenHeaderContainer;
+ body = this.frozenBodyContainer;
+ }
+ $('col', header).each(function(i, colNode) {
+ var w = index === i ? width : $(colNode).width();
+ widthArray.push(w);
+ totalWidth += w;
+ });
+ $.each([header, body], function(i, container) {
+ container.css({
+ 'table-layout': 'fixed',
+ width: totalWidth + 'px'
+ });
+ $('col:nth-child(' + (index + 1) + ')', container).css({
+ width: width + 'px'
+ });
+ });
+
+ if(this.containerBeingResized === CONTAINER_FROZEN) {
+ var headerView = $('.grid-header-view', this.domNode);
+ var bodyView = $('.grid-body-view', this.domNode);
+ $.each([headerView, bodyView], function(i, view) {
+ view.css({
+ left: totalWidth + 'px'
+ });
+ });
+ }
+ };
+
+ var positionResizer = function(event) {
+ var grid = event.data.grid;
+ if($(grid.resizer).hasClass('hidden')) {
+ return;
+ }
+
+ var pageX = event.pageX;
+ var gridOffsetX = $(grid.domNode).offset()['left'];
+ var leftMost = $(grid.resizerLeftmost).position()['left'];
+ var offsetX = pageX - gridOffsetX;
+ offsetX = offsetX >= leftMost ? offsetX : leftMost;
+ $(grid.resizer).css('left', offsetX + 'px');
+ };
+
+ var showMessage = function(msg) {
+ $('.detailed-text', this.messageNode).text(msg);
+ $(this.messageNode).removeClass('hidden');
+ };
+
+ var hideMessage = function() {
+ $(this.messageNode).addClass('hidden');
+ };
+
+ var reload = function() {
+ var data = this['opts']['data'];
+ if(!data) {
+ return;
+ }
+
+ $(this.messageNode).addClass('hidden');
+
+ if($.isArray(data)) {
+ return this.setData(data);
+ }
+
+ if($.isFunction(data)) {
+ var loadData = data;
+ $(this.maskNode).removeClass('hidden');
+ loadData($.proxy(function(data) {
+ this.setData(data);
+ $(this.maskNode).addClass('hidden');
+ }, this));
+ }
+ };
+
+ var destroy = function() {
+ $('body').off('mousemove.grid#' + this['opts']['id'], positionResizer);
+ $('body').off('mouseup.grid#' + this['opts']['id'], endResizing);
+ };
+
+ var createDOM = function() {
+ var containerID = this['opts']['container'];
+ var container = $('#' + containerID);
+ var gridID = this['opts']['id'];
+ var rowSelection = this['opts']['rowSelection'] || 'single';
+ var domNode = $(wok.substitute(htmlStr, {
+ id: gridID,
+ loading: i18n['GGBGRD6001M'],
+ message: i18n['GGBGRD6002M'],
+ buttonLabel: i18n['GGBGRD6003M'],
+ detailedLabel: i18n['GGBGRD6004M']
+ })).appendTo(container);
+ this.domNode = domNode;
+
+ var height = domNode.height();
+ var width = domNode.width();
+
+ var title = this['opts']['title'];
+ var titleNode = null;
+ if(title) {
+ titleNode = $('<div class="grid-caption">' + title + '</div>')
+ .prependTo(domNode);
+ }
+
+ var toolbarButtons = this['opts']['toolbarButtons'];
+ var toolbarNode = null;
+ if(toolbarButtons) {
+ toolbarNode = $('<div class="grid-toolbar"></div>');
+ if(titleNode) {
+ titleNode.after(toolbarNode);
+ }
+ else {
+ toolbarNode.prependTo(domNode);
+ }
+
+ $.each(toolbarButtons, function(i, button) {
+ var btnHTML = [
+ '<button',
+ button['id'] ? (' id="' + button['id'] + '"') : '',
+ ' class="grid-toolbar-button',
+ button['class'] ? (' ' + button['class']) : '',
+ '"',
+ button['disabled'] === true ? ' disabled' : '',
+ '>',
+ button['label'],
+ '</button>'
+ ].join('');
+ var btnNode = $(btnHTML).appendTo(toolbarNode);
+ button['onClick'] &&
+ btnNode.on('click', button['onClick']);
+ });
+ }
+
+ var frozenHeaderContainer = $('.grid-frozen-header-container', domNode);
+ var frozenBodyContainer = $('.grid-frozen-body-container', domNode);
+ var frozenWidth = setupHeaders(
+ frozenHeaderContainer,
+ frozenBodyContainer,
+ this['opts']['frozenFields']
+ );
+ this.frozenHeaderContainer = frozenHeaderContainer;
+ this.frozenBodyContainer = frozenBodyContainer;
+
+ var headerContainer = $('.grid-header-container', domNode);
+ var bodyContainer = $('.grid-body-container', domNode);
+ setupHeaders(headerContainer, bodyContainer, this['opts']['fields']);
+ this.headerContainer = headerContainer;
+ this.bodyContainer = bodyContainer;
+
+ fixTableLayout.call(this, 'auto');
+
+ var gridContentNode = $('.grid-content', domNode);
+ var captionHeight = titleNode && $(titleNode).height() || 0;
+ var toolbarHeight = toolbarNode && $(toolbarNode).height() || 0;
+ gridContentNode.css('top', (captionHeight + toolbarHeight) + 'px');
+
+ var maskNode = $('.grid-mask', domNode);
+ maskNode.css('top', captionHeight + 'px');
+ this.maskNode = maskNode;
+
+ var messageNode = $('.grid-message', domNode);
+ messageNode.css('top', captionHeight + 'px');
+ this.messageNode = messageNode;
+
+ var headerView = $('.grid-header-view', domNode);
+ var bodyView = $('.grid-body-view', domNode);
+ headerView.css('left', (frozenWidth) + 'px');
+ bodyView.css('left', (frozenWidth) + 'px');
+
+ var bodyWidth = width - frozenWidth;
+ headerContainer.css('width', bodyWidth + 'px');
+ bodyContainer.css('width', bodyWidth + 'px');
+
+ fixTableLayout.call(this);
+
+ var gridBody = $('.grid-body', domNode);
+ this.gridBody = gridBody;
+ this.resizerLeftmost = $('.grid-resizer-leftmost', domNode);
+ this.resizer = $('.grid-resizer', domNode);
+ var gridHeader = $('.grid-header', domNode);
+ $('th', gridHeader).on('mouseover mousemove', {
+ resizer: this.resizer
+ }, initResizing);
+
+ $('th', gridHeader).on('mouseout', {
+ resizer: this.resizer
+ }, clearResizing);
+
+ this.containerBeingResized = CONTAINER_NORMAL;
+ $('th', frozenHeaderContainer).on('mousedown', {
+ grid: this
+ }, function(event) {
+ event.data.grid.containerBeingResized = CONTAINER_FROZEN;
+ startResizing(this, event);
+ });
+ $('th', headerContainer).on('mousedown', {
+ grid: this
+ }, function(event) {
+ event.data.grid.containerBeingResized = CONTAINER_NORMAL;
+ startResizing(this, event);
+ });
+
+ $('body').on('mousemove.grid#' + this['opts']['id'], {
+ grid: this
+ }, positionResizer);
+ $('body').on('mouseup.grid#' + this['opts']['id'], endResizing);
+
+ var data = this['opts']['data'];
+
+ $('.retry-button', domNode).on('click', {
+ grid: this
+ }, function(event) {
+ event.data.grid.reload();
+ });
+ };
+
+ return {
+ opts: {
+ container: null,
+ id: null,
+ rowSelection: 'single',
+ onRowSelected: null,
+ title: null,
+ toolbarButtons: null,
+ frozenFields: null,
+ fields: null
+ },
+ createDOM: createDOM,
+ setData: setData,
+ getSelected: getSelected,
+ reload: reload,
+ destroy: destroy,
+ showMessage: showMessage
+ };
+})();
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.host.js b/plugins/gingerbase/ui/js/src/gingerbase.host.js
new file mode 100644
index 0000000..989461c
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.host.js
@@ -0,0 +1,859 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013-2014
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+kimchi.host={};
+
+kimchi.host_main = function() {
+ var expand = function(header, toExpand) {
+ var controlledNode = $(header).attr('aria-controls');
+ $('#' + controlledNode)[toExpand ? 'removeClass' : 'addClass']('hidden');
+ $(header).attr('aria-expanded', toExpand ? 'true' : 'false');
+ };
+
+ var repositoriesGrid = null;
+ var initRepositoriesGrid = function(repo_type) {
+ var gridFields=[];
+ if (repo_type == "yum") {
+ gridFields=[{
+ name: 'repo_id',
+ label: i18n['GGBREPO6004M'],
+ 'class': 'repository-id'
+ }, {
+ name: 'config[repo_name]',
+ label: i18n['GGBREPO6005M'],
+ 'class': 'repository-name'
+ }, {
+ name: 'enabled',
+ label: i18n['GGBREPO6009M'],
+ 'class': 'repository-enabled'
+ }];
+ }
+ else if (repo_type == "deb") {
+ gridFields=[{
+ name: 'baseurl',
+ label: i18n['GGBREPO6006M'],
+ makeTitle: true,
+ 'class': 'repository-baseurl deb'
+ }, {
+ name: 'enabled',
+ label: i18n['GGBREPO6009M'],
+ 'class': 'repository-enabled deb'
+ }, {
+ name: 'config[dist]',
+ label: "dist",
+ 'class': 'repository-gpgcheck deb'
+ }, {
+ name: 'config[comps]',
+ label: "comps",
+ 'class': 'repository-gpgcheck deb'
+ }];
+ }
+ else {
+ gridFields=[{
+ name: 'repo_id',
+ label: i18n['GGBREPO6004M'],
+ 'class': 'repository-id'
+ }, {
+ name: 'enabled',
+ label: i18n['GGBREPO6009M'],
+ 'class': 'repository-enabled'
+ }, {
+ name: 'baseurl',
+ label: i18n['GGBREPO6006M'],
+ makeTitle: true,
+ 'class': 'repository-baseurl'
+ }];
+ }
+ repositoriesGrid = new kimchi.widget.Grid({
+ container: 'repositories-grid-container',
+ id: 'repositories-grid',
+ title: i18n['GGBREPO6003M'],
+ toolbarButtons: [{
+ id: 'repositories-grid-add-button',
+ label: i18n['GGBREPO6012M'],
+ onClick: function(event) {
+ wok.window.open({url:'plugins/gingerbase/repository-add.html',
+ class: repo_type});
+ }
+ }, {
+ id: 'repositories-grid-enable-button',
+ label: i18n['GGBREPO6016M'],
+ disabled: true,
+ onClick: function(event) {
+ var repository = repositoriesGrid.getSelected();
+ if(!repository) {
+ return;
+ }
+ var name = repository['repo_id'];
+ var enable = !repository['enabled'];
+ $(this).prop('disabled', true);
+ kimchi.enableRepository(name, enable, function() {
+ wok.topic('kimchi/repositoryUpdated').publish();
+ });
+ }
+ }, {
+ id: 'repositories-grid-edit-button',
+ label: i18n['GGBREPO6013M'],
+ disabled: true,
+ onClick: function(event) {
+ var repository = repositoriesGrid.getSelected();
+ if(!repository) {
+ return;
+ }
+ kimchi.selectedRepository = repository['repo_id'];
+ wok.window.open({url:'plugins/gingerbase/repository-edit.html',
+ class: repo_type});
+ }
+ }, {
+ id: 'repositories-grid-remove-button',
+ label: i18n['GGBREPO6014M'],
+ disabled: true,
+ onClick: function(event) {
+ var repository = repositoriesGrid.getSelected();
+ if(!repository) {
+ return;
+ }
+
+ var settings = {
+ title : i18n['GGBREPO6001M'],
+ content : i18n['GGBREPO6002M'],
+ confirm : i18n['GGBAPI6004M'],
+ cancel : i18n['GGBAPI6003M']
+ };
+
+ wok.confirm(settings, function() {
+ kimchi.deleteRepository(
+ repository['repo_id'],
+ function(result) {
+ wok.topic('kimchi/repositoryDeleted').publish(result);
+ }, function(error) {
+ }
+ );
+ });
+ }
+ }],
+ onRowSelected: function(row) {
+ var repository = repositoriesGrid.getSelected();
+ if(!repository) {
+ return;
+ }
+ $('#repositories-grid-remove-button').prop('disabled', false);
+ $('#repositories-grid-edit-button').prop('disabled', false);
+ var enabled = repository['enabled'];
+ $('#repositories-grid-enable-button')
+ .text(i18n[enabled ? 'GGBREPO6017M' : 'GGBREPO6016M'])
+ .prop('disabled', false);
+ },
+ frozenFields: [],
+ fields: gridFields,
+ data: listRepositories
+ });
+ };
+
+ var listRepositories = function(gridCallback) {
+ kimchi.listRepositories(function(repositories) {
+ if($.isFunction(gridCallback)) {
+ gridCallback(repositories);
+ }
+ else {
+ if(repositoriesGrid) {
+ repositoriesGrid.setData(repositories);
+ }
+ else {
+ initRepositoriesGrid();
+ repositoriesGrid.setData(repositories);
+ }
+ }
+ },
+ function(error) {
+ var message = error && error['responseJSON'] && error['responseJSON']['reason'];
+
+ if($.isFunction(gridCallback)) {
+ gridCallback([]);
+ }
+ repositoriesGrid &&
+ repositoriesGrid.showMessage(message || i18n['GGBUPD6008M']);
+ });
+
+ $('#repositories-grid-remove-button').prop('disabled', true);
+ $('#repositories-grid-edit-button').prop('disabled', true);
+ $('#repositories-grid-enable-button').prop('disabled', true);
+ };
+
+ var softwareUpdatesGridID = 'software-updates-grid';
+ var softwareUpdatesGrid = null;
+ var progressAreaID = 'software-updates-progress-textarea';
+ var reloadProgressArea = function(result) {
+ var progressArea = $('#' + progressAreaID)[0];
+ $(progressArea).text(result['message']);
+ var scrollTop = $(progressArea).prop('scrollHeight');
+ $(progressArea).prop('scrollTop', scrollTop);
+ };
+
+ var initSoftwareUpdatesGrid = function(softwareUpdates) {
+ softwareUpdatesGrid = new kimchi.widget.Grid({
+ container: 'software-updates-grid-container',
+ id: softwareUpdatesGridID,
+ title: i18n['GGBUPD6001M'],
+ rowSelection: 'disabled',
+ toolbarButtons: [{
+ id: softwareUpdatesGridID + '-update-button',
+ label: i18n['GGBUPD6006M'],
+ disabled: true,
+ onClick: function(event) {
+ var updateButton = $(this);
+ var progressArea = $('#' + progressAreaID)[0];
+ $('#software-updates-progress-container').removeClass('hidden');
+ $(progressArea).text('');
+ !wok.isElementInViewport(progressArea) &&
+ progressArea.scrollIntoView();
+ $(updateButton).text(i18n['GGBUPD6007M']).prop('disabled', true);
+
+ kimchi.updateSoftware(function(result) {
+ reloadProgressArea(result);
+ $(updateButton).text(i18n['GGBUPD6006M']).prop('disabled', false);
+ wok.topic('kimchi/softwareUpdated').publish({
+ result: result
+ });
+ }, function(error) {
+ var message = error && error['responseJSON'] && error['responseJSON']['reason'];
+ wok.message.error(message || i18n['GGBUPD6009M']);
+ $(updateButton).text(i18n['GGBUPD6006M']).prop('disabled', false);
+ }, reloadProgressArea);
+ }
+ }],
+ frozenFields: [],
+ fields: [{
+ name: 'package_name',
+ label: i18n['GGBUPD6002M'],
+ 'class': 'software-update-name'
+ }, {
+ name: 'version',
+ label: i18n['GGBUPD6003M'],
+ 'class': 'software-update-version'
+ }, {
+ name: 'arch',
+ label: i18n['GGBUPD6004M'],
+ 'class': 'software-update-arch'
+ }, {
+ name: 'repository',
+ label: i18n['GGBUPD6005M'],
+ 'class': 'software-update-repos'
+ }],
+ data: listSoftwareUpdates
+ });
+ };
+
+ var listSoftwareUpdates = function(gridCallback) {
+ kimchi.listSoftwareUpdates(function(softwareUpdates) {
+ if($.isFunction(gridCallback)) {
+ gridCallback(softwareUpdates);
+ }
+ else {
+ if(softwareUpdatesGrid) {
+ softwareUpdatesGrid.setData(softwareUpdates);
+ }
+ else {
+ initSoftwareUpdatesGrid(softwareUpdates);
+ }
+ }
+
+ var updateButton = $('#' + softwareUpdatesGridID + '-update-button');
+ $(updateButton).prop('disabled', softwareUpdates.length === 0);
+ }, function(error) {
+ var message = error && error['responseJSON'] && error['responseJSON']['reason'];
+ if($.isFunction(gridCallback)) {
+ gridCallback([]);
+ }
+ softwareUpdatesGrid &&
+ softwareUpdatesGrid.showMessage(message || i18n['GGBUPD6008M']);
+ });
+ };
+
+ var reportGridID = 'available-reports-grid';
+ var reportGrid = null;
+ var enableReportButtons = function(toEnable) {
+ var buttonID = '#{grid}-{btn}-button';
+ $.each(['rename', 'remove', 'download'], function(i, n) {
+ $(wok.substitute(buttonID, {
+ grid: reportGridID,
+ btn: n
+ })).prop('disabled', !toEnable);
+ });
+ };
+ var initReportGrid = function(reports) {
+ reportGrid = new kimchi.widget.Grid({
+ container: 'available-reports-grid-container',
+ id: reportGridID,
+ title: i18n['GGBDR6002M'],
+ toolbarButtons: [{
+ id: reportGridID + '-generate-button',
+ label: i18n['GGBDR6006M'],
+ onClick: function(event) {
+ wok.window.open('plugins/gingerbase/report-add.html');
+ }
+ }, {
+ id: reportGridID + '-rename-button',
+ label: i18n['GGBDR6008M'],
+ disabled: true,
+ onClick: function(event) {
+ var report = reportGrid.getSelected();
+ if(!report) {
+ return;
+ }
+
+ kimchi.selectedReport = report['name'];
+ wok.window.open('plugins/gingerbase/report-rename.html');
+ }
+ }, {
+ id: reportGridID + '-remove-button',
+ label: i18n['GGBDR6009M'],
+ disabled: true,
+ onClick: function(event) {
+ var report = reportGrid.getSelected();
+ if(!report) {
+ return;
+ }
+
+ var settings = {
+ title : i18n['GGBAPI6004M'],
+ content : i18n['GGBDR6001M'],
+ confirm : i18n['GGBAPI6002M'],
+ cancel : i18n['GGBAPI6003M']
+ };
+
+ wok.confirm(settings, function() {
+ kimchi.deleteReport({
+ name: report['name']
+ }, function(result) {
+ listDebugReports();
+ }, function(error) {
+ wok.message.error(error.responseJSON.reason);
+ });
+ });
+ }
+ }, {
+ id: reportGridID + '-download-button',
+ label: i18n['GGBDR6010M'],
+ disabled: true,
+ onClick: function(event) {
+ var report = reportGrid.getSelected();
+ if(!report) {
+ return;
+ }
+
+ kimchi.downloadReport({
+ file: report['uri']
+ });
+ }
+ }],
+ onRowSelected: function(row) {
+ var report = reportGrid.getSelected();
+ // Only enable report buttons if the selected line is not a
+ // pending report
+ if (report['time'] == i18n['GGBDR6007M']) {
+ var gridElement = $('#'+ reportGridID);
+ var row = $('tr:contains(' + report['name'] + ')', gridElement);
+ enableReportButtons(false);
+ row.attr('class', '');
+ }
+ else {
+ enableReportButtons(true);
+ }
+ },
+ frozenFields: [],
+ fields: [{
+ name: 'name',
+ label: i18n['GGBDR6003M'],
+ 'class': 'debug-report-name'
+ }, {
+ name: 'time',
+ label: i18n['GGBDR6005M'],
+ 'class': 'debug-report-time'
+ }],
+ data: reports
+ });
+ };
+
+ var getPendingReports = function() {
+ var reports = []
+ var filter = 'status=running&target_uri=' + encodeURIComponent('^/plugins/gingerbase/debugreports/*')
+
+ kimchi.getTasksByFilter(filter, function(tasks) {
+ for(var i = 0; i < tasks.length; i++) {
+ reportName = tasks[i].target_uri.replace(/^\/plugins\/gingerbase\/debugreports\//, '') || i18n['GGBDR6012M'];
+ reports.push({'name': reportName, 'time': i18n['GGBDR6007M']})
+
+ if(kimchi.trackingTasks.indexOf(tasks[i].id) >= 0) {
+ continue;
+ }
+
+ kimchi.trackTask(tasks[i].id, function(result) {
+ wok.topic('kimchi/debugReportAdded').publish();
+ }, function(result) {
+ // Error message from Async Task status
+ if (result['message']) {
+ var errText = result['message'];
+ }
+ // Error message from standard kimchi exception
+ else {
+ var errText = result['responseJSON']['reason'];
+ }
+ result && wok.message.error(errText);
+ wok.topic('kimchi/debugReportAdded').publish();
+ }, null);
+ }
+ }, null, true);
+
+ return reports;
+ };
+
+ var listDebugReports = function() {
+ kimchi.listReports(function(reports) {
+ pendingReports = getPendingReports();
+ allReports = pendingReports.concat(reports);
+ $('#debug-report-section').removeClass('hidden');
+
+ // Row selection will be cleared so disable buttons here
+ enableReportButtons(false);
+
+ if(reportGrid) {
+ reportGrid.setData(allReports);
+ }
+ else {
+ initReportGrid(allReports);
+ }
+
+ // Set id-debug-img to pending reports
+ // It will display a loading icon
+ var gridElement = $('#' + reportGridID);
+ $.each($('td:contains(' + i18n['GGBDR6007M'] + ')', gridElement), function(index, row) {
+ $(row).parent().addClass('no-hover');
+ $(row).attr('id', 'id-debug-img');
+ });
+ }, function(error) {
+ if(error['status'] == 403) {
+ $('#debug-report-section').addClass('hidden');
+ return;
+ }
+ $('#debug-report-section').removeClass('hidden');
+ });
+ };
+
+ var shutdownButtonID = '#host-button-shutdown';
+ var restartButtonID = '#host-button-restart';
+ var shutdownHost = function(params) {
+ var settings = {
+ title : i18n['GGBAPI6004M'],
+ content : i18n['GGBHOST6008M'],
+ confirm : i18n['GGBAPI6002M'],
+ cancel : i18n['GGBAPI6003M']
+ };
+
+ wok.confirm(settings, function() {
+ kimchi.shutdown(params);
+ $(shutdownButtonID).prop('disabled', true);
+ $(restartButtonID).prop('disabled', true);
+ // Check if there is any VM is running.
+ // FIXME : Find alternative way to figure out if any vms running
+ // kimchi.listVMs(function(vms) {
+ // for(var i = 0; i < vms.length; i++) {
+ // if(vms[i]['state'] === 'running') {
+ // wok.message.error.code('GGBHOST6001E');
+ // $(shutdownButtonID).prop('disabled', false);
+ // $(restartButtonID).prop('disabled', false);
+ // return;
+ // }
+ // }
+ //
+ // });
+ }, function() {
+ });
+ };
+
+ var initPage = function() {
+ $('#host-info-container .section-header').each(function(i, header) {
+ $('<span class="arrow"></span>').prependTo(header);
+ var toExpand = $(header).attr('aria-expanded') !== 'false';
+ expand(header, toExpand);
+ });
+
+ $('#host-info-container').on('click', '.section-header', function(event) {
+ var toExpand = $(this).attr('aria-expanded') === 'false';
+ expand(this, toExpand);
+ });
+
+ $('#host-button-shutdown').on('click', function(event) {
+ shutdownHost(null);
+ });
+
+ $('#host-button-restart').on('click', function(event) {
+ shutdownHost({
+ reboot: true
+ });
+ });
+
+ var setupUI = function() {
+ if (kimchi.capabilities == undefined) {
+ setTimeout(setupUI, 2000);
+ return;
+ }
+
+ if((kimchi.capabilities['repo_mngt_tool']) && (kimchi.capabilities['repo_mngt_tool']!="None")) {
+ initRepositoriesGrid(kimchi.capabilities['repo_mngt_tool']);
+ $('#repositories-section').switchClass('hidden', kimchi.capabilities['repo_mngt_tool']);
+ wok.topic('kimchi/repositoryAdded')
+ .subscribe(listRepositories);
+ wok.topic('kimchi/repositoryUpdated')
+ .subscribe(listRepositories);
+ wok.topic('kimchi/repositoryDeleted')
+ .subscribe(listRepositories);
+ }
+
+ if(kimchi.capabilities['update_tool']) {
+ $('#software-update-section').removeClass('hidden');
+ initSoftwareUpdatesGrid();
+ wok.topic('kimchi/softwareUpdated')
+ .subscribe(listSoftwareUpdates);
+ $('#software-updates-progress-container').accordion({
+ collapsible: true
+ });
+ }
+
+ if(kimchi.capabilities['system_report_tool']) {
+ listDebugReports();
+ wok.topic('kimchi/debugReportAdded')
+ .subscribe(listDebugReports);
+ wok.topic('kimchi/debugReportRenamed')
+ .subscribe(listDebugReports);
+ }
+ };
+ setupUI();
+ };
+
+ kimchi.getHost(function(data) {
+ var htmlTmpl = $('#host-tmpl').html();
+ data['logo'] = data['logo'] || '';
+ data['memory'] = wok.formatMeasurement(data['memory'], {
+ fixed: 2
+ });
+ var templated = wok.substitute(htmlTmpl, data);
+ $('#host-content-container').html(templated);
+
+ initPage();
+ initTracker();
+ });
+
+ var StatsMgr = function() {
+ var statsArray = {
+ cpu: {
+ u: {
+ type: 'percent',
+ legend: i18n['GGBHOST6002M'],
+ points: []
+ }
+ },
+ memory: {
+ u: {
+ type: 'value',
+ base: 2,
+ fixed: 2,
+ legend: i18n['GGBHOST6003M'],
+ points: []
+ }
+ },
+ diskIO: {
+ r: {
+ type: 'value',
+ base: 2,
+ fixed: 2,
+ unit: 'B/s',
+ legend: i18n['GGBHOST6004M'],
+ points: []
+ },
+ w: {
+ type: 'value',
+ base: 2,
+ fixed: 2,
+ unit: 'B/s',
+ legend: i18n['GGBHOST6005M'],
+ 'class': 'disk-write',
+ points: []
+ }
+ },
+ networkIO: {
+ r: {
+ type: 'value',
+ base: 2,
+ fixed: 2,
+ unit: 'B/s',
+ legend: i18n['GGBHOST6006M'],
+ points: []
+ },
+ s: {
+ type: 'value',
+ base: 2,
+ fixed: 2,
+ unit: 'B/s',
+ legend: i18n['GGBHOST6007M'],
+ 'class': 'network-sent',
+ points: []
+ }
+ }
+ };
+ var SIZE = 20;
+ var cursor = SIZE;
+
+ var add = function(stats) {
+ for(var key in stats) {
+ var item = stats[key];
+ for(var metrics in item) {
+ var value = item[metrics]['v'];
+ var max = item[metrics]['max'];
+ var unifiedMetrics = statsArray[key][metrics];
+ var ps = unifiedMetrics['points'];
+ if(!Array.isArray(value)){
+ ps.push(value);
+ if(ps.length > SIZE + 1) {
+ ps.shift();
+ }
+ }
+ else{
+ ps=ps.concat(value);
+ ps.splice(0, ps.length-SIZE-1);
+ unifiedMetrics['points']=ps;
+ }
+ if(max !== undefined) {
+ unifiedMetrics['max'] = max;
+ }
+ else {
+ if(unifiedMetrics['type'] !== 'value') {
+ continue;
+ }
+ max = -Infinity;
+ $.each(ps, function(i, value) {
+ if(value > max) {
+ max = value;
+ }
+ });
+ if(max === 0) {
+ ++max;
+ }
+ max *= 1.1;
+ unifiedMetrics['max'] = max;
+ }
+ }
+ }
+ cursor++;
+ };
+
+ var get = function(which) {
+ var stats = statsArray[which];
+ var lines = [];
+ for(var k in stats) {
+ var obj = stats[k];
+ var line = {
+ type: obj['type'],
+ base: obj['base'],
+ unit: obj['unit'],
+ fixed: obj['fixed'],
+ legend: obj['legend']
+ };
+ if(obj['max']) {
+ line['max'] = obj['max'];
+ }
+ if(obj['class']) {
+ line['class'] = obj['class'];
+ }
+ var ps = obj['points'];
+ var numStats = ps.length;
+ var unifiedPoints = [];
+ $.each(ps, function(i, value) {
+ unifiedPoints.push({
+ x: cursor - numStats + i,
+ y: value
+ });
+ });
+ line['points'] = unifiedPoints;
+ lines.push(line);
+ }
+ return lines;
+ };
+
+ return {
+ add: add,
+ get: get
+ };
+ };
+
+ var Tracker = function(charts) {
+ var charts = charts;
+ var timer = null;
+ var statsPool = new StatsMgr();
+ var setCharts = function(newCharts) {
+ charts = newCharts;
+ for(var key in charts) {
+ var chart = charts[key];
+ chart.updateUI(statsPool.get(key));
+ }
+ };
+
+ var self = this;
+
+ var UnifyStats = function(stats) {
+ var result= {
+ cpu: {
+ u: {
+ v: stats['cpu_utilization']
+ }
+ },
+ memory: {
+ u: {
+ }
+ },
+ diskIO: {
+ r: {
+ v: stats['disk_read_rate']
+ },
+ w: {
+ v: stats['disk_write_rate']
+ }
+ },
+ networkIO: {
+ r: {
+ v: stats['net_recv_rate']
+ },
+ s: {
+ v: stats['net_sent_rate']
+ }
+ }
+ };
+ if(Array.isArray(stats['memory'])){
+ result.memory.u['v']=[];
+ result.memory.u['max']=-Infinity;
+ for(var i=0;i<stats['memory'].length;i++){
+ result.memory.u['v'].push(stats['memory'][i]['avail']);
+ result.memory.u['max']=Math.max(result.memory.u['max'],stats['memory'][i]['total']);
+ }
+ }
+ else {
+ result.memory.u['v']=stats['memory']['avail'],
+ result.memory.u['max']=stats['memory']['total']
+ }
+ return(result);
+ };
+
+
+ var statsCallback = function(stats) {
+ var unifiedStats = UnifyStats(stats);
+ statsPool.add(unifiedStats);
+ for(var key in charts) {
+ var chart = charts[key];
+ chart.updateUI(statsPool.get(key));
+ }
+ timer = setTimeout(function() {
+ continueTrack();
+ }, 1000);
+ };
+
+ var track = function() {
+ kimchi.getHostStatsHistory(statsCallback,
+ function() {
+ continueTrack();
+ });
+ };
+
+ var continueTrack = function() {
+ kimchi.getHostStats(statsCallback,
+ function() {
+ continueTrack();
+ });
+ };
+
+ var destroy = function() {
+ timer && clearTimeout(timer);
+ timer = null;
+ };
+
+ return {
+ setCharts: setCharts,
+ start: track,
+ stop: destroy
+ };
+ };
+
+ var initTracker = function() {
+ // TODO: Extend tabs with onUnload event to unregister timers.
+ if(kimchi.hostTimer) {
+ kimchi.hostTimer.stop();
+ delete kimchi.hostTimer;
+ }
+
+ var trackedCharts = {
+ cpu: new kimchi.widget.LineChart({
+ id: 'chart-cpu',
+ node: 'container-chart-cpu',
+ type: 'percent'
+ }),
+ memory: new kimchi.widget.LineChart({
+ id: 'chart-memory',
+ node: 'container-chart-memory',
+ type: 'value'
+ }),
+ diskIO: new kimchi.widget.LineChart({
+ id: 'chart-disk-io',
+ node: 'container-chart-disk-io',
+ type: 'value'
+ }),
+ networkIO: new kimchi.widget.LineChart({
+ id: 'chart-network-io',
+ node: 'container-chart-network-io',
+ type: 'value'
+ })
+ };
+
+ if(kimchi.hostTimer) {
+ kimchi.hostTimer.setCharts(trackedCharts);
+ }
+ else {
+ kimchi.hostTimer = new Tracker(trackedCharts);
+ kimchi.hostTimer.start();
+ }
+ };
+
+ $('#host-root-container').on('remove', function() {
+ if(kimchi.hostTimer) {
+ kimchi.hostTimer.stop();
+ delete kimchi.hostTimer;
+ }
+
+ repositoriesGrid && repositoriesGrid.destroy();
+ wok.topic('kimchi/repositoryAdded')
+ .unsubscribe(listRepositories);
+ wok.topic('kimchi/repositoryUpdated')
+ .unsubscribe(listRepositories);
+ wok.topic('kimchi/repositoryDeleted')
+ .unsubscribe(listRepositories);
+
+ softwareUpdatesGrid && softwareUpdatesGrid.destroy();
+ wok.topic('kimchi/softwareUpdated').unsubscribe(listSoftwareUpdates);
+
+ reportGrid && reportGrid.destroy();
+ wok.topic('kimchi/debugReportAdded').unsubscribe(listDebugReports);
+ wok.topic('kimchi/debugReportRenamed').unsubscribe(listDebugReports);
+ });
+};
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.line-chart.js b/plugins/gingerbase/ui/js/src/gingerbase.line-chart.js
new file mode 100644
index 0000000..13bbee1
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.line-chart.js
@@ -0,0 +1,202 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013-2014
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * new kimchi.widget.LineChart({
+ * node: 'line-chart-cpu',
+ * id: 'line-chart',
+ * type: 'value'
+ * });
+ */
+kimchi.widget.LineChart = function(params) {
+ var container = $('#' + params['node']);
+ container.addClass('chart-container');
+ var height = container.height();
+ var width = container.width();
+ var numHLines = 4;
+ var linesSpace = height / numHLines;
+ var period = params['period'] || 20;
+ var xFactor = width / period;
+ var yFactor = height / 100;
+ var xStart = params['xStart'] || 0;
+ var linesOffset = 0;
+ var canvasID = params['id'];
+ var maxValue = params['maxValue'] || -Infinity;
+ var type = params['type'];
+ var chartVAxis = null;
+ var chartTitle = null;
+ var chartLegend = null;
+ var seriesMap = {};
+ var formatSettings = {};
+
+ var setMaxValue = function(newValue) {
+ maxValue = newValue;
+ };
+
+ /**
+ *
+ * settings: {
+ * 'class': 'disk-read-rate'
+ * }
+ */
+ var updateUI = function(data) {
+ var container = $('#' + params['node']);
+ if(!container.length) {
+ return;
+ }
+
+ if(!$.isArray(data)) {
+ data = [data];
+ }
+ var seriesCount = 0;
+ var singleSeries = data.length === 1;
+ var firstSeries = data[0];
+
+ // TODO: Multiple axes support.
+ if(type === 'value') {
+ $.each(data, function(i, series) {
+ if(series['max'] > maxValue) {
+ maxValue = series['max'];
+ formatSettings = {
+ base: series['base'],
+ unit: series['unit'],
+ fixed: series['fixed']
+ };
+ }
+ });
+ }
+
+ var canvasNode = $('#' + canvasID);
+ canvasNode.length && canvasNode.remove();
+ var htmlStr = [
+ '<svg id="', canvasID, '" class="line-chart"',
+ ' height="', height, '" width="', width, '"',
+ '>',
+ '<rect height="', height, '" width="', width, '" class="background" />'
+ ];
+
+ for(var x = linesOffset; x < width; x += linesSpace) {
+ htmlStr.push(
+ '<line x1="', x, '" y1="', 0, '" x2="', x, '" y2="', height, '" />'
+ );
+ }
+
+ linesOffset -= xFactor;
+ while(linesOffset < 0) {
+ linesOffset = linesSpace + linesOffset;
+ }
+
+ for(var y = height - linesSpace; y > 0; y -= linesSpace) {
+ htmlStr.push(
+ '<line x1="', 0, '" y1="', y, '" x2="', width, '" y2="', y, '" />'
+ );
+ }
+
+ var maxValueLabel = i18n['GGBHOST6001M'] + ' ' +
+ (type === 'value'
+ ? wok.formatMeasurement(maxValue, formatSettings)
+ : '100%');
+ if(!chartVAxis) {
+ chartVAxis = $('<div class="chart-vaxis-container">' +
+ maxValueLabel +
+ '</div>'
+ );
+ container.before(chartVAxis);
+ }
+ else {
+ chartVAxis.text(maxValueLabel);
+ }
+
+ seriesNames = [];
+ $.each(data, function(i, series) {
+ var points = series['points'];
+ var className = series['class'];
+ var latestPoint = points.slice(-1).pop();
+ xStart = latestPoint['x'] - period;
+
+ htmlStr.push('<polyline',
+ ' class="series', className ? ' ' + className : '', '"',
+ ' points="'
+ );
+ var first = true;
+ $.each(points, function(i, point) {
+ if(first) {
+ first = false;
+ }
+ else {
+ htmlStr.push(' ');
+ }
+
+ var x = xFactor * (point['x'] - xStart);
+ var y = height - yFactor * (type === 'value' ?
+ point['y'] * 100 / maxValue :
+ point['y']
+ );
+ htmlStr.push(x, ',', y);
+ });
+ htmlStr.push('" />');
+ });
+
+ htmlStr.push('</svg>');
+
+ var canvasNode = $(htmlStr.join('')).appendTo(container);
+
+ if(!chartLegend) {
+ chartLegend = $('<div class="chart-legend-container"></div>');
+ container.after(chartLegend);
+ }
+ else {
+ chartLegend.empty();
+ }
+ $('polyline.series', canvasNode).each(function(i, polyline) {
+ var wrapper = $('<div class="legend-wrapper"></div>')
+ .appendTo(chartLegend);
+ $([
+ '<svg class="legend-icon" width="20" height="10">',
+ '<line x1="0" y1="5" x2="20" y2="5"/>',
+ '</svg>'
+ ].join('')).appendTo(wrapper);
+ $('line', wrapper).css({
+ stroke: $(polyline).css('stroke'),
+ 'stroke-width': $(polyline).css('stroke-width')
+ });
+ var label = data[i]['legend'];
+ var base = data[i]['base'];
+ $('<label class="legend-label">' + label + '</label>')
+ .appendTo(wrapper);
+ var latestPoint = data[i]['points'].slice(-1).pop();
+ var latestValue = latestPoint['y'];
+ if(type === 'value') {
+ latestValue = wok.formatMeasurement(
+ latestValue,
+ formatSettings
+ );
+ }
+ else {
+ latestValue += '%';
+ }
+ $('<div class="latest-value">' + latestValue + '</div>')
+ .appendTo(wrapper);
+ });
+ };
+
+ return {
+ setMaxValue: setMaxValue,
+ updateUI: updateUI
+ }
+};
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.main.js b/plugins/gingerbase/ui/js/src/gingerbase.main.js
new file mode 100644
index 0000000..2fdeb85
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.main.js
@@ -0,0 +1,26 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013-2014
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+kimchi.capabilities = undefined;
+kimchi.getCapabilities(function(result) {
+ kimchi.capabilities = result;
+
+ if(kimchi.capabilities.federation=="on")
+ $('#peers').removeClass('hide-content');
+}, function() {
+ kimchi.capabilities = {};
+});
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.report_add_main.js b/plugins/gingerbase/ui/js/src/gingerbase.report_add_main.js
new file mode 100644
index 0000000..87010b1
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.report_add_main.js
@@ -0,0 +1,72 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013-2014
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+kimchi.report_add_main = function() {
+ var reportGridID = 'available-reports-grid';
+ var addReportForm = $('#form-report-add');
+ var submitButton = $('#button-report-add');
+ var nameTextbox = $('input[name="name"]', addReportForm);
+ nameTextbox.select();
+
+ var submitForm = function(event) {
+ if(submitButton.prop('disabled')) {
+ return false;
+ }
+ var reportName = nameTextbox.val();
+ var validator = RegExp("^[_A-Za-z0-9-]*$");
+ if (!validator.test(reportName)) {
+ wok.message.error.code('GGBDR6011M');
+ return false;
+ }
+ var formData = addReportForm.serializeObject();
+ var taskAccepted = false;
+ var onTaskAccepted = function() {
+ if(taskAccepted) {
+ return;
+ }
+ taskAccepted = true;
+ wok.window.close();
+ wok.topic('kimchi/debugReportAdded').publish();
+ };
+
+ kimchi.createReport(formData, function(result) {
+ onTaskAccepted();
+ wok.topic('kimchi/debugReportAdded').publish();
+ }, function(result) {
+ // Error message from Async Task status
+ if (result['message']) {
+ var errText = result['message'];
+ }
+ // Error message from standard kimchi exception
+ else {
+ var errText = result['responseJSON']['reason'];
+ }
+ result && wok.message.error(errText);
+
+ taskAccepted &&
+ $('.grid-body-view table tr:first-child',
+ '#' + reportGridID).remove();
+ submitButton.prop('disabled', false);
+ nameTextbox.select();
+ }, onTaskAccepted);
+
+ event.preventDefault();
+ };
+
+ addReportForm.on('submit', submitForm);
+ submitButton.on('click', submitForm);
+};
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.report_rename_main.js b/plugins/gingerbase/ui/js/src/gingerbase.report_rename_main.js
new file mode 100644
index 0000000..6134b2e
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.report_rename_main.js
@@ -0,0 +1,66 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2014
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+kimchi.report_rename_main = function() {
+ var renameReportForm = $('#form-report-rename');
+ var submitButton = $('#button-report-rename');
+ var nameTextbox = $('input[name="name"]', renameReportForm);
+ var submitForm = function(event) {
+ if(submitButton.prop('disabled')) {
+ return false;
+ }
+ var reportName = nameTextbox.val();
+
+ // if the user hasn't changed the report's name,
+ // nothing should be done.
+ if (reportName == kimchi.selectedReport) {
+ wok.message.error.code('GGBDR6013M');
+ return false;
+ }
+
+ var validator = RegExp("^[A-Za-z0-9-]*$");
+ if (!validator.test(reportName)) {
+ wok.message.error.code('GGBDR6011M');
+ return false;
+ }
+ var formData = renameReportForm.serializeObject();
+ submitButton.prop('disabled', true);
+ nameTextbox.prop('disabled', true);
+ kimchi.renameReport(kimchi.selectedReport, formData, function(result) {
+ submitButton.prop('disabled', false);
+ nameTextbox.prop('disabled', false);
+ wok.window.close();
+ wok.topic('kimchi/debugReportRenamed').publish({
+ result: result
+ });
+ }, function(result) {
+ var errText = result &&
+ result['responseJSON'] &&
+ result['responseJSON']['reason'];
+ wok.message.error(errText);
+ submitButton.prop('disabled', false);
+ nameTextbox.prop('disabled', false).focus();
+ });
+
+ event.preventDefault();
+ };
+
+ renameReportForm.on('submit', submitForm);
+ submitButton.on('click', submitForm);
+
+ nameTextbox.val(kimchi.selectedReport).select();
+};
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.repository_add_main.js b/plugins/gingerbase/ui/js/src/gingerbase.repository_add_main.js
new file mode 100644
index 0000000..656306b
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.repository_add_main.js
@@ -0,0 +1,96 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2014
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+kimchi.repository_add_main = function() {
+
+ var addForm = $('#form-repository-add');
+ var addButton = $('#button-repository-add');
+
+ var validateField = function(event) {
+ var valid=($(this).val()!=='');
+ $(addButton).prop('disabled', !valid);
+ return(valid);
+ };
+
+ var validateForm = function(event) {
+ var valid=false;
+ addForm.find('input.required').each( function() {
+ valid=($(this).val()!=='');
+ return(!valid);
+ });
+ return(valid);
+ }
+
+ addForm.find('input.required').on('input propertychange', validateField);
+
+ var weedObject = function(obj) {
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ if((typeof(obj[key])==="object") && !Array.isArray(obj[key])) {
+ weedObject(obj[key]);
+ }
+ else if(obj[key] == '') {
+ delete obj[key];
+ }
+ }
+ }
+ }
+
+ var addRepository = function(event) {
+ var valid = validateForm();
+ if(!valid) {
+ return false;
+ }
+
+ var formData = $(addForm).serializeObject();
+
+ if (formData && formData.isMirror!=undefined) {
+ formData.isMirror=(String(formData.isMirror).toLowerCase() === 'true');
+ }
+ if(formData.isMirror) {
+ if(formData.config==undefined) {
+ formData.config=new Object();
+ }
+ formData.config.mirrorlist=formData.baseurl;
+ delete formData.baseurl;
+ delete formData.isMirror;
+ }
+ weedObject(formData);
+ if(formData.config && formData.config.comps) {
+ formData.config.comps=formData.config.comps.split(/[,\s]/);
+ for(var i=0; i>formData.config.comps.length; i++) {
+ formData.config.comps[i]=formData.config.comps[i].trim();
+ }
+ for (var j=formData.config.comps.indexOf(""); j!=-1; j=formData.config.comps.indexOf("")) {
+ formData.config.comps.splice(j, 1);
+ }
+ }
+
+ kimchi.createRepository(formData, function() {
+ wok.topic('kimchi/repositoryAdded').publish();
+ wok.window.close();
+ }, function(jqXHR, textStatus, errorThrown) {
+ var reason = jqXHR &&
+ jqXHR['responseJSON'] &&
+ jqXHR['responseJSON']['reason'];
+ wok.message.error(reason);
+ });
+ return false;
+ };
+
+ $(addForm).on('submit', addRepository);
+};
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.repository_edit_main.js b/plugins/gingerbase/ui/js/src/gingerbase.repository_edit_main.js
new file mode 100644
index 0000000..5bfc51e
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.repository_edit_main.js
@@ -0,0 +1,74 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2014
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+kimchi.repository_edit_main = function() {
+
+ var editForm = $('#form-repository-edit');
+
+ var saveButton = $('#repository-edit-button-save');
+
+ if(kimchi.capabilities['repo_mngt_tool']=="yum") {
+ editForm.find('input.deb').prop('disabled', true);
+ }
+ else if(kimchi.capabilities['repo_mngt_tool']=="deb") {
+ editForm.find('input.yum').prop('disabled', true);
+ }
+
+ kimchi.retrieveRepository(kimchi.selectedRepository, function(repository) {
+ editForm.fillWithObject(repository);
+
+ $('input', editForm).on('input propertychange', function(event) {
+ if($(this).val() !== '') {
+ $(saveButton).prop('disabled', false);
+ }
+ });
+ });
+
+
+ var editRepository = function(event) {
+ var formData = $(editForm).serializeObject();
+
+ if (formData && formData.config) {
+ formData.config.gpgcheck=(String(formData.config.gpgcheck).toLowerCase() === 'true');
+ }
+
+ if(formData.config && formData.config.comps) {
+ formData.config.comps=formData.config.comps.split(/[,\s]/);
+ for(var i=0; i>formData.config.comps.length; i++) {
+ formData.config.comps[i]=formData.config.comps[i].trim();
+ }
+ for (var j=formData.config.comps.indexOf(""); j!=-1; j=formData.config.comps.indexOf("")) {
+ formData.config.comps.splice(j, 1);
+ }
+ }
+
+ kimchi.updateRepository(kimchi.selectedRepository, formData, function() {
+ wok.topic('kimchi/repositoryUpdated').publish();
+ wok.window.close();
+ }, function(jqXHR, textStatus, errorThrown) {
+ var reason = jqXHR &&
+ jqXHR['responseJSON'] &&
+ jqXHR['responseJSON']['reason'];
+ wok.message.error(reason);
+ });
+
+ return false;
+ };
+
+ $(editForm).on('submit', editRepository);
+ $(saveButton).on('click', editRepository);
+};
diff --git a/plugins/gingerbase/ui/js/src/gingerbase.select.js b/plugins/gingerbase/ui/js/src/gingerbase.select.js
new file mode 100644
index 0000000..751167f
--- /dev/null
+++ b/plugins/gingerbase/ui/js/src/gingerbase.select.js
@@ -0,0 +1,50 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013-2014
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+kimchi.select = function(id, options) {
+ var listControl = $('#'+ id);
+ var targetId = listControl.data('target');
+ var labelId = listControl.data('label');
+ var value = $('#' + targetId).val();
+ var item;
+ var itemTag = 'li';
+ var selectedClass = 'active';
+ $.each(options, function(index, option) {
+ item = $('<' + itemTag + '></' + itemTag + '>');
+ item.text(option.label);
+ item.data('value', option.value);
+ if(option.value === value) {
+ item.addClass(selectedClass);
+ $('#' + labelId).text(option.label);
+ }
+ listControl.append(item);
+ });
+
+ listControl.on('click', itemTag, function() {
+ listControl.children().removeClass(selectedClass);
+ $(this).addClass(selectedClass);
+ $('#' + labelId).text($(this).text());
+ var target = $('#' + targetId);
+ var oldValue = target.val();
+ var newValue = $(this).data('value');
+ target.val(newValue);
+ if(oldValue !== newValue) {
+ target.change();
+ }
+ });
+};
diff --git a/plugins/gingerbase/ui/js/widgets/circleGauge.js b/plugins/gingerbase/ui/js/widgets/circleGauge.js
new file mode 100644
index 0000000..32973ac
--- /dev/null
+++ b/plugins/gingerbase/ui/js/widgets/circleGauge.js
@@ -0,0 +1,100 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2014
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ (function($) {
+ $.widget('kimchi.circleGauge', {
+
+ options : {
+ color : '#87C004',
+ fillColor : '#87C004',
+ lineWidth : 20,
+ shadowSize : '2px',
+ font : 'bold 13px Geneva, sans-serif',
+ textAlign : 'center',
+ radius : 35,
+ peakRate : 100,
+ display : 0,
+ circle : 0,
+ label : ''
+ },
+
+ _create : function() {
+ //valuesAttr="{" + this.element.data('value')+ "}";
+ //console.info(valuesAttr);
+ //values=eval("(" + valuesAttr + ")");
+ //$.extend(this.options, values);
+ this.options.display=this.element.data('display');
+ this.options.percentage=this.element.data('percentage');
+ this._fixupPeakRate();
+ this._draw();
+ },
+
+ setValues : function(values) {
+ $.extend(this.options, values);
+ this._fixupPeakRate();
+ this._draw();
+ },
+
+ _fixupPeakRate : function() {
+ if (this.options.circle>this.options.peakRate) {
+ this.options.peakRate=this.options.circle;
+ }
+ },
+
+ _draw : function() {
+ this.element.empty();
+ var canvas = document.createElement('canvas');
+ //this.element.append($(canvas)); //I don't quite understand this line so trying the one below...
+ this.element.append(canvas);
+
+ var ctx = canvas.getContext('2d');
+ var radius = this.options.radius;
+
+ var shadowSize = 2;
+ var width = height = radius * 2;
+ $(canvas).attr('height', height);
+ $(canvas).attr('width', width);
+
+ $(canvas).css({
+ 'boxShadow' : shadowSize + 'px ' + shadowSize + 'px ' + shadowSize + 'px #fff, -' + shadowSize + 'px -' + shadowSize + 'px ' + shadowSize + 'px #eaeaea',
+ borderRadius : radius + 'px'
+ });
+
+ ctx.clearRect(0, 0, width, height);
+ ctx.fillStyle = this.options.fillColor;
+ ctx.font = this.options.font;
+ ctx.textAlign = 'center';
+ var originPos = radius;
+ ctx.textBaseline = 'middle';
+ ctx.fillText(this.options.display, originPos, originPos);
+ ctx.strokeStyle = this.options.color;
+ ctx.lineWidth = this.options.lineWidth;
+ ctx.beginPath();
+ ctx.arc(originPos, originPos, radius, -.5 * Math.PI, (this.options.percentage / 50 - .5) * Math.PI);
+ ctx.stroke();
+ },
+
+ destroy : function() {
+ this.element.empty();
+ $.Widget.prototype.destroy.call(this);
+ }
+ });
+}(jQuery));
+
+kimchi.circleGauge = function(selector) {
+ $(selector).circleGauge();
+};
diff --git a/plugins/kimchi/ui/js/src/kimchi.host.js b/plugins/kimchi/ui/js/src/kimchi.host.js
deleted file mode 100644
index ab02333..0000000
--- a/plugins/kimchi/ui/js/src/kimchi.host.js
+++ /dev/null
@@ -1,858 +0,0 @@
-/*
- * Project Kimchi
- *
- * Copyright IBM, Corp. 2013-2014
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-kimchi.host={};
-
-kimchi.host_main = function() {
- var expand = function(header, toExpand) {
- var controlledNode = $(header).attr('aria-controls');
- $('#' + controlledNode)[toExpand ? 'removeClass' : 'addClass']('hidden');
- $(header).attr('aria-expanded', toExpand ? 'true' : 'false');
- };
-
- var repositoriesGrid = null;
- var initRepositoriesGrid = function(repo_type) {
- var gridFields=[];
- if (repo_type == "yum") {
- gridFields=[{
- name: 'repo_id',
- label: i18n['KCHREPO6004M'],
- 'class': 'repository-id'
- }, {
- name: 'config[repo_name]',
- label: i18n['KCHREPO6005M'],
- 'class': 'repository-name'
- }, {
- name: 'enabled',
- label: i18n['KCHREPO6009M'],
- 'class': 'repository-enabled'
- }];
- }
- else if (repo_type == "deb") {
- gridFields=[{
- name: 'baseurl',
- label: i18n['KCHREPO6006M'],
- makeTitle: true,
- 'class': 'repository-baseurl deb'
- }, {
- name: 'enabled',
- label: i18n['KCHREPO6009M'],
- 'class': 'repository-enabled deb'
- }, {
- name: 'config[dist]',
- label: "dist",
- 'class': 'repository-gpgcheck deb'
- }, {
- name: 'config[comps]',
- label: "comps",
- 'class': 'repository-gpgcheck deb'
- }];
- }
- else {
- gridFields=[{
- name: 'repo_id',
- label: i18n['KCHREPO6004M'],
- 'class': 'repository-id'
- }, {
- name: 'enabled',
- label: i18n['KCHREPO6009M'],
- 'class': 'repository-enabled'
- }, {
- name: 'baseurl',
- label: i18n['KCHREPO6006M'],
- makeTitle: true,
- 'class': 'repository-baseurl'
- }];
- }
- repositoriesGrid = new kimchi.widget.Grid({
- container: 'repositories-grid-container',
- id: 'repositories-grid',
- title: i18n['KCHREPO6003M'],
- toolbarButtons: [{
- id: 'repositories-grid-add-button',
- label: i18n['KCHREPO6012M'],
- onClick: function(event) {
- wok.window.open({url:'plugins/kimchi/repository-add.html',
- class: repo_type});
- }
- }, {
- id: 'repositories-grid-enable-button',
- label: i18n['KCHREPO6016M'],
- disabled: true,
- onClick: function(event) {
- var repository = repositoriesGrid.getSelected();
- if(!repository) {
- return;
- }
- var name = repository['repo_id'];
- var enable = !repository['enabled'];
- $(this).prop('disabled', true);
- kimchi.enableRepository(name, enable, function() {
- wok.topic('kimchi/repositoryUpdated').publish();
- });
- }
- }, {
- id: 'repositories-grid-edit-button',
- label: i18n['KCHREPO6013M'],
- disabled: true,
- onClick: function(event) {
- var repository = repositoriesGrid.getSelected();
- if(!repository) {
- return;
- }
- kimchi.selectedRepository = repository['repo_id'];
- wok.window.open({url:'plugins/kimchi/repository-edit.html',
- class: repo_type});
- }
- }, {
- id: 'repositories-grid-remove-button',
- label: i18n['KCHREPO6014M'],
- disabled: true,
- onClick: function(event) {
- var repository = repositoriesGrid.getSelected();
- if(!repository) {
- return;
- }
-
- var settings = {
- title : i18n['KCHREPO6001M'],
- content : i18n['KCHREPO6002M'],
- confirm : i18n['KCHAPI6004M'],
- cancel : i18n['KCHAPI6003M']
- };
-
- wok.confirm(settings, function() {
- kimchi.deleteRepository(
- repository['repo_id'],
- function(result) {
- wok.topic('kimchi/repositoryDeleted').publish(result);
- }, function(error) {
- }
- );
- });
- }
- }],
- onRowSelected: function(row) {
- var repository = repositoriesGrid.getSelected();
- if(!repository) {
- return;
- }
- $('#repositories-grid-remove-button').prop('disabled', false);
- $('#repositories-grid-edit-button').prop('disabled', false);
- var enabled = repository['enabled'];
- $('#repositories-grid-enable-button')
- .text(i18n[enabled ? 'KCHREPO6017M' : 'KCHREPO6016M'])
- .prop('disabled', false);
- },
- frozenFields: [],
- fields: gridFields,
- data: listRepositories
- });
- };
-
- var listRepositories = function(gridCallback) {
- kimchi.listRepositories(function(repositories) {
- if($.isFunction(gridCallback)) {
- gridCallback(repositories);
- }
- else {
- if(repositoriesGrid) {
- repositoriesGrid.setData(repositories);
- }
- else {
- initRepositoriesGrid();
- repositoriesGrid.setData(repositories);
- }
- }
- },
- function(error) {
- var message = error && error['responseJSON'] && error['responseJSON']['reason'];
-
- if($.isFunction(gridCallback)) {
- gridCallback([]);
- }
- repositoriesGrid &&
- repositoriesGrid.showMessage(message || i18n['KCHUPD6008M']);
- });
-
- $('#repositories-grid-remove-button').prop('disabled', true);
- $('#repositories-grid-edit-button').prop('disabled', true);
- $('#repositories-grid-enable-button').prop('disabled', true);
- };
-
- var softwareUpdatesGridID = 'software-updates-grid';
- var softwareUpdatesGrid = null;
- var progressAreaID = 'software-updates-progress-textarea';
- var reloadProgressArea = function(result) {
- var progressArea = $('#' + progressAreaID)[0];
- $(progressArea).text(result['message']);
- var scrollTop = $(progressArea).prop('scrollHeight');
- $(progressArea).prop('scrollTop', scrollTop);
- };
-
- var initSoftwareUpdatesGrid = function(softwareUpdates) {
- softwareUpdatesGrid = new kimchi.widget.Grid({
- container: 'software-updates-grid-container',
- id: softwareUpdatesGridID,
- title: i18n['KCHUPD6001M'],
- rowSelection: 'disabled',
- toolbarButtons: [{
- id: softwareUpdatesGridID + '-update-button',
- label: i18n['KCHUPD6006M'],
- disabled: true,
- onClick: function(event) {
- var updateButton = $(this);
- var progressArea = $('#' + progressAreaID)[0];
- $('#software-updates-progress-container').removeClass('hidden');
- $(progressArea).text('');
- !wok.isElementInViewport(progressArea) &&
- progressArea.scrollIntoView();
- $(updateButton).text(i18n['KCHUPD6007M']).prop('disabled', true);
-
- kimchi.updateSoftware(function(result) {
- reloadProgressArea(result);
- $(updateButton).text(i18n['KCHUPD6006M']).prop('disabled', false);
- wok.topic('kimchi/softwareUpdated').publish({
- result: result
- });
- }, function(error) {
- var message = error && error['responseJSON'] && error['responseJSON']['reason'];
- wok.message.error(message || i18n['KCHUPD6009M']);
- $(updateButton).text(i18n['KCHUPD6006M']).prop('disabled', false);
- }, reloadProgressArea);
- }
- }],
- frozenFields: [],
- fields: [{
- name: 'package_name',
- label: i18n['KCHUPD6002M'],
- 'class': 'software-update-name'
- }, {
- name: 'version',
- label: i18n['KCHUPD6003M'],
- 'class': 'software-update-version'
- }, {
- name: 'arch',
- label: i18n['KCHUPD6004M'],
- 'class': 'software-update-arch'
- }, {
- name: 'repository',
- label: i18n['KCHUPD6005M'],
- 'class': 'software-update-repos'
- }],
- data: listSoftwareUpdates
- });
- };
-
- var listSoftwareUpdates = function(gridCallback) {
- kimchi.listSoftwareUpdates(function(softwareUpdates) {
- if($.isFunction(gridCallback)) {
- gridCallback(softwareUpdates);
- }
- else {
- if(softwareUpdatesGrid) {
- softwareUpdatesGrid.setData(softwareUpdates);
- }
- else {
- initSoftwareUpdatesGrid(softwareUpdates);
- }
- }
-
- var updateButton = $('#' + softwareUpdatesGridID + '-update-button');
- $(updateButton).prop('disabled', softwareUpdates.length === 0);
- }, function(error) {
- var message = error && error['responseJSON'] && error['responseJSON']['reason'];
- if($.isFunction(gridCallback)) {
- gridCallback([]);
- }
- softwareUpdatesGrid &&
- softwareUpdatesGrid.showMessage(message || i18n['KCHUPD6008M']);
- });
- };
-
- var reportGridID = 'available-reports-grid';
- var reportGrid = null;
- var enableReportButtons = function(toEnable) {
- var buttonID = '#{grid}-{btn}-button';
- $.each(['rename', 'remove', 'download'], function(i, n) {
- $(wok.substitute(buttonID, {
- grid: reportGridID,
- btn: n
- })).prop('disabled', !toEnable);
- });
- };
- var initReportGrid = function(reports) {
- reportGrid = new kimchi.widget.Grid({
- container: 'available-reports-grid-container',
- id: reportGridID,
- title: i18n['KCHDR6002M'],
- toolbarButtons: [{
- id: reportGridID + '-generate-button',
- label: i18n['KCHDR6006M'],
- onClick: function(event) {
- wok.window.open('plugins/kimchi/report-add.html');
- }
- }, {
- id: reportGridID + '-rename-button',
- label: i18n['KCHDR6008M'],
- disabled: true,
- onClick: function(event) {
- var report = reportGrid.getSelected();
- if(!report) {
- return;
- }
-
- kimchi.selectedReport = report['name'];
- wok.window.open('plugins/kimchi/report-rename.html');
- }
- }, {
- id: reportGridID + '-remove-button',
- label: i18n['KCHDR6009M'],
- disabled: true,
- onClick: function(event) {
- var report = reportGrid.getSelected();
- if(!report) {
- return;
- }
-
- var settings = {
- title : i18n['KCHAPI6004M'],
- content : i18n['KCHDR6001M'],
- confirm : i18n['KCHAPI6002M'],
- cancel : i18n['KCHAPI6003M']
- };
-
- wok.confirm(settings, function() {
- kimchi.deleteReport({
- name: report['name']
- }, function(result) {
- listDebugReports();
- }, function(error) {
- wok.message.error(error.responseJSON.reason);
- });
- });
- }
- }, {
- id: reportGridID + '-download-button',
- label: i18n['KCHDR6010M'],
- disabled: true,
- onClick: function(event) {
- var report = reportGrid.getSelected();
- if(!report) {
- return;
- }
-
- kimchi.downloadReport({
- file: report['uri']
- });
- }
- }],
- onRowSelected: function(row) {
- var report = reportGrid.getSelected();
- // Only enable report buttons if the selected line is not a
- // pending report
- if (report['time'] == i18n['KCHDR6007M']) {
- var gridElement = $('#'+ reportGridID);
- var row = $('tr:contains(' + report['name'] + ')', gridElement);
- enableReportButtons(false);
- row.attr('class', '');
- }
- else {
- enableReportButtons(true);
- }
- },
- frozenFields: [],
- fields: [{
- name: 'name',
- label: i18n['KCHDR6003M'],
- 'class': 'debug-report-name'
- }, {
- name: 'time',
- label: i18n['KCHDR6005M'],
- 'class': 'debug-report-time'
- }],
- data: reports
- });
- };
-
- var getPendingReports = function() {
- var reports = []
- var filter = 'status=running&target_uri=' + encodeURIComponent('^/plugins/kimchi/debugreports/*')
-
- kimchi.getTasksByFilter(filter, function(tasks) {
- for(var i = 0; i < tasks.length; i++) {
- reportName = tasks[i].target_uri.replace(/^\/plugins\/kimchi\/debugreports\//, '') || i18n['KCHDR6012M'];
- reports.push({'name': reportName, 'time': i18n['KCHDR6007M']})
-
- if(kimchi.trackingTasks.indexOf(tasks[i].id) >= 0) {
- continue;
- }
-
- kimchi.trackTask(tasks[i].id, function(result) {
- wok.topic('kimchi/debugReportAdded').publish();
- }, function(result) {
- // Error message from Async Task status
- if (result['message']) {
- var errText = result['message'];
- }
- // Error message from standard kimchi exception
- else {
- var errText = result['responseJSON']['reason'];
- }
- result && wok.message.error(errText);
- wok.topic('kimchi/debugReportAdded').publish();
- }, null);
- }
- }, null, true);
-
- return reports;
- };
-
- var listDebugReports = function() {
- kimchi.listReports(function(reports) {
- pendingReports = getPendingReports();
- allReports = pendingReports.concat(reports);
- $('#debug-report-section').removeClass('hidden');
-
- // Row selection will be cleared so disable buttons here
- enableReportButtons(false);
-
- if(reportGrid) {
- reportGrid.setData(allReports);
- }
- else {
- initReportGrid(allReports);
- }
-
- // Set id-debug-img to pending reports
- // It will display a loading icon
- var gridElement = $('#' + reportGridID);
- $.each($('td:contains(' + i18n['KCHDR6007M'] + ')', gridElement), function(index, row) {
- $(row).parent().addClass('no-hover');
- $(row).attr('id', 'id-debug-img');
- });
- }, function(error) {
- if(error['status'] == 403) {
- $('#debug-report-section').addClass('hidden');
- return;
- }
- $('#debug-report-section').removeClass('hidden');
- });
- };
-
- var shutdownButtonID = '#host-button-shutdown';
- var restartButtonID = '#host-button-restart';
- var shutdownHost = function(params) {
- var settings = {
- title : i18n['KCHAPI6004M'],
- content : i18n['KCHHOST6008M'],
- confirm : i18n['KCHAPI6002M'],
- cancel : i18n['KCHAPI6003M']
- };
-
- wok.confirm(settings, function() {
- kimchi.shutdown(params);
- $(shutdownButtonID).prop('disabled', true);
- $(restartButtonID).prop('disabled', true);
- // Check if there is any VM is running.
- kimchi.listVMs(function(vms) {
- for(var i = 0; i < vms.length; i++) {
- if(vms[i]['state'] === 'running') {
- wok.message.error.code('KCHHOST6001E');
- $(shutdownButtonID).prop('disabled', false);
- $(restartButtonID).prop('disabled', false);
- return;
- }
- }
-
- });
- }, function() {
- });
- };
-
- var initPage = function() {
- $('#host-info-container .section-header').each(function(i, header) {
- $('<span class="arrow"></span>').prependTo(header);
- var toExpand = $(header).attr('aria-expanded') !== 'false';
- expand(header, toExpand);
- });
-
- $('#host-info-container').on('click', '.section-header', function(event) {
- var toExpand = $(this).attr('aria-expanded') === 'false';
- expand(this, toExpand);
- });
-
- $('#host-button-shutdown').on('click', function(event) {
- shutdownHost(null);
- });
-
- $('#host-button-restart').on('click', function(event) {
- shutdownHost({
- reboot: true
- });
- });
-
- var setupUI = function() {
- if (kimchi.capabilities == undefined) {
- setTimeout(setupUI, 2000);
- return;
- }
-
- if((kimchi.capabilities['repo_mngt_tool']) && (kimchi.capabilities['repo_mngt_tool']!="None")) {
- initRepositoriesGrid(kimchi.capabilities['repo_mngt_tool']);
- $('#repositories-section').switchClass('hidden', kimchi.capabilities['repo_mngt_tool']);
- wok.topic('kimchi/repositoryAdded')
- .subscribe(listRepositories);
- wok.topic('kimchi/repositoryUpdated')
- .subscribe(listRepositories);
- wok.topic('kimchi/repositoryDeleted')
- .subscribe(listRepositories);
- }
-
- if(kimchi.capabilities['update_tool']) {
- $('#software-update-section').removeClass('hidden');
- initSoftwareUpdatesGrid();
- wok.topic('kimchi/softwareUpdated')
- .subscribe(listSoftwareUpdates);
- $('#software-updates-progress-container').accordion({
- collapsible: true
- });
- }
-
- if(kimchi.capabilities['system_report_tool']) {
- listDebugReports();
- wok.topic('kimchi/debugReportAdded')
- .subscribe(listDebugReports);
- wok.topic('kimchi/debugReportRenamed')
- .subscribe(listDebugReports);
- }
- };
- setupUI();
- };
-
- kimchi.getHost(function(data) {
- var htmlTmpl = $('#host-tmpl').html();
- data['logo'] = data['logo'] || '';
- data['memory'] = wok.formatMeasurement(data['memory'], {
- fixed: 2
- });
- var templated = wok.substitute(htmlTmpl, data);
- $('#host-content-container').html(templated);
-
- initPage();
- initTracker();
- });
-
- var StatsMgr = function() {
- var statsArray = {
- cpu: {
- u: {
- type: 'percent',
- legend: i18n['KCHHOST6002M'],
- points: []
- }
- },
- memory: {
- u: {
- type: 'value',
- base: 2,
- fixed: 2,
- legend: i18n['KCHHOST6003M'],
- points: []
- }
- },
- diskIO: {
- r: {
- type: 'value',
- base: 2,
- fixed: 2,
- unit: 'B/s',
- legend: i18n['KCHHOST6004M'],
- points: []
- },
- w: {
- type: 'value',
- base: 2,
- fixed: 2,
- unit: 'B/s',
- legend: i18n['KCHHOST6005M'],
- 'class': 'disk-write',
- points: []
- }
- },
- networkIO: {
- r: {
- type: 'value',
- base: 2,
- fixed: 2,
- unit: 'B/s',
- legend: i18n['KCHHOST6006M'],
- points: []
- },
- s: {
- type: 'value',
- base: 2,
- fixed: 2,
- unit: 'B/s',
- legend: i18n['KCHHOST6007M'],
- 'class': 'network-sent',
- points: []
- }
- }
- };
- var SIZE = 20;
- var cursor = SIZE;
-
- var add = function(stats) {
- for(var key in stats) {
- var item = stats[key];
- for(var metrics in item) {
- var value = item[metrics]['v'];
- var max = item[metrics]['max'];
- var unifiedMetrics = statsArray[key][metrics];
- var ps = unifiedMetrics['points'];
- if(!Array.isArray(value)){
- ps.push(value);
- if(ps.length > SIZE + 1) {
- ps.shift();
- }
- }
- else{
- ps=ps.concat(value);
- ps.splice(0, ps.length-SIZE-1);
- unifiedMetrics['points']=ps;
- }
- if(max !== undefined) {
- unifiedMetrics['max'] = max;
- }
- else {
- if(unifiedMetrics['type'] !== 'value') {
- continue;
- }
- max = -Infinity;
- $.each(ps, function(i, value) {
- if(value > max) {
- max = value;
- }
- });
- if(max === 0) {
- ++max;
- }
- max *= 1.1;
- unifiedMetrics['max'] = max;
- }
- }
- }
- cursor++;
- };
-
- var get = function(which) {
- var stats = statsArray[which];
- var lines = [];
- for(var k in stats) {
- var obj = stats[k];
- var line = {
- type: obj['type'],
- base: obj['base'],
- unit: obj['unit'],
- fixed: obj['fixed'],
- legend: obj['legend']
- };
- if(obj['max']) {
- line['max'] = obj['max'];
- }
- if(obj['class']) {
- line['class'] = obj['class'];
- }
- var ps = obj['points'];
- var numStats = ps.length;
- var unifiedPoints = [];
- $.each(ps, function(i, value) {
- unifiedPoints.push({
- x: cursor - numStats + i,
- y: value
- });
- });
- line['points'] = unifiedPoints;
- lines.push(line);
- }
- return lines;
- };
-
- return {
- add: add,
- get: get
- };
- };
-
- var Tracker = function(charts) {
- var charts = charts;
- var timer = null;
- var statsPool = new StatsMgr();
- var setCharts = function(newCharts) {
- charts = newCharts;
- for(var key in charts) {
- var chart = charts[key];
- chart.updateUI(statsPool.get(key));
- }
- };
-
- var self = this;
-
- var UnifyStats = function(stats) {
- var result= {
- cpu: {
- u: {
- v: stats['cpu_utilization']
- }
- },
- memory: {
- u: {
- }
- },
- diskIO: {
- r: {
- v: stats['disk_read_rate']
- },
- w: {
- v: stats['disk_write_rate']
- }
- },
- networkIO: {
- r: {
- v: stats['net_recv_rate']
- },
- s: {
- v: stats['net_sent_rate']
- }
- }
- };
- if(Array.isArray(stats['memory'])){
- result.memory.u['v']=[];
- result.memory.u['max']=-Infinity;
- for(var i=0;i<stats['memory'].length;i++){
- result.memory.u['v'].push(stats['memory'][i]['avail']);
- result.memory.u['max']=Math.max(result.memory.u['max'],stats['memory'][i]['total']);
- }
- }
- else {
- result.memory.u['v']=stats['memory']['avail'],
- result.memory.u['max']=stats['memory']['total']
- }
- return(result);
- };
-
-
- var statsCallback = function(stats) {
- var unifiedStats = UnifyStats(stats);
- statsPool.add(unifiedStats);
- for(var key in charts) {
- var chart = charts[key];
- chart.updateUI(statsPool.get(key));
- }
- timer = setTimeout(function() {
- continueTrack();
- }, 1000);
- };
-
- var track = function() {
- kimchi.getHostStatsHistory(statsCallback,
- function() {
- continueTrack();
- });
- };
-
- var continueTrack = function() {
- kimchi.getHostStats(statsCallback,
- function() {
- continueTrack();
- });
- };
-
- var destroy = function() {
- timer && clearTimeout(timer);
- timer = null;
- };
-
- return {
- setCharts: setCharts,
- start: track,
- stop: destroy
- };
- };
-
- var initTracker = function() {
- // TODO: Extend tabs with onUnload event to unregister timers.
- if(kimchi.hostTimer) {
- kimchi.hostTimer.stop();
- delete kimchi.hostTimer;
- }
-
- var trackedCharts = {
- cpu: new kimchi.widget.LineChart({
- id: 'chart-cpu',
- node: 'container-chart-cpu',
- type: 'percent'
- }),
- memory: new kimchi.widget.LineChart({
- id: 'chart-memory',
- node: 'container-chart-memory',
- type: 'value'
- }),
- diskIO: new kimchi.widget.LineChart({
- id: 'chart-disk-io',
- node: 'container-chart-disk-io',
- type: 'value'
- }),
- networkIO: new kimchi.widget.LineChart({
- id: 'chart-network-io',
- node: 'container-chart-network-io',
- type: 'value'
- })
- };
-
- if(kimchi.hostTimer) {
- kimchi.hostTimer.setCharts(trackedCharts);
- }
- else {
- kimchi.hostTimer = new Tracker(trackedCharts);
- kimchi.hostTimer.start();
- }
- };
-
- $('#host-root-container').on('remove', function() {
- if(kimchi.hostTimer) {
- kimchi.hostTimer.stop();
- delete kimchi.hostTimer;
- }
-
- repositoriesGrid && repositoriesGrid.destroy();
- wok.topic('kimchi/repositoryAdded')
- .unsubscribe(listRepositories);
- wok.topic('kimchi/repositoryUpdated')
- .unsubscribe(listRepositories);
- wok.topic('kimchi/repositoryDeleted')
- .unsubscribe(listRepositories);
-
- softwareUpdatesGrid && softwareUpdatesGrid.destroy();
- wok.topic('kimchi/softwareUpdated').unsubscribe(listSoftwareUpdates);
-
- reportGrid && reportGrid.destroy();
- wok.topic('kimchi/debugReportAdded').unsubscribe(listDebugReports);
- wok.topic('kimchi/debugReportRenamed').unsubscribe(listDebugReports);
- });
-};
diff --git a/plugins/kimchi/ui/js/src/kimchi.report_add_main.js b/plugins/kimchi/ui/js/src/kimchi.report_add_main.js
deleted file mode 100644
index 5f098d3..0000000
--- a/plugins/kimchi/ui/js/src/kimchi.report_add_main.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Project Kimchi
- *
- * Copyright IBM, Corp. 2013-2014
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-kimchi.report_add_main = function() {
- var reportGridID = 'available-reports-grid';
- var addReportForm = $('#form-report-add');
- var submitButton = $('#button-report-add');
- var nameTextbox = $('input[name="name"]', addReportForm);
- nameTextbox.select();
-
- var submitForm = function(event) {
- if(submitButton.prop('disabled')) {
- return false;
- }
- var reportName = nameTextbox.val();
- var validator = RegExp("^[_A-Za-z0-9-]*$");
- if (!validator.test(reportName)) {
- wok.message.error.code('KCHDR6011M');
- return false;
- }
- var formData = addReportForm.serializeObject();
- var taskAccepted = false;
- var onTaskAccepted = function() {
- if(taskAccepted) {
- return;
- }
- taskAccepted = true;
- wok.window.close();
- wok.topic('kimchi/debugReportAdded').publish();
- };
-
- kimchi.createReport(formData, function(result) {
- onTaskAccepted();
- wok.topic('kimchi/debugReportAdded').publish();
- }, function(result) {
- // Error message from Async Task status
- if (result['message']) {
- var errText = result['message'];
- }
- // Error message from standard kimchi exception
- else {
- var errText = result['responseJSON']['reason'];
- }
- result && wok.message.error(errText);
-
- taskAccepted &&
- $('.grid-body-view table tr:first-child',
- '#' + reportGridID).remove();
- submitButton.prop('disabled', false);
- nameTextbox.select();
- }, onTaskAccepted);
-
- event.preventDefault();
- };
-
- addReportForm.on('submit', submitForm);
- submitButton.on('click', submitForm);
-};
diff --git a/plugins/kimchi/ui/js/src/kimchi.report_rename_main.js b/plugins/kimchi/ui/js/src/kimchi.report_rename_main.js
deleted file mode 100644
index 1bdb8d9..0000000
--- a/plugins/kimchi/ui/js/src/kimchi.report_rename_main.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Project Kimchi
- *
- * Copyright IBM, Corp. 2014
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-kimchi.report_rename_main = function() {
- var renameReportForm = $('#form-report-rename');
- var submitButton = $('#button-report-rename');
- var nameTextbox = $('input[name="name"]', renameReportForm);
- var submitForm = function(event) {
- if(submitButton.prop('disabled')) {
- return false;
- }
- var reportName = nameTextbox.val();
-
- // if the user hasn't changed the report's name,
- // nothing should be done.
- if (reportName == kimchi.selectedReport) {
- wok.message.error.code('KCHDR6013M');
- return false;
- }
-
- var validator = RegExp("^[A-Za-z0-9-]*$");
- if (!validator.test(reportName)) {
- wok.message.error.code('KCHDR6011M');
- return false;
- }
- var formData = renameReportForm.serializeObject();
- submitButton.prop('disabled', true);
- nameTextbox.prop('disabled', true);
- kimchi.renameReport(kimchi.selectedReport, formData, function(result) {
- submitButton.prop('disabled', false);
- nameTextbox.prop('disabled', false);
- wok.window.close();
- wok.topic('kimchi/debugReportRenamed').publish({
- result: result
- });
- }, function(result) {
- var errText = result &&
- result['responseJSON'] &&
- result['responseJSON']['reason'];
- wok.message.error(errText);
- submitButton.prop('disabled', false);
- nameTextbox.prop('disabled', false).focus();
- });
-
- event.preventDefault();
- };
-
- renameReportForm.on('submit', submitForm);
- submitButton.on('click', submitForm);
-
- nameTextbox.val(kimchi.selectedReport).select();
-};
diff --git a/plugins/kimchi/ui/js/src/kimchi.repository_add_main.js b/plugins/kimchi/ui/js/src/kimchi.repository_add_main.js
deleted file mode 100644
index 656306b..0000000
--- a/plugins/kimchi/ui/js/src/kimchi.repository_add_main.js
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Project Kimchi
- *
- * Copyright IBM, Corp. 2014
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-kimchi.repository_add_main = function() {
-
- var addForm = $('#form-repository-add');
- var addButton = $('#button-repository-add');
-
- var validateField = function(event) {
- var valid=($(this).val()!=='');
- $(addButton).prop('disabled', !valid);
- return(valid);
- };
-
- var validateForm = function(event) {
- var valid=false;
- addForm.find('input.required').each( function() {
- valid=($(this).val()!=='');
- return(!valid);
- });
- return(valid);
- }
-
- addForm.find('input.required').on('input propertychange', validateField);
-
- var weedObject = function(obj) {
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) {
- if((typeof(obj[key])==="object") && !Array.isArray(obj[key])) {
- weedObject(obj[key]);
- }
- else if(obj[key] == '') {
- delete obj[key];
- }
- }
- }
- }
-
- var addRepository = function(event) {
- var valid = validateForm();
- if(!valid) {
- return false;
- }
-
- var formData = $(addForm).serializeObject();
-
- if (formData && formData.isMirror!=undefined) {
- formData.isMirror=(String(formData.isMirror).toLowerCase() === 'true');
- }
- if(formData.isMirror) {
- if(formData.config==undefined) {
- formData.config=new Object();
- }
- formData.config.mirrorlist=formData.baseurl;
- delete formData.baseurl;
- delete formData.isMirror;
- }
- weedObject(formData);
- if(formData.config && formData.config.comps) {
- formData.config.comps=formData.config.comps.split(/[,\s]/);
- for(var i=0; i>formData.config.comps.length; i++) {
- formData.config.comps[i]=formData.config.comps[i].trim();
- }
- for (var j=formData.config.comps.indexOf(""); j!=-1; j=formData.config.comps.indexOf("")) {
- formData.config.comps.splice(j, 1);
- }
- }
-
- kimchi.createRepository(formData, function() {
- wok.topic('kimchi/repositoryAdded').publish();
- wok.window.close();
- }, function(jqXHR, textStatus, errorThrown) {
- var reason = jqXHR &&
- jqXHR['responseJSON'] &&
- jqXHR['responseJSON']['reason'];
- wok.message.error(reason);
- });
- return false;
- };
-
- $(addForm).on('submit', addRepository);
-};
diff --git a/plugins/kimchi/ui/js/src/kimchi.repository_edit_main.js b/plugins/kimchi/ui/js/src/kimchi.repository_edit_main.js
deleted file mode 100644
index 5bfc51e..0000000
--- a/plugins/kimchi/ui/js/src/kimchi.repository_edit_main.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Project Kimchi
- *
- * Copyright IBM, Corp. 2014
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-kimchi.repository_edit_main = function() {
-
- var editForm = $('#form-repository-edit');
-
- var saveButton = $('#repository-edit-button-save');
-
- if(kimchi.capabilities['repo_mngt_tool']=="yum") {
- editForm.find('input.deb').prop('disabled', true);
- }
- else if(kimchi.capabilities['repo_mngt_tool']=="deb") {
- editForm.find('input.yum').prop('disabled', true);
- }
-
- kimchi.retrieveRepository(kimchi.selectedRepository, function(repository) {
- editForm.fillWithObject(repository);
-
- $('input', editForm).on('input propertychange', function(event) {
- if($(this).val() !== '') {
- $(saveButton).prop('disabled', false);
- }
- });
- });
-
-
- var editRepository = function(event) {
- var formData = $(editForm).serializeObject();
-
- if (formData && formData.config) {
- formData.config.gpgcheck=(String(formData.config.gpgcheck).toLowerCase() === 'true');
- }
-
- if(formData.config && formData.config.comps) {
- formData.config.comps=formData.config.comps.split(/[,\s]/);
- for(var i=0; i>formData.config.comps.length; i++) {
- formData.config.comps[i]=formData.config.comps[i].trim();
- }
- for (var j=formData.config.comps.indexOf(""); j!=-1; j=formData.config.comps.indexOf("")) {
- formData.config.comps.splice(j, 1);
- }
- }
-
- kimchi.updateRepository(kimchi.selectedRepository, formData, function() {
- wok.topic('kimchi/repositoryUpdated').publish();
- wok.window.close();
- }, function(jqXHR, textStatus, errorThrown) {
- var reason = jqXHR &&
- jqXHR['responseJSON'] &&
- jqXHR['responseJSON']['reason'];
- wok.message.error(reason);
- });
-
- return false;
- };
-
- $(editForm).on('submit', editRepository);
- $(saveButton).on('click', editRepository);
-};
--
2.1.0
3
2
11 Sep '15
From: chandrureddy <chandra(a)linux.vnet.ibm.com>
---
plugins/gingerbase/tests/Makefile.am | 49 +++++
plugins/gingerbase/tests/run_tests.sh.in | 55 ++++++
plugins/gingerbase/tests/test_config.py.in | 154 ++++++++++++++++
plugins/gingerbase/tests/test_host.py | 152 ++++++++++++++++
plugins/gingerbase/tests/test_model.py | 280 +++++++++++++++++++++++++++++
plugins/gingerbase/tests/utils.py | 261 +++++++++++++++++++++++++++
plugins/kimchi/tests/test_host.py | 200 ---------------------
7 files changed, 951 insertions(+), 200 deletions(-)
create mode 100644 plugins/gingerbase/tests/Makefile.am
create mode 100644 plugins/gingerbase/tests/run_tests.sh.in
create mode 100644 plugins/gingerbase/tests/test_config.py.in
create mode 100644 plugins/gingerbase/tests/test_host.py
create mode 100644 plugins/gingerbase/tests/test_model.py
create mode 100644 plugins/gingerbase/tests/utils.py
delete mode 100644 plugins/kimchi/tests/test_host.py
diff --git a/plugins/gingerbase/tests/Makefile.am b/plugins/gingerbase/tests/Makefile.am
new file mode 100644
index 0000000..23e18f4
--- /dev/null
+++ b/plugins/gingerbase/tests/Makefile.am
@@ -0,0 +1,49 @@
+#
+# Kimchi
+#
+# Copyright IBM Corp, 2013
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+EXTRA_DIST = \
+ Makefile.am \
+ run_tests.sh.in \
+ test_config.py.in \
+ $(filter-out test_config.py, $(wildcard *.py)) \
+ $(NULL)
+
+noinst_SCRIPTS = run_tests.sh
+
+do_substitution = \
+ sed -e 's,[@]HAVE_PYMOD_UNITTEST[@],$(HAVE_PYMOD_UNITTEST),g' \
+ -e 's,[@]prefix[@],$(prefix),g' \
+ -e 's,[@]datadir[@],$(datadir),g' \
+ -e 's,[@]PYTHON_VERSION[@],$(PYTHON_VERSION),g' \
+ -e 's,[@]wokdir[@],$(pythondir)/wok,g' \
+ -e 's,[@]pkgdatadir[@],$(pkgdatadir),g'
+
+
+run_tests.sh: run_tests.sh.in Makefile
+ $(do_substitution) < $(srcdir)/run_tests.sh.in > run_tests.sh
+ chmod +x run_tests.sh
+
+test_config.py: test_config.py.in Makefile
+ $(do_substitution) < $(srcdir)/test_config.py.in > test_config.py
+
+check-local:
+ ./run_tests.sh
+
+BUILT_SOURCES = test_config.py
+CLEANFILES = run_tests.sh test_config.py
diff --git a/plugins/gingerbase/tests/run_tests.sh.in b/plugins/gingerbase/tests/run_tests.sh.in
new file mode 100644
index 0000000..beef75e
--- /dev/null
+++ b/plugins/gingerbase/tests/run_tests.sh.in
@@ -0,0 +1,55 @@
+#!/bin/bash
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+HAVE_UNITTEST=@HAVE_PYMOD_UNITTEST@
+PYTHON_VER=@PYTHON_VERSION@
+
+if [ "$1" = "-v" ]; then
+ OPTS="-v"
+ shift
+else
+ OPTS=""
+fi
+
+if [ $# -ne 0 ]; then
+ ARGS="$@"
+else
+ ARGS=`find -name "test_*.py" | xargs -I @ basename @ .py`
+fi
+
+if [ "$HAVE_UNITTEST" != "yes" -o "$PYTHON_VER" == "2.6" ]; then
+ CMD="unit2"
+else
+ CMD="python -m unittest"
+fi
+
+LIST=($ARGS)
+MODEL_LIST=()
+MOCK_LIST=()
+for ((i=0;i<${#LIST[@]};i++)); do
+
+ if [[ ${LIST[$i]} == test_model* ]]; then
+ MODEL_LIST+=(${LIST[$i]})
+ else
+ MOCK_LIST+=(${LIST[$i]})
+ fi
+done
+
+PYTHONPATH=../plugins:../src:../ $CMD $OPTS ${MODEL_LIST[@]} ${MOCK_LIST[@]}
diff --git a/plugins/gingerbase/tests/test_config.py.in b/plugins/gingerbase/tests/test_config.py.in
new file mode 100644
index 0000000..4ffe919
--- /dev/null
+++ b/plugins/gingerbase/tests/test_config.py.in
@@ -0,0 +1,154 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2014-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import unittest
+from cherrypy.lib.reprconf import Parser
+from wok.config import Paths
+
+from wok.plugins.gingerbase.config import KimchiPaths
+
+
+
+get_prefix = None
+
+
+def setUpModule():
+ global get_prefix
+ get_prefix = Paths.get_prefix
+
+
+def tearDownModule():
+ Paths.get_prefix = KimchiPaths.get_prefix = get_prefix
+
+
+class ConfigTests(unittest.TestCase):
+ def assertInstalledPath(self, actual, expected):
+ if '@pkgdatadir@' != '/usr/share/gingerbase':
+ usr_local = '/usr/local'
+ if not expected.startswith('/usr'):
+ expected = usr_local + expected
+ self.assertEquals(actual, expected)
+
+ def test_installed_paths(self):
+ Paths.get_prefix = lambda self: '@datadir@/wok'
+ paths = Paths()
+ self.assertInstalledPath(paths.state_dir, '/var/lib/wok')
+ self.assertInstalledPath(paths.log_dir, '/var/log/wok')
+ self.assertInstalledPath(paths.conf_dir, '/etc/wok')
+ self.assertInstalledPath(paths.src_dir, '@wokdir@')
+ self.assertInstalledPath(paths.plugins_dir, '@wokdir@/plugins')
+ self.assertInstalledPath(paths.ui_dir, '@datadir@/wok/ui')
+ self.assertInstalledPath(paths.mo_dir, '@prefix@/share/locale')
+
+ def test_uninstalled_paths(self):
+ Paths.get_prefix = lambda self: '/home/user/wok'
+ paths = Paths()
+ self.assertEquals(paths.state_dir, '/home/user/wok/data')
+ self.assertEquals(paths.log_dir, '/home/user/wok/log')
+ self.assertEquals(paths.conf_dir, '/home/user/wok/src')
+ self.assertEquals(paths.src_dir, '/home/user/wok/src/wok')
+ self.assertEquals(paths.plugins_dir, '/home/user/wok/plugins')
+ self.assertEquals(paths.ui_dir, '/home/user/wok/ui')
+ self.assertEquals(paths.mo_dir, '/home/user/wok/mo')
+
+ def test_installed_plugin_paths(self):
+ KimchiPaths.get_prefix = lambda self: '@datadir@/wok'
+ paths = KimchiPaths()
+ self.assertInstalledPath(paths.conf_dir, '/etc/wok/plugins.d')
+ self.assertInstalledPath(paths.conf_file,
+ '/etc/wok/plugins.d/gingerbase.conf')
+ self.assertInstalledPath(paths.src_dir, '@wokdir@/plugins/ginger-base')
+ self.assertInstalledPath(paths.ui_dir,
+ '@datadir@/wok/plugins/gingerbase/ui')
+ self.assertInstalledPath(paths.mo_dir, '@prefix@/share/locale')
+
+ def test_uninstalled_plugin_paths(self):
+ KimchiPaths.get_prefix = lambda self: '/home/user/wok'
+ paths = KimchiPaths()
+ self.assertEquals(paths.conf_dir, '/home/user/wok/plugins/gingerbase')
+ self.assertEquals(
+ paths.conf_file, '/home/user/wok/plugins/gingerbase/gingerbase.conf')
+ self.assertEquals(paths.src_dir, '/home/user/wok/plugins/gingerbase')
+ self.assertEquals(paths.ui_dir, '/home/user/wok/plugins/gingerbase/ui')
+ self.assertEquals(paths.mo_dir, '/home/user/wok/plugins/gingerbase/mo')
+
+ def test_kimchi_config(self):
+ Paths.get_prefix = KimchiPaths.get_prefix = get_prefix
+ CACHEEXPIRES = 31536000
+ SESSIONSTIMEOUT = 10
+ configObj = {
+ '/': {
+ 'tools.trailing_slash.on': False,
+ 'request.methods_with_bodies': ('POST', 'PUT'),
+ 'tools.nocache.on': True,
+ 'tools.proxy.on': True,
+ 'tools.sessions.on': True,
+ 'tools.sessions.name': 'wok',
+ 'tools.sessions.secure': True,
+ 'tools.sessions.httponly': True,
+ 'tools.sessions.locking': 'explicit',
+ 'tools.sessions.storage_type': 'ram',
+ 'tools.sessions.timeout': SESSIONSTIMEOUT,
+ 'tools.wokauth.on': False
+ },
+ '/css': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.dir': '%s/ui/css' % KimchiPaths().prefix,
+ 'tools.expires.on': True,
+ 'tools.expires.secs': CACHEEXPIRES,
+ 'tools.nocache.on': False
+ },
+ '/js': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.dir': '%s/ui/js' % KimchiPaths().prefix,
+ 'tools.expires.on': True,
+ 'tools.expires.secs': CACHEEXPIRES,
+ 'tools.nocache.on': False
+ },
+ '/libs': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.dir': '%s/ui/libs' % KimchiPaths().prefix,
+ 'tools.expires.on': True,
+ 'tools.expires.secs': CACHEEXPIRES,
+ 'tools.nocache.on': False,
+ },
+ '/images': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.dir': '%s/ui/images' % KimchiPaths().prefix,
+ 'tools.nocache.on': False
+ },
+ '/favicon.ico': {
+ 'tools.staticfile.on': True,
+ 'tools.staticfile.filename':
+ '%s/images/logo.ico' % KimchiPaths().ui_dir
+ },
+ '/robots.txt': {
+ 'tools.staticfile.on': True,
+ 'tools.staticfile.filename': '%s/robots.txt' % KimchiPaths().ui_dir
+ },
+ '/help': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.dir': '%s/ui/pages/help' % KimchiPaths().prefix,
+ 'tools.staticdir.index': 'en_US/index.html',
+ 'tools.nocache.on': True
+ }
+ }
+
+ kimchi_config = Parser().dict_from_file(KimchiPaths().conf_file)
+ self.assertEquals(kimchi_config, configObj)
diff --git a/plugins/gingerbase/tests/test_host.py b/plugins/gingerbase/tests/test_host.py
new file mode 100644
index 0000000..78765c0
--- /dev/null
+++ b/plugins/gingerbase/tests/test_host.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import json
+import os
+import platform
+import psutil
+import tempfile
+import time
+import unittest
+from functools import partial
+
+from wok.plugins.gingerbase.mockmodel import MockModel
+from utils import get_free_port, patch_auth, request, run_server, wait_task
+
+
+test_server = None
+model = None
+host = None
+ssl_port = None
+tmpfile = None
+
+
+def setUpModule():
+ global test_server, model, host, ssl_port, tmpfile
+
+ patch_auth()
+ tmpfile = tempfile.mktemp()
+ model = MockModel(tmpfile)
+ host = '127.0.0.1'
+ port = get_free_port('http')
+ ssl_port = get_free_port('https')
+ cherrypy_port = get_free_port('cherrypy_port')
+ test_server = run_server(host, port, ssl_port, test_mode=True,
+ cherrypy_port=cherrypy_port, model=model)
+
+
+def tearDownModule():
+ test_server.stop()
+ os.unlink(tmpfile)
+
+
+class HostTests(unittest.TestCase):
+ def setUp(self):
+ self.request = partial(request, host, ssl_port)
+
+ def test_hostinfo(self):
+ resp = self.request('/plugins/gingerbase/host').read()
+ info = json.loads(resp)
+ keys = ['os_distro', 'os_version', 'os_codename', 'cpu_model',
+ 'memory', 'cpus']
+ self.assertEquals(sorted(keys), sorted(info.keys()))
+
+ distro, version, codename = platform.linux_distribution()
+ self.assertEquals(distro, info['os_distro'])
+ self.assertEquals(version, info['os_version'])
+ self.assertEquals(unicode(codename, "utf-8"), info['os_codename'])
+ self.assertEquals(psutil.TOTAL_PHYMEM, info['memory'])
+
+ def test_hoststats(self):
+ time.sleep(1)
+ stats_keys = ['cpu_utilization', 'memory', 'disk_read_rate',
+ 'disk_write_rate', 'net_recv_rate', 'net_sent_rate']
+ resp = self.request('/plugins/gingerbase/host/stats').read()
+ stats = json.loads(resp)
+ self.assertEquals(sorted(stats_keys), sorted(stats.keys()))
+
+ cpu_utilization = stats['cpu_utilization']
+ self.assertIsInstance(cpu_utilization, float)
+ self.assertGreaterEqual(cpu_utilization, 0.0)
+ self.assertTrue(cpu_utilization <= 100.0)
+
+ memory_stats = stats['memory']
+ self.assertIn('total', memory_stats)
+ self.assertIn('free', memory_stats)
+ self.assertIn('cached', memory_stats)
+ self.assertIn('buffers', memory_stats)
+ self.assertIn('avail', memory_stats)
+
+ resp = self.request('/plugins/gingerbase/host/stats/history').read()
+ history = json.loads(resp)
+ self.assertEquals(sorted(stats_keys), sorted(history.keys()))
+
+ def test_host_actions(self):
+ def _task_lookup(taskid):
+ return json.loads(self.request('/plugins/gingerbase/tasks/%s' %
+ taskid).read())
+
+ resp = self.request('/plugins/gingerbase/host/shutdown', '{}', 'POST')
+ self.assertEquals(200, resp.status)
+ resp = self.request('/plugins/gingerbase/host/reboot', '{}', 'POST')
+ self.assertEquals(200, resp.status)
+
+ # Test system update
+ resp = self.request('/plugins/gingerbase/host/packagesupdate', None, 'GET')
+ pkgs = json.loads(resp.read())
+ self.assertEquals(3, len(pkgs))
+
+ pkg_keys = ['package_name', 'repository', 'arch', 'version']
+ for p in pkgs:
+ name = p['package_name']
+ resp = self.request('/plugins/gingerbase/host/packagesupdate/' + name,
+ None, 'GET')
+ info = json.loads(resp.read())
+ self.assertEquals(sorted(pkg_keys), sorted(info.keys()))
+
+ resp = self.request('/plugins/gingerbase/host/swupdate', '{}', 'POST')
+ task = json.loads(resp.read())
+ task_params = [u'id', u'message', u'status', u'target_uri']
+ self.assertEquals(sorted(task_params), sorted(task.keys()))
+
+ resp = self.request('/plugins/gingerbase/tasks/' + task[u'id'], None, 'GET')
+ task_info = json.loads(resp.read())
+ self.assertEquals(task_info['status'], 'running')
+ wait_task(_task_lookup, task_info['id'])
+ resp = self.request('/plugins/gingerbase/tasks/' + task[u'id'], None, 'GET')
+ task_info = json.loads(resp.read())
+ self.assertEquals(task_info['status'], 'finished')
+ self.assertIn(u'All packages updated', task_info['message'])
+ pkgs = model.packagesupdate_get_list()
+ self.assertEquals(0, len(pkgs))
+
+ def test_host_partitions(self):
+ resp = self.request('/plugins/gingerbase/host/partitions')
+ self.assertEquals(200, resp.status)
+ partitions = json.loads(resp.read())
+
+ keys = ['name', 'path', 'type', 'fstype', 'size', 'mountpoint',
+ 'available']
+ for item in partitions:
+ resp = self.request('/plugins/gingerbase/host/partitions/%s' %
+ item['name'])
+ info = json.loads(resp.read())
+ self.assertEquals(sorted(info.keys()), sorted(keys))
+
diff --git a/plugins/gingerbase/tests/test_model.py b/plugins/gingerbase/tests/test_model.py
new file mode 100644
index 0000000..d471ce5
--- /dev/null
+++ b/plugins/gingerbase/tests/test_model.py
@@ -0,0 +1,280 @@
+# -*- coding: utf-8 -*-
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import shutil
+import unittest
+
+import wok.objectstore
+from wok.basemodel import Singleton
+from wok.exception import InvalidParameter, NotFoundError, OperationFailed
+from wok.rollbackcontext import RollbackContext
+from wok.plugins.gingerbase.model import model
+
+# import iso_gen
+# import utils
+
+
+invalid_repository_urls = ['www.fedora.org', # missing protocol
+ '://www.fedora.org', # missing protocol
+ 'http://www.fedora', # invalid domain name
+ 'file:///home/foobar'] # invalid path
+
+TMP_DIR = '/var/lib/kimchi/tests/'
+# UBUNTU_ISO = TMP_DIR + 'ubuntu14.04.iso'
+
+
+def setUpModule():
+ if not os.path.exists(TMP_DIR):
+ os.makedirs(TMP_DIR)
+
+ # iso_gen.construct_fake_iso(UBUNTU_ISO, True, '14.04', 'ubuntu')
+
+ # Some FeatureTests functions depend on server to validate their result.
+ # As CapabilitiesModel is a Singleton class it will get the first result
+ # from FeatureTests which may be wrong when using the Model instance
+ # directly - the case of this test_model.py
+ # So clean Singleton instances to make sure to get the right result when
+ # running the following tests.
+ Singleton._instances = {}
+
+
+def tearDownModule():
+ shutil.rmtree(TMP_DIR)
+
+
+class ModelTests(unittest.TestCase):
+ def setUp(self):
+ self.tmp_store = '/tmp/kimchi-store-test'
+
+ def tearDown(self):
+ # FIXME: Tests using 'test:///default' URI should be moved to
+ # test_rest or test_mockmodel to avoid overriding problems
+ # LibvirtConnection._connections['test:///default'] = {}
+
+ os.unlink(self.tmp_store)
+
+ def test_repository_create(self):
+ inst = model.Model(objstore_loc=self.tmp_store)
+
+ yum_repos = [{'repo_id': 'fedora-fake',
+ 'baseurl': 'http://www.fedora.org'},
+ {'repo_id': 'fedora-updates-fake',
+ 'config':
+ {'mirrorlist': 'http://www.fedoraproject.org'}}]
+
+ deb_repos = [{'baseurl': 'http://archive.ubuntu.com/ubuntu/',
+ 'config': {'dist': 'quantal'}},
+ {'baseurl': 'http://archive.ubuntu.com/ubuntu/',
+ 'config': {'dist': 'quantal', 'comps': ['main']}}]
+
+ yum_invalid_repos = []
+ deb_invalid_repos = []
+
+ for url in invalid_repository_urls:
+ wrong_baseurl = {'repo_id': 'wrong-id', 'baseurl': url}
+ wrong_mirrorlist = {'repo_id': 'wrong-id',
+ 'baseurl': 'www.example.com',
+ 'config': {'mirrorlist': url}}
+ wrong_config_item = {
+ 'repo_id': 'wrong-id',
+ 'baseurl': 'www.example.com',
+ 'config': {
+ 'gpgkey': 'file:///tmp/KEY-fedora-updates-fake-19'}}
+
+ yum_invalid_repos.append(wrong_baseurl)
+ yum_invalid_repos.append(wrong_mirrorlist)
+ yum_invalid_repos.append(wrong_config_item)
+
+ wrong_baseurl['config'] = {'dist': 'tasty'}
+ wrong_config = {'baseurl': deb_repos[0]['baseurl'],
+ 'config': {
+ 'unsupported_item': "a_unsupported_item"}}
+ deb_invalid_repos.append(wrong_baseurl)
+ deb_invalid_repos.append(wrong_config)
+
+ repo_type = inst.capabilities_lookup()['repo_mngt_tool']
+ if repo_type == 'yum':
+ test_repos = yum_repos
+ invalid_repos = yum_invalid_repos
+ elif repo_type == 'deb':
+ test_repos = deb_repos
+ invalid_repos = deb_invalid_repos
+ else:
+ # repository management tool was not recognized by Kimchi
+ # skip test case
+ return
+
+ # create repositories with invalid data
+ for repo in invalid_repos:
+ self.assertRaises(InvalidParameter, inst.repositories_create, repo)
+
+ for repo in test_repos:
+ system_host_repos = len(inst.repositories_get_list())
+ repo_id = inst.repositories_create(repo)
+ host_repos = inst.repositories_get_list()
+ self.assertEquals(system_host_repos + 1, len(host_repos))
+
+ repo_info = inst.repository_lookup(repo_id)
+ self.assertEquals(repo_id, repo_info['repo_id'])
+ self.assertEquals(True, repo_info.get('enabled'))
+ self.assertEquals(repo.get('baseurl', ''),
+ repo_info.get('baseurl'))
+
+ original_config = repo.get('config', {})
+ config_info = repo_info.get('config', {})
+
+ if repo_type == 'yum':
+ self.assertEquals(original_config.get('mirrorlist', ''),
+ config_info.get('mirrorlist', ''))
+ self.assertEquals(True, config_info['gpgcheck'])
+ else:
+ self.assertEquals(original_config['dist'], config_info['dist'])
+ self.assertEquals(original_config.get('comps', []),
+ config_info.get('comps', []))
+
+ inst.repository_delete(repo_id)
+ self.assertRaises(NotFoundError, inst.repository_lookup, repo_id)
+
+ self.assertRaises(NotFoundError, inst.repository_lookup, 'google')
+
+ def test_repository_update(self):
+ inst = model.Model(objstore_loc=self.tmp_store)
+
+ yum_repo = {'repo_id': 'fedora-fake',
+ 'baseurl': 'http://www.fedora.org'}
+ yum_new_repo = {'baseurl': 'http://www.fedoraproject.org'}
+
+ deb_repo = {'baseurl': 'http://archive.ubuntu.com/ubuntu/',
+ 'config': {'dist': 'quantal'}}
+ deb_new_repo = {'baseurl': 'http://br.archive.canonical.com/ubuntu/',
+ 'config': {'dist': 'utopic'}}
+
+ yum_invalid_repos = []
+ deb_invalid_repos = []
+
+ for url in invalid_repository_urls:
+ wrong_baseurl = {'baseurl': url}
+ wrong_mirrorlist = {'baseurl': 'www.example.com',
+ 'config': {'mirrorlist': url}}
+
+ yum_invalid_repos.append(wrong_baseurl)
+ yum_invalid_repos.append(wrong_mirrorlist)
+
+ wrong_baseurl['config'] = {'dist': 'tasty'}
+ deb_invalid_repos.append(wrong_baseurl)
+
+ repo_type = inst.capabilities_lookup()['repo_mngt_tool']
+ if repo_type == 'yum':
+ repo = yum_repo
+ new_repo = yum_new_repo
+ invalid_repos = yum_invalid_repos
+ elif repo_type == 'deb':
+ repo = deb_repo
+ new_repo = deb_new_repo
+ invalid_repos = deb_invalid_repos
+ else:
+ # repository management tool was not recognized by Kimchi
+ # skip test case
+ return
+
+ system_host_repos = len(inst.repositories_get_list())
+
+ with RollbackContext() as rollback:
+ repo_id = inst.repositories_create(repo)
+ rollback.prependDefer(inst.repository_delete, repo_id)
+
+ host_repos = inst.repositories_get_list()
+ self.assertEquals(system_host_repos + 1, len(host_repos))
+
+ # update repositories with invalid data
+ for tmp_repo in invalid_repos:
+ self.assertRaises(InvalidParameter, inst.repository_update,
+ repo_id, tmp_repo)
+
+ new_repo_id = inst.repository_update(repo_id, new_repo)
+ repo_info = inst.repository_lookup(new_repo_id)
+
+ self.assertEquals(new_repo_id, repo_info['repo_id'])
+ self.assertEquals(new_repo['baseurl'], repo_info['baseurl'])
+ self.assertEquals(True, repo_info['enabled'])
+ inst.repository_update(new_repo_id, repo)
+
+ def test_repository_disable_enable(self):
+ inst = model.Model(objstore_loc=self.tmp_store)
+
+ yum_repo = {'repo_id': 'fedora-fake',
+ 'baseurl': 'http://www.fedora.org'}
+ deb_repo = {'baseurl': 'http://archive.ubuntu.com/ubuntu/',
+ 'config': {'dist': 'quantal'}}
+
+ repo_type = inst.capabilities_lookup()['repo_mngt_tool']
+ if repo_type == 'yum':
+ repo = yum_repo
+ elif repo_type == 'deb':
+ repo = deb_repo
+ else:
+ # repository management tool was not recognized by Kimchi
+ # skip test case
+ return
+
+ system_host_repos = len(inst.repositories_get_list())
+
+ repo_id = inst.repositories_create(repo)
+
+ host_repos = inst.repositories_get_list()
+ self.assertEquals(system_host_repos + 1, len(host_repos))
+
+ repo_info = inst.repository_lookup(repo_id)
+ self.assertEquals(True, repo_info['enabled'])
+
+ inst.repository_disable(repo_id)
+ repo_info = inst.repository_lookup(repo_id)
+ self.assertEquals(False, repo_info['enabled'])
+
+ inst.repository_enable(repo_id)
+ repo_info = inst.repository_lookup(repo_id)
+ self.assertEquals(True, repo_info['enabled'])
+
+ # remove files creates
+ inst.repository_delete(repo_id)
+
+
+class BaseModelTests(unittest.TestCase):
+ class FoosModel(object):
+ def __init__(self):
+ self.data = {}
+
+ def create(self, params):
+ self.data.update(params)
+
+ def get_list(self):
+ return list(self.data)
+
+ class TestModel(wok.basemodel.BaseModel):
+ def __init__(self):
+ foo = BaseModelTests.FoosModel()
+ super(BaseModelTests.TestModel, self).__init__([foo])
+
+ def test_root_model(self):
+ t = BaseModelTests.TestModel()
+ t.foos_create({'item1': 10})
+ self.assertEquals(t.foos_get_list(), ['item1'])
+
diff --git a/plugins/gingerbase/tests/utils.py b/plugins/gingerbase/tests/utils.py
new file mode 100644
index 0000000..986d91b
--- /dev/null
+++ b/plugins/gingerbase/tests/utils.py
@@ -0,0 +1,261 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import base64
+import cherrypy
+import grp
+import httplib
+import inspect
+import json
+import os
+import socket
+import ssl
+import sys
+import threading
+import time
+import unittest
+from contextlib import closing
+from lxml import etree
+
+import wok.server
+from wok.config import config, PluginPaths
+from wok.auth import User, USER_NAME, USER_GROUPS, USER_ROLES, tabs
+from wok.exception import NotFoundError, OperationFailed
+from wok.utils import wok_log
+
+from wok.plugins.gingerbase import mockmodel
+
+
+_ports = {}
+
+# provide missing unittest decorators and API for python 2.6; these decorators
+# do not actually work, just avoid the syntax failure
+if sys.version_info[:2] == (2, 6):
+ def skipUnless(condition, reason):
+ if not condition:
+ sys.stderr.write('[expected failure] ')
+ raise Exception(reason)
+ return lambda obj: obj
+
+ unittest.skipUnless = skipUnless
+ unittest.expectedFailure = lambda obj: obj
+
+ def assertGreater(self, a, b, msg=None):
+ if not a > b:
+ self.fail('%s not greater than %s' % (repr(a), repr(b)))
+
+ def assertGreaterEqual(self, a, b, msg=None):
+ if not a >= b:
+ self.fail('%s not greater than or equal to %s'
+ % (repr(a), repr(b)))
+
+ def assertIsInstance(self, obj, cls, msg=None):
+ if not isinstance(obj, cls):
+ self.fail('%s is not an instance of %r' % (repr(obj), cls))
+
+ def assertIn(self, a, b, msg=None):
+ if a not in b:
+ self.fail("%s is not in %b" % (repr(a), repr(b)))
+
+ def assertNotIn(self, a, b, msg=None):
+ if a in b:
+ self.fail("%s is in %b" % (repr(a), repr(b)))
+
+ unittest.TestCase.assertGreaterEqual = assertGreaterEqual
+ unittest.TestCase.assertGreater = assertGreater
+ unittest.TestCase.assertIsInstance = assertIsInstance
+ unittest.TestCase.assertIn = assertIn
+ unittest.TestCase.assertNotIn = assertNotIn
+
+
+def get_free_port(name='http'):
+ global _ports
+ if _ports.get(name) is not None:
+ return _ports[name]
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ with closing(sock):
+ try:
+ sock.bind(("0.0.0.0", 0))
+ except:
+ raise Exception("Could not find a free port")
+ _ports[name] = sock.getsockname()[1]
+ return _ports[name]
+
+
+def run_server(host, port, ssl_port, test_mode, cherrypy_port=None,
+ model=None, environment='development'):
+
+ if cherrypy_port is None:
+ cherrypy_port = get_free_port('cherrypy_port')
+
+ if ssl_port is None:
+ ssl_port = get_free_port('https')
+
+ args = type('_', (object,),
+ {'host': host, 'port': port, 'ssl_port': ssl_port,
+ 'cherrypy_port': cherrypy_port, 'max_body_size': '4*1024',
+ 'ssl_cert': '', 'ssl_key': '',
+ 'test': test_mode, 'access_log': '/dev/null',
+ 'error_log': '/dev/null', 'environment': environment,
+ 'log_level': 'debug'})()
+ if model is not None:
+ setattr(args, 'model', model)
+
+ s = wok.server.Server(args)
+ t = threading.Thread(target=s.start)
+ t.setDaemon(True)
+ t.start()
+ cherrypy.engine.wait(cherrypy.engine.states.STARTED)
+ return s
+
+
+def silence_server():
+ """
+ Silence server status messages on stdout
+ """
+ cherrypy.config.update({"environment": "embedded"})
+
+
+def running_as_root():
+ return os.geteuid() == 0
+
+
+def _request(conn, path, data, method, headers):
+ if headers is None:
+ headers = {'Content-Type': 'application/json',
+ 'Accept': 'application/json'}
+ if 'AUTHORIZATION' not in headers.keys():
+ user, pw = mockmodel.fake_user.items()[0]
+ hdr = "Basic " + base64.b64encode("%s:%s" % (user, pw))
+ headers['AUTHORIZATION'] = hdr
+ conn.request(method, path, data, headers)
+ return conn.getresponse()
+
+
+def request(host, port, path, data=None, method='GET', headers=None):
+ # verify if HTTPSConnection has context parameter
+ if "context" in inspect.getargspec(httplib.HTTPSConnection.__init__).args:
+ context = ssl._create_unverified_context()
+ conn = httplib.HTTPSConnection(host, port, context=context)
+ else:
+ conn = httplib.HTTPSConnection(host, port)
+
+ return _request(conn, path, data, method, headers)
+
+
+def get_remote_iso_path():
+ """
+ Get a remote iso with the right arch from the distro files shipped
+ with kimchi.
+ """
+ host_arch = os.uname()[4]
+ remote_path = ''
+ with open(os.path.join(PluginPaths('kimchi').conf_dir, 'distros.d', 'fedora.json')) \
+ as fedora_isos:
+ # Get a list of dicts
+ json_isos_list = json.load(fedora_isos)
+ for iso in json_isos_list:
+ if (iso.get('os_arch')) == host_arch:
+ remote_path = iso.get('path')
+ break
+
+ return remote_path
+
+
+class FakeUser(User):
+ auth_type = "fake"
+ sudo = True
+
+ def __init__(self, username):
+ self.user = {}
+ self.user[USER_NAME] = username
+ self.user[USER_GROUPS] = None
+ self.user[USER_ROLES] = dict.fromkeys(tabs, 'user')
+
+ def get_groups(self):
+ return sorted([group.gr_name for group in grp.getgrall()])[0:3]
+
+ def get_roles(self):
+ if self.sudo:
+ self.user[USER_ROLES] = dict.fromkeys(tabs, 'admin')
+ return self.user[USER_ROLES]
+
+ def get_user(self):
+ return self.user
+
+ @staticmethod
+ def authenticate(username, password, service="passwd"):
+ try:
+ return mockmodel.fake_user[username] == password
+ except KeyError, e:
+ raise OperationFailed("GGBAUTH0001E", {'username': 'username',
+ 'code': e.message})
+
+
+def patch_auth(sudo=True):
+ """
+ Override the authenticate function with a simple test against an
+ internal dict of users and passwords.
+ """
+ config.set("authentication", "method", "fake")
+ FakeUser.sudo = sudo
+
+
+def normalize_xml(xml_str):
+ return etree.tostring(etree.fromstring(xml_str,
+ etree.XMLParser(remove_blank_text=True)))
+
+
+def wait_task(task_lookup, taskid, timeout=10):
+ for i in range(0, timeout):
+ task_info = task_lookup(taskid)
+ if task_info['status'] == "running":
+ wok_log.info("Waiting task %s, message: %s",
+ taskid, task_info['message'])
+ time.sleep(1)
+ else:
+ return
+ wok_log.error("Timeout while process long-run task, "
+ "try to increase timeout value.")
+
+
+# The action functions in model backend raise NotFoundError exception if the
+# element is not found. But in some tests, these functions are called after
+# the element has been deleted if test finishes correctly, then NofFoundError
+# exception is raised and rollback breaks. To avoid it, this wrapper ignores
+# the NotFoundError.
+def rollback_wrapper(func, resource, *args):
+ try:
+ func(resource, *args)
+ except NotFoundError:
+ # VM has been deleted already
+ return
+
+
+# This function is used to test storage volume upload.
+# If we use self.request, we may encode multipart formdata by ourselves
+# requests lib take care of encode part, so use this lib instead
+def fake_auth_header():
+ headers = {'Accept': 'application/json'}
+ user, pw = mockmodel.fake_user.items()[0]
+ hdr = "Basic " + base64.b64encode("%s:%s" % (user, pw))
+ headers['AUTHORIZATION'] = hdr
+ return headers
+
diff --git a/plugins/kimchi/tests/test_host.py b/plugins/kimchi/tests/test_host.py
deleted file mode 100644
index e2aa196..0000000
--- a/plugins/kimchi/tests/test_host.py
+++ /dev/null
@@ -1,200 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2015
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-import json
-import os
-import platform
-import psutil
-import tempfile
-import time
-import unittest
-from functools import partial
-
-from wok.plugins.kimchi.mockmodel import MockModel
-
-from utils import get_free_port, patch_auth, request, run_server, wait_task
-
-
-test_server = None
-model = None
-host = None
-ssl_port = None
-tmpfile = None
-
-
-def setUpModule():
- global test_server, model, host, ssl_port, tmpfile
-
- patch_auth()
- tmpfile = tempfile.mktemp()
- model = MockModel(tmpfile)
- host = '127.0.0.1'
- port = get_free_port('http')
- ssl_port = get_free_port('https')
- cherrypy_port = get_free_port('cherrypy_port')
- test_server = run_server(host, port, ssl_port, test_mode=True,
- cherrypy_port=cherrypy_port, model=model)
-
-
-def tearDownModule():
- test_server.stop()
- os.unlink(tmpfile)
-
-
-class HostTests(unittest.TestCase):
- def setUp(self):
- self.request = partial(request, host, ssl_port)
-
- def test_hostinfo(self):
- resp = self.request('/plugins/kimchi/host').read()
- info = json.loads(resp)
- keys = ['os_distro', 'os_version', 'os_codename', 'cpu_model',
- 'memory', 'cpus']
- self.assertEquals(sorted(keys), sorted(info.keys()))
-
- distro, version, codename = platform.linux_distribution()
- self.assertEquals(distro, info['os_distro'])
- self.assertEquals(version, info['os_version'])
- self.assertEquals(unicode(codename, "utf-8"), info['os_codename'])
- self.assertEquals(psutil.TOTAL_PHYMEM, info['memory'])
-
- def test_hoststats(self):
- time.sleep(1)
- stats_keys = ['cpu_utilization', 'memory', 'disk_read_rate',
- 'disk_write_rate', 'net_recv_rate', 'net_sent_rate']
- resp = self.request('/plugins/kimchi/host/stats').read()
- stats = json.loads(resp)
- self.assertEquals(sorted(stats_keys), sorted(stats.keys()))
-
- cpu_utilization = stats['cpu_utilization']
- self.assertIsInstance(cpu_utilization, float)
- self.assertGreaterEqual(cpu_utilization, 0.0)
- self.assertTrue(cpu_utilization <= 100.0)
-
- memory_stats = stats['memory']
- self.assertIn('total', memory_stats)
- self.assertIn('free', memory_stats)
- self.assertIn('cached', memory_stats)
- self.assertIn('buffers', memory_stats)
- self.assertIn('avail', memory_stats)
-
- resp = self.request('/plugins/kimchi/host/stats/history').read()
- history = json.loads(resp)
- self.assertEquals(sorted(stats_keys), sorted(history.keys()))
-
- def test_host_actions(self):
- def _task_lookup(taskid):
- return json.loads(self.request('/plugins/kimchi/tasks/%s' %
- taskid).read())
-
- resp = self.request('/plugins/kimchi/host/shutdown', '{}', 'POST')
- self.assertEquals(200, resp.status)
- resp = self.request('/plugins/kimchi/host/reboot', '{}', 'POST')
- self.assertEquals(200, resp.status)
-
- # Test system update
- resp = self.request('/plugins/kimchi/host/packagesupdate', None, 'GET')
- pkgs = json.loads(resp.read())
- self.assertEquals(3, len(pkgs))
-
- pkg_keys = ['package_name', 'repository', 'arch', 'version']
- for p in pkgs:
- name = p['package_name']
- resp = self.request('/plugins/kimchi/host/packagesupdate/' + name,
- None, 'GET')
- info = json.loads(resp.read())
- self.assertEquals(sorted(pkg_keys), sorted(info.keys()))
-
- resp = self.request('/plugins/kimchi/host/swupdate', '{}', 'POST')
- task = json.loads(resp.read())
- task_params = [u'id', u'message', u'status', u'target_uri']
- self.assertEquals(sorted(task_params), sorted(task.keys()))
-
- resp = self.request('/plugins/kimchi/tasks/' + task[u'id'], None, 'GET')
- task_info = json.loads(resp.read())
- self.assertEquals(task_info['status'], 'running')
- wait_task(_task_lookup, task_info['id'])
- resp = self.request('/plugins/kimchi/tasks/' + task[u'id'], None, 'GET')
- task_info = json.loads(resp.read())
- self.assertEquals(task_info['status'], 'finished')
- self.assertIn(u'All packages updated', task_info['message'])
- pkgs = model.packagesupdate_get_list()
- self.assertEquals(0, len(pkgs))
-
- def test_host_partitions(self):
- resp = self.request('/plugins/kimchi/host/partitions')
- self.assertEquals(200, resp.status)
- partitions = json.loads(resp.read())
-
- keys = ['name', 'path', 'type', 'fstype', 'size', 'mountpoint',
- 'available']
- for item in partitions:
- resp = self.request('/plugins/kimchi/host/partitions/%s' %
- item['name'])
- info = json.loads(resp.read())
- self.assertEquals(sorted(info.keys()), sorted(keys))
-
- def test_host_devices(self):
- def asset_devices_type(devices, dev_type):
- for dev in devices:
- self.assertEquals(dev['device_type'], dev_type)
-
- resp = self.request('/plugins/kimchi/host/devices?_cap=scsi_host')
- nodedevs = json.loads(resp.read())
- # Mockmodel brings 3 preconfigured scsi fc_host
- self.assertEquals(3, len(nodedevs))
-
- nodedev = json.loads(self.request(
- '/plugins/kimchi/host/devices/scsi_host2').read())
- # Mockmodel generates random wwpn and wwnn
- self.assertEquals('scsi_host2', nodedev['name'])
- self.assertEquals('fc_host', nodedev['adapter']['type'])
- self.assertEquals(16, len(nodedev['adapter']['wwpn']))
- self.assertEquals(16, len(nodedev['adapter']['wwnn']))
-
- devs = json.loads(self.request('/plugins/kimchi/host/devices').read())
- dev_names = [dev['name'] for dev in devs]
- for dev_type in ('pci', 'usb_device', 'scsi'):
- resp = self.request('/plugins/kimchi/host/devices?_cap=%s' %
- dev_type)
- devsByType = json.loads(resp.read())
- names = [dev['name'] for dev in devsByType]
- self.assertTrue(set(names) <= set(dev_names))
- asset_devices_type(devsByType, dev_type)
-
- resp = self.request('/plugins/kimchi/host/devices?_passthrough=true')
- passthru_devs = [dev['name'] for dev in json.loads(resp.read())]
- self.assertTrue(set(passthru_devs) <= set(dev_names))
-
- for dev_type in ('pci', 'usb_device', 'scsi'):
- resp = self.request(
- '/plugins/kimchi/host/devices?_cap=%s&_passthrough=true' %
- dev_type)
- filteredDevs = json.loads(resp.read())
- filteredNames = [dev['name'] for dev in filteredDevs]
- self.assertTrue(set(filteredNames) <= set(dev_names))
- asset_devices_type(filteredDevs, dev_type)
-
- for dev in passthru_devs:
- resp = self.request(
- '/plugins/kimchi/host/devices?_passthrough_affected_by=%s' %
- dev)
- affected_devs = [dev['name'] for dev in json.loads(resp.read())]
- self.assertTrue(set(affected_devs) <= set(dev_names))
--
2.1.0
3
2
11 Sep '15
From: chandrureddy <chandra(a)linux.vnet.ibm.com>
---
plugins/gingerbase/model/Makefile.am | 25 ++
plugins/gingerbase/model/__init__.py | 18 ++
plugins/gingerbase/model/cpuinfo.py | 133 ++++++++++
plugins/gingerbase/model/debugreports.py | 213 ++++++++++++++++
plugins/gingerbase/model/host.py | 410 +++++++++++++++++++++++++++++++
plugins/gingerbase/model/model.py | 49 ++++
plugins/gingerbase/model/tasks.py | 64 +++++
plugins/kimchi/model/debugreports.py | 213 ----------------
8 files changed, 912 insertions(+), 213 deletions(-)
create mode 100644 plugins/gingerbase/model/Makefile.am
create mode 100644 plugins/gingerbase/model/__init__.py
create mode 100644 plugins/gingerbase/model/cpuinfo.py
create mode 100644 plugins/gingerbase/model/debugreports.py
create mode 100644 plugins/gingerbase/model/host.py
create mode 100644 plugins/gingerbase/model/model.py
create mode 100644 plugins/gingerbase/model/tasks.py
delete mode 100644 plugins/kimchi/model/debugreports.py
diff --git a/plugins/gingerbase/model/Makefile.am b/plugins/gingerbase/model/Makefile.am
new file mode 100644
index 0000000..6218b47
--- /dev/null
+++ b/plugins/gingerbase/model/Makefile.am
@@ -0,0 +1,25 @@
+#
+# Kimchi
+#
+# Copyright IBM Corp, 2013
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+model_PYTHON = *.py
+
+modeldir = $(pythondir)/wok/plugins/gingerbase/model
+
+install-data-local:
+ $(MKDIR_P) $(DESTDIR)$(modeldir)
diff --git a/plugins/gingerbase/model/__init__.py b/plugins/gingerbase/model/__init__.py
new file mode 100644
index 0000000..ca7ede4
--- /dev/null
+++ b/plugins/gingerbase/model/__init__.py
@@ -0,0 +1,18 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2014
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/plugins/gingerbase/model/cpuinfo.py b/plugins/gingerbase/model/cpuinfo.py
new file mode 100644
index 0000000..fad8814
--- /dev/null
+++ b/plugins/gingerbase/model/cpuinfo.py
@@ -0,0 +1,133 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2014-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import platform
+# from xml.etree import ElementTree as ET
+
+from wok.exception import InvalidParameter, InvalidOperation
+from wok.utils import run_command, wok_log
+from ..lscpu import LsCpu
+
+
+ARCH = 'power' if platform.machine().startswith('ppc') else 'x86'
+
+
+# def get_topo_capabilities(connect):
+# """
+# This helper function exists solely to be overridden for
+# mockmodel tests. Since other modules use getCapabilies(),
+# it can't be overridden directly.
+# """
+# xml = connect.getCapabilities()
+# capabilities = ET.fromstring(xml)
+# return capabilities.find('host').find('cpu').find('topology')
+
+
+class CPUInfoModel(object):
+ """
+ Get information about a CPU for hyperthreading (on x86)
+ or SMT (on POWER) for logic when creating templates and VMs.
+ """
+
+ def __init__(self, **kargs):
+ self.guest_threads_enabled = False
+ self.sockets = 0
+ self.cores_present = 0
+ self.cores_available = 0
+ self.cores_per_socket = 0
+ self.threads_per_core = 0
+ self.max_threads = 0
+ self.lscpu = LsCpu()
+
+ # self.conn = kargs['conn']
+ # libvirt_topology = None
+ # try:
+ # connect = self.conn.get()
+ # libvirt_topology = get_topo_capabilities(connect)
+ # except Exception as e:
+ # wok_log.info("Unable to get CPU topology capabilities: %s"
+ # % e.message)
+ # return
+ # if libvirt_topology is None:
+ # wok_log.info("cpu_info topology not supported.")
+ # return
+
+ if ARCH == 'power':
+ # IBM PowerPC
+ self.guest_threads_enabled = True
+ out, error, rc = run_command(['ppc64_cpu', '--smt'])
+ if rc or 'on' in out:
+ # SMT has to be disabled for guest to use threads as CPUs.
+ # rc is always zero, whether SMT is off or on.
+ self.guest_threads_enabled = False
+ out, error, rc = run_command(['ppc64_cpu', '--cores-present'])
+ if not rc:
+ self.cores_present = int(out.split()[-1])
+ out, error, rc = run_command(['ppc64_cpu', '--cores-on'])
+ if not rc:
+ self.cores_available = int(out.split()[-1])
+ out, error, rc = run_command(['ppc64_cpu', '--threads-per-core'])
+ if not rc:
+ self.threads_per_core = int(out.split()[-1])
+ self.sockets = self.cores_present/self.threads_per_core
+ if self.sockets == 0:
+ self.sockets = 1
+ self.cores_per_socket = self.cores_present/self.sockets
+ else:
+ # Intel or AMD
+ self.guest_threads_enabled = True
+ # self.sockets = int(libvirt_topology.get('sockets'))
+ # self.cores_per_socket = int(libvirt_topology.get('cores'))
+ # self.cores_present = self.cores_per_socket * self.sockets
+ # self.cores_available = self.cores_present
+ # self.threads_per_core = int(libvirt_topology.get('threads'))
+ self.sockets = int(self.lscpu.get_sockets())
+ self.cores_per_socket = int(self.lscpu.get_cores_per_socket())
+ self.cores_present = self.cores_per_socket * self.sockets
+ self.cores_available = self.cores_present
+ self.threads_per_core = self.lscpu.get_threads_per_core()
+
+ def lookup(self, ident):
+ return {
+ 'guest_threads_enabled': self.guest_threads_enabled,
+ 'sockets': self.sockets,
+ 'cores_per_socket': self.cores_per_socket,
+ 'cores_present': self.cores_present,
+ 'cores_available': self.cores_available,
+ 'threads_per_core': self.threads_per_core,
+ }
+
+ def check_topology(self, vcpus, topology):
+ """
+ param vcpus: should be an integer
+ param iso_path: the path of the guest ISO
+ param topology: {'sockets': x, 'cores': x, 'threads': x}
+ """
+ sockets = topology['sockets']
+ cores = topology['cores']
+ threads = topology['threads']
+
+ if not self.guest_threads_enabled:
+ raise InvalidOperation("GGBCPUINF0003E")
+ if vcpus != sockets * cores * threads:
+ raise InvalidParameter("GGBCPUINF0002E")
+ if vcpus > self.cores_available * self.threads_per_core:
+ raise InvalidParameter("GGBCPUINF0001E")
+ if threads > self.threads_per_core:
+ raise InvalidParameter("GGBCPUINF0002E")
diff --git a/plugins/gingerbase/model/debugreports.py b/plugins/gingerbase/model/debugreports.py
new file mode 100644
index 0000000..fc91a9c
--- /dev/null
+++ b/plugins/gingerbase/model/debugreports.py
@@ -0,0 +1,213 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2014-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import fnmatch
+import glob
+import logging
+import os
+import shutil
+import subprocess
+import time
+
+from wok.exception import InvalidParameter, NotFoundError, OperationFailed
+from wok.exception import WokException
+from wok.utils import add_task, wok_log
+from wok.utils import run_command
+
+from .. import config
+from tasks import TaskModel
+
+
+class DebugReportsModel(object):
+ def __init__(self, **kargs):
+ self.objstore = kargs['objstore']
+ self.task = TaskModel(**kargs)
+
+ def create(self, params):
+ ident = params.get('name').strip()
+ # Generate a name with time and millisec precision, if necessary
+ if ident is None or ident == "":
+ ident = 'report-' + str(int(time.time() * 1000))
+ else:
+ if ident in self.get_list():
+ raise InvalidParameter("GGBDR0008E", {"name": ident})
+ taskid = self._gen_debugreport_file(ident)
+ return self.task.lookup(taskid)
+
+ def get_list(self):
+ path = config.get_debugreports_path()
+ file_pattern = os.path.join(path, '*.*')
+ file_lists = glob.glob(file_pattern)
+ file_lists = [os.path.split(file)[1] for file in file_lists]
+ name_lists = [file.split('.', 1)[0] for file in file_lists]
+
+ return name_lists
+
+ def _gen_debugreport_file(self, name):
+ gen_cmd = self.get_system_report_tool()
+
+ if gen_cmd is not None:
+ return add_task('/plugins/gingerbase/debugreports/%s' % name, gen_cmd,
+ self.objstore, name)
+
+ raise OperationFailed("GGBDR0002E")
+
+ @staticmethod
+ def sosreport_generate(cb, name):
+ def log_error(e):
+ log = logging.getLogger('Model')
+ log.warning('Exception in generating debug file: %s', e)
+
+ try:
+ command = ['sosreport', '--batch', '--name=%s' % name]
+ output, error, retcode = run_command(command)
+
+ if retcode != 0:
+ raise OperationFailed("GGBDR0003E", {'name': name,
+ 'err': retcode})
+
+ # SOSREPORT might create file in /tmp or /var/tmp
+ # FIXME: The right way should be passing the tar.xz file directory
+ # though the parameter '--tmp-dir', but it is failing in Fedora 20
+ patterns = ['/tmp/sosreport-%s-*', '/var/tmp/sosreport-%s-*']
+ reports = []
+ reportFile = None
+ for p in patterns:
+ reports = reports + [f for f in glob.glob(p % name)]
+ for f in reports:
+ if not fnmatch.fnmatch(f, '*.md5'):
+ reportFile = f
+ break
+ # Some error in sosreport happened
+ if reportFile is None:
+ wok_log.error('Debug report file not found. See sosreport '
+ 'output for detail:\n%s', output)
+ fname = (patterns[0] % name).split('/')[-1]
+ raise OperationFailed('GGBDR0004E', {'name': fname})
+
+ md5_report_file = reportFile + '.md5'
+ report_file_extension = '.' + reportFile.split('.', 1)[1]
+ path = config.get_debugreports_path()
+ target = os.path.join(path, name + report_file_extension)
+ # Moving report
+ msg = 'Moving debug report file "%s" to "%s"' % (reportFile,
+ target)
+ wok_log.info(msg)
+ shutil.move(reportFile, target)
+ # Deleting md5
+ msg = 'Deleting report md5 file: "%s"' % (md5_report_file)
+ wok_log.info(msg)
+ with open(md5_report_file) as f:
+ md5 = f.read().strip()
+ wok_log.info('Md5 file content: "%s"', md5)
+ os.remove(md5_report_file)
+ cb('OK', True)
+ return
+
+ except WokException as e:
+ log_error(e)
+ raise
+
+ except OSError as e:
+ log_error(e)
+ raise
+
+ except Exception, e:
+ # No need to call cb to update the task status here.
+ # The task object will catch the exception raised here
+ # and update the task status there
+ log_error(e)
+ raise OperationFailed("GGBDR0005E", {'name': name, 'err': e})
+
+ @staticmethod
+ def get_system_report_tool():
+ # Please add new possible debug report command here
+ # and implement the report generating function
+ # based on the new report command
+ report_tools = ({'cmd': 'sosreport --help',
+ 'fn': DebugReportsModel.sosreport_generate},)
+
+ # check if the command can be found by shell one by one
+ for helper_tool in report_tools:
+ try:
+ retcode = subprocess.call(helper_tool['cmd'], shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ if retcode == 0:
+ return helper_tool['fn']
+ except Exception, e:
+ wok_log.info('Exception running command: %s', e)
+
+ return None
+
+
+class DebugReportModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def lookup(self, name):
+ path = config.get_debugreports_path()
+ file_pattern = os.path.join(path, name)
+ file_pattern = file_pattern + '.*'
+ try:
+ file_target = glob.glob(file_pattern)[0]
+ except IndexError:
+ raise NotFoundError("GGBDR0001E", {'name': name})
+
+ ctime = os.stat(file_target).st_mtime
+ ctime = time.strftime("%Y-%m-%d-%H:%M:%S", time.localtime(ctime))
+ file_target = os.path.split(file_target)[-1]
+ file_target = os.path.join("plugins/gingerbase/data/debugreports",
+ file_target)
+ return {'uri': file_target,
+ 'ctime': ctime}
+
+ def update(self, name, params):
+ path = config.get_debugreports_path()
+ file_pattern = os.path.join(path, name + '.*')
+ try:
+ file_source = glob.glob(file_pattern)[0]
+ except IndexError:
+ raise NotFoundError("GGBDR0001E", {'name': name})
+
+ file_target = file_source.replace(name, params['name'])
+ if os.path.isfile(file_target):
+ raise InvalidParameter('GGBDR0008E', {'name': params['name']})
+
+ shutil.move(file_source, file_target)
+ wok_log.info('%s renamed to %s' % (file_source, file_target))
+ return params['name']
+
+ def delete(self, name):
+ path = config.get_debugreports_path()
+ file_pattern = os.path.join(path, name + '.*')
+ try:
+ file_target = glob.glob(file_pattern)[0]
+ except IndexError:
+ raise NotFoundError("GGBDR0001E", {'name': name})
+
+ os.remove(file_target)
+
+
+class DebugReportContentModel(object):
+ def __init__(self, **kargs):
+ self._debugreport = DebugReportModel()
+
+ def lookup(self, name):
+ return self._debugreport.lookup(name)
diff --git a/plugins/gingerbase/model/host.py b/plugins/gingerbase/model/host.py
new file mode 100644
index 0000000..6522a27
--- /dev/null
+++ b/plugins/gingerbase/model/host.py
@@ -0,0 +1,410 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2014-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import platform
+import psutil
+import time
+from cherrypy.process.plugins import BackgroundTask
+from collections import defaultdict
+
+from wok import netinfo
+from wok.basemodel import Singleton
+from wok.exception import InvalidOperation, InvalidParameter
+from wok.exception import NotFoundError, OperationFailed
+from wok.utils import add_task, wok_log
+
+
+from .. import disks
+from ..repositories import Repositories
+from ..swupdate import SoftwareUpdate
+from debugreports import DebugReportsModel
+from tasks import TaskModel
+from wok.config import config as kconfig
+
+
+HOST_STATS_INTERVAL = 1
+
+
+class HostModel(object):
+ def __init__(self, **kargs):
+ # self.conn = kargs['conn']
+ self.objstore = kargs['objstore']
+ self.task = TaskModel(**kargs)
+ self.host_info = self._get_host_info()
+
+ def _get_ppc_cpu_info(self):
+ res = {}
+ with open('/proc/cpuinfo') as f:
+ for line in f.xreadlines():
+ # Parse CPU, CPU's revision and CPU's clock information
+ for key in ['cpu', 'revision', 'clock']:
+ if key in line:
+ info = line.split(':')[1].strip()
+ if key == 'clock':
+ value = float(info.split('MHz')[0].strip()) / 1000
+ else:
+ value = info.split('(')[0].strip()
+ res[key] = value
+
+ # Power machines show, for each cpu/core, a block with
+ # all cpu information. Here we control the scan of the
+ # necessary information (1st block provides
+ # everything), skipping the function when find all
+ # information.
+ if len(res.keys()) == 3:
+ return "%(cpu)s (%(revision)s) @ %(clock)s GHz\
+ " % res
+
+ return ""
+
+ def _get_host_info(self):
+ res = {}
+ if platform.machine().startswith('ppc'):
+ res['cpu_model'] = self._get_ppc_cpu_info()
+ else:
+ with open('/proc/cpuinfo') as f:
+ for line in f.xreadlines():
+ if "model name" in line:
+ res['cpu_model'] = line.split(':')[1].strip()
+ break
+
+ res['cpus'] = 0
+ res['memory'] = 0L
+
+ # Include IBM PowerKVM name to supported distro names
+ _sup_distros = platform._supported_dists + ('ibm_powerkvm',)
+ # 'fedora' '17' 'Beefy Miracle'
+ distro, version, codename = platform.linux_distribution(
+ supported_dists=_sup_distros)
+ res['os_distro'] = distro
+ res['os_version'] = version
+ res['os_codename'] = unicode(codename, "utf-8")
+
+ return res
+
+ def lookup(self, *name):
+ cpus = psutil.NUM_CPUS
+
+ # psutil is unstable on how to get the number of
+ # cpus, different versions call it differently
+ if hasattr(psutil, 'cpu_count'):
+ cpus = psutil.cpu_count()
+
+ elif hasattr(psutil, '_psplatform'):
+ for method_name in ['_get_num_cpus', 'get_num_cpus']:
+
+ method = getattr(psutil._psplatform, method_name, None)
+ if method is not None:
+ cpus = method()
+ break
+
+ self.host_info['cpus'] = cpus
+ self.host_info['memory'] = psutil.phymem_usage().total
+ return self.host_info
+
+ def swupdate(self, *name):
+ try:
+ swupdate = SoftwareUpdate()
+ except:
+ raise OperationFailed('GGBPKGUPD0004E')
+
+ pkgs = swupdate.getNumOfUpdates()
+ if pkgs == 0:
+ raise OperationFailed('GGBPKGUPD0001E')
+
+ wok_log.debug('Host is going to be updated.')
+ taskid = add_task('/plugins/gingerbase/host/swupdate', swupdate.doUpdate,
+ self.objstore, None)
+ return self.task.lookup(taskid)
+
+ def shutdown(self, args=None):
+ # Check for running vms before shutdown
+ # FIXME : Find alternative way to figure out if any vms running
+ # running_vms = self._get_vms_list_by_state('running')
+ # if len(running_vms) > 0:
+ # raise OperationFailed("GGBHOST0001E")
+
+ wok_log.info('Host is going to shutdown.')
+ os.system('shutdown -h now')
+
+ def reboot(self, args=None):
+ # Find running VMs
+ # FIXME : Find alternative way to figure out if any vms running
+ # running_vms = self._get_vms_list_by_state('running')
+ # if len(running_vms) > 0:
+ # raise OperationFailed("GGBHOST0002E")
+
+ wok_log.info('Host is going to reboot.')
+ os.system('reboot')
+
+ # def _get_vms_list_by_state(self, state):
+ # conn = self.conn.get()
+ # return [dom.name().decode('utf-8')
+ # for dom in conn.listAllDomains(0)
+ # if (DOM_STATE_MAP[dom.info()[0]]) == state]
+
+
+class HostStatsModel(object):
+ __metaclass__ = Singleton
+
+ def __init__(self, **kargs):
+ self.host_stats = defaultdict(list)
+ self.host_stats_thread = BackgroundTask(HOST_STATS_INTERVAL,
+ self._update_host_stats)
+ self.host_stats_thread.start()
+
+ def lookup(self, *name):
+ return {'cpu_utilization': self.host_stats['cpu_utilization'][-1],
+ 'memory': self.host_stats['memory'][-1],
+ 'disk_read_rate': self.host_stats['disk_read_rate'][-1],
+ 'disk_write_rate': self.host_stats['disk_write_rate'][-1],
+ 'net_recv_rate': self.host_stats['net_recv_rate'][-1],
+ 'net_sent_rate': self.host_stats['net_sent_rate'][-1]}
+
+ def _update_host_stats(self):
+ preTimeStamp = self.host_stats['timestamp']
+ timestamp = time.time()
+ # FIXME when we upgrade psutil, we can get uptime by psutil.uptime
+ # we get uptime by float(open("/proc/uptime").readline().split()[0])
+ # and calculate the first io_rate after the OS started.
+ with open("/proc/uptime") as time_f:
+ seconds = (timestamp - preTimeStamp if preTimeStamp else
+ float(time_f.readline().split()[0]))
+
+ self.host_stats['timestamp'] = timestamp
+ self._get_host_disk_io_rate(seconds)
+ self._get_host_network_io_rate(seconds)
+
+ self._get_percentage_host_cpu_usage()
+ self._get_host_memory_stats()
+
+ # store only 60 stats (1 min)
+ for key, value in self.host_stats.iteritems():
+ if isinstance(value, list):
+ if len(value) == 60:
+ self.host_stats[key] = value[10:]
+
+ def _get_percentage_host_cpu_usage(self):
+ # This is cpu usage producer. This producer will calculate the usage
+ # at an interval of HOST_STATS_INTERVAL.
+ # The psutil.cpu_percent works as non blocking.
+ # psutil.cpu_percent maintains a cpu time sample.
+ # It will update the cpu time sample when it is called.
+ # So only this producer can call psutil.cpu_percent in kimchi.
+ self.host_stats['cpu_utilization'].append(psutil.cpu_percent(None))
+
+ def _get_host_memory_stats(self):
+ virt_mem = psutil.virtual_memory()
+ # available:
+ # the actual amount of available memory that can be given
+ # instantly to processes that request more memory in bytes; this
+ # is calculated by summing different memory values depending on
+ # the platform (e.g. free + buffers + cached on Linux)
+ memory_stats = {'total': virt_mem.total,
+ 'free': virt_mem.free,
+ 'cached': virt_mem.cached,
+ 'buffers': virt_mem.buffers,
+ 'avail': virt_mem.available}
+ self.host_stats['memory'].append(memory_stats)
+
+ def _get_host_disk_io_rate(self, seconds):
+ disk_read_bytes = self.host_stats['disk_read_bytes']
+ disk_write_bytes = self.host_stats['disk_write_bytes']
+ prev_read_bytes = disk_read_bytes[-1] if disk_read_bytes else 0
+ prev_write_bytes = disk_write_bytes[-1] if disk_write_bytes else 0
+
+ disk_io = psutil.disk_io_counters(False)
+ read_bytes = disk_io.read_bytes
+ write_bytes = disk_io.write_bytes
+
+ rd_rate = int(float(read_bytes - prev_read_bytes) / seconds + 0.5)
+ wr_rate = int(float(write_bytes - prev_write_bytes) / seconds + 0.5)
+
+ self.host_stats['disk_read_rate'].append(rd_rate)
+ self.host_stats['disk_write_rate'].append(wr_rate)
+ self.host_stats['disk_read_bytes'].append(read_bytes)
+ self.host_stats['disk_write_bytes'].append(write_bytes)
+
+ def _get_host_network_io_rate(self, seconds):
+ net_recv_bytes = self.host_stats['net_recv_bytes']
+ net_sent_bytes = self.host_stats['net_sent_bytes']
+ prev_recv_bytes = net_recv_bytes[-1] if net_recv_bytes else 0
+ prev_sent_bytes = net_sent_bytes[-1] if net_sent_bytes else 0
+
+ net_ios = psutil.network_io_counters(True)
+ recv_bytes = 0
+ sent_bytes = 0
+ for key in set(netinfo.nics() +
+ netinfo.wlans()) & set(net_ios.iterkeys()):
+ recv_bytes = recv_bytes + net_ios[key].bytes_recv
+ sent_bytes = sent_bytes + net_ios[key].bytes_sent
+
+ rx_rate = int(float(recv_bytes - prev_recv_bytes) / seconds + 0.5)
+ tx_rate = int(float(sent_bytes - prev_sent_bytes) / seconds + 0.5)
+
+ self.host_stats['net_recv_rate'].append(rx_rate)
+ self.host_stats['net_sent_rate'].append(tx_rate)
+ self.host_stats['net_recv_bytes'].append(recv_bytes)
+ self.host_stats['net_sent_bytes'].append(sent_bytes)
+
+
+class HostStatsHistoryModel(object):
+ def __init__(self, **kargs):
+ self.history = HostStatsModel(**kargs)
+
+ def lookup(self, *name):
+ return {'cpu_utilization': self.history.host_stats['cpu_utilization'],
+ 'memory': self.history.host_stats['memory'],
+ 'disk_read_rate': self.history.host_stats['disk_read_rate'],
+ 'disk_write_rate': self.history.host_stats['disk_write_rate'],
+ 'net_recv_rate': self.history.host_stats['net_recv_rate'],
+ 'net_sent_rate': self.history.host_stats['net_sent_rate']}
+
+
+class CapabilitiesModel(object):
+ __metaclass__ = Singleton
+
+ def __init__(self, **kargs):
+ pass
+
+
+ def lookup(self, *ident):
+ report_tool = DebugReportsModel.get_system_report_tool()
+ try:
+ SoftwareUpdate()
+ except Exception:
+ update_tool = False
+ else:
+ update_tool = True
+
+ try:
+ repo = Repositories()
+ except Exception:
+ repo_mngt_tool = None
+ else:
+ repo_mngt_tool = repo._pkg_mnger.TYPE
+
+ return {
+ 'system_report_tool': bool(report_tool),
+ 'update_tool': update_tool,
+ 'repo_mngt_tool': repo_mngt_tool,
+ 'federation': kconfig.get("server", "federation")
+ }
+
+
+class PartitionsModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def get_list(self):
+ result = disks.get_partitions_names()
+ return result
+
+
+class PartitionModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def lookup(self, name):
+ return disks.get_partition_details(name)
+
+class PackagesUpdateModel(object):
+ def __init__(self, **kargs):
+ try:
+ self.host_swupdate = SoftwareUpdate()
+ except:
+ self.host_swupdate = None
+
+ def get_list(self):
+ if self.host_swupdate is None:
+ raise OperationFailed('GGBPKGUPD0004E')
+
+ return self.host_swupdate.getUpdates()
+
+
+class PackageUpdateModel(object):
+ def __init__(self, **kargs):
+ pass
+
+ def lookup(self, name):
+ try:
+ swupdate = SoftwareUpdate()
+ except Exception:
+ raise OperationFailed('GGBPKGUPD0004E')
+
+ return swupdate.getUpdate(name)
+
+
+class RepositoriesModel(object):
+ def __init__(self, **kargs):
+ try:
+ self.host_repositories = Repositories()
+ except:
+ self.host_repositories = None
+
+ def get_list(self):
+ if self.host_repositories is None:
+ raise InvalidOperation('GGBREPOS0014E')
+
+ return sorted(self.host_repositories.getRepositories())
+
+ def create(self, params):
+ if self.host_repositories is None:
+ raise InvalidOperation('GGBREPOS0014E')
+
+ return self.host_repositories.addRepository(params)
+
+
+class RepositoryModel(object):
+ def __init__(self, **kargs):
+ try:
+ self._repositories = Repositories()
+ except:
+ self._repositories = None
+
+ def lookup(self, repo_id):
+ if self._repositories is None:
+ raise InvalidOperation('GGBREPOS0014E')
+
+ return self._repositories.getRepository(repo_id)
+
+ def enable(self, repo_id):
+ if self._repositories is None:
+ raise InvalidOperation('GGBREPOS0014E')
+
+ return self._repositories.enableRepository(repo_id)
+
+ def disable(self, repo_id):
+ if self._repositories is None:
+ raise InvalidOperation('GGBREPOS0014E')
+
+ return self._repositories.disableRepository(repo_id)
+
+ def update(self, repo_id, params):
+ if self._repositories is None:
+ raise InvalidOperation('GGBREPOS0014E')
+
+ return self._repositories.updateRepository(repo_id, params)
+
+ def delete(self, repo_id):
+ if self._repositories is None:
+ raise InvalidOperation('GGBREPOS0014E')
+
+ return self._repositories.removeRepository(repo_id)
diff --git a/plugins/gingerbase/model/model.py b/plugins/gingerbase/model/model.py
new file mode 100644
index 0000000..2da70f4
--- /dev/null
+++ b/plugins/gingerbase/model/model.py
@@ -0,0 +1,49 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2014-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import inspect
+import os
+
+from wok.basemodel import BaseModel
+from wok.objectstore import ObjectStore
+from wok.utils import import_module, listPathModules
+
+
+class Model(BaseModel):
+ def __init__(self, objstore_loc=None):
+
+ self.objstore = ObjectStore(objstore_loc)
+ kargs = {'objstore': self.objstore}
+
+ this = os.path.basename(__file__)
+ this_mod = os.path.splitext(this)[0]
+
+ models = []
+ for mod_name in listPathModules(os.path.dirname(__file__)):
+ if mod_name.startswith("_") or mod_name == this_mod:
+ continue
+
+ module = import_module('plugins.gingerbase.model.' + mod_name)
+ members = inspect.getmembers(module, inspect.isclass)
+ for cls_name, instance in members:
+ if inspect.getmodule(instance) == module:
+ if cls_name.endswith('Model'):
+ models.append(instance(**kargs))
+
+ return super(Model, self).__init__(models)
diff --git a/plugins/gingerbase/model/tasks.py b/plugins/gingerbase/model/tasks.py
new file mode 100644
index 0000000..fe23e32
--- /dev/null
+++ b/plugins/gingerbase/model/tasks.py
@@ -0,0 +1,64 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2014
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+import time
+
+from wok.exception import TimeoutExpired
+
+
+class TasksModel(object):
+ def __init__(self, **kargs):
+ self.objstore = kargs['objstore']
+
+ def get_list(self):
+ with self.objstore as session:
+ return session.get_list('task')
+
+
+class TaskModel(object):
+ def __init__(self, **kargs):
+ self.objstore = kargs['objstore']
+
+ def lookup(self, id):
+ with self.objstore as session:
+ return session.get('task', str(id))
+
+ def wait(self, id, timeout=10):
+ """Wait for a task until it stops running (successfully or due to
+ an error). If the Task finishes its execution before <timeout>, this
+ function returns normally; otherwise an exception is raised.
+
+ Parameters:
+ id -- The Task ID.
+ timeout -- The maximum time, in seconds, that this function should wait
+ for the Task. If the Task runs for more than <timeout>,
+ "TimeoutExpired" is raised.
+ """
+ for i in range(0, timeout):
+ with self.objstore as session:
+ task = session.get('task', str(id))
+
+ if task['status'] != 'running':
+ return
+
+ time.sleep(1)
+
+ raise TimeoutExpired('GGBASYNC0001E', {'seconds': timeout,
+ 'task': task['target_uri']})
diff --git a/plugins/kimchi/model/debugreports.py b/plugins/kimchi/model/debugreports.py
deleted file mode 100644
index d20eb12..0000000
--- a/plugins/kimchi/model/debugreports.py
+++ /dev/null
@@ -1,213 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2014-2015
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-import fnmatch
-import glob
-import logging
-import os
-import shutil
-import subprocess
-import time
-
-from wok.exception import InvalidParameter, NotFoundError, OperationFailed
-from wok.exception import WokException
-from wok.utils import add_task, wok_log
-from wok.utils import run_command
-
-from .. import config
-from tasks import TaskModel
-
-
-class DebugReportsModel(object):
- def __init__(self, **kargs):
- self.objstore = kargs['objstore']
- self.task = TaskModel(**kargs)
-
- def create(self, params):
- ident = params.get('name').strip()
- # Generate a name with time and millisec precision, if necessary
- if ident is None or ident == "":
- ident = 'report-' + str(int(time.time() * 1000))
- else:
- if ident in self.get_list():
- raise InvalidParameter("KCHDR0008E", {"name": ident})
- taskid = self._gen_debugreport_file(ident)
- return self.task.lookup(taskid)
-
- def get_list(self):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, '*.*')
- file_lists = glob.glob(file_pattern)
- file_lists = [os.path.split(file)[1] for file in file_lists]
- name_lists = [file.split('.', 1)[0] for file in file_lists]
-
- return name_lists
-
- def _gen_debugreport_file(self, name):
- gen_cmd = self.get_system_report_tool()
-
- if gen_cmd is not None:
- return add_task('/plugins/kimchi/debugreports/%s' % name, gen_cmd,
- self.objstore, name)
-
- raise OperationFailed("KCHDR0002E")
-
- @staticmethod
- def sosreport_generate(cb, name):
- def log_error(e):
- log = logging.getLogger('Model')
- log.warning('Exception in generating debug file: %s', e)
-
- try:
- command = ['sosreport', '--batch', '--name=%s' % name]
- output, error, retcode = run_command(command)
-
- if retcode != 0:
- raise OperationFailed("KCHDR0003E", {'name': name,
- 'err': retcode})
-
- # SOSREPORT might create file in /tmp or /var/tmp
- # FIXME: The right way should be passing the tar.xz file directory
- # though the parameter '--tmp-dir', but it is failing in Fedora 20
- patterns = ['/tmp/sosreport-%s-*', '/var/tmp/sosreport-%s-*']
- reports = []
- reportFile = None
- for p in patterns:
- reports = reports + [f for f in glob.glob(p % name)]
- for f in reports:
- if not fnmatch.fnmatch(f, '*.md5'):
- reportFile = f
- break
- # Some error in sosreport happened
- if reportFile is None:
- wok_log.error('Debug report file not found. See sosreport '
- 'output for detail:\n%s', output)
- fname = (patterns[0] % name).split('/')[-1]
- raise OperationFailed('KCHDR0004E', {'name': fname})
-
- md5_report_file = reportFile + '.md5'
- report_file_extension = '.' + reportFile.split('.', 1)[1]
- path = config.get_debugreports_path()
- target = os.path.join(path, name + report_file_extension)
- # Moving report
- msg = 'Moving debug report file "%s" to "%s"' % (reportFile,
- target)
- wok_log.info(msg)
- shutil.move(reportFile, target)
- # Deleting md5
- msg = 'Deleting report md5 file: "%s"' % (md5_report_file)
- wok_log.info(msg)
- with open(md5_report_file) as f:
- md5 = f.read().strip()
- wok_log.info('Md5 file content: "%s"', md5)
- os.remove(md5_report_file)
- cb('OK', True)
- return
-
- except WokException as e:
- log_error(e)
- raise
-
- except OSError as e:
- log_error(e)
- raise
-
- except Exception, e:
- # No need to call cb to update the task status here.
- # The task object will catch the exception raised here
- # and update the task status there
- log_error(e)
- raise OperationFailed("KCHDR0005E", {'name': name, 'err': e})
-
- @staticmethod
- def get_system_report_tool():
- # Please add new possible debug report command here
- # and implement the report generating function
- # based on the new report command
- report_tools = ({'cmd': 'sosreport --help',
- 'fn': DebugReportsModel.sosreport_generate},)
-
- # check if the command can be found by shell one by one
- for helper_tool in report_tools:
- try:
- retcode = subprocess.call(helper_tool['cmd'], shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- if retcode == 0:
- return helper_tool['fn']
- except Exception, e:
- wok_log.info('Exception running command: %s', e)
-
- return None
-
-
-class DebugReportModel(object):
- def __init__(self, **kargs):
- pass
-
- def lookup(self, name):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, name)
- file_pattern = file_pattern + '.*'
- try:
- file_target = glob.glob(file_pattern)[0]
- except IndexError:
- raise NotFoundError("KCHDR0001E", {'name': name})
-
- ctime = os.stat(file_target).st_mtime
- ctime = time.strftime("%Y-%m-%d-%H:%M:%S", time.localtime(ctime))
- file_target = os.path.split(file_target)[-1]
- file_target = os.path.join("plugins/kimchi/data/debugreports",
- file_target)
- return {'uri': file_target,
- 'ctime': ctime}
-
- def update(self, name, params):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, name + '.*')
- try:
- file_source = glob.glob(file_pattern)[0]
- except IndexError:
- raise NotFoundError("KCHDR0001E", {'name': name})
-
- file_target = file_source.replace(name, params['name'])
- if os.path.isfile(file_target):
- raise InvalidParameter('KCHDR0008E', {'name': params['name']})
-
- shutil.move(file_source, file_target)
- wok_log.info('%s renamed to %s' % (file_source, file_target))
- return params['name']
-
- def delete(self, name):
- path = config.get_debugreports_path()
- file_pattern = os.path.join(path, name + '.*')
- try:
- file_target = glob.glob(file_pattern)[0]
- except IndexError:
- raise NotFoundError("KCHDR0001E", {'name': name})
-
- os.remove(file_target)
-
-
-class DebugReportContentModel(object):
- def __init__(self, **kargs):
- self._debugreport = DebugReportModel()
-
- def lookup(self, name):
- return self._debugreport.lookup(name)
--
2.1.0
3
2
[PATCH 06/17] Ginger Base : control, API, config, and INSTALL files
by chandra@linux.vnet.ibm.com 11 Sep '15
by chandra@linux.vnet.ibm.com 11 Sep '15
11 Sep '15
From: chandrureddy <chandra(a)linux.vnet.ibm.com>
---
plugins/gingerbase/API.json | 175 ++++++++
plugins/gingerbase/INSTALL | 369 ++++++++++++++++
plugins/gingerbase/config.rpath | 672 +++++++++++++++++++++++++++++
plugins/gingerbase/control/Makefile.am | 25 ++
plugins/gingerbase/control/__init__.py | 26 ++
plugins/gingerbase/control/cpuinfo.py | 37 ++
plugins/gingerbase/control/debugreports.py | 61 +++
plugins/gingerbase/control/host.py | 145 +++++++
plugins/gingerbase/control/tasks.py | 37 ++
plugins/kimchi/control/debugreports.py | 61 ---
10 files changed, 1547 insertions(+), 61 deletions(-)
create mode 100644 plugins/gingerbase/API.json
create mode 100644 plugins/gingerbase/INSTALL
create mode 100644 plugins/gingerbase/config.rpath
create mode 100644 plugins/gingerbase/control/Makefile.am
create mode 100644 plugins/gingerbase/control/__init__.py
create mode 100644 plugins/gingerbase/control/cpuinfo.py
create mode 100644 plugins/gingerbase/control/debugreports.py
create mode 100644 plugins/gingerbase/control/host.py
create mode 100644 plugins/gingerbase/control/tasks.py
delete mode 100644 plugins/kimchi/control/debugreports.py
diff --git a/plugins/gingerbase/API.json b/plugins/gingerbase/API.json
new file mode 100644
index 0000000..7b77eac
--- /dev/null
+++ b/plugins/gingerbase/API.json
@@ -0,0 +1,175 @@
+{
+ "$schema": "http://json-schema.org/draft-03/schema#",
+ "title": "Ginger Base API",
+ "description": "Json schema for Gigner Base API",
+ "type": "object",
+ "gingerbasetype": {
+ "cpu_info": {
+ "description": "Configure CPU specifics for a VM.",
+ "type": "object",
+ "properties": {
+ "topology": {
+ "description": "Configure the guest CPU topology.",
+ "type": "object",
+ "properties": {
+ "sockets": {
+ "type": "integer",
+ "required": true,
+ "minimum": 1,
+ "error": "GGBHOST0005E"
+ },
+ "cores": {
+ "type": "integer",
+ "required": true,
+ "minimum": 1,
+ "error": "GGBHOST0005E"
+ },
+ "threads": {
+ "type": "integer",
+ "required": true,
+ "minimum": 1,
+ "error": "GGBHOST0005E"
+ }
+ }
+ }
+ }
+ }
+ },
+ "properties": {
+ "debugreports_create": {
+ "type": "object",
+ "error": "GGBDR0006E",
+ "properties": {
+ "name": {
+ "description": "The name for the debug report file.",
+ "type": "string",
+ "pattern": "^[_A-Za-z0-9-]*$",
+ "error": "GGBDR0007E"
+ }
+ }
+ },
+ "debugreport_update": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "New name of debug report",
+ "type": "string",
+ "pattern": "^[_A-Za-z0-9-]*$",
+ "error": "GGBDR0007E"
+ }
+ },
+ "additionalProperties": false
+ },
+ "repositories_create": {
+ "type": "object",
+ "properties": {
+ "repo_id": {
+ "description": "Repository ID used for YUM repository.",
+ "type": "string",
+ "error": "GGBREPOS0001E"
+ },
+ "baseurl": {
+ "description": "URL to the directory where the repodata directory of a repository is located. Can be an http://, ftp:// or file:// URL.",
+ "type": "string",
+ "error": "GGBREPOS0002E"
+ },
+ "config": {
+ "description": "Dictionary containing repository configuration",
+ "type": "object",
+ "error": "GGBREPOS0003E",
+ "properties": {
+ "dist": {
+ "description": "Distribution to DEB repository",
+ "type": "string",
+ "error": "GGBREPOS0004E"
+ },
+ "comps": {
+ "description": "List of components to DEB repository",
+ "type": "array",
+ "error": "GGBREPOS0005E",
+ "uniqueItems": true,
+ "items": {
+ "description": "Component name",
+ "type": "string",
+ "error": "GGBREPOS0006E"
+ }
+ },
+ "repo_name": {
+ "description": "YUM repository name",
+ "type": "string",
+ "error": "GGBREPOS0023E"
+ },
+ "mirrorlist": {
+ "description": "URL to a file containing a list of baseurls",
+ "type": "string",
+ "error": "GGBREPOS0007E"
+ },
+ "metalink": {
+ "description": "URL to a metalink file for the repomd.xml",
+ "type": "string",
+ "error": "GGBREPOS0029E"
+ }
+ }
+ }
+ },
+ "additionalProperties": false,
+ "error": "KCHAPI0001E"
+ },
+ "repository_update": {
+ "type": "object",
+ "properties": {
+ "baseurl": {
+ "description": "URL to the directory where the repodata directory of a repository is located. Can be an http://, ftp:// or file:// URL.",
+ "type": "string",
+ "error": "GGBREPOS0002E"
+ },
+ "config": {
+ "description": "Dictionary containing repository configuration",
+ "type": "object",
+ "error": "GGBREPOS0003E",
+ "properties": {
+ "dist": {
+ "description": "Distribution to DEB repository",
+ "type": "string",
+ "error": "GGBREPOS0004E"
+ },
+ "comps": {
+ "description": "List of components to DEB repository",
+ "type": "array",
+ "error": "GGBREPOS0005E",
+ "uniqueItems": true,
+ "items": {
+ "description": "Component name",
+ "type": "string",
+ "error": "GGBREPOS0006E"
+ }
+ },
+ "repo_name": {
+ "description": "Human-readable string describing the YUM repository.",
+ "type": "string",
+ "error": "GGBREPOS0008E"
+ },
+ "mirrorlist": {
+ "description": "URL to a file containing a list of baseurls for YUM repository",
+ "type": "string",
+ "error": "GGBREPOS0007E"
+ },
+ "gpgcheck": {
+ "description": "Indicates if a GPG signature check on the packages gotten from repository should be performed.",
+ "type": "boolean",
+ "error": "GGBREPOS0009E"
+ },
+ "gpgkey": {
+ "description": "URL pointing to the ASCII-armored GPG key file for the repository.",
+ "type": "string",
+ "error": "GGBREPOS0010E"
+ }
+ }
+ }
+ },
+ "additionalProperties": false,
+ "error": "GGBAPI0001E"
+
+ }
+ }
+}
diff --git a/plugins/gingerbase/INSTALL b/plugins/gingerbase/INSTALL
new file mode 100644
index 0000000..63bf076
--- /dev/null
+++ b/plugins/gingerbase/INSTALL
@@ -0,0 +1,369 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994-1996, 1999-2002, 2004-2011 Free Software Foundation,
+Inc.
+
+ Copying and distribution of this file, with or without modification,
+are permitted in any medium without royalty provided the copyright
+notice and this notice are preserved. This file is offered as-is,
+without warranty of any kind.
+
+Basic Installation
+==================
+
+ Briefly, the shell commands `./configure; make; make install' should
+configure, build, and install this package. The following
+more-detailed instructions are generic; see the `README' file for
+instructions specific to this package. Some packages provide this
+`INSTALL' file but do not implement all of the features documented
+below. The lack of an optional feature in a given package is not
+necessarily a bug. More recommendations for GNU packages can be found
+in *note Makefile Conventions: (standards)Makefile Conventions.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+ It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring. Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+ The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'. You need `configure.ac' if
+you want to change it or regenerate `configure' using a newer version
+of `autoconf'.
+
+ The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system.
+
+ Running `configure' might take a while. While running, it prints
+ some messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Optionally, type `make check' to run any self-tests that come with
+ the package, generally using the just-built uninstalled binaries.
+
+ 4. Type `make install' to install the programs and any data files and
+ documentation. When installing into a prefix owned by root, it is
+ recommended that the package be configured and built as a regular
+ user, and only the `make install' phase executed with root
+ privileges.
+
+ 5. Optionally, type `make installcheck' to repeat any self-tests, but
+ this time using the binaries in their final installed location.
+ This target does not install anything. Running this target as a
+ regular user, particularly if the prior `make install' required
+ root privileges, verifies that the installation completed
+ correctly.
+
+ 6. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'. To also remove the
+ files that `configure' created (so you can compile the package for
+ a different kind of computer), type `make distclean'. There is
+ also a `make maintainer-clean' target, but that is intended mainly
+ for the package's developers. If you use it, you may have to get
+ all sorts of other programs in order to regenerate files that came
+ with the distribution.
+
+ 7. Often, you can also type `make uninstall' to remove the installed
+ files again. In practice, not all packages have tested that
+ uninstallation works correctly, even though it is required by the
+ GNU Coding Standards.
+
+ 8. Some packages, particularly those that use Automake, provide `make
+ distcheck', which can by used by developers to test that all other
+ targets like `make install' and `make uninstall' work correctly.
+ This target is generally not run by end users.
+
+Compilers and Options
+=====================
+
+ Some systems require unusual options for compilation or linking that
+the `configure' script does not know about. Run `./configure --help'
+for details on some of the pertinent environment variables.
+
+ You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment. Here
+is an example:
+
+ ./configure CC=c99 CFLAGS=-g LIBS=-lposix
+
+ *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+ You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you can use GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'. This
+is known as a "VPATH" build.
+
+ With a non-GNU `make', it is safer to compile the package for one
+architecture at a time in the source code directory. After you have
+installed the package for one architecture, use `make distclean' before
+reconfiguring for another architecture.
+
+ On MacOS X 10.5 and later systems, you can create libraries and
+executables that work on multiple system types--known as "fat" or
+"universal" binaries--by specifying multiple `-arch' options to the
+compiler but only a single `-arch' option to the preprocessor. Like
+this:
+
+ ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+ CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+ CPP="gcc -E" CXXCPP="g++ -E"
+
+ This is not guaranteed to produce working output in all cases, you
+may have to build one architecture at a time and combine the results
+using the `lipo' tool if you have problems.
+
+Installation Names
+==================
+
+ By default, `make install' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc. You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX', where PREFIX must be an
+absolute file name.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+ In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files. Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them. In general, the
+default for these options is expressed in terms of `${prefix}', so that
+specifying just `--prefix' will affect all of the other directory
+specifications that were not explicitly provided.
+
+ The most portable way to affect installation locations is to pass the
+correct locations to `configure'; however, many packages provide one or
+both of the following shortcuts of passing variable assignments to the
+`make install' command line to change installation locations without
+having to reconfigure or recompile.
+
+ The first method involves providing an override variable for each
+affected directory. For example, `make install
+prefix=/alternate/directory' will choose an alternate location for all
+directory configuration variables that were expressed in terms of
+`${prefix}'. Any directories that were specified during `configure',
+but not in terms of `${prefix}', must each be overridden at install
+time for the entire installation to be relocated. The approach of
+makefile variable overrides for each directory variable is required by
+the GNU Coding Standards, and ideally causes no recompilation.
+However, some platforms have known limitations with the semantics of
+shared libraries that end up requiring recompilation when using this
+method, particularly noticeable in packages that use GNU Libtool.
+
+ The second method involves providing the `DESTDIR' variable. For
+example, `make install DESTDIR=/alternate/directory' will prepend
+`/alternate/directory' before all installation names. The approach of
+`DESTDIR' overrides is not required by the GNU Coding Standards, and
+does not work on platforms that have drive letters. On the other hand,
+it does better at avoiding recompilation issues, and works well even
+when some directory options were not specified in terms of `${prefix}'
+at `configure' time.
+
+Optional Features
+=================
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+ Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+ Some packages offer the ability to configure how verbose the
+execution of `make' will be. For these packages, running `./configure
+--enable-silent-rules' sets the default to minimal output, which can be
+overridden with `make V=1'; while running `./configure
+--disable-silent-rules' sets the default to verbose, which can be
+overridden with `make V=0'.
+
+Particular systems
+==================
+
+ On HP-UX, the default C compiler is not ANSI C compatible. If GNU
+CC is not installed, it is recommended to use the following options in
+order to use an ANSI C compiler:
+
+ ./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
+
+and if that doesn't work, install pre-built binaries of GCC for HP-UX.
+
+ HP-UX `make' updates targets which have the same time stamps as
+their prerequisites, which makes it generally unusable when shipped
+generated files such as `configure' are involved. Use GNU `make'
+instead.
+
+ On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
+parse its `<wchar.h>' header file. The option `-nodtk' can be used as
+a workaround. If GNU CC is not installed, it is therefore recommended
+to try
+
+ ./configure CC="cc"
+
+and if that doesn't work, try
+
+ ./configure CC="cc -nodtk"
+
+ On Solaris, don't put `/usr/ucb' early in your `PATH'. This
+directory contains several dysfunctional programs; working variants of
+these programs are available in `/usr/bin'. So, if you need `/usr/ucb'
+in your `PATH', put it _after_ `/usr/bin'.
+
+ On Haiku, software installed for all users goes in `/boot/common',
+not `/usr/local'. It is recommended to use the following options:
+
+ ./configure --prefix=/boot/common
+
+Specifying the System Type
+==========================
+
+ There may be some features `configure' cannot figure out
+automatically, but needs to determine by the type of machine the package
+will run on. Usually, assuming the package is built to be run on the
+_same_ architectures, `configure' can figure that out, but if it prints
+a message saying it cannot guess the machine type, give it the
+`--build=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+ CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+ OS
+ KERNEL-OS
+
+ See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+ If you are _building_ compiler tools for cross-compiling, you should
+use the option `--target=TYPE' to select the type of system they will
+produce code for.
+
+ If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+ If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+ Variables not defined in a site shell script can be set in the
+environment passed to `configure'. However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost. In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'. For example:
+
+ ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for `CONFIG_SHELL' due to
+an Autoconf bug. Until the bug is fixed you can use this workaround:
+
+ CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+`configure' Invocation
+======================
+
+ `configure' recognizes the following options to control how it
+operates.
+
+`--help'
+`-h'
+ Print a summary of all of the options to `configure', and exit.
+
+`--help=short'
+`--help=recursive'
+ Print a summary of the options unique to this package's
+ `configure', and exit. The `short' variant lists options used
+ only in the top level, while the `recursive' variant lists options
+ also present in any nested packages.
+
+`--version'
+`-V'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`--cache-file=FILE'
+ Enable the cache: use and save the results of the tests in FILE,
+ traditionally `config.cache'. FILE defaults to `/dev/null' to
+ disable caching.
+
+`--config-cache'
+`-C'
+ Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made. To
+ suppress all normal output, redirect it to `/dev/null' (any error
+ messages will still be shown).
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`--prefix=DIR'
+ Use DIR as the installation prefix. *note Installation Names::
+ for more details, including other options available for fine-tuning
+ the installation locations.
+
+`--no-create'
+`-n'
+ Run the configure checks, but stop before creating any output
+ files.
+
+`configure' also accepts some other, not widely useful, options. Run
+`configure --help' for more details.
diff --git a/plugins/gingerbase/config.rpath b/plugins/gingerbase/config.rpath
new file mode 100644
index 0000000..17298f2
--- /dev/null
+++ b/plugins/gingerbase/config.rpath
@@ -0,0 +1,672 @@
+#! /bin/sh
+# Output a system dependent set of variables, describing how to set the
+# run time search path of shared libraries in an executable.
+#
+# Copyright 1996-2010 Free Software Foundation, Inc.
+# Taken from GNU libtool, 2001
+# Originally by Gordon Matzigkeit <gord(a)gnu.ai.mit.edu>, 1996
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+#
+# The first argument passed to this file is the canonical host specification,
+# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or
+# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld
+# should be set by the caller.
+#
+# The set of defined variables is at the end of this script.
+
+# Known limitations:
+# - On IRIX 6.5 with CC="cc", the run time search patch must not be longer
+# than 256 bytes, otherwise the compiler driver will dump core. The only
+# known workaround is to choose shorter directory names for the build
+# directory and/or the installation directory.
+
+# All known linkers require a `.a' archive for static linking (except MSVC,
+# which needs '.lib').
+libext=a
+shrext=.so
+
+host="$1"
+host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
+host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
+host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
+
+# Code taken from libtool.m4's _LT_CC_BASENAME.
+
+for cc_temp in $CC""; do
+ case $cc_temp in
+ compile | *[\\/]compile | ccache | *[\\/]ccache ) ;;
+ distcc | *[\\/]distcc | purify | *[\\/]purify ) ;;
+ \-*) ;;
+ *) break;;
+ esac
+done
+cc_basename=`echo "$cc_temp" | sed -e 's%^.*/%%'`
+
+# Code taken from libtool.m4's _LT_COMPILER_PIC.
+
+wl=
+if test "$GCC" = yes; then
+ wl='-Wl,'
+else
+ case "$host_os" in
+ aix*)
+ wl='-Wl,'
+ ;;
+ darwin*)
+ case $cc_basename in
+ xlc*)
+ wl='-Wl,'
+ ;;
+ esac
+ ;;
+ mingw* | cygwin* | pw32* | os2* | cegcc*)
+ ;;
+ hpux9* | hpux10* | hpux11*)
+ wl='-Wl,'
+ ;;
+ irix5* | irix6* | nonstopux*)
+ wl='-Wl,'
+ ;;
+ newsos6)
+ ;;
+ linux* | k*bsd*-gnu)
+ case $cc_basename in
+ ecc*)
+ wl='-Wl,'
+ ;;
+ icc* | ifort*)
+ wl='-Wl,'
+ ;;
+ lf95*)
+ wl='-Wl,'
+ ;;
+ pgcc | pgf77 | pgf90)
+ wl='-Wl,'
+ ;;
+ ccc*)
+ wl='-Wl,'
+ ;;
+ como)
+ wl='-lopt='
+ ;;
+ *)
+ case `$CC -V 2>&1 | sed 5q` in
+ *Sun\ C*)
+ wl='-Wl,'
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+ osf3* | osf4* | osf5*)
+ wl='-Wl,'
+ ;;
+ rdos*)
+ ;;
+ solaris*)
+ wl='-Wl,'
+ ;;
+ sunos4*)
+ wl='-Qoption ld '
+ ;;
+ sysv4 | sysv4.2uw2* | sysv4.3*)
+ wl='-Wl,'
+ ;;
+ sysv4*MP*)
+ ;;
+ sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+ wl='-Wl,'
+ ;;
+ unicos*)
+ wl='-Wl,'
+ ;;
+ uts4*)
+ ;;
+ esac
+fi
+
+# Code taken from libtool.m4's _LT_LINKER_SHLIBS.
+
+hardcode_libdir_flag_spec=
+hardcode_libdir_separator=
+hardcode_direct=no
+hardcode_minus_L=no
+
+case "$host_os" in
+ cygwin* | mingw* | pw32* | cegcc*)
+ # FIXME: the MSVC++ port hasn't been tested in a loooong time
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++.
+ if test "$GCC" != yes; then
+ with_gnu_ld=no
+ fi
+ ;;
+ interix*)
+ # we just hope/assume this is gcc and not c89 (= MSVC++)
+ with_gnu_ld=yes
+ ;;
+ openbsd*)
+ with_gnu_ld=no
+ ;;
+esac
+
+ld_shlibs=yes
+if test "$with_gnu_ld" = yes; then
+ # Set some defaults for GNU ld with shared library support. These
+ # are reset later if shared libraries are not supported. Putting them
+ # here allows them to be overridden if necessary.
+ # Unlike libtool, we use -rpath here, not --rpath, since the documented
+ # option of GNU ld is called -rpath, not --rpath.
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ case "$host_os" in
+ aix[3-9]*)
+ # On AIX/PPC, the GNU linker is very broken
+ if test "$host_cpu" != ia64; then
+ ld_shlibs=no
+ fi
+ ;;
+ amigaos*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ # Samuel A. Falvo II <kc5tja(a)dolphin.openprojects.net> reports
+ # that the semantics of dynamic libraries on AmigaOS, at least up
+ # to version 4, is to share data among multiple programs linked
+ # with the same dynamic library. Since this doesn't match the
+ # behavior of shared libraries on other platforms, we cannot use
+ # them.
+ ld_shlibs=no
+ ;;
+ beos*)
+ if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then
+ :
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ cygwin* | mingw* | pw32* | cegcc*)
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ hardcode_libdir_flag_spec='-L$libdir'
+ if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then
+ :
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ interix[3-9]*)
+ hardcode_direct=no
+ hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+ ;;
+ gnu* | linux* | k*bsd*-gnu)
+ if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then
+ :
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ netbsd*)
+ ;;
+ solaris*)
+ if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then
+ ld_shlibs=no
+ elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then
+ :
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
+ case `$LD -v 2>&1` in
+ *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*)
+ ld_shlibs=no
+ ;;
+ *)
+ if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then
+ hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ esac
+ ;;
+ sunos4*)
+ hardcode_direct=yes
+ ;;
+ *)
+ if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then
+ :
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ esac
+ if test "$ld_shlibs" = no; then
+ hardcode_libdir_flag_spec=
+ fi
+else
+ case "$host_os" in
+ aix3*)
+ # Note: this linker hardcodes the directories in LIBPATH if there
+ # are no directories specified by -L.
+ hardcode_minus_L=yes
+ if test "$GCC" = yes; then
+ # Neither direct hardcoding nor static linking is supported with a
+ # broken collect2.
+ hardcode_direct=unsupported
+ fi
+ ;;
+ aix[4-9]*)
+ if test "$host_cpu" = ia64; then
+ # On IA64, the linker does run time linking by default, so we don't
+ # have to do anything special.
+ aix_use_runtimelinking=no
+ else
+ aix_use_runtimelinking=no
+ # Test if we are trying to use run time linking or normal
+ # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+ # need to do runtime linking.
+ case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*)
+ for ld_flag in $LDFLAGS; do
+ if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
+ aix_use_runtimelinking=yes
+ break
+ fi
+ done
+ ;;
+ esac
+ fi
+ hardcode_direct=yes
+ hardcode_libdir_separator=':'
+ if test "$GCC" = yes; then
+ case $host_os in aix4.[012]|aix4.[012].*)
+ collect2name=`${CC} -print-prog-name=collect2`
+ if test -f "$collect2name" && \
+ strings "$collect2name" | grep resolve_lib_name >/dev/null
+ then
+ # We have reworked collect2
+ :
+ else
+ # We have old collect2
+ hardcode_direct=unsupported
+ hardcode_minus_L=yes
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_libdir_separator=
+ fi
+ ;;
+ esac
+ fi
+ # Begin _LT_AC_SYS_LIBPATH_AIX.
+ echo 'int main () { return 0; }' > conftest.c
+ ${CC} ${LDFLAGS} conftest.c -o conftest
+ aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; }
+}'`
+ if test -z "$aix_libpath"; then
+ aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; }
+}'`
+ fi
+ if test -z "$aix_libpath"; then
+ aix_libpath="/usr/lib:/lib"
+ fi
+ rm -f conftest.c conftest
+ # End _LT_AC_SYS_LIBPATH_AIX.
+ if test "$aix_use_runtimelinking" = yes; then
+ hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath"
+ else
+ if test "$host_cpu" = ia64; then
+ hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib'
+ else
+ hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath"
+ fi
+ fi
+ ;;
+ amigaos*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ # see comment about different semantics on the GNU ld section
+ ld_shlibs=no
+ ;;
+ bsdi[45]*)
+ ;;
+ cygwin* | mingw* | pw32* | cegcc*)
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++.
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ hardcode_libdir_flag_spec=' '
+ libext=lib
+ ;;
+ darwin* | rhapsody*)
+ hardcode_direct=no
+ if test "$GCC" = yes ; then
+ :
+ else
+ case $cc_basename in
+ xlc*)
+ ;;
+ *)
+ ld_shlibs=no
+ ;;
+ esac
+ fi
+ ;;
+ dgux*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ ;;
+ freebsd1*)
+ ld_shlibs=no
+ ;;
+ freebsd2.2*)
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ ;;
+ freebsd2*)
+ hardcode_direct=yes
+ hardcode_minus_L=yes
+ ;;
+ freebsd* | dragonfly*)
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ ;;
+ hpux9*)
+ hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+ hardcode_libdir_separator=:
+ hardcode_direct=yes
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ hardcode_minus_L=yes
+ ;;
+ hpux10*)
+ if test "$with_gnu_ld" = no; then
+ hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+ hardcode_libdir_separator=:
+ hardcode_direct=yes
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ hardcode_minus_L=yes
+ fi
+ ;;
+ hpux11*)
+ if test "$with_gnu_ld" = no; then
+ hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+ hardcode_libdir_separator=:
+ case $host_cpu in
+ hppa*64*|ia64*)
+ hardcode_direct=no
+ ;;
+ *)
+ hardcode_direct=yes
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ hardcode_minus_L=yes
+ ;;
+ esac
+ fi
+ ;;
+ irix5* | irix6* | nonstopux*)
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ ;;
+ netbsd*)
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ ;;
+ newsos6)
+ hardcode_direct=yes
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ ;;
+ openbsd*)
+ if test -f /usr/libexec/ld.so; then
+ hardcode_direct=yes
+ if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+ else
+ case "$host_os" in
+ openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*)
+ hardcode_libdir_flag_spec='-R$libdir'
+ ;;
+ *)
+ hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+ ;;
+ esac
+ fi
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ os2*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ ;;
+ osf3*)
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ ;;
+ osf4* | osf5*)
+ if test "$GCC" = yes; then
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ else
+ # Both cc and cxx compiler support -rpath directly
+ hardcode_libdir_flag_spec='-rpath $libdir'
+ fi
+ hardcode_libdir_separator=:
+ ;;
+ solaris*)
+ hardcode_libdir_flag_spec='-R$libdir'
+ ;;
+ sunos4*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_direct=yes
+ hardcode_minus_L=yes
+ ;;
+ sysv4)
+ case $host_vendor in
+ sni)
+ hardcode_direct=yes # is this really true???
+ ;;
+ siemens)
+ hardcode_direct=no
+ ;;
+ motorola)
+ hardcode_direct=no #Motorola manual says yes, but my tests say they lie
+ ;;
+ esac
+ ;;
+ sysv4.3*)
+ ;;
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ ld_shlibs=yes
+ fi
+ ;;
+ sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*)
+ ;;
+ sysv5* | sco3.2v5* | sco5v6*)
+ hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`'
+ hardcode_libdir_separator=':'
+ ;;
+ uts4*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ ;;
+ *)
+ ld_shlibs=no
+ ;;
+ esac
+fi
+
+# Check dynamic linker characteristics
+# Code taken from libtool.m4's _LT_SYS_DYNAMIC_LINKER.
+# Unlike libtool.m4, here we don't care about _all_ names of the library, but
+# only about the one the linker finds when passed -lNAME. This is the last
+# element of library_names_spec in libtool.m4, or possibly two of them if the
+# linker has special search rules.
+library_names_spec= # the last element of library_names_spec in libtool.m4
+libname_spec='lib$name'
+case "$host_os" in
+ aix3*)
+ library_names_spec='$libname.a'
+ ;;
+ aix[4-9]*)
+ library_names_spec='$libname$shrext'
+ ;;
+ amigaos*)
+ library_names_spec='$libname.a'
+ ;;
+ beos*)
+ library_names_spec='$libname$shrext'
+ ;;
+ bsdi[45]*)
+ library_names_spec='$libname$shrext'
+ ;;
+ cygwin* | mingw* | pw32* | cegcc*)
+ shrext=.dll
+ library_names_spec='$libname.dll.a $libname.lib'
+ ;;
+ darwin* | rhapsody*)
+ shrext=.dylib
+ library_names_spec='$libname$shrext'
+ ;;
+ dgux*)
+ library_names_spec='$libname$shrext'
+ ;;
+ freebsd1*)
+ ;;
+ freebsd* | dragonfly*)
+ case "$host_os" in
+ freebsd[123]*)
+ library_names_spec='$libname$shrext$versuffix' ;;
+ *)
+ library_names_spec='$libname$shrext' ;;
+ esac
+ ;;
+ gnu*)
+ library_names_spec='$libname$shrext'
+ ;;
+ hpux9* | hpux10* | hpux11*)
+ case $host_cpu in
+ ia64*)
+ shrext=.so
+ ;;
+ hppa*64*)
+ shrext=.sl
+ ;;
+ *)
+ shrext=.sl
+ ;;
+ esac
+ library_names_spec='$libname$shrext'
+ ;;
+ interix[3-9]*)
+ library_names_spec='$libname$shrext'
+ ;;
+ irix5* | irix6* | nonstopux*)
+ library_names_spec='$libname$shrext'
+ case "$host_os" in
+ irix5* | nonstopux*)
+ libsuff= shlibsuff=
+ ;;
+ *)
+ case $LD in
+ *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= ;;
+ *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 ;;
+ *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 ;;
+ *) libsuff= shlibsuff= ;;
+ esac
+ ;;
+ esac
+ ;;
+ linux*oldld* | linux*aout* | linux*coff*)
+ ;;
+ linux* | k*bsd*-gnu)
+ library_names_spec='$libname$shrext'
+ ;;
+ knetbsd*-gnu)
+ library_names_spec='$libname$shrext'
+ ;;
+ netbsd*)
+ library_names_spec='$libname$shrext'
+ ;;
+ newsos6)
+ library_names_spec='$libname$shrext'
+ ;;
+ nto-qnx*)
+ library_names_spec='$libname$shrext'
+ ;;
+ openbsd*)
+ library_names_spec='$libname$shrext$versuffix'
+ ;;
+ os2*)
+ libname_spec='$name'
+ shrext=.dll
+ library_names_spec='$libname.a'
+ ;;
+ osf3* | osf4* | osf5*)
+ library_names_spec='$libname$shrext'
+ ;;
+ rdos*)
+ ;;
+ solaris*)
+ library_names_spec='$libname$shrext'
+ ;;
+ sunos4*)
+ library_names_spec='$libname$shrext$versuffix'
+ ;;
+ sysv4 | sysv4.3*)
+ library_names_spec='$libname$shrext'
+ ;;
+ sysv4*MP*)
+ library_names_spec='$libname$shrext'
+ ;;
+ sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+ library_names_spec='$libname$shrext'
+ ;;
+ uts4*)
+ library_names_spec='$libname$shrext'
+ ;;
+esac
+
+sed_quote_subst='s/\(["`$\\]\)/\\\1/g'
+escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"`
+shlibext=`echo "$shrext" | sed -e 's,^\.,,'`
+escaped_libname_spec=`echo "X$libname_spec" | sed -e 's/^X//' -e "$sed_quote_subst"`
+escaped_library_names_spec=`echo "X$library_names_spec" | sed -e 's/^X//' -e "$sed_quote_subst"`
+escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"`
+
+LC_ALL=C sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' <<EOF
+
+# How to pass a linker flag through the compiler.
+wl="$escaped_wl"
+
+# Static library suffix (normally "a").
+libext="$libext"
+
+# Shared library suffix (normally "so").
+shlibext="$shlibext"
+
+# Format of library name prefix.
+libname_spec="$escaped_libname_spec"
+
+# Library names that the linker finds when passed -lNAME.
+library_names_spec="$escaped_library_names_spec"
+
+# Flag to hardcode \$libdir into a binary during linking.
+# This must work even if \$libdir does not exist.
+hardcode_libdir_flag_spec="$escaped_hardcode_libdir_flag_spec"
+
+# Whether we need a single -rpath flag with a separated argument.
+hardcode_libdir_separator="$hardcode_libdir_separator"
+
+# Set to yes if using DIR/libNAME.so during linking hardcodes DIR into the
+# resulting binary.
+hardcode_direct="$hardcode_direct"
+
+# Set to yes if using the -LDIR flag during linking hardcodes DIR into the
+# resulting binary.
+hardcode_minus_L="$hardcode_minus_L"
+
+EOF
diff --git a/plugins/gingerbase/control/Makefile.am b/plugins/gingerbase/control/Makefile.am
new file mode 100644
index 0000000..049570f
--- /dev/null
+++ b/plugins/gingerbase/control/Makefile.am
@@ -0,0 +1,25 @@
+#
+# Kimchi
+#
+# Copyright IBM Corp, 2013
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+control_PYTHON = *.py
+
+controldir = $(pythondir)/wok/plugins/gingerbase/control
+
+install-data-local:
+ $(MKDIR_P) $(DESTDIR)$(controldir)
diff --git a/plugins/gingerbase/control/__init__.py b/plugins/gingerbase/control/__init__.py
new file mode 100644
index 0000000..4ad9459
--- /dev/null
+++ b/plugins/gingerbase/control/__init__.py
@@ -0,0 +1,26 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013-2014
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+
+
+from wok.control.utils import load_url_sub_node
+
+
+sub_nodes = load_url_sub_node(os.path.dirname(__file__), __name__)
diff --git a/plugins/gingerbase/control/cpuinfo.py b/plugins/gingerbase/control/cpuinfo.py
new file mode 100644
index 0000000..31f316c
--- /dev/null
+++ b/plugins/gingerbase/control/cpuinfo.py
@@ -0,0 +1,37 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2014
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+from wok.control.base import Resource
+
+
+class CPUInfo(Resource):
+ def __init__(self, model):
+ super(CPUInfo, self).__init__(model)
+ self.admin_methods = ['GET']
+ self.role_key = 'host'
+ self.uri_fmt = "/host/cpuinfo"
+
+ @property
+ def data(self):
+ return {'threading_enabled': self.info['guest_threads_enabled'],
+ 'sockets': self.info['sockets'],
+ 'cores': self.info['cores_available'],
+ 'threads_per_core': self.info['threads_per_core']
+ }
diff --git a/plugins/gingerbase/control/debugreports.py b/plugins/gingerbase/control/debugreports.py
new file mode 100644
index 0000000..b5a3072
--- /dev/null
+++ b/plugins/gingerbase/control/debugreports.py
@@ -0,0 +1,61 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+from wok.control.base import AsyncCollection, Resource
+from wok.control.utils import internal_redirect
+from wok.control.utils import UrlSubNode
+
+
+@UrlSubNode('debugreports', True)
+class DebugReports(AsyncCollection):
+ def __init__(self, model):
+ super(DebugReports, self).__init__(model)
+ self.resource = DebugReport
+ self.role_key = 'host'
+ self.admin_methods = ['GET', 'POST']
+
+ def _get_resources(self, filter_params):
+ res_list = super(DebugReports, self)._get_resources(filter_params)
+ return sorted(res_list, key=lambda x: x.data['time'], reverse=True)
+
+
+class DebugReport(Resource):
+ def __init__(self, model, ident):
+ super(DebugReport, self).__init__(model, ident)
+ self.role_key = 'host'
+ self.admin_methods = ['GET', 'PUT', 'POST']
+ self.uri_fmt = '/debugreports/%s'
+ self.content = DebugReportContent(model, ident)
+
+ @property
+ def data(self):
+ return {'name': self.ident,
+ 'uri': self.info['uri'],
+ 'time': self.info['ctime']}
+
+
+class DebugReportContent(Resource):
+ def __init__(self, model, ident):
+ super(DebugReportContent, self).__init__(model, ident)
+ self.role_key = 'host'
+ self.admin_methods = ['GET']
+
+ def get(self):
+ self.lookup()
+ raise internal_redirect(self.info['uri'])
diff --git a/plugins/gingerbase/control/host.py b/plugins/gingerbase/control/host.py
new file mode 100644
index 0000000..6e6bd68
--- /dev/null
+++ b/plugins/gingerbase/control/host.py
@@ -0,0 +1,145 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+from wok.control.base import Collection, Resource, SimpleCollection
+from wok.control.utils import UrlSubNode
+from wok.exception import NotFoundError
+
+from cpuinfo import CPUInfo
+
+
+@UrlSubNode('host', True)
+class Host(Resource):
+ def __init__(self, model, id=None):
+ super(Host, self).__init__(model, id)
+ self.role_key = 'host'
+ self.admin_methods = ['GET', 'POST']
+ self.uri_fmt = '/host/%s'
+ self.reboot = self.generate_action_handler('reboot')
+ self.shutdown = self.generate_action_handler('shutdown')
+ self.stats = HostStats(self.model)
+ self.partitions = Partitions(self.model)
+ # self.devices = Devices(self.model)
+ self.packagesupdate = PackagesUpdate(self.model)
+ self.repositories = Repositories(self.model)
+ self.swupdate = self.generate_action_handler_task('swupdate')
+ self.cpuinfo = CPUInfo(self.model)
+ self.capabilities = Capabilities(self.model)
+
+ @property
+ def data(self):
+ return self.info
+
+
+class HostStats(Resource):
+ def __init__(self, model, id=None):
+ super(HostStats, self).__init__(model, id)
+ self.role_key = 'host'
+ self.admin_methods = ['GET']
+ self.history = HostStatsHistory(self.model)
+
+ @property
+ def data(self):
+ return self.info
+
+
+class HostStatsHistory(Resource):
+ @property
+ def data(self):
+ return self.info
+
+
+class Capabilities(Resource):
+ def __init__(self, model, id=None):
+ super(Capabilities, self).__init__(model, id)
+
+ @property
+ def data(self):
+ return self.info
+
+
+class Partitions(Collection):
+ def __init__(self, model):
+ super(Partitions, self).__init__(model)
+ self.role_key = 'storage'
+ self.admin_methods = ['GET']
+ self.resource = Partition
+
+ # Defining get_resources in order to return list of partitions in UI
+ # sorted by their path
+ def _get_resources(self, flag_filter):
+ res_list = super(Partitions, self)._get_resources(flag_filter)
+ res_list = filter(lambda x: x.info['available'], res_list)
+ res_list.sort(key=lambda x: x.info['path'])
+ return res_list
+
+
+class Partition(Resource):
+ def __init__(self, model, id):
+ self.role_key = 'storage'
+ self.admin_methods = ['GET']
+ super(Partition, self).__init__(model, id)
+
+ @property
+ def data(self):
+ if not self.info['available']:
+ raise NotFoundError("GGBPART0001E", {'name': self.info['name']})
+
+ return self.info
+
+
+class PackagesUpdate(Collection):
+ def __init__(self, model):
+ super(PackagesUpdate, self).__init__(model)
+ self.role_key = 'host'
+ self.admin_methods = ['GET']
+ self.resource = PackageUpdate
+
+
+class PackageUpdate(Resource):
+ def __init__(self, model, id=None):
+ super(PackageUpdate, self).__init__(model, id)
+ self.role_key = 'host'
+ self.admin_methods = ['GET']
+
+ @property
+ def data(self):
+ return self.info
+
+
+class Repositories(Collection):
+ def __init__(self, model):
+ super(Repositories, self).__init__(model)
+ self.role_key = 'host'
+ self.admin_methods = ['GET', 'POST']
+ self.resource = Repository
+
+
+class Repository(Resource):
+ def __init__(self, model, id):
+ super(Repository, self).__init__(model, id)
+ self.role_key = 'host'
+ self.admin_methods = ['GET', 'PUT', 'POST', 'DELETE']
+ self.uri_fmt = "/host/repositories/%s"
+ self.enable = self.generate_action_handler('enable')
+ self.disable = self.generate_action_handler('disable')
+
+ @property
+ def data(self):
+ return self.info
diff --git a/plugins/gingerbase/control/tasks.py b/plugins/gingerbase/control/tasks.py
new file mode 100644
index 0000000..b25d892
--- /dev/null
+++ b/plugins/gingerbase/control/tasks.py
@@ -0,0 +1,37 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013-2014
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+from wok.control.base import Collection, Resource
+from wok.control.utils import UrlSubNode
+
+
+@UrlSubNode("tasks", True)
+class Tasks(Collection):
+ def __init__(self, model):
+ super(Tasks, self).__init__(model)
+ self.resource = Task
+
+
+class Task(Resource):
+ def __init__(self, model, id):
+ super(Task, self).__init__(model, id)
+
+ @property
+ def data(self):
+ return self.info
diff --git a/plugins/kimchi/control/debugreports.py b/plugins/kimchi/control/debugreports.py
deleted file mode 100644
index b5a3072..0000000
--- a/plugins/kimchi/control/debugreports.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013-2015
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-from wok.control.base import AsyncCollection, Resource
-from wok.control.utils import internal_redirect
-from wok.control.utils import UrlSubNode
-
-
-@UrlSubNode('debugreports', True)
-class DebugReports(AsyncCollection):
- def __init__(self, model):
- super(DebugReports, self).__init__(model)
- self.resource = DebugReport
- self.role_key = 'host'
- self.admin_methods = ['GET', 'POST']
-
- def _get_resources(self, filter_params):
- res_list = super(DebugReports, self)._get_resources(filter_params)
- return sorted(res_list, key=lambda x: x.data['time'], reverse=True)
-
-
-class DebugReport(Resource):
- def __init__(self, model, ident):
- super(DebugReport, self).__init__(model, ident)
- self.role_key = 'host'
- self.admin_methods = ['GET', 'PUT', 'POST']
- self.uri_fmt = '/debugreports/%s'
- self.content = DebugReportContent(model, ident)
-
- @property
- def data(self):
- return {'name': self.ident,
- 'uri': self.info['uri'],
- 'time': self.info['ctime']}
-
-
-class DebugReportContent(Resource):
- def __init__(self, model, ident):
- super(DebugReportContent, self).__init__(model, ident)
- self.role_key = 'host'
- self.admin_methods = ['GET']
-
- def get(self):
- self.lookup()
- raise internal_redirect(self.info['uri'])
--
2.1.0
3
2