 All right, it's time to get started. So we're here today to talk to you about horizon dashboard plugins, kind of give you a deep view of where we're at and where we're headed, some of the things that you can do with them. Agenda, we're going to talk about them. We're going to talk about where we're going, some new stuff in them, and then also a new use case and a demo. So plugins and horizon, just a little timeline. Horizon officially became part of OpenStack way back in Essex. At that time, customizing horizon was essentially either building a custom Django app on top of horizon, hacking directly into the horizon code, or using a customization module, which was basically a glorified tool for doing monkey patching. In the Ice House release, we introduced the first rev of plugins for horizon. It was fairly crude support, but you could support Django content in the plugins. And we've slowly been adding to that since then. You can see in Juneau, we added some support for collecting of static files, CSS, JavaScript, images. Also support for AngularJS modules, as well as including new exception types. So if I'm talking to a different service, it's a returning exception from the API that Horizon currently didn't know about, you could add that as well. In Kilo, we added some testing features. So for Angular code, you could actually write a spec file. So that spec file could be run as part of Horizon's test suite. And then in Liberty, we added some auto-discovery of JavaScript. For a while there, you were having to line item every JavaScript file out in our plugin file. So this was a great simplification. We're collecting CSS now, as well as... We also transitioned to having all of our content being loaded via a plugin, becasism. So we're not only trying to push this on you. This is actually what we're using internally. And that's true for the content. Our API connection is not exactly using the same mechanism, but at least all the content is coming in. So it's our commitment to it. So what can you do with the plugin? We're going to cover a little naming just to make sure we're all on the same page. Horizon has some names here that are not standard, but I hear them referenced differently all the time. So we have some navigation elements that you can see. The top level navigation element is a dashboard. And all that is a container for content, essentially. It's a way of subdividing. Inside of those, you have another level of subdivision, which is a panel group. And then below that, where the content actually lives, are called panels. And so the top two are navigation. And once we start getting down to the panel level, that's where we're starting to talk about actual content. And in the plug-in scope of things, the panel content is fair game. Once you've specified the panel, you can put whatever content on there you want. You can have it be JavaScript-driven. You can have it be Python, Django-driven. You name it. So what can we do with them? Well, you can add dashboards, panel groups, or panels. We're just pointing out I've added a new panel group called tools and a couple panels in there. So we can add content. We can remove content. So if we go back one, there we go. The bottom, there's a whole identity dashboard. Perhaps that doesn't pertain to my deployment. With a very simple edit of an enabled file, we can eliminate that whole dashboard from even loading into the UI. So nice feature. And then you can reorder them. That's one of the features, too, is you can move everything around. So in this example, I've moved the identity dashboard up to the top, and I moved the project dashboard down to the bottom. But you can do that with individual panels or panel groups, or you can even move a panel from one panel group to another panel group. Basically just the way to say, this is how I want my views to be laid out. And then the other thing is, so maybe I created this new panel on the tools panel group. I think this is what everybody should land on when they come in, because it's the most important thing. So I can specify that as the default panel. So when somebody comes and logs in, that's the first panel they see. You can do the same thing with it. Well, you can do it per dashboard, and you can also set a default dashboard. So how do the plugins actually work in Horizon? Well, they're an installable Python module, or Python package. There's a little bit of wiring in a file. It's called an enabled file. It kind of specifies the contents of your plugin. And then that gets tied into Django's applications. So you're basically adding, Django uses the term application to specify content. And so what you're doing is adding content. You're adding a new Python module to the content list. And Horizon's taking care of this behind the scenes. That was bad. Hold on, hold on. Silly Max. All right. There we go. Let's do that. So what's the structure of a plugin? Like I said before, there's an enabled file. We happen to call them enabled files just because they ended up in a directory called enabled. Oh, yes. Yes. So you're going to create... What's that? What's the name of the package? No, you're going to create the package. So you're going to create an installable Python module. So that's what this is. The plugin there is the plugin. So you're going to want to have some packaging information in there so you can create a Python package. You want an enabled file that's going to specify the content. And then basically it's a bunch of links to how Horizon should interpret what you've just presented to it. And then there's actually the content. And so in this example, we have an API... So in Horizon, we have something called the API directory where we handle all the connections to the services. So we just duplicate this in the plugin. You can name it whatever you want. So in there, we have something that would talk to a different service. It's probably not already an open stack. And then we actually have the content below that. It's typical Django content. Again, you can... As long as you're getting to the point where you can render data with it, it doesn't matter if it's pure Django or if it's mostly JavaScript. So that's the typical content. Again, it's just you're taking some content, an enabled file, and packaging it up so you can go install it somewhere in the Python path on your system. So we'll go... There we go. That enabled file. So again, that's how we do all the wiring. So I was going to... There we go. Yep. Ah, yes. Good point. Yes. So I said you could order all the content. That number is basically specifying the ordering. So each of the enabled files has a number, and that tells it how to plug in. There's also a hierarchy of how these files are loaded in. Actually, it's more of a numerical order, so one would load before 2000. And then we have a couple of different directories where those get loaded into. And I'll get to that in just a second. But yeah, the number is significant in the fact that it does specify where. And if I specified that exact same file name in a later loading directory, it will supplant the content. But we'll cover that in just a sec. So some things that we can set up in the plugins. This is actually the entire list of potential settings at this point. The first one is the most important. It's just specifying the application or the actual module that you're loading into Horizon. The exceptions we talked about, some angular stuff we talked about, JavaScript. So we're just specifying content. The auto-discovery one Sean will talk about in just a few minutes. But that's a new thing that kind of gets rid of the ad.js files to a certain degree. And then you can specify whether you want it to be enabled or not, which we oddly named disabled first. But that's it. That's basically all the content you're putting in. So the first one will look like a Python path. Let's see. Let's go back. Yeah. That's basically what I'm telling you about that. And so there's two methods of actually using the plugin. There's a very simple one that is you can copy that file, the enabled file. So in that example, it was underscore 2000 logpanel.py. You can copy that into the local enabled directory inside of Horizon. When you do that, when Horizon boots up or starts up with Apache, it goes and it walks through that folder and it'll see that there's a new file on there and it'll go and try and resolve all the paths and pull that into Horizon. So that's how you get the content in. There's actually a more powerful way to do it. It's a little more cumbersome. But this is all settings based. And so for a lot of deployers, they don't want to have to muck with the file system as much as they want to be able to drive it through settings. And so in the second part, you could edit your local settings file to pull in the plugin itself. And then with this addition, you can actually do use policy files and setting specific to the plugin. That's missing in the other method. So it designates an example here where they do have a policy file and they've built a plugin with it. And so they use the second mechanism so that they can guard actions with policy so the user doesn't see things that they can't actually do. I promise this to say something about. No, it's OK. So where are we? This is where we are at with, so there's a huge family tree of plugins for Horizon. I have all the content that's actually delivered in Horizon in that box. So we're supporting 10 services inside. I went out and actually looked at the OpenStack namespace in GitHub. And there's some names in there I don't honestly recognize. But it's obviously a popular interface. And this is the mechanism they're using to get content in. As we move forward, the Horizon team is actually going to concentrate more on shared componentry inside of Horizon and move some of the content, like Trove and Sahara, outside. Because it's not really a shared resource. And so Trove and Sahara are moving very quickly. And they want a lot of content changes. And that's wonderful. We have a relatively small size team. We can get changes in, but not as fast as they would like them. So they should be in control of their own content a little bit more. So in the next cycle, we'll actually move those out into separated plugins as well. And that's, and that only has to focus on making Horizon a better toolkit to support the plugins even better. So what's new in Liberty? Sean, we'll take it over from here. Static file auto discovery mechanism for the plugins. All the static files that is for the client side, the computing and the rendering, are get discovered automatically and organized in the right order and get populated into the production page and test runner for you. Static files include JavaScript file that is using Angular framework and HTML file that has a Angular templates and SSS files. And all the Angular model names will be added to the system automatically as the dependency. So the only JavaScript get automatically ordered based on naming convention. Now JavaScript files that define Angular module will be named as .module.js. And the files that define unit test will be named as .spec.js. And the files provide mock object for unit test will be named as .mark.object.js. And all the other JavaScript file that defines Angular providers will be named to something else. And the order will be in production models will come first and then providers. In test runner, it is models and providers and mocks and specs. There is no need for manually to maintain a JavaScript list, which is tedious and error prone. To enable or to discover for your plugin, it's simple. You put all the Angular module names in the add Angular modules list and put your SSS files in the add SSS files list. And just put all the discovery static files equals true and the system will do the magic for you. OK. Thank you, Sean. Thank you, David. So, put on the sign, come on. All right. So David talked about how easy it is for you to add your own custom dashboard and your own custom panel. But it leaves a lot more that we desire. And Sean just recently covered his auto discovery stuff, which allows you to basically dynamically locate static resources in your project, right? So when you take these two and you combine them, what you end up with is Jrumroll JavaScript plugin. OK. So how does that work? What we're going to do is we're going to walk over a use case and then we're going to dive into the JavaScript plugin architecture. OK. So keep in mind that this use case here is actually a real world example. This is a problem that a real team is facing. This is not a hypothetical situation. So let's assume that you have team A, I mean, sorry, blue team and red team represented by David and Sean here. And they both want to modify an existing workflow. Blue team wants to add its own custom step and red team wants to add its own custom step. The only way to do that today would be to, one, you have to modify a source. Or two, you have to monkey patch. Now, both of these methods, you have to basically do your own manual resolution, right? And this is not ideal. We want to be in a scenario where blue team can do its own thing, red team can do its own thing. They both do their pip install and everything just magically works. OK. So before we do that, we're going to, again, level set and just cover some common terminologies. Actually, this is the wrong slide. This is the correct one. OK. So what is a wizard? Well, everyone's familiar with a wizard, right? I mean, you've done that probably throughout your life with Microsoft Software Installation. It's pretty much a step-by-step guide that you have to sort of fill out along the way. And then at the very end, you can take some kind of action. What does the code for that look like? It's actually really simple. The wizard has four properties. It has close, cancel, submit, and workflow. A submit is really just a callback function. So when you click on that submit button, you do something, right? Workflow, that's really just a JavaScript object that tells the wizard how to draw things. So we're going to cover that next. What is a workflow? So a workflow, again, is the thing that you see on the left, whatever the visual manifestation of that. And what does the code for that look like? So workflow is a service. And think of a service as a glorified singleton JavaScript object. And with the JavaScript object, you can easily manipulate that. With a workflow, you have a title. You have some text for your buttons. And most importantly, you have a list of steps that you're going to have in that workflow. And these steps are really just composed of a title and a template URL. So in this case, this step has a controller and an HTML template. We're going to cover what kind of content you should have in that controller. It's really easy. So if you think about it, this is MVC, right, at its basis. The controller is where you can pull in your data and it binds it to the view, which is your HTML page. And in this controller, what we're doing is we're saying, hey, I have a user object that's initially blank. And as I'm filling out the form, this user object is going to get populated with key value pairs. And we also have a watch collection on that user object so that every time this user object changes in property, we are aware of the change. And we broadcast this change to the parent controller. So this is really important. And we'll cover why that is important in a little bit. So again, this is a UML diagram of what we just talked about. At the very top, you have the wizard motor controller, which depends on a workflow service where you would define your workflow with a list of steps. And these steps then point to these individual components that you see here in the green region, OK? So how does that relate to what we have today? Today, we have tables. And at the very top here where it's highlighted in red, you'll see that you can click on this button to basically launch this wizard, OK? This is called a batch action. And again, it follows a similar model where you have a table controller, which has some HTML. The table controller populates data, which then gets shown in that HTML page. And then this table controller depends on this batch action service, which is an object that contains a list of other services, in this case actions, right? We have a create action service and a delete action service that this batch action includes. So when we put it together, what we end up with is this really big diagram. But it's really easy if we take it one step at a time. What we see is all the services here that are marked with a jigsaw puzzle, they're extensible. You can extend them. You can override them. You can manipulate them. And the things that we see in the purple and green regions are additional things that we can add to it. So in this case, you can add additional actions and additional steps, OK? This will be a little bit more clear in a little bit when we go over the demo. So I'm going to hand it over to Cindy to actually show you how to codify this. But keep in mind, in the back of your head, the use case that was covered earlier, because the demo would address that. OK, so before we take a look at the demo, let's look at the create user action which we're going to modify. OK, so this is the same table that Ty showed earlier. So if you click on this batch action, or this action, you'll see that we're using the wizard and the workflow is broken down into two steps. User details and select projects. So user details is the basic bare minimum information that you need to fill out in order to create a user. So if we do that, let's call our user Yoda and then password, enter a password in. So if we enter in a wrong password, it immediately tells us that we entered in a wrong password. Whereas before in our create user modal window, we had to create on the submit button in order to make a roundtrip call to the back end to check that the password was entered correctly. So let's put in a correct password. OK, so once we filled out the bare minimum, we see that the submit button is enabled. But let's take a look at the second step. It's called select projects. So here we can assign our user to multiple projects. So let's assign our user to an admin project and a demo project and change the role to, let's see, change this role to service. And this is a feature that has been in Keystone API, but it was Keystone V3 API. But it was not implemented in Horizon before. But now with Angular, we can enable that. So we filled in all our information and let's create our user. So our user was created instantly. As you can see here, no lag at all. Let's go to the projects panel to make sure that the user was created successfully. Double check. So if we come here, we can check our manage members. And in here, yes, we see Yoda is created successfully and also in admin. Yes, we have our service role for our user. OK, so before we go into the demo, let's recap our use case again. We have the blue team and the red team. And they both want to add a step to the workflow that we looked at earlier. And we don't want them to have to modify source code or have to manually merge conflicts between each other. So I promise that it works. And we will show you in a live demo. Fingers crossed that it works. OK, so we have our blue plugin and red plugin. These are separate from the horizon folder. I've put them on my desktop. So we'll take a look inside. These two are exactly the same except for the name. We have blue and then red. That's the only difference. So if we take a look inside our plugin structure, we see that all our files are under the static folder. This is how our client-side code is organized in Horizon. And this enables us to auto discover all the static files. This is a feature implemented by Sean. So if you write your Angular plugin, make sure to keep all your files under the static folder. So let's take a look at our first file. So in here, our module is referencing the identity user's namespace. And here, on line 26, we inject in the create user workflow that we want to modify. Once all the modules are loaded and all the dependencies are resolved, we will run this block of code down here. So we define our step, as Ty referred to earlier. So this is a new step that we want to inject into our workflow. So we have ID title template URL refers to the HTML content we want to put into our step. And help URL is some content that we want to put into our slide-out panel on our wizard. And once we have this, our create workflow service is making use of Justin Pomeroy's extensibility service, which allows us to append, prepend, replace, remove the steps. And in the future, we want to use this extensibility service for other UI components, like batch actions and actions. So we use workflow, and then we're going to prepend our steps. So we have two steps right now. We want to add a new step to the front. OK, so we're going to take a look at our template HTML file. So let's take a look at that. So here, this is just the content for our blue step. In the first line, we have a reference to our controller. This is where we handle the view model logic. Our second line is just the title of our step. And we use a translate attribute. This attribute was implemented and released in the Liberty cycle. And this allows us to collect and translate all our client-side text. So blue plug-in can be translated. And down below, here's some caption for our step. And we have two input fields, favorite color and favorite food. Very basic. So let's take a look at our help content. So this is just plain HTML. And we're using our translate attribute again, as you can see. And last but not least, the most important file for our step is the controller. So in our controller, we are injecting our wizard event. This allows us to intercept our data. And then we can modify this and or take this data and send it off to our own external service to do something with it. So in our simple example, we are just going to capture the events and just print out some trivial messages on our browser console. So we went through all the files in our plug-in. Red is exactly the same, except for instead of using prepend, we used upend. So it'll be after the steps. So now, OK, to bring these two plug-ins into horizon, we wrote a really short little script and we'll kind of talk about the script before we check out, see everything in action. OK, so this is our short little script. So in here, we are going to go into the blue plug-in and red plug-in. And we're just going to tar up these two files. And then we're going to copy over our enabled file from each of these into horizon. These files are read up at startup. And then that allows us to register the plug-in to horizon. And then we're going to pip install these two tar files and then start up our test server. So if everything goes well, this should work. So we're going to run our short little script. OK, it looks successful. No errors. So let's go to our browser. So we're going to open this up again. OK, ready, everyone? Drumroll, drumroll, please. OK, boom. We have, instead of two steps, we have four steps now. So the blue step was prepended and the red step was appended. So in our blue plug-in, we see the text is blue. And then we have our two input fields that we saw earlier. And then if we go to red step, we see that it's the same except the text is red. And then if we look over here, we have our help text as well. It was just basic HTML. So let's close this up. Also, if we look at our browser console, we'll see our events that we triggered. So we see wizarded switching steps. So we were clicking around so those registered over here. So you can see how powerful the extensibility service is. And now you have finer control over all your HTML components. And if you want to customize anything now that you can, this is a feature that a lot of users wanted before. And now it's possible. And let's hand it over to Ty for a recap. Thank you, Cindy. So a recap. OK, how do I get back? OK, if you want to try out that demo, it's a little complicated. But all these things are in review right now. But you will need to merge these two patches and then download Ty's plug-in example. And then follow the readme to install the plug-in. Thank you. All right, quick recap. All right. So it was obvious from the demo that there were performance improvements. When you click on that user, it was created instantly. Amazing. OK, it addresses the complex use case that we kind of covered earlier. It also allows you more granular control over your content, enables parallel development. So a red team and blue team, they don't have to fight for stuff. As you can see, we have customization hooks. There were a lot of events. So you can register too. And again, this flexible and modular architecture can be extended to other things like table columns in the future, perhaps. Our resources, you can find all four of us on IRC. Here's our IRC channel. We also include our email. And Cindy here is a volunteer to answer all your questions. So just hop on. And lastly, I just wanted to have sort of a leaving note, I guess. Right now is a really good time to be a horizon developer. We have a lot of really exciting things going on. And this new plugin thing really opens the door to a bunch of new, cool stuff. So if you're starting from scratch and you're a plugin developer, consider using this model. And if you're an existing plugin developer, consider migrating to this, maybe. And if you're a key decision maker, we really hope that you can continue to enable your folks so that they can contribute upstream to this great success. Thank you. We're going to open up to questions. You can step up to the mic if you have questions. That'd be great. May. So I have some architectural question. These steps in the wizard workflow they seem completely independent. Is there a possibility for one step to rely on some data from the previous steps? I'm curious. So the answer is yes. That's entirely possible. That's why we have that event registration mechanism. So if there's another step that depends on the previous step, you can register to the event. Does that make sense? But yeah, they are completely independent. You can treat them like such. All right, any other questions? Are you all too dazzled? I have one. OK. So let's say that if you want to create a plugin for an external service, can you get some identity information from OpenStack so you can use that as the credentials for the external service or something like that? That's entirely possible. So we have API for Keystone, right? So you can invoke that, and then you can register to certain events so that when you're creating this user, you can hook onto that event and then point that to your external plugin for future processing. Thank you. In a workflow, when I have a step, is there any mechanism wherein I can get the value which is in that step before actually submitting the workflow? Actually, you can, for example, in the launch instance, we have a centralized model for the owner steps. So when you put the submit button there, you will get the data from the model. The model is not specific for each step. It's class owner steps. But if you want, you can also put every step as independent of its own model. It's totally up to you. I do want to be clear. We are supporting two separate frameworks at this point. And that is true for the Angular workflow. There's still a Django workflow that doesn't support any of these new additions. So that's one of the reasons we're really excited about Angular is the ability to do this. Thank you very much, everybody.