mirror of
https://github.com/fleetbase/fleetbase.git
synced 2026-02-08 09:13:52 +00:00
Compare commits
258 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f55462ee9c | ||
|
|
989fa9a777 | ||
|
|
edec9d02a4 | ||
|
|
a79ebda392 | ||
|
|
66c210f42a | ||
|
|
43f9914afa | ||
|
|
cbcf7a239b | ||
|
|
58686b9d77 | ||
|
|
360f01ebf7 | ||
|
|
e02c8e831a | ||
|
|
2b8af0a47d | ||
|
|
7603c00ab1 | ||
|
|
6cd226b4f9 | ||
|
|
019ca9bc2d | ||
|
|
86a3b339d4 | ||
|
|
f1edcf607b | ||
|
|
44eac77cde | ||
|
|
529f1cfa90 | ||
|
|
517bc7b3bc | ||
|
|
ccf3fc7b31 | ||
|
|
a0febb1cbc | ||
|
|
768d2e3c85 | ||
|
|
6b5bbd1b7c | ||
|
|
e4a8f9519b | ||
|
|
3de9b6fccb | ||
|
|
979b451130 | ||
|
|
9d4b1167ab | ||
|
|
98cde6795a | ||
|
|
3a70c42054 | ||
|
|
becf73a83e | ||
|
|
f5c16ffe60 | ||
|
|
4b3d23c0b9 | ||
|
|
7ba7b62096 | ||
|
|
70c45a34d4 | ||
|
|
233154ecb2 | ||
|
|
04dd58397e | ||
|
|
6e537daf24 | ||
|
|
662c5ab716 | ||
|
|
502363efc4 | ||
|
|
b7d8a58861 | ||
|
|
8d8c1d2253 | ||
|
|
11f6d173b7 | ||
|
|
7d232597ff | ||
|
|
0e4baa34e0 | ||
|
|
d6d078df62 | ||
|
|
7252a95805 | ||
|
|
b25da51496 | ||
|
|
7d776f2bd5 | ||
|
|
ecfcec72e4 | ||
|
|
ca1741a4b2 | ||
|
|
7ba7821fed | ||
|
|
947565bcf0 | ||
|
|
2d4cc5cf66 | ||
|
|
af3c60fc7c | ||
|
|
53a87d6f38 | ||
|
|
a372b515da | ||
|
|
d7f8f87315 | ||
|
|
f8b9c0415b | ||
|
|
36673ef564 | ||
|
|
19341c81e7 | ||
|
|
b4ecf5bda9 | ||
|
|
1b714a7ef8 | ||
|
|
e41cd62ea5 | ||
|
|
1ca1342052 | ||
|
|
a5a5ddb0d5 | ||
|
|
c51f3ca6c8 | ||
|
|
a9b172081a | ||
|
|
a29ca0ecb9 | ||
|
|
6442644438 | ||
|
|
b525ad4b1a | ||
|
|
b161b415d0 | ||
|
|
e444d7994c | ||
|
|
0e44375d32 | ||
|
|
a9bb2f0166 | ||
|
|
ac02a6b6e0 | ||
|
|
4a640561d6 | ||
|
|
0238632fdd | ||
|
|
27652db9c3 | ||
|
|
5957bc6339 | ||
|
|
5eaf2039d4 | ||
|
|
d8c06ae0be | ||
|
|
c9011b3ffa | ||
|
|
520de0f6bc | ||
|
|
ba6ed235e3 | ||
|
|
9b15a6856f | ||
|
|
0a4e5b31cc | ||
|
|
0d47ffc785 | ||
|
|
3f2c739810 | ||
|
|
6b2ab28ec9 | ||
|
|
42df48c9b0 | ||
|
|
268749fcd9 | ||
|
|
50ae560409 | ||
|
|
2e48024949 | ||
|
|
908f60aaac | ||
|
|
a1f2992f18 | ||
|
|
0252b387e2 | ||
|
|
12e1ec2cac | ||
|
|
5f2003eec5 | ||
|
|
e05d12dd87 | ||
|
|
86378d3ede | ||
|
|
1d36bf144b | ||
|
|
293f67d6d1 | ||
|
|
01e8298968 | ||
|
|
094d1d375e | ||
|
|
8a1dee0cbd | ||
|
|
df2da8cea1 | ||
|
|
ced5e6b6fd | ||
|
|
de2cbd2ded | ||
|
|
894f4348dd | ||
|
|
2030a72a71 | ||
|
|
41d20c41c5 | ||
|
|
9976075843 | ||
|
|
b26f735fee | ||
|
|
791cc7283d | ||
|
|
21601737a1 | ||
|
|
0cd9e076b8 | ||
|
|
5fe799b708 | ||
|
|
878a70d328 | ||
|
|
4c5487b6bb | ||
|
|
599fd0e8f7 | ||
|
|
64561c85a8 | ||
|
|
c8d8be11d2 | ||
|
|
12847437b0 | ||
|
|
53eadee8a6 | ||
|
|
657de6c99d | ||
|
|
db601606c5 | ||
|
|
6ec176011a | ||
|
|
d416baf3cc | ||
|
|
d80b968eaa | ||
|
|
eef6847919 | ||
|
|
2ad569eaf6 | ||
|
|
dc18b48f6a | ||
|
|
3e93e161dc | ||
|
|
fac29308fd | ||
|
|
7b62e992eb | ||
|
|
18af3b4515 | ||
|
|
264c564ad6 | ||
|
|
dbf9a37ae9 | ||
|
|
46c4ac9dda | ||
|
|
e692bc3365 | ||
|
|
0b807011a3 | ||
|
|
80d84e1c3b | ||
|
|
08c722359a | ||
|
|
ebd9b0d8cb | ||
|
|
ec5ace214c | ||
|
|
396e0cbf36 | ||
|
|
3faac3481f | ||
|
|
36f837a4af | ||
|
|
094bd6c8ce | ||
|
|
aea35f6f5f | ||
|
|
96fa0294bc | ||
|
|
72ed388cd6 | ||
|
|
5a79a06642 | ||
|
|
010a6b0d05 | ||
|
|
6d138761ee | ||
|
|
1f59d8831b | ||
|
|
746b2142f0 | ||
|
|
343e07e24d | ||
|
|
d6094b8e29 | ||
|
|
59d7fa3de9 | ||
|
|
14c05a6c6a | ||
|
|
5c12254a58 | ||
|
|
65416833ca | ||
|
|
40e0de6e6b | ||
|
|
5986ced2c0 | ||
|
|
ca260bb94c | ||
|
|
95ef8ef3c9 | ||
|
|
df8513cf7b | ||
|
|
e2c544b0c9 | ||
|
|
77b39fccf9 | ||
|
|
eaa448f762 | ||
|
|
627522d61c | ||
|
|
201ac84dc9 | ||
|
|
78b1f31053 | ||
|
|
63cf8128c0 | ||
|
|
5d66c7a35f | ||
|
|
0603888393 | ||
|
|
cd50d349f8 | ||
|
|
e478767a60 | ||
|
|
17cb0cd274 | ||
|
|
a3289ddd41 | ||
|
|
ff8986541c | ||
|
|
04d0c126de | ||
|
|
ad43f74d5c | ||
|
|
3c013a3817 | ||
|
|
4392c7e3ff | ||
|
|
97dfbedd1a | ||
|
|
6d13f22a98 | ||
|
|
2c09c87bb6 | ||
|
|
f7f6991ef3 | ||
|
|
73acd4833c | ||
|
|
1c3c4c16a5 | ||
|
|
5d01438dc9 | ||
|
|
9d004410ee | ||
|
|
3a396f3b54 | ||
|
|
e86b970fc8 | ||
|
|
122a0d186a | ||
|
|
7e258f698f | ||
|
|
65ef642315 | ||
|
|
d79f034dbd | ||
|
|
05b7d5e112 | ||
|
|
61779ab102 | ||
|
|
a287c05380 | ||
|
|
ecc41e587e | ||
|
|
166529f9b4 | ||
|
|
189547f9de | ||
|
|
90a42e8a93 | ||
|
|
d28b1d41fb | ||
|
|
b056ef62b0 | ||
|
|
e70cbacbc2 | ||
|
|
fbd4a7490a | ||
|
|
b9b0eb308b | ||
|
|
48e1b89ec8 | ||
|
|
5e02c95b66 | ||
|
|
db8b5c4d6a | ||
|
|
aa214ccad7 | ||
|
|
cdd5524cf3 | ||
|
|
d99cefd2a4 | ||
|
|
1c58fd43c3 | ||
|
|
9226394159 | ||
|
|
9b23b39f32 | ||
|
|
5310fc3ff3 | ||
|
|
9569053f50 | ||
|
|
b08c054b99 | ||
|
|
2e517f2f95 | ||
|
|
7038d375b0 | ||
|
|
d5ea7f7790 | ||
|
|
4eb4b04121 | ||
|
|
24c5b93005 | ||
|
|
80610b9a48 | ||
|
|
9e5551972e | ||
|
|
b784f890f3 | ||
|
|
797a3d61fe | ||
|
|
d2f0bfe83e | ||
|
|
f28ad85c1a | ||
|
|
d7b0826f3f | ||
|
|
dd6008a8aa | ||
|
|
6671fefaaa | ||
|
|
7136f6195c | ||
|
|
2384887620 | ||
|
|
a2778f1552 | ||
|
|
acfda5ed1a | ||
|
|
e08255007a | ||
|
|
41761ea50e | ||
|
|
5c623819ed | ||
|
|
2bd885b1a2 | ||
|
|
88c3842441 | ||
|
|
2c2a4121a8 | ||
|
|
aadd03f14b | ||
|
|
706e94270d | ||
|
|
1dabc375f9 | ||
|
|
0efec46155 | ||
|
|
8bb2c6b65d | ||
|
|
1c0af1a119 | ||
|
|
dd65ee619b | ||
|
|
e790a0e123 | ||
|
|
723e3ca3d1 | ||
|
|
4eb706d33e |
44
.github/workflows/cd.yml
vendored
44
.github/workflows/cd.yml
vendored
@@ -13,9 +13,24 @@ env:
|
||||
GITHUB_AUTH_KEY: ${{ secrets._GITHUB_AUTH_TOKEN }}
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
name: Setup Environment
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
environment: ${{ steps.extract-env.outputs.environment }}
|
||||
steps:
|
||||
- name: Extract environment from branch name
|
||||
id: extract-env
|
||||
run: |
|
||||
ENVIRONMENT=$(echo "${{ github.ref_name }}" | sed 's|deploy/||')
|
||||
echo "environment=${ENVIRONMENT}" >> $GITHUB_OUTPUT
|
||||
echo "Deploying to environment: ${ENVIRONMENT}"
|
||||
|
||||
build_service:
|
||||
name: Build and Deploy the Service
|
||||
needs: [setup]
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ needs.setup.outputs.environment }}
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
contents: read # This is required for actions/checkout
|
||||
@@ -120,8 +135,9 @@ jobs:
|
||||
|
||||
build_frontend:
|
||||
name: Build and Deploy the Console
|
||||
needs: [build_service]
|
||||
needs: [setup, build_service]
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ needs.setup.outputs.environment }}
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
contents: read # This is required for actions/checkout
|
||||
@@ -192,20 +208,20 @@ jobs:
|
||||
fi
|
||||
working-directory: ./console
|
||||
|
||||
- name: Set Env Variables for QA
|
||||
if: startsWith(github.ref, 'refs/heads/deploy/qa')
|
||||
- name: Set Env Variables
|
||||
run: |
|
||||
echo "STRIPE_KEY=${{ secrets.STRIPE_TEST_KEY }}" >> ./environments/.env.production
|
||||
# ensure file ends with a newline
|
||||
printf '\n' >> ./environments/.env.production
|
||||
|
||||
echo "EXTENSIONS=${{ secrets.EXTENSIONS }}" >> ./environments/.env.production
|
||||
echo "LOGROCKET_APP_ID=${{ secrets.LOGROCKET_APP_ID }}" >> ./environments/.env.production
|
||||
echo "EXTENSIONS=@fleetbase/billing-engine,@fleetbase/internals-engine" >> ./environments/.env.production
|
||||
working-directory: ./console
|
||||
|
||||
- name: Set Env Variables for Production
|
||||
if: startsWith(github.ref, 'refs/heads/deploy/production')
|
||||
run: |
|
||||
echo "STRIPE_KEY=${{ secrets.STRIPE_KEY }}" >> ./environments/.env.production
|
||||
echo "LOGROCKET_APP_ID=${{ secrets.LOGROCKET_APP_ID }}" >> ./environments/.env.production
|
||||
echo "EXTENSIONS=@fleetbase/billing-engine,@fleetbase/internals-engine" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_LICENSE_ANNUAL_ID=${{ secrets.STRIPE_PRICE_LICENSE_ANNUAL_ID }}" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_LICENSE_MONTHLY_ID=${{ secrets.STRIPE_PRICE_LICENSE_MONTHLY_ID }}" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_LICENSE_MAJOR_ID=${{ secrets.STRIPE_PRICE_LICENSE_MAJOR_ID }}" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_LICENSE_MINOR_ID=${{ secrets.STRIPE_PRICE_LICENSE_MINOR_ID }}" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_INSTALLATION_FULL_ID=${{ secrets.STRIPE_PRICE_INSTALLATION_FULL_ID }}" >> ./environments/.env.production
|
||||
echo "STRIPE_PRICE_INSTALLATION_ASSISTED_ID=${{ secrets.STRIPE_PRICE_INSTALLATION_ASSISTED_ID }}" >> ./environments/.env.production
|
||||
working-directory: ./console
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -224,10 +240,6 @@ jobs:
|
||||
set -u
|
||||
|
||||
DEPLOY_BUCKET=${STATIC_DEPLOY_BUCKET:-${{ env.PROJECT }}-${{ env.STACK }}}
|
||||
NEW_BUCKET="${PROJECT}-${STACK}-console"
|
||||
if aws s3api head-bucket --bucket "$NEW_BUCKET" 2>/dev/null; then
|
||||
DEPLOY_BUCKET="$NEW_BUCKET"
|
||||
fi
|
||||
|
||||
# this value will come from the dotenv above
|
||||
echo "Deploying to $DEPLOY_BUCKET"
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
docker-compose.override.yml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
console/public/extensions.json
|
||||
api/public/hot
|
||||
api/public/storage
|
||||
api/storage/*.key
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
http://:8000 {
|
||||
root * /fleetbase/api/public
|
||||
encode zstd br gzip
|
||||
|
||||
php_server {
|
||||
resolve_root_symlink
|
||||
}
|
||||
|
||||
582
README.md
582
README.md
@@ -1,257 +1,325 @@
|
||||
<div id="hero">
|
||||
<p align="center" dir="auto">
|
||||
<a href="https://fleetbase.io" rel="nofollow">
|
||||
<img src="https://user-images.githubusercontent.com/58805033/191936702-fed04b0f-7966-4041-96d0-95e27bf98248.png" alt="Fleetbase logo" width="500" height="120" style="max-width: 100%;">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center" dir="auto">
|
||||
Modular logistics and supply chain operating system
|
||||
<br>
|
||||
<a href="https://docs.fleetbase.io/" rel="nofollow" target="_fleetbase_docs">Documentation</a>
|
||||
·
|
||||
<a href="https://console.fleetbase.io" rel="nofollow" target="_fleetbase_console">Cloud Version</a>
|
||||
·
|
||||
<a href="https://console.fleetbase.io/aws-marketplace" rel="nofollow" target="_aws_marketplace">Deploy on AWS</a>
|
||||
·
|
||||
<a href="https://tally.so/r/3NBpAW" rel="nofollow">Book a Demo</a>
|
||||
·
|
||||
<a href="https://discord.gg/V7RVWRQ2Wm" target="discord" rel="nofollow">Discord</a>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
## What is Fleetbase?
|
||||
|
||||
Fleetbase is a modular logistics and supply chain operating system designed to streamline management, planning, optimization, and operational control across various sectors of the supply chain industry.
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/fleetbase_overview.png" alt="Fleetbase Console" width="1200" style="max-width: 100%;" />
|
||||
</p>
|
||||
|
||||
## Visual Feature Showcase
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-board-kanban.png" alt="Fleetbase Order Board" width="1200" style="max-width: 100%;" />
|
||||
<em>Visualize and manage your orders with a dynamic Kanban board.</em>
|
||||
</p>
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-workflow-config.png" alt="Fleetbase Order Workflow Configuration" width="1200" style="max-width: 100%;" />
|
||||
<em>Create custom order flows and automation with the intuitive workflow builder.</em>
|
||||
</p>
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-map-view.png" alt="Fleetbase Order Map View" width="1200" style="max-width: 100%;" />
|
||||
<em>Track individual orders in real-time on an interactive map.</em>
|
||||
</p>
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/live-map-tracking.png" alt="Fleetbase Live Map Tracking" width="1200" style="max-width: 100%;" />
|
||||
<em>Get a complete overview of your fleet and active orders on a live map.</em>
|
||||
</p>
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/fleet-map-zones.png" alt="Fleetbase Fleet Map with Zones" width="1200" style="max-width: 100%;" />
|
||||
<em>Define and manage service areas and zones for your fleet.</em>
|
||||
</p>
|
||||
|
||||
**Quickstart**
|
||||
|
||||
```bash
|
||||
git clone git@github.com:fleetbase/fleetbase.git
|
||||
cd fleetbase && ./scripts/docker-install.sh
|
||||
```
|
||||
|
||||
## 📖 Table of contents
|
||||
|
||||
- [Features](#-features)
|
||||
- [Install](#-install)
|
||||
- [Deploy on AWS](#-deploy-on-aws-in-one-click)
|
||||
- [Extensions](#-extensions)
|
||||
- [Apps](#-apps)
|
||||
- [Roadmap](#-roadmap)
|
||||
- [Bugs and Feature Requests](#-bugs-and--feature-requests)
|
||||
- [Documentation](#-documentation)
|
||||
- [Contributing](#-contributing)
|
||||
- [Community](#-community)
|
||||
- [Creators](#-creators)
|
||||
- [License & Copyright](#-license-and-copyright)
|
||||
|
||||
## 📦 Features
|
||||
- **Extensible:** Build installable extensions and additional functionality directly into the OS via modular architecture.
|
||||
- **Developer Friendly:** RESTful API, socket, and webhooks to seamlessly integrate with external systems or develop custom applications.
|
||||
- **Native Apps:** Collection of open-source and native apps designed for operations and customer facing.
|
||||
- **Collaboration:** Dedicated chat and comments system for collaboration across your organization.
|
||||
- **Security:** Secure data encryption, adherence to industry-standard security practices, and a comprehensive dynamic Identity and Access Management (IAM) system.
|
||||
- **Telematics:** Integrate and connect to hardware devices and sensors to provide more feedback and visibility into operations.
|
||||
- **Internationalized:** Translate into multiple languages to accommodate diverse user bases and global operations.
|
||||
- **Framework:** PHP core built around logistics and supply chain abstractions to streamline extension development.
|
||||
- **Dynamic:** Configurable rules, flows and logic to enable automation and customization.
|
||||
- **UI/UX:** Clean, responsive user-friendly interface for efficient management and operations from desktop or mobile.
|
||||
- **Dashboards:** Create custom dashboards and widgets to get full visibility into operations.
|
||||
- **Scalability:** Uninterrupted growth with scalable infrastructure and design, capable of handling increasing data volume and user demand as your business expands.
|
||||
- **Continuous Improvements:** Commitment to continuous improvement, providing regular updates that seamlessly introduce optimizations, new features, and overall enhancements to the OS.
|
||||
- **Open Source:** Deploy it either on-premise or in the cloud according to your organization's needs and preferences.
|
||||
|
||||
## 💾 Install
|
||||
Getting up and running with Fleetbase via Docker is the quickest and most straightforward way. If you’d like to use Fleetbase without docker read the [full install guide in the Fleetbase documentation](https://docs.fleetbase.io/getting-started/install).
|
||||
|
||||
Make sure you have both the latest versions of docker and docker-compose installed on your system.
|
||||
|
||||
```bash
|
||||
git clone git@github.com:fleetbase/fleetbase.git
|
||||
cd fleetbase && ./scripts/docker-install.sh
|
||||
```
|
||||
|
||||
### Accessing Fleetbase
|
||||
Once successfully installed and running you can then access the Fleetbase console on port 4200 and the API will be accessible from port 8000.
|
||||
|
||||
Fleetbase Console: http://localhost:4200
|
||||
Fleetbase API: http://localhost:8000
|
||||
|
||||
### Additional Configurations
|
||||
|
||||
**CORS:** If you’re installing directly on a server you will need to configure the environment variables to the application container:
|
||||
```
|
||||
CONSOLE_HOST=http://{yourhost}:4200
|
||||
```
|
||||
If you have additional applications or frontends you can use the environment variable `FRONTEND_HOSTS` to add a comma delimited list of additioal frontend hosts.
|
||||
|
||||
**Application Key** If you get an issue about a missing application key just run:
|
||||
```bash
|
||||
docker compose exec application bash -c "php artisan key:generate --show"
|
||||
```
|
||||
Next copy this value to the `APP_KEY` environment variable in the application container and restart.
|
||||
|
||||
**Routing:** Fleetbase ships with a default OSRM server hosted by `[router.project-osrm.org](https://router.project-osrm.org)` but you’re able to use your own or any other OSRM compatible server. You can modify this in the `console/environments` directory by modifying the .env file of the environment you’re deploying and setting the `OSRM_HOST` to the OSRM server for Fleetbase to use.
|
||||
|
||||
**Services:** There are a few environment variables which need to be set for Fleetbase to function with full features. If you're deploying with docker then it's easiest to just create a `docker-compose.override.yml` and supply the environment variables in this file.
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
application:
|
||||
environment:
|
||||
CONSOLE_HOST: http://localhost:4200
|
||||
MAIL_MAILER: (ses, smtp, mailgun, postmark, sendgrid)
|
||||
OSRM_HOST: https://router.project-osrm.org
|
||||
IPINFO_API_KEY:
|
||||
GOOGLE_MAPS_API_KEY:
|
||||
GOOGLE_MAPS_LOCALE: us
|
||||
TWILIO_SID:
|
||||
TWILIO_TOKEN:
|
||||
TWILIO_FROM:
|
||||
|
||||
socket:
|
||||
environment:
|
||||
# IMPORTANT: Configure WebSocket origins for security
|
||||
# Development (localhost only - include WebSocket protocols):
|
||||
SOCKETCLUSTER_OPTIONS: '{"origins":"http://localhost:*,https://localhost:*,ws://localhost:*,wss://localhost:*"}'
|
||||
# Production (replace with your actual domain):
|
||||
# SOCKETCLUSTER_OPTIONS: '{"origins":"https://yourdomain.com:*,wss://yourdomain.com:*"}'
|
||||
```
|
||||
|
||||
**WebSocket Security:** The `SOCKETCLUSTER_OPTIONS` environment variable controls which domains can connect to the WebSocket server. Always restrict origins to your specific domains in production to prevent security vulnerabilities.
|
||||
|
||||
You can learn more about full installation, and configuration in the [official documentation](https://docs.fleetbase.io/getting-started/install).
|
||||
|
||||
## 🚀 Deploy on AWS in One Click
|
||||
|
||||
Deploy your complete Fleetbase logistics platform on AWS with enterprise-grade security, scalability, and reliability. No DevOps expertise required!
|
||||
|
||||
[](https://console.fleetbase.io/aws-marketplace)
|
||||
|
||||
### ✨ What You Get
|
||||
|
||||
- **Complete AWS Infrastructure**: ECS Fargate, RDS MySQL, ElastiCache Redis, S3, CloudFront, and more
|
||||
- **25-Minute Setup**: From zero to production-ready logistics platform
|
||||
- **Enterprise Security**: VPC isolation, encrypted storage, secrets management
|
||||
- **Auto-Scaling**: Handle traffic spikes with ECS Fargate auto-scaling
|
||||
- **High Availability**: Multi-AZ deployment with 99.9% uptime SLA
|
||||
- **Cost Optimized**: Pay-as-you-use with optimized resource allocation
|
||||
|
||||
### 🏗️ Infrastructure Included
|
||||
|
||||
Your AWS deployment includes a complete, production-ready infrastructure stack:
|
||||
|
||||
- **Compute**: ECS Fargate cluster with auto-scaling services
|
||||
- **Database**: RDS MySQL 8.0 with automated backups and Multi-AZ support
|
||||
- **Cache**: ElastiCache Redis for high-performance caching
|
||||
- **Storage**: S3 object storage with CloudFront CDN for global distribution
|
||||
- **Networking**: VPC with private subnets, NAT gateways, and security groups
|
||||
- **Load Balancing**: Application Load Balancer with SSL certificates
|
||||
- **Monitoring**: CloudWatch logs, container insights, and health monitoring
|
||||
- **Messaging**: SQS message queues for background job processing
|
||||
|
||||
[**🚀 Deploy Now**](https://console.fleetbase.io/aws-marketplace) | [**📖 Learn More**](https://docs.fleetbase.io/category/deploying/aws)
|
||||
|
||||
# 🧩 Extensions
|
||||
|
||||
Extensions are modular components that enhance the functionality of your Fleetbase instance. They allow you to add new features, customize existing behavior, or integrate with external systems.
|
||||
|
||||
You can find extensions available from the official [Fleetbase Console](https://console.fleetbase.io), here you will also be able get your registry token to install extensions to a self-hosted Fleetbase instance.
|
||||
|
||||
Additionally you're able to develop and publish your own extensions as well which you can read more about developing extensions via the [extension building guide](https://docs.fleetbase.io/developers/building-an-extension).
|
||||
|
||||
## ⌨️ Fleetbase CLI
|
||||
|
||||
The Fleetbase CLI is a powerful tool designed to simplify the management of extensions for your Fleetbase instance. With the CLI, you can effortlessly handle authentication, install and uninstall extensions, and scaffold new extensions if you are developing your own.
|
||||
|
||||
Get started with the CLI with npm:
|
||||
|
||||
```bash
|
||||
npm i -g @fleetbase/cli
|
||||
```
|
||||
|
||||
Once installed, you can access a variety of commands to manage your Fleetbase extensions.
|
||||
|
||||
# 📱 Apps
|
||||
|
||||
Fleetbase offers a few open sourced apps which are built on Fleetbase which can be cloned and customized. Every app is built so that the Fleetbase instance can be switched out whether on-premise install or cloud hosted.
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/fleetbase/storefront-app">Storefront App</a>: Fleetbase based ecommerce/on-demand app for launching your very own shop or marketplace to Apple or Android playstore.</li>
|
||||
<li><a href="https://github.com/fleetbase/navigator-app">Navigator App</a>: Fleetbase based driver app which can be used for drivers to manage and update order, additionally provides real time driver location which can be viewed in the Fleetbase Console.</li>
|
||||
</ul>
|
||||
|
||||
## 🛣️ Roadmap
|
||||
1. **Inventory and Warehouse Management** ~ Pallet will be Fleetbase’s first official extension for WMS & Inventory.
|
||||
2. **Accounting and Invoicing** ~ Ledger will be Fleetbase’s first official extension accounting and invoicing.
|
||||
3. **AI** ~ AI Agent intrgation for system and workflows.
|
||||
4. **Dynamic Rules System** ~ Trigger events, tasks jobs from a rule builder on resources.
|
||||
|
||||
## 🪲 Bugs and 💡 Feature Requests
|
||||
|
||||
Have a bug or a feature request? Please check the <a href="https://github.com/fleetbase/fleetbase/issues">issue tracker</a> and search for existing and closed issues. If your problem or idea is not addressed yet, please <a href="https://github.com/fleetbase/fleetbase/issues/new">open a new issue</a>.
|
||||
|
||||
## 👨💻 Contributing
|
||||
|
||||
Please read through our <a href="https://github.com/fleetbase/fleetbase/blob/main/CONTRIBUTING.md">contributing guidelines</a>. Included are directions for opening issues, coding standards, and notes on development.
|
||||
|
||||
## 👥 Community
|
||||
|
||||
Get updates on Fleetbase's development and chat with the project maintainers and community members by joining our <a href="https://discord.gg/V7RVWRQ2Wm">Discord</a>.
|
||||
|
||||
<ul>
|
||||
<li>Follow <a href="https://x.com/fleetbase_io">@fleetbase_io on X</a>.</li>
|
||||
<li>Read and subscribe to <a href="https://www.fleetbase.io/blog-2">The Official Fleetbase Blog</a>.</li>
|
||||
<li>Ask and explore <a href="https://github.com/orgs/fleetbase/discussions">our GitHub Discussions</a>.</li>
|
||||
</ul>
|
||||
<p dir="auto">See the <a href="https://github.com/fleetbase/fleetbase/releases">Releases</a> section of our GitHub project for changelogs for each release version of Fleetbase.</p>
|
||||
<p>Release announcement posts on <a href="https://www.fleetbase.io/blog-2" rel="nofollow">the official Fleetbase blog</a> contain summaries of the most noteworthy changes made in each release.</p>
|
||||
|
||||
## Creators
|
||||
|
||||
<p dir="auto"><strong>Ronald A. Richardson</strong>- Co-founder & CTO</p>
|
||||
<img src="https://user-images.githubusercontent.com/58805033/230263021-212f2553-1269-473d-be94-313cb3eecfa5.png" alt="Ron Image" width="75" height="75" style="max-width: 100%;">
|
||||
<p><a href="https://github.com/orgs/fleetbase/people/roncodes">Github</a> | <a href="https://www.linkedin.com/in/ronald-a-richardson/">LinkedIn</a></p>
|
||||
|
||||
<p dir="auto"><strong>Shiv Thakker</strong> - Co-founder & CEO</p>
|
||||
<img src="https://user-images.githubusercontent.com/58805033/230262598-1ce6d0cc-fb65-41f9-8384-5cf5cbf369c7.png" alt="Shiv Image" width="75" height="75" style="max-width: 100%;">
|
||||
<p><a href="https://github.com/orgs/fleetbase/people/shivthakker">Github</a> | <a href="https://www.linkedin.com/in/shivthakker/">LinkedIn</a></p>
|
||||
|
||||
|
||||
# License & Copyright
|
||||
|
||||
Fleetbase is made available under the terms of the <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">GNU Affero General Public License 3.0 (AGPL 3.0)</a>. For other licenses <a href="mailto:hello@fleetbase.io" target="_blank">contact us</a>.
|
||||
|
||||
<div id="hero">
|
||||
<p align="center" dir="auto">
|
||||
<a href="https://fleetbase.io" rel="nofollow">
|
||||
<img src="https://user-images.githubusercontent.com/58805033/191936702-fed04b0f-7966-4041-96d0-95e27bf98248.png" alt="Fleetbase logo" width="500" height="120" style="max-width: 100%;">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center" dir="auto">
|
||||
<a href="https://github.com/fleetbase/fleetbase/blob/main/LICENSE"><img src="https://img.shields.io/github/license/fleetbase/fleetbase" alt="License"></a>
|
||||
<a href="https://github.com/fleetbase/fleetbase/releases"><img src="https://img.shields.io/github/v/release/fleetbase/fleetbase" alt="Latest Release"></a>
|
||||
<a href="https://github.com/fleetbase/fleetbase/stargazers"><img src="https://img.shields.io/github/stars/fleetbase/fleetbase?style=social" alt="GitHub Stars"></a>
|
||||
<a href="https://discord.gg/V7RVWRQ2Wm"><img src="https://img.shields.io/discord/699834923032248430?logo=discord&label=Discord" alt="Discord"></a>
|
||||
<a href="https://github.com/fleetbase/fleetbase/issues"><img src="https://img.shields.io/github/issues/fleetbase/fleetbase" alt="GitHub Issues"></a>
|
||||
</p>
|
||||
<p align="center" dir="auto">
|
||||
Modular logistics and supply chain operating system
|
||||
<br>
|
||||
<a href="https://docs.fleetbase.io/" rel="nofollow" target="_fleetbase_docs">Documentation</a>
|
||||
·
|
||||
<a href="https://console.fleetbase.io" rel="nofollow" target="_fleetbase_console">Cloud Version</a>
|
||||
·
|
||||
<a href="https://tally.so/r/3NBpAW" rel="nofollow">Book a Demo</a>
|
||||
·
|
||||
<a href="https://discord.gg/V7RVWRQ2Wm" target="discord" rel="nofollow">Discord</a>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
## What is Fleetbase?
|
||||
|
||||
Fleetbase is a modular logistics and supply chain operating system designed to streamline management, planning, optimization, and operational control across various sectors of the supply chain industry.
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/fleetbase_overview.png" alt="Fleetbase Console" width="1200" style="max-width: 100%;" />
|
||||
</p>
|
||||
|
||||
## 🎯 Who Is Fleetbase For?
|
||||
|
||||
Fleetbase is designed for organizations that need powerful logistics and supply chain management:
|
||||
|
||||
- **E-commerce & Retail** - Manage deliveries, track orders, and optimize last-mile logistics
|
||||
- **Food & Beverage** - Coordinate restaurant deliveries, manage drivers, and track real-time orders
|
||||
- **Courier Services** - Dispatch drivers, optimize routes, and provide customer tracking
|
||||
- **Field Services** - Schedule technicians, manage service areas, and track job completion
|
||||
- **Enterprise Logistics** - Build custom supply chain solutions with full API access
|
||||
- **Developers** - Extend and customize with a modular architecture and comprehensive API
|
||||
|
||||
## Visual Feature Showcase
|
||||
|
||||
| Feature | Screenshot | Description |
|
||||
|---------|------------|-------------|
|
||||
| **Order Board** | <img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-board-kanban.png" alt="Fleetbase Order Board" width="600" /> | Visualize and manage your orders with a dynamic Kanban board. |
|
||||
| **Workflow Builder** | <img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-workflow-config.png" alt="Fleetbase Order Workflow Configuration" width="600" /> | Create custom order flows and automation with the intuitive workflow builder. |
|
||||
| **Order Tracking** | <img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/order-map-view.png" alt="Fleetbase Order Map View" width="600" /> | Track individual orders in real-time on an interactive map. |
|
||||
| **Live Fleet Map** | <img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/live-map-tracking.png" alt="Fleetbase Live Map Tracking" width="600" /> | Get a complete overview of your fleet and active orders on a live map. |
|
||||
| **Service Zones** | <img src="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/fleet-map-zones.png" alt="Fleetbase Fleet Map with Zones" width="600" /> | Define and manage service areas and zones for your fleet. |
|
||||
|
||||
**Quickstart**
|
||||
|
||||
```bash
|
||||
git clone git@github.com:fleetbase/fleetbase.git
|
||||
cd fleetbase && ./scripts/docker-install.sh
|
||||
```
|
||||
|
||||
## 📖 Table of contents
|
||||
|
||||
- [Features](#-features)
|
||||
- [Install](#-install)
|
||||
- [Extensions](#-extensions)
|
||||
- [Apps](#-apps)
|
||||
- [Roadmap](#-roadmap)
|
||||
- [Deployment Options](#-deployment-options)
|
||||
- [Bugs and Feature Requests](#-bugs-and--feature-requests)
|
||||
- [Documentation](#-documentation)
|
||||
- [Contributing](#-contributing)
|
||||
- [Community](#-community)
|
||||
- [Creators](#creators)
|
||||
- [License & Copyright](#license--copyright)
|
||||
|
||||
## 📦 Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| 🔌 **Extensible** | Build installable extensions and additional functionality directly into the OS via modular architecture. |
|
||||
| 👨💻 **Developer Friendly** | RESTful API, socket, and webhooks to seamlessly integrate with external systems or develop custom applications. |
|
||||
| 📱 **Native Apps** | Collection of open-source and native apps designed for operations and customer facing. |
|
||||
| 🤝 **Collaboration** | Dedicated chat and comments system for collaboration across your organization. |
|
||||
| 🔒 **Security** | Secure data encryption, adherence to industry-standard security practices, and a comprehensive dynamic Identity and Access Management (IAM) system. |
|
||||
| 📡 **Telematics** | Integrate and connect to hardware devices and sensors to provide more feedback and visibility into operations. |
|
||||
| 🌐 **Internationalized** | Translate into multiple languages to accommodate diverse user bases and global operations. |
|
||||
| ⚙️ **Framework** | PHP core built around logistics and supply chain abstractions to streamline extension development. |
|
||||
| 🔄 **Dynamic** | Configurable rules, flows and logic to enable automation and customization. |
|
||||
| 🎨 **UI/UX** | Clean, responsive user-friendly interface for efficient management and operations from desktop or mobile. |
|
||||
| 📊 **Dashboards** | Create custom dashboards and widgets to get full visibility into operations. |
|
||||
| 📈 **Scalability** | Uninterrupted growth with scalable infrastructure and design, capable of handling increasing data volume and user demand as your business expands. |
|
||||
| 🔄 **Continuous Improvements** | Commitment to continuous improvement, providing regular updates that seamlessly introduce optimizations, new features, and overall enhancements to the OS. |
|
||||
| 🌍 **Open Source** | Deploy it either on-premise or in the cloud according to your organization's needs and preferences. |
|
||||
|
||||
## 💾 Install
|
||||
Getting up and running with Fleetbase via Docker is the quickest and most straightforward way. If you'd like to use Fleetbase without docker read the [full install guide in the Fleetbase documentation](https://docs.fleetbase.io/getting-started/install).
|
||||
|
||||
Make sure you have both the latest versions of docker and docker-compose installed on your system.
|
||||
|
||||
```bash
|
||||
git clone git@github.com:fleetbase/fleetbase.git
|
||||
cd fleetbase && ./scripts/docker-install.sh
|
||||
```
|
||||
|
||||
### Accessing Fleetbase
|
||||
Once successfully installed and running you can then access the Fleetbase console on port 4200 and the API will be accessible from port 8000.
|
||||
|
||||
Fleetbase Console: http://localhost:4200
|
||||
Fleetbase API: http://localhost:8000
|
||||
|
||||
### Additional Configurations
|
||||
|
||||
**CORS:** If you're installing directly on a server you will need to configure the environment variables to the application container:
|
||||
```
|
||||
CONSOLE_HOST=http://{yourhost}:4200
|
||||
```
|
||||
If you have additional applications or frontends you can use the environment variable `FRONTEND_HOSTS` to add a comma delimited list of additional frontend hosts.
|
||||
|
||||
**Application Key** If you get an issue about a missing application key just run:
|
||||
```bash
|
||||
docker compose exec application bash -c "php artisan key:generate --show"
|
||||
```
|
||||
Next copy this value to the `APP_KEY` environment variable in the application container and restart.
|
||||
|
||||
**Routing:** Fleetbase ships with a default OSRM server hosted by [router.project-osrm.org](https://router.project-osrm.org) but you're able to use your own or any other OSRM compatible server. You can modify this in the `console/environments` directory by modifying the .env file of the environment you're deploying and setting the `OSRM_HOST` to the OSRM server for Fleetbase to use.
|
||||
|
||||
**Services:** There are a few environment variables which need to be set for Fleetbase to function with full features. If you're deploying with docker then it's easiest to just create a `docker-compose.override.yml` and supply the environment variables in this file.
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
application:
|
||||
environment:
|
||||
CONSOLE_HOST: http://localhost:4200
|
||||
MAIL_MAILER: (ses, smtp, mailgun, postmark, sendgrid)
|
||||
OSRM_HOST: https://router.project-osrm.org
|
||||
IPINFO_API_KEY:
|
||||
GOOGLE_MAPS_API_KEY:
|
||||
GOOGLE_MAPS_LOCALE: us
|
||||
TWILIO_SID:
|
||||
TWILIO_TOKEN:
|
||||
TWILIO_FROM:
|
||||
|
||||
socket:
|
||||
environment:
|
||||
# IMPORTANT: Configure WebSocket origins for security
|
||||
# Development (localhost only - include WebSocket protocols):
|
||||
SOCKETCLUSTER_OPTIONS: '{"origins":"http://localhost:*,https://localhost:*,ws://localhost:*,wss://localhost:*"}'
|
||||
# Production (replace with your actual domain):
|
||||
# SOCKETCLUSTER_OPTIONS: '{"origins":"https://yourdomain.com:*,wss://yourdomain.com:*"}'
|
||||
```
|
||||
|
||||
**WebSocket Security:** The `SOCKETCLUSTER_OPTIONS` environment variable controls which domains can connect to the WebSocket server. Always restrict origins to your specific domains in production to prevent security vulnerabilities.
|
||||
|
||||
You can learn more about full installation, and configuration in the [official documentation](https://docs.fleetbase.io/getting-started/install).
|
||||
|
||||
# 🧩 Extensions
|
||||
|
||||
Extensions are modular components that enhance the functionality of your Fleetbase instance. They allow you to add new features, customize existing behavior, or integrate with external systems.
|
||||
|
||||
You can find extensions available from the official [Fleetbase Console](https://console.fleetbase.io), here you will also be able get your registry token to install extensions to a self-hosted Fleetbase instance.
|
||||
|
||||
Additionally you're able to develop and publish your own extensions as well which you can read more about developing extensions via the [extension building guide](https://docs.fleetbase.io/developers/building-an-extension).
|
||||
|
||||
## ⌨️ Fleetbase CLI
|
||||
|
||||
The Fleetbase CLI is a powerful tool designed to simplify the management of extensions for your Fleetbase instance. With the CLI, you can effortlessly handle authentication, install and uninstall extensions, and scaffold new extensions if you are developing your own.
|
||||
|
||||
Get started with the CLI with npm:
|
||||
|
||||
```bash
|
||||
npm i -g @fleetbase/cli
|
||||
```
|
||||
|
||||
Once installed, you can access a variety of commands to manage your Fleetbase extensions.
|
||||
|
||||
# 📱 Apps
|
||||
|
||||
Fleetbase offers open-source mobile apps that can be customized and deployed:
|
||||
|
||||
| App | Description | Platform | Repository |
|
||||
|-----|-------------|----------|------------|
|
||||
| **Storefront App** | E-commerce/on-demand app for launching your own shop or marketplace | iOS & Android | [GitHub](https://github.com/fleetbase/storefront-app) |
|
||||
| **Navigator App** | Driver app for managing orders with real-time location tracking | iOS & Android | [GitHub](https://github.com/fleetbase/navigator-app) |
|
||||
|
||||
## 🛣️ Roadmap
|
||||
|
||||
| Feature | Status | Expected Release | Description |
|
||||
|---------|--------|------------------|-------------|
|
||||
| **Pallet (WMS)** | 🚧 In Development | Late Q1 / Early Q2 2026 | Inventory and Warehouse Management extension |
|
||||
| **Ledger** | 🚧 In Development | Late Q1 / Early Q2 2026 | Accounting and Invoicing extension |
|
||||
| **AI Agent** | 🔬 Research | Q4 2026 | AI integration for system and workflow automation |
|
||||
| **Dynamic Rules** | 📋 Planned | 2027 | Rule builder to trigger events, tasks, and jobs |
|
||||
|
||||
Want to influence our roadmap? [Join the discussion](https://github.com/orgs/fleetbase/discussions)
|
||||
|
||||
## 🚀 Deployment Options
|
||||
|
||||
| Option | Best For | Setup Time | Maintenance |
|
||||
|--------|----------|------------|-------------|
|
||||
| **Docker (Local)** | Development & Testing | 5 minutes | Self-managed |
|
||||
| **On-Premise** | Production on your own infrastructure | 30-60 minutes | Self-managed |
|
||||
| **Cloud Self-Hosted** | Production (AWS, GCP, Azure) | 30-60 minutes | Self-managed |
|
||||
| **Fleetbase Cloud** | Quick start, no DevOps | Instant | Fully managed |
|
||||
|
||||
[View detailed deployment guides →](https://docs.fleetbase.io/category/deploying)
|
||||
|
||||
## 🐛 Bugs and 💡 Feature Requests
|
||||
|
||||
Have a bug or a feature request? Please check the <a href="https://github.com/fleetbase/fleetbase/issues">issue tracker</a> and search for existing and closed issues. If your problem or idea is not addressed yet, please <a href="https://github.com/fleetbase/fleetbase/issues/new">open a new issue</a>.
|
||||
|
||||
## 📄 Documentation
|
||||
|
||||
Fleetbase has comprehensive documentation to help you get started and make the most of the platform:
|
||||
|
||||
- **Getting Started**: [Installation Guide](https://docs.fleetbase.io/getting-started/install)
|
||||
- **API Reference**: [API Documentation](https://docs.fleetbase.io/api-reference)
|
||||
- **Extension Development**: [Building Extensions](https://docs.fleetbase.io/developers/building-an-extension)
|
||||
- **Deployment**: [Deployment Guides](https://docs.fleetbase.io/deployment)
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions from the community! Here's how you can help:
|
||||
|
||||
- **Report Bugs**: [Open an issue](https://github.com/fleetbase/fleetbase/issues/new)
|
||||
- **Suggest Features**: [Start a discussion](https://github.com/orgs/fleetbase/discussions)
|
||||
- **Submit PRs**: Read our [Contributing Guide](https://github.com/fleetbase/fleetbase/blob/main/CONTRIBUTING.md)
|
||||
- **Write Documentation**: Help improve our [docs](https://docs.fleetbase.io)
|
||||
- **Build Extensions**: Create and share [extensions](https://docs.fleetbase.io/developers/building-an-extension)
|
||||
|
||||
**Development Setup**: See our [Development Installation Guide](https://docs.fleetbase.io/getting-started/install/for-development) for detailed instructions on setting up your local development environment.
|
||||
|
||||
## 👥 Community
|
||||
|
||||
Get updates on Fleetbase's development and chat with the project maintainers and community members by joining our <a href="https://discord.gg/V7RVWRQ2Wm">Discord</a>.
|
||||
|
||||
<ul>
|
||||
<li>Follow <a href="https://x.com/fleetbase_io">@fleetbase_io on X</a>.</li>
|
||||
<li>Read and subscribe to <a href="https://www.fleetbase.io/blog-2">The Official Fleetbase Blog</a>.</li>
|
||||
<li>Ask and explore <a href="https://github.com/orgs/fleetbase/discussions">our GitHub Discussions</a>.</li>
|
||||
</ul>
|
||||
<p dir="auto">See the <a href="https://github.com/fleetbase/fleetbase/releases">Releases</a> section of our GitHub project for changelogs for each release version of Fleetbase.</p>
|
||||
<p>Release announcement posts on <a href="https://www.fleetbase.io/blog-2" rel="nofollow">the official Fleetbase blog</a> contain summaries of the most noteworthy changes made in each release.</p>
|
||||
|
||||
## Creators
|
||||
|
||||
<table style="border: none;">
|
||||
<tr>
|
||||
<td align="center" style="border: none;">
|
||||
<img src="https://user-images.githubusercontent.com/58805033/230263021-212f2553-1269-473d-be94-313cb3eecfa5.png" alt="Ronald A. Richardson" width="120" height="120" style="border-radius: 50%;">
|
||||
<br>
|
||||
<strong>Ronald A. Richardson</strong>
|
||||
<br>
|
||||
Co-founder & CTO
|
||||
<br>
|
||||
<a href="https://github.com/orgs/fleetbase/people/roncodes">GitHub</a> | <a href="https://www.linkedin.com/in/ronald-a-richardson/">LinkedIn</a>
|
||||
</td>
|
||||
<td align="center" style="border: none;">
|
||||
<img src="https://user-images.githubusercontent.com/58805033/230262598-1ce6d0cc-fb65-41f9-8384-5cf5cbf369c7.png" alt="Shiv Thakker" width="120" height="120" style="border-radius: 50%;">
|
||||
<br>
|
||||
<strong>Shiv Thakker</strong>
|
||||
<br>
|
||||
Co-founder & CEO
|
||||
<br>
|
||||
<a href="https://github.com/orgs/fleetbase/people/shivthakker">GitHub</a> | <a href="https://www.linkedin.com/in/shivthakker/">LinkedIn</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
# License & Copyright
|
||||
|
||||
Fleetbase is available under a **dual-licensing model** to accommodate both open-source community users and commercial enterprises:
|
||||
|
||||
## Open Source License (AGPL-3.0)
|
||||
|
||||
By default, Fleetbase is licensed under the [GNU Affero General Public License v3.0 (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html). This license allows you to use, modify, and distribute Fleetbase freely, provided that:
|
||||
|
||||
- Any modifications or derivative works are also made available under AGPL-3.0
|
||||
- If you run a modified version as a network service, you must make the source code available to users
|
||||
|
||||
The AGPL-3.0 is ideal for open-source projects, academic research, and organizations committed to sharing their improvements with the community.
|
||||
|
||||
## Commercial License (FCL)
|
||||
|
||||
For organizations that require more flexibility, Fleetbase offers a **Fleetbase Commercial License (FCL)** that provides:
|
||||
|
||||
- **Freedom from AGPL obligations** – Deploy and modify Fleetbase without source code disclosure requirements
|
||||
- **Proprietary integrations** – Build closed-source extensions and integrations
|
||||
- **Commercial protections** – Warranties, indemnities, and legal assurances not provided under AGPL
|
||||
- **Derivative work ownership** – Retain full ownership of your modifications and customizations
|
||||
- **Flexible licensing options** – Choose from annual, monthly, or perpetual license models
|
||||
|
||||
### Commercial License Options
|
||||
|
||||
| License Type | Price | Support & Updates | Best For |
|
||||
|--------------|-------|-------------------|----------|
|
||||
| **Annual License** | $25,000/year | ✅ All upgrades & Business Support included | Organizations requiring continuous updates and support |
|
||||
| **Monthly License** | $2,500/month | ✅ All upgrades & Business Support included | Pilot projects and short-term deployments |
|
||||
| **Major Version License** | $25,000 (one-time) | ❌ No ongoing support | Stable deployments on a single major version |
|
||||
| **Minor Version License** | $15,000 (one-time) | ❌ No ongoing support | Locked version deployments |
|
||||
|
||||
### When You Need a Commercial License
|
||||
|
||||
You should consider a commercial license if you:
|
||||
|
||||
- Want to build proprietary extensions or integrations without open-sourcing them
|
||||
- Need to embed Fleetbase in a commercial product without AGPL obligations
|
||||
- Require enterprise-grade support, SLAs, and legal protections
|
||||
- Plan to modify Fleetbase without sharing your changes publicly
|
||||
|
||||
### Get a Commercial License
|
||||
|
||||
For more information about commercial licensing options, please contact us:
|
||||
|
||||
- **Email:** [hello@fleetbase.io](mailto:hello@fleetbase.io)
|
||||
- **Website:** [fleetbase.io](https://fleetbase.io)
|
||||
|
||||
---
|
||||
|
||||
**Copyright © 2026 Fleetbase Pte. Ltd.** All rights reserved.
|
||||
|
||||
|
||||
281
RELEASE.md
281
RELEASE.md
@@ -1,281 +1,21 @@
|
||||
# 🚀 Fleetbase v0.7.20 — 2025-12-05
|
||||
# 🚀 Fleetbase v0.7.28 — 2026-02-05
|
||||
|
||||
> "Major architectural & universe framework refactor for improved build, loading and initialization of extensions and system."
|
||||
> "Critical patch: eager load driver and vehicle in consumable orders query API"
|
||||
|
||||
---
|
||||
|
||||
## @fleetbase/ember-core v0.3.7
|
||||
|
||||
### 🎯 Major Features
|
||||
|
||||
#### Universe Service Refactor
|
||||
Complete architectural overhaul of the Universe service with separation of concerns into specialized sub-services:
|
||||
|
||||
- **ExtensionManager**: Manages extension loading, engine instances, and lifecycle hooks
|
||||
- **RegistryService**: Fully dynamic, Map-based registry system for cross-engine resource sharing
|
||||
- **MenuService**: Dedicated menu item and panel management with event support
|
||||
- **WidgetService**: Dashboard widget registration and retrieval system
|
||||
- **HookService**: Application lifecycle hooks and custom event hooks
|
||||
|
||||
**Key Benefits:**
|
||||
- Improved performance with lazy loading and proper engine lifecycle management
|
||||
- Better separation of concerns and maintainability
|
||||
- Enhanced cross-engine resource sharing via Ember's application container
|
||||
- Backward compatibility maintained through facade pattern
|
||||
|
||||
#### Enhanced Extension System
|
||||
|
||||
**New Extension Patterns:**
|
||||
- `extension.js` hook patterns with `setupExtension()` and `onEngineLoaded()` support
|
||||
- `whenEngineLoaded()` utility method that handles both loaded and not-yet-loaded engines
|
||||
- Comprehensive extension checking methods: `isInstalled()`, `isEngineLoaded()`, `isEngineLoading()`
|
||||
- Automatic hook execution for router-loaded engines
|
||||
|
||||
**Engine Loading Improvements:**
|
||||
- Fixed race conditions between engine loading and hook registration
|
||||
- Proper tracking of router-loaded engines in `loadedEngines` Map
|
||||
- Patched `buildChildEngineInstance` to run hooks for router-loaded engines
|
||||
- Two-phase extension setup: registration first, then hook execution
|
||||
|
||||
**Performance Optimizations:**
|
||||
- `extensions.json` loading with localStorage caching
|
||||
- Reduced console noise by removing excessive debug logs
|
||||
- Performance monitoring for extension load times
|
||||
|
||||
#### Helper Registration System
|
||||
- Cross-engine helper sharing via application container
|
||||
- `registerHelper()` method for template helpers
|
||||
- Support for both class-based and function-based helpers
|
||||
- Automatic registration to application container for global access
|
||||
|
||||
#### Component Registration Enhancements
|
||||
- `registerRenderableComponent()` for cross-engine component sharing
|
||||
- Support for component classes via `ExtensionComponent`
|
||||
- Automatic component registration to engine containers
|
||||
- `getRenderableComponents()` for retrieving registered components
|
||||
|
||||
### 🔧 API Improvements
|
||||
|
||||
#### UniverseService
|
||||
- `getService()` method with flexible naming pattern resolution:
|
||||
- Short names: `"menu"` → `universe/menu-service`
|
||||
- Plural variations: `"hooks"` or `"hook"` → `universe/hook-service`
|
||||
- CamelCase: `"menuService"` → `universe/menu-service`
|
||||
- Full names: `"menu-service"` → `universe/menu-service`
|
||||
- With namespace: `"universe/menu-service"` → `universe/menu-service`
|
||||
- `whenEngineLoaded()` for simplified engine-dependent setup
|
||||
- `getServiceFromEngine()` for accessing engine-specific services
|
||||
- `getApplicationInstance()` with fallback to `window.Fleetbase`
|
||||
|
||||
#### MenuService
|
||||
- Computed getters for template access: `headerMenuItems`, `adminMenuItems`, `adminMenuPanels`
|
||||
- Event triggers for backward compatibility
|
||||
- Proper `finalView` normalization for all menu items
|
||||
- `onClick` handlers wrapped with `menuItem` and `universe` parameters
|
||||
- Fixed panel item slug/view structure to match original behavior
|
||||
|
||||
#### WidgetService
|
||||
- Per-dashboard widget registration
|
||||
- `registerWidgets()` for making widgets available on dashboards
|
||||
- `registerDefaultWidgets()` for auto-loading specific widgets
|
||||
- Proper widget filtering by dashboard ID
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
#### Extension Loading
|
||||
- Resolved timeout errors during engine boot
|
||||
- Fixed duplicate dashboard ID errors
|
||||
- Corrected engine instance tracking and retrieval
|
||||
- Fixed race conditions in extension setup
|
||||
|
||||
#### Menu System
|
||||
- Fixed registry name mismatch for header menu items
|
||||
- Corrected panel item structure for proper routing
|
||||
- Added null checks before `dasherize` calls
|
||||
- Fixed `onClick` handler parameter passing
|
||||
|
||||
#### Registry System
|
||||
- Fixed widget registry lookup to work with array storage
|
||||
- Proper filtering by registration key
|
||||
- Shared registries Map across all engines via application container
|
||||
- Triggered reactivity when creating new registries
|
||||
|
||||
#### Service Resolution
|
||||
- Fixed service injection on engine boot
|
||||
- Corrected service map injection
|
||||
- Added missing `getServiceFromEngine` method
|
||||
- Fixed `virtualRouteRedirect` and `getViewFromTransition` methods
|
||||
|
||||
### 📦 Exports & Structure
|
||||
- Named exports on "exports" namespace
|
||||
- Separated default and named exports in app/exports re-exports
|
||||
|
||||
### 🔄 ResourceActionService
|
||||
- Allow properties to be set from initialization options
|
||||
- Improved flexibility for service configuration
|
||||
|
||||
## @fleetbase/ember-ui v0.3.12
|
||||
|
||||
### 🎯 Major Features
|
||||
|
||||
#### Dashboard Service Refactor
|
||||
Complete rewrite to support the new Universe service architecture:
|
||||
|
||||
- Direct injection of `widgetService` instead of going through universe
|
||||
- Updated to use new widget service API
|
||||
- Proper dashboard cleanup and recreation on route revisit
|
||||
- Race condition fixes with `drop` task modifier
|
||||
|
||||
#### Widget Panel Enhancements
|
||||
|
||||
**Search & Filter:**
|
||||
- Real-time keyword search for widgets by name and description
|
||||
- `@tracked searchQuery` with `updateSearchQuery` action
|
||||
- Improved widget discoverability
|
||||
|
||||
**Floating Pagination:**
|
||||
- New floating pagination style (bottom-right, rounded, shadowed)
|
||||
- Reveals horizontal scrollbar for better table navigation
|
||||
- `@useTfootPagination` arg for backward compatibility
|
||||
- Default to floating, opt-in to table footer pagination
|
||||
|
||||
**Thin Scrollbars:**
|
||||
- Consistent 8px scrollbar height for both axes
|
||||
- Webkit scrollbar styles for modern browsers
|
||||
- Firefox `scrollbar-width: thin` support
|
||||
- Dark mode scrollbar colors
|
||||
|
||||
### 🔧 Component Improvements
|
||||
|
||||
#### LazyEngineComponent
|
||||
- Support for both path-based (lazy) and class-based (immediate) components
|
||||
- Automatic component registration to engine containers
|
||||
- Proper engine component lookup
|
||||
- Helper for use with `{{component}}` syntax
|
||||
|
||||
#### RegistryYield
|
||||
- Converted `yieldables` to computed getter for automatic reactivity
|
||||
- Automatic wrapping of components with `LazyEngineComponent`
|
||||
- `isComponent` getter to detect component types vs menu items
|
||||
- Updated to use `registryService.getRenderableComponents()`
|
||||
- Removed event listener approach (now uses reactive getters)
|
||||
|
||||
#### LoadEngine Component
|
||||
- New component for explicit engine loading
|
||||
- Complements `LazyEngineComponent` for different use cases
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
#### Dashboard Service
|
||||
- Fixed duplicate dashboard ID errors with `drop` task modifier
|
||||
- Added try-catch in `_createDefaultDashboard` for race conditions
|
||||
- Proper state checks (`isDeleted`, `isDestroying`, `isDestroyed`)
|
||||
- Fixed reset() to unload dashboard-widgets before dashboards
|
||||
- Prevents identity map conflicts when recreating dashboards
|
||||
|
||||
#### Widget Panel
|
||||
- Fixed `availableWidgets` reactivity by converting to getter
|
||||
- Use `args.defaultDashboardId` first, then fallback to `this.defaultDashboardId`
|
||||
- Ensures widgets registered after component creation are visible
|
||||
- Handles string-represented widgets and components
|
||||
|
||||
#### RegistryYield
|
||||
- Fixed TypeError by removing `registryService.on` event listener
|
||||
- RegistryService doesn't have Evented mixin, now uses reactive getters
|
||||
- Components registered after construction now properly yielded
|
||||
|
||||
#### Table Styling
|
||||
- Fixed floating pagination spacing (reduced padding, adjusted margins)
|
||||
- Applied `overflow-x` to `.next-table-wrapper` instead of table
|
||||
- Horizontal scrollbar now visible and not hidden by pagination
|
||||
- Added `overflow-y: visible` to prevent vertical scroll issues
|
||||
|
||||
### 🎨 UX Improvements
|
||||
- Minor UX tweaks to widget panel component
|
||||
- Improved widget panel layout and spacing
|
||||
- Better visual hierarchy for widget selection
|
||||
- Enhanced table navigation with floating pagination
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### For Extension Developers
|
||||
|
||||
#### Using the New Extension Patterns
|
||||
|
||||
**Before:**
|
||||
```javascript
|
||||
// In addon/engine.js
|
||||
export default class MyEngine extends Engine {
|
||||
setupExtension() {
|
||||
// Setup code here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```javascript
|
||||
// In addon/extension.js
|
||||
import { Widget } from '@fleetbase/ember-core/contracts';
|
||||
|
||||
export default {
|
||||
setupExtension(universe) {
|
||||
// Get widget service
|
||||
const widgetService = universe.getService('widget');
|
||||
|
||||
// Register widgets, menu items, etc.
|
||||
widgetService.registerWidgets('dashboard', [new Widget({ ... })]);
|
||||
|
||||
// Register hooks for when other engines load
|
||||
universe.whenEngineLoaded('@fleetbase/fleetops-engine', (engineInstance) => {
|
||||
// Setup integration with FleetOps
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Accessing Universe Sub-Services
|
||||
|
||||
**Flexible Service Resolution:**
|
||||
```javascript
|
||||
// All of these work:
|
||||
const menuService = universe.getService('menu');
|
||||
const menuService = universe.getService('menu-service');
|
||||
const menuService = universe.getService('menuService');
|
||||
const menuService = universe.getService('universe/menu-service');
|
||||
|
||||
const hookService = universe.getService('hooks');
|
||||
const widgetService = universe.getService('widgets');
|
||||
const registry = universe.getService('registry');
|
||||
```
|
||||
|
||||
#### Widget Registration
|
||||
|
||||
**Before:**
|
||||
```javascript
|
||||
universe.registerWidget(widgetObject);
|
||||
```
|
||||
|
||||
**After:**
|
||||
```javascript
|
||||
// Register widgets available for a dashboard
|
||||
universe.registerWidgets('dashboard', [widget1, widget2]);
|
||||
|
||||
// Register widgets that auto-load on a dashboard
|
||||
universe.registerDefaultWidgets('dashboard', [widget1]);
|
||||
```
|
||||
## ✨ Highlights
|
||||
- Critical patch which fixes eager loading relationship when querying orders via the consumable API, this is done to handle the new `whenLoaded` resource conditional methods. [fleetbase/fleetops#203](https://github.com/fleetbase/fleetops/pull/203)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Breaking Changes
|
||||
- **NO MAJOR BREAKING CHANGED** Both releases maintain full backward compatibility with existing code. The refactor uses a facade pattern to ensure all existing APIs continue to work while providing new, improved patterns for future development.
|
||||
### Notice
|
||||
- Extensions must adapt to the new `extension.js` pattern for initialization setup.
|
||||
- Extension dependencies are not auto-loaded and initialized, dependencies must be explicitly loaded from the setup `extension.js`
|
||||
- `UniverseService` has major changes which refactored core responsibilities to sub-services. There is backwards compatability, but plan to migrate to the new integration architecture.
|
||||
- None 🙂
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Upgrade Steps
|
||||
|
||||
```bash
|
||||
# Pull latest version
|
||||
git pull origin main --no-rebase
|
||||
@@ -288,12 +28,7 @@ docker compose down && docker compose up -d
|
||||
docker compose exec application bash -c "./deploy.sh"
|
||||
```
|
||||
|
||||
### Post-Upgrade
|
||||
|
||||
1. Clear browser cache and localStorage
|
||||
2. Restart your development server
|
||||
3. Test extension loading and dashboard functionality
|
||||
4. Check console for any deprecation warnings
|
||||
---
|
||||
|
||||
## Need help?
|
||||
Join the discussion on [GitHub Discussions](https://github.com/fleetbase/fleetbase/discussions) or drop by [#fleetbase on Discord](https://discord.com/invite/HnTqQ6zAVn)
|
||||
Join the discussion on [GitHub Discussions](https://github.com/fleetbase/ember-ui/discussions) or drop by [#fleetbase on Discord](https://discord.com/invite/HnTqQ6zAVn)
|
||||
|
||||
@@ -40,7 +40,6 @@ class Kernel extends HttpKernel
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -18,12 +18,21 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.0 <=8.2.28",
|
||||
"php": ">=8.0 <=8.2.30",
|
||||
"appstract/laravel-opcache": "^4.0",
|
||||
"fleetbase/core-api": "^1.6.27",
|
||||
"fleetbase/fleetops-api": "^0.6.28",
|
||||
"fleetbase/registry-bridge": "^0.1.1",
|
||||
"fleetbase/storefront-api": "^0.4.8",
|
||||
"fleetbase/core-api": "^1.6.35",
|
||||
"fleetbase/fleetops-api": "^0.6.35",
|
||||
"fleetbase/registry-bridge": "^0.1.5",
|
||||
"fleetbase/storefront-api": "^0.4.13",
|
||||
"fleetbase/aws-marketplace": "^0.0.8",
|
||||
"fleetbase/billing-api": "^0.1.20",
|
||||
"fleetbase/customer-portal-api": "^0.0.10",
|
||||
"fleetbase/flespi-integration": "^0.1.16",
|
||||
"fleetbase/internals-api": "^0.0.27",
|
||||
"fleetbase/samsara-api": "^0.0.3",
|
||||
"fleetbase/solid-api": "^0.0.7",
|
||||
"fleetbase/valhalla-api": "^0.0.3",
|
||||
"fleetbase/vroom-api": "^0.0.3",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/octane": "^2.3",
|
||||
@@ -36,7 +45,7 @@
|
||||
"psr/http-factory-implementation": "*",
|
||||
"resend/resend-php": "^0.14.0",
|
||||
"s-ichikawa/laravel-sendgrid-driver": "^4.0",
|
||||
"stripe/stripe-php": "13.13.0",
|
||||
"stripe/stripe-php": "^17.0",
|
||||
"symfony/mailgun-mailer": "^7.1",
|
||||
"symfony/postmark-mailer": "^7.1"
|
||||
},
|
||||
@@ -52,6 +61,14 @@
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://registry.fleetbase.io"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/fleetbase/aws-marketplace"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/fleetbase/internals"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
|
||||
2602
api/composer.lock
generated
2602
api/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ return [
|
||||
|
||||
'allowed_headers' => ['*'],
|
||||
|
||||
'exposed_headers' => ['x-compressed-json', 'access-console-sandbox', 'access-console-sandbox-key'],
|
||||
'exposed_headers' => ['x-compressed-json', 'access-console-sandbox', 'access-console-sandbox-key', 'content-disposition'],
|
||||
|
||||
'max_age' => 0,
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ return [
|
||||
'channels' => [
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => ['single'],
|
||||
'channels' => ['single', 'stdout'],
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
|
||||
@@ -105,8 +105,8 @@ return [
|
||||
OperationTerminated::class => [
|
||||
FlushOnce::class,
|
||||
FlushTemporaryContainerInstances::class,
|
||||
// DisconnectFromDatabases::class,
|
||||
// CollectGarbage::class,
|
||||
DisconnectFromDatabases::class,
|
||||
CollectGarbage::class,
|
||||
],
|
||||
|
||||
WorkerErrorOccurred::class => [
|
||||
|
||||
@@ -43,4 +43,10 @@ return [
|
||||
'secret' => env('STRIPE_SECRET', env('STRIPE_API_SECRET')),
|
||||
'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
|
||||
],
|
||||
|
||||
'callpromn' => [
|
||||
'api_key' => env('CALLPROMN_API_KEY', ''),
|
||||
'from' => env('CALLPROMN_FROM', ''),
|
||||
'base_url' => env('CALLPROMN_BASE_URL', 'https://api.messagepro.mn' ),
|
||||
],
|
||||
];
|
||||
|
||||
20
api/resources/lang/uz/auth.php
Normal file
20
api/resources/lang/uz/auth.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used during authentication for various
|
||||
| messages that we need to display to the user. You are free to modify
|
||||
| these language lines according to your application's requirements.
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => 'Ushbu hisobga olish maʼlumotlari bizning yozuvlarimizga mos kelmadi.',
|
||||
'password' => 'Taqdim etilgan parol noto‘g‘ri.',
|
||||
'throttle' => 'Kirishga urinishlar juda koʻp. Iltimos, :soniyadan keyin qayta urinib koʻring.',
|
||||
|
||||
];
|
||||
19
api/resources/lang/uz/pagination.php
Normal file
19
api/resources/lang/uz/pagination.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pagination Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used by the paginator library to build
|
||||
| the simple pagination links. You are free to change them to anything
|
||||
| you want to customize your views to better match your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'previous' => '« Oldingi',
|
||||
'next' => 'Keyingi »',
|
||||
|
||||
];
|
||||
22
api/resources/lang/uz/passwords.php
Normal file
22
api/resources/lang/uz/passwords.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are the default lines which match reasons
|
||||
| that are given by the password broker for a password update attempt
|
||||
| has failed, such as for an invalid token or invalid new password.
|
||||
|
|
||||
*/
|
||||
|
||||
'reset' => 'Sizning parolingiz tiklandi!',
|
||||
'sent' => 'Parolni tiklash havolasini elektron pochta orqali yubordik!',
|
||||
'throttled' => 'Qayta urinishdan oldin kuting.',
|
||||
'token' => 'Ushbu parolni tiklash belgisi yaroqsiz.',
|
||||
'user' => "Biz bu elektron pochta manziliga ega foydalanuvchini topa olmadik.",
|
||||
|
||||
];
|
||||
163
api/resources/lang/uz/validation.php
Normal file
163
api/resources/lang/uz/validation.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines contain the default error messages used by
|
||||
| the validator class. Some of these rules have multiple versions such
|
||||
| as the size rules. Feel free to tweak each of these messages here.
|
||||
|
|
||||
*/
|
||||
|
||||
'accepted' => ':attribute qabul qilinishi kerak.',
|
||||
'accepted_if' => ':other :value bo‘lsa, :attribute qabul qilinishi kerak.',
|
||||
'active_url' => ':attribute yaroqli URL emas.',
|
||||
'after' => ':attribute :date dan keyingi sana boʻlishi kerak.',
|
||||
'after_or_equal' => ':attribute :date ga teng yoki undan keyingi sana boʻlishi kerak.',
|
||||
'alpha' => ':attribute faqat harflardan iborat boʻlishi kerak.',
|
||||
'alpha_dash' => ':attribute faqat harflar, raqamlar, chiziqlar va pastki chiziqlardan iborat boʻlishi kerak.',
|
||||
'alpha_num' => ':attribute faqat harflar va raqamlardan iborat boʻlishi kerak.',
|
||||
'array' => ':attribute massiv boʻlishi kerak.',
|
||||
'before' => ':attribute :date gacha boʻlgan sana boʻlishi kerak.',
|
||||
'before_or_equal' => ':attribute :date ga teng yoki undan oldingi sana boʻlishi kerak.',
|
||||
'between' => [
|
||||
'numeric' => ':attribute :min va :max orasida boʻlishi kerak.',
|
||||
'file' => ':attribute :min va :max kilobayt orasida boʻlishi kerak.',
|
||||
'string' => ':attribute :min va :max belgilar orasida boʻlishi kerak.',
|
||||
'array' => ':attribute :min va :max ta element orasida boʻlishi kerak.',
|
||||
],
|
||||
'boolean' => ':attribute maydoni rost yoki yolgʻon boʻlishi kerak.',
|
||||
'confirmed' => ':attribute tasdiqlanishi mos kelmadi.',
|
||||
'current_password' => 'Parol noto‘g‘ri.',
|
||||
'date' => ':attribute yaroqli sana emas.',
|
||||
'date_equals' => ':attribute :date ga teng sana boʻlishi kerak.',
|
||||
'date_format' => ':attribute :format formatiga mos kelmadi.',
|
||||
'declined' => ':attribute rad etilishi kerak.',
|
||||
'declined_if' => ':other :value bo‘lsa, :attribute rad etilishi kerak.',
|
||||
'different' => ':attribute va :other har xil boʻlishi kerak.',
|
||||
'digits' => ':attribute :digits raqamdan iborat boʻlishi kerak.',
|
||||
'digits_between' => ':attribute :min va :max raqamlari orasida boʻlishi kerak.',
|
||||
'dimensions' => ':attribute rasm oʻlchamlari yaroqsiz.',
|
||||
'distinct' => ':attribute maydonida takroriy qiymat mavjud.',
|
||||
'email' => ':attribute yaroqli elektron pochta manzili boʻlishi kerak.',
|
||||
'ends_with' => ':attribute quyidagilardan biri bilan tugashi kerak: :values.',
|
||||
'enum' => 'Tanlangan :attribute yaroqsiz.',
|
||||
'exists' => 'Tanlangan :attribute yaroqsiz.',
|
||||
'file' => ':attribute fayl boʻlishi kerak.',
|
||||
'filled' => ':attribute maydonida qiymat boʻlishi kerak.',
|
||||
'gt' => [
|
||||
'numeric' => ':attribute :value dan katta boʻlishi kerak.',
|
||||
'file' => ':attribute :value kilobaytdan katta boʻlishi kerak.',
|
||||
'string' => ':attribute :value belgidan katta boʻlishi kerak.',
|
||||
'array' => ':attribute :value dan koʻp elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'gte' => [
|
||||
'numeric' => ':attribute :value ga teng yoki undan katta boʻlishi kerak.',
|
||||
'file' => ':attribute :value kilobaytga teng yoki undan katta boʻlishi kerak.',
|
||||
'string' => ':attribute :value belgiga teng yoki undan katta boʻlishi kerak.',
|
||||
'array' => ':attribute :value yoki undan koʻp elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'image' => ':attribute rasm boʻlishi kerak.',
|
||||
'in' => 'Tanlangan :attribute yaroqsiz.',
|
||||
'in_array' => ':attribute maydoni :other da mavjud emas.',
|
||||
'integer' => ':attribute butun son boʻlishi kerak.',
|
||||
'ip' => ':attribute yaroqli IP manzil boʻlishi kerak.',
|
||||
'ipv4' => ':attribute yaroqli IPv4 manzil boʻlishi kerak.',
|
||||
'ipv6' => ':attribute yaroqli IPv6 manzil boʻlishi kerak.',
|
||||
'json' => ':attribute yaroqli JSON qatori boʻlishi kerak.',
|
||||
'lt' => [
|
||||
'numeric' => ':attribute :value dan kichik boʻlishi kerak.',
|
||||
'file' => ':attribute :value kilobaytdan kichik boʻlishi kerak.',
|
||||
'string' => ':attribute :value belgidan kichik boʻlishi kerak.',
|
||||
'array' => ':attribute :value dan kam elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'lte' => [
|
||||
'numeric' => ':attribute :value ga teng yoki undan kichik boʻlishi kerak.',
|
||||
'file' => ':attribute :value kilobaytga teng yoki undan kichik boʻlishi kerak.',
|
||||
'string' => ':attribute :value belgiga teng yoki undan kichik boʻlishi kerak.',
|
||||
'array' => ':attribute :value dan koʻp boʻlmagan elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'mac_address' => ':attribute yaroqli MAC manzil boʻlishi kerak.',
|
||||
'max' => [
|
||||
'numeric' => ':attribute :max dan katta boʻlmasligi kerak.',
|
||||
'file' => ':attribute :max kilobaytdan katta boʻlmasligi kerak.',
|
||||
'string' => ':attribute :max belgidan katta boʻlmasligi kerak.',
|
||||
'array' => ':attribute :max dan koʻp boʻlmagan elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'mimes' => ':attribute :values turidagi fayl boʻlishi kerak.',
|
||||
'mimetypes' => ':attribute :values turidagi fayl boʻlishi kerak.',
|
||||
'min' => [
|
||||
'numeric' => ':attribute kamida :min boʻlishi kerak.',
|
||||
'file' => ':attribute kamida :min kilobayt boʻlishi kerak.',
|
||||
'string' => ':attribute kamida :min belgidan iborat boʻlishi kerak.',
|
||||
'array' => ':attribute kamida :min ta elementga ega boʻlishi kerak.',
|
||||
],
|
||||
'multiple_of' => ':attribute :value ning karrali boʻlishi kerak.',
|
||||
'not_in' => 'Tanlangan :attribute yaroqsiz.',
|
||||
'not_regex' => ':attribute formati yaroqsiz.',
|
||||
'numeric' => ':attribute raqam boʻlishi kerak.',
|
||||
'password' => 'Parol noto‘g‘ri.',
|
||||
'present' => ':attribute maydoni mavjud boʻlishi kerak.',
|
||||
'prohibited' => ':attribute maydoni taqiqlangan.',
|
||||
'prohibited_if' => ':other :value bo‘lsa, :attribute maydoni taqiqlangan.',
|
||||
'prohibited_unless' => ':other :values da boʻlmasa, :attribute maydoni taqiqlangan.',
|
||||
'prohibits' => ':attribute maydoni :other ning mavjud boʻlishini taqiqlaydi.',
|
||||
'regex' => ':attribute formati yaroqsiz.',
|
||||
'required' => ':attribute maydoni toʻldirilishi shart.',
|
||||
'required_array_keys' => ':attribute maydonida :values uchun yozuvlar boʻlishi kerak.',
|
||||
'required_if' => ':other :value bo‘lsa, :attribute maydoni toʻldirilishi shart.',
|
||||
'required_unless' => ':other :values da boʻlmasa, :attribute maydoni toʻldirilishi shart.',
|
||||
'required_with' => ':values mavjud boʻlganda :attribute maydoni toʻldirilishi shart.',
|
||||
'required_with_all' => ':values mavjud boʻlganda :attribute maydoni toʻldirilishi shart.',
|
||||
'required_without' => ':values mavjud boʻlmaganda :attribute maydoni toʻldirilishi shart.',
|
||||
'required_without_all' => ':values ning hech biri mavjud boʻlmaganda :attribute maydoni toʻldirilishi shart.',
|
||||
'same' => ':attribute va :other mos kelishi kerak.',
|
||||
'size' => [
|
||||
'numeric' => ':attribute :size boʻlishi kerak.',
|
||||
'file' => ':attribute :size kilobayt boʻlishi kerak.',
|
||||
'string' => ':attribute :size belgidan iborat boʻlishi kerak.',
|
||||
'array' => ':attribute :size ta elementdan iborat boʻlishi kerak.',
|
||||
],
|
||||
'starts_with' => ':attribute quyidagilardan biri bilan boshlanishi kerak: :values.',
|
||||
'string' => ':attribute qator boʻlishi kerak.',
|
||||
'timezone' => ':attribute yaroqli vaqt mintaqasi boʻlishi kerak.',
|
||||
'unique' => ':attribute allaqachon olingan.',
|
||||
'uploaded' => ':attribute yuklab olinmadi.',
|
||||
'url' => ':attribute yaroqli URL boʻlishi kerak.',
|
||||
'uuid' => ':attribute yaroqli UUID boʻlishi kerak.',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify custom validation messages for attributes using the
|
||||
| convention "attribute.rule" to name the lines. This makes it quick to
|
||||
| specify a specific custom language line for a given attribute rule.
|
||||
|
|
||||
*/
|
||||
|
||||
'custom' => [
|
||||
'attribute-name' => [
|
||||
'rule-name' => 'custom-message',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used to swap our attribute placeholder
|
||||
| with something more reader friendly such as "E-Mail Address" instead
|
||||
| of "email". This simply helps us make our message more expressive.
|
||||
|
|
||||
*/
|
||||
|
||||
'attributes' => [],
|
||||
|
||||
];
|
||||
@@ -2,6 +2,7 @@ import Application from '@ember/application';
|
||||
import Resolver from 'ember-resolver';
|
||||
import loadInitializers from 'ember-load-initializers';
|
||||
import config from '@fleetbase/console/config/environment';
|
||||
import './deprecation-workflow';
|
||||
|
||||
export default class App extends Application {
|
||||
modulePrefix = config.modulePrefix;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<Overlay @isOpen={{@isOpen}} @onLoad={{this.setOverlayContext}} @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width={{or this.width @width "400px"}}>
|
||||
<Overlay::Header @title={{t "component.dashboard-widget-panel.select-widgets"}} @hideStatusDot={{true}} @titleWrapperClass="leading-5">
|
||||
<div class="flex flex-1 justify-end">
|
||||
<Button @type="default" @icon="times" @helpText={{t "component.dashboard-widget-panel.close-and-save"}} @onClick={{this.onPressClose}} />
|
||||
</div>
|
||||
</Overlay::Header>
|
||||
|
||||
<Overlay::Body @wrapperClass="new-service-rate-overlay-body px-4 space-y-4 pt-4">
|
||||
<div class="grid grid-cols-1 gap-4 text-xs dark:text-gray-100">
|
||||
{{#each this.availableWidgets as |widget|}}
|
||||
<div
|
||||
class="rounded-lg border border-gray-300 bg-white dark:border-gray-700 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all duration-300 ease-in-out shadow-md px-4 py-2 cursor-pointer"
|
||||
{{on "click" (fn this.addWidgetToDashboard widget)}}
|
||||
>
|
||||
<div class="flex flex-row items-center leading-6 mb-2.5">
|
||||
<div class="w-8 flex items-center justify-start">
|
||||
<FaIcon @icon={{widget.icon}} class="text-lg text-gray-600 dark:text-gray-300" />
|
||||
</div>
|
||||
<p class="text-sm truncate font-semibold dark:text-gray-100 text-gray-800">
|
||||
{{t "component.dashboard-widget-panel.widget-name" widgetName=widget.name}}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs dark:text-gray-100 text-gray-800">{{widget.description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
</Overlay::Body>
|
||||
</Overlay>
|
||||
@@ -1,60 +0,0 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class DashboardWidgetPanelComponent extends Component {
|
||||
@service universe;
|
||||
@tracked availableWidgets = [];
|
||||
@tracked dashboard;
|
||||
@tracked isOpen = true;
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Constructs the component and applies initial state.
|
||||
*/
|
||||
constructor(owner, { dashboard }) {
|
||||
super(...arguments);
|
||||
|
||||
this.availableWidgets = this.universe.getDashboardWidgets();
|
||||
this.dashboard = dashboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the overlay context.
|
||||
*
|
||||
* @action
|
||||
* @param {OverlayContextObject} overlayContext
|
||||
*/
|
||||
@action setOverlayContext(overlayContext) {
|
||||
this.context = overlayContext;
|
||||
|
||||
if (typeof this.args.onLoad === 'function') {
|
||||
this.args.onLoad(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@action addWidgetToDashboard(widget) {
|
||||
// If widget is a component definition/class
|
||||
if (typeof widget.component === 'function') {
|
||||
widget.component = widget.component.name;
|
||||
}
|
||||
|
||||
this.args.dashboard.addWidget(widget).catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles cancel button press.
|
||||
*
|
||||
* @action
|
||||
*/
|
||||
@action onPressClose() {
|
||||
this.isOpen = false;
|
||||
|
||||
if (typeof this.args.onClose === 'function') {
|
||||
this.args.onClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,61 @@
|
||||
<div class="bg-white dark:bg-gray-800 py-5 px-4 shadow rounded-lg w-full">
|
||||
<div class="mb-4">
|
||||
<Image src={{@brand.logo_url}} @fallbackSrc="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}} height="56" class="h-10 object-contain mx-auto" />
|
||||
<div class="mt-2">
|
||||
<h2 class="text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
{{t "onboard.index.title"}}
|
||||
</h2>
|
||||
<div class="flex items-center justify-center h-screen min-h-screen px-4 py-12 bg-gray-50 dark:bg-gray-900 sm:px-6 lg:px-8 overflow-y-scroll">
|
||||
<div class="w-full max-w-md h-screen flex items-center justify-center py-4">
|
||||
<div class="bg-white dark:bg-gray-800 py-5 px-4 shadow rounded-lg w-full">
|
||||
<div class="mb-4">
|
||||
<Image src={{@brand.logo_url}} @fallbackSrc="/images/fleetbase-logo-svg.svg" alt={{t "app.name"}} height="56" class="h-10 object-contain mx-auto" />
|
||||
<div class="mt-2">
|
||||
<h2 class="text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
{{t "onboard.index.title"}}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex px-3 py-2 mb-4 rounded-md shadow-sm bg-blue-200">
|
||||
<div>
|
||||
<FaIcon @icon="hand-spock" @size="lg" class="text-blue-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-blue-900 dark:text-blue-900">
|
||||
{{t "onboard.index.welcome-title" htmlSafe=true companyName=(t "app.name")}}
|
||||
{{t "onboard.index.welcome-text"}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form {{on "submit" (perform this.onboard)}}>
|
||||
{{#if this.error}}
|
||||
<InfoBlock @icon="exclamation-triangle" @text={{this.error}} class="mb-6 px-3 py-2 bg-red-300 text-red-900" @textClass="text-red-900" />
|
||||
{{/if}}
|
||||
<InputGroup @name={{t "onboard.index.full-name"}} @value={{this.name}} @helpText={{t "onboard.index.full-name-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.your-email"}} @type="email" @value={{this.email}} @helpText={{t "onboard.index.your-email-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.phone"}} @helpText={{t "onboard.index.phone-help-text"}}>
|
||||
<PhoneInput @onInput={{fn (mut this.phone)}} class="form-input input-lg w-full" />
|
||||
</InputGroup>
|
||||
<InputGroup @name={{t "onboard.index.organization-name"}} @value={{this.organization_name}} @helpText={{t "onboard.index.organization-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.password"}} @value={{this.password}} @type="password" @helpText={{t "onboard.index.password-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup
|
||||
@name={{t "onboard.index.confirm-password"}}
|
||||
@value={{this.password_confirmation}}
|
||||
@type="password"
|
||||
@helpText={{t "onboard.index.confirm-password-help-text"}}
|
||||
@inputClass="input-lg"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-end mt-5">
|
||||
<Button
|
||||
@buttonType="submit"
|
||||
@icon="check"
|
||||
@iconPrefix="fas"
|
||||
@type="primary"
|
||||
@size="lg"
|
||||
@text={{t "onboard.index.continue-button-text"}}
|
||||
@isLoading={{this.onboard.isRunning}}
|
||||
@disabled={{not this.filled}}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<RegistryYield @registry="onboard" as |YieldedComponent ctx|>
|
||||
<YieldedComponent @context={{ctx}} />
|
||||
</RegistryYield>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex px-3 py-2 mb-4 rounded-md shadow-sm bg-blue-200">
|
||||
<div>
|
||||
<FaIcon @icon="hand-spock" @size="lg" class="text-blue-900 mr-4" />
|
||||
</div>
|
||||
<p class="flex-1 text-sm text-blue-900 dark:text-blue-900">
|
||||
{{t "onboard.index.welcome-title" htmlSafe=true companyName=(t "app.name")}}
|
||||
{{t "onboard.index.welcome-text"}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form {{on "submit" (perform this.onboard)}}>
|
||||
{{#if this.error}}
|
||||
<InfoBlock @icon="exclamation-triangle" @text={{this.error}} class="mb-6 px-3 py-2 bg-red-300 text-red-900" @textClass="text-red-900" />
|
||||
{{/if}}
|
||||
<InputGroup @name={{t "onboard.index.full-name"}} @value={{this.name}} @helpText={{t "onboard.index.full-name-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.your-email"}} @type="email" @value={{this.email}} @helpText={{t "onboard.index.your-email-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.phone"}} @helpText={{t "onboard.index.phone-help-text"}}>
|
||||
<PhoneInput @onInput={{fn (mut this.phone)}} class="form-input input-lg w-full" />
|
||||
</InputGroup>
|
||||
<InputGroup @name={{t "onboard.index.organization-name"}} @value={{this.organization_name}} @helpText={{t "onboard.index.organization-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.password"}} @value={{this.password}} @type="password" @helpText={{t "onboard.index.password-help-text"}} @inputClass="input-lg" />
|
||||
<InputGroup @name={{t "onboard.index.confirm-password"}} @value={{this.password_confirmation}} @type="password" @helpText={{t "onboard.index.confirm-password-help-text"}} @inputClass="input-lg" />
|
||||
|
||||
<div class="flex items-center justify-end mt-5">
|
||||
<Button @buttonType="submit" @icon="check" @iconPrefix="fas" @type="primary" @size="lg" @text={{t "onboard.index.continue-button-text"}} @isLoading={{this.onboard.isRunning}} @disabled={{not this.filled}} />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<RegistryYield @registry="onboard" as |YieldedComponent ctx|>
|
||||
<YieldedComponent @context={{ctx}} />
|
||||
</RegistryYield>
|
||||
</div>
|
||||
@@ -1,78 +1,82 @@
|
||||
{{page-title (t "onboard.verify-email.header-title")}}
|
||||
|
||||
{{#if this.initialized}}
|
||||
<div class="bg-white dark:bg-gray-800 py-8 px-4 shadow rounded-lg w-full">
|
||||
<div class="mb-6">
|
||||
<LinkTo @route="console" class="flex items-center justify-center">
|
||||
<LogoIcon @size="12" class="rounded-md" />
|
||||
</LinkTo>
|
||||
<h2 class="mt-6 text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
{{t "onboard.verify-email.title"}}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="flex items-center justify-center h-screen min-h-screen px-4 py-12 bg-gray-50 dark:bg-gray-900 sm:px-6 lg:px-8 overflow-y-scroll">
|
||||
<div class="w-full max-w-md h-screen flex items-center justify-center py-4">
|
||||
{{#if this.initialized}}
|
||||
<div class="bg-white dark:bg-gray-800 py-8 px-4 shadow rounded-lg w-full">
|
||||
<div class="mb-6">
|
||||
<LinkTo @route="console" class="flex items-center justify-center">
|
||||
<LogoIcon @size="12" class="rounded-md" />
|
||||
</LinkTo>
|
||||
<h2 class="mt-6 text-center text-lg font-extrabold text-gray-900 dark:text-white truncate">
|
||||
{{t "onboard.verify-email.title"}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<InfoBlock @type="info" @icon="shield-halved" @iconSize="lg">
|
||||
{{t "onboard.verify-email.message-text" htmlSafe=true}}
|
||||
</InfoBlock>
|
||||
<InfoBlock @type="info" @icon="shield-halved" @iconSize="lg">
|
||||
{{t "onboard.verify-email.message-text" htmlSafe=true}}
|
||||
</InfoBlock>
|
||||
|
||||
<form class="mt-8 space-y-6" {{on "submit" (perform this.verify)}}>
|
||||
<InputGroup
|
||||
@type="tel"
|
||||
@name={{t "onboard.verify-email.verification-input-label"}}
|
||||
@value={{this.code}}
|
||||
@helpText={{t "onboard.verify-email.verification-code-text"}}
|
||||
@inputClass="input-lg"
|
||||
{{on "input" this.verification.validateInput}}
|
||||
{{did-insert this.verification.validateInput}}
|
||||
/>
|
||||
<form class="mt-8 space-y-6" {{on "submit" (perform this.verify)}}>
|
||||
<InputGroup
|
||||
@type="tel"
|
||||
@name={{t "onboard.verify-email.verification-input-label"}}
|
||||
@value={{this.code}}
|
||||
@helpText={{t "onboard.verify-email.verification-code-text"}}
|
||||
@inputClass="input-lg"
|
||||
{{on "input" this.verification.validateInput}}
|
||||
{{did-insert this.verification.validateInput}}
|
||||
/>
|
||||
|
||||
<div class="flex flex-row items-center space-x-4">
|
||||
<Button
|
||||
@icon="check"
|
||||
@iconPrefix="fas"
|
||||
@buttonType="submit"
|
||||
@type="primary"
|
||||
@size="lg"
|
||||
@text="Verify & Continue"
|
||||
@isLoading={{this.verify.isRunning}}
|
||||
@disabled={{not this.verification.ready}}
|
||||
/>
|
||||
<a href="#" {{on "click" this.verification.didntReceiveCode}} class="text-sm text-blue-400 hover:text-blue-300">{{t "onboard.verify-email.didnt-receive-a-code"}}</a>
|
||||
</div>
|
||||
<div class="flex flex-row items-center space-x-4">
|
||||
<Button
|
||||
@icon="check"
|
||||
@iconPrefix="fas"
|
||||
@buttonType="submit"
|
||||
@type="primary"
|
||||
@size="lg"
|
||||
@text="Verify & Continue"
|
||||
@isLoading={{this.verify.isRunning}}
|
||||
@disabled={{not this.verification.ready}}
|
||||
/>
|
||||
<a href="#" {{on "click" this.verification.didntReceiveCode}} class="text-sm text-blue-400 hover:text-blue-300">{{t "onboard.verify-email.didnt-receive-a-code"}}</a>
|
||||
</div>
|
||||
|
||||
{{#if this.verification.waiting}}
|
||||
<div class="flex flex-col flex-grow-0 flex-shrink-0 text-sm bg-yellow-800 border border-yellow-600 px-2 py-2 rounded-md text-yellow-100 my-4 transition-all">
|
||||
<div class="flex flex-row items-start mb-2">
|
||||
<div class="w-8 flex-grow-0 flex-shrink-0">
|
||||
<FaIcon @icon="triangle-exclamation" @size="xl" class="pt-1" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex-1 text-sm text-yellow-100">
|
||||
<div>{{t "auth.verification.didnt-receive-a-code" htmlSafe=true}}</div>
|
||||
<div>{{t "auth.verification.not-sent.alternative-choice" htmlSafe=true}}</div>
|
||||
{{#if this.verification.waiting}}
|
||||
<div class="flex flex-col flex-grow-0 flex-shrink-0 text-sm bg-yellow-800 border border-yellow-600 px-2 py-2 rounded-md text-yellow-100 my-4 transition-all">
|
||||
<div class="flex flex-row items-start mb-2">
|
||||
<div class="w-8 flex-grow-0 flex-shrink-0">
|
||||
<FaIcon @icon="triangle-exclamation" @size="xl" class="pt-1" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex-1 text-sm text-yellow-100">
|
||||
<div>{{t "auth.verification.didnt-receive-a-code" htmlSafe=true}}</div>
|
||||
<div>{{t "auth.verification.not-sent.alternative-choice" htmlSafe=true}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Button
|
||||
@text={{t "auth.verification.not-sent.resend-email"}}
|
||||
@buttonType="button"
|
||||
@type="link"
|
||||
class="text-yellow-100"
|
||||
@wrapperClass="px-4 py-2 bg-gray-900 bg-opacity-25 hover:opacity-50"
|
||||
@onClick={{this.verification.resendEmail}}
|
||||
/>
|
||||
<Button
|
||||
@text={{t "auth.verification.not-sent.send-by-sms"}}
|
||||
@buttonType="button"
|
||||
@type="link"
|
||||
class="text-yellow-100"
|
||||
@wrapperClass="px-4 py-2 bg-gray-900 bg-opacity-25 hover:opacity-50"
|
||||
@onClick={{this.verification.resendBySms}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Button
|
||||
@text={{t "auth.verification.not-sent.resend-email"}}
|
||||
@buttonType="button"
|
||||
@type="link"
|
||||
class="text-yellow-100"
|
||||
@wrapperClass="px-4 py-2 bg-gray-900 bg-opacity-25 hover:opacity-50"
|
||||
@onClick={{this.verification.resendEmail}}
|
||||
/>
|
||||
<Button
|
||||
@text={{t "auth.verification.not-sent.send-by-sms"}}
|
||||
@buttonType="button"
|
||||
@type="link"
|
||||
class="text-yellow-100"
|
||||
@wrapperClass="px-4 py-2 bg-gray-900 bg-opacity-25 hover:opacity-50"
|
||||
@onClick={{this.verification.resendBySms}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</form>
|
||||
{{/if}}
|
||||
</form>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -1,7 +1,9 @@
|
||||
<section class="onboarding step-host">
|
||||
{{#if this.initialized}}
|
||||
{{#if this.currentComponent}}
|
||||
{{component this.currentComponent context=this.context orchestrator=this.orchestrator brand=@brand}}
|
||||
{{#if this.orchestrator.wrapper}}
|
||||
{{component (lazy-engine-component this.orchestrator.wrapper) currentStepComponent=this.currentComponent context=this.context orchestrator=this.orchestrator brand=@brand}}
|
||||
{{else if this.currentComponent}}
|
||||
{{component (lazy-engine-component this.currentComponent) context=this.context orchestrator=this.orchestrator brand=@brand}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class="flex items-center justify-center min-h-24">
|
||||
|
||||
@@ -68,6 +68,7 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
subject_uuid: this.user.id,
|
||||
subject_type: 'user',
|
||||
type: 'user_avatar',
|
||||
resize: 'md'
|
||||
},
|
||||
(uploadedFile) => {
|
||||
this.user.setProperties({
|
||||
|
||||
@@ -77,7 +77,7 @@ export default class ConsoleAdminOrganizationsIndexUsersController extends Contr
|
||||
valuePath: 'roleName',
|
||||
},
|
||||
{
|
||||
label: this.intl.t('common.phone-number'),
|
||||
label: this.intl.t('common.phone'),
|
||||
valuePath: 'phone',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,6 +2,9 @@ import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class ConsoleAdminVirtualController extends Controller {
|
||||
@tracked bodyClass = 'overflow-y-scroll h-full';
|
||||
@tracked containerClass = 'container mx-auto h-screen';
|
||||
@tracked wrapperClass = 'max-w-3xl my-10 mx-auto';
|
||||
@tracked view;
|
||||
queryParams = ['view'];
|
||||
}
|
||||
|
||||
@@ -6,35 +6,13 @@ import createNotificationKey from '@fleetbase/ember-core/utils/create-notificati
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class ConsoleSettingsNotificationsController extends Controller {
|
||||
/**
|
||||
* Inject the notifications service.
|
||||
*
|
||||
* @memberof ConsoleSettingsNotificationsController
|
||||
*/
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* Inject the fetch service.
|
||||
*
|
||||
* @memberof ConsoleSettingsNotificationsController
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* The notification settings value JSON.
|
||||
*
|
||||
* @memberof ConsoleSettingsNotificationsController
|
||||
* @var {Object}
|
||||
*/
|
||||
@service store;
|
||||
@service currentUser;
|
||||
@tracked notificationSettings = {};
|
||||
|
||||
/**
|
||||
* Notification transport methods enabled.
|
||||
*
|
||||
* @memberof ConsoleSettingsNotificationsController
|
||||
* @var {Array}
|
||||
*/
|
||||
@tracked notificationTransportMethods = ['email', 'sms'];
|
||||
@tracked company;
|
||||
|
||||
/**
|
||||
* Creates an instance of ConsoleSettingsNotificationsController.
|
||||
@@ -45,6 +23,40 @@ export default class ConsoleSettingsNotificationsController extends Controller {
|
||||
this.getSettings.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the "Alphanumeric Sender ID" feature for the current company.
|
||||
*
|
||||
* Updates the company's `options` object by setting the
|
||||
* `alpha_numeric_sender_id_enabled` flag. This controls whether the
|
||||
* organization uses a custom alphanumeric sender ID when sending SMS.
|
||||
*
|
||||
* @action
|
||||
* @param {boolean} enabled - Whether the feature should be enabled or disabled.
|
||||
* @returns {void}
|
||||
*/
|
||||
@action toggleAlphaNumericSenderId(enabled) {
|
||||
const currentOptions = this.company.options ?? {};
|
||||
this.company.set('options', { ...currentOptions, alpha_numeric_sender_id_enabled: enabled });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Alphanumeric Sender ID string for the current company.
|
||||
*
|
||||
* Reads the input's value from the event and updates the company's `options`
|
||||
* object by setting the `alpha_numeric_sender_id` field. This value represents
|
||||
* the sender name that will appear in outbound SMS messages (subject to carrier
|
||||
* support and restrictions).
|
||||
*
|
||||
* @action
|
||||
* @param {Event} event - Input event containing the alphanumeric sender ID value.
|
||||
* @returns {void}
|
||||
*/
|
||||
@action setAlphaNumericSenderId(event) {
|
||||
const value = event.target.value;
|
||||
const currentOptions = this.company.options ?? {};
|
||||
this.company.set('options', { ...currentOptions, alpha_numeric_sender_id: value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Selectes notifiables for settings.
|
||||
*
|
||||
@@ -94,7 +106,8 @@ export default class ConsoleSettingsNotificationsController extends Controller {
|
||||
const { notificationSettings } = this;
|
||||
|
||||
try {
|
||||
yield this.fetch.post('notifications/save-settings', { notificationSettings });
|
||||
yield this.fetch.post('notifications/save-settings', { notificationSettings: notificationSettings ?? {} });
|
||||
yield this.saveCompanyOptions.perform();
|
||||
this.notifications.success('Notification settings successfully saved.');
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error);
|
||||
@@ -114,4 +127,26 @@ export default class ConsoleSettingsNotificationsController extends Controller {
|
||||
this.notifications.serverError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the updated company options to the backend.
|
||||
*
|
||||
* This ember-concurrency task attempts to persist the company's modified
|
||||
* `options` object by calling `company.save()`. If the request fails, a server
|
||||
* error notification is displayed. No action is taken if no company is loaded.
|
||||
*
|
||||
* @task
|
||||
* @generator
|
||||
* @yields {Promise} Resolves when the save request completes.
|
||||
* @returns {Promise<void>} Task completion state.
|
||||
*/
|
||||
@task *saveCompanyOptions() {
|
||||
if (!this.company) return;
|
||||
|
||||
try {
|
||||
yield this.company.save();
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
console/app/deprecation-workflow.js
Normal file
9
console/app/deprecation-workflow.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import setupDeprecationWorkflow from 'ember-cli-deprecation-workflow';
|
||||
|
||||
setupDeprecationWorkflow({
|
||||
workflow: [
|
||||
{ handler: 'silence', matchId: 'ember-concurrency.deprecate-decorator-task' },
|
||||
{ handler: 'silence', matchId: 'new-helper-names' },
|
||||
{ handler: 'silence', matchId: 'ember-data:deprecate-non-strict-relationships' },
|
||||
],
|
||||
});
|
||||
@@ -10,9 +10,11 @@
|
||||
|
||||
{{content-for "head"}}
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/favicon/android-chrome-192x192.png" />
|
||||
<link rel="icon" type="image/png" sizes="256x256" href="/favicon/android-chrome-256x256.png" />
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/vendor.css">
|
||||
|
||||
@@ -2,6 +2,7 @@ import translations from 'ember-intl/translations';
|
||||
import { all } from 'rsvp';
|
||||
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
const isValidLang = (lang) => typeof lang === 'string' && /^[a-z]{2,3}$/i.test(lang);
|
||||
|
||||
function langOf(tag = 'en') {
|
||||
return String(tag).toLowerCase().split('-')[0];
|
||||
@@ -15,6 +16,8 @@ async function loadBasePolyfills() {
|
||||
}
|
||||
|
||||
async function loadLocaleData(lang) {
|
||||
if (!isValidLang(lang)) return;
|
||||
|
||||
return all([
|
||||
import(`@formatjs/intl-numberformat/locale-data/${lang}.js`),
|
||||
import(`@formatjs/intl-pluralrules/locale-data/${lang}.js`),
|
||||
@@ -26,11 +29,11 @@ async function loadLocaleData(lang) {
|
||||
export function initialize(application) {
|
||||
if (!isBrowser) return;
|
||||
|
||||
// Build-time list of locales from the generated module
|
||||
const locales = translations.map(([locale]) => String(locale));
|
||||
const langs = [...new Set(locales.map(langOf))];
|
||||
const langs = [...new Set(locales.map(langOf).filter(isValidLang))];
|
||||
|
||||
application.deferReadiness();
|
||||
|
||||
(async () => {
|
||||
await loadBasePolyfills();
|
||||
await all(langs.map(loadLocaleData));
|
||||
|
||||
@@ -21,8 +21,7 @@ export function initialize(appInstance) {
|
||||
debug('[Initializing Router Patch] Applying router refresh bug fix...');
|
||||
|
||||
try {
|
||||
const application = appInstance.application;
|
||||
applyRouterFix(application);
|
||||
applyRouterFix(appInstance);
|
||||
|
||||
const endTime = performance.now();
|
||||
debug(`[Initializing Router Patch] Router fix applied in ${(endTime - startTime).toFixed(2)}ms`);
|
||||
|
||||
@@ -6,12 +6,10 @@ export async function initialize(appInstance) {
|
||||
const application = appInstance.application;
|
||||
const extensionManager = appInstance.lookup('service:universe/extension-manager');
|
||||
|
||||
if (!application.extensions || application.extensions.length === 0) {
|
||||
try {
|
||||
await extensionManager.loadExtensions(application);
|
||||
} catch (error) {
|
||||
console.error('[load-extensions] Error:', error);
|
||||
}
|
||||
try {
|
||||
await extensionManager.loadExtensions(application);
|
||||
} catch (error) {
|
||||
console.error('[load-extensions] Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function initialize(appInstance) {
|
||||
// Set window.Fleetbase to the application instance for global access
|
||||
// This is used by services and engines to access the root application instance
|
||||
if (typeof window !== 'undefined') {
|
||||
window.Fleetbase = appInstance;
|
||||
}
|
||||
|
||||
// Look up UniverseService and set the application instance
|
||||
const universeService = appInstance.lookup('service:universe');
|
||||
if (universeService) {
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class Company extends Model {
|
||||
@attr('string') logo_url;
|
||||
@attr('string') backdrop_url;
|
||||
@attr('string') description;
|
||||
@attr('raw') options;
|
||||
@attr('object') options;
|
||||
@attr('number') users_count;
|
||||
@attr('string') type;
|
||||
@attr('string') currency;
|
||||
@@ -33,6 +33,7 @@ export default class Company extends Model {
|
||||
@attr('string') phone;
|
||||
@attr('string') status;
|
||||
@attr('string') slug;
|
||||
@attr('boolean', { defaultValue: false }) onboarding_completed;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') joined_at;
|
||||
|
||||
@@ -34,6 +34,7 @@ export default class UserModel extends Model {
|
||||
@attr('boolean') is_admin;
|
||||
@attr('boolean') is_subscribed;
|
||||
@attr('boolean') is_trialing;
|
||||
@attr('boolean') company_onboarding_completed;
|
||||
@attr('raw') meta;
|
||||
@attr('raw') subscription;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import groupBy from '@fleetbase/ember-core/utils/group-by';
|
||||
|
||||
export default class ConsoleSettingsNotificationsRoute extends Route {
|
||||
@service fetch;
|
||||
@service currentUser;
|
||||
|
||||
model() {
|
||||
return hash({
|
||||
@@ -13,10 +14,11 @@ export default class ConsoleSettingsNotificationsRoute extends Route {
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, { registry, notifiables }) {
|
||||
async setupController(controller, { registry, notifiables }) {
|
||||
super.setupController(...arguments);
|
||||
|
||||
controller.groupedNotifications = groupBy(registry, 'package');
|
||||
controller.notifiables = notifiables;
|
||||
controller.company = await this.currentUser.loadCompany();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ export default class OnboardIndexRoute extends Route {
|
||||
};
|
||||
|
||||
beforeModel() {
|
||||
this.orchestrator.start();
|
||||
// Resume from previous session if data exists in localStorage
|
||||
this.orchestrator.start(null, { resume: true });
|
||||
}
|
||||
|
||||
model() {
|
||||
|
||||
@@ -4,8 +4,6 @@ import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
export default class UserSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
|
||||
/**
|
||||
* Embedded relationship attributes
|
||||
*
|
||||
* @var {Object}
|
||||
*/
|
||||
get attrs() {
|
||||
return {
|
||||
@@ -16,22 +14,45 @@ export default class UserSerializer extends ApplicationSerializer.extend(Embedde
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize serializer so that the password is never sent to the server via Ember Data
|
||||
* Prevent partial payloads from overwriting fully-loaded
|
||||
* user records in the store.
|
||||
*
|
||||
* @param {Snapshot} snapshot
|
||||
* @param {Object} options
|
||||
* @return {Object} json
|
||||
* This runs ONLY on incoming data.
|
||||
*/
|
||||
normalize(modelClass, resourceHash, prop) {
|
||||
let normalized = super.normalize(modelClass, resourceHash, prop);
|
||||
|
||||
// Existing user already loaded in the store?
|
||||
let existing = this.store.peekRecord(normalized.data.type, normalized.data.id);
|
||||
|
||||
if (existing) {
|
||||
let attrs = normalized.data.attributes || {};
|
||||
|
||||
for (let key in attrs) {
|
||||
if (attrs[key] === null || attrs[key] === undefined || key === 'avatar_url') {
|
||||
delete attrs[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize serializer so that sensitive or server-managed
|
||||
* fields are never sent to the backend.
|
||||
*/
|
||||
serialize() {
|
||||
const json = super.serialize(...arguments);
|
||||
|
||||
// delete the password always
|
||||
// Never send password
|
||||
delete json.password;
|
||||
// delete verification attributes
|
||||
|
||||
// Verification flags
|
||||
delete json.email_verified_at;
|
||||
delete json.phone_verified_at;
|
||||
|
||||
// delete server managed dates
|
||||
// Server-managed timestamps
|
||||
delete json.deleted_at;
|
||||
delete json.created_at;
|
||||
delete json.updated_at;
|
||||
|
||||
@@ -1,39 +1,133 @@
|
||||
import Service, { inject as service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
const CONTEXT_PREFIX = 'onboarding:context:';
|
||||
const KEYS_INDEX = `${CONTEXT_PREFIX}__keys__`;
|
||||
|
||||
export default class OnboardingContextService extends Service {
|
||||
@service appCache;
|
||||
@tracked data = {};
|
||||
|
||||
/**
|
||||
* Get a value from in-memory state first, then fallback to cache
|
||||
*/
|
||||
get(key) {
|
||||
return this.data[key] ?? this.appCache.get(`onboarding:context:${key}`);
|
||||
return this.data[key] ?? this.appCache.get(`${CONTEXT_PREFIX}${key}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value directly from cache
|
||||
*/
|
||||
getFromCache(key) {
|
||||
return this.appCache.get(`onboarding:context:${key}`);
|
||||
return this.appCache.get(`${CONTEXT_PREFIX}${key}`);
|
||||
}
|
||||
|
||||
set(key, value, options = {}) {
|
||||
this.data = { ...this.data, [key]: value };
|
||||
if (options?.persist === true) {
|
||||
this.appCache.set(`onboarding:context:${key}`, value);
|
||||
/**
|
||||
* Restore all persisted onboarding context values from cache
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
restore() {
|
||||
const keys = this.appCache.get(KEYS_INDEX) ?? [];
|
||||
const persisted = {};
|
||||
|
||||
for (const key of keys) {
|
||||
const value = this.appCache.get(`${CONTEXT_PREFIX}${key}`);
|
||||
if (value !== undefined) {
|
||||
persisted[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return persisted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge data into the context
|
||||
* Optionally persist all merged values
|
||||
*/
|
||||
merge(data = {}, options = {}) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter out sensitive fields
|
||||
const sensitiveFields = ['password', 'password_confirmation'];
|
||||
const filteredData = {};
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (!sensitiveFields.includes(key)) {
|
||||
filteredData[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
this.data = { ...this.data, ...filteredData };
|
||||
|
||||
if (options.persist === true) {
|
||||
const keys = new Set(this.appCache.get(KEYS_INDEX) ?? []);
|
||||
|
||||
for (const key of Object.keys(filteredData)) {
|
||||
keys.add(key);
|
||||
this.appCache.set(`${CONTEXT_PREFIX}${key}`, this.data[key]);
|
||||
}
|
||||
|
||||
this.appCache.set(KEYS_INDEX, [...keys]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a single value
|
||||
* Optionally persist it
|
||||
*/
|
||||
set(key, value, options = {}) {
|
||||
// Don't store sensitive fields
|
||||
const sensitiveFields = ['password', 'password_confirmation'];
|
||||
if (sensitiveFields.includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.data = { ...this.data, [key]: value };
|
||||
|
||||
if (options.persist === true) {
|
||||
const keys = new Set(this.appCache.get(KEYS_INDEX) ?? []);
|
||||
keys.add(key);
|
||||
|
||||
this.appCache.set(`${CONTEXT_PREFIX}${key}`, value);
|
||||
this.appCache.set(KEYS_INDEX, [...keys]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience alias for persisted set
|
||||
*/
|
||||
persist(key, value) {
|
||||
this.set(key, value, { persist: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a key from memory and cache
|
||||
*/
|
||||
del(key) {
|
||||
const { [key]: _drop, ...rest } = this.data; // eslint-disable-line no-unused-vars
|
||||
const { [key]: _removed, ...rest } = this.data; // eslint-disable-line no-unused-vars
|
||||
this.data = rest;
|
||||
this.appCache.set(`onboarding:context:${key}`, undefined);
|
||||
|
||||
const keys = new Set(this.appCache.get(KEYS_INDEX) ?? []);
|
||||
keys.delete(key);
|
||||
|
||||
this.appCache.set(`${CONTEXT_PREFIX}${key}`, undefined);
|
||||
this.appCache.set(KEYS_INDEX, [...keys]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully reset onboarding context (memory + persistence)
|
||||
*/
|
||||
reset() {
|
||||
for (let key in this.data) {
|
||||
this.appCache.set(`onboarding:context:${key}`, undefined);
|
||||
const keys = this.appCache.get(KEYS_INDEX) ?? [];
|
||||
|
||||
for (const key of keys) {
|
||||
this.appCache.set(`${CONTEXT_PREFIX}${key}`, undefined);
|
||||
}
|
||||
|
||||
this.appCache.set(KEYS_INDEX, []);
|
||||
this.data = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,43 @@ export default class OnboardingOrchestratorService extends Service {
|
||||
@service onboardingContext;
|
||||
|
||||
@tracked flow = null;
|
||||
@tracked wrapper = null;
|
||||
@tracked current = null;
|
||||
@tracked history = [];
|
||||
@tracked sessionId = null;
|
||||
|
||||
start(flowId = null, opts = {}) {
|
||||
/**
|
||||
* localStorage key for persisting navigation history
|
||||
*/
|
||||
get historyStorageKey() {
|
||||
return `onboarding:history:${this.flow?.id || 'default'}`;
|
||||
}
|
||||
|
||||
async start(flowId = null, opts = {}) {
|
||||
const flow = this.onboardingRegistry.getFlow(flowId ?? this.onboardingRegistry.defaultFlow);
|
||||
if (!flow) throw new Error(`Onboarding flow '${flowId}' not found`);
|
||||
|
||||
this.flow = flow;
|
||||
this.wrapper = flow.wrapper || null;
|
||||
this.sessionId = opts.sessionId || null;
|
||||
this.history = [];
|
||||
this.goto(flow.entry);
|
||||
|
||||
// Restore history if resuming from a previous session
|
||||
if (opts.resume) {
|
||||
this._restoreHistory();
|
||||
}
|
||||
|
||||
// Execute onFlowWillStart hook if defined
|
||||
if (typeof this.flow.onFlowWillStart === 'function') {
|
||||
await this.flow.onFlowWillStart(this.flow, this);
|
||||
}
|
||||
|
||||
await this.goto(flow.entry);
|
||||
|
||||
// Execute onFlowDidStart hook if defined
|
||||
if (typeof this.flow.onFlowDidStart === 'function') {
|
||||
await this.flow.onFlowDidStart(this.flow, this);
|
||||
}
|
||||
}
|
||||
|
||||
async goto(stepId) {
|
||||
@@ -25,27 +51,46 @@ export default class OnboardingOrchestratorService extends Service {
|
||||
const step = this.flow.steps.find((s) => s.id === stepId);
|
||||
if (!step) throw new Error(`Step '${stepId}' not found`);
|
||||
|
||||
// Execute onStepWillChange hook if defined
|
||||
const previousStep = this.current;
|
||||
if (typeof this.flow.onStepWillChange === 'function') {
|
||||
await this.flow.onStepWillChange(step, previousStep, this);
|
||||
}
|
||||
|
||||
// Guard function - skip step if guard returns false
|
||||
if (typeof step.guard === 'function' && !step.guard(this.onboardingContext)) {
|
||||
return this.next();
|
||||
}
|
||||
|
||||
// beforeEnter lifecycle hook
|
||||
if (typeof step.beforeEnter === 'function') {
|
||||
await step.beforeEnter(this.onboardingContext);
|
||||
}
|
||||
|
||||
this.current = step;
|
||||
|
||||
// Execute onStepDidChange hook if defined
|
||||
if (typeof this.flow.onStepDidChange === 'function') {
|
||||
await this.flow.onStepDidChange(this.current, previousStep, this);
|
||||
}
|
||||
}
|
||||
|
||||
async next() {
|
||||
if (!this.flow || !this.current) return;
|
||||
|
||||
const leaving = this.current;
|
||||
|
||||
// afterLeave lifecycle hook
|
||||
if (typeof leaving.afterLeave === 'function') {
|
||||
await leaving.afterLeave(this.onboardingContext);
|
||||
}
|
||||
|
||||
if (!this.history.includes(leaving)) this.history.push(leaving);
|
||||
if (!this.history.includes(leaving)) {
|
||||
this.history.push(leaving);
|
||||
this._persistHistory();
|
||||
}
|
||||
|
||||
// Support both string and function for next property
|
||||
let nextId;
|
||||
if (typeof leaving.next === 'function') {
|
||||
nextId = leaving.next(this.onboardingContext);
|
||||
@@ -53,8 +98,23 @@ export default class OnboardingOrchestratorService extends Service {
|
||||
nextId = leaving.next;
|
||||
}
|
||||
|
||||
// If no next step, flow is complete
|
||||
if (!nextId) {
|
||||
// Execute onFlowWillEnd hook if defined
|
||||
if (typeof this.flow.onFlowWillEnd === 'function') {
|
||||
await this.flow.onFlowWillEnd(leaving, this);
|
||||
}
|
||||
|
||||
this.current = null; // finished
|
||||
|
||||
// Execute onFlowDidEnd hook if defined
|
||||
if (typeof this.flow.onFlowDidEnd === 'function') {
|
||||
await this.flow.onFlowDidEnd(leaving, this);
|
||||
}
|
||||
|
||||
// Clear history from localStorage when flow completes
|
||||
this._clearHistory();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,6 +126,89 @@ export default class OnboardingOrchestratorService extends Service {
|
||||
const prev = this.history[this.history.length - 1];
|
||||
if (prev && prev.allowBack === false) return;
|
||||
this.history = this.history.slice(0, -1);
|
||||
this._persistHistory();
|
||||
await this.goto(prev.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current path (for flows with multiple paths)
|
||||
* This is a helper method that can be used by flows to determine the current path
|
||||
*/
|
||||
getCurrentPath() {
|
||||
if (!this.flow || !this.flow.paths) return null;
|
||||
|
||||
// Determine path based on context or current step
|
||||
for (const [pathId, pathDef] of Object.entries(this.flow.paths)) {
|
||||
if (pathDef.steps && pathDef.steps.some(s => s.id === this.current?.id)) {
|
||||
return pathDef;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a step is in the current path
|
||||
*/
|
||||
isStepInPath(stepId) {
|
||||
const currentPath = this.getCurrentPath();
|
||||
if (!currentPath) return true; // If no paths defined, all steps are valid
|
||||
|
||||
return currentPath.steps?.some(s => s.id === stepId) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist navigation history to localStorage
|
||||
* Stores only step IDs to keep storage lightweight
|
||||
* @private
|
||||
*/
|
||||
_persistHistory() {
|
||||
if (!this.flow) return;
|
||||
|
||||
try {
|
||||
const historyIds = this.history.map(step => step.id);
|
||||
localStorage.setItem(this.historyStorageKey, JSON.stringify(historyIds));
|
||||
} catch (error) {
|
||||
console.warn('[OnboardingOrchestrator] Failed to persist history:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore navigation history from localStorage
|
||||
* Reconstructs step objects from stored IDs
|
||||
* @private
|
||||
*/
|
||||
_restoreHistory() {
|
||||
if (!this.flow) return;
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem(this.historyStorageKey);
|
||||
if (!stored) return;
|
||||
|
||||
const historyIds = JSON.parse(stored);
|
||||
this.history = historyIds
|
||||
.map(id => this.flow.steps.find(s => s.id === id))
|
||||
.filter(Boolean); // Remove any invalid steps
|
||||
|
||||
console.log('[OnboardingOrchestrator] Restored history:', this.history.map(s => s.id));
|
||||
} catch (error) {
|
||||
console.warn('[OnboardingOrchestrator] Failed to restore history:', error);
|
||||
this.history = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear navigation history from localStorage
|
||||
* Called when flow completes or is reset
|
||||
* @private
|
||||
*/
|
||||
_clearHistory() {
|
||||
if (!this.flow) return;
|
||||
|
||||
try {
|
||||
localStorage.removeItem(this.historyStorageKey);
|
||||
} catch (error) {
|
||||
console.warn('[OnboardingOrchestrator] Failed to clear history:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default class OnboardingRegistryService extends Service {
|
||||
this.defaultFlow = flowId;
|
||||
}
|
||||
|
||||
registerFlow(flow) {
|
||||
registerFlow(flow, options = {}) {
|
||||
if (!flow || !flow.id || !flow.entry || !Array.isArray(flow.steps)) {
|
||||
throw new Error('Invalid FlowDef: id, entry, steps are required');
|
||||
}
|
||||
@@ -23,6 +23,11 @@ export default class OnboardingRegistryService extends Service {
|
||||
}
|
||||
}
|
||||
this.flows.set(flow.id, flow);
|
||||
|
||||
// If specified, set as default flow
|
||||
if (options.default) {
|
||||
this.defaultFlow = flow.id;
|
||||
}
|
||||
}
|
||||
|
||||
getFlow(id) {
|
||||
|
||||
@@ -4,21 +4,27 @@
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto">
|
||||
<ContentPanel @title={{t "common.your-profile"}} @open={{true}} @wrapperClass="bordered-classic">
|
||||
<form class="flex flex-col md:flex-row" {{on "submit" (perform this.saveProfile)}}>
|
||||
<form class="flex flex-col items-start md:flex-row" {{on "submit" (perform this.saveProfile)}}>
|
||||
<div class="w-32 flex flex-col justify-center mb-6 mr-6">
|
||||
<Image src={{this.user.avatar_url}} @fallbackSrc={{config "defaultValues.userImage"}} alt={{this.user.name}} class="w-32 h-32 rounded-md" />
|
||||
<FileUpload @name={{t "console.account.index.photos"}} @accept="image/*" @onFileAdded={{this.uploadNewPhoto}} @labelClass="flex flex-row items-center justify-center" as |queue|>
|
||||
<Image src={{this.user.avatar_url}} @fallbackSrc={{config "defaultValues.userImage"}} alt={{this.user.name}} class="w-32 h-32 rounded-md mt-1" />
|
||||
<FileUpload
|
||||
@name={{t "console.account.index.photos"}}
|
||||
@accept="image/*"
|
||||
@onFileAdded={{this.uploadNewPhoto}}
|
||||
@labelClass="flex flex-row items-center justify-center"
|
||||
as |queue|
|
||||
>
|
||||
<a tabindex={{0}} class="flex items-center px-0 mt-2 text-xs no-underline truncate btn btn-sm btn-default" disabled={{queue.files.length}}>
|
||||
{{#if queue.files.length}}
|
||||
<div class="mr-1.5">
|
||||
<Spinner />
|
||||
</div>
|
||||
<span>
|
||||
{{t "common.uploading"}}
|
||||
{{t "common.uploading"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<FaIcon @icon="image" class="mr-1.5" />
|
||||
<span>
|
||||
<span>
|
||||
{{t "console.account.index.upload-new"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
@@ -34,11 +40,31 @@
|
||||
</InputGroup>
|
||||
<InputGroup @name={{t "common.date-of-birth"}} @type="date" @value={{this.user.date_of_birth}} />
|
||||
<InputGroup @name={{t "common.timezone"}} @helpText={{t "console.account.index.timezone"}}>
|
||||
<Select @value={{this.user.timezone}} @options={{this.timezones}} @onSelect={{fn (mut this.user.timezone)}} @placeholder={{t "console.account.index.timezone"}} />
|
||||
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
||||
<PowerSelect
|
||||
@options={{this.timezones}}
|
||||
@selected={{this.user.timezone}}
|
||||
@onChange={{fn (mut this.user.timezone)}}
|
||||
@placeholder={{t "console.account.index.timezone"}}
|
||||
@triggerClass="form-select form-input"
|
||||
@searchEnabled={{true}}
|
||||
as |option|
|
||||
>
|
||||
<div>{{option}}</div>
|
||||
</PowerSelect>
|
||||
</div>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center justify-end">
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-changes"}} @onClick={{perform this.saveProfile}} @isLoading={{not this.saveProfile.isIdle}} />
|
||||
<Button
|
||||
@buttonType="submit"
|
||||
@type="primary"
|
||||
@size="lg"
|
||||
@icon="save"
|
||||
@text={{t "common.save-changes"}}
|
||||
@onClick={{perform this.saveProfile}}
|
||||
@isLoading={{not this.saveProfile.isIdle}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{{page-title @model.title}}
|
||||
<Layout::Section::Header @title={{@model.title}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-">
|
||||
<LazyEngineComponent @component={{@model.component}} @params={{@model.componentParams}} />
|
||||
<Layout::Section::Body class={{this.bodyClass}}>
|
||||
<div class={{this.containerClass}}>
|
||||
<div class={{this.wrapperClass}}>
|
||||
{{component (lazy-engine-component @model.component) params=@model.componentParams controller=this}}
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
@selected={{get this.notificationSettings (concat (get-notification-key notification.definition notification.name) ".notifiables")}}
|
||||
@onChange={{fn this.onSelectNotifiable notification}}
|
||||
@placeholder="Select notifiables..."
|
||||
@triggerClass="form-select form-input form-input-sm flex-1"
|
||||
@triggerClass="form-select form-input flex-1"
|
||||
as |notifiable|
|
||||
>
|
||||
{{notifiable.label}}
|
||||
@@ -27,6 +27,21 @@
|
||||
{{/each}}
|
||||
</ContentPanel>
|
||||
{{/each-in}}
|
||||
|
||||
<ContentPanel @title="SMS Notification Settings" @open={{true}} @wrapperClass="bordered-classic">
|
||||
<Toggle @isToggled={{this.company.options.alpha_numeric_sender_id_enabled}} @onToggle={{this.toggleAlphaNumericSenderId}} @label="Enable Alpha-Numeric Sender ID" @wrapperClass="mb-4" />
|
||||
<InputGroup @name="Alpha-Numeric Sender ID" @value={{this.company.options.alpha_numeric_sender_id}} @helpText="Set the custom alphanumeric name that will appear as the sender for all SMS sent by your organization. Up to 11 letters or numbers. Not supported in all countries." @disabled={{not this.company.options.alpha_numeric_sender_id_enabled}} />
|
||||
<div class="space-y-2 mb-3">
|
||||
<InfoBlock>
|
||||
<p>Alphanumeric Sender IDs allow your organization to replace a traditional phone number with a custom text-based sender name when sending SMS notifications (e.g., Fleetbase, MyStore, DispatchHQ). This can improve brand recognition, increase message trust, and enhance deliverability in regions where numeric senders are restricted by local carriers.</p>
|
||||
<p>When enabled, Fleetbase will use this sender ID for all outbound SMS messages sent on behalf of your organization, including order updates, verification codes, driver notifications, and other automated alerts. Sender IDs can contain up to 11 characters using letters and numbers.</p>
|
||||
<p>Some countries require or enforce specific messaging rules, and certain carriers may only support alphanumeric senders. Using a Sender ID can significantly improve message delivery in these regions.</p>
|
||||
</InfoBlock>
|
||||
<InfoBlock @type="warning">
|
||||
<p>Delivery of SMS using Alphanumeric Sender IDs depends on local carrier policies. Some regions may restrict or block numeric senders or require the use of alphanumeric senders for successful delivery. While Fleetbase will attempt to deliver all messages using your configured Sender ID, message delivery cannot be guaranteed in countries with carrier-level filtering or regulatory restrictions. Your organization is responsible for ensuring compliance with local messaging regulations in the countries you send SMS to.</p>
|
||||
</InfoBlock>
|
||||
</div>
|
||||
</ContentPanel>
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
<div class="flex items-center justify-center h-screen min-h-screen px-4 py-12 bg-gray-50 dark:bg-gray-900 sm:px-6 lg:px-8 overflow-y-scroll">
|
||||
<div class="w-full max-w-md h-screen flex items-center justify-center py-4">
|
||||
{{outlet}}
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
<div class="onboard-route-wrapper">
|
||||
{{outlet}}
|
||||
</div>
|
||||
@@ -79,6 +79,12 @@ function getCachedConfig() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Application version has changed
|
||||
if (cachedVersion !== config.APP.version) {
|
||||
debug(`[Runtime Config] Version mismatch (cached: ${cachedVersion}, current: ${config.APP.version})`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const cacheData = JSON.parse(cached);
|
||||
const cacheAge = Date.now() - cacheData.timestamp;
|
||||
|
||||
@@ -101,14 +107,14 @@ function getCachedConfig() {
|
||||
*
|
||||
* @param {Object} config Config object
|
||||
*/
|
||||
function setCachedConfig(config) {
|
||||
function setCachedConfig(runtimeConfig) {
|
||||
try {
|
||||
const cacheData = {
|
||||
config,
|
||||
config: runtimeConfig,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData));
|
||||
localStorage.setItem(CACHE_VERSION_KEY, '1');
|
||||
localStorage.setItem(CACHE_VERSION_KEY, config.APP.version);
|
||||
debug('[Runtime Config] Config cached to localStorage');
|
||||
} catch (e) {
|
||||
debug(`[Runtime Config] Failed to cache config: ${e.message}`);
|
||||
@@ -147,12 +153,15 @@ export default async function loadRuntimeConfig() {
|
||||
return;
|
||||
}
|
||||
|
||||
// // Try cache first
|
||||
// const cachedConfig = getCachedConfig();
|
||||
// if (cachedConfig) {
|
||||
// applyRuntimeConfig(cachedConfig);
|
||||
// return;
|
||||
// }
|
||||
const isProduction = config?.environment === 'production';
|
||||
if (isProduction) {
|
||||
// Try cache first
|
||||
const cachedConfig = getCachedConfig();
|
||||
if (cachedConfig) {
|
||||
applyRuntimeConfig(cachedConfig);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss - fetch from server
|
||||
try {
|
||||
|
||||
@@ -23,7 +23,7 @@ module.exports = function (environment) {
|
||||
APP: {
|
||||
autoboot: true,
|
||||
extensions: asArray(getenv('EXTENSIONS')),
|
||||
disableRuntimeConfig: toBoolean(getenv('DISABLE_RUNTIME_CONFIG')),
|
||||
disableRuntimeConfig: toBoolean(getenv('DISABLE_RUNTIME_CONFIG', environment === 'production')),
|
||||
},
|
||||
|
||||
API: {
|
||||
|
||||
@@ -5,16 +5,7 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app');
|
||||
const Funnel = require('broccoli-funnel');
|
||||
const writeFile = require('broccoli-file-creator');
|
||||
const mergeTrees = require('broccoli-merge-trees');
|
||||
const postcssImport = require('postcss-import');
|
||||
const postcssPresetEnv = require('postcss-preset-env');
|
||||
const postcssEach = require('postcss-each');
|
||||
const postcssMixins = require('postcss-mixins');
|
||||
const postcssConditionals = require('postcss-conditionals-renewed');
|
||||
const postcssAtRulesVariables = require('postcss-at-rules-variables');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const tailwind = require('tailwindcss');
|
||||
const toBoolean = require('./config/utils/to-boolean');
|
||||
const environment = process.env.EMBER_ENV;
|
||||
|
||||
module.exports = function (defaults) {
|
||||
const app = new EmberApp(defaults, {
|
||||
@@ -38,29 +29,6 @@ module.exports = function (defaults) {
|
||||
useSessionSetupMethod: true,
|
||||
},
|
||||
|
||||
postcssOptions: {
|
||||
compile: {
|
||||
enabled: true,
|
||||
cacheInclude: [/.*\.(css|scss|hbs)$/, /.*\/tailwind\/config\.js$/, /.*tailwind\.js$/],
|
||||
plugins: [
|
||||
postcssAtRulesVariables,
|
||||
postcssImport({
|
||||
path: ['node_modules'],
|
||||
plugins: [postcssAtRulesVariables, postcssImport],
|
||||
}),
|
||||
postcssMixins,
|
||||
postcssPresetEnv({ stage: 1 }),
|
||||
postcssEach,
|
||||
tailwind('./tailwind.config.js'),
|
||||
autoprefixer,
|
||||
],
|
||||
},
|
||||
filter: {
|
||||
enabled: true,
|
||||
plugins: [postcssAtRulesVariables, postcssMixins, postcssEach, postcssConditionals, tailwind('./tailwind.config.js')],
|
||||
},
|
||||
},
|
||||
|
||||
babel: {
|
||||
plugins: [require.resolve('ember-auto-import/babel-plugin')],
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
API_HOST=
|
||||
API_HOST=https://api.fleetbase.io
|
||||
API_NAMESPACE=int/v1
|
||||
API_SECURE=true
|
||||
SOCKETCLUSTER_PATH=/socketcluster/
|
||||
SOCKETCLUSTER_HOST=
|
||||
SOCKETCLUSTER_HOST=socket.fleetbase.io
|
||||
SOCKETCLUSTER_SECURE=true
|
||||
SOCKETCLUSTER_PORT=38000
|
||||
OSRM_HOST=https://router.project-osrm.org
|
||||
SOCKETCLUSTER_PORT=8000
|
||||
OSRM_HOST=https://router.project-osrm.org
|
||||
DISABLE_RUNTIME_CONFIG=true
|
||||
9
console/environments/.env.qa
Normal file
9
console/environments/.env.qa
Normal file
@@ -0,0 +1,9 @@
|
||||
API_HOST=https://api.qa.fleetbase.io
|
||||
API_NAMESPACE=int/v1
|
||||
API_SECURE=true
|
||||
SOCKETCLUSTER_PATH=/socketcluster/
|
||||
SOCKETCLUSTER_HOST=socket.qa.fleetbase.io
|
||||
SOCKETCLUSTER_SECURE=true
|
||||
SOCKETCLUSTER_PORT=8000
|
||||
OSRM_HOST=https://router.project-osrm.org
|
||||
DISABLE_RUNTIME_CONFIG=true
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fleetbase/console",
|
||||
"version": "0.7.20",
|
||||
"version": "0.7.28",
|
||||
"private": true,
|
||||
"description": "Modular logistics and supply chain operating system (LSOS)",
|
||||
"repository": "https://github.com/fleetbase/fleetbase",
|
||||
@@ -33,15 +33,24 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ember/legacy-built-in-components": "^0.4.2",
|
||||
"@fleetbase/dev-engine": "^0.2.11",
|
||||
"@fleetbase/ember-core": "^0.3.7",
|
||||
"@fleetbase/ember-ui": "^0.3.12",
|
||||
"@fleetbase/fleetops-data": "^0.1.23",
|
||||
"@fleetbase/fleetops-engine": "^0.6.28",
|
||||
"@fleetbase/iam-engine": "^0.1.5",
|
||||
"@fleetbase/aws-marketplace": "^0.0.8",
|
||||
"@fleetbase/billing-engine": "^0.1.20",
|
||||
"@fleetbase/customer-portal-engine": "^0.0.10",
|
||||
"@fleetbase/dev-engine": "^0.2.12",
|
||||
"@fleetbase/ember-core": "^0.3.10",
|
||||
"@fleetbase/ember-ui": "^0.3.18",
|
||||
"@fleetbase/fleetops-data": "^0.1.25",
|
||||
"@fleetbase/fleetops-engine": "^0.6.35",
|
||||
"@fleetbase/flespi-engine": "^0.1.16",
|
||||
"@fleetbase/iam-engine": "^0.1.6",
|
||||
"@fleetbase/internals-engine": "^0.0.27",
|
||||
"@fleetbase/leaflet-routing-machine": "^3.2.17",
|
||||
"@fleetbase/registry-bridge-engine": "^0.1.1",
|
||||
"@fleetbase/storefront-engine": "^0.4.8",
|
||||
"@fleetbase/registry-bridge-engine": "^0.1.5",
|
||||
"@fleetbase/samsara-engine": "^0.0.3",
|
||||
"@fleetbase/solid-engine": "^0.0.7",
|
||||
"@fleetbase/storefront-engine": "^0.4.13",
|
||||
"@fleetbase/valhalla-engine": "^0.0.3",
|
||||
"@fleetbase/vroom-engine": "^0.0.3",
|
||||
"@formatjs/intl-datetimeformat": "^6.18.2",
|
||||
"@formatjs/intl-numberformat": "^8.15.6",
|
||||
"@formatjs/intl-pluralrules": "^5.4.6",
|
||||
@@ -82,6 +91,8 @@
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"broccoli-file-creator": "^2.1.1",
|
||||
"broccoli-funnel": "^3.0.8",
|
||||
"broccoli-merge-trees": "^4.2.0",
|
||||
"chokidar": "4.0.3",
|
||||
"concurrently": "^8.2.2",
|
||||
"date-fns": "^2.30.0",
|
||||
"dragula": "^3.7.3",
|
||||
@@ -91,6 +102,7 @@
|
||||
"ember-cli-babel": "^8.2.0",
|
||||
"ember-cli-clean-css": "^3.0.0",
|
||||
"ember-cli-dependency-checker": "^3.3.2",
|
||||
"ember-cli-deprecation-workflow": "^4.0.0",
|
||||
"ember-cli-dotenv": "^3.1.0",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
"ember-cli-inject-live-reload": "^2.1.0",
|
||||
@@ -147,9 +159,9 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@fleetbase/ember-core": "latest",
|
||||
"@fleetbase/ember-ui": "latest",
|
||||
"@fleetbase/fleetops-data": "latest"
|
||||
"@fleetbase/ember-core": "^0.3.10",
|
||||
"@fleetbase/ember-ui": "^0.3.18",
|
||||
"@fleetbase/fleetops-data": "^0.1.25"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
@@ -166,5 +178,6 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903"
|
||||
}
|
||||
|
||||
4068
console/pnpm-lock.yaml
generated
4068
console/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,6 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon/android-chrome-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"name": "Fleetbase Console",
|
||||
"short_name": "Fleetbase",
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | dashboard/widget-panel', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`<Dashboard::WidgetPanel />`);
|
||||
|
||||
assert.dom().hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<Dashboard::WidgetPanel>
|
||||
template block text
|
||||
</Dashboard::WidgetPanel>
|
||||
`);
|
||||
|
||||
assert.dom().hasText('template block text');
|
||||
});
|
||||
});
|
||||
@@ -346,6 +346,7 @@ common:
|
||||
{resource} ({resourceName}) deleted.
|
||||
continue-without-saving: Continue Without Saving?
|
||||
continue-without-saving-prompt: You have unsaved changes to this {resource}. Continuing will discard them. Click Continue to proceed.
|
||||
changelog: Changelog
|
||||
|
||||
resource:
|
||||
alert: Alert
|
||||
|
||||
777
console/translations/uz-uz.yaml
Normal file
777
console/translations/uz-uz.yaml
Normal file
@@ -0,0 +1,777 @@
|
||||
app:
|
||||
name: Fleetbase
|
||||
|
||||
common:
|
||||
new: Yangi
|
||||
create: Yaratish
|
||||
add: Qo'shish
|
||||
edit: Tahrirlash
|
||||
update: Yangilash
|
||||
save: Saqlash
|
||||
save-changes: O'zgarishlarni saqlash
|
||||
delete: O'chirish
|
||||
delete-selected: Tanlanganlarni o'chirish
|
||||
delete-selected-count: "{count} ta tanlanganni o'chirish"
|
||||
your-profile: Sizning profilingiz
|
||||
date-of-birth: Tug'ilgan sana
|
||||
organization: Tashkilot
|
||||
two-factor: Ikki faktorli
|
||||
remove: O'chirish
|
||||
cancel: Bekor qilish
|
||||
confirm: Tasdiqlash
|
||||
close: Yopish
|
||||
open: Ochish
|
||||
view: Ko'rish
|
||||
preview: Ko'rib chiqish
|
||||
upload: Yuklash
|
||||
download: Yuklab olish
|
||||
import: Import qilish
|
||||
export: Eksport qilish
|
||||
print: Chop etish
|
||||
duplicate: Nusxalash
|
||||
copy: Nusxa olish
|
||||
paste: Joylashtirish
|
||||
share: Ulashish
|
||||
refresh: Yangilash
|
||||
reset: Qayta o'rnatish
|
||||
retry: Qayta urinish
|
||||
back: Orqaga
|
||||
next: Keyingisi
|
||||
previous: Oldingisi
|
||||
submit: Yuborish
|
||||
apply: Qo'llash
|
||||
continue: Davom etish
|
||||
proceed: Davom etish
|
||||
select: Tanlash
|
||||
deselect: Tanlovni bekor qilish
|
||||
search: Qidirish
|
||||
filter: Filtr
|
||||
sort: Saralash
|
||||
view-all: Hammasini ko'rish
|
||||
clear: Tozalash
|
||||
done: Bajarildi
|
||||
finish: Tugatish
|
||||
skip: O'tkazib yuborish
|
||||
method: Usul
|
||||
bulk-delete: Ommaviy o'chirish
|
||||
bulk-delete-resource: "{resource} ni ommaviy o'chirish"
|
||||
bulk-cancel: Ommaviy bekor qilish
|
||||
bulk-cancel-resource: "{resource} ni ommaviy bekor qilish"
|
||||
bulk-actions: Ommaviy amallar
|
||||
column: Ustun
|
||||
row: Qator
|
||||
table: Jadval
|
||||
list: Ro'yxat
|
||||
grid: Setka
|
||||
form: Shakl
|
||||
field: Maydon
|
||||
section: Bo'lim
|
||||
panel: Panel
|
||||
card: Karta
|
||||
tab: Varaqa
|
||||
modal: Modal
|
||||
dialog: Dialog
|
||||
menu: Menyu
|
||||
dropdown: Ochiladigan menyu
|
||||
tooltip: Maslahat
|
||||
sidebar: Yon panel
|
||||
toolbar: Asboblar paneli
|
||||
footer: Pastki qism
|
||||
header: Yuqori qism
|
||||
title: Sarlavha
|
||||
subtitle: Kichik sarlavha
|
||||
description: Tavsif
|
||||
placeholder: Joy egallovchi
|
||||
label: Yorliq
|
||||
button: Tugma
|
||||
icon: Belgi
|
||||
avatar: Avatar
|
||||
link: Havola
|
||||
badge: Nishon
|
||||
tag: Teg
|
||||
banner: Banner
|
||||
step: Qadam
|
||||
progress: Jarayon
|
||||
map: Xarita
|
||||
board: Doska
|
||||
loading: Yuklanmoqda
|
||||
loading-resource: "{resource} yuklanmoqda"
|
||||
saving: Saqlanmoqda
|
||||
processing: Ishlanmoqda
|
||||
fetching: Olinmoqda
|
||||
updating: Yangilanmoqda
|
||||
uploading: Yuklanmoqda
|
||||
completed: Bajarildi
|
||||
success: Muvaffaqiyatli
|
||||
failed: Muvaffaqiyatsiz
|
||||
error: Xato
|
||||
warning: Ogohlantirish
|
||||
info: Ma'lumot
|
||||
ready: Tayyor
|
||||
activity: Faoliyat
|
||||
active: Faol
|
||||
inactive: Nofaol
|
||||
enabled: Yoqilgan
|
||||
disabled: O'chirilgan
|
||||
online: Onlayn
|
||||
offline: Oflayn
|
||||
pending: Kutishda
|
||||
archived: Arxivlangan
|
||||
hidden: Yashirin
|
||||
visible: Ko'rinadigan
|
||||
empty: Bo'sh
|
||||
not-found: Topilmadi
|
||||
no-results: Natijalar yo'q
|
||||
try-again: Qayta urinib ko'ring
|
||||
are-you-sure: Ishonchingiz komilmi?
|
||||
changes-saved: O'zgarishlar muvaffaqiyatli saqlandi.
|
||||
saved-successfully: O'zgarishlar muvaffaqiyatli saqlandi.
|
||||
field-saved: >-
|
||||
{field} muvaffaqiyatli saqlandi.
|
||||
changes-discarded: O'zgarishlar bekor qilindi.
|
||||
delete-confirm: Haqiqatan ham bu elementni oʻchirib tashlamoqchimisiz?
|
||||
action-successful: Amal muvaffaqiyatli bajarildi.
|
||||
action-failed: Amal bajarilmadi. Iltimos, qaytadan urining.
|
||||
something-went-wrong: Nimadir noto'g'ri ketdi.
|
||||
please-wait: Iltimos kuting...
|
||||
sign-in: Kirish
|
||||
sign-out: Chiqish
|
||||
sign-up: Ro'yxatdan o'tish
|
||||
log-in: Kirish
|
||||
log-out: Chiqish
|
||||
register: Ro'yxatdan o'tish
|
||||
forgot-password: Parolni unutdingizmi
|
||||
reset-password: Parolni tiklash
|
||||
change-password: Parolni o'zgartirish
|
||||
password: Parol
|
||||
confirm-password: Parolni tasdiqlang
|
||||
email: Elektron pochta
|
||||
username: Foydalanuvchi nomi
|
||||
remember-me: Meni eslab qol
|
||||
welcome: Xush kelibsiz
|
||||
welcome-back: Xush kelibsiz
|
||||
profile: Profil
|
||||
account: Hisob
|
||||
settings: Sozlamalar
|
||||
preferences: Afzalliklar
|
||||
record: Yozuv
|
||||
records: Yozuvlar
|
||||
item: Element
|
||||
items: Elementlar
|
||||
entry: Yozuv
|
||||
entries: Yozuvlar
|
||||
id: ID
|
||||
name: Ism
|
||||
type: Tur
|
||||
category: Kategoriya
|
||||
overview: Umumiy koʻrinish
|
||||
value: Qiymat
|
||||
amount: Miqdor
|
||||
price: Narx
|
||||
quantity: Miqdori
|
||||
status: Holat
|
||||
date: Sana
|
||||
date-created: Yaratilgan sana
|
||||
date-updated: Yangilangan sana
|
||||
time: Vaqt
|
||||
created-at: Yaratilgan vaqti
|
||||
updated-at: Yangilangan vaqti
|
||||
expired-at: Yaroqlilik muddati
|
||||
last-seen-at: Oxirgi marta ko'rilgan
|
||||
last-modified: Oxirgi o'zgartirish
|
||||
last-modified-data: >-
|
||||
Oxirgi o'zgartirish: {date}
|
||||
actions: Amallar
|
||||
details: Tafsilotlar
|
||||
notes: Eslatmalar
|
||||
reference: Malumot
|
||||
filter-by: Filtr bo'yicha
|
||||
filter-by-field: "{field} bo'yicha filtr"
|
||||
sort-by: Saralash
|
||||
ascending: O'sish tartibida
|
||||
descending: Kamayish tartibida
|
||||
all: Hammasi
|
||||
none: Hech qanday
|
||||
select-all: Hammasini tanlash
|
||||
deselect-all: Barchasini bekor qilish
|
||||
show-more: Ko'proq ko'rsatish
|
||||
show-less: Kamroq ko'rsatish
|
||||
page: Sahifa
|
||||
of: ning
|
||||
total: Jami
|
||||
items-per-page: Har bir sahifadagi elementlar
|
||||
showing: Ko'rsatilmoqda
|
||||
to: gacha
|
||||
results: Natijalar
|
||||
load-more: Ko'proq yuklash
|
||||
no-more-results: Boshqa natijalar yo'q
|
||||
today: Bugun
|
||||
yesterday: Kecha
|
||||
tomorrow: Ertaga
|
||||
day: Kun
|
||||
week: Hafta
|
||||
month: Oy
|
||||
year: Yil
|
||||
date-range: Sana oralig'i
|
||||
start-date: Boshlanish sanasi
|
||||
end-date: Tugash sanasi
|
||||
time-zone: Vaqt mintaqasi
|
||||
system: Tizim
|
||||
dashboard: Boshqaruv paneli
|
||||
home: Bosh sahifa
|
||||
analytics: Analitika
|
||||
reports: Hisobotlar
|
||||
logs: Jurnallar
|
||||
help: Yordam
|
||||
support: Qo'llab-quvvatlash
|
||||
contact: Aloqa
|
||||
documentation: Hujjatlar
|
||||
language: Til
|
||||
timezone: Vaqt mintaqasi
|
||||
version: Versiya
|
||||
theme: Mavzu
|
||||
light-mode: Yorug'lik rejimi
|
||||
dark-mode: Qorong'u rejim
|
||||
update-available: Yangilanish mavjud
|
||||
install-update: Yangilanishni o'rnatish
|
||||
maintenance-mode: Texnik xizmat ko'rsatish rejimi
|
||||
notification: Bildirishnoma
|
||||
notifications: Bildirishnomalar
|
||||
mark-as-read: O'qilgan deb belgilash
|
||||
mark-all-as-read: Hammasini o'qilgan deb belgilash
|
||||
clear-notifications: Bildirishnomalarni tozalash
|
||||
company: Kompaniya
|
||||
companies: Kompaniyalar
|
||||
user: Foydalanuvchi
|
||||
users: Foydalanuvchilar
|
||||
role: Rol
|
||||
roles: Rollar
|
||||
permission: Ruxsat
|
||||
permissions: Ruxsatlar
|
||||
group: Guruh
|
||||
groups: Guruhlar
|
||||
unauthorized: Ruxsatsiz
|
||||
forbidden: Taqiqlandi
|
||||
resource-not-found: Resurs topilmadi
|
||||
server-error: Server xatosi
|
||||
validation-error: Tasdiqlash xatosi
|
||||
timeout-error: So'rov vaqti tugadi
|
||||
network-error: Tarmoq xatosi
|
||||
unknown-error: Noma'lum xato
|
||||
file: Fayl
|
||||
files: Fayllar
|
||||
folder: Papka
|
||||
folders: Papkalar
|
||||
upload-file: Fayl yuklash
|
||||
upload-files: Fayllarni yuklash
|
||||
upload-image: Rasm yuklash
|
||||
upload-image-supported: PNG, JPEG va GIF-larni qo'llab-quvvatlaydi
|
||||
choose-file: Faylni tanlang
|
||||
choose-files: Fayllarni tanlang
|
||||
drag-and-drop: Tortib tashlang
|
||||
download-file: Faylni yuklab olish
|
||||
file-size: Fayl hajmi
|
||||
file-type: Fayl turi
|
||||
confirm-delete: O'chirishni tasdiqlang
|
||||
confirm-action: Amalni tasdiqlang
|
||||
confirm-exit: Chiqishni tasdiqlang
|
||||
confirm-and-save-changes: Tasdiqlang va o'zgarishlarni saqlang
|
||||
are-you-sure-exit: Haqiqatan ham chiqmoqchimisiz?
|
||||
unsaved-changes-warning: Sizda saqlanmagan o'zgarishlar mavjud.
|
||||
connected: Ulangan
|
||||
disconnected: Ulanmagan
|
||||
reconnecting: Qayta ulanmoqda
|
||||
connection-lost: Ulanish uzildi
|
||||
connection-restored: Ulanish tiklandi
|
||||
show: Ko'rsatish
|
||||
hide: Yashirish
|
||||
expand: Kengaytirish
|
||||
collapse: Yig'ish
|
||||
enable: Yoqish
|
||||
disable: O'chirish
|
||||
minimize: Kichraytirish
|
||||
maximize: Kattalashtirish
|
||||
restore: Qayta tiklash
|
||||
zoom-in: Kattalashtirish
|
||||
zoom-out: Kichraytirish
|
||||
fullscreen: To'liq ekran
|
||||
exit-fullscreen: To'liq ekrandan chiqish
|
||||
yes: Ha
|
||||
no: Yo'q
|
||||
ok: OK
|
||||
none-available: Mavjud emas
|
||||
default: Standart
|
||||
custom: Maxsus
|
||||
general: Umumiy
|
||||
advanced: Kengaytirilgan
|
||||
placeholder-text: Bu yerga matn kiriting...
|
||||
learn-more: Ko'proq bilish
|
||||
view-resource: "{resource} ni ko'rish"
|
||||
view-resource-details: "{resource} tafsilotlarini ko'rish"
|
||||
create-a-new-resource: "Yangi {resource} yaratish"
|
||||
create-new-resource: "Yangi {resource} yaratish"
|
||||
search-resource: "{resource} ni qidirish"
|
||||
new-resource: "Yangi {resource}"
|
||||
update-resource: "{resource} ni yangilash"
|
||||
save-resource-changes: "{resource} o'zgarishlarini saqlash"
|
||||
creating-resource: "{resource} yaratilmoqda"
|
||||
cancel-resource: "{resource} ni bekor qilish"
|
||||
delete-resource: "{resource} ni o'chirish"
|
||||
delete-resource-name: >-
|
||||
O'chirish: {resourceName}
|
||||
delete-resource-named: "{resource} ({resourceName}) ni o'chirish"
|
||||
delete-resource-prompt: Bu amalni bekor qilib bo'lmaydi. O'chirilgandan so'ng, yozuv butunlay o'chiriladi.
|
||||
delete-cannot-be-undone: Bu amalni bekor qilib bo'lmaydi. O'chirilgandan so'ng, yozuv butunlay o'chiriladi.
|
||||
create-resource: "{resource} yaratish"
|
||||
edit-resource: "{resource} ni tahrirlash"
|
||||
edit-resource-details: "{resource} tafsilotlarini tahrirlash"
|
||||
edit-resource-type-name: >-
|
||||
Tahrirlash {resource}: {resourceName}
|
||||
edit-resource-name: >-
|
||||
Tahrirlash: {resourceName}
|
||||
config: Konfiguratsiya
|
||||
select-field: "{field} ni tanlang"
|
||||
columns: Ustunlar
|
||||
metadata: Metama'lumotlar
|
||||
meta: Meta
|
||||
resource-created-success: "Yangi {resource} muvaffaqiyatli yaratildi."
|
||||
resource-created-success-name: "Yangi {resource} ({resourceName}) muvaffaqiyatli yaratildi."
|
||||
resource-updated-success: >-
|
||||
{resource} ({resourceName}) muvaffaqiyatli yangilandi.
|
||||
resource-action-success: >-
|
||||
{resource} ({resourceName}) {action} muvaffaqiyatli bajarildi.
|
||||
resource-deleted-success: >-
|
||||
{resource} ({resourceName}) muvaffaqiyatli o'chirildi.
|
||||
resource-deleted: >-
|
||||
{resource} ({resourceName}) o'chirildi.
|
||||
continue-without-saving: Saqlamasdan davom etish kerakmi?
|
||||
continue-without-saving-prompt: Sizda bu {resource} ga saqlanmagan o'zgarishlar mavjud. Davom etish ularni bekor qiladi. Davom etish uchun Davom etish tugmasini bosing.
|
||||
changelog: O'zgarishlar jurnali
|
||||
|
||||
resource:
|
||||
alert: Ogohlantirish
|
||||
alerts: Ogohlantirishlar
|
||||
brand: Brend
|
||||
brands: Brendlar
|
||||
category: Kategoriya
|
||||
categories: Kategoriyalar
|
||||
chat-attachment: Chat ilovasi
|
||||
chat-attachments: Chat ilovalari
|
||||
chat-channel: Chat kanali
|
||||
chat-channels: Chat kanallari
|
||||
chat-log: Chat jurnali
|
||||
chat-logs: Chat jurnallari
|
||||
chat-message: Chat xabari
|
||||
chat-messages: Chat xabarlari
|
||||
chat-participant: Chat ishtirokchisi
|
||||
chat-participants: Chat ishtirokchilari
|
||||
chat-receipt: Chat kvitansiyasi
|
||||
chat-receipts: Chat kvitansiyalari
|
||||
comment: Izoh
|
||||
comments: Izohlar
|
||||
company: Kompaniya
|
||||
companies: Kompaniyalar
|
||||
custom-field-value: Maxsus maydon qiymati
|
||||
custom-field-values: Maxsus maydon qiymatlari
|
||||
custom-field: Maxsus maydon
|
||||
custom-fields: Maxsus maydonlar
|
||||
dashboard-widget: Boshqaruv paneli vidjeti
|
||||
dashboard-widgets: Boshqaruv paneli vidjetlari
|
||||
dashboard: Boshqaruv paneli
|
||||
dashboards: Boshqaruv panellari
|
||||
extension: Kengaytma
|
||||
extensions: Kengaytmalar
|
||||
file: Fayl
|
||||
files: Fayllar
|
||||
group: Guruh
|
||||
groups: Guruhlar
|
||||
notification: Bildirishnoma
|
||||
notifications: Bildirishnomalar
|
||||
permission: Ruxsat
|
||||
permissions: Ruxsatlar
|
||||
policy: Siyosat
|
||||
policies: Siyosatlar
|
||||
report: Hisobot
|
||||
reports: Hisobotlar
|
||||
role: Rol
|
||||
roles: Rollar
|
||||
setting: Sozlama
|
||||
settings: Sozlamalar
|
||||
transaction: Tranzaksiya
|
||||
transactions: Tranzaksiyalar
|
||||
user-device: Foydalanuvchi qurilmasi
|
||||
user-devices: Foydalanuvchi qurilmalari
|
||||
user: Foydalanuvchi
|
||||
users: Foydalanuvchilar
|
||||
|
||||
dropzone:
|
||||
file: fayl
|
||||
drop-to-upload: Yuklash uchun tashlang
|
||||
invalid: Noto'g'ri
|
||||
files-ready-for-upload: >-
|
||||
{numOfFiles} ta fayl yuklashga tayyor.
|
||||
upload-images-videos: Rasmlar va videolarni yuklash
|
||||
upload-documents: Hujjatlarni yuklash
|
||||
upload-documents-files: Hujjatlar va fayllarni yuklash
|
||||
upload-avatar-files: Maxsus avatarlarni yuklash
|
||||
dropzone-supported-images-videos: Rasm va video fayllarni bu yerga tortib tashlang
|
||||
dropzone-supported-avatars: SVG yoki PNG fayllarini tortib tashlang
|
||||
dropzone-supported-files: Fayllarni bu yerga tortib tashlang
|
||||
or-select-button-text: yoki yuklash uchun fayllarni tanlang.
|
||||
upload-queue: Yuklash navbati
|
||||
uploading: Yuklanmoqda...
|
||||
|
||||
two-fa-enforcement-alert:
|
||||
message: Hisobingiz xavfsizligini oshirish uchun tashkilotingiz Ikki Faktorli Autentifikatsiyani (2FA) talab qiladi. Qo'shimcha himoya qatlami uchun hisob sozlamalaringizda 2FA-ni yoqing.
|
||||
button-text: 2FA-ni sozlash
|
||||
|
||||
comment-thread:
|
||||
publish-comment-button-text: Izohni chop etish
|
||||
publish-reply-button-text: Javobni chop etish
|
||||
reply-comment-button-text: Javob berish
|
||||
edit-comment-button-text: Tahrirlash
|
||||
delete-comment-button-text: O'chirish
|
||||
comment-published-ago: >-
|
||||
{createdAgo} oldin
|
||||
comment-input-placeholder: Yangi izoh kiriting...
|
||||
comment-reply-placeholder: Javobingizni kiriting...
|
||||
comment-input-empty-notification: Bo'sh izohlarni chop eta olmaysiz...
|
||||
comment-min-length-notification: Izoh kamida 2 ta belgidan iborat bo'lishi kerak
|
||||
|
||||
dashboard:
|
||||
select-dashboard: Boshqaruv panelini tanlang
|
||||
create-new-dashboard: Yangi boshqaruv paneli yaratish
|
||||
create-a-new-dashboard: Yangi boshqaruv paneli yaratish
|
||||
confirm-create-dashboard: Boshqaruv panelini yaratish!
|
||||
edit-layout: Tartibni tahrirlash
|
||||
add-widgets: Vidjetlarni qo'shish
|
||||
delete-dashboard: Boshqaruv panelini o'chirish
|
||||
save-dashboard: Boshqaruv panelini saqlash
|
||||
you-cannot-delete-this-dashboard: Siz bu boshqaruv panelini o'chira olmaysiz.
|
||||
are-you-sure-you-want-delete-dashboard: Haqiqatan ham bu {dashboardName} ni o'chirmoqchimisiz?
|
||||
dashboard-widget-panel:
|
||||
widget-name: >-
|
||||
{widgetName} vidjeti
|
||||
select-widgets: Vidjetlarni tanlang
|
||||
close-and-save: Yopish va saqlash
|
||||
filter-widgets: Vidjetlarni kalit so'z bo'yicha filtrlash
|
||||
|
||||
filters-picker:
|
||||
filters: Filtrlar
|
||||
filter-data: Ma'lumotlarni filtrlash
|
||||
|
||||
visible-column-picker:
|
||||
select-viewable-columns: Ko'rinadigan ustunlarni tanlang
|
||||
customize-columns: Ustunlarni moslashtirish
|
||||
|
||||
component:
|
||||
file:
|
||||
dropdown-label: Fayl amallari
|
||||
import-modal:
|
||||
loading-message: Import qayta ishlanmoqda...
|
||||
drop-upload: Yuklash uchun tashlang
|
||||
invalid: Noto'g'ri
|
||||
ready-upload: yuklashga tayyor.
|
||||
upload-spreadsheets: Jadvallarni yuklash
|
||||
drag-drop: Jadval fayllarini bu yerga tortib tashlang
|
||||
button-text: yoki yuklash uchun jadvallarni tanlang
|
||||
spreadsheets: jadvallar
|
||||
upload-queue: Yuklash navbati
|
||||
dropzone:
|
||||
file: fayl
|
||||
drop-to-upload: Yuklash uchun tashlang
|
||||
invalid: Noto'g'ri
|
||||
files-ready-for-upload: >-
|
||||
{numOfFiles} ta fayl yuklashga tayyor.
|
||||
upload-images-videos: Rasmlar va videolarni yuklash
|
||||
upload-documents: Hujjatlarni yuklash
|
||||
upload-documents-files: Hujjatlar va fayllarni yuklash
|
||||
upload-avatar-files: Maxsus avatarlarni yuklash
|
||||
dropzone-supported-images-videos: Rasm va video fayllarni bu yerga tortib tashlang
|
||||
dropzone-supported-avatars: SVG yoki PNG fayllarini tortib tashlang
|
||||
dropzone-supported-files: Fayllarni bu yerga tortib tashlang
|
||||
or-select-button-text: yoki yuklash uchun fayllarni tanlang.
|
||||
upload-queue: Yuklash navbati
|
||||
uploading: Yuklanmoqda...
|
||||
two-fa-enforcement-alert:
|
||||
message: Hisobingiz xavfsizligini oshirish uchun tashkilotingiz Ikki Faktorli Autentifikatsiyani (2FA) talab qiladi. Qo'shimcha himoya qatlami uchun hisob sozlamalaringizda 2FA-ni yoqing.
|
||||
button-text: 2FA-ni sozlash
|
||||
comment-thread:
|
||||
publish-comment-button-text: Izohni chop etish
|
||||
publish-reply-button-text: Javobni chop etish
|
||||
reply-comment-button-text: Javob berish
|
||||
edit-comment-button-text: Tahrirlash
|
||||
delete-comment-button-text: O'chirish
|
||||
comment-published-ago: >-
|
||||
{createdAgo} oldin
|
||||
comment-input-placeholder: Yangi izoh kiriting...
|
||||
comment-reply-placeholder: Javobingizni kiriting...
|
||||
comment-input-empty-notification: Bo'sh izohlarni chop eta olmaysiz...
|
||||
comment-min-length-notification: Izoh kamida 2 ta belgidan iborat bo'lishi kerak
|
||||
dashboard:
|
||||
select-dashboard: Boshqaruv panelini tanlang
|
||||
create-new-dashboard: Yangi boshqaruv paneli yaratish
|
||||
create-a-new-dashboard: Yangi boshqaruv paneli yaratish
|
||||
confirm-create-dashboard: Boshqaruv panelini yaratish!
|
||||
edit-layout: Tartibni tahrirlash
|
||||
add-widgets: Vidjetlarni qo'shish
|
||||
delete-dashboard: Boshqaruv panelini o'chirish
|
||||
save-dashboard: Boshqaruv panelini saqlash
|
||||
you-cannot-delete-this-dashboard: Siz bu boshqaruv panelini o'chira olmaysiz.
|
||||
are-you-sure-you-want-delete-dashboard: Haqiqatan ham bu {dashboardName} ni o'chirmoqchimisiz?
|
||||
dashboard-widget-panel:
|
||||
widget-name: >-
|
||||
{widgetName} vidjeti
|
||||
select-widgets: Vidjetlarni tanlang
|
||||
close-and-save: Yopish va saqlash
|
||||
|
||||
services:
|
||||
dashboard-service:
|
||||
create-dashboard-success-notification: Yangi `{dashboardName}` boshqaruv paneli muvaffaqiyatli yaratildi.
|
||||
delete-dashboard-success-notification: >-
|
||||
`{dashboardName}` boshqaruv paneli o'chirildi.
|
||||
|
||||
auth:
|
||||
verification:
|
||||
header-title: Hisobni tekshirish
|
||||
title: Elektron pochta manzilingizni tasdiqlang
|
||||
message-text: <strong>Deyarli tayyor!</strong><br> Tasdiqlash kodi uchun elektron pochtangizni tekshiring.
|
||||
verification-code-text: Elektron pochta orqali olgan tasdiqlash kodini kiriting.
|
||||
verification-input-label: Tasdiqlash kodi
|
||||
verify-button-text: Tasdiqlash va davom etish
|
||||
didnt-receive-a-code: Hali kod olmadizmi?
|
||||
not-sent:
|
||||
message: Hali kod olmadizmi?
|
||||
alternative-choice: Hisobingizni tasdiqlash uchun quyidagi muqobil variantlardan foydalaning.
|
||||
resend-email: E-pochtani qayta yuborish
|
||||
send-by-sms: SMS orqali yuborish
|
||||
two-fa:
|
||||
verify-code:
|
||||
verification-code: Tasdiqlash kodi
|
||||
check-title: E-pochtangizni yoki telefoningizni tekshiring
|
||||
check-subtitle: Biz sizga tasdiqlash kodini yubordik. Kirish jarayonini yakunlash uchun quyidagi kodni kiriting.
|
||||
expired-help-text: Sizning 2FA autentifikatsiya kodingiz muddati tugagan. Agar ko'proq vaqt kerak bo'lsa, boshqa kod so'rashingiz mumkin.
|
||||
resend-code: Kodni qayta yuborish
|
||||
verify-code: Kodni tasdiqlash
|
||||
cancel-two-factor: Ikki faktorli autentifikatsiyani bekor qilish
|
||||
invalid-session-error-notification: Noto'g'ri sessiya. Iltimos, qaytadan urining.
|
||||
verification-successful-notification: Tasdiqlash muvaffaqiyatli!
|
||||
verification-code-expired-notification: Tasdiqlash kodi muddati tugagan. Iltimos, yangisini so'rang.
|
||||
verification-code-failed-notification: Tasdiqlash muvaffaqiyatsiz tugadi. Iltimos, qaytadan urining.
|
||||
resend-code:
|
||||
verification-code-resent-notification: Yangi tasdiqlash kodi yuborildi.
|
||||
verification-code-resent-error-notification: Tasdiqlash kodini qayta yuborishda xato. Iltimos, qaytadan urining.
|
||||
forgot-password:
|
||||
success-message: Davom etish uchun elektron pochtangizni tekshiring!
|
||||
is-sent:
|
||||
title: Deyarli tayyor!
|
||||
message: <strong>E-pochtangizni tekshiring!</strong><br> Biz sizga parolingizni tiklash imkonini beruvchi sehrli havolani elektron pochtangizga yubordik. Havola 15 daqiqadan so'ng tugaydi.
|
||||
not-sent:
|
||||
title: Parolingizni unutdingizmi?
|
||||
message: <strong>Xavotir olmang, biz sizga yordam beramiz.</strong><br> {appName} ga kirish uchun foydalanadigan elektron pochta manzilingizni kiriting va biz sizga parolingizni tiklash uchun xavfsiz havola yuboramiz.
|
||||
form:
|
||||
email-label: Sizning elektron pochta manzilingiz
|
||||
submit-button: OK, menga sehrli havola yuboring!
|
||||
nevermind-button: Hechqisi yo'q
|
||||
login:
|
||||
title: Hisobingizga kiring
|
||||
no-identity-notification: E-pochtangizni kiritishni unutdingizmi?
|
||||
no-password-notification: Parolingizni kiritishni unutdingizmi?
|
||||
unverified-notification: Davom etish uchun hisobingiz tasdiqlanishi kerak.
|
||||
password-reset-required: Davom etish uchun parolni tiklash talab qilinadi.
|
||||
failed-attempt:
|
||||
message: <strong>Parolingizni unutdingizmi?</strong><br> Parolingizni tiklash uchun quyidagi tugmani bosing.
|
||||
button-text: Ok, tiklashga yordam bering!
|
||||
form:
|
||||
email-label: Elektron pochta manzili
|
||||
password-label: Parol
|
||||
remember-me-label: Meni eslab qol
|
||||
forgot-password-label: Parolingizni unutdingizmi?
|
||||
sign-in-button: Kirish
|
||||
create-account-button: Yangi hisob yaratish
|
||||
slow-connection-message: Ulanish muammolari yuzaga kelmoqda.
|
||||
reset-password:
|
||||
success-message: Parolingiz tiklandi! Davom etish uchun tizimga kiring.
|
||||
invalid-verification-code: Bu parolni tiklash havolasi yaroqsiz yoki muddati o'tgan.
|
||||
title: Parolingizni tiklang
|
||||
form:
|
||||
code:
|
||||
label: Sizning tiklash kodingiz
|
||||
help-text: E-pochtangizda olgan tasdiqlash kodi.
|
||||
password:
|
||||
label: Yangi parol
|
||||
help-text: Davom etish uchun kamida 6 ta belgidan iborat parol kiriting.
|
||||
confirm-password:
|
||||
label: Yangi parolni tasdiqlang
|
||||
help-text: Davom etish uchun kamida 6 ta belgidan iborat parol kiriting.
|
||||
submit-button: Parolni tiklash
|
||||
back-button: Orqaga
|
||||
|
||||
console:
|
||||
create-or-join-organization:
|
||||
modal-title: Tashkilot yaratish yoki unga qo'shilish
|
||||
join-success-notification: Siz yangi tashkilotga qo'shildingiz!
|
||||
create-success-notification: Siz yangi tashkilot yaratdingiz!
|
||||
switch-organization:
|
||||
modal-title: Haqiqatan ham tashkilotni {organizationName} ga o'zgartirmoqchimisiz?
|
||||
modal-body: Tasdiqlaganingizdan so'ng hisobingiz tizimda qoladi, lekin asosiy tashkilotingiz o'zgaradi.
|
||||
modal-accept-button-text: Ha, men tashkilotni o'zgartirmoqchiman
|
||||
success-notification: Siz tashkilotlarni o'zgartirdingiz
|
||||
account:
|
||||
index:
|
||||
upload-new: Yangisini yuklash
|
||||
phone: Sizning telefon raqamingiz.
|
||||
photos: rasmlar
|
||||
timezone: Vaqt mintaqangizni tanlang.
|
||||
admin:
|
||||
menu:
|
||||
overview: Umumiy ko'rinish
|
||||
organizations: Tashkilotlar
|
||||
branding: Brending
|
||||
"2fa-config": 2FA konfiguratsiyasi
|
||||
schedule-monitor: Jadval monitori
|
||||
services: Xizmatlar
|
||||
mail: Pochta
|
||||
filesystem: Fayl tizimi
|
||||
queue: Navbat
|
||||
socket: Soket
|
||||
push-notifications: Push-bildirishnomalar
|
||||
schedule-monitor:
|
||||
schedule-monitor: Jadval monitori
|
||||
task-logs-for: >-
|
||||
Vazifalar jurnali uchun:
|
||||
showing-last-count: Oxirgi {count} ta jurnal ko'rsatilmoqda
|
||||
name: Nomi
|
||||
type: Turi
|
||||
timezone: Vaqt mintaqasi
|
||||
last-started: Oxirgi boshlangan
|
||||
last-finished: Oxirgi tugallangan
|
||||
last-failure: Oxirgi muvaffaqiyatsizlik
|
||||
date: Sana
|
||||
memory: Xotira
|
||||
runtime: Ish vaqti
|
||||
output: Chiqish
|
||||
no-output: Chiqish yo'q
|
||||
config:
|
||||
database:
|
||||
title: Ma'lumotlar bazasi konfiguratsiyasi
|
||||
filesystem:
|
||||
title: Fayl tizimi konfiguratsiyasi
|
||||
mail:
|
||||
title: Pochta konfiguratsiyasi
|
||||
notification-channels:
|
||||
title: Push-bildirishnomalar konfiguratsiyasi
|
||||
queue:
|
||||
title: Navbat konfiguratsiyasi
|
||||
services:
|
||||
title: Xizmatlar konfiguratsiyasi
|
||||
socket:
|
||||
title: Soket konfiguratsiyasi
|
||||
branding:
|
||||
title: Brending
|
||||
icon-text: Belgi
|
||||
upload-new: Yangisini yuklash
|
||||
reset-default: Standart holatga qaytarish
|
||||
logo-text: Logotip
|
||||
theme: Standart mavzu
|
||||
index:
|
||||
total-users: Jami foydalanuvchilar
|
||||
total-organizations: Jami tashkilotlar
|
||||
total-transactions: Jami tranzaksiyalar
|
||||
notifications:
|
||||
title: Bildirishnomalar
|
||||
notification-settings: Bildirishnomalar sozlamalari
|
||||
organizations:
|
||||
index:
|
||||
title: Tashkilotlar
|
||||
owner-name-column: Egasi
|
||||
owner-phone-column: Egasining telefoni
|
||||
owner-email-column: Egasining telefoni
|
||||
users-count-column: Foydalanuvchilar
|
||||
phone-column: Telefon
|
||||
email-column: Elektron pochta
|
||||
users:
|
||||
title: Foydalanuvchilar
|
||||
settings:
|
||||
index:
|
||||
title: Tashkilot sozlamalari
|
||||
organization-name: Tashkilot nomi
|
||||
organization-description: Tashkilot tavsifi
|
||||
organization-phone: Tashkilot telefon raqami
|
||||
organization-currency: Tashkilot valyutasi
|
||||
organization-id: Tashkilot IDsi
|
||||
organization-branding: Tashkilot brendingi
|
||||
logo: Logotip
|
||||
logo-help-text: Tashkilotingiz uchun logotip.
|
||||
upload-new-logo: Yangi logotip yuklash
|
||||
backdrop: Orqa fon
|
||||
backdrop-help-text: Tashkilotingiz uchun ixtiyoriy banner yoki fon rasmi.
|
||||
upload-new-backdrop: Yangi orqa fon yuklash
|
||||
organization-timezone: Tashkilotingiz uchun standart vaqt mintaqasini tanlang.
|
||||
select-timezone: Vaqt mintaqasini tanlang.
|
||||
extensions:
|
||||
title: Kengaytmalar tez kunda!
|
||||
message: Kengaytmalar ombori va bozorini ishga tushirishga tayyorgarlik ko'rayotganimiz sababli, kelgusi versiyalarda qayta tekshiring.
|
||||
notifications:
|
||||
select-all: Hammasini tanlash
|
||||
mark-as-read: O'qilgan deb belgilash
|
||||
received: >-
|
||||
Qabul qilindi:
|
||||
message: Ko'rsatish uchun bildirishnomalar yo'q.
|
||||
|
||||
invite:
|
||||
for-users:
|
||||
invitation-message: Sizni {companyName} ga qo'shilishga taklif qilishdi
|
||||
invitation-sent-message: Sizni {appName} dagi {companyName} tashkilotiga qo'shilishga taklif qilishdi. Ushbu taklifni qabul qilish uchun elektron pochta orqali olgan taklifnomangiz kodini kiriting va davom etish tugmasini bosing.
|
||||
invitation-code-sent-text: Sizning taklifnoma kodingiz
|
||||
accept-invitation-text: Taklifni qabul qilish
|
||||
|
||||
onboard:
|
||||
index:
|
||||
title: Hisobingizni yarating
|
||||
welcome-title: <strong>{companyName} ga xush kelibsiz!</strong><br />
|
||||
welcome-text: Boshlash uchun quyida talab qilinadigan ma'lumotlarni to'ldiring.
|
||||
full-name: To'liq ism
|
||||
full-name-help-text: Sizning to'liq ismingiz
|
||||
your-email: Elektron pochta manzili
|
||||
your-email-help-text: Sizning elektron pochta manzilingiz
|
||||
phone: Telefon raqami
|
||||
phone-help-text: Sizning telefon raqamingiz
|
||||
organization-name: Tashkilot nomi
|
||||
organization-help-text: Sizning tashkilotingiz nomi, barcha xizmatlaringiz va resurslaringiz ushbu tashkilot ostida boshqariladi, keyinchalik xohlagancha yoki kerak bo'lganda ko'plab tashkilotlar yaratishingiz mumkin.
|
||||
password: Parol kiriting
|
||||
password-help-text: Sizning parolingiz, yaxshi ekanligiga ishonch hosil qiling.
|
||||
confirm-password: Parolingizni tasdiqlang
|
||||
confirm-password-help-text: Yuqorida kiritgan parolingizni tasdiqlash uchun.
|
||||
continue-button-text: Davom etish
|
||||
verify-email:
|
||||
header-title: Hisobni tekshirish
|
||||
title: Elektron pochta manzilingizni tasdiqlang
|
||||
message-text: <strong>Deyarli tayyor!</strong><br> Tasdiqlash kodi uchun elektron pochtangizni tekshiring.
|
||||
verification-code-text: Elektron pochta orqali olgan tasdiqlash kodini kiriting.
|
||||
verification-input-label: Tasdiqlash kodi
|
||||
verify-button-text: Tasdiqlash va davom etish
|
||||
didnt-receive-a-code: Hali kod olmadizmi?
|
||||
not-sent:
|
||||
message: Hali kod olmadizmi?
|
||||
alternative-choice: Hisobingizni tasdiqlash uchun quyidagi muqobil variantlardan foydalaning.
|
||||
resend-email: E-pochtani qayta yuborish
|
||||
send-by-sms: SMS orqali yuborish
|
||||
|
||||
install:
|
||||
installer-header: O'rnatuvchi
|
||||
failed-message-sent: O'rnatish muvaffaqiyatsiz tugadi! O'rnatishni qayta urinish uchun quyidagi tugmani bosing.
|
||||
retry-install: Qayta o'rnatish
|
||||
start-install: O'rnatishni boshlash
|
||||
|
||||
layout:
|
||||
header:
|
||||
menus:
|
||||
organization:
|
||||
settings: Tashkilot sozlamalari
|
||||
create-or-join: Tashkilot yaratish yoki unga qo'shilish
|
||||
explore-extensions: Kengaytmalarni o'rganish
|
||||
user:
|
||||
view-profile: Profilni ko'rish
|
||||
keyboard-shortcuts: Klaviatura yorliqlarini ko'rsatish
|
||||
changelog: O'zgarishlar jurnali
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
# Base stage
|
||||
FROM dunglas/frankenphp:1.5.0-php8.2-bookworm AS base
|
||||
FROM dunglas/frankenphp:1.11-php8.2-bookworm AS base
|
||||
|
||||
# Install packages
|
||||
RUN apt-get update && apt-get install -y git bind9-utils mycli nodejs npm nano uuid-runtime \
|
||||
@@ -75,7 +75,7 @@ ENV QUEUE_CONNECTION=redis
|
||||
ENV CADDYFILE_PATH=/fleetbase/Caddyfile
|
||||
ENV CONSOLE_PATH=/fleetbase/console
|
||||
ENV OCTANE_SERVER=frankenphp
|
||||
ENV FLEETBASE_VERSION=0.7.20
|
||||
ENV FLEETBASE_VERSION=0.7.28
|
||||
|
||||
# Set environment
|
||||
ARG ENVIRONMENT=production
|
||||
@@ -158,14 +158,14 @@ CMD ["php", "artisan", "queue:work"]
|
||||
# Application dev stage
|
||||
FROM base AS app-dev
|
||||
ENTRYPOINT ["docker-php-entrypoint"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=250 --port=8000 --host=0.0.0.0 --watch"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=1000 --port=8000 --host=0.0.0.0 --watch"]
|
||||
|
||||
# Application release stage
|
||||
FROM base AS app-release
|
||||
ENTRYPOINT ["docker-php-entrypoint"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=250 --port=8000 --host=0.0.0.0"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=1000 --port=8000 --host=0.0.0.0"]
|
||||
|
||||
# Application stage
|
||||
FROM base AS app
|
||||
ENTRYPOINT ["/sbin/ssm-parent", "-c", ".ssm-parent.yaml", "run", "--", "docker-php-entrypoint"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=250 --port=8000 --host=0.0.0.0"]
|
||||
CMD ["sh", "-c", "php artisan octane:frankenphp --max-requests=1000 --port=8000 --host=0.0.0.0"]
|
||||
|
||||
Submodule packages/core-api updated: 831600c181...04ae1ac455
Submodule packages/dev-engine updated: b68cc9a0f3...64a379ce12
Submodule packages/ember-core updated: 8df352f5ff...54a45bbdaa
Submodule packages/ember-ui updated: d35cee3ae6...c6694206e9
Submodule packages/fleetbase-extensions-indexer updated: db26b8add3...a6f2b4cde9
Submodule packages/fleetops updated: 91cf968a25...09487c1c50
Submodule packages/fleetops-data updated: f20408d663...08520b3981
Submodule packages/iam-engine updated: 885f4abe81...99698152e2
Submodule packages/pallet updated: b343c621f1...9e7592eddb
Submodule packages/registry-bridge updated: 09215a4c73...c54b945dc1
Submodule packages/storefront updated: 3d6695e235...b5776acef6
@@ -125,7 +125,7 @@ docker compose up -d
|
||||
DB_SERVICE="database" # ← change if your docker‑compose uses a different name
|
||||
DB_WAIT_TIMEOUT=60 # seconds
|
||||
|
||||
echo "⏳ Waiting for “$DB_SERVICE” to become ready (timeout: ${DB_WAIT_TIMEOUT}s)…"
|
||||
echo "⏳ Waiting for $DB_SERVICE to become ready (timeout: ${DB_WAIT_TIMEOUT}s)..."
|
||||
DB_CONTAINER=$(docker compose ps -q "$DB_SERVICE")
|
||||
|
||||
if [ -z "$DB_CONTAINER" ]; then
|
||||
|
||||
Reference in New Issue
Block a user