GitHub
Adding Ajax Panels to the CakePHP Status Plugin
Previously On...
Last time we made a simple panel that showed the last 10 users that signed up for you web app. It was a good example for using the requestAction approach (or as AD7six pointed out you can skip the requestAction part and go right to the model).
Let's Get Ajaxy Up In This Mofo
This time we'll do a panel that shows the number of sign ups based on a time frame. We'll add a drop down that let's you select day, week, month or year and it'll show a breakdown of new users for that period. The updates will use ajax.
The Element
Just like last time we're going to create a new element: /app/views/elements/panels/signups.ctp. This time, however, the element will contain a form:
<?php echo $form->create('User', array('id' => 'SignupsForm')); ?>
<h1>
<?php __('Signups'); ?>
<?php echo $form->input('signup_span', array('type' => 'select', 'div' => false, 'label' => false,
'options' => array('day' => 'day',
'week' => 'week',
'month' => 'month',
'year' => 'year')));
?>
</h1>
<?php echo $form->end(); ?>
<div id="signup-table">
<p><?php echo $html->image('/status/img/ajax-loader.gif') ?></p>
</div>
In addition to the form, there is a div (signup-table) that will be used to hold the result of the Ajax call. For now we'll just stick a spinny icon in there.
The JavaScript
<script type="text/javascript">
$(function(){
$("#UserSignupSpan").change(function() {
$("#signup-table").html("<p><img src=\"/status/img/ajax-loader.gif\" \></p>");
$.get("/users/signups/" + $(this).val(), function(data) {
$("#signup-table").html(data);
});
}).change();
});
</script>
Pretty standard jQuery. We catch the change in the span select, set the holder div to the spinny icon and make the request for the updated data.
The Controller
function signups($span=1) {
if (!Configure::read('Status.allow')) {
die;
}
switch ($span) {
default:
case 'day':
$field = 'SUBSTR(created, 1, 13)';
$limit = 24;
break;
case 'week':
$field = 'SUBSTR(created, 1, 10)';
$limit = 7;
break;
case 'month':
$field = 'SUBSTR(created, 1, 7)';
$limit = 12;
break;
case 'year':
$field = 'SUBSTR(created, 1, 4)';
$limit = 5;
break;
}
$data = $this->User->find('all', array('fields' => array($field . ' AS span', 'count(*) as cnt'),
'group' => 'span',
'order' => 'span DESC',
'limit' => $limit));
$this->set('data', $data);
}
In practice I would move most of this code to a custom find type, but for the sake of this example I'll put it all in the controller. An important note: Unlike the example in the previous post, this action has a view associated with it. That means you can access it directly without going through the status panel. To protect against that we add the check at the top to make sure the user is allowed to access the status panel.
Include The New Panel
Remember to add the panel to the list of panels in bootstrap.php:
Configure::write('Status.panels', array('panels/signups'));
What It Looks Like

Adding Custom Panels to the CakePHP Status Plugin
Intro
A few weeks ago I released a status plugin for CakePHP. It is basically a single page where you can get a quick snapshot of your app. The plugin ships with a few random panels, but it's easy enough to add your own. This post will tell you how.
Make sure to get the latest version from GitHub. Also note that the way to include the default panels has changed. You now have to include the "Status." prefix.
requestAction vs Ajax
There are two different ways to add a panel - using requestAction or Ajax. The general rule is if the panel is pretty fast to load and content is static use requestAction. If the panel takes some time or you want to be able to interact with it then use Ajax. For example, the logs panel uses requestAction since it's pretty quick to grab the log entries and you really don't need to interact with them. The Google Analytics panel, on the other hand, takes a few seconds to query the API and has options to change the timeframe, so it uses Ajax.
Packaging Your Panel
Panels can be packaged as plugins or just files in your app. For this example we'll just put the files as part of the app. Making your panel as a plugin is pretty much the same, you just need to put all the files in the plugin's directory and include the plugin name as a prefix when setting which panels to use. Check out the click plugin for an example. I'm just going to cover the requestAction approach today. Look for another post of using Ajax soon.
The requestAction Way
The Element
Let's build a panel that shows the last 10 users that signed up for your app. The first thing you need is an element. In /app/views/elements/panels (note the extra directory level - this is optional, but helps to keep things clean) create a file called 'latest_users.ctp'. In that file put:
<?php
$users = $this->requestAction(array('controller' => 'users', 'action' => 'latest'));
?>
The Controller
In the users controller we now need to add an action called latest that will get the user data and pass it back to the element.
function latest() {
return $this->User->find('all', array('order' => 'created desc', 'limit' => 10));
}
Since there is no view associated with this action anyone trying to access it directly with just get an error. If you wanted to extra sure that no one unauthorized could exploit this action you could add this to the beginning:
if (!Configure::read('Status.allow')) {
die;
}
Back To The Element
Now you have a list of $users so it's just a matter of displaying them. Here's the complete element:
<?php
$users = $this->requestAction(array('controller' => 'users', 'action' => 'latest'));
?>
<h1><?php __('Latest Users') ?></h1>
<?php if($users) { ?>
<table>
<?php foreach($users as $user) { ?>
<tr>
<td><?php echo $user['User']['email'] ?></td>
<td><?php echo $time->timeAgoInWords($user['User']['created']) ?></td>
</tr>
<?php } ?>
</table>
<?php } else { ?>
<p>Your app has no users...loser!</p>
<?php } ?>
Including Your Panel
Now you just have to include your panel on the status dashboard. In /app/bootstrap.php add the line:
Configure::write('Status.panels', array('panels/latest_users'));
Here's What It Looks Like

CakePHP Status Dashboard - Everything You Need To Know About Your App On One Page
Beta
First off this thing is still pretty rough. I don't have any unit tests at the moment and I'm likely to make huge, wholesale changes on a whim.
Download
What The Heck Is This Thing?
You build a cool CakePHP app and now you have to maintain it. You go to Google to check your analytics; you SSH to your system to check log files; You log into your web admin to check affiliate clicks... Wouldn't it be cool if you could check all this (and more) from one page? That's what this status plugin does. Check out the screenshot:

Or visit this live (but admittedly boring) example.
What Are The Different Panels Available?
Right now there are four "core" widgets, some of which have multiple options, so they can produce multiple panels.
Google Analytics
Multiple panels for quick info on your visits, referrers and search engine keywords using the Google Analytics API. Adjustable timeframes to get a recent or long range snapshot.
Shells
Add logging to your shells to capture info when they run - great for shells that run from cron. Clicking on the shell link in the panel pops open a window with detailed information. Requires integrating the log task in your shell.
System
Basic information about the system hardware, including disk space and uptime.
Logs
Show Cake's log files. Can be used on any log file in /app/tmp/logs, including custom ones. Just pass the log file name(s).
Extending For Custom Panels
It's pretty easy to write your own custom panels. Check out the click plugin, which tracks outbound clicks and displays the results via panels in the status dashboard. I'll write full post on how to code your own panels in bit.
CakePHP Book and Code Updates
The Book
For anyone who missed it, I released a free CakePHP book a month ago. Since then it's been downloaded over 2500 times. I've also received some great feedback and have made a bunch of corrections to the code and grammar. The biggest change is that the table of contents is now links and will jump you right to the section. That's reason enough alone to grab the updated version. The new permanent home page for the book is here.
Interactive Console and DebugKit Panel
Github Link
http://github.com/mcurry/interactive
Updates
I fixed a couple issues that were pointed out in the comments of the announcement post.
One of the suggestions was to have the up arrow cycle previous commands when using this in the console. It's a great idea and actually works magically on ForTheWindows. I checked it out and I didn't see a way to make it happen for all systems though. So, I did the next best thing. Hitting enter on a blank line re-runs the previous commands. Entering 'h' brings up a list of the last 20 commands and you can run one by just entering the number. Command history is also saved across sessions, so you can exit and go back in and still run old commands.
Custom Find
Github Link
Pagination
I pulled in some of the ideas from Daniel Salazar's post and now the custom find types have built in pagination support. It's kind of scary how easy it is. Here's an example:
$this->paginate = am($this->paginate, $this->Post->find('published', array('paginate' => true)));
$this->set('posts', $this->paginate());
This only works if you are still using one of the default find types to run the actual query, as opposed to Model::query();
Extending Model
I struggled with how to make this plugin easy to install. I didn't want to force people to have to copy and paste a bunch of code into their AppModel. I felt like that would make updates harder and would just make AppModel too messy. So instead I recommend that you have your AppModel extend FindAppModel instead of Model. FindAppModel in turn extends Model. Multiple inheritance would come in handy here.
Ajax Chat
Github Link
Updates
A couple small changes here. The helper now handles cake installs that aren't running in webroot automatically, so you don't have to set it in the JavaScript manually. Also the posted chat message now appears instantly in the chat window. Before you'd have to wait for an update for the message to appear.
Asset
Github Link
http://github.com/mcurry/asset
Updates
The order that scripts and codeblocks is now maintained. The previous version always put codeblocks after scripts, but there are times when the codeblock needs to go before a script. The new version keeps everything in the order that it was entered. This means if you do script1, codeblock, script2 that the two scripts won't be merged because it is assumed that the codeblock is dependent on script1 and script2 is dependent on the codeblock.
JS Validate
Github Link
http://github.com/mcurry/js_validate
Updates
I merged a couple changes from javierm's fork. These include a fix for the numeric check, matching Cake's default error HTML and including the fieldName when calling custom rules, which should make doing Ajax validation much easier. I'll have a post showing some examples of this next week.
Interactive Plugin For CakePHP Console and DebugKit
I teased this on twitter last week, so here's the official release. The interactive plugin is a super easy way to run code against your Cake app. You can use it either through a custom panel in the DebugKit or from the command line as a shell.
Download
Instructions
I'll skip the install instructions, since they're included in the README. It's pretty easy to install. If you're using the latest stable release of the DebugKit you do need to apply a patch for better plugin handling. That fix is already in the trunk, so it's not a dirty hack.
What It Can Do
Let's get right to the good stuff.
Run Simple PHP
Pretty much any simple PHP commands will run:
10 % 4 //returns 2 is_array(49) //returns false
Run SQL
SELECT id FROM users WHERE email = 'test@test.com' //returns the id of the user UPDATE posts SET published = false; //returns "No results found.", but does the update.
Use Cake Functions
__('Test Message', true) //returns the translated text matching the msg id for your current language.
Configure::write('Config.language', 'es');
__('Test Message', true); //returns the translated text in Spanish.
Use Cake Libs
Security::hash('my_pAssw0rd', null, true) //returns the hashed password
Set::extract('/id', array(array('id' => 3), array('name' => 'test', 'id' => 4))) //array(3, 4)
Use Cake Helpers
$html->link('Posts', array('controller' => 'posts')) //the html code for the link and the link itself
$form->input('Post.title'); //the html code for the field and the field itself
Use Your App Code
Post::find('first') //The first record from your posts table
User::findById(3) //The user record for id 3
Debugging
If you have a command that isn't returning the results you expect, you can turn on debug first:
Configure::write('debug', 2);
User::findByIdd(3) //will show error output
Notes
When using through the DebugKit you can stack commands, just separate them with a ";". However you can't define a variable in one command then use it in another. For example this doesn't work:
$i = 10; $i ++; echo $i;
You'll also notice I never explicitly declared any of the models I was using. The plugin does this automatically. You can write your model calls statically or just pretend you have an instance. These are all the same and will work:
Post::find('first');
$Post->find('first');
Post->find('first');
$this->Post->find('first');
The DebugKit = Awesomeness
Like most of you, I read this post from Mark Story on the new JavaScript for the DebugKit. Just reading that post, you don't get a full appreciation for how much work he (and others) have put into it. The JavaScript lib is virtually it's own mini-framework. Everything I needed to create this was already included - Ajax, Event handing, Elements...Really great stuff.
That's About It
Let me know if you hit any commands you want to run that aren't working. I tried to cover most of the basics.
