Kaffy - a quick and flexible admin interface for phoenix applications

Hello guys,

I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the current global pandemic, I kind of had the mindset and time to work on a few things on that list.

The package is in its very early stages, but I started using it for my own projects.
The following is part of the README file.

Kaffy

Extremely simple yet powerful admin interface for phoenix applications

Why another admin interface

Kaffy was created out of a need to have a minimum, flexible, and customizable admin interface
without the need to touch the current codebase. It should work out of the box just by adding some
configs in your config.exs file (with the exception of adding a one liner to your router.ex file).

A few points that encouraged the creation of Kaffy:

  • Taking contexts into account.
    • Supporting contexts makes the admin interface better organized.
  • Can handle as many schemas as necessary.
    • Whether we have 1 schema or 1000 schemas, the admin interface should adapt well.
  • Have a visually pleasant user interface.
    • This might be subjective.
  • No generators or generated templates.
    • I believe the less files there are the better. This also means it’s easier to upgrade for users when releasing new versions. This might mean some flexibility and customizations will be lost, but it’s a trade-off.
  • Existing schemas/contexts shouldn’t have to be modified.
    • I shouldn’t have to change my code in order to adapt to the package, the package should adapt to my code.
  • Should be easy to use whether with a new project or with existing projects with a lot of schemas.
    • Adding kaffy should be as easy for existing projects as it is for new ones.
  • Highly flexible and customizable.
    • Provide as many configurable options as possible.
  • As few dependencies as possible.
    • Currently kaffy only depends on phoenix and ecto.
  • Simple authorization.
    • I need to limit access for some admins to some schemas.
  • Minimum assumptions.
    • Need to modify a schema’s primary key? Need to hide a certain field? No problem.

GitHub link
Hex package

All critique, criticism, PRs, and feedback are welcome.

43 Likes

v0.2.0 was just released

Changes

  • Kaffy now supports phoenix 1.4 and higher.
  • The :otp_app config is now required (breaking change).
  • Removed some deprecation warnings when compiling kaffy (apparently introduced some new warnings).
  • Massively simplified configurations. The only required configs now are otp_app , ecto_repo , and router .
  • Added a CHANGELOG file to communicate and track changes easily.
  • Kaffy will now auto-detect your schemas and admin modules if they’re not set explicitly. See the README file for more. This should get me closer to my original post’s points:

Can handle as many schemas as necessary

and

Should be easy to use whether with a new project or with existing projects with a lot of schemas

Thank you for your interest. I’m getting extremely motivated to work on this project with the current response/feedback.

10 Likes

I’m seeing an error on the show view for a resource with an embedded struct:

Protocol.UndefinedError: protocol Phoenix.HTML.Safe not implemented for %{} of type Map. This protocol is implemented for the following type(s): Decimal, Phoenix.LiveView.Rendered, Phoenix.LiveView.Comprehension, Phoenix.LiveView.Component, Float, Time, BitString, Phoenix.HTML.Form, Atom, DateTime, NaiveDateTime, Integer, Date, List, Tuple


1
File "lib/phoenix_html/safe.ex" line 1 in Phoenix.HTML.Safe.impl_for!/1 (phoenix_html)
2
File "lib/phoenix_html/safe.ex" line 15 in Phoenix.HTML.Safe.to_iodata/1 (phoenix_html)
3
File "lib/phoenix_html.ex" line 154 in Phoenix.HTML.html_escape/1 (phoenix_html)
4
File "lib/keyword.ex" line 832 in Keyword.update!/4 (elixir)
5
File "lib/phoenix_html/form.ex" line 825 in Phoenix.HTML.Form.generic_input/4 (phoenix_html)
6
File "lib/kaffy_web/templates/resource/show.html.eex" line 8 in anonymous fn/4 in KaffyWeb.ResourceView."show.html"/1 (kaffy)
7
File "lib/enum.ex" line 2111 in Enum."-reduce/3-lists^foldl/2-0-"/3 (elixir)
8
File "lib/kaffy_web/templates/resource/show.html.eex" line 5 in KaffyWeb.ResourceView."show.html"/1 (kaffy)

On the list view it displays [object Object] for what I assume is the offending column

Thank you for reporting this. Support for embedded schemas/fields is still not there yet. There’s already an issue on GitHub though so it’s planned. If you could share some generic code here or on GitHub of what your schema looks like that would greatly help.

Thanks.

I’ll add it to the Github issue, which probably would have been the proper place to report this in the first place. Sorry, and thank you!

v0.2.1 (2020-05-10)

  • [feature] Added support for embedded schemas.
  • [feature] Added support for :map fields for json values.
  • [enhancement] Use the json library configured for phoenix instead of hardcoding Jason.
  • [bugfix] Don’t crash when the schema has a has_many or has_one association.
  • [bugfix] Don’t crash when the schema has a map field or an embedded schema.
3 Likes

v0.3.0 (2020-05-11)

This version has a few bug fixes and a few nice additions.

Mainly:

  • A much improved and much more detailed README file.
  • The introduction of callbacks!
4 Likes

v0.3.1 (2020-05-12)

  • [enhancement] A better way to handle foreign key fields with a huge amount of records to select from.
  • [enhancement] Retrieve the actual name of the association field from the association struct.

This update will make things extremely easier for people who have too many records in the database.

Example:

Creating a new Comment record with a belongs_to :post, MyApp.Blog.Post association, where we have more than 20 posts. Kaffy will render the following:

Screen Shot 2020-05-12 at 06.40.22

Once the search button is clicked, a new window will open like the following:

Posts are paginated and searchable. Once you select a post, kaffy will populate its ID in the form and close the window:

Screen Shot 2020-05-12 at 06.41.11

5 Likes

this project looks very promising. well done @aesmail - at the moment I’m using react-admin to handle admin panel on a phoenix application, but happy to start migrating over Kaffy, will check it out soon.

Coming from Ruby 2 years ago, I really missed something similar to active admin on elixir. and Kaffy looks very similar to it :blush:

1 Like

Thank you @antonioparisi !

I hope you’ll try and find kaffy useful. Please let me know if you face any issues since I’m very interested in the experience of kaffy being used in existing projects.

And you’re right. My biggest inspirations are the built-in admin from django and activeadmin from rails since I’ve been using both heavily for other projects. I’m trying to combine the best features from these two.

I’m not familiar with react-admin, but it looks awesome! If you don’t mind me asking, what do you like most about it?

I’ll try to use it for a few days and see what good ideas can be implemented in kaffy.

1 Like

To be fair I’ve been kinda forced to use react-admin because of the open source availability in terms of admin panels out there at the time when I started. It offers the most reasonable approach to build something straight forward and customisable using react.
Unique big downside of it is that being detached from the APIs (phoenix app), you’ve to build your own endpoints for any action, which to be honest with you is a huge ball-ache :slight_smile:

I’ll try Kaffy ASAP since it looks awesome so far in the Phoenix/Elixir world. I’ll give you my feedback on it as soon as I can.

Thanks so much for the amazing work you put on it so far @aesmail keep up the good work!

2 Likes

A demo is up at https://kaffy.gigalixirapp.com/admin

5 Likes

v0.4.0 (2020-05-13)

  • [breaking] pass conn struct to all callback functions.
  • [feature] introducing custom actions for single resources.
  • [enhancement] fix typo in the resource form (thanks @axelclark).

Custom Actions

Kaffy supports performing custom actions on single resources by defining the resource_actions/1 function.

defmodule MyApp.Blog.ProductAdmin
  def resource_actions(_conn) do
    [
      publish: %{name: "Publish this product", action: fn _c, p -> restock(p) end},
      soldout: %{name: "Sold out!", action: fn _c, p -> soldout(p) end}
    ]
  end

  defp restock(product) do
    update_product(product, %{"status" => "available"})
  end

  defp soldout(product) do
    case product.id == 3 do
      true ->
        {:error, product, "This product should never be sold out!"}

      false ->
        update_product(product, %{"status" => "soldout"})
    end
  end

Result

resource_actions

resource_actions/1 takes a conn and must return a keyword list.
The keys must be atoms defining the unqiue action “keys”.
The values are maps providing a human-friendly :name and an :action that is an anonymous function with arity 2 that takes a conn and the record.

Actions must return one of the following:

  • {:ok, record} indicating the action was performed successfully.
  • {:error, changeset} indicating there was a validation error.
  • {:error, record, custom_error} to communicate a custom error message to the user where custom_error is a string.
2 Likes

Thanks for making this, I’ve been missing the power of Django admin in Phoenix, and Kaffy looks like it just might fill that gap!

3 Likes

I really hope you’ll find it useful. Thank you for giving it a shot.

1 Like

v0.4.1 (2020-05-14)

  • [feature] add custom field filters.
  • [bugfix] sometimes if index/1 is not defined in the admin module, the index page is empty.

Custom filters

You can also provide some basic column-based filtration by providing the :filters option:

defmodule MyApp.Products.ProductAdmin do
  def index(_) do
    [
      title: nil,
      category_id: %{
        value: fn p -> get_category!(p.category_id).name end,
        filters: Enum.map(list_categories(), fn c -> {c.name, c.id} end)
      },
      price: %{value: fn p -> Decimal.to_string(p.price) end},
      quantity: nil,
      status: %{
        name: "Is it available?",
        value: fn p -> available?(p) end,
        filters: [{"Available", "available"}, {"Sold out", "soldout"}]
      },
      views: nil
    ]
  end
end

:filters must be a list of tuples where the first element is a human-frieldy string and the second element is the actual field value used to filter the records.

Result

3 Likes

Amazing work :muscle:
Also a big fan of Django-admin here, we definitely need something like it for Phoenix!

1 Like

I’m very pleased to announce kaffy v0.5.0 with dashboard widgets.

This was the biggest missing thing on my todo list. I’m not 100% done with it. This is hopefully the tip of the iceberg.

Thanks to everyone who provided comments, suggestions, and/or PRs.

As always, your feedback is highly appreciated.

Check the dashboard page section.

7 Likes

v0.6.0 (2020-05-18) (demo and github)

incompatible with v0.5.x
  • [feature] support custom actions for a group of selected resources in index page.
  • [enhancement] breaking change - always include the :kaffy_browser pipeline to display templates correctly. Please check the minimum configurations section in the README for more information on how to upgrade.
  • [bugfix] resource index page table was displayed incorrectly when using a custom pipeline.
  • [bugfix] all side menu resources are shown by default including sections that are not currently active.
  • [bugfix] side menu does not scroll when there are too many contexts/schemas.
  • [bugfix] side menu items all popup at the same time when viewed on small screens.
  • [misc] added a demo link to the hex package page.

v0.5.1 (2020-05-17)

compatible with v0.5.0
  • [enhancement] add a rich text editor option for form fields (type: :richtext).
  • [bugfix] dashboard widgets were displayed improperly on small screens.
4 Likes

Gonna give this a shot, next time i need a administrator panel!

Thank you for your work, we really needed this @aesmail. :slight_smile:

2 Likes