Mandarin + Forage - An admin tool for phoenix

I’ve been working on two packages (not on hex.pm yet) to build admin interfaces for phoenix apps:

  • bureaucrat - which contains a bunch of generators to create very feature-rich CRUD views for your resources (with filtering, sorting and proper pagination out of the box)

  • forage (the name comes from the rummage package) - which does the bulk of the work by allowing easy creating of dynamic ecto queries based on plug query parameters. It also contains widgets which make it easy to create search filters, pagination widgets, etc. It acts as a bridge between the form in the frontend and the ecto queries in the backend. Unlike rummage, it’s coupled to plug applications, while rummage tries to be frontend-independent. I don’t think that in the current state of the elixir ecosystem decoupling it from plug is worth it.

Documentation is severely lacking, but those who are interested can look at an example project here: https://github.com/tmbb/bureaucrat_demo_app

The packages lack a lot of documentation, and I’ll work on that over the next weeks now that I’m happy with the functionality.

Roadmap

I’m planning on doing the following:

  1. Documentation, documentation, documentation…

  2. In particular, documenting forage’s functionality, because it can be useful outside Bureaucrat. I do try very hard to make it work as a black box, that is, as longa s you use the correct widgets in the frontend things should just work, but people who want to extend it need some guidance.

  3. Make Paginator (a library which I use for pagination) support ecto 3.

  4. Improving forage so that it is less dependent on the Repo. Everything should work with “raw” ecto queries independent of the Repo. That will probably require some PRs to the paginator package which I use for pagination.

  5. Make bureaucrat play better with many-to-many relations. That will require working mostly on forage, and only minor changes to Bureaucrat itself

  6. Make bureaucrat independent of JQuery, and find a better way to packaging the necessary Javascript. The current generated templates get all JS and CSS from a CDN and embed some inline Javascript to make the Select2 widgets work.

  7. Write a proper tutorial, based on [this tutorial](https://flask-appbuilder.readthedocs.io/en/latest/quickminimal.html, for the Flask application framework (which is a python project)

17 Likes

I see a problem here, as there is already one Elixir project named Bureaucrat.

1 Like

I could swear I had looked that up on hex.pm… Well, I have to pick some other name then.

EDIT: I accept suggestions

EDIT2: maybe I should squat on forage too, before someone claims it

Will it be like RailsAdmin? If so what about PhoenixAdmin? :003:

I have never used Rails (much less RailsAdmin), so I don’t know how it compares.

Additionally, PhoenixAdmin sounds like something very “official”, like if it were the thing everyone shoul be using. I don’t think I want that kind of responsibility :stuck_out_tongue:

But thank you for you suggestion. I’ll think about it.

1 Like

Understandable :smiley:

I’d definitely look at RailsAdmin for inspiration tho - although I haven’t used it myself for a while I remember how easy to set up it was, you could get away with almost zero configuration from what I remember, very plug and play :023:

There is a tradeoff between “being very plug and play” (AKA magic) and “being easily customizable”. If you look at the readme of the sample project above, you’ll see that it requires a couple of easy changes to your router and to your repo, but most of the work is done by phoenix-style generators.

Such generators will generate context, schemas, views, controllers and templates for your modules. This results in A LOT of extra lines of code for your project. I could cut back on it by encapsulating most of that in a macro, such as: gen_admin(MySchema), and that wouldn’t pollute your application’s source with the extra files. But then customizing that would be hell. Dumping literal files into your source is still the easiest way of allowing for customization.

I could save on lines of code by dumping files containing modules with lot’s of overrideable function calls. That’s what most python packages do: they have some classes that implement the full behaviour you want and make you override methods in order to customize the functionality you want. But having the source of the original “method” (in elixir’s case, function) in front of you helps with customizability.

2 Likes

I’ve taken a look at a demo app generated with Rails admin (here: http://rails-admin-tb.herokuapp.com/admin/). I support pretty much the same functionality, although not in such an elegant way. RailsAdmin probably does most of the work inside its classes, while I just dump code into your project. I think I support some things RailsAdmin doesn’t, like more specific filters, but that might just be the app I’ve seen.

The theme in my dashboard was copied from AdminLTE, which I got to know because of ExAdmin, by @smpallen99, without which this package wouldn’t exist, even though we share no actual code :stuck_out_tongue:. I’ve also taken inspiration from torch, which also uses generators and from which I’ve copied the frontend part of the filters (again, without copying any actual code).

My project is actually very similar to torch. I think the main difference is that I support association filters, while torch doesn’t, because my query builder (forage) might be a little more powerful than theirs (filtrex). I also like my templates better, even though they could use some improvement.

I should have acknowledged all these sources of inspiration in the Readme, but I didn’t have the time. I’ll be sure to include all sources of inspiration, as well as competing libraries and why I haven’t used them.

Also, something I’ve forgotten to mentions. Because this project uses normal Phoenix abstractions, it’s trivially easy to have muitiple admin interfaces. The admin interfaces are namepaced according to the context they refer to, and you could have multiple ones, such as Admin, Staff, Moderator, User, etc, each protected by different permissions and all of that.

1 Like

I’d use something like bureacratic or frontdesk.

Well, this is open source. Assuming a car doesn’t kill you on the spot one morning, you can always hand ownership over to somebody else. I see nothing wrong with a bit more official-looking name.

3 Likes

Go for an “adjacent” name then, for example:

The Bennu is an ancient Egyptian deity linked with the sun, creation, and rebirth. It may have been the inspiration for the phoenix in Greek mythology.

just as an example.

3 Likes

Very interested in this. Using ex_admin now, but the projects dead.

Do you have screenshots for the admin interface?

I think it would be better not to generate code, but use macros that generate the code. It’s way more maintainable, otherwise you’re stuck with a lot of scaffolds in your app, and it’s way harder to upgrade when there is more functionality. Just provide the minimal information to be able to build the admin interface. I think ExAdmin in that sense is pretty neat (execution is not great though).

1 Like

I have been spending the past few days looking for a good admin package. I’m glad you posted this now!

As far as names go I recommend PhAdmin!

ExAdmin’s (questionable) execution is a direct result of the way it uses macros instead of generators. Using macros makes it terribly hard to customize the admin interface.

In my experience with python admin interfaces which use classes with overridable methods instead of clde generation, there are so many little things you want to customize that generating a scaffold feels like a good solution.

Bureaucrat is quite minimal. I expect most improvements to come from the forage package, which is a normal package with normal functions (no generators). Your scaffolding will remain useable even if forage is upgraded.

I can add both screenshots and a video for the more dynamic parts.

I like this a lot!

1 Like

In particular, I think the templates should always be generated using generators and not macros, because you end up customizing those a lot.

I can see the controller and the context accessors being generated with macros, though.

The controller could just be:

defmodule MyApp.Admin.UserController do
  use Bureaucrat.Controller
end

with the controller actions implemented as defoverridable functions which the user could override.

I could have the same for the context accessors:

defmodule MyApp.Admin do
  alias MyApp.Admin.User
  use Bureaucrat.Context, schema: User
end

again with defoverridable implementations for the functions, and maybe some options like:

defmodule MyApp.Admin do
  # read-only context
  alias MyApp.Admin.User
  use Bureaucrat.Context,
    schema: User,
    except: [:update, :create]
end

I’ve decided to go with generators that generate everything without many macros brcause the architecture tha Phoenix 1.3+ pushes forward is very dependent on generators anyway.

Not that modules are “hidden” behind contexts, I have to generate a context file with accessors and a new model (i.e. schema) inside that context. That lends itself naturally to the use of code generators like the default ones that Phoenix provides.

What I could do is have a switch (say, --magic or --succint) which would cause the generators to generate very minimal files with macros like the ones above. I would accept a PR with that functionality, but I’not going to work on it myself for the time being.

I have more important things to do, namely add tests to both packages. I’ve been “testing” them only with end-to-end manual integration testing (aka playing with them)

2 Likes

Do you plan to generate JavaScript files too?

I’ve been working on similar project with the same approach but the front-end is going to use JS + jQuery + plugins.

The default template includes JS from a CDN and some custom (constant) Javascript. I haven’t had the need to generate any dynamic Javascript.

1 Like

More specifically, I need JQuery for the Select2 widget, which I need for my association picker. The JS for both JQuery and Select2 coms from CDNs. The little snippet of custom Javascript I need to customize Select2 for my needs is embedded directly in the template.

JQuery is quite a big dependency, but all good custimizeable select widgets I’ve found depend on JQuery. I need one of the good widgets because I need features like remote data access (to load associations from the server) and the ability to customize pagination. That way you can use these widgets, tables with millions of rows and suff like that.

You might want to contribute to either forage or bureuacrat, then. Documentation is not the best, but it will get there.

1 Like

A question. Let’s say I have table A with a one-to-one relation to table B. In the crud page for A, I need a select witdget to (dynamically) request models from B. That requires an HTTP request to a controller. My question is: should I request it to the ControllerB, or should I have a special action in ControllerA?

Currently the functionality to request the B data for the select widget lives in ControllerB

This isn’t set in stone, of course. I’m asking about what the generator should generate by default. The user can always customize the generated controller and routes.

I should explain why I’ve started my own admin framework instead of contributing to one that already exists.

The main reason is that I didn’t like any of the current dynamic query generators. Some used String.to_atom() inplaces where it was hard for me to audit. Some used a format for query parameters which I didn’t like. Some had good functionality in the backend but no easy-to-use widgets in the frontend to generate those queries (my forage library couples the frontend to the backedn by design; in theory the user doesn’t even need to know the query format if we’re talking about a Plug app). Some had little to no documentation.

Regarding the admin generators themselves, Talon would be the top contender, but I’ve found out that the use of Slime templates overcomplicates the design. My library only supports “vanilla” HTML. Indentation-based languages get very complex very quickly, and until I find one I’m satisfied with, I don’t want to supoort such templates. Talon also doesn’t support pagination in many-to-one relations, and supoorts much simpler filters than I do. With some advantages, of course, like real-time search. I intend to implement it when Phoenix LiveView goes, well, live, if it turns out to play nice with the JS widgets I need. Also, one of the design goals of Talon is to be a “general frontend for CRUD apps”. I have no such pretentions. Forage is a general dynamic query builder. Bureaucrat is a (very customizable) admin framework. If you want a CRUD framework on top of forage, you can implement your own.

Another strong contender is torch. It uses generators (like Bureaucrat), but it has some problems. Templates are more verbose, and filters don’t seem as powerful. I also didn’t like their query builder very much.

Anyway, when I assume that I need a new query builder, it makes sense to couple the admin “frontend” to the query builder.