From afde87169fdd7d4990db5600281505148e62e94d Mon Sep 17 00:00:00 2001 From: Whisperity Date: Fri, 21 Jul 2017 16:40:26 +0200 Subject: [PATCH] Web interface for product listing - The CodeChecker Viewer now by default opens a product list. Users can select the product they want to view and will be routed to the run list. - If only ONE products exist on the server, simply opening http://host:port will automatically redirect the user to the run list of this single product. - This will only happen if this only product is properly connected. - Added a button to run lists to explicitly return to the product list page - The current product's name is now shown in the run list's title bar [ci skip] --- api/README.md | 5 + api/products.thrift | 19 +- docs/images/products.png | Bin 0 -> 21899 bytes .../server/client_db_access_handler.py | 2 +- .../server/client_db_access_server.py | 76 +++++-- www/index.html | 2 + www/products.html | 102 +++++++++ www/scripts/codecheckerviewer/CommentView.js | 8 +- www/scripts/codecheckerviewer/ListOfRuns.js | 21 +- .../codecheckerviewer/codecheckerviewer.js | 40 +++- www/scripts/codecheckerviewer/util.js | 41 +++- www/scripts/productlist/ListOfProducts.js | 200 ++++++++++++++++++ www/scripts/productlist/productlist.js | 103 +++++++++ www/style/codecheckerviewer.css | 2 +- www/style/productlist.css | 74 +++++++ 15 files changed, 635 insertions(+), 60 deletions(-) create mode 100644 docs/images/products.png create mode 100644 www/products.html create mode 100644 www/scripts/productlist/ListOfProducts.js create mode 100644 www/scripts/productlist/productlist.js create mode 100644 www/style/productlist.css diff --git a/api/README.md b/api/README.md index 72d5c72933..94ef5e9f69 100644 --- a/api/README.md +++ b/api/README.md @@ -9,3 +9,8 @@ See [report_server.thrift](https://raw.githubusercontent.com/Ericsson/codechecke ## Authentication system API The authentication layer is used for supporting privileged-access only access. See [authentication.thrift](https://raw.githubusercontent.com/Ericsson/codechecker/master/thrift_api/authentication.thrift) + +## Product management system API +The product management layer is responsible for handling requests about the +different products and their configuration. See +[products.thrift](https://raw.githubusercontent.com/Ericsson/codechecker/master/thrift_api/products.thrift) \ No newline at end of file diff --git a/api/products.thrift b/api/products.thrift index 6b76bdf406..6915d35347 100644 --- a/api/products.thrift +++ b/api/products.thrift @@ -50,10 +50,25 @@ typedef list Products service codeCheckerProductService { - // *** Handling the add-modify-remove of products registered *** // - Products getProducts() + // Return the product management API version. + string getAPIVersion(), + + // Returns the CodeChecker version that is running on the server. + string getPackageVersion(), + + // *** Handling of product lists and metadata querying *** // + + // Get the list of product that matches the display name and endpoint + // filters specified. + Products getProducts(1: string productEndpointFilter, + 2: string productNameFilter) throws (1: shared.RequestFailed requestError), + Product getCurrentProduct() + throws (1: shared.RequestFailed requestError), + + // *** Handling the add-modify-remove of products registered *** // + bool addProduct(1: Product product) throws (1: shared.RequestFailed requestError), diff --git a/docs/images/products.png b/docs/images/products.png new file mode 100644 index 0000000000000000000000000000000000000000..ee8e02aedefb57b0c9f3cef3fbfe1ec0f4aa89fb GIT binary patch literal 21899 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 literal 0 HcmV?d00001 diff --git a/libcodechecker/server/client_db_access_handler.py b/libcodechecker/server/client_db_access_handler.py index 1a1c2d5a6e..a036c4ed4d 100644 --- a/libcodechecker/server/client_db_access_handler.py +++ b/libcodechecker/server/client_db_access_handler.py @@ -38,7 +38,7 @@ from run_db_model import * -LOG = LoggerFactory.get_new_logger('ACCESS HANDLER') +LOG = LoggerFactory.get_new_logger('RUN ACCESS HANDLER') def conv(text): diff --git a/libcodechecker/server/client_db_access_server.py b/libcodechecker/server/client_db_access_server.py index 50f303eb83..75963c6398 100644 --- a/libcodechecker/server/client_db_access_server.py +++ b/libcodechecker/server/client_db_access_server.py @@ -45,13 +45,23 @@ LOG = LoggerFactory.get_new_logger('DB ACCESS') -NON_PRODUCT_NAMES = ['index.html', +# A list of top-level path elements under the webserver root +# which should not be considered as a product route. +NON_PRODUCT_NAMES = ['products.html', + 'index.html', 'fonts', 'images', 'scripts', 'style' ] +# A list of top-level path elements in requests (such as Thrift endpoints) +# which should not be considered as a product route. +NON_PRODUCT_NAMES += ['Authentication', + 'Products', + 'CodeCheckerService' + ] + class RequestHandler(SimpleHTTPRequestHandler): """ @@ -60,9 +70,6 @@ class RequestHandler(SimpleHTTPRequestHandler): """ def __init__(self, request, client_address, server): - self.db_version_info = server.db_version_info - self.manager = server.manager - BaseHTTPRequestHandler.__init__(self, request, client_address, @@ -80,7 +87,7 @@ def __check_auth_in_request(self): present. """ - if not self.manager.isEnabled(): + if not self.server.manager.isEnabled(): return None success = None @@ -98,9 +105,10 @@ def __check_auth_in_request(self): values = cookie.split("=") if len(values) == 2 and \ values[0] == session_manager.SESSION_COOKIE_NAME: - if self.manager.is_valid(values[1], True): + if self.server.manager.is_valid(values[1], True): # The session cookie contains valid data. - success = self.manager.get_session(values[1], True) + success = self.server.manager.get_session(values[1], + True) if success is None: # Session cookie was invalid (or not found...) @@ -114,7 +122,8 @@ def __check_auth_in_request(self): self.headers.getheader("Authorization"). replace("Basic ", "")) - session = self.manager.create_or_get_session(authString) + session = self.server.manager.create_or_get_session( + authString) if session: LOG.info("Client from " + client_host + ":" + str(client_port) + @@ -144,7 +153,8 @@ def __get_product_name(self): # viewer client from the www/ folder. # The split array looks like ['', 'product-name', ...]. - return urlparse.urlparse(self.path).path.split('/', 2)[1] + first_part = urlparse.urlparse(self.path).path.split('/', 2)[1] + return first_part if first_part not in NON_PRODUCT_NAMES else None def do_GET(self): """ @@ -167,12 +177,12 @@ def do_GET(self): self.send_header("Content-length", str(len(error_body))) self.send_header('Connection', 'close') self.end_headers() - self.wfile.write(self.manager.getRealm()["error"]) + self.wfile.write(error_body) return else: product_name = self.__get_product_name() - if product_name not in NON_PRODUCT_NAMES and product_name != '': + if product_name is not None and product_name != '': if not self.server.get_product(product_name): LOG.info("Product named '{0}' does not exist." .format(product_name)) @@ -189,13 +199,14 @@ def do_GET(self): LOG.debug("Redirecting user from /{0} to /{0}/index.html" .format(product_name)) - # WARN: Browsers cache '301 Moved Permanently' responses, + # WARN: Browsers cache '308 Permanent Redirect' responses, # in the event of debugging this, use Private Browsing! - self.send_response(301) + self.send_response(308) self.send_header("Location", self.path.replace(product_name, product_name + '/', 1)) self.end_headers() + return else: # Serves the main page and the resources: # /prod/(index.html) -> /(index.html) @@ -205,24 +216,35 @@ def do_GET(self): "{0}/".format(product_name), "", 1) LOG.debug("Product routing after: " + self.path) else: - self.send_response(200) # 200 OK - if auth_session is not None: - # Browsers get a standard cookie for session. - self.send_header( - "Set-Cookie", - "{0}={1}; Path=/".format( - session_manager.SESSION_COOKIE_NAME, - auth_session.auth_token)) + if self.path in ['/', '/index.html']: + only_product = self.server.get_only_product() + if only_product and only_product.connected: + LOG.debug("Redirecting '/' to ONLY product '/{0}'" + .format(only_product.endpoint)) + + self.send_response(307) # 307 Temporary Redirect + self.send_header("Location", + '/{0}'.format(only_product.endpoint)) + self.end_headers() + return - if self.path in ['/', '/index.html', '/products.html']: # Route homepage queries to serving the product list. LOG.debug("Serving product list as homepage.") - self.path = "/products.html" + self.path = '/products.html' else: # The path requested does not specify a product: it is most # likely a resource file. LOG.debug("Serving resource '{0}'".format(self.path)) + self.send_response(200) # 200 OK + if auth_session is not None: + # Browsers get a standard cookie for session. + self.send_header( + "Set-Cookie", + "{0}={1}; Path=/".format( + session_manager.SESSION_COOKIE_NAME, + auth_session.token)) + SimpleHTTPRequestHandler.do_GET(self) def do_POST(self): @@ -558,6 +580,14 @@ def get_product(self, endpoint): """ return self.__products.get(endpoint, None) + def get_only_product(self): + """ + Returns the Product object for the only product connected to by the + server, or None, if there are 0 or >= 2 products managed. + """ + return self.__products.items()[0][1] if len(self.__products) == 1 \ + else None + def start_server(package_data, port, db_conn_string, suppress_handler, listen_address, context, check_env): diff --git a/www/index.html b/www/index.html index 5144c748c5..b95cf3e8b8 100644 --- a/www/index.html +++ b/www/index.html @@ -40,6 +40,8 @@ + + diff --git a/www/products.html b/www/products.html new file mode 100644 index 0000000000..e0af183986 --- /dev/null +++ b/www/products.html @@ -0,0 +1,102 @@ + + + + CodeChecker + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/scripts/codecheckerviewer/CommentView.js b/www/scripts/codecheckerviewer/CommentView.js index 8d44fbebbe..560b2a87fd 100644 --- a/www/scripts/codecheckerviewer/CommentView.js +++ b/www/scripts/codecheckerviewer/CommentView.js @@ -20,9 +20,9 @@ define([ 'dijit/tree/ObjectStoreModel', 'codechecker/HtmlTree', 'codechecker/util'], -function (declare, dom, style, topic, Memory, Observable, ConfirmDialog, Dialog, - ContentPane, Button, SimpleTextarea, TextBox, ObjectStoreModel, HtmlTree, - util) { +function (declare, dom, style, topic, Memory, Observable, ConfirmDialog, + Dialog, ContentPane, Button, SimpleTextarea, TextBox, ObjectStoreModel, + HtmlTree, util) { var Reply = declare(ContentPane, { constructor : function () { @@ -207,4 +207,4 @@ function (declare, dom, style, topic, Memory, Observable, ConfirmDialog, Dialog, }); } }); -}); \ No newline at end of file +}); diff --git a/www/scripts/codecheckerviewer/ListOfRuns.js b/www/scripts/codecheckerviewer/ListOfRuns.js index 0cd1e56128..56090028d5 100644 --- a/www/scripts/codecheckerviewer/ListOfRuns.js +++ b/www/scripts/codecheckerviewer/ListOfRuns.js @@ -20,25 +20,6 @@ define([ function (declare, domConstruct, ItemFileWriteStore, topic, Dialog, Button, RadioButton, TextBox, BorderContainer, ContentPane, DataGrid, util) { - function prettifyDuration(seconds) { - var prettyDuration = "--------"; - - if (seconds >= 0) { - var durHours = Math.floor(seconds / 3600); - var durMins = Math.floor(seconds / 60) - durHours * 60; - var durSecs = seconds - durMins * 60 - durHours * 3600; - - var prettyDurHours = (durHours < 10 ? '0' : '') + durHours; - var prettyDurMins = (durMins < 10 ? '0' : '') + durMins; - var prettyDurSecs = (durSecs < 10 ? '0' : '') + durSecs; - - prettyDuration - = prettyDurHours + ':' + prettyDurMins + ':' + prettyDurSecs; - } - - return prettyDuration; - } - /** * This function helps to format a data grid cell with two radio buttons. * @param args {runData, listOfRunsGrid} - the value from the data store that @@ -188,7 +169,7 @@ function (declare, domConstruct, ItemFileWriteStore, topic, Dialog, Button, name : '' + runData.name + '', date : currItemDate[0] + ' ' + currItemDate[1], numberofbugs : runData.resultCount, - duration : prettifyDuration(runData.duration), + duration : util.prettifyDuration(runData.duration), runData : runData, checkcmd : 'Show', del : false, diff --git a/www/scripts/codecheckerviewer/codecheckerviewer.js b/www/scripts/codecheckerviewer/codecheckerviewer.js index 9831f8953c..0e03b4b97e 100644 --- a/www/scripts/codecheckerviewer/codecheckerviewer.js +++ b/www/scripts/codecheckerviewer/codecheckerviewer.js @@ -11,6 +11,7 @@ define([ 'dijit/Dialog', 'dijit/DropDownMenu', 'dijit/MenuItem', + 'dijit/form/Button', 'dijit/form/DropDownButton', 'dijit/layout/BorderContainer', 'dijit/layout/ContentPane', @@ -20,8 +21,8 @@ define([ 'codechecker/ListOfRuns', 'codechecker/util'], function (declare, topic, domConstruct, Dialog, DropDownMenu, MenuItem, - DropDownButton, BorderContainer, ContentPane, TabContainer, hashHelper, - ListOfBugs, ListOfRuns, util, filterHelper) { + Button, DropDownButton, BorderContainer, ContentPane, TabContainer, + hashHelper, ListOfBugs, ListOfRuns, util) { return function () { @@ -29,12 +30,19 @@ function (declare, topic, domConstruct, Dialog, DropDownMenu, MenuItem, CC_SERVICE = new codeCheckerDBAccess.codeCheckerDBAccessClient( new Thrift.Protocol(new Thrift.Transport("CodeCheckerService"))); - CC_AUTH_SERVICE = + + CC_OBJECTS = codeCheckerDBAccess; + + AUTH_SERVICE = new codeCheckerAuthentication.codeCheckerAuthenticationClient( new Thrift.TJSONProtocol( new Thrift.Transport("/Authentication"))); - CC_OBJECTS = codeCheckerDBAccess; + PROD_SERVICE = + new codeCheckerProductManagement.codeCheckerProductServiceClient( + new Thrift.Protocol(new Thrift.Transport("Products"))); + + PROD_OBJECTS = codeCheckerProductManagement; //----------------------------- Main layout ------------------------------// @@ -48,6 +56,9 @@ function (declare, topic, domConstruct, Dialog, DropDownMenu, MenuItem, //--- Logo ---// + var currentProduct = PROD_SERVICE.getCurrentProduct(); + document.title = currentProduct.displayedName + ' - CodeChecker'; + var logoContainer = domConstruct.create('div', { id : 'logo-container' }, headerPane.domNode); @@ -56,7 +67,7 @@ function (declare, topic, domConstruct, Dialog, DropDownMenu, MenuItem, var logoText = domConstruct.create('div', { id : 'logo-text', - innerHTML : 'CodeChecker' + innerHTML : 'CodeChecker - ' + currentProduct.displayedName }, logoContainer); var version = domConstruct.create('span', { @@ -64,7 +75,7 @@ function (declare, topic, domConstruct, Dialog, DropDownMenu, MenuItem, innerHTML : CC_SERVICE.getPackageVersion() }, logoText); - var user = CC_AUTH_SERVICE.getLoggedInUser(); + var user = AUTH_SERVICE.getLoggedInUser(); var loginUserSpan = null; if (user.length > 0) { loginUserSpan = domConstruct.create('span', { @@ -73,7 +84,7 @@ function (declare, topic, domConstruct, Dialog, DropDownMenu, MenuItem, }); } - //--- Menu button ---// + //--- Menu button ---// var credits = new Dialog({ title : 'Credits', @@ -116,6 +127,18 @@ function (declare, topic, domConstruct, Dialog, DropDownMenu, MenuItem, dropDown : menuItems }); + //--- Back button to product list ---// + + var productListButton = new Button({ + class : 'mainMenuButton', + label : 'Back to product list', + onClick : function () { + // Use explicit URL here, as '/' could redirect back to this product + // if there is only one product. + window.open('/products.html', '_self'); + } + }); + var headerMenu = domConstruct.create('div', { id : 'header-menu' }); @@ -123,8 +146,9 @@ function (declare, topic, domConstruct, Dialog, DropDownMenu, MenuItem, if (loginUserSpan != null) domConstruct.place(loginUserSpan, headerMenu); - domConstruct.place(menuButton.domNode, headerMenu); + domConstruct.place(productListButton.domNode, headerMenu); + domConstruct.place(menuButton.domNode, headerMenu); domConstruct.place(headerMenu, headerPane.domNode); diff --git a/www/scripts/codecheckerviewer/util.js b/www/scripts/codecheckerviewer/util.js index f854b54f41..68d4cba25a 100644 --- a/www/scripts/codecheckerviewer/util.js +++ b/www/scripts/codecheckerviewer/util.js @@ -63,6 +63,45 @@ function (locale, dom, style) { return '#' + '00000'.substring(0, 6 - c.length) + c; }, + /** + * This function creates a colour from a string, then blend it with the + * given other colour with the given ratio. + * + * @param blendColour a variable applicable to the constructor of + * dojo.Color. It can be a color name, a hex string, on an array of RGB. + */ + strToColorBlend : function (str, blendColour, ratio) { + if (ratio === undefined) { + ratio = 0.75; + } + + var baseColour = new dojo.Color(this.strToColor(str)); + return dojo.blendColors(baseColour, new dojo.Color(blendColour), ratio); + }, + + /** + * Converts the given number of seconds into a more human-readable + * 'hh:mm:ss' format. + */ + prettifyDuration: function (seconds) { + var prettyDuration = "--------"; + + if (seconds >= 0) { + var durHours = Math.floor(seconds / 3600); + var durMins = Math.floor(seconds / 60) - durHours * 60; + var durSecs = seconds - durMins * 60 - durHours * 3600; + + var prettyDurHours = (durHours < 10 ? '0' : '') + durHours; + var prettyDurMins = (durMins < 10 ? '0' : '') + durMins; + var prettyDurSecs = (durSecs < 10 ? '0' : '') + durSecs; + + prettyDuration + = prettyDurHours + ':' + prettyDurMins + ':' + prettyDurSecs; + } + + return prettyDuration; + }, + /** * Creates a human friendly relative time ago on the date. */ @@ -73,7 +112,7 @@ function (locale, dom, style) { hour = minute * 60, day = hour * 24, week = day * 7, - month = day * 30 + month = day * 30, year = day * 365; var fuzzy; diff --git a/www/scripts/productlist/ListOfProducts.js b/www/scripts/productlist/ListOfProducts.js new file mode 100644 index 0000000000..7f2ab652ed --- /dev/null +++ b/www/scripts/productlist/ListOfProducts.js @@ -0,0 +1,200 @@ +// ------------------------------------------------------------------------- +// The CodeChecker Infrastructure +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// ------------------------------------------------------------------------- + +define([ + 'dojo/_base/declare', + 'dojo/dom-construct', + 'dojo/data/ItemFileWriteStore', + 'dojo/topic', + 'dijit/form/Button', + 'dijit/form/TextBox', + 'dijit/layout/BorderContainer', + 'dijit/layout/ContentPane', + 'dojox/grid/DataGrid', + 'codechecker/util'], +function (declare, domConstruct, ItemFileWriteStore, topic, Button, + TextBox, BorderContainer, ContentPane, DataGrid, util) { + + var ListOfProductsGrid = declare(DataGrid, { + constructor : function () { + this.store = new ItemFileWriteStore({ + data : { identifier : 'endpoint', items : [] } + }); + + // TODO: Support access control for products and handle locks well. + // TODO: Support showing the last checkin's information for products. + this.structure = [ + { name : ' ', field : 'status', cellClasses : 'status', width : '20px', noresize : true }, + { name : ' ', field : 'icon', cellClasses : 'product-icon', width : '40px', noresize : true }, + { name : 'Name', field : 'name', cellClasses : 'product-name', width : '25%' }, + { name : 'Description', field : 'description', styles : 'text-align: left;', width : '70%' }/*, + { name : 'Last check date', field : 'date', styles : 'text-align: center;', width : '30%' }, + { name : 'Last check bugs', field : 'numberofbugs', styles : 'text-align: center;', width : '20%' }, + { name : 'Last check duration', field : 'duration', styles : 'text-align: center;' }*/ + ]; + + this.focused = true; + this.selectable = true; + this.keepSelection = true; + this.escapeHTMLInData = false; + }, + + postCreate : function () { + this.inherited(arguments); + this._populateProducts(); + }, + + onRowClick : function (evt) { + var item = this.getItem(evt.rowIndex); + + switch (evt.cell.field) { + case 'name': + if (item.connected[0] && item.accessible[0]) { + window.open('/' + item.endpoint[0], '_self'); + } + break; + } + }, + + getItemsWhere : function (func) { + var result = []; + + for (var i = 0; i < this.rowCount; ++i) { + var item = this.getItem(i); + if (func(item)) + result.push(item); + } + + return result; + }, + + _addProductData : function (item) { + var icon ='
' + + '' + + item.displayedName[0].toUpperCase() + + '
'; + + var name = item.displayedName; + var description = item.description ? item.description : ""; + var statusIcon = ''; + + if (!item.connected || !item.accessible) { + name = '' + + item.displayedName + ''; + + if (!item.connected) { + statusIcon = ''; + description = '' + + 'The database connection for this product could not be made!' + + '
' + description; + } else if (!item.accessible) { + statusIcon = ''; + description = '' + + 'You do not have access to this product!' + + '
' + description; + } + } else { + name = '' + item.displayedName + ''; + } + + this.store.newItem({ + status : statusIcon, + icon : icon, + endpoint : item.endpoint, + name : name, + description : description, + connected : item.connected, + accessible : item.accessible + }); + }, + + _populateProducts : function (productNameFilter) { + var that = this; + + PROD_SERVICE.getProducts(null, productNameFilter, function (productList) { + that.onLoaded(productList); + + productList.forEach(function (item) { + that._addProductData(item); + }); + }); + }, + + /** + * This function refreshes grid with available product data based on + * text name filter. + */ + refreshGrid : function (productNameFilter) { + var that = this; + + this.store.fetch({ + onComplete : function (products) { + products.forEach(function (products) { + that.store.deleteItem(products); + }); + that.store.save(); + } + }); + + PROD_SERVICE.getProducts(null, productNameFilter, function (productDataList) { + productDataList.forEach(function (item) { + that._addProductData(item); + }); + }); + }, + + onLoaded : function (productDataList) {} + }); + + var ProductFilter = declare(ContentPane, { + constructor : function () { + var that = this; + + this._productFilter = new TextBox({ + id : 'products-filter', + placeHolder : 'Search for products...', + onKeyUp : function (evt) { + clearTimeout(this.timer); + + var filter = this.get('value'); + this.timer = setTimeout(function () { + that.listOfProductsGrid.refreshGrid(filter); + }, 500); + } + }); + }, + + postCreate : function () { + this.addChild(this._productFilter); + } + }); + + return declare(BorderContainer, { + postCreate : function () { + var that = this; + + var filterPane = new ProductFilter({ + id : 'products-filter-container', + region : 'top' + }); + + var listOfProductsGrid = new ListOfProductsGrid({ + id : 'productGrid', + region : 'center', + onLoaded : that.onLoaded + }); + + filterPane.set('listOfProductsGrid', listOfProductsGrid); + + this.addChild(filterPane); + this.addChild(listOfProductsGrid); + }, + + onLoaded : function (productDataList) {} + }); +}); diff --git a/www/scripts/productlist/productlist.js b/www/scripts/productlist/productlist.js new file mode 100644 index 0000000000..0567f19262 --- /dev/null +++ b/www/scripts/productlist/productlist.js @@ -0,0 +1,103 @@ +// ------------------------------------------------------------------------- +// The CodeChecker Infrastructure +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// ------------------------------------------------------------------------- + +define([ + 'dojo/_base/declare', + 'dojo/topic', + 'dojo/dom-construct', + 'dijit/form/Button', + 'dijit/layout/BorderContainer', + 'dijit/layout/ContentPane', + 'products/ListOfProducts'], +function (declare, topic, domConstruct, Button, BorderContainer, + ContentPane, ListOfProducts) { + + return function () { + + //---------------------------- Global objects ----------------------------// + + PROD_SERVICE = + new codeCheckerProductManagement.codeCheckerProductServiceClient( + new Thrift.Protocol(new Thrift.Transport("Products"))); + + PROD_OBJECTS = codeCheckerProductManagement; + + AUTH_SERVICE = + new codeCheckerAuthentication.codeCheckerAuthenticationClient( + new Thrift.TJSONProtocol( + new Thrift.Transport("/Authentication"))); + + //----------------------------- Main layout ------------------------------// + + var layout = new BorderContainer({ id : 'mainLayout' }); + + var headerPane = new ContentPane({ id : 'headerPane', region : 'top' }); + layout.addChild(headerPane); + + var productsPane = new ContentPane({ region : 'center' }); + layout.addChild(productsPane); + + //--- Logo ---// + + var logoContainer = domConstruct.create('div', { + id : 'logo-container' + }, headerPane.domNode); + + var logo = domConstruct.create('span', { id : 'logo' }, logoContainer); + + var logoText = domConstruct.create('div', { + id : 'logo-text', + innerHTML : 'CodeChecker - Products on this server' + }, logoContainer); + + var version = domConstruct.create('span', { + id : 'logo-version', + innerHTML : PROD_SERVICE.getPackageVersion() + }, logoText); + + var user = AUTH_SERVICE.getLoggedInUser(); + var loginUserSpan = null; + if (user.length > 0) { + loginUserSpan = domConstruct.create('span', { + id: 'loggedin', + innerHTML: "Logged in as " + user + "." + }); + } + + //--- Admin button ---// + + // TODO: Show admin button only if superuser. + var menuButton = new Button({ + class : 'mainMenuButton', + label : 'Administration', + onClick : function () { + window.open('/Administration', '_self'); + } + }); + + var headerMenu = domConstruct.create('div', { + id : 'header-menu' + }); + + if (loginUserSpan != null) + domConstruct.place(loginUserSpan, headerMenu); + + domConstruct.place(menuButton.domNode, headerMenu); + + domConstruct.place(headerMenu, headerPane.domNode); + + //--- Center panel ---// + + var listOfProducts = new ListOfProducts(); + + productsPane.addChild(listOfProducts); + + //--- Init page ---// + + document.body.appendChild(layout.domNode); + layout.startup(); + }; +}); diff --git a/www/style/codecheckerviewer.css b/www/style/codecheckerviewer.css index 8eec9626c6..c6f7168a8f 100644 --- a/www/style/codecheckerviewer.css +++ b/www/style/codecheckerviewer.css @@ -47,7 +47,7 @@ html, body { #logo-version { font-family: 'Source Code Pro', monospace; font-size: 16pt; - padding-left: 12px; + padding-left: 16px; color: white; } diff --git a/www/style/productlist.css b/www/style/productlist.css new file mode 100644 index 0000000000..6c604ad9b5 --- /dev/null +++ b/www/style/productlist.css @@ -0,0 +1,74 @@ + +/* Do not use this stylesheet without codecheckerviewer.css! */ + +/*** Product grid ***/ + +#productGrid .dojoxGridCell { + border: 0; +} + +.dojoxGridCell.product-icon { + text-align: center; +} + +.dojoxGridCell.product-name { + text-align: left; + font-size: 12pt; +} + +.dojoxGridCell.product-name .product-error { + text-decoration: line-through; +} + +.dojoxGridCell .product-description-error { + font-style: italic; + font-family: 'Source Code Pro', monospace; + font-size: 12px; +} + +.dojoxGridCell .product-description-error.database { + color: #b32424; +} + +.dojoxGridCell .product-description-error.access { + color: #5b6169; +} + +div.product-avatar { + width: 40px; + height: 40px; + border-radius: 55%; + background-color: #eee; +} + +span.product-avatar { + font-size: 18px; + width: 40px; + height: 40px; + text-align: center; + vertical-align: middle; + color: black; + font-weight: 600; + font-family: Helvetica, Arial, sans-serif; + display: table-cell; +} + +.dojoxGridCell.status { + text-align: center; +} + +.customIcon.product-noaccess { + color: #5b6169; +} + +.customIcon.product-noaccess:before { + content: "\e014"; +} + +.customIcon.product-error { + color: #b32424; +} + +.customIcon.product-error:before { + content: "\e015"; +}