Merge pull request #1775 from coollabsio/next

v4.0.0-beta.224
This commit is contained in:
Andras Bacsai 2024-02-23 13:57:11 +01:00 committed by GitHub
commit 8c803f1c4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 503 additions and 255 deletions

View File

@ -44,7 +44,7 @@ public function handle(Request $request, Closure $next): Response
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
return redirect(RouteServiceProvider::HOME);
}
if (isSubscriptionActive() && $request->path() === 'subscription') {
if (isSubscriptionActive() && $request->routeIs('subscription.index')) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);

View File

@ -7,6 +7,13 @@
class Actions extends Component
{
public $server_limits = 0;
public function mount()
{
$limits = currentTeam()->limits;
$this->server_limits = data_get($limits, 'serverLimit', 0);
}
public function cancel()
{
try {
@ -69,7 +76,8 @@ public function resume()
return handleError($e, $this);
}
}
public function stripeCustomerPortal() {
public function stripeCustomerPortal()
{
$session = getStripeCustomerPortalSession(currentTeam());
redirect($session->url);
}

View File

@ -10,14 +10,19 @@ class Index extends Component
{
public InstanceSettings $settings;
public bool $alreadySubscribed = false;
public function mount() {
public function mount()
{
if (!isCloud()) {
return redirect(RouteServiceProvider::HOME);
}
if (data_get(currentTeam(), 'subscription')) {
return redirect()->route('subscription.show');
}
$this->settings = InstanceSettings::get();
$this->alreadySubscribed = currentTeam()->subscription()->exists();
}
public function stripeCustomerPortal() {
public function stripeCustomerPortal()
{
$session = getStripeCustomerPortalSession(currentTeam());
if (is_null($session)) {
return;

View File

@ -0,0 +1,22 @@
<?php
namespace App\Livewire\Subscription;
use Livewire\Component;
class Show extends Component
{
public function mount()
{
if (!isCloud()) {
return redirect()->route('dashboard');
}
if (!data_get(currentTeam(), 'subscription')) {
return redirect()->route('subscription.index');
}
}
public function render()
{
return view('livewire.subscription.show');
}
}

View File

@ -16,6 +16,11 @@ public function makeAdmin()
$this->dispatch('reloadWindow');
}
public function makeOwner()
{
$this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'owner']);
$this->dispatch('reloadWindow');
}
public function makeReadonly()
{
$this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'member']);
@ -26,7 +31,7 @@ public function remove()
{
$this->member->teams()->detach(currentTeam());
Cache::forget("team:{$this->member->id}");
Cache::remember('team:' . $this->member->id, 3600, function() {
Cache::remember('team:' . $this->member->id, 3600, function () {
return $this->member->teams()->first();
});
$this->dispatch('reloadWindow');

View File

@ -6,7 +6,6 @@
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
class Service extends BaseModel
{
@ -28,47 +27,73 @@ public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
public function status() {
$foundRunning = false;
$isDegraded = false;
$foundRestaring = false;
public function status()
{
$applications = $this->applications;
$databases = $this->databases;
$complexStatus = null;
$complexHealth = null;
foreach ($applications as $application) {
if ($application->exclude_from_status) {
continue;
}
if (Str::of($application->status)->startsWith('running')) {
$foundRunning = true;
} else if (Str::of($application->status)->startsWith('restarting')) {
$foundRestaring = true;
$status = str($application->status)->before('(')->trim();
$health = str($application->status)->between('(', ')')->trim();
if ($complexStatus === 'degraded') {
continue;
}
if ($status->startsWith('running')) {
if ($complexStatus === 'exited') {
$complexStatus = 'degraded';
} else {
$complexStatus = 'running';
}
} else if ($status->startsWith('restarting')) {
$complexStatus = 'degraded';
} else if ($status->startsWith('exited')) {
$complexStatus = 'exited';
}
if ($health->value() === 'healthy') {
if ($complexHealth === 'unhealthy') {
continue;
}
$complexHealth = 'healthy';
} else {
$isDegraded = true;
$complexHealth = 'unhealthy';
}
}
foreach ($databases as $database) {
if ($database->exclude_from_status) {
continue;
}
if (Str::of($database->status)->startsWith('running')) {
$foundRunning = true;
} else if (Str::of($database->status)->startsWith('restarting')) {
$foundRestaring = true;
$status = str($database->status)->before('(')->trim();
$health = str($database->status)->between('(', ')')->trim();
if ($complexStatus === 'degraded') {
continue;
}
if ($status->startsWith('running')) {
if ($complexStatus === 'exited') {
$complexStatus = 'degraded';
} else {
$complexStatus = 'running';
}
} else if ($status->startsWith('restarting')) {
$complexStatus = 'degraded';
} else if ($status->startsWith('exited')) {
$complexStatus = 'exited';
}
if ($health->value() === 'healthy') {
if ($complexHealth === 'unhealthy') {
continue;
}
$complexHealth = 'healthy';
} else {
$isDegraded = true;
$complexHealth = 'unhealthy';
}
}
if ($foundRestaring) {
return 'degraded';
}
if ($foundRunning && !$isDegraded) {
return 'running';
} else if ($foundRunning && $isDegraded) {
return 'degraded';
} else if (!$foundRunning && !$isDegraded) {
return 'exited';
}
return 'exited';
return "{$complexStatus}:{$complexHealth}";
}
public function extraFields()
{
@ -414,7 +439,7 @@ public function link()
public function documentation()
{
$services = getServiceTemplates();
$service = data_get($services, Str::of($this->name)->beforeLast('-')->value, []);
$service = data_get($services, str($this->name)->beforeLast('-')->value, []);
return data_get($service, 'documentation', config('constants.docs.base_url'));
}
public function applications()

View File

@ -30,8 +30,7 @@ public function type()
if (in_array($subscription, $ultimate)) {
return 'ultimate';
}
}
if (isStripe()) {
} else if (isStripe()) {
if (!$this->stripe_plan_id) {
return 'zero';
}
@ -55,7 +54,7 @@ public function type()
};
})->first();
if ($stripePlanId) {
return Str::of($stripePlanId)->after('stripe_price_id_')->before('_')->lower();
return str($stripePlanId)->after('stripe_price_id_')->before('_')->lower();
}
}
return 'zero';

View File

@ -67,7 +67,7 @@ public function createToken(string $name, array $abilities = ['*'], DateTimeInte
'team_id' => session('currentTeam')->id
]);
return new NewAccessToken($token, $token->getKey().'|'.$plainTextToken);
return new NewAccessToken($token, $token->getKey() . '|' . $plainTextToken);
}
public function teams()
{
@ -103,9 +103,13 @@ public function sendPasswordResetNotification($token): void
public function isAdmin()
{
return data_get($this->pivot, 'role') === 'admin' || data_get($this->pivot, 'role') === 'owner';
return $this->role() === 'admin' || $this->role() === 'owner';
}
public function isOwner()
{
return $this->role() === 'owner';
}
public function isAdminFromSession()
{
if (auth()->user()->id === 0) {
@ -155,6 +159,9 @@ public function otherTeams()
public function role()
{
return session('currentTeam')->pivot->role;
if (data_get($this, 'pivot')) {
return $this->pivot->role;
}
return auth()->user()->teams->where('id', currentTeam()->id)->first()->pivot->role;
}
}

View File

@ -109,7 +109,7 @@ function isPaddle()
function getStripeCustomerPortalSession(Team $team)
{
Stripe::setApiKey(config('subscription.stripe_api_key'));
$return_url = route('team.index');
$return_url = route('subscription.show');
$stripe_customer_id = data_get($team,'subscription.stripe_customer_id');
if (!$stripe_customer_id) {
return null;
@ -123,7 +123,7 @@ function getStripeCustomerPortalSession(Team $team)
function allowedPathsForUnsubscribedAccounts()
{
return [
'subscription',
'subscription/new',
'login',
'logout',
'waitlist',

View File

@ -7,7 +7,7 @@
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.223',
'release' => '4.0.0-beta.224',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@ -13,6 +13,12 @@
'stripe_price_id_ultimate_yearly' => env('STRIPE_PRICE_ID_ULTIMATE_YEARLY', null),
'stripe_excluded_plans' => env('STRIPE_EXCLUDED_PLANS', null),
'stripe_price_id_basic_monthly_old' => env('STRIPE_PRICE_ID_BASIC_MONTHLY_OLD', null),
'stripe_price_id_basic_yearly_old' => env('STRIPE_PRICE_ID_BASIC_YEARLY_OLD', null),
'stripe_price_id_pro_monthly_old' => env('STRIPE_PRICE_ID_PRO_MONTHLY_OLD', null),
'stripe_price_id_pro_yearly_old' => env('STRIPE_PRICE_ID_PRO_YEARLY_OLD', null),
'stripe_price_id_ultimate_monthly_old' => env('STRIPE_PRICE_ID_ULTIMATE_MONTHLY_OLD', null),
'stripe_price_id_ultimate_yearly_old' => env('STRIPE_PRICE_ID_ULTIMATE_YEARLY_OLD', null),
// Paddle
'paddle_vendor_id' => env('PADDLE_VENDOR_ID', null),

View File

@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.223';
return '4.0.0-beta.224';

View File

@ -57,6 +57,12 @@ services:
- STRIPE_PRICE_ID_PRO_YEARLY
- STRIPE_PRICE_ID_ULTIMATE_MONTHLY
- STRIPE_PRICE_ID_ULTIMATE_YEARLY
- STRIPE_PRICE_ID_BASIC_MONTHLY_OLD
- STRIPE_PRICE_ID_BASIC_YEARLY_OLD
- STRIPE_PRICE_ID_PRO_MONTHLY_OLD
- STRIPE_PRICE_ID_PRO_YEARLY_OLD
- STRIPE_PRICE_ID_ULTIMATE_MONTHLY_OLD
- STRIPE_PRICE_ID_ULTIMATE_YEARLY_OLD
- STRIPE_EXCLUDED_PLANS
- PADDLE_VENDOR_ID
- PADDLE_WEBHOOK_SECRET

21
public/svgs/firefly.svg Normal file
View File

@ -0,0 +1,21 @@
<!--
- maskable-icon.svg
- Copyright (c) 2022 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<svg height="377.95276" width="377.95276" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h377.95276v377.95276h-377.95276z" fill="#cd5029" stroke-width="1.96129"/><g transform="matrix(.77452773 0 0 .77452773 21.636074 21.374655)"><path d="m140.49013 78.646381 2.249 53.017999s-40.103 29.566-45.538 68l-16.001 1.231s-11.539 2.564-11.539 14.103v37.18s3.846 11.538 12.82 11.538l16.487-.319s8 30.5 36.5 50.5v25.5s-2 8.5 15.5 11 40.75 2.25 44.5-1.5 3.75-4.5 3.75-9c0 0 21.25 5 60.25 0v5s3.5 7 29 7 33-3 37.5-12v-25s37.009-36.264 35.75-91.75c-1.083-47.75-15.901-64.299-35.806-82.96-22.67-21.254-69.944-31.165-117.944-25.353.001-.001-24.341-43.937999-67.478-36.187999z" fill="#fff"/><circle cx="135.46912" cy="214.39638" fill="#cd5029" r="9.5"/><path d="m360.08113 190.51238s-18.218-8.742-40.662 3.996c0 0-26.711-8.987-40.99 2.593-14.828 12.025-16.299 26.115-15.525 42.785 0 0 12.837-43.915 45.252-32.571 0 0-22.947 40.43 12.761 47.508 0 0 8.436-.05 15.401-4.256 6.644-4.011 11.842-11.433 9.711-24.814 0 0-4.348-13.336-15.569-21.42 0 0 11.042-7.806 31.988-2.209z" fill="#cd5029"/><path d="m320.19013 213.01938s-16.689 31.461 5.607 29.767c0 0 11.838-5.656 4.887-17.127-7.147-11.796-10.494-12.64-10.494-12.64z" fill="#fff"/></g><path d="m188.97638 175.70052s4.01698 13.60604-3.69586 21.52748c-7.713 7.92145-6.8792 16.6767-3.75227 20.84588 3.12692 4.16917 2.91831 7.29593.41674 9.58905-2.50141 2.29312-4.58608 3.96073-6.04523.20846-1.45916-3.75228-3.12676-3.75228-3.75228-5.62834-.62552-1.87605-1.87622-5.21142-1.87622-5.21142s-3.96072 6.25384-6.46229 10.00611c-2.50157 3.75228-2.50141 9.58922-.83381 12.71598 1.66761 3.12676 1.04226 6.87903-.20845 12.09046-1.2507 5.21143.4169 13.13288 6.25369 16.2598 5.83678 3.12692 12.92459 5.62833 16.05135 8.5468s10.42301 5.62833 19.80362 3.54382c9.3806-2.0845 21.26294-11.67355 23.34744-18.13585 0 0 5.41988-6.04523 4.37763-13.96668s-4.79469-7.71316-6.4623-13.75839c-1.6676-6.04523 3.60854-4.55469-.8338-14.93382 0 0-1.98012-4.94005-9.50352-8.49899-4.83404-2.28661-1.54469-12.63061-10.09149-23.05347s-16.73295-12.14688-16.73295-12.14688z" fill="#ffa284" stroke-width=".162598"/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -44,9 +44,10 @@ class="{{ request()->is('project/*') || request()->is('projects') ? 'text-warnin
<div>
<button x-on:click.prevent="open = !open" x-on:click.away="open = false" type="button"
class="py-4 mx-4" id="menu-button" aria-expanded="true" aria-haspopup="true">
<svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M224 128a8 8 0 0 1-8 8h-80v80a8 8 0 0 1-16 0v-80H40a8 8 0 0 1 0-16h80V40a8 8 0 0 1 16 0v80h80a8 8 0 0 1 8 8" />
<svg class="icon text-neutral-400" xmlns="http://www.w3.org/2000/svg" width="200" height="200"
viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
@ -131,11 +132,24 @@ class="{{ request()->is('team*') ? 'text-warning icon' : 'icon' }}"
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
</svg>
Teams @if (isCloud())
/ Subscription
@endif
Teams
</a>
</li>
@if (isCloud())
<li title="Subscription" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline"
href="{{ route('subscription.show') }}">
<svg xmlns="http://www.w3.org/2000/svg"
class="{{ request()->is('subscription*') ? 'text-warning icon' : 'icon' }}"
viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2"
d="M3 8a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3zm0 2h18M7 15h.01M11 15h2" />
</svg>
Subscription
</a>
</li>
@endif
@if (isInstanceAdmin())
<li title="Settings" class="hover:bg-coolgray-200">

View File

@ -34,7 +34,7 @@ class="font-bold text-warning">{{ config('constants.limits.trial_period') }}
<div>
</div>
</div>
<div class="p-4 rounded bg-coolgray-400">
{{-- <div class="p-4 rounded bg-coolgray-400">
<h2 id="tier-hobby" class="flex items-start gap-4 text-4xl font-bold tracking-tight">Unlimited Trial
<x-forms.button><a class="font-bold text-white hover:no-underline"
href="https://github.com/coollabsio/coolify">Get Started</a></x-forms.button>
@ -42,8 +42,11 @@ class="font-bold text-warning">{{ config('constants.limits.trial_period') }}
<p class="mt-4 text-sm leading-6">Start self-hosting <span class="text-warning">without limits</span> with
our
OSS version. Same features as the paid version, but you have to manage by yourself.</p>
</div>
</div> --}}
<div class="flow-root mt-12">
<div class="pb-10 text-xl text-center">For the detailed list of features, please visit our landing page: <a
class="font-bold text-white underline" href="https://coolify.io">coolify.io</a></div>
<div
class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap-y-16 sm:mx-auto lg:-mx-8 lg:mt-0 lg:max-w-none lg:grid-cols-3 lg:divide-x lg:divide-y-0 xl:-mx-4">
@ -70,21 +73,18 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
{{ $basic }}
@endisset
@endif
<p class="mt-10 text-sm leading-6 text-white h-[6.5rem]">Start self-hosting in
the cloud
with a
single
server.
<p class="mt-10 text-sm leading-6 text-white h-[6.5rem]">Begin hosting your own services in the
cloud.
</p>
<ul role="list" class="space-y-3 text-sm leading-6 ">
<li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
<li class="flex">
<svg class="flex-none w-5 h-6 mr-3 text-warning" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
</svg>
2 servers <x-helper helper="Bring Your Own Server." />
Connect <span class="px-1 font-bold text-white">2</span> servers
</li>
<li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
@ -141,17 +141,18 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
{{ $pro }}
@endisset
@endif
<p class="h-20 mt-10 text-sm leading-6 text-white">Scale your business or self-hosting environment.
<p class="h-20 mt-10 text-sm leading-6 text-white">Expand your business or set up your own hosting
environment.
</p>
<ul role="list" class="mt-6 space-y-3 text-sm leading-6 ">
<li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
<li class="flex ">
<svg class="flex-none w-5 h-6 mr-3 text-warning" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
</svg>
10 servers <x-helper helper="Bring Your Own Server." />
Connect <span class="px-1 font-bold text-white">10</span> servers
</li>
<li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
@ -208,17 +209,17 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
{{ $ultimate }}
@endisset
@endif
<p class="h-20 mt-10 text-sm leading-6 text-white">Deploy complex infrastructures and
manage them easily in one place.</p>
<p class="h-20 mt-10 text-sm leading-6 text-white">Easily manage complex infrastructures in a
single location.</p>
<ul role="list" class="mt-6 space-y-3 text-sm leading-6 ">
<li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
<li class="flex ">
<svg class="flex-none w-5 h-6 mr-3 text-warning" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
</svg>
? servers <x-helper helper="Bring Your Own Server." />
Connect <span class="px-1 font-bold text-white">unlimited</span> servers
</li>
<li class="flex gap-x-3">
@ -254,7 +255,7 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
</ul>
</div>
</div>
<div class="p-4 mt-10 rounded">
{{-- <div class="p-4 mt-10 rounded">
<div class="flex items-start gap-4 text-xl tracking-tight">Need official support for
your self-hosted instance?
<x-forms.button>
@ -263,9 +264,10 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
Us</a>
</x-forms.button>
</div>
</div>
</div> --}}
</div>
<div class="pt-8 pb-12 text-4xl font-bold text-center text-white">Included in all plans</div>
{{-- <div class="pt-8 pb-12 text-4xl font-bold text-center text-white">Included in all plans</div>
<div class="grid grid-cols-1 gap-10 md:grid-cols-2 gap-y-28">
<div>
<div class="flex items-center gap-4 mb-4">
@ -433,7 +435,7 @@ class="text-warning">*</span>
</div>
<div class="pt-20 text-xs">
<span class="text-warning">*</span> Some features are work in progress and will be available soon.
</div>
</div> --}}
</div>
@isset($other)
{{ $other }}

View File

@ -1,11 +1,11 @@
<div class="navbar-main" x-data>
<a class="{{ request()->routeIs('project.service.configuration') ? 'text-white' : '' }}"
<a class="{{ request()->routeIs('project.service.configuration') ? 'text-white' : '' }}"
href="{{ route('project.service.configuration', $parameters) }}">
<button>Configuration</button>
</a>
<x-services.links />
<div class="flex-1"></div>
@if ($service->status() === 'degraded')
@if (str($service->status())->contains('degraded'))
<button wire:click='deploy' onclick="startService.showModal()"
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
@ -26,11 +26,10 @@ class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"
Stop
</button>
@endif
@if ($service->status() === 'running')
@if (str($service->status())->contains('running'))
<button wire:click='restart' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
@ -47,7 +46,7 @@ class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"
Stop
</button>
@endif
@if ($service->status() === 'exited')
@if (str($service->status())->contains('exited'))
<button wire:click='stop(true)'
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 " viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
@ -70,9 +69,9 @@ class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"
</div>
@script
<script>
$wire.on('image-pulled', () => {
startService.showModal();
});
</script>
<script>
$wire.on('image-pulled', () => {
startService.showModal();
});
</script>
@endscript

View File

@ -2,7 +2,12 @@
'status' => 'Degraded',
])
<x-loading wire:loading.delay.longer />
<div class="flex items-center gap-2" wire:loading.remove.delay.longer>
<div class="flex items-center" wire:loading.remove.delay.longer>
<div class="badge badge-warning badge-xs"></div>
<div class="text-xs font-medium tracking-wide text-warning">{{ Str::headline($status) }}</div>
<div class="pl-2 pr-1 text-xs font-bold tracking-widerr text-warning">
{{ str($status)->before(':')->headline() }}
</div>
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
<div class="text-xs text-warning">({{ str($status)->after(':') }})</div>
@endif
</div>

View File

@ -2,7 +2,7 @@
'status' => 'Restarting',
])
<x-loading wire:loading.delay.longer />
<div class="flex items-center " wire:loading.remove.delay.longer>
<div class="flex items-center" wire:loading.remove.delay.longer>
<div class="badge badge-warning badge-xs"></div>
<div class="pl-2 pr-1 text-xs font-bold tracking-widerr text-warning">
{{ str($status)->before(':')->headline() }}

View File

@ -3,5 +3,5 @@
You can try out Coolify, without payment information for free. If you like it, you can upgrade to a paid plan at any time.
[Click here](https://app.coolify.io/subscription) to start your trial.
[Click here](https://app.coolify.io/subscription/new) to start your trial.
</x-emails.layout>

View File

@ -5,7 +5,7 @@
<h1>Dashboard</h1>
<div class="subtitle">Your self-hosted environment</div>
@if (request()->query->get('success'))
<div class="text-white rounded alert alert-success">
<div class="mb-10 text-white rounded alert alert-success">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
@ -16,53 +16,52 @@
</div>
@endif
@if ($projects->count() === 0 && $servers->count() === 0)
No resources found. Add your first server / private key <a class="text-white underline"
href="{{ route('server.create') }}">here</a>.
No resources found. Add your first server & private key <a class="text-white underline"
href="{{ route('server.create') }}">here</a> or go to the <a class="text-white underline" href="{{ route('boarding') }}">boarding page</a>.
@endif
@if ($projects->count() > 0)
<h3 class="pb-4">Projects</h3>
@endif
@if ($projects->count() === 1)
<div class="grid grid-cols-1 gap-2">
@else
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@endif
@foreach ($projects as $project)
<div class="gap-2 border border-transparent cursor-pointer box group">
@if (data_get($project, 'environments')->count() === 1)
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
href="{{ route('project.resource.index', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<div class="font-bold text-white">{{ $project->name }}</div>
<div class="description">
{{ $project->description }}</div>
</a>
@if ($projects->count() === 1)
<div class="grid grid-cols-1 gap-2">
@else
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
<div class="font-bold text-white">{{ $project->name }}</div>
<div class="description">
{{ $project->description }}</div>
</a>
@endif
<div class="flex items-center">
<a class="mx-4 rounded group-hover:text-white hover:no-underline"
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="font-bold hover:text-warning">+ Add Resource</span>
</a>
<a class="mx-4 rounded group-hover:text-white"
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
</a>
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@endif
@foreach ($projects as $project)
<div class="gap-2 border border-transparent cursor-pointer box group">
@if (data_get($project, 'environments')->count() === 1)
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
href="{{ route('project.resource.index', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<div class="font-bold text-white">{{ $project->name }}</div>
<div class="description">
{{ $project->description }}</div>
</a>
@else
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
<div class="font-bold text-white">{{ $project->name }}</div>
<div class="description">
{{ $project->description }}</div>
</a>
@endif
<div class="flex items-center">
<a class="mx-4 rounded group-hover:text-white hover:no-underline"
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="font-bold hover:text-warning">+ Add Resource</span>
</a>
<a class="mx-4 rounded group-hover:text-white"
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
</a>
</div>
</div>
</div>
@endforeach
@endforeach
</div>
@if ($projects->count() > 0)
<h3 class="py-4">Servers</h3>
@ -139,6 +138,7 @@
<div>No deployments running.</div>
@endforelse
</div>
@endif
<script>
function gotoProject(uuid, environment = 'production') {
window.location.href = '/project/' + uuid + '/' + environment;

View File

@ -168,7 +168,7 @@
<x-forms.button wire:click="loadServices('force')">Reload List</x-forms.button>
<input
class="w-full text-white rounded input input-sm bg-coolgray-200 disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
wire:model.live.debounce.200ms="search" placeholder="Search...">
wire:model.live.debounce.200ms="search" autofocus placeholder="Search...">
</div>
<div class="grid justify-start grid-cols-1 gap-4 text-left xl:grid-cols-3">
@if ($loadingServices)
@ -176,28 +176,29 @@ class="w-full text-white rounded input input-sm bg-coolgray-200 disabled:bg-cool
@else
@forelse ($services as $serviceName => $service)
@if (data_get($service, 'minversion') && version_compare(config('version'), data_get($service, 'minversion'), '<'))
<x-resource-view wire="setType('one-click-service-{{ $serviceName }}')">
<x-slot:title> {{ Str::headline($serviceName) }}</x-slot>
<x-slot:description>
@if (data_get($service, 'slogan'))
{{ data_get($service, 'slogan') }}
@endif
<x-resource-view wire="setType('one-click-service-{{ $serviceName }}')">
<x-slot:title> {{ Str::headline($serviceName) }}</x-slot>
<x-slot:description>
@if (data_get($service, 'slogan'))
{{ data_get($service, 'slogan') }}
@endif
</x-slot>
<x-slot:logo>
@if (data_get($service, 'logo'))
<img class="w-[4.5rem]
</x-slot>
<x-slot:logo>
@if (data_get($service, 'logo'))
<img class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100"
src="{{ asset(data_get($service, 'logo')) }}">
@endif
</x-slot:logo>
<x-slot:documentation>
{{ data_get($service, 'documentation') }}
</x-slot>
<x-slot:upgrade>
You need to upgrade Coolify to {{ data_get($service, 'minversion') }} to use this service.
</x-slot>
</x-resource-view>
src="{{ asset(data_get($service, 'logo')) }}">
@endif
</x-slot:logo>
<x-slot:documentation>
{{ data_get($service, 'documentation') }}
</x-slot>
<x-slot:upgrade>
You need to upgrade Coolify to {{ data_get($service, 'minversion') }} to use this
service.
</x-slot>
</x-resource-view>
{{-- <button class="text-left cursor-not-allowed bg-coolgray-100 box-without-bg" disabled>
<div class="flex flex-col mx-6">
<div class="font-bold">
@ -215,10 +216,14 @@ class="w-full text-white rounded input input-sm bg-coolgray-200 disabled:bg-cool
@endif
</x-slot>
<x-slot:logo>
@if (data_get($service, 'logo'))
@if (file_exists(public_path(data_get($service, 'logo'))))
<img class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100"
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100"
src="{{ asset(data_get($service, 'logo')) }}">
@else
<img class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100"
src="{{ asset('svgs/unknown.svg') }}">
@endif
</x-slot:logo>
<x-slot:documentation>

View File

@ -1,25 +1,33 @@
<div>
@if (subscriptionProvider() === 'stripe')
<x-forms.button wire:click='stripeCustomerPortal'>Manage My Subscription</x-forms.button>
<div class="pt-4">
<div>Current Plan: <span class="text-warning">{{ data_get(currentTeam(), 'subscription')->type() }}<span>
</div>
<div class="pb-4">Your current Plan is: <strong
class="text-warning">{{ data_get(currentTeam(), 'subscription')->type() }}</strong></div>
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
<div>Subscription is active but on cancel period.</div>
@else
<div>Subscription is active. Last invoice is
{{ currentTeam()->subscription->stripe_invoice_paid ? 'paid' : 'not paid' }}.</div>
@endif
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
<a class="hover:no-underline" href="{{ route('subscription.index') }}"><x-forms.button>Subscribe
again</x-forms.button></a>
@endif
<div>To update your subscription (upgrade / downgrade), please <a class="text-white underline"
href="{{ config('coolify.contact') }}" target="_blank">contact us.</a></div>
<h3 class="pt-4">Limits</h3>
<div>Server: {{ $server_limits }}</div>
<h3 class="pt-4">Actions</h3>
<div class="pb-4">Cancel, upgrade or downgrade your subscription.</div>
<div class="flex gap-2">
<x-forms.button wire:click='stripeCustomerPortal'>Manage your subscription on <svg
xmlns="http://www.w3.org/2000/svg" class="w-12" viewBox="0 0 512 214">
<path fill="#635BFF"
d="M512 110.08c0-36.409-17.636-65.138-51.342-65.138c-33.85 0-54.33 28.73-54.33 64.854c0 42.808 24.179 64.426 58.88 64.426c16.925 0 29.725-3.84 39.396-9.244v-28.445c-9.67 4.836-20.764 7.823-34.844 7.823c-13.796 0-26.027-4.836-27.591-21.618h69.547c0-1.85.284-9.245.284-12.658Zm-70.258-13.511c0-16.071 9.814-22.756 18.774-22.756c8.675 0 17.92 6.685 17.92 22.756h-36.694Zm-90.31-51.627c-13.939 0-22.899 6.542-27.876 11.094l-1.85-8.818h-31.288v165.83l35.555-7.537l.143-40.249c5.12 3.698 12.657 8.96 25.173 8.96c25.458 0 48.64-20.48 48.64-65.564c-.142-41.245-23.609-63.716-48.498-63.716Zm-8.534 97.991c-8.391 0-13.37-2.986-16.782-6.684l-.143-52.765c3.698-4.124 8.818-6.968 16.925-6.968c12.942 0 21.902 14.506 21.902 33.137c0 19.058-8.818 33.28-21.902 33.28ZM241.493 36.551l35.698-7.68V0l-35.698 7.538V36.55Zm0 10.809h35.698v124.444h-35.698V47.36Zm-38.257 10.524L200.96 47.36h-30.72v124.444h35.556V87.467c8.39-10.951 22.613-8.96 27.022-7.396V47.36c-4.551-1.707-21.191-4.836-29.582 10.524Zm-71.112-41.386l-34.702 7.395l-.142 113.92c0 21.05 15.787 36.551 36.836 36.551c11.662 0 20.195-2.133 24.888-4.693V140.8c-4.55 1.849-27.022 8.391-27.022-12.658V77.653h27.022V47.36h-27.022l.142-30.862ZM35.982 83.484c0-5.546 4.551-7.68 12.09-7.68c10.808 0 24.461 3.272 35.27 9.103V51.484c-11.804-4.693-23.466-6.542-35.27-6.542C19.2 44.942 0 60.018 0 85.192c0 39.252 54.044 32.995 54.044 49.92c0 6.541-5.688 8.675-13.653 8.675c-11.804 0-26.88-4.836-38.827-11.378v33.849c13.227 5.689 26.596 8.106 38.827 8.106c29.582 0 49.92-14.648 49.92-40.106c-.142-42.382-54.329-34.845-54.329-50.774Z" />
</svg></x-forms.button>
</div>
</div>
<div class="pt-4">
If you have any problem, please <a class="text-white underline" href="{{ config('coolify.contact') }}"
target="_blank">contact us.</a>
</div>
@endif
@if (subscriptionProvider() === 'lemon')
{{-- @if (subscriptionProvider() === 'lemon')
<div>Status: {{ currentTeam()->subscription->lemon_status }}</div>
<div>Type: {{ currentTeam()->subscription->lemon_variant_name }}</div>
@if (currentTeam()->subscription->lemon_status === 'cancelled')
@ -49,6 +57,5 @@ class="text-white underline" href="{{ config('coolify.contact') }}" target="_bla
Subscription</x-forms.button></a>
</div>
</div>
@endif
@endif --}}
</div>

View File

@ -0,0 +1,7 @@
<div>
<div>
<h1>Subscription</h1>
<div>Here you can see and manage your subscription.</div>
</div>
<livewire:subscription.actions />
</div>

View File

@ -14,19 +14,6 @@
</div>
</form>
@if (isCloud())
<div class="pb-8">
<h2>Subscription</h2>
@if (data_get(currentTeam(), 'subscription'))
<livewire:subscription.actions />
@else
<x-forms.button class="mt-4"><a class="text-white hover:no-underline"
href="{{ route('subscription.index') }}">Subscribe Now</a>
</x-forms.button>
@endif
</div>
@endif
<div>
<h2>Danger Zone</h2>
<div class="pb-4">Woah. I hope you know what are you doing.</div>
@ -36,7 +23,7 @@
@elseif(auth()->user()->teams()->get()->count() === 1)
<div>You can't delete your last team.</div>
@elseif(currentTeam()->subscription && currentTeam()->subscription?->lemon_status !== 'cancelled')
<div>Please cancel your subscription before delete this team (Manage My Subscription).</div>
<div>Please cancel your subscription <a class="text-white underline" href="{{route('subscription.show')}}">here</a> before delete this team.</div>
@else
@if (currentTeam()->isEmpty())
<div class="pb-4">This will delete your team. Beware! There is no coming back!</div>

View File

@ -1,39 +1,55 @@
<div>
@if ($invitations->count() > 0)
<h4 class="pb-2">Pending Invitations</h4>
<div class="overflow-x-auto">
<table>
<thead>
<tr>
<th>Email</th>
<th>Via</th>
<th>Role</th>
<th>Invitation Link</th>
<th>Actions</th>
</tr>
</thead>
<tbody x-data>
@foreach ($invitations as $invite)
<tr>
<td>{{ $invite->email }}</td>
<td>{{ $invite->via }}</td>
<td>{{ $invite->role }}</td>
<td class="flex gap-2" x-data="checkProtocol">
<template x-if="isHttps">
<x-forms.button x-on:click="copyToClipboard('{{ $invite->link }}')">Copy Invitation
Link</x-forms.button>
</template>
<x-forms.input id="null" type="password" value="{{ $invite->link }}" />
</td>
<td>
<x-forms.button wire:click.prevent='deleteInvitation({{ $invite->id }})'>Revoke
Invitation
</x-forms.button>
</td>
</tr>
@endforeach
</tbody>
</table>
<h2 class="pb-2">Pending Invitations</h2>
<div class="flex flex-col">
<div class="flex flex-col">
<div class="overflow-x-auto">
<div class="inline-block min-w-full">
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-coolgray-400">
<thead>
<tr class="text-neutral-500">
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Email
</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">
Via</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Role</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Invitation Link
</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Actions
</th>
</tr>
</thead>
<tbody class="divide-y divide-coolgray-400">
@foreach ($invitations as $invite)
<tr class="text-white bg-coolblack hover:bg-coolgray-100/40">
<td class="px-5 py-4 text-sm whitespace-nowrap">{{ $invite->email }}</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">{{ $invite->via }}</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">{{ $invite->role }}</td>
<td class="px-5 py-4 text-sm whitespace-nowrap" x-data="checkProtocol">
<template x-if="isHttps">
<x-forms.button
x-on:click="copyToClipboard('{{ $invite->link }}')">Copy
Invitation
Link</x-forms.button>
</template>
<x-forms.input id="null" type="password"
value="{{ $invite->link }}" />
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
<x-forms.button
wire:click.prevent='deleteInvitation({{ $invite->id }})'>Revoke
Invitation
</x-forms.button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endif
</div>

View File

@ -2,6 +2,7 @@
<form wire:submit='viaLink' class="flex items-center gap-2">
<x-forms.input id="email" type="email" name="email" placeholder="Email" />
<x-forms.select id="role" name="role">
<option value="owner">Owner</option>
<option value="admin">Admin</option>
<option value="member">Member</option>
</x-forms.select>

View File

@ -1,25 +1,47 @@
<tr>
<td>
{{ $member->name }}</th>
<td>{{ $member->email }}</td>
<td>
{{ data_get($member, 'pivot.role') }}</td>
<td>
{{-- TODO: This is not good --}}
<tr @class([
'text-white bg-coolblack hover:bg-coolgray-100',
'bg-coolgray-100' => $member->id == auth()->user()->id,
])>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ $member->name }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ $member->email }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($member, 'pivot.role') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
@if (auth()->user()->isAdminFromSession())
@if ($member->id !== auth()->user()->id)
@if (data_get($member, 'pivot.role') !== 'owner')
@if (data_get($member, 'pivot.role') !== 'admin')
<x-forms.button wire:click="makeAdmin">Convert to Admin</x-forms.button>
@else
<x-forms.button wire:click="makeReadonly">Convert to Member</x-forms.button>
@if (auth()->user()->isOwner())
@if (data_get($member, 'pivot.role') === 'owner')
<x-forms.button wire:click="makeAdmin">To Admin</x-forms.button>
<x-forms.button wire:click="makeReadonly">To Member</x-forms.button>
<x-forms.button isError wire:click="remove">Remove</x-forms.button>
@endif
@if (data_get($member, 'pivot.role') === 'admin')
<x-forms.button wire:click="makeOwner">To Owner</x-forms.button>
<x-forms.button wire:click="makeReadonly">To Member</x-forms.button>
<x-forms.button isError wire:click="remove">Remove</x-forms.button>
@endif
@if (data_get($member, 'pivot.role') === 'member')
<x-forms.button wire:click="makeOwner">To Owner</x-forms.button>
<x-forms.button wire:click="makeAdmin">To Admin</x-forms.button>
<x-forms.button isError wire:click="remove">Remove</x-forms.button>
@endif
@elseif (auth()->user()->isAdmin())
@if (data_get($member, 'pivot.role') === 'admin')
<x-forms.button wire:click="makeReadonly">To Member</x-forms.button>
<x-forms.button isError wire:click="remove">Remove</x-forms.button>
@endif
@if (data_get($member, 'pivot.role') === 'member')
<x-forms.button wire:click="makeAdmin">To Admin</x-forms.button>
<x-forms.button isError wire:click="remove">Remove</x-forms.button>
@endif
<x-forms.button wire:click="remove">Remove</x-forms.button>
@else
<x-forms.button disabled>Remove</x-forms.button>
@endif
@else
<x-forms.button disabled>Remove</x-forms.button>
<div class="text-neutral-500">(This is you)</div>
@endif
@endif
</td>

View File

@ -1,29 +1,39 @@
<div>
<x-team.navbar />
<h2>Members</h2>
<div class="pt-4 overflow-hidden">
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (currentTeam()->members->sortBy('name') as $member)
<livewire:team.member :member="$member" :wire:key="$member->id" />
@endforeach
</tbody>
</table>
<div class="flex flex-col">
<div class="flex flex-col">
<div class="overflow-x-auto">
<div class="inline-block min-w-full">
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-coolgray-400">
<thead>
<tr class="text-neutral-500">
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Name
</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Email</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Role</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-coolgray-400">
@foreach (currentTeam()->members as $member)
<livewire:team.member :member="$member" :wire:key="$member->id" />
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@if (auth()->user()->isAdminFromSession())
<div class="py-4">
@if (is_transactional_emails_active())
<h3 class="pb-4">Invite a new member</h3>
<h2 class="pb-4">Invite New Member</h2>
@else
<h3>Invite a new member</h3>
<h2>Invite New Member</h2>
@if (isInstanceAdmin())
<div class="pb-4 text-xs text-warning">You need to configure (as root team) <a href="/settings#smtp"
class="underline text-warning">Transactional

View File

@ -72,6 +72,7 @@
use App\Livewire\Server\Proxy\Logs as ProxyLogs;
use App\Livewire\Source\Github\Change as GitHubChange;
use App\Livewire\Subscription\Index as SubscriptionIndex;
use App\Livewire\Subscription\Show as SubscriptionShow;
use App\Livewire\Tags\Index as TagsIndex;
use App\Livewire\Tags\Show as TagsShow;
@ -110,7 +111,8 @@
Route::get('/', Dashboard::class)->name('dashboard');
Route::get('/boarding', BoardingIndex::class)->name('boarding');
Route::get('/subscription', SubscriptionIndex::class)->name('subscription.index');
Route::get('/subscription', SubscriptionShow::class)->name('subscription.show');
Route::get('/subscription/new', SubscriptionIndex::class)->name('subscription.index');
Route::get('/settings', SettingsIndex::class)->name('settings.index');
Route::get('/settings/license', SettingsLicense::class)->name('settings.license');

View File

@ -816,9 +816,7 @@
Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
}
$subscription->update([
'stripe_plan_id' => $planId,
'stripe_invoice_paid' => true,
]);
break;

View File

@ -7,11 +7,11 @@ services:
appsmith:
image: index.docker.io/appsmith/appsmith-ce:latest
environment:
- SERVICE_FQDN
- SERVICE_FQDN_APPSMITH
- APPSMITH_MAIL_ENABLED=false
- APPSMITH_DISABLE_TELEMETRY=true
- APPSMITH_DISABLE_INTERCOM=true
- APPSMITH_SENTRY_DSN=
- APPSMITH_SENTRY_DSN=
- APPSMITH_SMART_LOOK_ID=
volumes:
- stacks-data:/appsmith-stacks

View File

@ -0,0 +1,56 @@
# documentation: https://firefly-iii.org
# slogan: A personal finances manager that can help you save money.
# tags: finance, money, personal, manager
# logo: svgs/firefly.svg
services:
firefly:
image: fireflyiii/core:latest
environment:
- SERVICE_FQDN_FIREFLY
- APP_KEY=$SERVICE_BASE64_APPKEY
- DB_HOST=mysql
- DB_PORT=3306
- DB_CONNECTION=mysql
- DB_DATABASE=${MYSQL_DATABASE:-firefly}
- DB_USERNAME=$SERVICE_USER_MYSQL
- DB_PASSWORD=$SERVICE_PASSWORD_MYSQL
- STATIC_CRON_TOKEN=$SERVICE_BASE64_CRONTOKEN
volumes:
- firefly-upload:/var/www/html/storage/upload
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080"]
interval: 5s
timeout: 20s
retries: 10
depends_on:
mysql:
condition: service_healthy
mysql:
image: mariadb:lts
environment:
- MYSQL_USER=${SERVICE_USER_MYSQL}
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
- MYSQL_DATABASE=${MYSQL_DATABASE:-firefly}
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
healthcheck:
test:
[
"CMD",
"mysqladmin",
"ping",
"-h",
"localhost",
"-uroot",
"-p${SERVICE_PASSWORD_MYSQLROOT}",
]
interval: 5s
timeout: 20s
retries: 10
volumes:
- firefly-mysql-data:/var/lib/mysql
# cron:
# image: alpine
# command: sh -c "echo \"0 3 * * * wget -qO- http://app:8080/api/v1/cron/$STATIC_CRON_TOKEN\" | crontab - && crond -f -L /dev/stdout"
# environment:
# - STATIC_CRON_TOKEN=$SERVICE_PASSWORD_32_CRONTOKEN

View File

@ -2,7 +2,7 @@
"appsmith": {
"documentation": "https:\/\/appsmith.com",
"slogan": "Appsmith is low-code application platform for building internal tools.",
"compose": "c2VydmljZXM6CiAgYXBwc21pdGg6CiAgICBpbWFnZTogJ2luZGV4LmRvY2tlci5pby9hcHBzbWl0aC9hcHBzbWl0aC1jZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE4KICAgICAgLSBBUFBTTUlUSF9NQUlMX0VOQUJMRUQ9ZmFsc2UKICAgICAgLSBBUFBTTUlUSF9ESVNBQkxFX1RFTEVNRVRSWT10cnVlCiAgICAgIC0gQVBQU01JVEhfRElTQUJMRV9JTlRFUkNPTT10cnVlCiAgICAgIC0gQVBQU01JVEhfU0VOVFJZX0RTTj0KICAgICAgLSBBUFBTTUlUSF9TTUFSVF9MT09LX0lEPQogICAgdm9sdW1lczoKICAgICAgLSAnc3RhY2tzLWRhdGE6L2FwcHNtaXRoLXN0YWNrcycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gTk9ORQo=",
"compose": "c2VydmljZXM6CiAgYXBwc21pdGg6CiAgICBpbWFnZTogJ2luZGV4LmRvY2tlci5pby9hcHBzbWl0aC9hcHBzbWl0aC1jZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQVBQU01JVEgKICAgICAgLSBBUFBTTUlUSF9NQUlMX0VOQUJMRUQ9ZmFsc2UKICAgICAgLSBBUFBTTUlUSF9ESVNBQkxFX1RFTEVNRVRSWT10cnVlCiAgICAgIC0gQVBQU01JVEhfRElTQUJMRV9JTlRFUkNPTT10cnVlCiAgICAgIC0gQVBQU01JVEhfU0VOVFJZX0RTTj0KICAgICAgLSBBUFBTTUlUSF9TTUFSVF9MT09LX0lEPQogICAgdm9sdW1lczoKICAgICAgLSAnc3RhY2tzLWRhdGE6L2FwcHNtaXRoLXN0YWNrcycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gTk9ORQo=",
"tags": [
"lowcode",
"nocode",
@ -192,6 +192,19 @@
"logo": "svgs\/filebrowser.svg",
"minversion": "0.0.0"
},
"firefly": {
"documentation": "https:\/\/firefly-iii.org",
"slogan": "A personal finances manager that can help you save money.",
"compose": "c2VydmljZXM6CiAgZmlyZWZseToKICAgIGltYWdlOiAnZmlyZWZseWlpaS9jb3JlOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GSVJFRkxZCiAgICAgIC0gQVBQX0tFWT0kU0VSVklDRV9CQVNFNjRfQVBQS0VZCiAgICAgIC0gREJfSE9TVD1teXNxbAogICAgICAtIERCX1BPUlQ9MzMwNgogICAgICAtIERCX0NPTk5FQ1RJT049bXlzcWwKICAgICAgLSAnREJfREFUQUJBU0U9JHtNWVNRTF9EQVRBQkFTRTotZmlyZWZseX0nCiAgICAgIC0gREJfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICAtIERCX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIC0gU1RBVElDX0NST05fVE9LRU49JFNFUlZJQ0VfQkFTRTY0X0NST05UT0tFTgogICAgdm9sdW1lczoKICAgICAgLSAnZmlyZWZseS11cGxvYWQ6L3Zhci93d3cvaHRtbC9zdG9yYWdlL3VwbG9hZCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo4MDgwJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgICBkZXBlbmRzX29uOgogICAgICBteXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIG15c3FsOgogICAgaW1hZ2U6ICdtYXJpYWRiOmx0cycKICAgIGVudmlyb25tZW50OgogICAgICAtICdNWVNRTF9VU0VSPSR7U0VSVklDRV9VU0VSX01ZU1FMfScKICAgICAgLSAnTVlTUUxfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01ZU1FMfScKICAgICAgLSAnTVlTUUxfREFUQUJBU0U9JHtNWVNRTF9EQVRBQkFTRTotZmlyZWZseX0nCiAgICAgIC0gJ01ZU1FMX1JPT1RfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01ZU1FMUk9PVH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gbXlzcWxhZG1pbgogICAgICAgIC0gcGluZwogICAgICAgIC0gJy1oJwogICAgICAgIC0gbG9jYWxob3N0CiAgICAgICAgLSAnLXVyb290JwogICAgICAgIC0gJy1wJHtTRVJWSUNFX1BBU1NXT1JEX01ZU1FMUk9PVH0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2ZpcmVmbHktbXlzcWwtZGF0YTovdmFyL2xpYi9teXNxbCcK",
"tags": [
"finance",
"money",
"personal",
"manager"
],
"logo": "svgs\/firefly.svg",
"minversion": "0.0.0"
},
"formbricks": {
"documentation": "https:\/\/formbricks.com",
"slogan": "Open Source Experience Management",

View File

@ -4,7 +4,7 @@
"version": "3.12.36"
},
"v4": {
"version": "4.0.0-beta.223"
"version": "4.0.0-beta.224"
}
}
}