Commit eafff053 authored by Leigh Stoller's avatar Leigh Stoller

Changes for Keith to develop the new profile picker:

1. Instead of a plain list of profiles, generate a more detailed list that
   includes last used and usage counts and project name and favorite flag,
   so that the new picker can be sorted/grouped.

   This list is *ordered* by most recent usage (if a real user), or most
   popular (if a guest). 

2. Move the modal from quickvm_sup to the template, and generate the
   current list from the new json info.

3. Add new table apt_profile_favorites to record favorite profiles for
   users.

4. Add new ajax calls for above, MarkFavorite and ClearFavorite that take a
   single argument, the uuid of the profile. There is no UI for this, Keith
   is going to add that.
parent e52e86a1
......@@ -505,7 +505,7 @@ sub Delete($$)
if (!defined($purge));
DBQueryWarn("lock tables apt_profiles write, apt_profile_versions write, ".
" web_tasks write")
" web_tasks write, apt_profile_favorites write")
or return -1;
if ($purge) {
......@@ -527,6 +527,9 @@ sub Delete($$)
" web_tasks.object_uuid=apt_profile_versions.uuid ".
"where apt_profile_versions.profileid='$profileid'");
}
DBQueryWarn("delete from apt_profile_favorites ".
"where profileid='$profileid'")
or goto bad;
DBQueryWarn("delete from apt_profiles where profileid='$profileid'")
or goto bad;
......
......@@ -656,6 +656,8 @@ sub Delete($)
or return -1;
DBQueryWarn("delete from user_stats where uid_idx='$uid_idx'")
or return -1;
DBQueryWarn("delete from apt_profile_favorites where uid_idx='$uid_idx'")
or return -1;
DBQueryWarn("delete from users where uid_idx='$uid_idx'")
or return -1;
......
......@@ -197,7 +197,8 @@ CREATE TABLE `apt_instance_history` (
`rspec` mediumtext,
`params` mediumtext,
`manifest` mediumtext,
PRIMARY KEY (`uuid`)
PRIMARY KEY (`uuid`),
KEY `profile_id` (`profile_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
......@@ -247,6 +248,19 @@ CREATE TABLE `apt_instances` (
-- Table structure for table `apt_profile_versions`
--
DROP TABLE IF EXISTS `apt_profile_favorites`;
CREATE TABLE `apt_profile_favorites` (
`uid` varchar(8) NOT NULL default '',
`uid_idx` mediumint(8) unsigned NOT NULL default '0',
`profileid` int(10) unsigned NOT NULL default '0',
`marked` datetime default NULL,
PRIMARY KEY (`uid_idx`,`profileid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `apt_profile_versions`
--
DROP TABLE IF EXISTS `apt_profile_versions`;
CREATE TABLE `apt_profile_versions` (
`name` varchar(64) NOT NULL default '',
......
use strict;
use libdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBTableExists("apt_profile_favorites")) {
DBQueryFatal("CREATE TABLE `apt_profile_favorites` ( ".
" `uid` varchar(8) NOT NULL default '', ".
" `uid_idx` mediumint(8) unsigned NOT NULL default '0', ".
" `profileid` int(10) unsigned NOT NULL default '0', ".
" `marked` datetime default NULL, ".
" PRIMARY KEY (`uid_idx`,`profileid`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1");
}
if (!DBKeyExists("apt_instance_history", "profile_id")) {
DBQueryFatal("alter table apt_instance_history add ".
" KEY `profile_id` (`profile_id`)");
}
return 0;
}
# Local Variables:
# mode:perl
# End:
......@@ -56,6 +56,7 @@ function Do_GetProfile()
$amdefault = $profile->BestAggregate();
}
$ispp = ($profile->isParameterized() ? 1 : 0);
list ($lastused, $count) = $profile->UsageInfo($this_user);
#
# Knowing the UUID means the user can instantiate it,
......@@ -63,6 +64,11 @@ function Do_GetProfile()
#
SPITAJAX_RESPONSE(array('rspec' => $profile->rspec(),
'name' => $profile->name(),
'version' => $profile->version(),
'lastused' => $lastused,
'usecount' => $count,
'creator' => $profile->creator(),
'created' => $profile->created(),
'ispprofile' => $ispp,
'amdefault' => $amdefault));
}
......@@ -840,6 +846,52 @@ function Do_Submit()
return;
}
#
# Mark (or clear) a profile as a favorite.
#
function Do_MarkFavorite()
{
global $this_user;
global $ajax_args;
if (!isset($ajax_args["uuid"])) {
SPITAJAX_ERROR(1, "Missing profile uuid");
return;
}
$profile = Profile::Lookup($ajax_args["uuid"]);
if (!$profile) {
SPITAJAX_ERROR(1, "Unknown profile uuid");
return;
}
if (!isset($this_user)) {
SPITAJAX_ERROR(1, "Guest users may not set profile favorites");
return;
}
$profile->MarkFavorite($this_user);
SPITAJAX_RESPONSE(0);
}
function Do_ClearFavorite()
{
global $this_user;
global $ajax_args;
if (!isset($ajax_args["uuid"])) {
SPITAJAX_ERROR(1, "Missing profile uuid");
return;
}
$profile = Profile::Lookup($ajax_args["uuid"]);
if (!$profile) {
SPITAJAX_ERROR(1, "Unknown profile uuid");
return;
}
if (!isset($this_user)) {
SPITAJAX_ERROR(1, "Guest users may not set profile favorites");
return;
}
$profile->ClearFavorite($this_user);
SPITAJAX_RESPONSE(0);
}
# Local Variables:
# mode:php
# End:
......
......@@ -246,6 +246,46 @@ else {
}
}
#
# Rebuild the array with extra info for the profile picker.
#
$tmp_array = array();
while (list ($uuid, $title) = each ($profile_array)) {
$tmp = Profile::Lookup($uuid);
if ($tmp) {
list ($lastused, $count) = $tmp->UsageInfo($this_user);
$tmp_array[$uuid] =
array("name" => $tmp->name(),
"project" => $tmp->pid(),
"favorite" => $tmp->isFavorite($this_user),
"lastused" => $lastused,
"usecount" => $count);
}
}
#
# Now we want to order the list.
#
if ($this_user) {
uasort($tmp_array, function($a, $b) {
if ($a["lastused"] == $b["lastused"]) {
return 0;
}
return ($a["lastused"] > $b["lastused"]) ? -1 : 1;
});
}
else {
uasort($tmp_array, function($a, $b) {
if ($a["usecount"] == $b["usecount"]) {
return 0;
}
return ($a["usecount"] > $b["usecount"]) ? -1 : 1;
});
}
$profile_array = $tmp_array;
#TBERROR(print_r($profile_array, true), 0);
function SPITFORM($formfields, $newuser, $errors)
{
global $TBBASE, $APTMAIL, $ISCLOUD;
......@@ -269,6 +309,7 @@ function SPITFORM($formfields, $newuser, $errors)
else {
$profilename = "null";
}
SPITHEADER(1);
# I think this will take care of XSS prevention?
echo "<script type='text/plain' id='form-json'>\n";
......@@ -277,6 +318,9 @@ function SPITFORM($formfields, $newuser, $errors)
echo "<script type='text/plain' id='error-json'>\n";
echo htmlentities(json_encode($errors));
echo "</script>\n";
echo "<script type='text/plain' id='profiles-json'>\n";
echo htmlentities(json_encode($profile_array));
echo "</script>\n";
# Gack.
if (isset($this_user) && $this_user->IsNonLocal()) {
......@@ -287,8 +331,6 @@ function SPITFORM($formfields, $newuser, $errors)
}
}
SPITHEADER(1);
# Place to hang the toplevel template.
echo "<div id='main-body'></div>\n";
......@@ -323,7 +365,6 @@ function SPITFORM($formfields, $newuser, $errors)
echo "</script>\n";
}
SpitTopologyViewModal("quickvm_topomodal", $profile_array);
SpitOopsModal("oops");
echo "<script type='text/javascript'>\n";
echo " window.PROFILE = '" . $formfields["profile"] . "';\n";
......
......@@ -14,6 +14,7 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
var ajaxurl;
var amlist = null;
var projlist = null;
var profilelist = null;
var amdefault = null;
var selected_uuid = null;
var selected_rspec = null;
......@@ -57,18 +58,19 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
showpicker = window.SHOWPICKER;
if ($('#amlist-json').length) {
amlist = JSON.parse(_.unescape($('#amlist-json')[0].textContent));
amlist = decodejson('#amlist-json');
_.each(_.keys(amlist), function (key) {
amValueToKey[amlist[key]] = key;
});
}
if ($('#projects-json').length) {
projlist = JSON.parse(_.unescape($('#projects-json')[0].textContent));
projlist = decodejson('#projects-json');
}
var formfields = JSON.parse(_.unescape($('#form-json')[0].textContent));
profilelist = decodejson('#profiles-json');
var html = mainTemplate({
formfields: formfields,
formfields: decodejson('#form-json'),
profiles: profilelist,
projects: projlist,
amlist: amlist,
registered: registered,
......@@ -243,6 +245,11 @@ function (_, Constraints, sup, ppstart, JacksEditor, wt,
_.delay(function () {$('.dropdown-toggle').dropdown();}, 500);
}
// Helper.
function decodejson(id) {
return JSON.parse(_.unescape($(id)[0].textContent));
}
var doingformcheck = 0;
// Step is changing
......
......@@ -432,6 +432,99 @@ class Profile
return 0;
}
function UsageInfo($user) {
$profile_id = $this->profileid();
$userclause = "";
if ($user) {
$creator_idx = $user->idx();
$userclause = "and creator_idx='$creator_idx' ";
}
#
# This is last used.
#
$query_result =
DBQueryFatal("select max(UNIX_TIMESTAMP(created)) ".
" from apt_instances ".
"where profile_id='$profile_id' ".
$userclause);
$row = mysql_fetch_row($query_result);
if (!$row[0]) {
$query_result =
DBQueryFatal("select max(UNIX_TIMESTAMP(created)) ".
" from apt_instance_history ".
"where profile_id='$profile_id' ".
$userclause);
$row = mysql_fetch_row($query_result);
}
if (!$row[0]) {
return array(0, 0);
}
$lastused = $row[0];
#
# Now we want number of times used.
#
$count = 0;
$query_result =
DBQueryFatal("select ".
"(select count(profile_id) ".
" from apt_instances ".
" where profile_id='$profile_id' ".
$userclause . ") as count1, ".
"(select count(profile_id) ".
" from apt_instance_history ".
" where profile_id='$profile_id' ".
$userclause . ") as count2");
if (mysql_num_rows($query_result)) {
$row = mysql_fetch_row($query_result);
$count = ($row[0] ? $row[0] : 0) + ($row[1] ? $row[1] : 0);
}
return array($lastused, $count);
}
function isFavorite($user) {
if (!$user) {
return 0;
}
$profile_id = $this->profileid();
$user_idx = $user->idx();
$query_result =
DBQueryFatal("select * from apt_profile_favorites ".
"where uid_idx='$user_idx' and ".
" profileid='$profile_id'");
return mysql_num_rows($query_result);
}
function MarkFavorite($user) {
$profile_id = $this->profileid();
$user_uid = $user->uid();
$user_idx = $user->idx();
if (!DBQueryWarn("replace into apt_profile_favorites set ".
" uid='$user_uid',uid_idx='$user_idx', ".
" profileid='$profile_id',now()")) {
return -1;
}
return 0;
}
function ClearFavorite($user) {
$profile_id = $this->profileid();
$user_uid = $user->uid();
$user_idx = $user->idx();
if (!DBQueryWarn("delete from apt_profile_favorites ".
"where uid_idx='$user_idx' and ".
" profileid='$profile_id'")) {
return -1;
}
return 0;
}
function BestAggregate($rspec = null) {
if (!$rspec) {
$rspec = $this->rspec();
......
......@@ -581,65 +581,6 @@ function SpitLoginModal($id)
<?php
}
#
# Topology view modal, shared across a few pages.
#
function SpitTopologyViewModal($modal_name, $profile_array)
{
echo "<!-- This is the topology view modal -->
<div id='$modal_name' class='modal fade'>
<div class='modal-dialog' id='showtopo_dialog'>
<div class='modal-content'>
<div class='modal-header'>
<button type='button' class='close' data-dismiss='modal'
aria-hidden='true'>
&times;</button>
<h3>Select a Profile</h3>
</div>
<div class='modal-body'>
<!-- This topo diagram goes inside this div -->
<div class='row'
id='showtopo_container'>
<div class='form-group col-md-3 col-sm-3 col-xs-3'>
<input type='text' class='form-control'
placeholder='Search'
id='profile_picker_search'>
<ul class='list-group' id='profile_name'
name='profile'
>\n";
while (list ($id, $title) = each ($profile_array)) {
$selected = "";
if ($profile_value == $id)
$selected = "selected";
echo " <li class='list-group-item profile-item' $selected
value='$id'>$title </li>\n";
}
echo " </ul>
</div>
<div class='col-md-9 col-sm-9 col-xs-9'>
<div class='panel-body'>
<span id='showtopo_title'></span>
<div id='showtopo_div' class='jacks'></div>
<span class='pull-left' id='showtopo_description'></span>
</div>
</div>
</div>
<div id='showtopo_buttons' class='pull-right'>
<button id='showtopo_select'
class='btn btn-primary btn-sm'
type='submit' name='select'>
Select Profile</button>
<button type='button' class='btn btn-default btn-sm'
data-dismiss='modal' aria-hidden='true'>
Cancel</button>
</div>
</div>
</div>
</div>
</div>\n";
}
#
# Please Wait.
#
......
......@@ -71,7 +71,11 @@ $routing = array("myprofiles" =>
"GetParameters" =>
"Do_GetParameters",
"GetImageInfo" =>
"Do_GetImageInfo")),
"Do_GetImageInfo",
"MarkFavorite" =>
"Do_MarkFavorite",
"ClearFavorite" =>
"Do_ClearFavorite")),
"manage_profile" =>
array("file" => "manage_profile.ajax",
"guest" => false,
......
......@@ -379,6 +379,53 @@
</div>
</div>
</div>
<!-- This is the topology view modal -->
<div id='quickvm_topomodal' class='modal fade'>
<div class='modal-dialog' id='showtopo_dialog'>
<div class='modal-content'>
<div class='modal-header'>
<button type='button' class='close' data-dismiss='modal'
aria-hidden='true'>
&times;</button>
<h3>Select a Profile</h3>
</div>
<div class='modal-body'>
<!-- This topo diagram goes inside this div -->
<div class='row' id='showtopo_container'>
<div class='form-group col-md-3 col-sm-3 col-xs-3'>
<input type='text' class='form-control'
placeholder='Search'
id='profile_picker_search'>
<ul class='list-group' id='profile_name'
name='profile'>
<% _.each(profiles, function(value, key) { %>
<li class='list-group-item profile-item'
value='<%- key %>'><%- value.name %>
</li>
<% }); %>
</ul>
</div>
<div class='col-md-9 col-sm-9 col-xs-9'>
<div class='panel-body'>
<span id='showtopo_title'></span>
<div id='showtopo_div' class='jacks'></div>
<span class='pull-left' id='showtopo_description'></span>
</div>
</div>
</div>
<div id='showtopo_buttons' class='pull-right'>
<button id='showtopo_select'
class='btn btn-primary btn-sm'
type='submit' name='select'>
Select Profile</button>
<button type='button' class='btn btn-default btn-sm'
data-dismiss='modal' aria-hidden='true'>
Cancel</button>
</div>
</div>
</div>
</div>
</div>
<div id='waitwait_div'></div>
<div id='ppviewmodal_div'></div>
<div id='ppmodal_div'></div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment