naymspace / backpex Goto Github PK
View Code? Open in Web Editor NEWBackpex is a highly customizable administration panel for Phoenix LiveView applications.
Home Page: https://backpex.live
License: MIT License
Backpex is a highly customizable administration panel for Phoenix LiveView applications.
Home Page: https://backpex.live
License: MIT License
Streams by default for any kind of collection is a good intuition to have. You should use streams any time you don’t want to hold the list of items in memory – which is most times. Streams are also a goto when you want to efficiently update a single list item without refactoring to a layer of LiveComponents for the items.
https://fly.io/phoenix-files/phoenix-dev-blog-streams/
https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream/4
A clean install of phoenix version 1.7.14 and backpex 0.3.1 fails dependency checks
Because your app depends on backpex ~> 0.3.1 which depends on phoenix_html ~> 3.0, phoenix_html ~> 3.0 is required.
So, because your app depends on phoenix_html ~> 4.1, version solving failed.
Updating to backpex 0.4.0 fixes the issue but then causes problems with floki and heroicons:
Dependencies have diverged:
* floki (Hex package)
the :only option for dependency floki
> In mix.exs:
{:floki, ">= 0.30.0", [env: :prod, hex: "floki", only: :test, repo: "hexpm"]}
does not match the :only option calculated for
> In deps/backpex/mix.exs:
{:floki, ">= 0.30.0", [env: :prod, hex: "floki", repo: "hexpm", optional: false]}
Remove the :only restriction from your dep
* heroicons (https://github.com/tailwindlabs/heroicons.git - v2.1.1)
the dependency heroicons in mix.exs is overriding a child dependency:
> In mix.exs:
{:heroicons, [env: :prod, git: "https://github.com/tailwindlabs/heroicons.git", tag: "v2.1.1", sparse: "optimized", app: false, compile: false, depth: 1]}
> In deps/backpex/mix.exs:
{:heroicons, "~> 0.5.0", [env: :prod, hex: "heroicons", repo: "hexpm", optional: false]}
Ensure they match or specify one of the above in your deps and set "override: true"
** (Mix) Can't continue due to errors on dependencies
In Backpex.HTML.Form.field_input/1
we do not read the value from the form field.
This results in the UI not reflecting the value in the changeset in some cases. In the demo, changes to the changeset always result from changes to the form. But in some applications we might want to reverse this and set a value in the changeset and have the UI reflect that value. This doesn't work at the moment.
To simplify reuse, it should be possible to specify the presets within the flilter definition, as these are very verbose.
issued_at: %{
module: DateRange,
presets: [
%{
label: dgettext("time_tracking", "Current Week"),
values: fn ->
%{
"start" =>
DateTime.now!(@default_timezone)
|> DateTime.to_date()
|> Date.beginning_of_week(:monday),
"end" =>
DateTime.now!(@default_timezone)
|> DateTime.to_date()
|> Date.end_of_week(:monday)
}
end
},
%{
label: dgettext("time_tracking", "Last Week"),
values: fn ->
%{
"start" =>
DateTime.now!(@default_timezone)
|> DateTime.to_date()
|> Date.beginning_of_week(:monday)
|> Date.add(-7),
"end" =>
DateTime.now!(@default_timezone)
|> DateTime.to_date()
|> Date.end_of_week(:monday)
|> Date.add(-7)
}
end
},
%{
label: dgettext("time_tracking", "Current Month"),
values: fn ->
%{
"start" =>
DateTime.now!(@default_timezone)
|> DateTime.to_date()
|> Date.beginning_of_month(),
"end" =>
DateTime.now!(@default_timezone)
|> DateTime.to_date()
|> Date.end_of_month()
}
end
},
%{
label: dgettext("time_tracking", "Last Month"),
values: fn ->
%{
"start" =>
DateTime.now!(@default_timezone)
|> DateTime.to_date()
|> Date.beginning_of_month()
|> Date.add(-1)
|> Date.beginning_of_month(),
"end" =>
DateTime.now!(@default_timezone)
|> DateTime.to_date()
|> Date.beginning_of_month()
|> Date.add(-1)
|> Date.end_of_month()
}
end
}
]
}
"show more" closes the dropdown. It should stay open.
On the demo site, you can filter by the number of Likes a Post has. You can put in one or two numbers to be the endpoints of the range of Likes to show. But the fields don't say which is which, and if someone is unfamiliar, they may not even know why there are two fields. Fortunately, the example links for "over 100" and "1–99" fill in the correct numbers so the behavior can be discovered that way.
Suggested labels:
[______]
"[______]
"[_______]
"[______]
"[______]
to [______]
"[______]
– [_______]
"You should be able to determine the text of the anchor that is displayed on index
/show
via a configuration value. Currently, the URL is used as the anchor text.
Make all resources searchable via a global search in the topbar.
The apply_criteria
function expects a field_name
to be present in the order criteria, but we do not set the value in the case order_by
contains a value that is not orderable.
See https://github.com/naymspace/backpex/blob/0.3.1/lib/backpex/live_resource.ex#L1336
To increase the reusability, a label should be configurable from the outside. The following filter can only be used when the label is the same:
defmodule EvuPlatformWeb.Filters.DateRange do
@moduledoc false
use Backpex.Filters.Range
import EvuPlatformWeb.Gettext
@impl Backpex.Filters.Range
def type, do: :datetime
@impl Backpex.Filter
def label, do: dgettext("time_tracking", "Date")
end
Implement a language switch to demonstrate translation features.
Sorting should not be case sensitive.
Possible solutions:
fragment("lower(?)")
: Works on text onlyFilters should work in the same way as fields: without defining a separate module. We should provide a set of generic filter modules wich can then be configured in the LiveResource (just like fields).
This will work well with #336 together.
Invalid filter values should not result in a 500 internal server error.
For example, the user filter on the post resource is a multi select filter. You can add more users by repeating the filters[user][]
parameter in the URL:
/posts?filters[user_id][]=<user_id>&filters[user_id][]=<user_id>
But because we expect the multi select filter values to be in this structure, the following URL will return a 500 internal server error:
/posts?filters[user_id]=<user_id>
Either the filter values should be ignored in this case or a bad request should be returned by the server.
There is also an error if the given `user_id' is not a UUID.
We probably should use the HTML dialog
element for all modals.
See also daisyUI implementation: https://daisyui.com/components/modal/
This would also solve a current problem: The backdrop is currently scrollable while a modal is open.
mix backpex.gen.gettext
generates the language filemix backpex.gen.resource
generates a live resourceI saw the following log in the developer console:
Multiple IDs detected: index_form_index_input. Ensure unique element ids.
This was certainly introduced with the index editable
feature.
Installation guide should be updated.
@tailwindcss/forms
plugin can lead to styling issues (describe how to use strategy: 'class'
approach)body
-tag should be removeddeps
folder path in tailwind.config.js
Heroicons
plugin approachThe label for the create button should be configurable. This also includes the create button in the empty state.
create_button_label
function / option to live resourcesWe should make the field_input
component generic so that it can be used by other projects as well as wherever we need form inputs in backpex (e.g., filter).
Between Cancel and Save, there should be a "Save & continue editing" button.
Sometimes we need to override a select field only because of one additional class like w-36
:
~H"""
<div>
<.form
:let={f}
for={%{}}
as={:index_form}
class="relative"
phx-change="update-field"
phx-submit="update-field"
phx-target={@myself}
>
<%= Phoenix.HTML.Form.select(
f,
:index_input,
@options,
class: ["select w-36 select-sm", if(@valid, do: "hover:input-bordered", else: "select-error")],
selected: @value,
disabled: @readonly,
id: @input_id,
prompt: Map.get(@prompt, :prompt)
) %>
</.form>
</div>
"""
It would be helpful to pass additional classes to the fields.
All pubsub settings could be optional:
pubsub
: use :pubsub_server
option of the endpoint?topic
: derive from resource name?event_prefix
: derive from resource name?Calling the edit route with an invalid ID (that not exists) results in an internal server error.
The documentation https://hexdocs.pm/backpex/what-is-a-field.html#configuration mentions a Email field, but it seems to not actually exist.
I need it for my current project so I will implement it myself if its missing. Would you be interested in a PR with this?
Thank you!
I created a new empty mix phoenix project, added backpex following installation guide. I apllied the fixes from #399 and changed path in taliwind config to ../deps/backpex/**/*.*ex
as in https://github.com/rakso-startkoden/backpex-test/pull/1/files.
The app loads but the layout is broken. Notice logout button in the upper right corner
app-1 | * 1st argument: not an already existing atom
app-1 |
app-1 | :erlang.binary_to_existing_atom("ddd", :utf8)
app-1 | (backpex 0.2.0) lib/backpex/live_resource.ex:2081: Backpex.LiveResource.maybe_to_atom/1
app-1 | (backpex 0.2.0) lib/backpex/live_resource.ex:1721: Backpex.LiveResource.order_options_by_params/4
Try the following url: https://backpex.live/admin/users?order_by=ddd
After making the switch to daisyUI we still use a lot of specific colors (like bg-gray-100
). We should only use the colours from the respective theme. For example, base-100
as the background and then base-content
for content that is placed on it. Then the dark mode also works by default and we can support several themes.
Hi, I've been trying to add backpex to my project. Most things seems to work, but there is something with the styles that is not working properly. I also tried creating a fresh phoenix project (https://github.com/rakso-startkoden/backpex-test) and following in the steps, but its also not working 100% as expected.
In both cases there are no errors in consoles to give any clues as to what is wrong. Alpinejs is installed and tailwind config is updated as instructed.
Below are also some screenshots from my two attempts.
(From the fresh phoenix project)
(From my already existing project)
Thank you for a great project with lots of potential! I look forward to getting rid of my repetitive code for admin views.:)
After a recent refactoring the fields are now completely identical.
In the app_shell
function the mobile menu has a z-index that puts the top of the menu below the topbar.
ps: It is a single char fix to put it below z-40 at:
backpex/lib/backpex/html/layout.ex
Line 79 in 7c5672a
@Flo0807 Do you need help with the project? I see a lot PR's pending and some work required, I'm willing to help.
Also do you have a roadmap of feature you want to implement?
Cheers
Item Actions can be configured to be displayed on show view (see Placement of Item Actions).
The placement on show view doesn't seem to work.
With Phoenix LiveView 1.0 phx-feedback-for
is being removed (see changelog). Adjust Backpex accordingly and use the new way of hiding errors of untouched form inputs.
field_input
componentphx-no-feedback
tailwind variant from demo projectphx-no-feedback
from demo projectbackpex version: 0.4.0
phoenix version: 1.7.14
phoenix_live_view version: 0.20.17
elixir version: 1.17.1
otp version: 26.2.5
I followed the installation guide on a freshly generated mix phoenix project. I applied the fixes from #399 and got the app to boot but it crashes when I try to create a post
[error] ** (ArgumentError) attempting to set id attribute to 1, but setting the DOM ID to a number can lead to unpredictable behaviour. Instead consider prefixing the id with a string, such as "user-1" or similar
(phoenix_html 4.1.1) lib/phoenix_html.ex:258: Phoenix.HTML.id_value/1
(phoenix_html 4.1.1) lib/phoenix_html.ex:223: Phoenix.HTML.build_attrs/1
(phoenix_html 4.1.1) lib/phoenix_html.ex:191: Phoenix.HTML.attributes_escape/1
(backpex 0.4.0) lib/backpex/html/resource/resource_index_table.html.heex:78: anonymous fn/4 in Backpex.HTML.Resource."resource_index_table (overridable 1)"/1
(phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:391: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:559: anonymous fn/3 in Phoenix.LiveView.Diff.traverse_comprehension/5
(elixir 1.17.1) lib/enum.ex:1829: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
(elixir 1.17.1) lib/enum.ex:1829: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
(elixir 1.17.1) lib/enum.ex:1829: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
(phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:480: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:532: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
(elixir 1.17.1) lib/enum.ex:2531: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:389: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:532: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
(elixir 1.17.1) lib/enum.ex:2531: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:389: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:532: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
(elixir 1.17.1) lib/enum.ex:2531: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:389: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.20.17) lib/phoenix_live_view/diff.ex:532: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
Here's my post schema
defmodule BackpexPlayground.Blog.Post do
use Ecto.Schema
import Ecto.Changeset
schema "blog_posts" do
field :title, :string
field :views, :integer
timestamps(type: :utc_datetime)
end
@doc false
def changeset(post, attrs) do
post
|> cast(attrs, [:title, :views])
|> validate_required([:title, :views])
end
def update_changeset(post, attrs, _metadata \\ []) do
changeset(post, attrs)
end
def create_changeset(post, attrs, _metadata \\ []) do
changeset(post, attrs)
end
end
and live view
defmodule BackpexPlaygroundWeb.Live.PostLive do
use Backpex.LiveResource,
layout: {BackpexPlaygroundWeb.Layouts, :admin},
schema: BackpexPlayground.Blog.Post,
repo: BackpexPlayground.Repo,
update_changeset: &BackpexPlayground.Blog.Post.update_changeset/3,
create_changeset: &BackpexPlayground.Blog.Post.create_changeset/3,
pubsub: BackpexPlayground.PubSub,
topic: "posts",
event_prefix: "post_"
@impl Backpex.LiveResource
def singular_name, do: "Post"
@impl Backpex.LiveResource
def plural_name, do: "Posts"
@impl Backpex.LiveResource
def fields do
[
title: %{
module: Backpex.Fields.Text,
label: "Title"
},
views: %{
module: Backpex.Fields.Number,
label: "Views"
}
]
end
end
This works:
https://backpex.live/admin/posts?filters[user_id][]=098f1b60-54d6-40bc-a8f9-986b2c95c1a9
This results in a bad request:
https://backpex.live/admin/posts?filters[user_id][]=INVALID
This is because we are trying to cast the value to a UUID internally.
Backpex still uses the old way of installing Heroicons by relying on the Heroicons package. In newly generated Phoenix applications, this package is no longer used. Instead, the Heroicons GitHub repository is tracked, and a Tailwind plugin generates the appropriate CSS for an icon
component.
This is a problem because Backpex tries to load the hex package heroicons
while newly generated applications try to track the Hericons GitHub repository under that name.
We should probably rely on the new way of integrating Heroicons as well, but then the user has to provide the Tailwind plugin, since Backpex does not ship with a tailwind.config.js
.
related to #399
When a LiveResource includes two BelongsTo
fields referencing the same schema, an error occurs due to alias conflicts during query joins. The alias for each join is generated from the schema name of the associated field (see resource.ex, L108).
Attempting to set the same alias for the second BelongsTo
field raises an Ecto.Query.CompileError
with the message: "alias :<schema_name>
already exists."
The Floki dependency is only being used for inserting the Backpex logo (see Layout.ex#L245
).
Maybe we can provide a component for the logo that contains the svg
. That way we could remove the dependency entirely.
The dependency causes a conflict in some Phoenix applications because the only: [:test]
option is set by default in newly generated apps.
related to #399
Hi, I'm using currently kaffy and consider to switch over to backpex. Is there a dashboard feature in planning?
Backpex.Field
Backpex.Filter
Backpex.Metric
Stoerti.LiveResource
see also nimble_options
defmodule Stoerti.Fields.Date do
# ...
def options() do
[
format: [
type: :string,
default: "%Y-%m-%d"
],
foo: [
type: :non_neg_integer,
required: true
]
]
end
end
config :backpe, Backpex.Fields.Date,
format: "%d.%m.%Y"
config: :backpex, Backpex.Fields.Upload,
max_file_size: 42_000_000
The familiar way:
def fields do
[
%{
module: Stoerti.Fields.Date,
label: "Created At",
name: :created_at,
format: "%d.%m.%Y"
}
]
end
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.