]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5646 Initial dashborad js app
authorStas Vilchik <vilchiks@gmail.com>
Tue, 23 Sep 2014 10:33:01 +0000 (16:33 +0600)
committerStas Vilchik <vilchiks@gmail.com>
Tue, 23 Sep 2014 11:11:10 +0000 (17:11 +0600)
21 files changed:
server/sonar-web/Gruntfile.coffee
server/sonar-web/src/main/es6/dashboard/app.js [new file with mode: 0644]
server/sonar-web/src/main/es6/dashboard/collections/widgets.js [new file with mode: 0644]
server/sonar-web/src/main/es6/dashboard/mockjax.js [new file with mode: 0644]
server/sonar-web/src/main/es6/dashboard/models/widget.js [new file with mode: 0644]
server/sonar-web/src/main/es6/dashboard/views/widget-view.js [new file with mode: 0644]
server/sonar-web/src/main/es6/dashboard/views/widgets-view.js [new file with mode: 0644]
server/sonar-web/src/main/hbs/dashboard/widget.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/dashboard/widgets.hbs [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/dashboard2_controller.rb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_configure_widget.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_header.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_definition.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_definitions.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_properties.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_title.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/configure.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/empty.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/index.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/no_dashboard.html.erb [new file with mode: 0644]

index 0c3624005f8c611fea70296423aadbecf9d60453..6f2c24bf29d0278555b6f0a846c6ebc31e638a9c 100644 (file)
@@ -264,6 +264,9 @@ module.exports = (grunt) ->
           '<%= pkg.assets %>js/templates/libraries.js': [
             '<%= pkg.sources %>hbs/libraries/**/*.hbs'
           ]
+          '<%= pkg.assets %>js/templates/dashboard.js': [
+            '<%= pkg.sources %>hbs/dashboard/**/*.hbs'
+          ]
 
 
     clean:
diff --git a/server/sonar-web/src/main/es6/dashboard/app.js b/server/sonar-web/src/main/es6/dashboard/app.js
new file mode 100644 (file)
index 0000000..7239062
--- /dev/null
@@ -0,0 +1,58 @@
+requirejs.config({
+  baseUrl: `${baseUrl}/js`,
+
+  paths: {
+    'backbone': 'third-party/backbone',
+    'backbone.marionette': 'third-party/backbone.marionette',
+    'handlebars': 'third-party/handlebars'
+  },
+
+  shim: {
+    'backbone.marionette': {
+      deps: ['backbone'],
+      exports: 'Marionette'
+    },
+    'backbone': {
+      exports: 'Backbone'
+    },
+    'handlebars': {
+      exports: 'Handlebars'
+    }
+  }
+});
+
+
+requirejs([
+  'backbone',
+  'backbone.marionette',
+  'dashboard/collections/widgets',
+  'dashboard/views/widgets-view',
+  'dashboard/mockjax',
+  'common/handlebars-extensions'
+], function (Backbone, Marionette, Widgets, WidgetsView) {
+
+  var App = new Marionette.Application(),
+      $ = jQuery;
+
+  App.addInitializer(function () {
+    this.widgetsView = new WidgetsView({
+      collection: this.widgets,
+      dashboard: this.dashboard,
+      el: $('#dashboard')
+    });
+    this.widgetsView.render();
+  });
+
+  var requestDetails = function () {
+    return $.get(`${baseUrl}/api/dashboards/details`, { did: window.did }, function (data) {
+      console.log(JSON.stringify(data));
+      App.dashboard = new Backbone.Model(_.omit(data, 'widgets'));
+      App.widgets = new Widgets(data.widgets);
+    });
+  };
+
+  $.when(requestDetails(), window.requestMessages()).done(function () {
+    App.start();
+  });
+
+});
diff --git a/server/sonar-web/src/main/es6/dashboard/collections/widgets.js b/server/sonar-web/src/main/es6/dashboard/collections/widgets.js
new file mode 100644 (file)
index 0000000..03826f3
--- /dev/null
@@ -0,0 +1,12 @@
+define([
+  'backbone',
+  'dashboard/models/widget'
+], function (
+    Backbone,
+    Widget) {
+
+  return Backbone.Collection.extend({
+    model: Widget
+  });
+
+});
diff --git a/server/sonar-web/src/main/es6/dashboard/mockjax.js b/server/sonar-web/src/main/es6/dashboard/mockjax.js
new file mode 100644 (file)
index 0000000..b3cce8f
--- /dev/null
@@ -0,0 +1,58 @@
+define(['third-party/jquery.mockjax'], function () {
+
+  jQuery.mockjaxSettings.contentType = 'text/json';
+  jQuery.mockjaxSettings.responseTime = 250;
+
+  jQuery.mockjax({
+    url: `${baseUrl}/api/dashboards/details`,
+    responseText: JSON.stringify({
+      name: 'Helicopter View',
+      description: '',
+      shared: true,
+      layout: '50%-50%',
+
+      canManageDashboards: true,
+      canManageWidgets: true,
+
+      widgets: [
+        {
+          key: 'measure_filter_list',
+          props: [
+            {
+              key: 'filter',
+              value: '48'
+            }
+          ],
+          layout: {
+            column: 1,
+            row: 1
+          }
+        },
+        {
+          key: 'my_reviews',
+          props: [],
+          layout: {
+            column: 2,
+            row: 1
+          }
+        }
+      ]
+    })
+  });
+
+  jQuery.mockjax({
+    url: `${baseUrl}/api/dashboards/available_widgets`,
+    responseText: JSON.stringify({
+      widgets: [
+        {
+          key: '',
+          name: '',
+          description: '',
+          category: '',
+          props: []
+        }
+      ]
+    })
+  });
+
+});
diff --git a/server/sonar-web/src/main/es6/dashboard/models/widget.js b/server/sonar-web/src/main/es6/dashboard/models/widget.js
new file mode 100644 (file)
index 0000000..79300be
--- /dev/null
@@ -0,0 +1,7 @@
+define(['backbone'], function (Backbone) {
+
+  return Backbone.Model.extend({
+    idAttribute: 'key'
+  });
+
+});
diff --git a/server/sonar-web/src/main/es6/dashboard/views/widget-view.js b/server/sonar-web/src/main/es6/dashboard/views/widget-view.js
new file mode 100644 (file)
index 0000000..0507d59
--- /dev/null
@@ -0,0 +1,45 @@
+define([
+  'backbone.marionette',
+  'templates/dashboard'
+], function (Marionette, Templates) {
+
+  var $ = jQuery;
+
+
+  class WidgetView extends Marionette.ItemView {
+
+    initialize() {
+      this.requestContent();
+    }
+
+    requestContent() {
+      var props = this.getWidgetProps();
+      $.get(`${baseUrl}/widget/show?id=${this.model.id}&${props}`, (html) => {
+        this.model.set('html', html);
+        this.render();
+      });
+    }
+
+    getWidgetProps() {
+      var props = this.model.get('props')
+          .map(function (prop) {
+            return `${prop.key}=${encodeURIComponent(prop.value)}`;
+          })
+          .join('&');
+      return props;
+    }
+
+    serializeData() {
+      var props = this.getWidgetProps();
+      return _.extend(super.serializeData(), {
+        url: `${baseUrl}/widget?id=${this.model.id}&${props}`
+      });
+    }
+
+  }
+
+  WidgetView.prototype.template = Templates['widget']
+
+  return WidgetView;
+
+});
diff --git a/server/sonar-web/src/main/es6/dashboard/views/widgets-view.js b/server/sonar-web/src/main/es6/dashboard/views/widgets-view.js
new file mode 100644 (file)
index 0000000..ce8692d
--- /dev/null
@@ -0,0 +1,31 @@
+define([
+  'backbone.marionette',
+  'templates/dashboard',
+  'dashboard/views/widget-view'
+], function (Marionette, Templates, WidgetView) {
+
+  class WidgetsView extends Marionette.CompositeView {
+
+    appendHtml(compositeView, itemView) {
+      var layout = itemView.model.get('layout'),
+          column = layout.column - 1;
+      var $container = this.getItemViewContainer(compositeView);
+      $container.eq(column).append(itemView.el);
+    }
+
+    serializeData() {
+      return _.extend(super.serializeData(), {
+        dashboard: this.options.dashboard.toJSON(),
+        manageDashboardsUrl: `${baseUrl}/dashboards`
+      });
+    }
+
+  }
+
+  WidgetsView.prototype.template = Templates['widgets'];
+  WidgetsView.prototype.itemView = WidgetView;
+  WidgetsView.prototype.itemViewContainer = '.dashboard-column';
+
+  return WidgetsView;
+
+});
diff --git a/server/sonar-web/src/main/hbs/dashboard/widget.hbs b/server/sonar-web/src/main/hbs/dashboard/widget.hbs
new file mode 100644 (file)
index 0000000..91dcae7
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="block">
+  <div class="widget">
+    {{#if html}}
+      {{{html}}}
+    {{else}}
+      <i class="spinner spinner-margin"></i>
+    {{/if}}
+  </div>
+</div>
diff --git a/server/sonar-web/src/main/hbs/dashboard/widgets.hbs b/server/sonar-web/src/main/hbs/dashboard/widgets.hbs
new file mode 100644 (file)
index 0000000..7dd17d1
--- /dev/null
@@ -0,0 +1,19 @@
+<div class="line-block">
+  <div class="operations noprint button-group">
+    <button>{{t 'dashboard.configure_widgets'}}</button>
+    <a class="button" href="{{manageDashboardsUrl}}">{{t 'dashboard.manage_dashboards'}}</a>
+  </div>
+</div>
+
+{{#eq dashboard.layout '50%-50%'}}
+  <div class="dashboard-column-wrapper" style="width: 50%;margin: 0 -1px 0 0;">
+    <div class="dashboard-column" style="margin: 0 5px 0 0;">
+
+    </div>
+  </div>
+  <div class="dashboard-column-wrapper" style="width: 50%;margin: 0 -1px 0 0;">
+    <div class="dashboard-column" style="margin: 0 0 0 5px;">
+
+    </div>
+  </div>
+{{/eq}}
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/dashboard2_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/dashboard2_controller.rb
new file mode 100644 (file)
index 0000000..73a3a5e
--- /dev/null
@@ -0,0 +1,227 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube 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 3 of the License, or (at your option) any later version.
+#
+# SonarQube 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 program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+class Dashboard2Controller < ApplicationController
+
+  SECTION=Navigation::SECTION_RESOURCE
+
+  before_filter :login_required, :except => [:index]
+
+  def index
+    load_resource()
+
+    if !@resource || @resource.display_dashboard?
+      load_dashboard()
+      load_authorized_widget_definitions()
+    else
+      # display the layout of the parent without the sidebar, usually the directory, but display the file viewers
+      @hide_sidebar = true
+      @file = @resource
+      @project = @snapshot.parent.project
+      @metric=params[:metric]
+      render :action => 'no_dashboard'
+    end
+  end
+
+  def configure
+    load_resource()
+    load_dashboard()
+
+    @category=params[:category]
+    load_widget_definitions(@category)
+  end
+
+  def set_layout
+    verify_post_request
+    dashboard=Dashboard.find(params[:did])
+    if dashboard.editable_by?(current_user)
+      dashboard.column_layout=params[:layout]
+      dashboard.save!
+      columns=dashboard.column_layout.split('-')
+      dashboard.widgets.find(:all, :conditions => ["column_index > ?", columns.size()]).each do |widget|
+        widget.column_index=columns.size()
+        widget.save
+      end
+    end
+    redirect_to :action => 'configure', :did => dashboard.id, :id => params[:id]
+  end
+
+  def set_dashboard
+    verify_post_request
+    load_dashboard()
+
+    dashboardstate=params[:dashboardstate]
+
+    columns=dashboardstate.split(";")
+    all_ids=[]
+    columns.each_with_index do |col, index|
+      ids=col.split(",")
+      ids.each_with_index do |id, order|
+        widget=@dashboard.widgets.to_a.find { |i| i.id==id.to_i() }
+        if widget
+          widget.column_index=index+1
+          widget.row_index=order+1
+          widget.save!
+          all_ids<<widget.id
+        end
+      end
+    end
+    @dashboard.widgets.reject { |w| all_ids.include?(w.id) }.each do |w|
+      w.destroy
+    end
+    render :json => {:status => 'ok'}
+  end
+
+  def add_widget
+    verify_post_request
+    dashboard=Dashboard.find(params[:did])
+    widget_id=nil
+    if dashboard.editable_by?(current_user)
+      definition=java_facade.getWidget(params[:widget])
+      if definition
+        first_column_widgets=dashboard.widgets.select { |w| w.column_index==1 }.sort_by { |w| w.row_index }
+        new_widget=dashboard.widgets.create(:widget_key => definition.getId(),
+                                            :name => definition.getTitle(),
+                                            :column_index => 1,
+                                            :row_index => 1,
+                                            :configured => !(definition.hasRequiredProperties() || (dashboard.global && !definition.isGlobal)))
+        widget_id=new_widget.id
+        first_column_widgets.each_with_index do |w, index|
+          w.row_index=index+2
+          w.save
+        end
+      end
+    end
+    redirect_to :action => 'configure', :did => dashboard.id, :id => params[:id], :highlight => widget_id, :category => params[:category]
+  end
+
+  def save_widget
+    verify_post_request
+    widget=Widget.find(params[:wid])
+    #TODO check owner of dashboard
+    Widget.transaction do
+      widget.properties.clear
+      widget.java_definition.getWidgetProperties().each do |java_property|
+        value=params[java_property.key()] || java_property.defaultValue()
+        if value && !value.empty?
+          prop = widget.properties.build(:kee => java_property.key, :text_value => value)
+          prop.save!
+        end
+      end
+      widget.resource_id=Project.by_key(params[:resource_id]).id if params[:resource_id].present?
+      widget.configured=true
+      widget.save!
+      render :update do |page|
+        page.redirect_to(url_for(:action => 'configure', :did => widget.dashboard_id, :id => params[:id]))
+      end
+    end
+  end
+
+  def widget_definitions
+    @category=params[:category]
+    load_resource()
+    load_dashboard()
+    load_widget_definitions(@category)
+    render :partial => 'widget_definitions', :locals => {:category => @category}
+  end
+
+  private
+
+  def load_dashboard
+    active=nil
+    @dashboard=nil
+
+    if logged_in?
+      if params[:did]
+        @dashboard=Dashboard.first(:conditions => ['id=? AND user_id=?', params[:did].to_i, current_user.id])
+      elsif params[:name]
+        @dashboard=Dashboard.first(:conditions => ['name=? AND user_id=?', params[:name], current_user.id])
+      elsif params[:id]
+        active=ActiveDashboard.user_dashboards(current_user, false).first
+      else
+        active=ActiveDashboard.user_dashboards(current_user, true).first
+      end
+    end
+
+    unless active or @dashboard
+      # anonymous or not found in user dashboards
+      if params[:did]
+        @dashboard=Dashboard.first(:conditions => ['id=? AND shared=?', params[:did].to_i, true])
+      elsif params[:name]
+        @dashboard=Dashboard.first(:conditions => ['name=? AND shared=?', params[:name], true])
+      elsif params[:id]
+        active=ActiveDashboard.user_dashboards(nil, false).first
+      else
+        active=ActiveDashboard.user_dashboards(nil, true).first
+      end
+    end
+
+    unless @dashboard
+      @dashboard=(active && active.dashboard)
+    end
+
+    not_found('dashboard') unless @dashboard
+
+    @dashboard_configuration=Api::DashboardConfiguration.new(@dashboard, :period_index => params[:period], :snapshot => @snapshot) if @dashboard && @snapshot
+  end
+
+  def load_resource
+    if params[:id]
+      @resource=Project.by_key(params[:id])
+      return project_not_found unless @resource
+      @resource=@resource.permanent_resource
+
+      @snapshot=@resource.last_snapshot
+      return project_not_analyzed unless @snapshot
+
+      access_denied unless has_role?(:user, @resource)
+
+      @project=@resource # for backward compatibility with old widgets
+    end
+  end
+
+  def project_not_found
+    flash[:error] = message('dashboard.project_not_found')
+    redirect_to :action => :index
+  end
+
+  def project_not_analyzed
+    render :action => 'empty'
+  end
+
+  def load_authorized_widget_definitions
+    @authorized_widget_definitions=java_facade.getWidgets().select do |widget|
+      roles = widget.getUserRoles()
+      roles.empty? || roles.any? { |role| (role=='user') || (role=='viewer') || has_role?(role, @resource) }
+    end
+  end
+
+  def load_widget_definitions(filter_on_category)
+    @widget_definitions=java_facade.getWidgets().to_a.sort {|w1,w2| widgetL10nName(w1) <=> widgetL10nName(w2)}
+
+    @widget_categories=@widget_definitions.map(&:getWidgetCategories).to_a.flatten.uniq.sort
+    unless filter_on_category.blank?
+      @widget_definitions=@widget_definitions.select { |definition| definition.getWidgetCategories().to_a.include?(filter_on_category) }
+    end
+  end
+
+  def widgetL10nName(widget)
+    Api::Utils.message('widget.' + widget.id + '.name')
+  end
+end
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_configure_widget.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_configure_widget.html.erb
new file mode 100644 (file)
index 0000000..e2ceea9
--- /dev/null
@@ -0,0 +1,55 @@
+<% 
+  switch_to_widget_resource(widget)
+  widget_body=widget_body(widget) 
+  default_layout=(widget.layout=='DEFAULT')
+%>
+
+<div class="widget-header">
+  <div class="widget-actions">
+    <% if widget.java_definition.isEditable() || (!widget.java_definition.global && @dashboard.global) %>
+      <a class="link-action" onclick="portal.editWidget(<%= widget.id -%>);return false;"><%= message('edit') -%></a>
+    <% end %>
+    <a class="link-action" onclick="portal.deleteWidget(this);return false;"><%= message('delete') -%></a>
+  </div>
+  <div class="widget-handle">
+    <%= h message('widget.' + widget.java_definition.getId() + '.name', :default => widget.java_definition.getTitle()) -%>
+  </div>
+</div>
+
+<div class="widget_props" id="widget_props_<%= widget.id -%>" style="<%= 'display:none' if widget.configured -%>">
+  <%= render :partial => 'widget_properties', :locals => {:widget => widget} -%>
+</div>
+
+<%= render :partial => 'widget_title', :locals => {:widget => widget} -%>
+<div id="widget_<%= widget.id -%>" class="configure_widget <%= h widget.java_definition.getId() -%>" style="height:100%;<%= 'display:none;' if !widget.configured -%>">
+  <div class="transparent"></div>
+  <% if !widget_body %>
+    <div class="block">
+      <% if default_layout %>
+      <div class="widget">   
+        <span class="empty_widget"><%= message('widget.error_occurred_please_read_logs', :params => [widget.key]) -%></span>
+        <div class="clear"></div>
+      </div>
+      <% else %>
+        <span class="empty_widget"><%= message('widget.error_occurred_please_read_logs', :params => [widget.key]) -%></span>
+      <% end %>
+    </div>
+  <% elsif widget_body.include? '<' %>
+    <%
+       
+       if default_layout
+    %>
+      <div class="widget">
+    <% end %>
+    <%= widget_body -%>
+    <% if default_layout %>
+      <div class="clear"></div>
+      </div>
+    <% end %>
+  <% else %>
+    <div class="widget"><p><%= message('no_data') -%></p></div>
+  <% end %>
+  <div style="clear: both;"></div>
+</div>
+
+<% restore_global_resource %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_header.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_header.html.erb
new file mode 100644 (file)
index 0000000..900075c
--- /dev/null
@@ -0,0 +1,34 @@
+<% if logged_in? || @snapshot %>
+<div class="line-block">
+
+  <% if logged_in? %>
+    <div class="operations noprint button-group">
+      <% if back %>
+        <%= link_to message('dashboard.back_to_dashboard'), dashboard_action(:index), :class => 'button' -%>
+      <% else %>
+        <% if @dashboard.editable_by?(current_user) %>
+          <%= link_to message('dashboard.configure_widgets'), dashboard_action(:configure), :class => 'button' -%>
+        <% end %>
+      <% end %>
+      <%= link_to message('dashboard.manage_dashboards'), {:controller => :dashboards, :action => :index, :resource => (@resource.id if @resource) }, :class => 'button' -%>
+    </div>
+  <% end %>
+
+  <% if @snapshot %>
+    <div id="snapshot_title" class="page_title">
+      <h4>
+        <%= "Version #{@snapshot.version} - " if @snapshot.version.present? -%><%= l @snapshot.created_at -%>
+        <% if @snapshot.project_snapshot.periods? %>
+          <% period_options = period_select_option_tags(@snapshot, 'small') %>
+          <% if period_options %>
+            <form method="GET" action="<%= url_for :controller => :dashboard, :action => :index, :id => @resource.id -%>" style="display: inline" class="spacer-left">
+              <input type="hidden" name="did" value="<%= @dashboard.id -%>"/>
+              <%= dropdown_tag 'period', period_options, {:width => '250px'}, {:id => 'select-comparison', :onchange => 'submit()'} -%>
+            </form>
+          <% end %>
+        <% end %>
+      </h4>
+    </div>
+  <% end %>
+</div>
+<% end %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget.html.erb
new file mode 100644 (file)
index 0000000..b706fd1
--- /dev/null
@@ -0,0 +1,48 @@
+<% 
+  switch_to_widget_resource(widget)
+  widget_body=widget_body(widget)
+  default_layout=(widget.java_definition.getWidgetLayout().name()=='DEFAULT')
+%>
+
+<% if !widget.configured %>
+<div class="block">
+  <div class="widget">
+    <span class="empty_widget">
+      <a class="note" href="<%= url_for(dashboard_action(:configure)) -%>"><%= message('dashboard.please_configure_the_widget_x', :params => widget.java_definition.getTitle()) -%></a>
+    </span>
+    <div class="clear"></div>
+  </div>
+</div>
+
+<% elsif !widget_body %>
+  <div class="block">
+   <% if default_layout %>
+     <div class="widget">   
+       <span class="empty_widget"><%= message('widget.error_occurred_please_read_logs', :params => [widget.key]) -%></span>
+       <div class="clear"></div>
+     </div>
+   <% else %>
+     <span class="empty_widget"><%= message('widget.error_occurred_please_read_logs', :params => [widget.key]) -%></span>
+   <% end %>
+  </div>
+
+<% elsif widget_body.include?('<') %>
+  <div class="block" id="block_<%= widget.id -%>">
+    <%= render :partial => 'widget_title', :locals => {:widget => widget} -%>
+
+    <div class="<%= h widget.key -%>" style="height:100%;">
+      <% if default_layout %>
+        <div class="widget">
+          <%= widget_body -%>
+          <div class="clear"></div>
+        </div>
+      <% else %>
+        <%= widget_body -%>
+      <% end %>
+
+      <div style="clear: both;"></div>
+    </div>
+  </div>
+<% end %>
+
+<% restore_global_resource %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_definition.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_definition.html.erb
new file mode 100644 (file)
index 0000000..c0ab986
--- /dev/null
@@ -0,0 +1,10 @@
+  <div class="widget_def" id="def_<%= definition.id.tr('_', '') -%>">
+    <p><b><%= h message("widget.#{definition.id}.name", :default => definition.title) -%></b></p>
+
+    <p><%= h message("widget.#{definition.id}.description", :default => definition.description) -%></p>
+
+    <% form_tag dashboard_action(:add_widget, :widget => definition.id) do -%>
+      <input type="hidden" name="category" value="<%= category -%>">
+      <input type="submit" value="<%= message('dashboard.add_widget') -%>" id="add-widget-<%= u(definition.id) -%>">
+    <% end -%>
+  </div>
\ No newline at end of file
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_definitions.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_definitions.html.erb
new file mode 100644 (file)
index 0000000..64ed279
--- /dev/null
@@ -0,0 +1,27 @@
+<table class="width100">
+  <tr style="line-height: 20px;text-align: right">
+    <td valign="top">
+      <ul class="horizontal widget_categs" id="widget-filters">
+        <li>Category:</li>
+        <li class="<%= 'selected' if category.blank? -%>">
+          <a href="#" onClick="return filterWidgetsByCategory('')" id="widget-filter-none"><%= message('any') -%></a>
+        </li>
+        <% @widget_categories.each do |c| %>
+          <li class="<%= 'selected' if category==c -%>">
+            <a href="#" onClick="return filterWidgetsByCategory('<%= escape_javascript(c) -%>')" id="widget-filter-<%= u(c) -%>"><%= h(c) -%></a>
+          </li>
+        <% end %>
+      </ul>
+      <%= image_tag 'loading.gif', :style => 'vertical-align: top; display: none', :id => 'filter-widgets-loading' -%>
+    </td>
+    <td nowrap valign="top">
+      Search:
+      <input id="filter-widget-box" name="filter-widget-box" size="10" onKeyUp="filtersWidgetsByContent(this.value);" type="text"/>
+    </td>
+  </tr>
+</table>
+
+<% @widget_definitions.each do |definition| %>
+  <%= render :partial => 'widget_definition', :locals => {:definition => definition, :dashboard => @dashboard, :resource => @resource, :category => category} %>
+<% end %>
+
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_properties.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_properties.html.erb
new file mode 100644 (file)
index 0000000..ae41953
--- /dev/null
@@ -0,0 +1,67 @@
+<form onsubmit="$j.ajax({
+                url:   '<%= ApplicationController.root_context-%>/dashboard/save_widget?wid=<%=widget.id-%>&id=<%= params[:id] -%>',
+                type:  'post',
+                data:  $j(this).serialize(),
+                error: function(request)
+                        {
+                                $j('#error<%=widget.id-%>').text(request.responseText);
+                                $j('#error<%=widget.id-%>').show();
+                        }
+                });
+           return false;"
+         method="post"
+         action="<%= ApplicationController.root_context-%>/dashboard/save_widget?wid=<%=widget.id-%>">
+  <div id="error<%= widget.id -%>" class="error" style="display: none"></div>
+  <table class="table width100">
+    <tbody>
+
+    <% if !widget.java_definition.global && @dashboard.global %>
+      <tr>
+        <td class="form-key-cell"><%= message('widget.resource_id') %> <em class="mandatory">*</em></td>
+        <td class="form-val-cell" id="row_resource">
+          <%= resource_select_tag 'resource_id', {
+              :resource_type_property => 'supportsGlobalDashboards',
+              :selected_resource => widget.resource,
+              :width => '250px',
+              :html_id => "widget-#{widget.id}-select-prj-#{widget.key.parameterize}",
+              :html_class => "widget-select-prj-#{widget.key.parameterize}"
+          } -%>
+        </td>
+      </tr>
+    <% end %>
+
+    <% widget.java_definition.getWidgetProperties().each do |property_def| %>
+      <tr>
+        <td class="form-key-cell"><%= message("widget." + widget.key + ".property." + property_def.key() + ".name", :default => property_def.key()) -%><%= " *" unless property_def.optional() -%></td>
+        <td class="form-val-cell" id="row_<%= property_def.key() -%>">
+          <%= property_value_field(property_def, widget.property_text_value(property_def.key()), widget) -%>
+          <div class="form-val-note">
+            <%
+               # Old key used for retro-compatibility
+               property_description = message("widget." + widget.key + ".param." + property_def.key(), :default => '')
+               property_description = message("widget." + widget.key + ".property." + property_def.key() + ".desc", :default => property_def.description()) unless property_description != ''
+            -%>
+
+            <% unless property_description.blank? -%>
+              <div><%= property_description -%></div>
+            <% end %>
+            <% if !property_def.defaultValue.blank? || property_def.type.name == PropertyType::TYPE_BOOLEAN -%>
+              <div><%= message('default') %>: <%= h(default_value property_def) -%></div>
+            <% end -%>
+          </div>
+        </td>
+      </tr>
+    <% end %>
+
+    <tr>
+      <td colspan="2">
+        <%= submit_tag message('save'), :id => "widget-#{widget.id}-save-#{widget.key.parameterize}", :class => "widget-save-#{widget.key.parameterize}" -%>
+        <% if widget.configured %>
+          <a href="#" onClick="portal.cancelEditWidget(<%= widget.id -%>);return false;"><%= message('cancel') -%></a>
+        <% end %>
+      </td>
+    </tr>
+    </tbody>
+  </table>
+  <%= hidden_field_tag "widgetid", "", :class => "widgetid" %>
+</form>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_title.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/_widget_title.html.erb
new file mode 100644 (file)
index 0000000..0c9fb7a
--- /dev/null
@@ -0,0 +1,3 @@
+<% unless widget_title(widget).blank? %>
+  <div class="widget-title" id="widget_title_<%= widget.id -%>"><%= widget_title(widget) -%></div>
+<% end %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/configure.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/configure.html.erb
new file mode 100644 (file)
index 0000000..30c43cc
--- /dev/null
@@ -0,0 +1,105 @@
+<div id="dashboard">
+  <%= render :partial => 'header', :locals => {:back => true} %>
+
+  <div id="configure">
+    <div id="widget_defs">
+      <%= render :partial => 'widget_definitions', :locals => {:category => @category} -%>
+    </div>
+
+    <div id="edit-layout">
+      <% {'100%', 'layout100.png',
+          '50%-50%', 'layout5050.png',
+          '30%-70%', 'layout3070.png',
+          '70%-30%', 'layout7030.png',
+          '33%-33%-33%', 'layout333333.png'
+         }.each_pair do |layout, picto| %>
+        <div class="select-layout <%= 'selected' if @dashboard.layout==layout -%>">
+          <%= link_to image_tag(picto), dashboard_action(:set_layout, :layout => layout), :method => :post, :title => layout %>
+        </div>
+      <% end %>
+    </div>
+  </div>
+
+  <%
+     columns=@dashboard.column_layout.split('-')
+     for index in 1..columns.size()
+  %>
+    <div class="dashboard-column-wrapper" style="width: <%= columns[index-1] -%>;margin: 0 -1px 0 0;">
+      <div class="dashboard-column" id="dashboard-column-<%= index -%>" style="margin: 0 <%= index<columns.size() ? "5px" : "0px" -%> 0 <%= index>1 ? "5px" : "0px" -%>;">
+        <%
+           @dashboard.widgets.select { |widget| widget.column_index==index && widget.java_definition }.sort_by { |widget| widget.row_index }.each do |widget|
+        %>
+          <div class="block" id="block_<%= widget.id -%>">
+            <%= render :partial => 'configure_widget', :locals => {:widget => widget} %>
+          </div>
+        <%
+           end
+        %>
+        <div class="column-handle" style="display: none"></div>
+      </div>
+    </div>
+  <% end %>
+</div>
+
+<script type="text/javascript">
+  <!--
+  var options = {
+    editorEnabled: true,
+    column: 'dashboard-column',
+    columnHandle: 'column-handle',
+    block: 'block',
+    hoverClass: 'block-hover',
+    dashboardState: 'dashboardstate',
+    highlightDuration: 2000,
+    highlightStartColor: '#cae3f2',
+    highlightEndColor: '#ffffff',
+    saveUrl: '<%= url_for dashboard_action(:set_dashboard) -%>'
+  };
+  var portal;
+  function init_dashboard() {
+    portal = new Portal(options);
+  <% if params[:highlight] %>
+    portal.highlightWidget(<%= escape_javascript(params[:highlight]) -%>);
+  <% end %>
+  }
+  $j(document).ready(function(){init_dashboard();});
+
+  function filterWidgetsByCategory(category) {
+      $j('#filter-widgets-loading').show();
+      $j.ajax ({    url: '<%= add_category_to_url(url_for dashboard_action(:widget_definitions))  -%>' + encodeURIComponent(category),
+                    success: function(responseHTML){$j('#widget_defs').html(responseHTML);},
+                    error:function(error){alert('error');}
+              });
+      return false;
+  }
+
+  var widgetContents = [
+    <%
+      number_of_widgets = @widget_definitions.size()
+      @widget_definitions.each_with_index do |definition, index|
+        widget_id = "def_#{definition.id.tr('_', '')}"
+        widget_title = message("widget.#{definition.id}.name", :default => definition.title).downcase
+        widget_description = message("widget.#{definition.id}.description", :default => definition.description).downcase
+    %>
+    { id:"<%= widget_id -%>", c:"<%= widget_title.gsub(/\r\n?/, " ") -%> <%= widget_description.gsub(/\r\n?/, " ") -%>" } <%= "," unless index==number_of_widgets-1 -%>
+    <% end %>
+  ];
+
+  function filtersWidgetsByContent(text) {
+    var userInput = text.toLowerCase();
+    widgetContents.forEach(function (widget) {
+      var element = $j('#'+widget.id);
+      if (element != null) {
+        if (widget.c.indexOf(userInput) != -1) {
+          element.show();
+        } else {
+          element.hide();
+        }
+      }
+    });
+    return false;
+  }
+  //-->
+
+  $j('#filter-widget-box').focus();
+</script>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/empty.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/empty.html.erb
new file mode 100644 (file)
index 0000000..a297b76
--- /dev/null
@@ -0,0 +1,3 @@
+<% if @resource and !@snapshot %>
+<p class="warning marginbottom10"><%= message('provisioning.no_analysis') -%></p>
+<% end %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/index.html.erb
new file mode 100644 (file)
index 0000000..8dd759d
--- /dev/null
@@ -0,0 +1,12 @@
+<% content_for :script do %>
+  <script data-main="<%= ApplicationController.root_context -%>/js/dashboard/app" src="<%= ApplicationController.root_context -%>/js/require.js"></script>
+<% end %>
+
+<div id="dashboard">
+  <i class="spinner"></i>
+</div>
+
+<script>
+  window.did = '<%= @dashboard.id -%>';
+  jQuery('html').addClass('dashboard-page');
+</script>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/no_dashboard.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard2/no_dashboard.html.erb
new file mode 100644 (file)
index 0000000..8802eb1
--- /dev/null
@@ -0,0 +1,12 @@
+<% content_for :script do %>
+  <script data-main="<%= ApplicationController.root_context -%>/js/dashboard/file-app" src="<%= ApplicationController.root_context -%>/js/require.js"></script>
+<% end %>
+
+<div class="marginbottom10"></div>
+<div id="accordion-panel"></div>
+
+<script type="text/javascript">
+  window.fileKey = '<%= @file.key -%>';
+  window.metric = '<%= @metric -%>';
+  document.getElementById('crumbs').remove();
+</script>