diff --git a/config/dev-server-config.json b/config/dev-server-config.json index b8d83879c..880eb05ae 100644 --- a/config/dev-server-config.json +++ b/config/dev-server-config.json @@ -11,6 +11,7 @@ "handlerArgs": { "ownerName": ["_env", "${USER}-dev"], "blobRoot": "/bs-and-maybe-also-index/", + "statusRoot": "/status/", "searchRoot": "/my-search/", "stealth": false } @@ -58,6 +59,10 @@ } }, + "/status/": { + "handler": "status" + }, + "/sync/": { "handler": "sync", "handlerArgs": { diff --git a/pkg/server/root.go b/pkg/server/root.go index eb44df062..56f5c4fce 100644 --- a/pkg/server/root.go +++ b/pkg/server/root.go @@ -44,6 +44,7 @@ type RootHandler struct { // search root. BlobRoot string SearchRoot string + statusRoot string Storage blobserver.Storage // of BlobRoot, or nil @@ -75,6 +76,7 @@ func newRootFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handle OwnerName: conf.OptionalString("ownerName", u.Name), } root.Stealth = conf.OptionalBool("stealth", false) + root.statusRoot = conf.OptionalString("statusRoot", "") if err = conf.Validate(); err != nil { return } @@ -163,6 +165,7 @@ func (rh *RootHandler) serveDiscovery(rw http.ResponseWriter, req *http.Request) "blobRoot": rh.BlobRoot, "searchRoot": rh.SearchRoot, "ownerName": rh.OwnerName, + "statusRoot": rh.statusRoot, } if gener, ok := rh.Storage.(blobserver.Generationer); ok { initTime, gen, err := gener.StorageGeneration() diff --git a/pkg/server/status.go b/pkg/server/status.go new file mode 100644 index 000000000..5fa8b8af3 --- /dev/null +++ b/pkg/server/status.go @@ -0,0 +1,66 @@ +/* +Copyright 2013 The Camlistore Authors. + +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. +*/ + +package server + +import ( + "net/http" + + "camlistore.org/pkg/blobserver" + "camlistore.org/pkg/buildinfo" + "camlistore.org/pkg/httputil" + "camlistore.org/pkg/jsonconfig" +) + +// StatusHandler publishes server status information. +type StatusHandler struct { + Version string +} + +func init() { + blobserver.RegisterHandlerConstructor("status", newStatusFromConfig) +} + +func newStatusFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) { + sh := &StatusHandler{ + Version: buildinfo.Version(), + } + return sh, nil +} + +func (sh *StatusHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + suffix := req.Header.Get("X-PrefixHandler-PathSuffix") + if req.Method != "GET" { + http.Error(rw, "Illegal URL.", 404) + } + if suffix == "status.json" { + sh.serveStatus(rw, req) + return + } + http.Error(rw, "Illegal URL.", 404) +} + +type statusResponse struct { + Version string `json:"version"` +} + +func (sh *StatusHandler) serveStatus(rw http.ResponseWriter, req *http.Request) { + res := &statusResponse{ + Version: sh.Version, + } + + httputil.ReturnJSON(rw, res) +} diff --git a/pkg/serverconfig/genconfig.go b/pkg/serverconfig/genconfig.go index 763e99198..05fcd4a13 100644 --- a/pkg/serverconfig/genconfig.go +++ b/pkg/serverconfig/genconfig.go @@ -253,6 +253,7 @@ func genLowLevelPrefixes(params *configPrefixesParams) (m jsonconfig.Obj) { "handlerArgs": map[string]interface{}{ "stealth": false, "blobRoot": root, + "statusRoot": "/status/", }, } if haveIndex { @@ -263,6 +264,10 @@ func genLowLevelPrefixes(params *configPrefixesParams) (m jsonconfig.Obj) { "handler": "setup", } + m["/status/"] = map[string]interface{}{ + "handler": "status", + } + if params.shareHandler { m["/share/"] = map[string]interface{}{ "handler": "share", diff --git a/pkg/serverconfig/serverconfig.go b/pkg/serverconfig/serverconfig.go index 260b4ba03..bd82afd6c 100644 --- a/pkg/serverconfig/serverconfig.go +++ b/pkg/serverconfig/serverconfig.go @@ -298,7 +298,7 @@ func handerTypeWantsAuth(handlerType string) bool { // TODO(bradfitz): ask the handler instead? This is a bit of a // weird spot for this policy maybe? switch handlerType { - case "ui", "search", "jsonsign", "sync": + case "ui", "search", "jsonsign", "sync", "status": return true } return false diff --git a/server/camlistored/newui/index.css b/server/camlistored/newui/index.css index 5c5c347e9..f90664dad 100644 --- a/server/camlistored/newui/index.css +++ b/server/camlistored/newui/index.css @@ -1,3 +1,12 @@ .cam-index-page { - font: 16px/1.4 normal Arial, sans-serif; + font: 16px/1.4 normal Arial, sans-serif; +} + +.cam-index-title { + display: inline-block; +} + +.cam-index-serverinfo { + display: inline; + float: right; } diff --git a/server/camlistored/newui/index.js b/server/camlistored/newui/index.js index c7f4e8859..0d9af7420 100644 --- a/server/camlistored/newui/index.js +++ b/server/camlistored/newui/index.js @@ -10,10 +10,12 @@ goog.require('goog.dom.classes'); goog.require('goog.events.EventHandler'); goog.require('goog.events.EventType'); goog.require('goog.ui.Component'); +goog.require('goog.ui.Textarea'); goog.require('camlistore.BlobItemContainer'); goog.require('camlistore.ServerConnection'); goog.require('camlistore.Toolbar'); goog.require('camlistore.Toolbar.EventType'); +goog.require('camlistore.ServerType'); /** @@ -47,6 +49,12 @@ camlistore.IndexPage = function(config, opt_domHelper) { this.connection_, opt_domHelper); this.blobItemContainer_.setHasCreateItem(true); + /** + * @type {Element} + * @private + */ + this.serverInfo_; + /** * @type {camlistore.Toolbar} * @private @@ -81,10 +89,13 @@ camlistore.IndexPage.prototype.decorateInternal = function(element) { var el = this.getElement(); goog.dom.classes.add(el, 'cam-index-page'); - var titleEl = this.dom_.createDom('h1', 'cam-index-page-title'); + var titleEl = this.dom_.createDom('h1', 'cam-index-title'); this.dom_.setTextContent(titleEl, this.config_.ownerName + '\'s Vault'); this.dom_.appendChild(el, titleEl); + this.serverInfo_ = this.dom_.createDom('div', 'cam-index-serverinfo'); + this.dom_.appendChild(el, this.serverInfo_); + this.addChild(this.toolbar_, true); this.addChild(this.blobItemContainer_, true); }; @@ -103,6 +114,12 @@ camlistore.IndexPage.prototype.disposeInternal = function() { camlistore.IndexPage.prototype.enterDocument = function() { camlistore.IndexPage.superClass_.enterDocument.call(this); + this.connection_.serverStatus( + goog.bind(function(resp) { + this.handleServerStatus_(resp); + }, this) + ); + this.eh_.listen( this.toolbar_, camlistore.Toolbar.EventType.BIGGER, function() { @@ -273,3 +290,22 @@ camlistore.IndexPage.prototype.addItemsToSetDone_ = function(permanode) { this.toolbar_.toggleAddToSetButton(false); this.blobItemContainer_.showRecent(); }; + +/** + * @param {camlistore.ServerType.StatusResponse} resp response for a status request + * @private + */ +camlistore.IndexPage.prototype.handleServerStatus_ = +function(resp) { + if (resp == null) { + return; + } + goog.dom.removeChildren(this.serverInfo_); + if (resp.version) { + var version = "Camlistore version: " + resp.version + "\n"; + var div = this.dom_.createDom('div'); + goog.dom.setTextContent(div, version); + goog.dom.appendChild(this.serverInfo_, div); + } +}; + diff --git a/server/camlistored/newui/server_connection.js b/server/camlistored/newui/server_connection.js index fa990ba5c..8eebbedb8 100644 --- a/server/camlistored/newui/server_connection.js +++ b/server/camlistored/newui/server_connection.js @@ -142,6 +142,24 @@ function(success, opt_fail) { ); }; +/** + * @param {function(camlistore.ServerType.StatusResponse)} success. + * @param {?Function} opt_fail optional failure calback + */ +camlistore.ServerConnection.prototype.serverStatus = +function(success, opt_fail) { + var path = goog.uri.utils.appendPath( + this.config_.statusRoot, 'status.json' + ); + + this.sendXhr_(path, + goog.bind(this.handleXhrResponseJson_, this, + success, this.safeFail_(opt_fail) + ) + ); +}; + + /** * @param {Function} success Success callback. * @param {?Function} opt_fail Optional fail callback. diff --git a/server/camlistored/newui/server_type.js b/server/camlistored/newui/server_type.js index 319164d4e..70589f1e1 100644 --- a/server/camlistored/newui/server_type.js +++ b/server/camlistored/newui/server_type.js @@ -23,6 +23,7 @@ camlistore.ServerType.DiscoveryRoot; * ownerName: string, * publishRoots: Array., * searchRoot: string, + * statusRoot: string, * storageGeneration: string, * storageInitTime: string, * signing: camlistore.ServerType.SigningDiscoveryDocument, @@ -128,3 +129,10 @@ camlistore.ServerType.SearchWithAttrResponse; * }} */ camlistore.ServerType.DescribeResponse; + +/** + * @typedef {{ + * version: string, + * }} +*/ +camlistore.ServerType.StatusResponse;