Improved the admin's interface

This commit is contained in:
Thorsten Sommer 2015-06-27 19:57:14 +02:00
parent 4138c85639
commit 6e1800aa5f
11 changed files with 326 additions and 5 deletions

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -22,4 +22,12 @@ func init() {
if _, err := AdminTemplates.Parse(Templates.Overview); err != nil { 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()) 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())
}
} }

9
Admin/Scheme.go Normal file
View File

@ -0,0 +1,9 @@
package Admin
import (
"github.com/SommerEngineering/Ocean/ConfigurationDB"
)
type AdminWebConfiguration struct {
Configuration []ConfigurationDB.ConfigurationDBEntry
}

View File

@ -0,0 +1,47 @@
package Templates
var Configuration = `
{{define "Configuration"}}
<!DOCTYPE html>
<!-- This site was created in Webflow. http://www.webflow.com-->
<!-- Last Published: Fri Jun 26 2015 05:50:41 GMT+0000 (UTC) -->
<html data-wf-site="547b44aa3e9ac2216ec5d048" data-wf-page="558ce60e0939295c77a362db">
<head>
<meta charset="utf-8">
<title>Configuration</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="generator" content="Webflow">
<link rel="stylesheet" type="text/css" href="/admin/css/normalize.css">
<link rel="stylesheet" type="text/css" href="/admin/css/webflow.css">
<link rel="stylesheet" type="text/css" href="/admin/css/admin.css">
<script type="text/javascript" src="/admin/js/modernizr.js"></script>
</head>
<body>
<div class="w-section headercontainer">
<h1>Cluster Configuration</h1>
</div>
<div class="w-container adminsection">
<p class="introtext"><strong>Attention:</strong> This configuration applies to the whole cluster of your Ocean servers. Therefore, <strong>this is not</strong> an individual configuration for any single server! Thus, please consider beforehand if the desired change matches all of your servers.
</p>
<div class="w-form">
<form id="configuration" name="configuration" data-name="configuration" method="post" action="/configuration">
{{range .Configuration}}
<label for="{{.Name}}">Configuration parameter:&nbsp;{{.Name}}</label>
<input class="w-input" id="{{.Name}}" type="text" name="{{.Name}}" data-name="{{.Name}}" required="required" value="{{.Value}}" placeholder="{{.Value}}">
{{end}}
<input class="w-button button optionbuttons" type="submit" value="Apply all changes" data-wait="Please wait...">
</form>
<div class="w-form-done">
<p>Thank you! Your submission has been received!</p>
</div>
<div class="w-form-fail">
<p>Oops! Something went wrong while submitting the form</p>
</div>
</div>
</div>
<script type="text/javascript" src="/admin/js/jquery.min.js"></script>
<script type="text/javascript" src="/admin/js/webflow.js"></script>
</body>
</html>
{{end}}
`

View File

@ -0,0 +1,44 @@
package Templates
var FileUpload = `
{{define "FileUpload"}}
<!DOCTYPE html>
<!-- This site was created in Webflow. http://www.webflow.com-->
<!-- Last Published: Fri Jun 26 2015 05:50:41 GMT+0000 (UTC) -->
<html data-wf-site="547b44aa3e9ac2216ec5d048" data-wf-page="558ce167e20f1f4d31c64577">
<head>
<meta charset="utf-8">
<title>Upload a file</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="generator" content="Webflow">
<link rel="stylesheet" type="text/css" href="/admin/css/normalize.css">
<link rel="stylesheet" type="text/css" href="/admin/css/webflow.css">
<link rel="stylesheet" type="text/css" href="/admin/css/admin.css">
<script type="text/javascript" src="/admin/js/modernizr.js"></script>
</head>
<body>
<div class="w-section headercontainer">
<h1>Upload a file</h1>
</div>
<div class="w-container adminsection">
<p class="introtext">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&nbsp;approx. 10 MB!</p>
<div class="w-form">
<form id="upload" name="upload" data-name="upload" method="post" action="/upload" enctype="multipart/form-data">
<label for="file">Please select a file to upload:</label>
<input class="w-input" id="file" type="file" name="file" data-name="file">
<input class="w-button button optionbuttons" type="submit" value="Upload this file" data-wait="Please wait...">
</form>
<div class="w-form-done">
<p>Thank you! Your submission has been received!</p>
</div>
<div class="w-form-fail">
<p>Oops! Something went wrong while submitting the form</p>
</div>
</div>
</div>
<script type="text/javascript" src="/admin/js/jquery.min.js"></script>
<script type="text/javascript" src="/admin/js/webflow.js"></script>
</body>
</html>
{{end}}
`

View File

@ -24,8 +24,10 @@ var Overview = `
<div class="w-row"> <div class="w-row">
<div class="w-col w-col-4"><a class="button adminbutton" href="/log">Logging Viewer</a> <div class="w-col w-col-4"><a class="button adminbutton" href="/log">Logging Viewer</a>
</div> </div>
<div class="w-col w-col-4"></div> <div class="w-col w-col-4"><a class="button adminbutton" href="/upload">Upload a file</a>
<div class="w-col w-col-4"></div> </div>
<div class="w-col w-col-4"><a class="button adminbutton" href="/configuration">Configuration</a>
</div>
</div> </div>
</div> </div>
<script type="text/javascript" src="/admin/js/jquery.min.js"></script> <script type="text/javascript" src="/admin/js/jquery.min.js"></script>

View File

@ -16,7 +16,7 @@ func checkConfiguration() {
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerEnabled`, `True`) CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerEnabled`, `True`)
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerReadTimeoutSeconds`, `10`) CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerReadTimeoutSeconds`, `10`)
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerWriteTimeoutSeconds`, `10`) CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerWriteTimeoutSeconds`, `10`)
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerMaxHeaderLenBytes`, `1048576`) CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerMaxHeaderLenBytes`, `10485760`)
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerPort`, `50000`) CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerPort`, `50000`)
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerReadTimeoutSeconds`, `10`) CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerReadTimeoutSeconds`, `10`)
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerWriteTimeoutSeconds`, `10`) CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerWriteTimeoutSeconds`, `10`)

View File

@ -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
}

View File

@ -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
}

View File

@ -37,7 +37,7 @@ func InitHandlers() {
Handlers.AddPublicHandler(`/ICCC`, ICCC.ICCCHandler) Handlers.AddPublicHandler(`/ICCC`, ICCC.ICCCHandler)
// //
// Private Handlers: // Private/Admin Handlers:
// //
// Handler for the web frameworks like e.g. jQuery, Bootstrap, etc. // Handler for the web frameworks like e.g. jQuery, Bootstrap, etc.
@ -58,7 +58,13 @@ func InitHandlers() {
// Handler for the web logging: // Handler for the web logging:
Handlers.AddAdminHandler(`/log`, Web.HandlerWebLog) 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/normalize.css`, Admin.HandlerCSSNormalize)
Handlers.AddAdminHandler(`/admin/css/webflow.css`, Admin.HandlerCSSWebflow) Handlers.AddAdminHandler(`/admin/css/webflow.css`, Admin.HandlerCSSWebflow)
Handlers.AddAdminHandler(`/admin/css/admin.css`, Admin.HandlerCSSAdmin) Handlers.AddAdminHandler(`/admin/css/admin.css`, Admin.HandlerCSSAdmin)