|
|
@ -0,0 +1,562 @@ |
|
|
|
<!doctype html> |
|
|
|
<html lang="en" prefix="og: https://ogp.me/ns#"> |
|
|
|
<head> |
|
|
|
|
|
|
|
<meta charset="utf-8"> |
|
|
|
<meta http-equiv="x-ua-compatible" content="ie=edge"> |
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
|
|
<title>This Field Was Green - Using Perforce Chronicle for application configuration</title> |
|
|
|
<link rel="canonical" href="https://thisfieldwas.green/blog/2012/11/07/using-perforce-chronicle-for-application-configuration/"> |
|
|
|
<link rel="shortcut icon" sizes="16x16 32x32 48x48 64x64 96x96 128x128 256x256" href="https://thisfieldwas.green/favicon.ico"> |
|
|
|
|
|
|
|
<meta property="og:site_name" content=""> |
|
|
|
<meta property="og:title" content="Using Perforce Chronicle for application configuration"> |
|
|
|
<meta property="og:url" content="https://thisfieldwas.green/blog/2012/11/07/using-perforce-chronicle-for-application-configuration/"> |
|
|
|
<meta property="og:description" content=""> |
|
|
|
<meta property="og:image:url" content="https://thisfieldwas.green/images/grass-256x256.png"> |
|
|
|
|
|
|
|
<meta property="article:published_time" content="2012-11-07T13:54:00-06:00"> |
|
|
|
<meta property="article:modified_time" content="2012-11-07T13:54:00-06:00"> |
|
|
|
<meta property="article:author" content="Logan McGrath"> |
|
|
|
<meta property="article:tag" content="perforce"> |
|
|
|
<meta property="article:tag" content="configuration management"> |
|
|
|
|
|
|
|
<meta name="twitter:card" content="summary"> |
|
|
|
<meta name="twitter:site" content="@thisgreenfield"> |
|
|
|
<meta name="twitter:title" content="Using Perforce Chronicle for application configuration"> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="/css/main.css"> |
|
|
|
<script src="/js/main.js"></script> |
|
|
|
<script src="/js/scroll-shadows.js"></script> |
|
|
|
<script> |
|
|
|
let scrollShadowsXSelectors = ["pre.sourceCode",] |
|
|
|
let scrollShadowsYSelectors = [] |
|
|
|
window.addEventListener("load", () => { |
|
|
|
scrollShadowsXSelectors.forEach(updateScrollShadowsXSelector) |
|
|
|
scrollShadowsYSelectors.forEach(updateScrollShadowsYSelector) |
|
|
|
}) |
|
|
|
window.addEventListener("resize", () => { |
|
|
|
scrollShadowsXSelectors.forEach(updateScrollShadowsXSelector) |
|
|
|
}) |
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
|
|
</head> |
|
|
|
<body> |
|
|
|
|
|
|
|
<header class="page-header"> |
|
|
|
<div class="content-bound"> |
|
|
|
<div class="logo-icon"><a href="/"></a></div> |
|
|
|
<div class="logo-nav"> |
|
|
|
<h1 class="logo"><a href="/">This Field Was Green</a></h1> |
|
|
|
<nav class="main-nav"> |
|
|
|
<a href="/">Home</a> |
|
|
|
<a href="/blog/">Blog</a> |
|
|
|
<a href="/resume/">Resume</a> |
|
|
|
<a href="/contact/">Contact</a> |
|
|
|
</nav> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</header> |
|
|
|
|
|
|
|
<main class="page-content"> |
|
|
|
<div class="content-bound"> |
|
|
|
<div class="post post-full"> |
|
|
|
<h1 class="post-title">Using Perforce Chronicle for application configuration</h1> |
|
|
|
<div class="post-meta"> |
|
|
|
<p class="post-published"> |
|
|
|
Posted on November 7, 2012 |
|
|
|
by Logan McGrath |
|
|
|
</p> |
|
|
|
<p class="post-tags"> |
|
|
|
Tags: <a class="tag" href="/tags/perforce">perforce</a>, <a class="tag" href="/tags/configuration-management">configuration management</a> |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="estimated-reading-time"> |
|
|
|
<p>Estimated reading time: <span class="length">15m 56s</span></p> |
|
|
|
</div> |
|
|
|
<p>Following Paul Hammant’s post <a href="http://paulhammant.com/2012/07/10/app-config-workflow-using-scm/">App-config workflow using SCM</a> and subsequent |
|
|
|
<a href="http://paulhammant.com/2012/08/14/app-config-using-git-and-angular/">proof of concept</a> backed by Git, I will show that an app-config application |
|
|
|
backed by Perforce is possible using <a href="http://www.perforce.com/products/chronicle">Perforce Chronicle</a>.</p> |
|
|
|
<!--more--> |
|
|
|
<h2 id="perforce-and-permissions-for-branches">Perforce and permissions for branches</h2> |
|
|
|
<p><a href="http://en.wikipedia.org/wiki/Perforce">Perforce</a> is an enterprise-class source control management (SCM) system, |
|
|
|
remarkably similar to Subversion (Subversion was inspired by Perforce :) |
|
|
|
Perforce is more bulletproof than Subversion in many ways and it’s generally |
|
|
|
faster. Git does not impose any security constraints or permissions on branches, |
|
|
|
Perforce gives comprehensive security options allowing you to control access to |
|
|
|
different branches: for example, development, staging, and production. |
|
|
|
Subversion, however, can support permissions on branches with some extra |
|
|
|
configuration (Apache plus mod_dav_svn/mod_dav_authz). For these reasons, |
|
|
|
Perforce is a better option for storing configuration data than either Git or |
|
|
|
Subversion.</p> |
|
|
|
<h2 id="perforce-cms-as-an-application-server">Perforce CMS as an application server</h2> |
|
|
|
<p><a href="http://www.perforce.com/products/chronicle">Perforce Chronicle</a> is a content management system (CMS) using Perforce as |
|
|
|
the back-end store for configuration and content. The app-config application is |
|
|
|
built on top of Chronicle because Perforce does not offer a web view into the |
|
|
|
depot the way Subversion can through Apache. Branching and maintaining |
|
|
|
divergence between environments can be managed through the user interface, and |
|
|
|
Chronicle provides user authentication and management, so access between |
|
|
|
different configuration files can be restricted appropriately. The INSTALL.txt |
|
|
|
file that is distributed with Chronicle helps with an easy install, mine being |
|
|
|
set up to run locally from <code>http://localhost</code>.</p> |
|
|
|
<p>There is a key issue in using Chronicle, however. The system is designed for the |
|
|
|
management of <em>content</em> and not necessarily arbitrary <em>files</em>. In order to make |
|
|
|
the app-config application work, I had to add a custom content type and write a |
|
|
|
module. Configuration and HTML are both plain-text content, so I created a ” |
|
|
|
Plain Text” content type with the fields <em>title</em> and <em>content</em>:</p> |
|
|
|
<ol type="1"> |
|
|
|
<li>Go to “Manage” > “Content Types”</li> |
|
|
|
<li>Click “Add Content Type”</li> |
|
|
|
<li>Enter the following information:</li> |
|
|
|
</ol> |
|
|
|
<div class="sourceCode" id="cb1"><pre class="sourceCode numberSource ini numberLines"><code class="sourceCode ini"><span id="cb1-1"><a href="#cb1-1"></a><span class="dt">Id: plaintext</span></span> |
|
|
|
<span id="cb1-2"><a href="#cb1-2"></a><span class="dt">Label: Plain Text</span></span> |
|
|
|
<span id="cb1-3"><a href="#cb1-3"></a><span class="dt">Group: Assets</span></span> |
|
|
|
<span id="cb1-4"><a href="#cb1-4"></a><span class="dt">Elements:</span></span> |
|
|
|
<span id="cb1-5"><a href="#cb1-5"></a></span> |
|
|
|
<span id="cb1-6"><a href="#cb1-6"></a><span class="kw">[title]</span></span> |
|
|
|
<span id="cb1-7"><a href="#cb1-7"></a><span class="dt">type </span><span class="ot">=</span><span class="st"> text</span></span> |
|
|
|
<span id="cb1-8"><a href="#cb1-8"></a><span class="dt">options.label </span><span class="ot">=</span><span class="st"> Title</span></span> |
|
|
|
<span id="cb1-9"><a href="#cb1-9"></a><span class="dt">options.required </span><span class="ot">=</span><span class="st"> </span><span class="kw">true</span></span> |
|
|
|
<span id="cb1-10"><a href="#cb1-10"></a><span class="dt">display.tagName </span><span class="ot">=</span><span class="st"> h1</span></span> |
|
|
|
<span id="cb1-11"><a href="#cb1-11"></a><span class="dt">display.filters.0 </span><span class="ot">=</span><span class="st"> HtmlSpecialChars</span></span> |
|
|
|
<span id="cb1-12"><a href="#cb1-12"></a></span> |
|
|
|
<span id="cb1-13"><a href="#cb1-13"></a><span class="kw">[content]</span></span> |
|
|
|
<span id="cb1-14"><a href="#cb1-14"></a><span class="dt">type </span><span class="ot">=</span><span class="st"> textarea</span></span> |
|
|
|
<span id="cb1-15"><a href="#cb1-15"></a><span class="dt">options.label </span><span class="ot">=</span><span class="st"> "Content"</span></span> |
|
|
|
<span id="cb1-16"><a href="#cb1-16"></a><span class="dt">options.required </span><span class="ot">=</span><span class="st"> </span><span class="kw">true</span></span> |
|
|
|
<span id="cb1-17"><a href="#cb1-17"></a><span class="dt">display.tagName </span><span class="ot">=</span><span class="st"> pre</span></span> |
|
|
|
<span id="cb1-18"><a href="#cb1-18"></a><span class="dt">display.filters.0 </span><span class="ot">=</span><span class="st"> HtmlSpecialChars</span></span></code></pre></div> |
|
|
|
<p>Click “Save”.</p> |
|
|
|
<h2 id="the-config-app">The Config App</h2> |
|
|
|
<p>I’ve borrowed heavily from Paul’s <a href="https://github.com/paul-hammant/app-config-app/blob/master/index.html">app-config HTML page</a>, which uses |
|
|
|
<a href="http://angularjs.org/">AngularJS</a> to manage the UI and interaction with the server. Where Paul’s |
|
|
|
app-config app used the <a href="http://kmkeen.com/jshon/">jshon</a> command to encode and decode JSON, Zend |
|
|
|
Framework has a utility class for encoding, decoding, and pretty-printing JSON, |
|
|
|
and Chronicle also ships with the <a href="https://github.com/paulgb/simplediff/">simplediff</a> utility for performing diffs |
|
|
|
with PHP.</p> |
|
|
|
<p>The source JSON configuration is the same, albeit sorted:</p> |
|
|
|
<div class="sourceCode" id="cb2"><pre class="sourceCode numberSource json numberLines"><code class="sourceCode json"><span id="cb2-1"><a href="#cb2-1"></a><span class="fu">{</span></span> |
|
|
|
<span id="cb2-2"><a href="#cb2-2"></a> <span class="dt">"bannedNicks"</span><span class="fu">:</span> <span class="ot">[</span></span> |
|
|
|
<span id="cb2-3"><a href="#cb2-3"></a> <span class="st">"derek"</span><span class="ot">,</span></span> |
|
|
|
<span id="cb2-4"><a href="#cb2-4"></a> <span class="st">"dino"</span><span class="ot">,</span></span> |
|
|
|
<span id="cb2-5"><a href="#cb2-5"></a> <span class="st">"ffff"</span><span class="ot">,</span></span> |
|
|
|
<span id="cb2-6"><a href="#cb2-6"></a> <span class="st">"jjjj"</span><span class="ot">,</span></span> |
|
|
|
<span id="cb2-7"><a href="#cb2-7"></a> <span class="st">"werwer"</span></span> |
|
|
|
<span id="cb2-8"><a href="#cb2-8"></a> <span class="ot">]</span><span class="fu">,</span></span> |
|
|
|
<span id="cb2-9"><a href="#cb2-9"></a> <span class="dt">"defaultErrorReciever"</span><span class="fu">:</span> <span class="st">"piglet@thoughtworks.com"</span><span class="fu">,</span></span> |
|
|
|
<span id="cb2-10"><a href="#cb2-10"></a> <span class="dt">"lighton"</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span> |
|
|
|
<span id="cb2-11"><a href="#cb2-11"></a> <span class="dt">"loadMaxPercent"</span><span class="fu">:</span> <span class="st">"88"</span><span class="fu">,</span></span> |
|
|
|
<span id="cb2-12"><a href="#cb2-12"></a> <span class="dt">"nextShutdownDate"</span><span class="fu">:</span> <span class="st">"8</span><span class="ch">\/</span><span class="st">9</span><span class="ch">\/</span><span class="st">2012"</span></span> |
|
|
|
<span id="cb2-13"><a href="#cb2-13"></a><span class="fu">}</span></span></code></pre></div> |
|
|
|
<p>The <code>index.html</code> page has been modified from the original to support only the |
|
|
|
basic <em>commit</em> and <em>diffs</em> functionality:</p> |
|
|
|
<div class="sourceCode" id="cb3"><pre class="sourceCode numberSource html numberLines"><code class="sourceCode html"><span id="cb3-1"><a href="#cb3-1"></a><span class="dt"><!DOCTYPE </span>html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"</span> |
|
|
|
<span id="cb3-2"><a href="#cb3-2"></a> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"<span class="dt">></span></span> |
|
|
|
<span id="cb3-3"><a href="#cb3-3"></a><span class="kw"><html</span> <span class="er">lang</span><span class="ot">=</span><span class="st">"en"</span> <span class="er">xmlns:ng</span><span class="ot">=</span><span class="st">"http://angularjs.org"</span><span class="kw">></span></span> |
|
|
|
<span id="cb3-4"><a href="#cb3-4"></a><span class="kw"><head></span></span> |
|
|
|
<span id="cb3-5"><a href="#cb3-5"></a><span class="kw"><meta</span> <span class="er">http-equiv</span><span class="ot">=</span><span class="st">"content-type"</span> <span class="er">content</span><span class="ot">=</span><span class="st">"text/html; charset=UTF-8"</span><span class="kw">></span></span> |
|
|
|
<span id="cb3-6"><a href="#cb3-6"></a> <span class="kw"><title></span>Configuration application (alpha)<span class="kw"></title></span></span> |
|
|
|
<span id="cb3-7"><a href="#cb3-7"></a> <span class="kw"><script</span><span class="ot"> type=</span><span class="st">"text/javascript"</span> <span class="er">ng:autobind</span> <span class="er">src</span><span class="ot">=</span><span class="st">"http://code.angularjs.org/0.9.19/angular-0.9.19.min.js"</span><span class="kw">></script></span></span> |
|
|
|
<span id="cb3-8"><a href="#cb3-8"></a> <span class="kw"><style</span> <span class="er">type</span><span class="ot">=</span><span class="st">"text/css"</span><span class="kw">></span></span> |
|
|
|
<span id="cb3-9"><a href="#cb3-9"></a> ins { <span class="kw">color</span>: <span class="cn">#00CC00</span><span class="op">;</span> <span class="kw">text-decoration</span>: <span class="dv">none</span><span class="op">;</span> }</span> |
|
|
|
<span id="cb3-10"><a href="#cb3-10"></a> del { <span class="kw">color</span>: <span class="cn">#CC0000</span><span class="op">;</span> <span class="kw">text-decoration</span>: <span class="dv">none</span><span class="op">;</span> }</span> |
|
|
|
<span id="cb3-11"><a href="#cb3-11"></a> <span class="kw"></style></span></span> |
|
|
|
<span id="cb3-12"><a href="#cb3-12"></a><span class="kw"></head></span></span> |
|
|
|
<span id="cb3-13"><a href="#cb3-13"></a><span class="kw"><body</span> <span class="er">ng:controller</span><span class="ot">=</span><span class="st">"AppCfg"</span><span class="kw">></span></span> |
|
|
|
<span id="cb3-14"><a href="#cb3-14"></a><span class="kw"><script</span><span class="ot"> type=</span><span class="st">"text/javascript"</span><span class="kw">></span></span> |
|
|
|
<span id="cb3-15"><a href="#cb3-15"></a> <span class="kw">function</span> <span class="fu">AppCfg</span>($resource<span class="op">,</span> $xhr) {</span> |
|
|
|
<span id="cb3-16"><a href="#cb3-16"></a> <span class="kw">var</span> self <span class="op">=</span> <span class="kw">this</span><span class="op">;</span></span> |
|
|
|
<span id="cb3-17"><a href="#cb3-17"></a> <span class="kw">this</span><span class="op">.</span><span class="at">newNickname</span> <span class="op">=</span> <span class="st">""</span><span class="op">;</span></span> |
|
|
|
<span id="cb3-18"><a href="#cb3-18"></a> <span class="kw">this</span><span class="op">.</span><span class="at">svrMessage</span><span class="op">;</span></span> |
|
|
|
<span id="cb3-19"><a href="#cb3-19"></a> <span class="kw">this</span><span class="op">.</span><span class="at">message</span><span class="op">;</span></span> |
|
|
|
<span id="cb3-20"><a href="#cb3-20"></a> <span class="kw">this</span><span class="op">.</span><span class="at">cfg</span> <span class="op">=</span> <span class="fu">$resource</span>(<span class="st">"/appconfig/stack_configuration.json"</span>)<span class="op">.</span><span class="fu">get</span>({})<span class="op">;</span></span> |
|
|
|
<span id="cb3-21"><a href="#cb3-21"></a></span> |
|
|
|
<span id="cb3-22"><a href="#cb3-22"></a> <span class="kw">this</span><span class="op">.</span><span class="at">save</span> <span class="op">=</span> <span class="kw">function</span>() {</span> |
|
|
|
<span id="cb3-23"><a href="#cb3-23"></a> self<span class="op">.</span><span class="at">cfg</span><span class="op">.</span><span class="fu">$save</span>({<span class="dt">message</span><span class="op">:</span> self<span class="op">.</span><span class="at">message</span>}<span class="op">,</span> <span class="kw">function</span>() {</span> |
|
|
|
<span id="cb3-24"><a href="#cb3-24"></a> <span class="fu">alert</span>(<span class="st">"Config saved to server"</span>)<span class="op">;</span></span> |
|
|
|
<span id="cb3-25"><a href="#cb3-25"></a> }<span class="op">,</span> <span class="kw">function</span>() {</span> |
|
|
|
<span id="cb3-26"><a href="#cb3-26"></a> <span class="fu">alert</span>(<span class="st">"ERROR on save"</span>)<span class="op">;</span></span> |
|
|
|
<span id="cb3-27"><a href="#cb3-27"></a> })<span class="op">;</span></span> |
|
|
|
<span id="cb3-28"><a href="#cb3-28"></a> self<span class="op">.</span><span class="at">message</span> <span class="op">=</span> <span class="st">""</span><span class="op">;</span></span> |
|
|
|
<span id="cb3-29"><a href="#cb3-29"></a> }<span class="op">;</span></span> |
|
|
|
<span id="cb3-30"><a href="#cb3-30"></a></span> |
|
|
|
<span id="cb3-31"><a href="#cb3-31"></a> <span class="kw">this</span><span class="op">.</span><span class="at">newNick</span> <span class="op">=</span> <span class="kw">function</span>() {</span> |
|
|
|
<span id="cb3-32"><a href="#cb3-32"></a> self<span class="op">.</span><span class="at">cfg</span><span class="op">.</span><span class="at">bannedNicks</span><span class="op">.</span><span class="fu">push</span>(self<span class="op">.</span><span class="at">newNickname</span>)<span class="op">;</span></span> |
|
|
|
<span id="cb3-33"><a href="#cb3-33"></a> self<span class="op">.</span><span class="at">newNickname</span> <span class="op">=</span> <span class="st">""</span><span class="op">;</span></span> |
|
|
|
<span id="cb3-34"><a href="#cb3-34"></a> }<span class="op">;</span></span> |
|
|
|
<span id="cb3-35"><a href="#cb3-35"></a></span> |
|
|
|
<span id="cb3-36"><a href="#cb3-36"></a> <span class="kw">this</span><span class="op">.</span><span class="at">diffs</span> <span class="op">=</span> <span class="kw">function</span>() {</span> |
|
|
|
<span id="cb3-37"><a href="#cb3-37"></a> <span class="fu">$xhr</span>(<span class="st">"post"</span><span class="op">,</span> <span class="st">"/appconfig/diffs/stack_configuration.json"</span><span class="op">,</span> angular<span class="op">.</span><span class="fu">toJson</span>(self<span class="op">.</span><span class="at">cfg</span>)<span class="op">,</span> <span class="kw">function</span>(code<span class="op">,</span> svrMessage) {</span> |
|
|
|
<span id="cb3-38"><a href="#cb3-38"></a> self<span class="op">.</span><span class="at">svrMessage</span> <span class="op">=</span> svrMessage<span class="op">;</span></span> |
|
|
|
<span id="cb3-39"><a href="#cb3-39"></a> })<span class="op">;</span></span> |
|
|
|
<span id="cb3-40"><a href="#cb3-40"></a> }<span class="op">;</span></span> |
|
|
|
<span id="cb3-41"><a href="#cb3-41"></a></span> |
|
|
|
<span id="cb3-42"><a href="#cb3-42"></a> <span class="kw">this</span><span class="op">.</span><span class="at">deleteNick</span> <span class="op">=</span> <span class="kw">function</span>(nick) {</span> |
|
|
|
<span id="cb3-43"><a href="#cb3-43"></a> <span class="kw">var</span> oldBannedNicks <span class="op">=</span> self<span class="op">.</span><span class="at">cfg</span><span class="op">.</span><span class="at">bannedNicks</span><span class="op">;</span></span> |
|
|
|
<span id="cb3-44"><a href="#cb3-44"></a> self<span class="op">.</span><span class="at">cfg</span><span class="op">.</span><span class="at">bannedNicks</span> <span class="op">=</span> []<span class="op">;</span></span> |
|
|
|
<span id="cb3-45"><a href="#cb3-45"></a> angular<span class="op">.</span><span class="fu">forEach</span>(oldBannedNicks<span class="op">,</span> <span class="kw">function</span>(n) {</span> |
|
|
|
<span id="cb3-46"><a href="#cb3-46"></a> <span class="cf">if</span> (nick <span class="op">!=</span> n) {</span> |
|
|
|
<span id="cb3-47"><a href="#cb3-47"></a> self<span class="op">.</span><span class="at">cfg</span><span class="op">.</span><span class="at">bannedNicks</span><span class="op">.</span><span class="fu">push</span>(n)<span class="op">;</span></span> |
|
|
|
<span id="cb3-48"><a href="#cb3-48"></a> }</span> |
|
|
|
<span id="cb3-49"><a href="#cb3-49"></a> })<span class="op">;</span></span> |
|
|
|
<span id="cb3-50"><a href="#cb3-50"></a> }<span class="op">;</span></span> |
|
|
|
<span id="cb3-51"><a href="#cb3-51"></a> }</span> |
|
|
|
<span id="cb3-52"><a href="#cb3-52"></a></span> |
|
|
|
<span id="cb3-53"><a href="#cb3-53"></a> AppCfg<span class="op">.</span><span class="at">$inject</span> <span class="op">=</span> [<span class="st">"$resource"</span><span class="op">,</span> <span class="st">"$xhr"</span>]<span class="op">;</span></span> |
|
|
|
<span id="cb3-54"><a href="#cb3-54"></a><span class="kw"></script></span></span> |
|
|
|
<span id="cb3-55"><a href="#cb3-55"></a> Light is on: <span class="kw"><input</span> <span class="er">type</span><span class="ot">=</span><span class="st">"checkbox"</span> <span class="er">name</span><span class="ot">=</span><span class="st">"cfg.lighton"</span><span class="kw">/></span> <span class="kw"><br/></span></span> |
|
|
|
<span id="cb3-56"><a href="#cb3-56"></a> Default Error Reciever (email): <span class="kw"><input</span> <span class="er">name</span><span class="ot">=</span><span class="st">"cfg.defaultErrorReciever"</span> <span class="er">ng:validate</span><span class="ot">=</span><span class="st">"email"</span><span class="kw">/></span> <span class="kw"><br/></span></span> |
|
|
|
<span id="cb3-57"><a href="#cb3-57"></a> Max Load Percentage: <span class="kw"><input</span> <span class="er">name</span><span class="ot">=</span><span class="st">"cfg.loadMaxPercent"</span> <span class="er">ng:validate</span><span class="ot">=</span><span class="st">"number:0:100"</span><span class="kw">/></span> <span class="kw"><br/></span></span> |
|
|
|
<span id="cb3-58"><a href="#cb3-58"></a> Next Shutdown Date: <span class="kw"><input</span> <span class="er">name</span><span class="ot">=</span><span class="st">"cfg.nextShutdownDate"</span> <span class="er">ng:validate</span><span class="ot">=</span><span class="st">"date"</span><span class="kw">/></span> <span class="kw"><br/></span></span> |
|
|
|
<span id="cb3-59"><a href="#cb3-59"></a> Banned nicks:</span> |
|
|
|
<span id="cb3-60"><a href="#cb3-60"></a> <span class="kw"><ol></span></span> |
|
|
|
<span id="cb3-61"><a href="#cb3-61"></a> <span class="kw"><li</span> <span class="er">ng:repeat</span><span class="ot">=</span><span class="st">"nick in cfg.bannedNicks"</span><span class="kw">><span></span>{{nick}} <span class="dv">&nbsp;&nbsp;</span><span class="kw"><a</span> <span class="er">ng:click</span><span class="ot">=</span><span class="st">"deleteNick(nick)"</span><span class="kw">></span>[X]<span class="kw"></a></span></li></span></span> |
|
|
|
<span id="cb3-62"><a href="#cb3-62"></a> <span class="kw"></ol></span></span> |
|
|
|
<span id="cb3-63"><a href="#cb3-63"></a> <span class="kw"><form</span> <span class="er">ng:submit</span><span class="ot">=</span><span class="st">"newNick()"</span><span class="kw">></span></span> |
|
|
|
<span id="cb3-64"><a href="#cb3-64"></a> <span class="kw"><input</span> <span class="er">type</span><span class="ot">=</span><span class="st">"text"</span> <span class="er">name</span><span class="ot">=</span><span class="st">"newNickname"</span> <span class="er">size</span><span class="ot">=</span><span class="st">"20"</span><span class="kw">/></span></span> |
|
|
|
<span id="cb3-65"><a href="#cb3-65"></a> <span class="kw"><input</span> <span class="er">type</span><span class="ot">=</span><span class="st">"submit"</span> <span class="er">value</span><span class="ot">=</span><span class="st">"</span><span class="dv">&lt;</span><span class="st">-- Add Nick"</span><span class="kw">/><br/></span></span> |
|
|
|
<span id="cb3-66"><a href="#cb3-66"></a> <span class="kw"></form></span></span> |
|
|
|
<span id="cb3-67"><a href="#cb3-67"></a> <span class="kw"><hr/></span></span> |
|
|
|
<span id="cb3-68"><a href="#cb3-68"></a> <span class="kw"><button</span> <span class="er">ng:click</span><span class="ot">=</span><span class="st">"diffs()"</span><span class="kw">></span>View Diffs<span class="kw"></button><br/></span></span> |
|
|
|
<span id="cb3-69"><a href="#cb3-69"></a> <span class="kw"><button</span> <span class="er">ng:disabled</span><span class="ot">=</span><span class="st">"{{!message}}"</span> <span class="er">ng:click</span><span class="ot">=</span><span class="st">"save()"</span><span class="kw">></span>Commit Changes<span class="kw"></button></span> Commit Message: <span class="kw"><input</span> <span class="er">name</span><span class="ot">=</span><span class="st">"message"</span><span class="kw">></button><br/></span></span> |
|
|
|
<span id="cb3-70"><a href="#cb3-70"></a> Last Server operation: <span class="kw"><br/></span></span> |
|
|
|
<span id="cb3-71"><a href="#cb3-71"></a> <span class="kw"><div</span> <span class="er">ng:bind</span><span class="ot">=</span><span class="st">"svrMessage | html:'unsafe'"</span><span class="kw">></span></span> |
|
|
|
<span id="cb3-72"><a href="#cb3-72"></a> <span class="kw"></div></span></span> |
|
|
|
<span id="cb3-73"><a href="#cb3-73"></a><span class="kw"></body></span></span> |
|
|
|
<span id="cb3-74"><a href="#cb3-74"></a><span class="kw"></html></span></span></code></pre></div> |
|
|
|
<p>Both of these assets were added by performing:</p> |
|
|
|
<ol type="1"> |
|
|
|
<li>Click “Add” from the top navbar</li> |
|
|
|
<li>Click “Add Content”</li> |
|
|
|
<li>Select “Assets” > “Plain Text”</li> |
|
|
|
<li>For “Title”, enter “<code>index.html</code>” or “<code>stack_configuration.json</code>”</li> |
|
|
|
<li>Paste in the appropriate “Content”</li> |
|
|
|
<li>Click “URL”, select “Custom”, and enter the same value as “Title” (otherwise, |
|
|
|
Chronicle will convert underscores to dashes, so be careful!)</li> |
|
|
|
<li>Click “Save”, enter a commit message, then click the next “Save”</li> |
|
|
|
<li>Both assets should be viewable as mangled Chronicle content entries |
|
|
|
from <code>http://localhost/index.html</code> |
|
|
|
and <code>http://localhost/stack_configuration.json</code>. <em>You normally will not use |
|
|
|
these URLs</em>.</li> |
|
|
|
</ol> |
|
|
|
<p>At this point, neither asset is actually usable. Most content is heavily |
|
|
|
decorated with additional HTML and then displayed within a layout template, but |
|
|
|
I want both the <code>index.html</code> and <code>stack_configuration.json</code> assets to be |
|
|
|
viewable as standalone files and provide a REST interface for AngularJS to work |
|
|
|
against.</p> |
|
|
|
<h2 id="come-back-php-all-is-forgiven">Come back PHP! All is forgiven</h2> |
|
|
|
<p>Chronicle is largely built using <a href="http://framework.zend.com/">Zend Framework</a> and makes adding extra |
|
|
|
modules to the system pretty easy. My module needs to be able to display |
|
|
|
plaintext assets, update their content using an <code>HTTP POST</code>, and provide diffs |
|
|
|
between the last commit and the current content.</p> |
|
|
|
<p>To create the module, the following paths need to be added:</p> |
|
|
|
<ul> |
|
|
|
<li><code>INSTALL/application/appconfig</code></li> |
|
|
|
<li><code>INSTALL/application/appconfig/controllers</code></li> |
|
|
|
<li><code>INSTALL/application/appconfig/views/scripts/index</code></li> |
|
|
|
</ul> |
|
|
|
<p>Declare the module with <code>INSTALL/application/appconfig/module.ini</code>:</p> |
|
|
|
<div class="sourceCode" id="cb4"><pre class="sourceCode numberSource ini numberLines"><code class="sourceCode ini"><span id="cb4-1"><a href="#cb4-1"></a><span class="dt">version </span><span class="ot">=</span><span class="st"> </span><span class="fl">1.0</span></span> |
|
|
|
<span id="cb4-2"><a href="#cb4-2"></a><span class="dt">description </span><span class="ot">=</span><span class="st"> Application config proof of concept</span></span> |
|
|
|
<span id="cb4-3"><a href="#cb4-3"></a><span class="dt">icon </span><span class="ot">=</span><span class="st"> images/icon.png</span></span> |
|
|
|
<span id="cb4-4"><a href="#cb4-4"></a><span class="dt">tags </span><span class="ot">=</span><span class="st"> config</span></span> |
|
|
|
<span id="cb4-5"><a href="#cb4-5"></a></span> |
|
|
|
<span id="cb4-6"><a href="#cb4-6"></a><span class="kw">[maintainer]</span></span> |
|
|
|
<span id="cb4-7"><a href="#cb4-7"></a><span class="dt">name </span><span class="ot">=</span><span class="st"> Perforce Software</span></span> |
|
|
|
<span id="cb4-8"><a href="#cb4-8"></a><span class="dt">email </span><span class="ot">=</span><span class="st"> support@perforce.com</span></span> |
|
|
|
<span id="cb4-9"><a href="#cb4-9"></a><span class="dt">url </span><span class="ot">=</span><span class="st"> http://www.perforce.com</span></span> |
|
|
|
<span id="cb4-10"><a href="#cb4-10"></a></span> |
|
|
|
<span id="cb4-11"><a href="#cb4-11"></a><span class="kw">[routes]</span></span> |
|
|
|
<span id="cb4-12"><a href="#cb4-12"></a><span class="dt">appconfig.type </span><span class="ot">=</span><span class="st"> Zend_Controller_Router_Route_Regex</span></span> |
|
|
|
<span id="cb4-13"><a href="#cb4-13"></a><span class="dt">appconfig.route </span><span class="ot">=</span><span class="st"> 'appconfig/(.+)'</span></span> |
|
|
|
<span id="cb4-14"><a href="#cb4-14"></a><span class="dt">appconfig.reverse </span><span class="ot">=</span><span class="st"> appconfig/%s</span></span> |
|
|
|
<span id="cb4-15"><a href="#cb4-15"></a><span class="dt">appconfig.defaults.module </span><span class="ot">=</span><span class="st"> appconfig</span></span> |
|
|
|
<span id="cb4-16"><a href="#cb4-16"></a><span class="dt">appconfig.defaults.controller </span><span class="ot">=</span><span class="st"> index</span></span> |
|
|
|
<span id="cb4-17"><a href="#cb4-17"></a><span class="dt">appconfig.defaults.action </span><span class="ot">=</span><span class="st"> index</span></span> |
|
|
|
<span id="cb4-18"><a href="#cb4-18"></a><span class="dt">appconfig.map.resource </span><span class="ot">=</span><span class="st"> </span><span class="dv">1</span></span> |
|
|
|
<span id="cb4-19"><a href="#cb4-19"></a></span> |
|
|
|
<span id="cb4-20"><a href="#cb4-20"></a><span class="dt">appconfig-operation.type </span><span class="ot">=</span><span class="st"> Zend_Controller_Router_Route_Regex</span></span> |
|
|
|
<span id="cb4-21"><a href="#cb4-21"></a><span class="dt">appconfig-operation.route </span><span class="ot">=</span><span class="st"> 'appconfig/([^/]+)/(.+)'</span></span> |
|
|
|
<span id="cb4-22"><a href="#cb4-22"></a><span class="dt">appconfig-operation.reverse </span><span class="ot">=</span><span class="st"> appconfig/%s/%s</span></span> |
|
|
|
<span id="cb4-23"><a href="#cb4-23"></a><span class="dt">appconfig-operation.defaults.module </span><span class="ot">=</span><span class="st"> appconfig</span></span> |
|
|
|
<span id="cb4-24"><a href="#cb4-24"></a><span class="dt">appconfig-operation.defaults.controller </span><span class="ot">=</span><span class="st"> index</span></span> |
|
|
|
<span id="cb4-25"><a href="#cb4-25"></a><span class="dt">appconfig-operation.defaults.action </span><span class="ot">=</span><span class="st"> index</span></span> |
|
|
|
<span id="cb4-26"><a href="#cb4-26"></a><span class="dt">appconfig-operation.map.action </span><span class="ot">=</span><span class="st"> </span><span class="dv">1</span></span> |
|
|
|
<span id="cb4-27"><a href="#cb4-27"></a><span class="dt">appconfig-operation.map.resource </span><span class="ot">=</span><span class="st"> </span><span class="dv">2</span></span></code></pre></div> |
|
|
|
<p>Add a view script for displaying plaintext |
|
|
|
assets, <code>INSTALL/application/appconfig/views/scripts/index/index.phtml</code>:</p> |
|
|
|
<div class="sourceCode" id="cb5"><pre class="sourceCode numberSource php numberLines"><code class="sourceCode php"><span id="cb5-1"><a href="#cb5-1"></a><span class="kw"><?</span><span class="op">=</span><span class="va">$this</span>->entry->getValue(<span class="st">'content'</span>) <span class="kw">?></span></span></code></pre></div> |
|
|
|
<p>Add a view script for displaying |
|
|
|
diffs, <code>INSTALL/application/appconfig/views/scripts/index/diffs.phtml</code>:</p> |
|
|
|
<div class="sourceCode" id="cb6"><pre class="sourceCode numberSource php numberLines"><code class="sourceCode php"><span id="cb6-1"><a href="#cb6-1"></a><span class="op"><</span>pre<span class="op">><</span><span class="ot">?</span><span class="op">=</span><span class="va">$this</span>->diffs <span class="kw">?></span><span class="op"></</span>pre<span class="op">></span></span></code></pre></div> |
|
|
|
<p>And a controller |
|
|
|
at <code>INSTALL/application/appconfig/controllers/IndexController.phtml</code>:</p> |
|
|
|
<div class="sourceCode" id="cb7"><pre class="sourceCode numberSource php numberLines"><code class="sourceCode php"><span id="cb7-1"><a href="#cb7-1"></a><span class="kw"><?php</span></span> |
|
|
|
<span id="cb7-2"><a href="#cb7-2"></a></span> |
|
|
|
<span id="cb7-3"><a href="#cb7-3"></a><span class="fu">defined</span>(<span class="st">'LIBRARY_PATH'</span>) <span class="op">or</span> <span class="fu">define</span>(<span class="st">'LIBRARY_PATH'</span><span class="ot">,</span> <span class="fu">dirname</span>(<span class="cn">__DIR__</span>))<span class="ot">;</span></span> |
|
|
|
<span id="cb7-4"><a href="#cb7-4"></a><span class="kw">require_once</span> <span class="cn">LIBRARY_PATH</span> <span class="op">.</span> <span class="st">'/simplediff/simplediff.php'</span><span class="ot">;</span></span> |
|
|
|
<span id="cb7-5"><a href="#cb7-5"></a></span> |
|
|
|
<span id="cb7-6"><a href="#cb7-6"></a><span class="kw">class</span> <span class="cn">A</span>ppconfig_IndexController <span class="kw">extends</span> <span class="cn">Z</span>end_Controller_Action</span> |
|
|
|
<span id="cb7-7"><a href="#cb7-7"></a>{</span> |
|
|
|
<span id="cb7-8"><a href="#cb7-8"></a> <span class="kw">private</span> <span class="va">$entry</span><span class="ot">;</span></span> |
|
|
|
<span id="cb7-9"><a href="#cb7-9"></a> </span> |
|
|
|
<span id="cb7-10"><a href="#cb7-10"></a> <span class="kw">private</span> <span class="va">$mimeTypes</span> <span class="op">=</span> <span class="dt">array</span>(</span> |
|
|
|
<span id="cb7-11"><a href="#cb7-11"></a> <span class="st">'.html'</span> => <span class="st">'text/html'</span><span class="ot">,</span></span> |
|
|
|
<span id="cb7-12"><a href="#cb7-12"></a> <span class="st">'.json'</span> => <span class="st">'application/json'</span><span class="ot">,</span></span> |
|
|
|
<span id="cb7-13"><a href="#cb7-13"></a> )<span class="ot">;</span></span> |
|
|
|
<span id="cb7-14"><a href="#cb7-14"></a> </span> |
|
|
|
<span id="cb7-15"><a href="#cb7-15"></a> <span class="kw">public</span> <span class="kw">function</span> preDispatch()</span> |
|
|
|
<span id="cb7-16"><a href="#cb7-16"></a> {</span> |
|
|
|
<span id="cb7-17"><a href="#cb7-17"></a> <span class="va">$request</span> <span class="op">=</span> <span class="va">$this</span>->getRequest()<span class="ot">;</span></span> |
|
|
|
<span id="cb7-18"><a href="#cb7-18"></a> <span class="va">$request</span>->setParams(<span class="cn">U</span>rl_Model_Url::fetch(<span class="va">$request</span>->getParam(<span class="st">'resource'</span>))->getParams())<span class="ot">;</span></span> |
|
|
|
<span id="cb7-19"><a href="#cb7-19"></a> <span class="va">$this</span>->entry <span class="op">=</span> <span class="cn">P</span><span class="er">4</span>Cms_Content::fetch(<span class="va">$request</span>->getParam(<span class="st">'id'</span>)<span class="ot">,</span> <span class="dt">array</span>(<span class="st">'includeDeleted'</span> => <span class="kw">true</span>))<span class="ot">;</span></span> |
|
|
|
<span id="cb7-20"><a href="#cb7-20"></a> }</span> |
|
|
|
<span id="cb7-21"><a href="#cb7-21"></a> </span> |
|
|
|
<span id="cb7-22"><a href="#cb7-22"></a> <span class="kw">public</span> <span class="kw">function</span> indexAction()</span> |
|
|
|
<span id="cb7-23"><a href="#cb7-23"></a> {</span> |
|
|
|
<span id="cb7-24"><a href="#cb7-24"></a> <span class="va">$this</span>->getResponse()->setHeader(<span class="st">'Content-Type'</span><span class="ot">,</span> <span class="va">$this</span>->getMimeType()<span class="ot">,</span> <span class="kw">true</span>)<span class="ot">;</span></span> |
|
|
|
<span id="cb7-25"><a href="#cb7-25"></a> <span class="va">$this</span>->view->entry <span class="op">=</span> <span class="va">$this</span>->entry<span class="ot">;</span></span> |
|
|
|
<span id="cb7-26"><a href="#cb7-26"></a> </span> |
|
|
|
<span id="cb7-27"><a href="#cb7-27"></a> <span class="cf">if</span> (<span class="va">$this</span>->getRequest()->isPost()) {</span> |
|
|
|
<span id="cb7-28"><a href="#cb7-28"></a> <span class="va">$this</span>->entry->setValue(<span class="st">'content'</span><span class="ot">,</span> <span class="va">$this</span>->getJsonPost())<span class="ot">;</span></span> |
|
|
|
<span id="cb7-29"><a href="#cb7-29"></a> <span class="va">$this</span>->entry->save(<span class="va">$this</span>->getRequest()->getParam(<span class="st">'message'</span>))<span class="ot">;</span></span> |
|
|
|
<span id="cb7-30"><a href="#cb7-30"></a> }</span> |
|
|
|
<span id="cb7-31"><a href="#cb7-31"></a> }</span> |
|
|
|
<span id="cb7-32"><a href="#cb7-32"></a> </span> |
|
|
|
<span id="cb7-33"><a href="#cb7-33"></a> <span class="kw">private</span> <span class="kw">function</span> getMimeType()</span> |
|
|
|
<span id="cb7-34"><a href="#cb7-34"></a> {</span> |
|
|
|
<span id="cb7-35"><a href="#cb7-35"></a> <span class="va">$url</span> <span class="op">=</span> <span class="va">$this</span>->entry->getValue(<span class="st">'url'</span>)<span class="ot">;</span></span> |
|
|
|
<span id="cb7-36"><a href="#cb7-36"></a> <span class="va">$suffix</span> <span class="op">=</span> <span class="fu">substr</span>(<span class="va">$url</span>[<span class="st">'path'</span>]<span class="ot">,</span> <span class="fu">strrpos</span>(<span class="va">$url</span>[<span class="st">'path'</span>]<span class="ot">,</span> <span class="st">'.'</span>))<span class="ot">;</span></span> |
|
|
|
<span id="cb7-37"><a href="#cb7-37"></a> </span> |
|
|
|
<span id="cb7-38"><a href="#cb7-38"></a> <span class="cf">if</span> (<span class="fu">array_key_exists</span>(<span class="va">$suffix</span><span class="ot">,</span> <span class="va">$this</span>->mimeTypes)) {</span> |
|
|
|
<span id="cb7-39"><a href="#cb7-39"></a> <span class="cf">return</span> <span class="va">$this</span>->mimeTypes[<span class="va">$suffix</span>]<span class="ot">;</span></span> |
|
|
|
<span id="cb7-40"><a href="#cb7-40"></a> } <span class="cf">else</span> {</span> |
|
|
|
<span id="cb7-41"><a href="#cb7-41"></a> <span class="cf">return</span> <span class="st">'text/plain'</span><span class="ot">;</span></span> |
|
|
|
<span id="cb7-42"><a href="#cb7-42"></a> }</span> |
|
|
|
<span id="cb7-43"><a href="#cb7-43"></a> }</span> |
|
|
|
<span id="cb7-44"><a href="#cb7-44"></a> </span> |
|
|
|
<span id="cb7-45"><a href="#cb7-45"></a> <span class="kw">public</span> <span class="kw">function</span> diffsAction()</span> |
|
|
|
<span id="cb7-46"><a href="#cb7-46"></a> {</span> |
|
|
|
<span id="cb7-47"><a href="#cb7-47"></a> <span class="va">$this</span>->getResponse()->setHeader(<span class="st">'Content-Type'</span><span class="ot">,</span> <span class="st">'text/html'</span><span class="ot">,</span> <span class="kw">true</span>)<span class="ot">;</span></span> |
|
|
|
<span id="cb7-48"><a href="#cb7-48"></a> <span class="va">$this</span>->view->diffs <span class="op">=</span> htmlDiff(<span class="va">$this</span>->entry->getValue(<span class="st">'content'</span>)<span class="ot">,</span> <span class="va">$this</span>->getJsonPost())<span class="ot">;</span></span> |
|
|
|
<span id="cb7-49"><a href="#cb7-49"></a> }</span> |
|
|
|
<span id="cb7-50"><a href="#cb7-50"></a> </span> |
|
|
|
<span id="cb7-51"><a href="#cb7-51"></a> <span class="kw">public</span> <span class="kw">function</span> postDispatch()</span> |
|
|
|
<span id="cb7-52"><a href="#cb7-52"></a> {</span> |
|
|
|
<span id="cb7-53"><a href="#cb7-53"></a> <span class="va">$this</span>->getHelper(<span class="st">'layout'</span>)->disableLayout()<span class="ot">;</span></span> |
|
|
|
<span id="cb7-54"><a href="#cb7-54"></a> }</span> |
|
|
|
<span id="cb7-55"><a href="#cb7-55"></a> </span> |
|
|
|
<span id="cb7-56"><a href="#cb7-56"></a> <span class="kw">private</span> <span class="kw">function</span> getJsonPost()</span> |
|
|
|
<span id="cb7-57"><a href="#cb7-57"></a> {</span> |
|
|
|
<span id="cb7-58"><a href="#cb7-58"></a> <span class="cf">if</span> (<span class="va">$this</span>->getRequest()->isPost()) {</span> |
|
|
|
<span id="cb7-59"><a href="#cb7-59"></a> <span class="cf">return</span> <span class="va"> |