Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e29aeb30f | ||
|
|
cca548dfbc | ||
|
|
3d4b7fad12 | ||
|
|
5ffb4bbedc | ||
|
|
d2e9f9ed65 | ||
|
|
c5898fbf74 | ||
|
|
0180dd3715 |
@@ -26,6 +26,7 @@
|
|||||||
"symfony/http-client": "8.0.*",
|
"symfony/http-client": "8.0.*",
|
||||||
"symfony/mcp-bundle": "^0.6.0",
|
"symfony/mcp-bundle": "^0.6.0",
|
||||||
"symfony/mime": "8.0.*",
|
"symfony/mime": "8.0.*",
|
||||||
|
"symfony/monolog-bundle": "^4.0",
|
||||||
"symfony/property-access": "8.0.*",
|
"symfony/property-access": "8.0.*",
|
||||||
"symfony/property-info": "8.0.*",
|
"symfony/property-info": "8.0.*",
|
||||||
"symfony/runtime": "8.0.*",
|
"symfony/runtime": "8.0.*",
|
||||||
|
|||||||
261
composer.lock
generated
261
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "3e2146f74bbda750c75ab52eb437d2d4",
|
"content-hash": "6fd67ba307d74fa0bcb9e6b9bf72f8bc",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "api-platform/doctrine-common",
|
"name": "api-platform/doctrine-common",
|
||||||
@@ -2625,6 +2625,109 @@
|
|||||||
},
|
},
|
||||||
"time": "2026-02-23T21:42:54+00:00"
|
"time": "2026-02-23T21:42:54+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "monolog/monolog",
|
||||||
|
"version": "3.10.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Seldaek/monolog.git",
|
||||||
|
"reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0",
|
||||||
|
"reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"psr/log": "^2.0 || ^3.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/log-implementation": "3.0.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"aws/aws-sdk-php": "^3.0",
|
||||||
|
"doctrine/couchdb": "~1.0@dev",
|
||||||
|
"elasticsearch/elasticsearch": "^7 || ^8",
|
||||||
|
"ext-json": "*",
|
||||||
|
"graylog2/gelf-php": "^1.4.2 || ^2.0",
|
||||||
|
"guzzlehttp/guzzle": "^7.4.5",
|
||||||
|
"guzzlehttp/psr7": "^2.2",
|
||||||
|
"mongodb/mongodb": "^1.8 || ^2.0",
|
||||||
|
"php-amqplib/php-amqplib": "~2.4 || ^3",
|
||||||
|
"php-console/php-console": "^3.1.8",
|
||||||
|
"phpstan/phpstan": "^2",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^2",
|
||||||
|
"phpstan/phpstan-strict-rules": "^2",
|
||||||
|
"phpunit/phpunit": "^10.5.17 || ^11.0.7",
|
||||||
|
"predis/predis": "^1.1 || ^2",
|
||||||
|
"rollbar/rollbar": "^4.0",
|
||||||
|
"ruflin/elastica": "^7 || ^8",
|
||||||
|
"symfony/mailer": "^5.4 || ^6",
|
||||||
|
"symfony/mime": "^5.4 || ^6"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
|
||||||
|
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
|
||||||
|
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
|
||||||
|
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
|
||||||
|
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
|
||||||
|
"ext-mbstring": "Allow to work properly with unicode symbols",
|
||||||
|
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
|
||||||
|
"ext-openssl": "Required to send log messages using SSL",
|
||||||
|
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
|
||||||
|
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
|
||||||
|
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
|
||||||
|
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
|
||||||
|
"rollbar/rollbar": "Allow sending log messages to Rollbar",
|
||||||
|
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Monolog\\": "src/Monolog"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jordi Boggiano",
|
||||||
|
"email": "j.boggiano@seld.be",
|
||||||
|
"homepage": "https://seld.be"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
|
||||||
|
"homepage": "https://github.com/Seldaek/monolog",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"logging",
|
||||||
|
"psr-3"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Seldaek/monolog/issues",
|
||||||
|
"source": "https://github.com/Seldaek/monolog/tree/3.10.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Seldaek",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-01-02T08:56:05+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "nelmio/cors-bundle",
|
"name": "nelmio/cors-bundle",
|
||||||
"version": "2.6.1",
|
"version": "2.6.1",
|
||||||
@@ -5789,6 +5892,162 @@
|
|||||||
],
|
],
|
||||||
"time": "2026-03-06T13:17:40+00:00"
|
"time": "2026-03-06T13:17:40+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/monolog-bridge",
|
||||||
|
"version": "v8.0.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/monolog-bridge.git",
|
||||||
|
"reference": "4dae5fe7f503c0e5ed304db684c3f0d95017e429"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/4dae5fe7f503c0e5ed304db684c3f0d95017e429",
|
||||||
|
"reference": "4dae5fe7f503c0e5ed304db684c3f0d95017e429",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"monolog/monolog": "^3",
|
||||||
|
"php": ">=8.4",
|
||||||
|
"symfony/http-kernel": "^7.4|^8.0",
|
||||||
|
"symfony/service-contracts": "^2.5|^3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/console": "^7.4|^8.0",
|
||||||
|
"symfony/http-client": "^7.4|^8.0",
|
||||||
|
"symfony/mailer": "^7.4|^8.0",
|
||||||
|
"symfony/messenger": "^7.4|^8.0",
|
||||||
|
"symfony/mime": "^7.4|^8.0",
|
||||||
|
"symfony/security-core": "^7.4|^8.0",
|
||||||
|
"symfony/var-dumper": "^7.4|^8.0"
|
||||||
|
},
|
||||||
|
"type": "symfony-bridge",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Bridge\\Monolog\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides integration for Monolog with various Symfony components",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/monolog-bridge/tree/v8.0.6"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-02-17T13:07:04+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/monolog-bundle",
|
||||||
|
"version": "v4.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/monolog-bundle.git",
|
||||||
|
"reference": "3b4ee2717ee56c5e1edb516140a175eb2a72bc66"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/3b4ee2717ee56c5e1edb516140a175eb2a72bc66",
|
||||||
|
"reference": "3b4ee2717ee56c5e1edb516140a175eb2a72bc66",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer-runtime-api": "^2.0",
|
||||||
|
"monolog/monolog": "^3.5",
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/config": "^7.3 || ^8.0",
|
||||||
|
"symfony/dependency-injection": "^7.3 || ^8.0",
|
||||||
|
"symfony/http-kernel": "^7.3 || ^8.0",
|
||||||
|
"symfony/monolog-bridge": "^7.3 || ^8.0",
|
||||||
|
"symfony/polyfill-php84": "^1.30"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^11.5.41 || ^12.3",
|
||||||
|
"symfony/console": "^7.3 || ^8.0",
|
||||||
|
"symfony/yaml": "^7.3 || ^8.0"
|
||||||
|
},
|
||||||
|
"type": "symfony-bundle",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Bundle\\MonologBundle\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony MonologBundle",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"logging"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/symfony/monolog-bundle/issues",
|
||||||
|
"source": "https://github.com/symfony/monolog-bundle/tree/v4.0.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-12-08T08:00:13+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/password-hasher",
|
"name": "symfony/password-hasher",
|
||||||
"version": "v8.0.6",
|
"version": "v8.0.6",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle;
|
|||||||
use Nelmio\CorsBundle\NelmioCorsBundle;
|
use Nelmio\CorsBundle\NelmioCorsBundle;
|
||||||
use Symfony\AI\McpBundle\McpBundle;
|
use Symfony\AI\McpBundle\McpBundle;
|
||||||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||||
|
use Symfony\Bundle\MonologBundle\MonologBundle;
|
||||||
use Symfony\Bundle\SecurityBundle\SecurityBundle;
|
use Symfony\Bundle\SecurityBundle\SecurityBundle;
|
||||||
use Symfony\Bundle\TwigBundle\TwigBundle;
|
use Symfony\Bundle\TwigBundle\TwigBundle;
|
||||||
|
|
||||||
@@ -24,4 +25,5 @@ return [
|
|||||||
DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
||||||
LexikJWTAuthenticationBundle::class => ['all' => true],
|
LexikJWTAuthenticationBundle::class => ['all' => true],
|
||||||
McpBundle::class => ['all' => true],
|
McpBundle::class => ['all' => true],
|
||||||
|
MonologBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ mcp:
|
|||||||
path: /_mcp
|
path: /_mcp
|
||||||
session:
|
session:
|
||||||
store: file
|
store: file
|
||||||
directory: '%kernel.cache_dir%/mcp-sessions'
|
directory: '%kernel.project_dir%/var/mcp-sessions'
|
||||||
ttl: 3600
|
ttl: 3600
|
||||||
|
|||||||
56
config/packages/monolog.yaml
Normal file
56
config/packages/monolog.yaml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
monolog:
|
||||||
|
channels:
|
||||||
|
- deprecation
|
||||||
|
|
||||||
|
when@dev:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: rotating_file
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
max_files: 7
|
||||||
|
channels: ["!event"]
|
||||||
|
console:
|
||||||
|
type: console
|
||||||
|
process_psr_3_messages: false
|
||||||
|
channels: ["!event", "!doctrine", "!console"]
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: fingers_crossed
|
||||||
|
action_level: error
|
||||||
|
handler: nested
|
||||||
|
excluded_http_codes: [404, 405]
|
||||||
|
channels: ["!event"]
|
||||||
|
nested:
|
||||||
|
type: stream
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
|
||||||
|
when@prod:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: fingers_crossed
|
||||||
|
action_level: error
|
||||||
|
handler: nested
|
||||||
|
excluded_http_codes: [404, 405]
|
||||||
|
channels: ["!deprecation"]
|
||||||
|
buffer_size: 50
|
||||||
|
nested:
|
||||||
|
type: rotating_file
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
max_files: 30
|
||||||
|
console:
|
||||||
|
type: console
|
||||||
|
process_psr_3_messages: false
|
||||||
|
channels: ["!event", "!doctrine"]
|
||||||
|
deprecation:
|
||||||
|
type: rotating_file
|
||||||
|
channels: [deprecation]
|
||||||
|
path: "%kernel.logs_dir%/deprecations.log"
|
||||||
|
max_files: 7
|
||||||
@@ -59,6 +59,7 @@ security:
|
|||||||
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
||||||
# Version de l'application en public
|
# Version de l'application en public
|
||||||
- { path: ^/api/version, roles: PUBLIC_ACCESS, methods: [ GET ] }
|
- { path: ^/api/version, roles: PUBLIC_ACCESS, methods: [ GET ] }
|
||||||
|
- { path: ^/_mcp, roles: PUBLIC_ACCESS, methods: [ GET ] }
|
||||||
- { path: ^/_mcp, roles: IS_AUTHENTICATED_FULLY }
|
- { path: ^/_mcp, roles: IS_AUTHENTICATED_FULLY }
|
||||||
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.2.0'
|
app.version: '0.2.6'
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ sudo chmod 600 /etc/lesstime-release-token
|
|||||||
## 5. Deployer une release
|
## 5. Deployer une release
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /usr/local/bin/deploy-lesstime v0.2.0
|
sudo /usr/local/bin/deploy-lesstime v0.2.1
|
||||||
```
|
```
|
||||||
|
|
||||||
Le script telecharge l'artefact, extrait les fichiers, clear le cache et lance les migrations.
|
Le script telecharge l'artefact, extrait les fichiers, clear le cache et lance les migrations.
|
||||||
|
|||||||
@@ -65,6 +65,20 @@
|
|||||||
@blur="touched.title = true"
|
@blur="touched.title = true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Project select (create mode with project list) -->
|
||||||
|
<div v-if="showProjectSelect" class="mt-4">
|
||||||
|
<MalioSelect
|
||||||
|
v-model="form.projectId"
|
||||||
|
:options="projectOptions"
|
||||||
|
label="Projet *"
|
||||||
|
empty-option-label="Sélectionner un projet"
|
||||||
|
min-width="w-full"
|
||||||
|
/>
|
||||||
|
<p v-if="touched.project && !form.projectId" class="mt-1 text-xs text-red-500">
|
||||||
|
Le projet est requis
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Two-column selects -->
|
<!-- Two-column selects -->
|
||||||
<div class="mt-4 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2">
|
<div class="mt-4 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2">
|
||||||
<MalioSelect
|
<MalioSelect
|
||||||
@@ -266,6 +280,8 @@ import type { TaskGroup } from '~/services/dto/task-group'
|
|||||||
import type { UserData } from '~/services/dto/user-data'
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import { useTaskService } from '~/services/tasks'
|
import { useTaskService } from '~/services/tasks'
|
||||||
|
|
||||||
|
import type { Project } from '~/services/dto/project'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: boolean
|
modelValue: boolean
|
||||||
task: Task | null
|
task: Task | null
|
||||||
@@ -276,6 +292,7 @@ const props = defineProps<{
|
|||||||
tags: TaskTag[]
|
tags: TaskTag[]
|
||||||
groups: TaskGroup[]
|
groups: TaskGroup[]
|
||||||
users: UserData[]
|
users: UserData[]
|
||||||
|
projects?: Project[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -318,10 +335,12 @@ const form = reactive({
|
|||||||
groupId: null as number | null,
|
groupId: null as number | null,
|
||||||
tagIds: [] as number[],
|
tagIds: [] as number[],
|
||||||
clientTicketId: null as number | null,
|
clientTicketId: null as number | null,
|
||||||
|
projectId: null as number | null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const touched = reactive({
|
const touched = reactive({
|
||||||
title: false,
|
title: false,
|
||||||
|
project: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const statusOptions = computed(() =>
|
const statusOptions = computed(() =>
|
||||||
@@ -340,8 +359,22 @@ const userOptions = computed(() =>
|
|||||||
props.users.map(u => ({ label: u.username, value: u.id }))
|
props.users.map(u => ({ label: u.username, value: u.id }))
|
||||||
)
|
)
|
||||||
|
|
||||||
const groupOptions = computed(() =>
|
const groupOptions = computed(() => {
|
||||||
props.groups.map(g => ({ label: g.title, value: g.id }))
|
let filtered = props.groups
|
||||||
|
if (showProjectSelect.value && form.projectId) {
|
||||||
|
filtered = filtered.filter(g => g.project?.id === form.projectId)
|
||||||
|
}
|
||||||
|
return filtered.map(g => ({ label: g.title, value: g.id }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const showProjectSelect = computed(() => !!props.projects?.length && !isEditing.value)
|
||||||
|
|
||||||
|
const projectOptions = computed(() =>
|
||||||
|
(props.projects ?? []).map(p => ({ label: p.name, value: p.id }))
|
||||||
|
)
|
||||||
|
|
||||||
|
const resolvedProjectId = computed(() =>
|
||||||
|
showProjectSelect.value ? form.projectId : props.projectId
|
||||||
)
|
)
|
||||||
|
|
||||||
const canArchive = computed(() => {
|
const canArchive = computed(() => {
|
||||||
@@ -385,8 +418,10 @@ function populateForm(task: Task | null) {
|
|||||||
form.groupId = null
|
form.groupId = null
|
||||||
form.tagIds = []
|
form.tagIds = []
|
||||||
form.clientTicketId = null
|
form.clientTicketId = null
|
||||||
|
form.projectId = null
|
||||||
}
|
}
|
||||||
touched.title = false
|
touched.title = false
|
||||||
|
touched.project = false
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.modelValue, async (open) => {
|
watch(() => props.modelValue, async (open) => {
|
||||||
@@ -394,9 +429,14 @@ watch(() => props.modelValue, async (open) => {
|
|||||||
confirmDeleteDocOpen.value = false
|
confirmDeleteDocOpen.value = false
|
||||||
documentToDelete.value = null
|
documentToDelete.value = null
|
||||||
populateForm(props.task)
|
populateForm(props.task)
|
||||||
try {
|
const pid = resolvedProjectId.value
|
||||||
clientTickets.value = await clientTicketService.getAll({ project: props.projectId })
|
if (pid) {
|
||||||
} catch {
|
try {
|
||||||
|
clientTickets.value = await clientTicketService.getAll({ project: pid })
|
||||||
|
} catch {
|
||||||
|
clientTickets.value = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
clientTickets.value = []
|
clientTickets.value = []
|
||||||
}
|
}
|
||||||
if (props.task?.project?.giteaOwner && props.task?.project?.giteaRepo && !giteaUrl.value) {
|
if (props.task?.project?.giteaOwner && props.task?.project?.giteaRepo && !giteaUrl.value) {
|
||||||
@@ -426,6 +466,22 @@ const clientTicketOptions = computed(() =>
|
|||||||
clientTickets.value.map(ct => ({ label: `CT-${String(ct.number).padStart(3, '0')} — ${ct.title}`, value: ct.id }))
|
clientTickets.value.map(ct => ({ label: `CT-${String(ct.number).padStart(3, '0')} — ${ct.title}`, value: ct.id }))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Reset group and reload client tickets when project changes in create mode
|
||||||
|
watch(() => form.projectId, async (pid) => {
|
||||||
|
if (!showProjectSelect.value) return
|
||||||
|
form.groupId = null
|
||||||
|
form.clientTicketId = null
|
||||||
|
if (pid) {
|
||||||
|
try {
|
||||||
|
clientTickets.value = await clientTicketService.getAll({ project: pid })
|
||||||
|
} catch {
|
||||||
|
clientTickets.value = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clientTickets.value = []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const isAdmin = computed(() => authStore.user?.roles?.includes('ROLE_ADMIN') ?? false)
|
const isAdmin = computed(() => authStore.user?.roles?.includes('ROLE_ADMIN') ?? false)
|
||||||
|
|
||||||
@@ -541,7 +597,9 @@ async function handleUnarchive() {
|
|||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
touched.title = true
|
touched.title = true
|
||||||
|
touched.project = true
|
||||||
if (!form.title.trim()) return
|
if (!form.title.trim()) return
|
||||||
|
if (showProjectSelect.value && !form.projectId) return
|
||||||
|
|
||||||
isSubmitting.value = true
|
isSubmitting.value = true
|
||||||
try {
|
try {
|
||||||
@@ -553,7 +611,7 @@ async function handleSubmit() {
|
|||||||
priority: form.priorityId ? `/api/task_priorities/${form.priorityId}` : null,
|
priority: form.priorityId ? `/api/task_priorities/${form.priorityId}` : null,
|
||||||
assignee: form.assigneeId ? `/api/users/${form.assigneeId}` : null,
|
assignee: form.assigneeId ? `/api/users/${form.assigneeId}` : null,
|
||||||
group: form.groupId ? `/api/task_groups/${form.groupId}` : null,
|
group: form.groupId ? `/api/task_groups/${form.groupId}` : null,
|
||||||
project: `/api/projects/${props.projectId}`,
|
project: `/api/projects/${resolvedProjectId.value}`,
|
||||||
tags: form.tagIds.map(id => `/api/task_tags/${id}`),
|
tags: form.tagIds.map(id => `/api/task_tags/${id}`),
|
||||||
clientTicket: form.clientTicketId ? `/api/client_tickets/${form.clientTicketId}` : null,
|
clientTicket: form.clientTicketId ? `/api/client_tickets/${form.clientTicketId}` : null,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,8 @@
|
|||||||
"allEfforts": "Tous les efforts",
|
"allEfforts": "Tous les efforts",
|
||||||
"allAssignees": "Tous",
|
"allAssignees": "Tous",
|
||||||
"noTasks": "Aucune tâche",
|
"noTasks": "Aucune tâche",
|
||||||
"backlog": "Backlog"
|
"backlog": "Backlog",
|
||||||
|
"createTask": "Créer une tâche"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Tableau de bord",
|
"title": "Tableau de bord",
|
||||||
|
|||||||
@@ -123,9 +123,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="h-full flex-1 flex flex-col min-h-0">
|
<div class="h-full flex-1 flex flex-col min-h-0 min-w-0">
|
||||||
<AppTopNav :user="auth.user" />
|
<AppTopNav :user="auth.user" />
|
||||||
<main class="flex flex-1 flex-col overflow-y-auto bg-white px-4 pb-24 sm:px-8 lg:px-16">
|
<main class="flex flex-1 flex-col overflow-y-auto overflow-x-hidden bg-white px-4 pb-24 sm:px-8 lg:px-16">
|
||||||
<div aria-hidden="true" class="pointer-events-none sticky top-0 z-30 h-8 flex-shrink-0 bg-white sm:h-12" />
|
<div aria-hidden="true" class="pointer-events-none sticky top-0 z-30 h-8 flex-shrink-0 bg-white sm:h-12" />
|
||||||
<slot/>
|
<slot/>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -214,6 +214,11 @@ async function onDropBacklog(event: DragEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Modal
|
// Modal
|
||||||
|
function openTaskCreate() {
|
||||||
|
selectedTask.value = null
|
||||||
|
taskModalOpen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
function openTaskEdit(task: Task) {
|
function openTaskEdit(task: Task) {
|
||||||
selectedTask.value = task
|
selectedTask.value = task
|
||||||
taskModalOpen.value = true
|
taskModalOpen.value = true
|
||||||
@@ -229,28 +234,37 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="min-w-0">
|
||||||
<!-- Header + Filters -->
|
<!-- Header + Filters -->
|
||||||
<div class="sticky top-8 z-20 bg-white pb-4 sm:top-12">
|
<div class="sticky top-8 z-20 bg-white pb-4 sm:top-12">
|
||||||
<div class="flex items-center justify-between gap-3">
|
<div class="flex items-center justify-between gap-3">
|
||||||
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('myTasks.title') }}</h1>
|
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('myTasks.title') }}</h1>
|
||||||
<div class="flex gap-1">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
class="flex items-center justify-center rounded-md p-1.5 transition-colors"
|
class="flex items-center gap-1.5 rounded-lg bg-primary-500 px-3 py-1.5 text-sm font-semibold text-white transition-colors hover:bg-secondary-500"
|
||||||
:class="viewMode === 'kanban' ? 'bg-primary-500 text-white' : 'text-neutral-400 hover:text-primary-500'"
|
@click="openTaskCreate"
|
||||||
:title="$t('myTasks.viewKanban')"
|
|
||||||
@click="viewMode = 'kanban'"
|
|
||||||
>
|
>
|
||||||
<Icon name="mdi:view-column-outline" size="18" />
|
<Icon name="mdi:plus" size="18" />
|
||||||
</button>
|
{{ $t('myTasks.createTask') }}
|
||||||
<button
|
|
||||||
class="flex items-center justify-center rounded-md p-1.5 transition-colors"
|
|
||||||
:class="viewMode === 'list' ? 'bg-primary-500 text-white' : 'text-neutral-400 hover:text-primary-500'"
|
|
||||||
:title="$t('myTasks.viewList')"
|
|
||||||
@click="viewMode = 'list'"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:view-list-outline" size="18" />
|
|
||||||
</button>
|
</button>
|
||||||
|
<div class="flex gap-1">
|
||||||
|
<button
|
||||||
|
class="flex items-center justify-center rounded-md p-1.5 transition-colors"
|
||||||
|
:class="viewMode === 'kanban' ? 'bg-primary-500 text-white' : 'text-neutral-400 hover:text-primary-500'"
|
||||||
|
:title="$t('myTasks.viewKanban')"
|
||||||
|
@click="viewMode = 'kanban'"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:view-column-outline" size="18" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="flex items-center justify-center rounded-md p-1.5 transition-colors"
|
||||||
|
:class="viewMode === 'list' ? 'bg-primary-500 text-white' : 'text-neutral-400 hover:text-primary-500'"
|
||||||
|
:title="$t('myTasks.viewList')"
|
||||||
|
@click="viewMode = 'list'"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:view-list-outline" size="18" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -314,11 +328,11 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- Kanban View -->
|
<!-- Kanban View -->
|
||||||
<div v-if="viewMode === 'kanban'">
|
<div v-if="viewMode === 'kanban'">
|
||||||
<div class="mt-6 flex gap-4 overflow-x-auto pb-4">
|
<div class="mt-6 flex gap-3 overflow-x-auto pb-4">
|
||||||
<div
|
<div
|
||||||
v-for="status in sortedStatuses"
|
v-for="status in sortedStatuses"
|
||||||
:key="status.id"
|
:key="status.id"
|
||||||
class="flex w-72 shrink-0 flex-col rounded-lg transition-colors"
|
class="flex min-w-36 flex-1 flex-col rounded-lg transition-colors"
|
||||||
:class="dragOverStatusId === status.id ? 'bg-neutral-200' : 'bg-neutral-50'"
|
:class="dragOverStatusId === status.id ? 'bg-neutral-200' : 'bg-neutral-50'"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@dragenter.prevent="onDragEnter(status.id)"
|
@dragenter.prevent="onDragEnter(status.id)"
|
||||||
@@ -446,6 +460,7 @@ onMounted(() => {
|
|||||||
:tags="tags"
|
:tags="tags"
|
||||||
:groups="selectedTask?.project?.id ? groups.filter(g => g.project?.id === selectedTask?.project?.id) : groups"
|
:groups="selectedTask?.project?.id ? groups.filter(g => g.project?.id === selectedTask?.project?.id) : groups"
|
||||||
:users="users"
|
:users="users"
|
||||||
|
:projects="projects"
|
||||||
@saved="onSaved"
|
@saved="onSaved"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="min-w-0">
|
||||||
<div class="sticky top-8 z-20 bg-white pb-4 sm:top-12">
|
<div class="sticky top-8 z-20 bg-white pb-4 sm:top-12">
|
||||||
<div class="flex items-center justify-between gap-3">
|
<div class="flex items-center justify-between gap-3">
|
||||||
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ project?.name ?? '' }}</h1>
|
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ project?.name ?? '' }}</h1>
|
||||||
@@ -62,11 +62,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Kanban -->
|
<!-- Kanban -->
|
||||||
<div class="mt-6 flex gap-4 overflow-x-auto pb-4">
|
<div class="mt-6 flex gap-3 overflow-x-auto pb-4">
|
||||||
<div
|
<div
|
||||||
v-for="status in statuses"
|
v-for="status in statuses"
|
||||||
:key="status.id"
|
:key="status.id"
|
||||||
class="flex w-72 shrink-0 flex-col rounded-lg transition-colors"
|
class="flex min-w-36 flex-1 flex-col rounded-lg transition-colors"
|
||||||
:class="dragOverStatusId === status.id ? 'bg-neutral-200' : 'bg-neutral-50'"
|
:class="dragOverStatusId === status.id ? 'bg-neutral-200' : 'bg-neutral-50'"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@dragenter.prevent="onDragEnter(status.id)"
|
@dragenter.prevent="onDragEnter(status.id)"
|
||||||
|
|||||||
@@ -80,6 +80,11 @@ fi
|
|||||||
|
|
||||||
echo "Release ${TAG} deployed to ${DEPLOY_DIR}"
|
echo "Release ${TAG} deployed to ${DEPLOY_DIR}"
|
||||||
|
|
||||||
|
# Ensure var/log exists and is writable by PHP (www-data)
|
||||||
|
mkdir -p "${DEPLOY_DIR}/var/log"
|
||||||
|
chown www-data:www-data "${DEPLOY_DIR}/var/log"
|
||||||
|
chmod 775 "${DEPLOY_DIR}/var/log"
|
||||||
|
|
||||||
if [ -f "${DEPLOY_DIR}/.env.local" ]; then
|
if [ -f "${DEPLOY_DIR}/.env.local" ]; then
|
||||||
echo "Clearing cache..."
|
echo "Clearing cache..."
|
||||||
php "${DEPLOY_DIR}/bin/console" cache:clear --env=prod --no-debug
|
php "${DEPLOY_DIR}/bin/console" cache:clear --env=prod --no-debug
|
||||||
|
|||||||
12
symfony.lock
12
symfony.lock
@@ -172,6 +172,18 @@
|
|||||||
"symfony/mcp-bundle": {
|
"symfony/mcp-bundle": {
|
||||||
"version": "v0.6.0"
|
"version": "v0.6.0"
|
||||||
},
|
},
|
||||||
|
"symfony/monolog-bundle": {
|
||||||
|
"version": "4.0",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "3.7",
|
||||||
|
"ref": "1b9efb10c54cb51c713a9391c9300ff8bceda459"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/monolog.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/property-info": {
|
"symfony/property-info": {
|
||||||
"version": "8.0",
|
"version": "8.0",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|||||||
Reference in New Issue
Block a user