LiveAdmin - Phoenix admin UI built on LiveView

After working on it for a couple of months and using it in production for most of that time, today I’ve released LiveAdmin, a LiveView based Admin UI for Phoenix apps.

Not much to say about it, it follows in the footsteps of Kaffy and ExAdmin before it to provide a pre-rolled “administrator” UI for Ecto/Phoenix apps.

The main thing that sets it apart, aside from using LiveView, is that it has native support for multi tenant applications. I built it primarily to solve my own use case and for the sake of my own curiosity and learning, but its design is also strongly informed by the intention to remain as lean and simple to configure as possible, while providing powerful escape hatches in the form of full view overrides.

Although it is solidly usable now, it is still essentially in the prototype stage, so I would advise trying it out mainly if you have time/energy for a bit of experimentation. That said, I’d really appreciate any testing/feedback and opinions about how to do things differently.

What it has now:

  • Basic CRUD
  • Schema fields autodiscovery
  • Search
  • Editable embeds
  • Editable belongs_to associations via search
  • Resource and record level operations
  • View overrides

Planned:

  • File uploads
  • Dedicated show view
  • Easy CSS overrides
  • Better default CSS
  • Form input overrides

Cheers, and thanks in advance for your thoughts and suggestions.

39 Likes

is there an example repo showing it’s abilities?

At the bottom of the project readme, there is a link to a demo phoenix app with example configuration. You can also run that app locally, but I still need to document the steps for that. For now the easiest way to test it out if you already have a phoenix app set up to use live view is to just add it there, since it is only a few extra lines of config.

3 Likes

Just released 0.2.

Mainly consists of various minor improvements and bugfixes, but it does contain a fix for a somewhat nasty issue that can cause form views to hang, so I would recommend updating.

7 Likes

Hi @tfwright,
Thanks for this super handy library, the setup was a breeze! And the ability to traverse/update belongs_to associations is awesome.

Looking forward to your planned features, in particular - css overrides. (I have already been asked about a ‘dark mode’ :P)

A way to customize the display of particular fields/ecto types would be nice, maybe I missed it in the docs…

I have a bit of UI/display issue with UUID fields out of the box -
image

I dont really care to see the value - but a way to copy it to clipboard would be slick, and maybe just an icon with a title attribute to show the value on hover would be a better way to squeeze it in the cell. A simpler approach might just be truncating via css Text Overflow - Tailwind CSS

Might make a PR if I can figure out a way to pull it off…

Thanks again for your great contribution to the community!

2 Likes

Hi @harmon25, thanks for the kind words and feedback, it’s great to hear someone else is finding the lib useful.

CSS overrides should not be far off at all. Right now CSS is read from a file that is compiled and stored in the package. I think it would be fairly easy to support an option taking a path to any other file to read instead. I can certainly make that a priority in terms of the roadmap. And a little further off, configurable through the UI itself so people don’t have to fight about light vs dark mode :slight_smile:

And agreed that the current UI is not optimal for binary ids. I like the idea of a copy button but I’m wary of cluttering up the table UI too much. I was thinking of adding a modal “show” view though where copy buttons would find a good home.

Let me know what you think, and of course, PRs always welcome.

1 Like

Thanks for this library @tfwright – I agree, things were really easy for me to set up!

The only snag I ran into was in dev while also running the phoenix_profiler, but disabling it resolved everything. I can file an issue with the stacktrace I saw if that’s helpful!

It seems like the best way to display the list view for a resource with longer text fields is to create a custom component for those views, is that correct?

How would I go about adding parameters like slugs to the live_admin route? If the route for my resource is /resource/:slug, I would love to tell our users to “just put /admin at the beginning of a resource you want to modify”, so that the resulting admin view is /admin/resource/:slug.

Thanks for your work so far! I see this being really helpful for our team.

1 Like

Hi @jondelmil thanks for trying LiveAdmin out and glad to hear it’s working well for you so far.

Please do open an issue. Of course I’d like to maintain as much compatibility as possible with other libraries, or at least be able to add advice to the README.

It depends on exactly what your need is, but yes, generally the default list view does not make it easy to display the full contents of larger fields. Right now the index is not actually overridable yet so I will add that to my short term todo list as well. Although I’d like to improve the CSS to alleviate the issue short of overrides, which I’d like to be a last resort for real corner cases, and this sounds like a common problem people are facing.

I can’t think of a simple way to handle fully customizable urls right now, although for slugs specifically I think it would be feasible to add an option to customize the id field. I’ll add that to my list as well.

1 Like

Released v0.3.

Notable changes:

  • Fixes enum fields with custom mappings
  • Restructures CSS and exposes override config
  • Adds copy functionality to index
  • Adds override support for index component

As part of the CSS restructuring, color-related CSS declarations were moved to a separate file, which can serve as an example for changing the “theme.” @harmon25 hopefully that facilitates the addition of a dark mode. If you have success with that customization feel free to share it here!–and/or let me know where it could be improved.

After experimenting with a modal “field inspector”, I ended up just adding a copy button. I am still weighing adding a show view, but implementing a modal accessed from the index view introduced too many UX snags so this seemed like the least intrusive way to solve that use case.

Overall I feel like I am really at my limit UI-wise so if anyone has strong opinions about how to improve things I am all ears…I think I spent as much time on the CSS/JS underlying these features as I did on the whole initial prototype :sweat_smile:

2 Likes

Nice release!
The copy feature is super handy!

I just finished with some css tweaks that were pretty easy, here is an example for other folks.

admin_css = """
body {
  /* dark body, with white text */
  background-color: #18181b;
  color: white;
}

.nav {
  /* think a fixed width is better on the nav, was taking up too much valuable space otherwise */
  width: 250px;
  /* slightly lighter colour for the nav */
  background-color: #27272a;
}

/* since the nav is now a fixed width - the content should `flex-grow` to fit remaining width*/
.content {
  flex: 1;
}

/* this is the same as the nav colour*/
.resource__header {
  background-color: #27272a;
}

/* this is all still default, but could be tweaked further */
.resource__action--btn {
  background-color: rgb(67, 56, 202);
  border-color: rgb(67, 56, 202);
  color: rgb(243 244 246);
}

.resource__action--btn:hover {
  background-color: rgb(55 48 163);
  border-color: rgb(67, 56, 202);
}

.nav a:hover {
  background-color: rgb(165 180 252);
}

.toast__container--error {
  border-color: rgb(239, 68, 68);
  color: rgb(239, 68, 68);
}

.toast__container--success {
  border-color: rgb(102, 153, 0);
  color: rgb(102, 153, 0);
}
"""
# pass the override to live_admin app config
config :live_admin,
  ecto_repo: App.Repo,
  css_overrides: admin_css

I think it would be helpful to supply a field_name → class map - for supplying custom classes for each td on a resource row.

Would resolve field → class here - live_admin/index.ex at main · tfwright/live_admin (github.com)

This would allow truncating specific fields like Id's to keep them from wrapping on multiple lines.

Let me know what you think, I might be able to hack together a POC if you like the idea…

1 Like

I think it would be helpful to supply a field_name → class map - for supplying custom classes for each td on a resource row.

This sounds like a great idea!

Generally speaking I think adding classes to any/all elements that wrap what can be considered a UI “unit” (admittedly not a super well-defined category) makes sense to maximize what can be achieved through CSS overrides.

1 Like

I created a fork - and sent a PR that fixes something I noticed in with the custom css applied.

I’ll take a look at adding more custom class stuff in future PRs!

1 Like

v0.4

Very small release with a couple of improvements:

1 Like

v0.5

  • Support for decimal field types
  • Switch to component-focused override model

The second is a major change that will likely break things if you are using the old components option. Originally I conceptualized this as a “view” override using a render function MFA (similar to other option values) but that model turned out not to make much sense with LiveView, because views are by default fairly tied to their components if there is any state involved, which, in the case of forms, there is a good deal. So I decided to change the option value to a module instead, which makes it much cleaner and more straightforward to customize the form behavior when needed.

Migrating to the new model should not be difficult, all that should be required is to move your custom render function into a LiveView component module and pass that as value to the component you want to override instead. I updated the dev app with a more useful example of how and why to do this. Basically, the main non-cosmetic reason I’ve found is to be able to use virtual fields (like passwords) in the form. While working on this I considered some ways I might be able to support those automatically without overrides but for now this is the best approach to handle things like password encryption in form actions.

4 Likes

Forgot to include it in my post above, but v0.5 also includes this: switch to type specific classes for index table cells · tfwright/live_admin@b269877 · GitHub

v0.5.1 fixes an issue in the last release that broke compilation.

1 Like

v0.6

For this most part this is a maintenance release with various minor bugfixes and a somewhat hefty refactor of some of the business logic. However it also includes some UI changes that might break CSS overrides, as well as a config change that brings the home page override API into line with the other views, so there’s a decent chance upgrading will require changing some code.

2 Likes

v0.7

Mostly QoL improvements but there are some notable changes/features:

  • LV dep updated to 0.18 - if you are including as a separate dep you will need to upgrade
  • Configurable list field rendering - previously all table cell contents were simply ‘stringified’ field values, now you can control how those fields are rendered. as usual the dev app has examples
  • Add/remove embeds - no longer restricted to editing existing embed structs

There are also various UI changes, mainly an “actions” columns is now prepended to table rows for easier access to actions. Initially these were at the end of the row, and then moved to a per-cell drop down. I moved them back to a separate column mainly to simplify/facilitate markup in custom cell rendering functions. If anyone with stronger UI skills and/or opinions wants to take a crack at improving the look and feel, DM me :pray:

There are some nice features in LV 0.18, especially the app level layout, that I think will help with feature I have planned around user settings, like css/themes, default prefix, etc, so that should be coming up next. If there’s something else you’d like to see, let me know here or in GH issues :zap:

6 Likes

0.8 is still in progress but, just a quick note because I have gotten a few questions and bug reports about this:

I have published a few bugfix releases so if you have encountered issues on 0.7.0, especially related to Phoenix/LV deps, I would recommend updating to 0.7.3. As any of you using LV in your own apps know by now, recent versions of Phoenix core libs contain a lot of changes to dependencies and several of them affect LiveAdmin.

In particular, if you are trying to use LiveAdmin with LV 0.18.x and/or Phoenix 1.7.x, please make sure to make these 2 adjustments to your Phoenix app:

  1. Ensure route helpers are still enabled by removing helpers: false option from use Phoenix.Router if necessary (should only apply to apps created with 1.7)
  2. Ensure you are including phoenix_view in your deps

If you have completed both of those steps and are still encountering errors with LiveAdmin, feel free to open a new issue–but please use the bug report template with as much info as possible as these problems can be very hard to track down without adequate info. :pray:

Even better, if you are feeling brave try out the changes in main which should have better compatibility with Phoenix 1.7, obviating the need for the 2 step patch above. :muscle:

1 Like

Finally releasing v0.8 today :balloon:

Lots of subtle and not so subtle UI changes in this release, including a revamped nav menu. But also, if you’re not a fan of the changes, the nav menu is now one of the components that is available to override :+1:

The new nav bar also contains a new link to manage the Session. LiveAdmin has had a session concept since the beginning, but until now it has been used only to store the prefix to be passed to function overrides so they would be aware of which tenant to run queries in. The new view exposes the session object, including a new metadata key, for editing/adding arbitrary data (using a new MapInput component for map fields). Since the session should be passed to any component/function overrides, you can use it to customize behavior for the current user in all sorts of ways. For example, the css_overrides config now accepts a MFA and the session will be prepended to the arguments. This means you can implement “css themes” using a function that inspects the metadata.

This session has been backed by a very simple Agent so that if you closed the window and later returned, it would remember which prefix you were using. However, due to the state being in a GenServer, this would be erased when that process ended, for example after restarting the app. 0.8 introduces a new session_store config, which allows you to provide your own module implementing the behavior for storing/loading session state. Since the init! callback receives the current conn object, you can use it to identify a logged in user and load/persist their session state to your app’s repo, for example a Postgres DB. That way, any session metadata that user has configured will be preserved even through restarts.

This was the last major feature I planned on adding before releasing 1.0, so please add any requests to the project’s GH issues if there additions you’d like to see included. Other than that future minor releases should focus on documentation, refactors and minor improvements, and bugfixes of course.

14 Likes