From 6e1800aa5f9a41eba1770c4663f622cd58306477 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 27 Jun 2015 19:57:14 +0200 Subject: [PATCH] Improved the admin's interface --- Admin/HandlerConfiguration.go | 64 ++++++++++++++++++++++ Admin/HandlerFileUpload.go | 78 +++++++++++++++++++++++++++ Admin/Init.go | 8 +++ Admin/Scheme.go | 9 ++++ Admin/Templates/Configuration.go | 47 ++++++++++++++++ Admin/Templates/FileUpload.go | 44 +++++++++++++++ Admin/Templates/Overview.go | 6 ++- ConfigurationDB/CheckConfiguration.go | 2 +- ConfigurationDB/ReadAll.go | 16 ++++++ ConfigurationDB/UpdateValue.go | 47 ++++++++++++++++ System/InitHandlers.go | 10 +++- 11 files changed, 326 insertions(+), 5 deletions(-) create mode 100644 Admin/HandlerConfiguration.go create mode 100644 Admin/HandlerFileUpload.go create mode 100644 Admin/Scheme.go create mode 100644 Admin/Templates/Configuration.go create mode 100644 Admin/Templates/FileUpload.go create mode 100644 ConfigurationDB/ReadAll.go create mode 100644 ConfigurationDB/UpdateValue.go diff --git a/Admin/HandlerConfiguration.go b/Admin/HandlerConfiguration.go new file mode 100644 index 0000000..f92a040 --- /dev/null +++ b/Admin/HandlerConfiguration.go @@ -0,0 +1,64 @@ +package Admin + +import ( + "github.com/SommerEngineering/Ocean/ConfigurationDB" + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "github.com/SommerEngineering/Ocean/MimeTypes" + "github.com/SommerEngineering/Ocean/Shutdown" + "net/http" + "strings" +) + +// Handler for accessing the file upload function. +func HandlerConfiguration(response http.ResponseWriter, request *http.Request) { + + // Case: The system goes down now. + if Shutdown.IsDown() { + http.NotFound(response, request) + return + } + + if strings.ToLower(request.Method) == `get` { + // + // Case: Send the website to the client + // + + // Read all configuration values: + values := ConfigurationDB.ReadAll() + + // Build the data type for the template: + data := AdminWebConfiguration{} + data.Configuration = values + + // Write the MIME type and execute the template: + MimeTypes.Write2HTTP(response, MimeTypes.TypeWebHTML) + if executeError := AdminTemplates.ExecuteTemplate(response, `Configuration`, data); executeError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameEXECUTE, `Was not able to execute the configuration template.`, executeError.Error()) + } + } else { + // + // Case: Receive the changed configuration + // + + // Read all configuration values: + values := ConfigurationDB.ReadAll() + + // Loop over all current known values: + for _, value := range values { + + // Read the new value from the client side: + newValue := request.FormValue(value.Name) + + // Store the new value: + value.Value = newValue + + // Update the database: + ConfigurationDB.UpdateValue(value.Name, value) + } + + // Redirect the client to the admin's overview: + defer http.Redirect(response, request, "/configuration", 302) + } + +} diff --git a/Admin/HandlerFileUpload.go b/Admin/HandlerFileUpload.go new file mode 100644 index 0000000..d545014 --- /dev/null +++ b/Admin/HandlerFileUpload.go @@ -0,0 +1,78 @@ +package Admin + +import ( + "fmt" + "github.com/SommerEngineering/Ocean/CustomerDB" + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "github.com/SommerEngineering/Ocean/MimeTypes" + "github.com/SommerEngineering/Ocean/Shutdown" + "io" + "net/http" + "strings" +) + +// Handler for accessing the file upload function. +func HandlerFileUpload(response http.ResponseWriter, request *http.Request) { + + // Case: The system goes down now. + if Shutdown.IsDown() { + http.NotFound(response, request) + return + } + + if strings.ToLower(request.Method) == `get` { + // + // Case: Send the website to the client + // + + // Write the MIME type and execute the template: + MimeTypes.Write2HTTP(response, MimeTypes.TypeWebHTML) + if executeError := AdminTemplates.ExecuteTemplate(response, `FileUpload`, nil); executeError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameEXECUTE, `Was not able to execute the file upload template.`, executeError.Error()) + } + } else { + // + // Case: Receive the file to upload + // + + if file, fileHeader, fileError := request.FormFile(`file`); fileError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameBROWSER, `Was not able to access the file uploaded.`, fileError.Error()) + } else { + // + // Case: Access was possible. + // + + // Get the GridFS from the database: + dbSession, gridFS := CustomerDB.GridFS() + defer dbSession.Close() + + // Try to create the desired file at the grid file system: + if newFile, errNewFile := gridFS.Create(fileHeader.Filename); errNewFile != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to create the desired file at the grid file system.`, errNewFile.Error(), fmt.Sprintf("filename='%s'", fileHeader.Filename)) + } else { + + // Close the files afterwards: + defer file.Close() + defer newFile.Close() + + // Try to copy the file's content to the database: + if _, errCopy := io.Copy(newFile, file); errCopy != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNETWORK, `Was not able to copy the desired file's content to the grid file system.`, errNewFile.Error(), fmt.Sprintf("filename='%s'", fileHeader.Filename)) + } else { + // Try to determine the MIME type: + if mimeType, errMime := MimeTypes.DetectType(fileHeader.Filename); errMime != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityLow, LM.ImpactLow, LM.MessageNamePARSE, `Was not able to parse the desired file's MIME type.`, errMime.Error(), fmt.Sprintf("filename='%s'", fileHeader.Filename)) + } else { + // Set also the MIME type in the database: + newFile.SetContentType(mimeType.MimeType) + } + } + } + } + + // Redirect the client to the admin's overview: + defer http.Redirect(response, request, "/", 302) + } + +} diff --git a/Admin/Init.go b/Admin/Init.go index 0d925d4..037bff6 100644 --- a/Admin/Init.go +++ b/Admin/Init.go @@ -22,4 +22,12 @@ func init() { if _, err := AdminTemplates.Parse(Templates.Overview); err != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to parse the template for the admin overview.`, err.Error()) } + + if _, err := AdminTemplates.Parse(Templates.FileUpload); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to parse the template for the file upload.`, err.Error()) + } + + if _, err := AdminTemplates.Parse(Templates.Configuration); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to parse the template for the configuration.`, err.Error()) + } } diff --git a/Admin/Scheme.go b/Admin/Scheme.go new file mode 100644 index 0000000..f6208ab --- /dev/null +++ b/Admin/Scheme.go @@ -0,0 +1,9 @@ +package Admin + +import ( + "github.com/SommerEngineering/Ocean/ConfigurationDB" +) + +type AdminWebConfiguration struct { + Configuration []ConfigurationDB.ConfigurationDBEntry +} diff --git a/Admin/Templates/Configuration.go b/Admin/Templates/Configuration.go new file mode 100644 index 0000000..b023403 --- /dev/null +++ b/Admin/Templates/Configuration.go @@ -0,0 +1,47 @@ +package Templates + +var Configuration = ` +{{define "Configuration"}} + + + + + + + Configuration + + + + + + + + +
+

Cluster Configuration

+
+
+

Attention: This configuration applies to the whole cluster of your Ocean servers. Therefore, this is not an individual configuration for any single server! Thus, please consider beforehand if the desired change matches all of your servers. +

+
+
+ {{range .Configuration}} + + + {{end}} + +
+
+

Thank you! Your submission has been received!

+
+
+

Oops! Something went wrong while submitting the form

+
+
+
+ + + + +{{end}} +` diff --git a/Admin/Templates/FileUpload.go b/Admin/Templates/FileUpload.go new file mode 100644 index 0000000..d9dd2fe --- /dev/null +++ b/Admin/Templates/FileUpload.go @@ -0,0 +1,44 @@ +package Templates + +var FileUpload = ` +{{define "FileUpload"}} + + + + + + + Upload a file + + + + + + + + +
+

Upload a file

+
+
+

This function enables you to upload a file to the distributed file system of the MongoDB database. If the desired file is already present, a new revision of this file is created. Therefore, an already existing file gets never overwritten! Please consider, that the configured maximum size of the header of the admin web server (see configuration) forces the maximum file size. Thus, please check the current maximum. The default maximum is approx. 10 MB!

+
+
+ + + +
+
+

Thank you! Your submission has been received!

+
+
+

Oops! Something went wrong while submitting the form

+
+
+
+ + + + +{{end}} +` diff --git a/Admin/Templates/Overview.go b/Admin/Templates/Overview.go index df10b9c..2e47063 100644 --- a/Admin/Templates/Overview.go +++ b/Admin/Templates/Overview.go @@ -24,8 +24,10 @@ var Overview = `
-
-
+ +
diff --git a/ConfigurationDB/CheckConfiguration.go b/ConfigurationDB/CheckConfiguration.go index ca1d7e3..5891501 100644 --- a/ConfigurationDB/CheckConfiguration.go +++ b/ConfigurationDB/CheckConfiguration.go @@ -16,7 +16,7 @@ func checkConfiguration() { CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerEnabled`, `True`) CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerReadTimeoutSeconds`, `10`) CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerWriteTimeoutSeconds`, `10`) - CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerMaxHeaderLenBytes`, `1048576`) + CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerMaxHeaderLenBytes`, `10485760`) CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerPort`, `50000`) CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerReadTimeoutSeconds`, `10`) CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerWriteTimeoutSeconds`, `10`) diff --git a/ConfigurationDB/ReadAll.go b/ConfigurationDB/ReadAll.go new file mode 100644 index 0000000..4b277a5 --- /dev/null +++ b/ConfigurationDB/ReadAll.go @@ -0,0 +1,16 @@ +package ConfigurationDB + +import ( + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "gopkg.in/mgo.v2/bson" +) + +// This function reads all configuration values e.g. for the admin's configuration web interface. +func ReadAll() (values []ConfigurationDBEntry) { + if errFind := collection.Find(bson.D{}).All(&values); errFind != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to read all configuration values out of the database.`, `Error while find.`, errFind.Error()) + } + + return +} diff --git a/ConfigurationDB/UpdateValue.go b/ConfigurationDB/UpdateValue.go new file mode 100644 index 0000000..77ccb69 --- /dev/null +++ b/ConfigurationDB/UpdateValue.go @@ -0,0 +1,47 @@ +package ConfigurationDB + +import ( + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "gopkg.in/mgo.v2/bson" +) + +// This function updates a configuration value e.g. from the admin's configuration web interface. +func UpdateValue(name string, configuration ConfigurationDBEntry) { + + // Check the configuration's name: + if name == `` { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameSTATE, `Was not able to update a configuration value.`, `The given name was nil!`) + return + } + + // Ensure, that the configuration is already present: + if count, errFind := collection.Find(bson.D{{"Name", name}}).Count(); errFind != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to update a configuration value.`, `Error while find the old value.`, errFind.Error()) + return + } else { + // Is the configuration already present? + if count == 0 { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameSTATE, `Was not able to update a configuration value.`, `The desired configuration is not present.`) + return + } + } + + // Ensure, that the configuration value also uses the same name: + if name != configuration.Name { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameSTATE, `Was not able to update a configuration value.`, `The given name was different with the name of the desired configuration value.`) + return + } + + // + // Case: Any precondition is fulfilled + // + + // Selection of the correct configuration (the name is a unique value): + selector := bson.D{{"Name", name}} + if errUpdate := collection.Update(selector, configuration); errUpdate != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to update a configuration value.`, `Error while updating the database.`, errUpdate.Error()) + } + + return +} diff --git a/System/InitHandlers.go b/System/InitHandlers.go index 1c142c1..a6fd2ea 100644 --- a/System/InitHandlers.go +++ b/System/InitHandlers.go @@ -37,7 +37,7 @@ func InitHandlers() { Handlers.AddPublicHandler(`/ICCC`, ICCC.ICCCHandler) // - // Private Handlers: + // Private/Admin Handlers: // // Handler for the web frameworks like e.g. jQuery, Bootstrap, etc. @@ -58,7 +58,13 @@ func InitHandlers() { // Handler for the web logging: Handlers.AddAdminHandler(`/log`, Web.HandlerWebLog) - // Handler for the web logging's CSS and JS: + // Handler for the file upload: + Handlers.AddAdminHandler(`/upload`, Admin.HandlerFileUpload) + + // Handler for the configuration view: + Handlers.AddAdminHandler(`/configuration`, Admin.HandlerConfiguration) + + // Handler for the admin area: Handlers.AddAdminHandler(`/admin/css/normalize.css`, Admin.HandlerCSSNormalize) Handlers.AddAdminHandler(`/admin/css/webflow.css`, Admin.HandlerCSSWebflow) Handlers.AddAdminHandler(`/admin/css/admin.css`, Admin.HandlerCSSAdmin)